Fix display placement issues

This commit is contained in:
wheaney 2025-03-01 16:54:21 -08:00
parent f980d0bf2b
commit a0527a0944
4 changed files with 58 additions and 43 deletions

View File

@ -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))
}
}

View File

@ -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);

View File

@ -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,

@ -1 +1 @@
Subproject commit 748ce7816c50af50780b9186e394eaf3ebd3cb40
Subproject commit a274fc23c385b3f039fe6baba0a138fe31c7ad35