use std::{ f32::consts::PI, sync::{ atomic::{AtomicUsize, Ordering}, Arc, }, }; use anyhow::Ok; use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A}; use vulkano::image::view::ImageView; use crate::state::AppState; use super::input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit}; static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); pub trait OverlayBackend: OverlayRenderer + InteractionHandler {} pub struct OverlayState { pub id: usize, pub name: Arc, pub want_visible: bool, pub show_hide: bool, pub grabbable: bool, pub interactable: bool, pub recenter: bool, pub dirty: bool, pub alpha: f32, pub transform: Affine3A, pub saved_point: Option, pub spawn_scale: f32, // aka width pub spawn_point: Vec3A, pub spawn_rotation: Quat, pub relative_to: RelativeTo, pub primary_pointer: Option, pub interaction_transform: Affine2, } impl Default for OverlayState { fn default() -> Self { OverlayState { id: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed), name: Arc::from(""), want_visible: false, show_hide: false, grabbable: false, recenter: false, interactable: false, dirty: true, alpha: 1.0, relative_to: RelativeTo::None, saved_point: None, spawn_scale: 1.0, spawn_point: Vec3A::NEG_Z, spawn_rotation: Quat::IDENTITY, transform: Affine3A::IDENTITY, primary_pointer: None, interaction_transform: Affine2::IDENTITY, } } } pub struct OverlayData where T: Default, { pub state: OverlayState, pub backend: Box, pub primary_pointer: Option, pub data: T, } impl Default for OverlayData where T: Default, { fn default() -> Self { OverlayData { state: Default::default(), backend: Box::::default(), primary_pointer: None, data: Default::default(), } } } impl OverlayState { pub fn parent_transform(&self, app: &AppState) -> Option { match self.relative_to { RelativeTo::None => None, RelativeTo::Head => Some(app.input_state.hmd), RelativeTo::Hand(idx) => Some(app.input_state.pointers[idx].pose), } } pub fn auto_movement(&mut self, app: &mut AppState) { if let Some(parent) = self.parent_transform(app) { let point = self.saved_point.unwrap_or(self.spawn_point); self.transform = parent * Affine3A::from_scale_rotation_translation( Vec3::ONE * self.spawn_scale, self.spawn_rotation * Quat::from_rotation_x(f32::to_radians(-180.0)) * Quat::from_rotation_z(f32::to_radians(180.0)), point.into(), ); self.dirty = true; } } pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) { let scale = if hard_reset { self.saved_point = None; self.spawn_scale } else { self.transform.x_axis.length() }; let point = self.saved_point.unwrap_or(self.spawn_point); let translation = app.input_state.hmd.transform_point3a(point); self.transform = Affine3A::from_scale_rotation_translation( Vec3::ONE * scale, Quat::IDENTITY, translation.into(), ); self.realign(&app.input_state.hmd); self.dirty = true; } pub fn realign(&mut self, hmd: &Affine3A) { let to_hmd = hmd.translation - self.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 = (self.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 - self.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 = (self.transform.translation - dn_point).normalize(); } else { // perfectly upright up_dir = Vec3A::Y; } } let scale = self.transform.x_axis.length(); let col_z = (self.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(self.spawn_rotation) * Mat3A::from_quat(Quat::from_axis_angle(Vec3::Y, PI)); self.transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot; } } impl OverlayData where T: Default, { pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.state.reset(app, true); self.backend.init(app) } pub fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.backend.render(app) } pub fn view(&mut self) -> Option> { self.backend.view() } pub fn set_visible(&mut self, app: &mut AppState, visible: bool) -> anyhow::Result<()> { let old_visible = self.state.want_visible; self.state.want_visible = visible; if visible != old_visible { if visible { self.backend.resume(app)?; } else { self.backend.pause(app)?; } } Ok(()) } } pub trait OverlayRenderer { fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn render(&mut self, app: &mut AppState) -> anyhow::Result<()>; fn view(&mut self) -> Option>; } pub struct FallbackRenderer; impl OverlayRenderer for FallbackRenderer { fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } fn render(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } fn view(&mut self) -> Option> { None } } // Boilerplate and dummies #[derive(Clone, Copy, Debug, Default)] pub enum RelativeTo { #[default] None, Head, Hand(usize), } pub struct SplitOverlayBackend { pub renderer: Box, pub interaction: Box, } impl Default for SplitOverlayBackend { fn default() -> SplitOverlayBackend { SplitOverlayBackend { renderer: Box::new(FallbackRenderer), interaction: Box::new(DummyInteractionHandler), } } } impl OverlayBackend for SplitOverlayBackend {} impl OverlayRenderer for SplitOverlayBackend { fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.renderer.init(app) } fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.renderer.pause(app) } fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.renderer.resume(app) } fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.renderer.render(app) } fn view(&mut self) -> Option> { self.renderer.view() } } impl InteractionHandler for SplitOverlayBackend { fn on_left(&mut self, app: &mut AppState, pointer: usize) { self.interaction.on_left(app, pointer); } fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option { self.interaction.on_hover(app, hit) } fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) { self.interaction.on_scroll(app, hit, delta); } fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { self.interaction.on_pointer(app, hit, pressed); } } pub fn ui_transform(extent: &[u32; 2]) -> Affine2 { let center = Vec2 { x: 0.5, y: 0.5 }; if extent[1] > extent[0] { Affine2::from_cols( Vec2::X * (extent[1] as f32 / extent[0] as f32), Vec2::NEG_Y, center, ) } else { Affine2::from_cols( Vec2::X, Vec2::NEG_Y * (extent[0] as f32 / extent[1] as f32), center, ) } }