diff --git a/gnome/src/math.js b/gnome/src/math.js index 497274e..f1c9983 100644 --- a/gnome/src/math.js +++ b/gnome/src/math.js @@ -1,3 +1,20 @@ export function degreeToRadian(degree) { return degree * Math.PI / 180; +} + +// FOV in radians is spherical, so doesn't follow Pythagoras' theorem +export function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) { + // first convert from a spherical FOV to a diagonal FOV on a flat plane at a generic distance of 1.0 + const flatDiagonalFOV = 2 * Math.tan(diagonalFOVRadians / 2); + + // then convert to flat plane horizontal and vertical FOVs + const flatVerticalFOV = flatDiagonalFOV / Math.sqrt(1 + aspectRatio * aspectRatio); + const flatHorizontalFOV = flatVerticalFOV * aspectRatio; + + // then convert back to spherical FOV + return { + diagonal: diagonalFOVRadians, + horizontal: 2 * Math.atan(Math.tan(flatHorizontalFOV / 2)), + vertical: 2 * Math.atan(Math.tan(flatVerticalFOV / 2)) + } } \ No newline at end of file diff --git a/gnome/src/virtualdisplayeffect.js b/gnome/src/virtualdisplayeffect.js index 0a86435..d565a9d 100644 --- a/gnome/src/virtualdisplayeffect.js +++ b/gnome/src/virtualdisplayeffect.js @@ -4,6 +4,7 @@ import GObject from 'gi://GObject'; import Shell from 'gi://Shell'; import Globals from './globals.js'; +import { degreeToRadian, diagonalToCrossFOVs } from './math.js'; // how far to look ahead is how old the IMU data is plus a constant that is either the default for this device or an override function lookAheadMS(imuDateMs, lookAheadCfg, override) { @@ -236,8 +237,7 @@ export const VirtualDisplayEffect = GObject.registerClass({ this.set_uniform_float(this.get_uniform_location("u_show_banner"), 1, [this.show_banner ? 1.0 : 0.0]); } - perspective(fovVerticalRadians, aspect, near, far) { - const fovHorizontalRadians = fovVerticalRadians * aspect; + perspective(fovHorizontalRadians, aspect, near, far) { const f = 1.0 / Math.tan(fovHorizontalRadians / 2.0); const range = far - near; @@ -383,17 +383,15 @@ export const VirtualDisplayEffect = GObject.registerClass({ vfunc_paint_target(node, paintContext) { if (!this._initialized) { const aspect = this.target_monitor.width / this.target_monitor.height; - const fovDiagonalRadians = Globals.data_stream.device_data.displayFov * Math.PI / 180.0; - const diagToVertRatio = Math.sqrt(aspect * aspect + 1); - const fovVerticalRadians = fovDiagonalRadians / diagToVertRatio; + const fovRadians = diagonalToCrossFOVs(degreeToRadian(Globals.data_stream.device_data.displayFov), aspect); const projection_matrix = this.perspective( - fovVerticalRadians, + fovRadians.horizontal, aspect, 0.0001, 1000.0 ); this.set_uniform_matrix(this.get_uniform_location("u_projection_matrix"), false, 4, projection_matrix); - this.set_uniform_float(this.get_uniform_location("u_fov_vertical_radians"), 1, [fovVerticalRadians]); + this.set_uniform_float(this.get_uniform_location("u_fov_vertical_radians"), 1, [fovRadians.vertical]); this.set_uniform_float(this.get_uniform_location("u_display_resolution"), 2, [this.target_monitor.width, this.target_monitor.height]); this.set_uniform_float(this.get_uniform_location("u_look_ahead_cfg"), 4, Globals.data_stream.device_data.lookAheadCfg); this.set_uniform_float(this.get_uniform_location("u_actor_to_display_ratios"), 2, this.actor_to_display_ratios); diff --git a/gnome/src/virtualdisplaysactor.js b/gnome/src/virtualdisplaysactor.js index 0cf4c6d..b93113f 100644 --- a/gnome/src/virtualdisplaysactor.js +++ b/gnome/src/virtualdisplaysactor.js @@ -8,6 +8,7 @@ import Shell from 'gi://Shell'; import St from 'gi://St'; import { VirtualDisplayEffect } from './virtualdisplayeffect.js'; +import { degreeToRadian, diagonalToCrossFOVs } from './math.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; @@ -84,16 +85,16 @@ function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, foc )) ); - const distancePixels = fovDetails.completeScreenDistance * Math.tan(distance); - const distanceToMonitorSize = distancePixels / monitor.width; + const distanceFromCenterPixels = fovDetails.completeScreenDistancePixels * Math.tan(distance); + const distanceFromCenterSizeRatio = distanceFromCenterPixels / monitor.width; if (currentFocusedIndex === index) { - currentFocusedDistance = distanceToMonitorSize * focusedMonitorDistance; + currentFocusedDistance = distanceFromCenterSizeRatio * focusedMonitorDistance; } - if (distanceToMonitorSize < closestDistance) { + if (distanceFromCenterSizeRatio < closestDistance) { closestIndex = index; - closestDistance = distanceToMonitorSize; + closestDistance = distanceFromCenterSizeRatio; } }); @@ -108,10 +109,6 @@ function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, foc return currentFocusedIndex; } -function degreesToRadians(degrees) { - return degrees * Math.PI / 180.0; -} - /*** * @returns {Object} - containing `begin`, `center`, and `end` radians for rotating the given monitor */ @@ -195,18 +192,19 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch // monitors wrap around us horizontally // distance to a horizontal edge is the hypothenuse of the triangle where the opposite side is half the width of the reference fov screen - const sideEdgeRadius = Math.sqrt(Math.pow(fovDetails.widthPixels / 2, 2) + Math.pow(fovDetails.completeScreenDistance, 2)); + const sideEdgeRadius = Math.sqrt(Math.pow(fovDetails.widthPixels / 2, 2) + Math.pow(fovDetails.completeScreenDistancePixels, 2)); const monitorSpacingPixels = monitorSpacing * fovDetails.widthPixels; - cachedMonitorRadians[0] = -fovDetails.horizontalRadians / 2; + cachedMonitorRadians[0] = -fovDetails.defaultDistanceHorizontalRadians / 2; monitorDetailsList.forEach(monitorDetails => { const monitorWrapDetails = monitorWrap(cachedMonitorRadians, sideEdgeRadius, monitorSpacingPixels, monitorDetails.x, monitorDetails.width); + const monitorCenterRadius = Math.sqrt(Math.pow(sideEdgeRadius, 2) - Math.pow(monitorDetails.width / 2, 2)); const upTopPixels = monitorDetails.y + (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels; const upCenterPixels = upTopPixels + monitorDetails.height / 2 - fovDetails.heightPixels / 2; monitorPlacements.push({ topLeftNoRotate: [ - fovDetails.completeScreenDistance, + monitorCenterRadius, // west stays aligned with (0, 0), will apply rotationAngleRadians value during rendering -(monitorDetails.width - fovDetails.widthPixels) / 2, @@ -215,7 +213,7 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch -upTopPixels ], centerNoRotate: [ - fovDetails.completeScreenDistance, + monitorCenterRadius, // west centered about the FOV center 0, @@ -225,10 +223,10 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch ], center: [ // north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians - fovDetails.completeScreenDistance * Math.cos(monitorWrapDetails.center), + monitorCenterRadius * Math.cos(monitorWrapDetails.center), // west is opposite where radius is the hypotenuse, using monitorWrapDetails.center as the radians - -fovDetails.completeScreenDistance * Math.sin(monitorWrapDetails.center), + -monitorCenterRadius * Math.sin(monitorWrapDetails.center), // up is flat when wrapping horizontally -upCenterPixels @@ -243,18 +241,19 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch // monitors wrap around us vertically // distance to the top edge is the hypothenuse of the triangle where the opposite side is half the height of the reference fov screen - const topEdgeRadius = Math.sqrt(Math.pow(fovDetails.heightPixels / 2, 2) + Math.pow(fovDetails.completeScreenDistance, 2)); + const topEdgeRadius = Math.sqrt(Math.pow(fovDetails.heightPixels / 2, 2) + Math.pow(fovDetails.completeScreenDistancePixels, 2)); const monitorSpacingPixels = monitorSpacing * fovDetails.heightPixels; - cachedMonitorRadians[0] = -fovDetails.verticalRadians / 2; + cachedMonitorRadians[0] = -fovDetails.defaultDistanceVerticalRadians / 2; monitorDetailsList.forEach(monitorDetails => { const monitorWrapDetails = monitorWrap(cachedMonitorRadians, topEdgeRadius, monitorSpacingPixels, monitorDetails.y, monitorDetails.height); + const monitorCenterRadius = Math.sqrt(Math.pow(topEdgeRadius, 2) - Math.pow(monitorDetails.height / 2, 2)); const westPixels = monitorDetails.x + (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels; const westCenterPixels = westPixels + monitorDetails.width / 2 - fovDetails.widthPixels / 2; monitorPlacements.push({ topLeftNoRotate: [ - fovDetails.completeScreenDistance, + monitorCenterRadius, // west is flat when wrapping vertically, apply it here as a constant, not touched by rendering westPixels, @@ -263,7 +262,7 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch (monitorDetails.height - fovDetails.heightPixels) / 2 ], centerNoRotate: [ - fovDetails.completeScreenDistance, + monitorCenterRadius, // west is flat when wrapping horizontally westCenterPixels, @@ -273,13 +272,13 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch ], center: [ // north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians - fovDetails.completeScreenDistance * Math.cos(monitorWrapDetails.center), + monitorCenterRadius * Math.cos(monitorWrapDetails.center), // west is flat when wrapping vertically -westCenterPixels, // up is opposite where radius is the hypotenuse, using monitorWrapDetails.center as the radians - -fovDetails.completeScreenDistance * Math.sin(monitorWrapDetails.center) + -monitorCenterRadius * Math.sin(monitorWrapDetails.center) ], rotationAngleRadians: { x: -monitorWrapDetails.center, @@ -298,17 +297,17 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch const upCenterPixels = upPixels + monitorDetails.height / 2 - fovDetails.heightPixels / 2; monitorPlacements.push({ topLeftNoRotate: [ - fovDetails.completeScreenDistance, + fovDetails.completeScreenDistancePixels, westPixels, -upPixels ], centerNoRotate: [ - fovDetails.completeScreenDistance, + fovDetails.completeScreenDistancePixels, westCenterPixels, -upCenterPixels ], center: [ - fovDetails.completeScreenDistance, + fovDetails.completeScreenDistancePixels, -westCenterPixels, -upCenterPixels ], @@ -690,24 +689,25 @@ export const VirtualDisplaysActor = GObject.registerClass({ _fov_details() { const aspect = this.target_monitor.width / this.target_monitor.height; - const fovVerticalRadiansFullScreen = degreesToRadians(Globals.data_stream.device_data.displayFov / Math.sqrt(1 + aspect * aspect)); - const fovVerticalRadians = Math.atan(Math.tan(fovVerticalRadiansFullScreen) / this._display_distance_default()); + const fovRadians = diagonalToCrossFOVs(degreeToRadian(Globals.data_stream.device_data.displayFov), aspect); + const defaultDistanceVerticalRadians = 2 * Math.atan(Math.tan(fovRadians.vertical / 2) / this._display_distance_default()); + const defaultDistanceHorizontalRadians = 2 * Math.atan(Math.tan(fovRadians.horizontal / 2) / this._display_distance_default()); // distance needed for the FOV-sized monitor to fill up the screen - const fullScreenDistance = this.target_monitor.height / 2 / Math.tan(fovVerticalRadiansFullScreen / 2); - const lensDistance = fullScreenDistance / (1.0 - Globals.data_stream.device_data.lensDistanceRatio) - fullScreenDistance; + const fullScreenDistance = this.target_monitor.height / 2 / Math.tan(fovRadians.vertical / 2); + const lensDistancePixels = fullScreenDistance / (1.0 - Globals.data_stream.device_data.lensDistanceRatio) - fullScreenDistance; // distance of a display at the default (most zoomed out) distance, plus the lens distance constant - const lensToScreenDistance = this.target_monitor.height / 2 / Math.tan(fovVerticalRadians / 2); - const completeScreenDistance = lensToScreenDistance + lensDistance; + const lensToScreenDistance = this.target_monitor.height / 2 / Math.tan(defaultDistanceVerticalRadians / 2); + const completeScreenDistancePixels = lensToScreenDistance + lensDistancePixels; return { widthPixels: this.target_monitor.width, heightPixels: this.target_monitor.height, - verticalRadians: fovVerticalRadians, - horizontalRadians: fovVerticalRadians * aspect, - lensDistance, - completeScreenDistance + defaultDistanceVerticalRadians, + defaultDistanceHorizontalRadians, + lensDistancePixels, + completeScreenDistancePixels }; } @@ -756,7 +756,7 @@ export const VirtualDisplaysActor = GObject.registerClass({ this._horizontal_monitor_sort(); const fovDetails = this._fov_details(); - this.lens_vector = [0.0, 0.0, -fovDetails.lensDistance]; + this.lens_vector = [0.0, 0.0, -fovDetails.lensDistancePixels]; this.monitor_placements = monitorsToPlacements( fovDetails, diff --git a/modules/XRLinuxDriver b/modules/XRLinuxDriver index 748ce78..a274fc2 160000 --- a/modules/XRLinuxDriver +++ b/modules/XRLinuxDriver @@ -1 +1 @@ -Subproject commit 748ce7816c50af50780b9186e394eaf3ebd3cb40 +Subproject commit a274fc23c385b3f039fe6baba0a138fe31c7ad35