diff --git a/gnome/src/math.js b/gnome/src/math.js index e2dd554..a17d8b5 100644 --- a/gnome/src/math.js +++ b/gnome/src/math.js @@ -4,18 +4,23 @@ export function degreeToRadian(degree) { // 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); + // first convert from a spherical FOV to a diagonal FOV on a flat plane at a unit distance of 1.0 + const diagonalLengthUnitDistance = 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; + const heightUnitDistance = diagonalLengthUnitDistance / Math.sqrt(1 + aspectRatio * aspectRatio); + const widthUnitDistance = heightUnitDistance * aspectRatio; - // then convert back to spherical FOV return { - diagonal: diagonalFOVRadians, - horizontal: 2 * Math.atan(flatHorizontalFOV / 2), - vertical: 2 * Math.atan(flatVerticalFOV / 2) + // then convert back to spherical FOV + diagonalRadians: diagonalFOVRadians, + horizontalRadians: 2 * Math.atan(widthUnitDistance / 2), + verticalRadians: 2 * Math.atan(heightUnitDistance / 2), + + // flat values are relative to a unit distance of 1.0 + diagonalLengthUnitDistance, + widthUnitDistance, + heightUnitDistance } } @@ -31,7 +36,10 @@ export const fovConversionFns = { fovEdgeToScreenCenterDistance: (edgeDistance, screenLength) => Math.sqrt(Math.pow(edgeDistance, 2) - Math.pow(screenLength / 2, 2)), lengthToRadians: (fovRadians, fovLength, screenEdgeDistance, toLength) => Math.asin(toLength / 2 / screenEdgeDistance) * 2, angleToLength: (fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) => { - return toAngleOpposite / toAngleAdjacent * screenDistance + return toAngleOpposite / toAngleAdjacent * screenDistance; + }, + fovRadiansAtDistance: (fovRadians, unitLength, newScreenDistance) => { + return 2 * Math.atan(unitLength / 2 / newScreenDistance); }, radiansToSegments: (screenRadians) => 1 }, @@ -42,6 +50,7 @@ export const fovConversionFns = { fovEdgeToScreenCenterDistance: (edgeDistance, screenLength) => edgeDistance, lengthToRadians: (fovRadians, fovLength, screenEdgeDistance, toLength) => fovRadians / fovLength * toLength, angleToLength: (fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) => fovLength / fovRadians * Math.atan2(toAngleOpposite, toAngleAdjacent), + fovRadiansAtDistance: (fovRadians, unitLength, newScreenDistance) => fovRadians / newScreenDistance, radiansToSegments: (screenRadians) => Math.ceil(screenRadians * segmentsPerRadian) } } diff --git a/gnome/src/virtualdisplayeffect.js b/gnome/src/virtualdisplayeffect.js index 83316b4..be32a18 100644 --- a/gnome/src/virtualdisplayeffect.js +++ b/gnome/src/virtualdisplayeffect.js @@ -28,33 +28,34 @@ function lookAheadMS(imuDateMs, lookAheadCfg, override) { // Create a mesh of vertices in a pattern suitable for TRIANGLE_STRIP function createVertexMesh(fovDetails, monitorDetails, positionVectorNWU) { - let fovConversions = fovDetails.curvedDisplay ? fovConversionFns.curved : fovConversionFns.flat; - const sideEdgeDistancePixels = fovConversions.centerToFovEdgeDistance( + let horizontalWrap = fovDetails.monitorWrappingScheme === 'horizontal'; + const horizontalConversions = fovDetails.curvedDisplay && horizontalWrap ? fovConversionFns.curved : fovConversionFns.flat; + const sideEdgeDistancePixels = horizontalConversions.centerToFovEdgeDistance( fovDetails.completeScreenDistancePixels, fovDetails.sizeAdjustedWidthPixels ); - const horizontalRadians = fovConversions.lengthToRadians( + const horizontalRadians = horizontalConversions.lengthToRadians( fovDetails.defaultDistanceHorizontalRadians, fovDetails.widthPixels, sideEdgeDistancePixels, monitorDetails.width ); - const topEdgeDistancePixels = fovConversions.centerToFovEdgeDistance( + let verticalWrap = fovDetails.monitorWrappingScheme === 'vertical'; + const verticalConversions = fovDetails.curvedDisplay && verticalWrap ? fovConversionFns.curved : fovConversionFns.flat; + const topEdgeDistancePixels = verticalConversions.centerToFovEdgeDistance( fovDetails.completeScreenDistancePixels, fovDetails.sizeAdjustedHeightPixels ); - const verticalRadians = fovConversions.lengthToRadians( + const verticalRadians = verticalConversions.lengthToRadians( fovDetails.defaultDistanceVerticalRadians, fovDetails.heightPixels, topEdgeDistancePixels, monitorDetails.height ); - let horizontalWrap = fovDetails.monitorWrappingScheme === 'horizontal'; - let verticalWrap = fovDetails.monitorWrappingScheme === 'vertical'; - const xSegments = horizontalWrap ? fovConversions.radiansToSegments(horizontalRadians) : 1; - const ySegments = verticalWrap ? fovConversions.radiansToSegments(verticalRadians) : 1; + const xSegments = horizontalConversions.radiansToSegments(horizontalRadians); + const ySegments = verticalConversions.radiansToSegments(verticalRadians); const texXLeft = 0; const texYTop = 0; @@ -419,8 +420,8 @@ 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(fovHorizontalRadians, aspect, near, far) { - const f = 1.0 / Math.tan(fovHorizontalRadians / 2.0); + perspective(widthUnitDistance, aspect, near, far) { + const f = 2.0 / widthUnitDistance; const range = far - near; return [ @@ -562,15 +563,15 @@ export const VirtualDisplayEffect = GObject.registerClass({ this._initialized = true; const aspect = this.target_monitor.width / this.target_monitor.height; - const fovRadians = diagonalToCrossFOVs(degreeToRadian(Globals.data_stream.device_data.displayFov), aspect); + const fovLengths = diagonalToCrossFOVs(degreeToRadian(Globals.data_stream.device_data.displayFov), aspect); const projection_matrix = this.perspective( - fovRadians.horizontal, + fovLengths.widthUnitDistance, aspect, 1.0, 10000.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, [fovRadians.vertical]); + this.set_uniform_float(this.get_uniform_location("u_fov_vertical_radians"), 1, [fovLengths.verticalRadians]); 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 024dd9f..27fd049 100644 --- a/gnome/src/virtualdisplaysactor.js +++ b/gnome/src/virtualdisplaysactor.js @@ -7,7 +7,7 @@ import Shell from 'gi://Shell'; import St from 'gi://St'; import { VirtualDisplayEffect, SMOOTH_FOLLOW_SLERP_TIMELINE_MS } from './virtualdisplayeffect.js'; -import { applyQuaternionToVector, degreeToRadian, diagonalToCrossFOVs, fovConversionFns, normalizeVector, vectorMagnitude } from './math.js'; +import { applyQuaternionToVector, degreeToRadian, diagonalToCrossFOVs, fovConversionFns, vectorMagnitude } from './math.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; @@ -21,8 +21,11 @@ const UNFOCUS_THRESHOLD = 1.1 / 2.0; // returns how far the look vector is from the center of the monitor, as a percentage of the monitor's dimensions function getMonitorDistance(fovDetails, lookUpPixels, lookWestPixels, monitorVector, monitorDetails, upAngleToLength, westAngleToLength) { + // since the monitor vector has been modified to be relative to the lens position, we need to calculate its distance from the lens + // we need to adjust all angle-based lengths based on new vector distance const monitorDistance = vectorMagnitude(monitorVector); const distanceAdjustment = monitorDistance / fovDetails.completeScreenDistancePixels; + const vectorUpPixels = upAngleToLength( fovDetails.defaultDistanceVerticalRadians, fovDetails.heightPixels, @@ -664,10 +667,10 @@ export const VirtualDisplaysActor = GObject.registerClass({ this._property_connections.push(this.connect(`notify::${property}`, fn.bind(this))); }).bind(this); - notifyToFunction('toggle-display-distance-start', this._handle_display_distance_properties_change); - notifyToFunction('toggle-display-distance-end', this._handle_display_distance_properties_change); - notifyToFunction('display-distance', this._handle_display_distance_properties_change); - notifyToFunction('display-size', this._handle_display_size_change); + notifyToFunction('toggle-display-distance-start', this._handle_display_size_distance_change); + notifyToFunction('toggle-display-distance-end', this._handle_display_size_distance_change); + notifyToFunction('display-distance', this._handle_display_size_distance_change); + notifyToFunction('display-size', this._handle_display_size_distance_change); notifyToFunction('monitor-wrapping-scheme', this._update_monitor_placements); notifyToFunction('monitor-spacing', this._update_monitor_placements); notifyToFunction('headset-display-as-viewport-center', this._update_monitor_placements); @@ -678,8 +681,7 @@ export const VirtualDisplaysActor = GObject.registerClass({ notifyToFunction('custom-banner-enabled', this._handle_banner_update); notifyToFunction('framerate-cap', this._handle_frame_rate_cap_change); notifyToFunction('smooth-follow-enabled', this._handle_smooth_follow_enabled_change); - this._handle_display_size_change(false); - this._handle_display_distance_properties_change(); + this._handle_display_size_distance_change(); this._handle_frame_rate_cap_change(); const actorToDisplayRatios = [ @@ -860,33 +862,58 @@ export const VirtualDisplaysActor = GObject.registerClass({ _fov_details() { const aspect = this.target_monitor.width / this.target_monitor.height; - const fovRadians = diagonalToCrossFOVs(degreeToRadian(Globals.data_stream.device_data.displayFov), aspect); - + const fovLengths = diagonalToCrossFOVs(degreeToRadian(Globals.data_stream.device_data.displayFov), aspect); + const monitorWrappingScheme = this._actual_wrap_scheme(); + const defaultDistance = this._display_distance_default(); + const horizontalConversions = this.curved_display && monitorWrappingScheme === 'horizontal' ? fovConversionFns.curved : fovConversionFns.flat; + const verticalConversions = this.curved_display && monitorWrappingScheme === 'vertical' ? fovConversionFns.curved : fovConversionFns.flat; + // adjusted angles based on how far away the screens are e.g. a closer screen takes up a larger slice of our FOV - 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()); + const defaultDistanceVerticalRadians = verticalConversions.fovRadiansAtDistance( + fovLengths.verticalRadians, + fovLengths.heightUnitDistance, + defaultDistance + ); + const defaultDistanceHorizontalRadians = horizontalConversions.fovRadiansAtDistance( + fovLengths.horizontalRadians, + fovLengths.widthUnitDistance, + defaultDistance + ); // distance needed for the FOV-sized monitor to fill up the screen - const fullScreenDistancePixels = this.target_monitor.height / 2 / Math.tan(fovRadians.vertical / 2); - const lensDistancePixels = fullScreenDistancePixels / (1.0 - Globals.data_stream.device_data.lensDistanceRatio) - fullScreenDistancePixels; + const fullScreenDistancePixels = this.target_monitor.width / fovLengths.widthUnitDistance; + + // TODO - fix usage of full/complete distance values, so we can factor lens distance back in + const lensDistancePixels = 0.0; // fullScreenDistancePixels / (1.0 - Globals.data_stream.device_data.lensDistanceRatio) - fullScreenDistancePixels; // 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(defaultDistanceVerticalRadians / 2); - const completeScreenDistancePixels = lensToScreenDistance + lensDistancePixels; - const sizeAdjustedWidthPixels = this.target_monitor.width * this._distance_adjusted_size; - const sizeAdjustedHeightPixels = this.target_monitor.height * this._distance_adjusted_size; - - return { + const lensToScreenDistancePixels = fullScreenDistancePixels * defaultDistance; + const completeScreenDistancePixels = lensToScreenDistancePixels + lensDistancePixels; + Globals.logger.log_debug(`\t\t\tFOV Details: ${JSON.stringify({ widthPixels: this.target_monitor.width, - sizeAdjustedWidthPixels, + sizeAdjustedWidthPixels: this.target_monitor.width * this._distance_adjusted_size, heightPixels: this.target_monitor.height, - sizeAdjustedHeightPixels, + sizeAdjustedHeightPixels: this.target_monitor.height * this._distance_adjusted_size, defaultDistanceVerticalRadians, defaultDistanceHorizontalRadians, lensDistancePixels, fullScreenDistancePixels, completeScreenDistancePixels, - monitorWrappingScheme: this._actual_wrap_scheme(), + monitorWrappingScheme, + curvedDisplay: this.curved_display + })}`); + + return { + widthPixels: this.target_monitor.width, + sizeAdjustedWidthPixels: this.target_monitor.width * this._distance_adjusted_size, + heightPixels: this.target_monitor.height, + sizeAdjustedHeightPixels: this.target_monitor.height * this._distance_adjusted_size, + defaultDistanceVerticalRadians, + defaultDistanceHorizontalRadians, + lensDistancePixels, + fullScreenDistancePixels, + completeScreenDistancePixels, + monitorWrappingScheme, curvedDisplay: this.curved_display }; } @@ -944,17 +971,12 @@ export const VirtualDisplaysActor = GObject.registerClass({ } } - _handle_display_distance_properties_change() { + _handle_display_size_distance_change() { this._distance_adjusted_size = this._display_distance_default() * this.display_size; const distance_from_end = Math.abs(this.display_distance - this.toggle_display_distance_end); const distance_from_start = Math.abs(this.display_distance - this.toggle_display_distance_start); this._is_display_distance_at_end = distance_from_end < distance_from_start; - this._update_monitor_placements(); - } - - _handle_display_size_change(update_placements = true) { - this._distance_adjusted_size = this._display_distance_default() * this.display_size; const sizeComplement = (1.0 - this._distance_adjusted_size) / 2.0; const sizeViewportOffsetX = sizeComplement * this.target_monitor.width; @@ -965,7 +987,7 @@ export const VirtualDisplaysActor = GObject.registerClass({ width: monitor.width * this._distance_adjusted_size, height: monitor.height * this._distance_adjusted_size })); - if (update_placements) this._update_monitor_placements(); + this._update_monitor_placements(); } _handle_banner_update() {