Fix rendering issues caused by curved displays at closer distances
This commit is contained in:
parent
dc4f8634e1
commit
31d11307d0
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue