use glam::{Affine3A, Mat3A, Quat, Vec3, Vec3A}; use idmap_derive::IntegerId; use std::{f32::consts::PI, sync::Arc}; use wlx_common::windowing::{OverlayWindowState, Positioning}; use crate::{ state::AppState, subsystem::input::KeyboardFocus, windowing::{ backend::{FrameMeta, OverlayBackend, RenderResources, ShouldRender}, snap_upright, }, }; pub struct OverlayWindowData { pub config: OverlayWindowConfig, pub data: T, pub birthframe: usize, pub primary_pointer: Option, } impl OverlayWindowData where T: Default, { pub fn from_config(config: OverlayWindowConfig) -> Self { Self { data: T::default(), config, primary_pointer: None, birthframe: 0, } } } impl OverlayWindowData { pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { //TODO: load state? self.config.backend.init(app) } pub fn should_render(&mut self, app: &mut AppState) -> anyhow::Result { self.config.backend.should_render(app) } pub fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> { self.config.backend.render(app, rdr) } pub fn frame_meta(&mut self) -> Option { self.config.backend.frame_meta() } } #[derive(Debug, Clone, Copy, IntegerId, PartialEq)] pub enum OverlayCategory { Internal, Keyboard, Panel, Screen, Mirror, WayVR, } pub struct OverlayWindowConfig { pub name: Arc, pub backend: Box, /// The default state to show when the overlay is newly spawned. pub default_state: OverlayWindowState, /// The current state to show. None if the overlay is hidden. pub active_state: Option, /// Order to draw overlays in. Overlays with higher numbers will be drawn over ones with lower numbers. pub z_order: u32, /// If set, hovering this overlay will cause the HID provider to switch focus. pub keyboard_focus: Option, /// Category of the overlay, used by toolbox on the watch. pub category: OverlayCategory, /// Should the overlay be displayed on the next frame? pub show_on_spawn: bool, /// Does not belong to any set; switching sets does not affect this overlay. pub global: bool, /// True if transform, curvature, alpha has changed. Only used by OpenVR. pub dirty: bool, /// True if the window is showing the edit overlay pub editing: bool, /// Used by grab to pause following of HMD or other devices pub pause_movement: bool, } impl OverlayWindowConfig { pub fn from_backend(backend: Box) -> Self { Self { name: "".into(), backend, default_state: OverlayWindowState { transform: Affine3A::from_translation(Vec3::NEG_Z), ..OverlayWindowState::default() }, active_state: None, z_order: 0, keyboard_focus: None, category: OverlayCategory::Internal, show_on_spawn: false, global: false, dirty: true, editing: false, pause_movement: false, } } pub const fn is_active(&self) -> bool { self.active_state.is_some() } pub fn activate(&mut self, app: &mut AppState) { log::debug!("activate {}", self.name.as_ref()); self.dirty = true; self.active_state = Some(self.default_state.clone()); self.reset(app, true); } pub fn deactivate(&mut self) { log::debug!("deactivate {}", self.name.as_ref()); self.active_state = None; } pub fn toggle(&mut self, app: &mut AppState) { if self.active_state.take().is_none() { self.activate(app); } else { log::debug!("deactivate {} (toggle)", self.name.as_ref()); } } pub fn auto_movement(&mut self, app: &mut AppState) { if self.pause_movement { return; } let Some(state) = self.active_state.as_mut() else { return; }; let cur_transform = state .saved_transform .unwrap_or(self.default_state.transform); let (parent_transform, lerp) = match state.positioning { Positioning::FollowHead { lerp } => (app.input_state.hmd, lerp), Positioning::FollowHand { hand, lerp } => { (app.input_state.pointers[hand as usize].pose, lerp) } Positioning::Anchored => (app.anchor, 1.0), _ => return, }; let target_transform = parent_transform * cur_transform; state.transform = match lerp { 1.0 => target_transform, lerp => { let scale = target_transform.matrix3.x_axis.length(); let rot_from = Quat::from_mat3a(&state.transform.matrix3.div_scalar(scale)); let rot_to = Quat::from_mat3a(&target_transform.matrix3.div_scalar(scale)); let rotation = rot_from.slerp(rot_to, lerp); let translation = state .transform .translation .slerp(target_transform.translation, lerp); Affine3A::from_scale_rotation_translation( Vec3::ONE * scale, rotation, translation.into(), ) } }; self.dirty = true; } /// Returns true if changes were saved. pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) { let Some(state) = self.active_state.as_mut() else { return; }; let cur_transform = state .saved_transform .unwrap_or(self.default_state.transform); let parent_transform = match state.positioning { Positioning::Floating | Positioning::FollowHead { .. } => app.input_state.hmd, Positioning::FollowHand { hand, .. } => app.input_state.pointers[hand as usize].pose, Positioning::Anchored => app.anchor, Positioning::Static => return, }; if hard_reset { state.saved_transform = None; } state.transform = parent_transform * cur_transform; if state.grabbable && hard_reset { realign(&mut state.transform, &app.input_state.hmd); } self.dirty = true; } } pub fn realign(transform: &mut Affine3A, hmd: &Affine3A) { let to_hmd = hmd.translation - transform.translation; let up_dir: Vec3A; if hmd.x_axis.dot(Vec3A::Y).abs() > 0.2 { // Snap upright up_dir = hmd.y_axis; } else { let dot = to_hmd.normalize().dot(hmd.z_axis); let z_dist = to_hmd.length(); let y_dist = (transform.translation.y - hmd.translation.y).abs(); let x_angle = (y_dist / z_dist).asin(); if dot < -f32::EPSILON { // facing down let up_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::Y; up_dir = (up_point - transform.translation).normalize(); } else if dot > f32::EPSILON { // facing up let dn_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::NEG_Y; up_dir = (transform.translation - dn_point).normalize(); } else { // perfectly upright up_dir = Vec3A::Y; } } let scale = transform.x_axis.length(); let col_z = (transform.translation - hmd.translation).normalize(); let col_y = up_dir; let col_x = col_y.cross(col_z); let col_y = col_z.cross(col_x).normalize(); let col_x = col_x.normalize(); let rot = Mat3A::from_quat(Quat::from_axis_angle(Vec3::Y, PI)); transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot; } pub fn save_transform(state: &mut OverlayWindowState, app: &mut AppState) { let parent_transform = match state.positioning { Positioning::Floating => snap_upright(app.input_state.hmd, Vec3A::Y), Positioning::FollowHead { .. } => app.input_state.hmd, Positioning::FollowHand { hand, .. } => app.input_state.pointers[hand as usize].pose, Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y), Positioning::Static => return, }; state.saved_transform = Some(parent_transform.inverse() * state.transform); }