From 80f54f5297fe6bb8bf003ad4b2aa293088e1ad11 Mon Sep 17 00:00:00 2001 From: wheaney <42350981+wheaney@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:34:15 -0800 Subject: [PATCH] WIP --- gnome/src/extension.js | 2 +- gnome/src/testactor.js | 245 +++++++++++++++++++++++++---------------- 2 files changed, 152 insertions(+), 95 deletions(-) diff --git a/gnome/src/extension.js b/gnome/src/extension.js index 01b45b1..9f81e5a 100644 --- a/gnome/src/extension.js +++ b/gnome/src/extension.js @@ -529,7 +529,7 @@ export default class BreezyDesktopExtension extends Extension { this._distance_connection = null; } if (this._data_stream_connection) { - this._device_data_stream.unbind(this._data_stream_connection); + this._data_stream_connection.unbind(); this._data_stream_connection = null; } if (this._follow_threshold_connection) { diff --git a/gnome/src/testactor.js b/gnome/src/testactor.js index 86064c7..bc6f0fa 100644 --- a/gnome/src/testactor.js +++ b/gnome/src/testactor.js @@ -31,7 +31,6 @@ function findClosestVector(quaternion, vectors, previousClosestIndex) { const lookVector = [1.0, 0.0, 0.0]; // NWU vector pointing to the center of the screen const rotatedLookVector = applyQuaternionToVector(lookVector, [quaternion.x, quaternion.y, quaternion.z, quaternion.w]); - Globals.logger.log(`\t\t\tQuaternion: ${JSON.stringify(quaternion)}`); Globals.logger.log(`\t\t\tRotated look vector: ${rotatedLookVector}`); let closestIndex = -1; @@ -81,23 +80,28 @@ function monitorWrap(radiusPixels, previousMonitorEndRadians, monitorPixels) { const monitorHalfRadians = Math.asin(monitorHalfPixels / radiusPixels); const centerRadians = previousMonitorEndRadians + monitorHalfRadians; return { + begin: previousMonitorEndRadians, center: centerRadians, end: centerRadians + monitorHalfRadians } } /** - * Convert the given monitor details into NWU vectors pointing to the center of each monitor. + * Convert the given monitor details into NWU vectors describing the center of the fully placed monitor, + * and the top-left of the partially placed monitor (minus only a single-axis rotation) * * @param {Object} fovDetails - contains reference fovDegrees (diagonal), widthPixels, heightPixels * @param {Object[]} monitorDetailsList - contains x, y, width, height (coordinates from top-left) * @param {string} monitorWrappingScheme - horizontal, vertical, none - * @returns {number[]} - Vector [x, y, z] + * @returns {Object[]} - contains NWU vectors pointing to `topLeftNoRotate` and `center` of each monitor */ function monitorsToVectors(fovDetails, monitorDetailsList, monitorWrappingScheme) { const aspect = fovDetails.widthPixels / fovDetails.heightPixels; const fovVerticalRadians = degreesToRadians(fovDetails.fovDegrees / Math.sqrt(1 + aspect * aspect)); + // distance needed for the FOV-sized monitor to fill up the screen + const centerRadius = fovDetails.heightPixels / 2 / Math.sin(fovVerticalRadians / 2); + // NWU vectors pointing to the center of the screen for each monitor const monitorVectors = []; @@ -105,55 +109,76 @@ function monitorsToVectors(fovDetails, monitorDetailsList, monitorWrappingScheme // monitors wrap around us horizontally const fovHorizontalRadians = fovVerticalRadians * aspect; - // radius is the hypothenuse of the triangle where the opposite side is half the width of the reference fov screen - const radius = fovDetails.widthPixels / 2 / Math.sin(fovHorizontalRadians / 2); + // 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 edgeRadius = fovDetails.widthPixels / 2 / Math.sin(fovHorizontalRadians / 2); let previousMonitorEndRadians = -fovHorizontalRadians / 2; monitorDetailsList.forEach(monitorDetails => { - const monitorWrapDetails = monitorWrap(radius, previousMonitorEndRadians, monitorDetails.width); + const monitorWrapDetails = monitorWrap(edgeRadius, previousMonitorEndRadians, monitorDetails.width); previousMonitorEndRadians = monitorWrapDetails.end; - monitorVectors.push([ - // north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians - radius * Math.cos(monitorWrapDetails.center), + monitorVectors.push({ + topLeftNoRotate: [ + centerRadius, + fovDetails.widthPixels / 2, + -(monitorDetails.y - fovDetails.heightPixels / 2) + ], + center: [ + // north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians + centerRadius * Math.cos(monitorWrapDetails.center), - // west is opposite where radius is the hypotenuse, using monitorWrapDetails.center as the radians - -radius * Math.sin(monitorWrapDetails.center), + // west is opposite where radius is the hypotenuse, using monitorWrapDetails.center as the radians + -centerRadius * Math.sin(monitorWrapDetails.center), - // up is flat when wrapping horizontally - -(monitorDetails.y + monitorDetails.height / 2 - fovDetails.heightPixels / 2) - ]); + // up is flat when wrapping horizontally + -(monitorDetails.y + monitorDetails.height / 2 - fovDetails.heightPixels / 2) + ] + }); }); } else if (monitorWrappingScheme === 'vertical') { // monitors wrap around us vertically - // radius is the hypothenuse of the triangle where the opposite side is half the height of the reference fov screen - const radius = fovDetails.heightPixels / 2 / Math.sin(fovVerticalRadians / 2); + // distance to a vertical edge is the hypothenuse of the triangle where the opposite side is half the height of the reference fov screen + const edgeRadius = fovDetails.heightPixels / 2 / Math.sin(fovVerticalRadians / 2); let previousMonitorEndRadians = -fovVerticalRadians / 2; monitorDetailsList.forEach(monitorDetails => { - const monitorWrapDetails = monitorWrap(radius, previousMonitorEndRadians, monitorDetails.height); + const monitorWrapDetails = monitorWrap(edgeRadius, previousMonitorEndRadians, monitorDetails.height); previousMonitorEndRadians = monitorWrapDetails.end; - monitorVectors.push([ - // north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians - radius * Math.cos(monitorWrapDetails.center), + monitorVectors.push({ + topLeftNoRotate: [ + centerRadius, + -(monitorDetails.x - fovDetails.widthPixels / 2), + fovDetails.heightPixels / 2 + ], + center: [ + // north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians + centerRadius * Math.cos(monitorWrapDetails.center), - // west is flat when wrapping vertically - -(monitorDetails.x + monitorDetails.width / 2 - fovDetails.widthPixels / 2), + // west is flat when wrapping vertically + -(monitorDetails.x + monitorDetails.width / 2 - fovDetails.widthPixels / 2), - // up is opposite where radius is the hypotenuse, using monitorWrapDetails.center as the radians - -radius * Math.sin(monitorWrapDetails.center) - ]); + // up is opposite where radius is the hypotenuse, using monitorWrapDetails.center as the radians + -centerRadius * Math.sin(monitorWrapDetails.center) + ] + }); }); } else { // monitors make a flat wall in front of us, no wrapping monitorDetailsList.forEach(monitorDetails => { - monitorVectors.push([ - fovDetails.heightPixels / 2 / Math.sin(fovVerticalRadians / 2), - -(monitorDetails.x + monitorDetails.width / 2 - fovDetails.widthPixels / 2), - -(monitorDetails.y + monitorDetails.height / 2 - fovDetails.heightPixels / 2) - ]); + monitorVectors.push({ + topLeftNoRotate: [ + centerRadius, + -(monitorDetails.x - fovDetails.widthPixels / 2), + -(monitorDetails.y - fovDetails.heightPixels / 2) + ], + center: [ + centerRadius, + -(monitorDetails.x + monitorDetails.width / 2 - fovDetails.widthPixels / 2), + -(monitorDetails.y + monitorDetails.height / 2 - fovDetails.heightPixels / 2) + ] + }); }); } @@ -164,13 +189,13 @@ function monitorVectorToRotationAngle(vector, monitorWrappingScheme) { if (monitorWrappingScheme === 'horizontal') { // monitors wrap around us horizontally return { - angle: radiansToDegrees(Math.atan2(vector[1], vector[0])), + angle: Math.atan2(vector[1], vector[0]), axis: Clutter.RotateAxis.Y_AXIS }; } else if (monitorWrappingScheme === 'vertical') { // monitors wrap around us vertically return { - angle: radiansToDegrees(Math.atan2(vector[2], vector[0])), + angle: Math.atan2(vector[2], vector[0]), axis: Clutter.RotateAxis.X_AXIS } } else { @@ -222,6 +247,13 @@ export const TestActorEffect = GObject.registerClass({ GObject.ParamFlags.READWRITE, 'horizontal', ['horizontal', 'vertical', 'none'] ), + 'monitor-wrapping-rotation-radians': GObject.ParamSpec.double( + 'monitor-wrapping-rotation-radians', + 'Monitor Wrapping Rotation Radians', + 'Rotation of the monitor wrapping around the viewport', + GObject.ParamFlags.READWRITE, + -360.0, 360.0, 0.0 + ), 'focused-monitor-index': GObject.ParamSpec.int( 'focused-monitor-index', 'Focused Monitor Index', @@ -234,9 +266,9 @@ export const TestActorEffect = GObject.registerClass({ 'Display Distance', 'Distance of the display from the camera', GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.0 + 0.0, + 10000.0, + 2900.0 ), 'toggle-display-distance-start': GObject.ParamSpec.double( 'toggle-display-distance-start', @@ -256,6 +288,12 @@ export const TestActorEffect = GObject.registerClass({ 2.5, 1.05 ), + 'actor-to-display-ratios': GObject.ParamSpec.jsobject( + 'actor-to-display-ratios', + 'Actor to Display Ratios', + 'Ratios to convert actor coordinates to display coordinates', + GObject.ParamFlags.READWRITE + ) } }, class TestActorEffect extends Shell.GLSLEffect { perspective(fovDiagonalRadians, aspect, near, far) { @@ -281,58 +319,66 @@ export const TestActorEffect = GObject.registerClass({ uniform vec4 u_quaternion; uniform mat4 u_projection_matrix; uniform float u_display_north_offset; + uniform float u_rotation_x_radians; + uniform float u_rotation_y_radians; + uniform float u_aspect_ratio; + + // for some reason the vector positions are relative to the width and height of the uiGroup actor + uniform vec2 u_actor_to_display_ratios; + + // constants that help me adjust CoGL vector positions so their components are at the ratios intended, for proper rotation + float cogl_position_width = 51.7; // no idea... + float cogl_z_factor = 2.5; // no idea... vec4 applyQuaternionToVector(vec4 v, vec4 q) { vec3 t = 2.0 * cross(q.xyz, v.xyz); vec3 rotated = v.xyz + q.w * t + cross(q.xyz, t); return vec4(rotated, v.w); } + + vec4 applyXRotationToVector(vec4 v, float angle) { + float c = cos(angle); + float s = sin(angle); + return vec4(v.x, v.y * c - v.z * s, v.y * s + v.z * c, v.w); + } + + vec4 applyYRotationToVector(vec4 v, float angle) { + float c = cos(angle); + float s = sin(angle); + return vec4(v.x * c + v.z * s, v.y, v.z * c - v.x * s, v.w); + } `; const main = ` vec4 world_pos = cogl_position_in; - // // move pixel space to texcoord space - // world_pos.x = (world_pos.x / 192.0); - // world_pos.y = (world_pos.y / 108.0); + float cogl_position_height = cogl_position_width / u_aspect_ratio; + float position_width_adjustment_count = u_actor_to_display_ratios.x - 1.0; + float position_height_adjustment_count = u_actor_to_display_ratios.y - 1.0; - // float displayAspectRatio = 1920.0 / 1080.0; - // float diagToVertRatio = sqrt(pow(displayAspectRatio, 2) + 1); - // float halfFovZRads = radians(46.0 / diagToVertRatio) / 2.0; - // float halfFovYRads = halfFovZRads * displayAspectRatio; - // vec2 fovHalfWidths = vec2(tan(halfFovYRads), tan(halfFovZRads)); - // vec2 fovWidths = fovHalfWidths * 2.0; + world_pos.z /= cogl_z_factor; - // float vec_y = -world_pos.x * fovWidths.x + fovHalfWidths.x; - // float vec_z = -world_pos.y * fovWidths.y + fovHalfWidths.y; - // vec4 look_vector = vec4(1.0, vec_y, vec_z, 1.0); - // // vec3 rotated_vector = applyQuaternionToVector(look_vector, u_quaternion).xyz; - // vec3 rotated_vector = look_vector.xyz; + // if the perspective includes more than just our actor, move vertices towards the center of the perspective so they'll be properly rotated + world_pos.x += position_width_adjustment_count * cogl_position_width; + world_pos.y += position_height_adjustment_count * cogl_position_height; - // // scale back to the screen distance - // rotated_vector /= rotated_vector.x; - // cogl_position_out = vec4( - // ((fovHalfWidths.x - rotated_vector.y) / fovWidths.x) * 2.0 - 1.0, - // ((fovHalfWidths.y - rotated_vector.z) / fovWidths.y) * 2.0 - 1.0, - // 0.0, - // 1.0 - // ); - - // float z_orig = world_pos.z; - // world_pos.z -= z_orig / 1920.0; - // world_pos.x /= 2.0; - // world_pos *= u_display_north_offset; + world_pos.z *= u_aspect_ratio; + world_pos = applyXRotationToVector(world_pos, u_rotation_x_radians); + world_pos = applyYRotationToVector(world_pos, u_rotation_y_radians); world_pos = applyQuaternionToVector(world_pos, u_quaternion); - // world_pos /= u_display_north_offset; - // world_pos.x *= 2.0; - // world_pos.z += z_orig / 1920.0; - world_pos = cogl_modelview_matrix * world_pos; - cogl_position_out = cogl_projection_matrix * world_pos; + world_pos.z /= u_aspect_ratio; - // cogl_position_out.x = world_pos.x / 103.4; - // cogl_position_out.y = world_pos.y / 29.075; - // cogl_position_out.z = -1.0; - // cogl_position_out.w = 1.0; + world_pos.x /= u_actor_to_display_ratios.x; + world_pos.y /= u_actor_to_display_ratios.y; + + world_pos = u_projection_matrix * world_pos; + + // if the perspective includes more than just our actor, move the vertices back to just the area we can see. + // this needs to be done after the projection matrix multiplication so it will be projected as if centered in our vision + world_pos.x -= 0.5 * position_width_adjustment_count * world_pos.w; + world_pos.y -= 0.5 * position_height_adjustment_count * world_pos.w; + + cogl_position_out = world_pos; cogl_tex_coord_out[0] = cogl_tex_coord_in; ` @@ -351,12 +397,16 @@ export const TestActorEffect = GObject.registerClass({ ); Globals.logger.log(`aspect: ${aspect}, fov: ${this.fov_degrees}, width: ${this.get_actor().width}, height: ${this.get_actor().height}, projection matrix: ${JSON.stringify(projection_matrix)}`); this.set_uniform_matrix(this.get_uniform_location("u_projection_matrix"), false, 4, projection_matrix); + this.set_uniform_float(this.get_uniform_location("u_rotation_x_radians"), 1, [this.monitor_wrapping_scheme === 'vertical' ? this.monitor_wrapping_rotation_radians : 0.0]); + this.set_uniform_float(this.get_uniform_location("u_rotation_y_radians"), 1, [this.monitor_wrapping_scheme === 'horizontal' ? this.monitor_wrapping_rotation_radians : 0.0]); + this.set_uniform_float(this.get_uniform_location("u_aspect_ratio"), 1, [aspect]); + this.set_uniform_float(this.get_uniform_location("u_actor_to_display_ratios"), 2, this.actor_to_display_ratios); this._initialized = true; } - this.set_uniform_float(this.get_uniform_location("u_display_north_offset"), 1, [this.focused_monitor_index === this.monitor_index ? this.display_distance : this.toggle_display_distance_start]); + this.set_uniform_float(this.get_uniform_location("u_display_north_offset"), 1, [this.display_distance]); - // NUW to east-up-south conversion, inverted + // NWU to east-up-south conversion, inverted this.set_uniform_float(this.get_uniform_location("u_quaternion"), 4, [this.quaternion.y, -this.quaternion.z, this.quaternion.x, this.quaternion.w]); this.get_pipeline().set_layer_filters( @@ -450,58 +500,65 @@ export const TestActor = GObject.registerClass({ })), 'horizontal' ); - this.monitorAsNormalizedVectors = this.monitorsAsVectors.map(vector => { + + // normalize the center vectors + this.monitorAsNormalizedVectors = this.monitorsAsVectors.map(monitorVectors => { + const vector = monitorVectors.center; const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]); return [vector[0] / length, vector[1] / length, vector[2] / length]; }); + + const actorToDisplayRatios = [ + Main.layoutManager.uiGroup.width / this.width, + Main.layoutManager.uiGroup.height / this.height + ]; Main.layoutManager.monitors.forEach(((monitor, index) => { // if (index === 0) return; Globals.logger.log(`\t\t\tMonitor ${index}: ${monitor.x}, ${monitor.y}, ${monitor.width}, ${monitor.height}`); // this is in NWU coordinates - const monitorVector = this.monitorsAsVectors[index]; - const monitorRotation = monitorVectorToRotationAngle(monitorVector, 'horizontal'); - Globals.logger.log_debug(`\t\t\tMonitor ${index} vector: ${monitorVector} rotation: ${JSON.stringify(monitorRotation)}`); + const noRotationVector = this.monitorsAsVectors[index].topLeftNoRotate; + Globals.logger.log_debug(`\t\t\tMonitor ${index} vectors: ${JSON.stringify(this.monitorsAsVectors[index])}`); // actor coordinates are east-up-south const containerActor = new Clutter.Actor({ - x: -monitorVector[1], - y: -monitorVector[2], - 'z-position': -monitorVector[0], + x: -noRotationVector[1], + y: -noRotationVector[2], + 'z-position': -noRotationVector[0], width: monitor.width, height: monitor.height, - reactive: false + reactive: false, }); // Create a clone of the stage content for this monitor const monitorClone = new Clutter.Clone({ source: Main.layoutManager.uiGroup, - reactive: false + reactive: false, + x: -containerActor.x - monitor.x, + y: -containerActor.y - monitor.y }); - - monitorClone.x = -containerActor.x; - // monitorActor.y = 0; - monitorClone.set_clip(monitor.x, 0, monitor.width, monitor.height); + monitorClone.set_clip(monitor.x, monitor.y, monitor.width, monitor.height); // Add the monitor actor to the scene containerActor.add_child(monitorClone); - containerActor.set_pivot_point(0.5, 0.5); - containerActor.set_rotation_angle(monitorRotation.axis, monitorRotation.angle); const effect = new TestActorEffect({ quaternion: this.quaternion, fov_degrees: this.fov_degrees, monitor_index: index, - display_distance: this.toggle_display_distance_start + display_distance: noRotationVector[0], + monitor_wrapping_scheme: 'horizontal', + monitor_wrapping_rotation_radians: monitorVectorToRotationAngle(this.monitorsAsVectors[index].center, 'horizontal').angle, + actor_to_display_ratios: actorToDisplayRatios }); containerActor.add_effect_with_name('viewport-effect', effect); this.add_child(containerActor); this.bind_property('quaternion', effect, 'quaternion', GObject.BindingFlags.DEFAULT); this.bind_property('focused-monitor-index', effect, 'focused-monitor-index', GObject.BindingFlags.DEFAULT); - this.bind_property('display-distance', effect, 'display-distance', GObject.BindingFlags.DEFAULT); + // this.bind_property('display-distance', effect, 'display-distance', GObject.BindingFlags.DEFAULT); }).bind(this)); - GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, (() => { + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, (() => { if (this.quaternion) { const closestMonitorIndex = findClosestVector(this.quaternion, this.monitorAsNormalizedVectors, this.closestMonitorIndex); @@ -516,9 +573,9 @@ export const TestActor = GObject.registerClass({ }).bind(this)); this._distance_ease_timeline = null; - this.connect('notify::toggle-display-distance-start', this._handle_display_distance_properties_change.bind(this)); - this.connect('notify::toggle-display-distance-end', this._handle_display_distance_properties_change.bind(this)); - this.connect('notify::display-distance', this._handle_display_distance_properties_change.bind(this)); + // this.connect('notify::toggle-display-distance-start', this._handle_display_distance_properties_change.bind(this)); + // this.connect('notify::toggle-display-distance-end', this._handle_display_distance_properties_change.bind(this)); + // this.connect('notify::display-distance', this._handle_display_distance_properties_change.bind(this)); this._handle_display_distance_properties_change(); }