diff --git a/src/backend/input.rs b/src/backend/input.rs index ae099f0..2838703 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -6,8 +6,9 @@ use glam::{Affine3A, Vec2, Vec3, Vec3A, Vec3Swizzles}; use smallvec::{smallvec, SmallVec}; -use crate::backend::common::{snap_upright, OverlaySelector}; -use crate::config::{AStrMapExt, GeneralConfig}; +use crate::backend::common::OverlaySelector; +use crate::backend::overlay::Positioning; +use crate::config::AStrMapExt; use crate::overlays::anchor::ANCHOR_NAME; use crate::state::{AppSession, AppState, KeyboardFocus}; @@ -334,17 +335,10 @@ fn interact_hand( where O: Default, { - let hmd = app.input_state.hmd; let mut pointer = &mut app.input_state.pointers[idx]; if let Some(grab_data) = pointer.interaction.grabbed { if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) { - pointer.handle_grabbed( - grabbed, - &hmd, - &app.anchor, - &mut app.tasks, - &mut app.session.config, - ); + Pointer::handle_grabbed(idx, grabbed, app); } else { log::warn!("Grabbed overlay {} does not exist", grab_data.grabbed_id.0); pointer.interaction.grabbed = None; @@ -537,6 +531,13 @@ impl Pointer { old_curvature: overlay.state.curvature, grab_all: matches!(self.interaction.mode, PointerMode::Right), }); + overlay.state.positioning = match overlay.state.positioning { + Positioning::FollowHand { hand, lerp } => Positioning::FollowHandPaused { hand, lerp }, + Positioning::FollowHead { lerp } => Positioning::FollowHeadPaused { lerp }, + x => x, + }; + + // Show anchor tasks.enqueue(TaskType::Overlay( OverlaySelector::Name(ANCHOR_NAME.clone()), Box::new(|app, o| { @@ -553,25 +554,20 @@ impl Pointer { log::info!("Hand {}: grabbed {}", self.idx, overlay.state.name); } - fn handle_grabbed( - &mut self, - overlay: &mut OverlayData, - hmd: &Affine3A, - anchor: &Affine3A, - tasks: &mut TaskContainer, - config: &mut GeneralConfig, - ) where + fn handle_grabbed(idx: usize, overlay: &mut OverlayData, app: &mut AppState) + where O: Default, { - if self.now.grab { - if let Some(grab_data) = self.interaction.grabbed.as_mut() { - if self.now.click { - self.interaction.mode = PointerMode::Special; + let mut pointer = &mut app.input_state.pointers[idx]; + if pointer.now.grab { + if let Some(grab_data) = pointer.interaction.grabbed.as_mut() { + if pointer.now.click { + pointer.interaction.mode = PointerMode::Special; let cur_scale = overlay.state.transform.x_axis.length(); - if cur_scale < 0.1 && self.now.scroll_y > 0.0 { + if cur_scale < 0.1 && pointer.now.scroll_y > 0.0 { return; } - if cur_scale > 20. && self.now.scroll_y < 0.0 { + if cur_scale > 20. && pointer.now.scroll_y < 0.0 { return; } @@ -579,46 +575,62 @@ impl Pointer { .state .transform .matrix3 - .mul_scalar(0.025f32.mul_add(-self.now.scroll_y, 1.0)); - } else if config.allow_sliding && self.now.scroll_y.is_finite() { - grab_data.offset.z -= self.now.scroll_y * 0.05; + .mul_scalar(0.025f32.mul_add(-pointer.now.scroll_y, 1.0)); + } else if app.session.config.allow_sliding && pointer.now.scroll_y.is_finite() { + grab_data.offset.z -= pointer.now.scroll_y * 0.05; } - overlay.state.transform.translation = self.pose.transform_point3a(grab_data.offset); - overlay.state.realign(hmd); + overlay.state.transform.translation = + pointer.pose.transform_point3a(grab_data.offset); + overlay.state.realign(&app.input_state.hmd); overlay.state.dirty = true; } else { log::error!("Grabbed overlay {} does not exist", overlay.state.id.0); - self.interaction.grabbed = None; + pointer.interaction.grabbed = None; } } else { - if overlay.state.anchored { - overlay.state.saved_transform = - Some(snap_upright(*anchor, Vec3A::Y).inverse() * overlay.state.transform); + overlay.state.positioning = match overlay.state.positioning { + Positioning::FollowHandPaused { hand, lerp } => { + Positioning::FollowHand { hand, lerp } + } + Positioning::FollowHeadPaused { lerp } => Positioning::FollowHead { lerp }, + x => x, + }; - if let Some(grab_data) = self.interaction.grabbed.as_ref() { + let save_success = overlay.state.save_transform(app); + + // re-borrow + pointer = &mut app.input_state.pointers[idx]; + + if save_success { + if let Some(grab_data) = pointer.interaction.grabbed.as_ref() { if overlay.state.curvature != grab_data.old_curvature { if let Some(val) = overlay.state.curvature { - config.curve_values.arc_set(overlay.state.name.clone(), val); + app.session + .config + .curve_values + .arc_set(overlay.state.name.clone(), val); } else { let ref_name = overlay.state.name.as_ref(); - config.curve_values.arc_rm(ref_name); + app.session.config.curve_values.arc_rm(ref_name); } } } - config.transform_values.arc_set( + app.session.config.transform_values.arc_set( overlay.state.name.clone(), overlay.state.saved_transform.unwrap(), // safe ); } - self.interaction.grabbed = None; - tasks.enqueue(TaskType::Overlay( + pointer.interaction.grabbed = None; + + // Hide anchor + app.tasks.enqueue(TaskType::Overlay( OverlaySelector::Name(ANCHOR_NAME.clone()), Box::new(|_app, o| { o.want_visible = false; }), )); - log::info!("Hand {}: dropped {}", self.idx, overlay.state.name); + log::info!("Hand {}: dropped {}", idx, overlay.state.name); } } diff --git a/src/backend/overlay.rs b/src/backend/overlay.rs index 81ab789..d96614e 100644 --- a/src/backend/overlay.rs +++ b/src/backend/overlay.rs @@ -17,7 +17,10 @@ use crate::{ state::{AppState, KeyboardFocus}, }; -use super::input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit}; +use super::{ + common::snap_upright, + input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit}, +}; static OVERLAY_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); @@ -44,7 +47,6 @@ pub struct OverlayState { pub grabbable: bool, pub interactable: bool, pub recenter: bool, - pub anchored: bool, pub keyboard_focus: Option, pub dirty: bool, pub alpha: f32, @@ -54,7 +56,7 @@ pub struct OverlayState { pub spawn_point: Vec3A, pub spawn_rotation: Quat, pub saved_transform: Option, - pub relative_to: RelativeTo, + pub positioning: Positioning, pub curvature: Option, pub interaction_transform: Affine2, pub birthframe: usize, @@ -70,12 +72,11 @@ impl Default for OverlayState { grabbable: false, recenter: false, interactable: false, - anchored: false, keyboard_focus: None, dirty: true, alpha: 1.0, z_order: Z_ORDER_DEFAULT, - relative_to: RelativeTo::None, + positioning: Positioning::Floating, curvature: None, spawn_scale: 1.0, spawn_point: Vec3A::NEG_Z, @@ -113,23 +114,6 @@ where } impl OverlayState { - pub const fn parent_transform(&self, app: &AppState) -> Option { - match self.relative_to { - RelativeTo::Head => Some(app.input_state.hmd), - RelativeTo::Hand(idx) => Some(app.input_state.pointers[idx].pose), - _ => None, - } - } - - const fn get_anchor(&self, app: &AppState) -> Affine3A { - if self.anchored { - app.anchor - } else { - // fake anchor that's always in front of HMD - app.input_state.hmd - } - } - fn get_transform(&self) -> Affine3A { self.saved_transform.unwrap_or_else(|| { Affine3A::from_scale_rotation_translation( @@ -141,21 +125,57 @@ impl OverlayState { } pub fn auto_movement(&mut self, app: &mut AppState) { - if let Some(parent) = self.parent_transform(app) { - self.transform = parent * self.get_transform(); - self.dirty = true; - } + let (target_transform, lerp) = match self.positioning { + Positioning::FollowHead { lerp } => (app.input_state.hmd * self.get_transform(), lerp), + Positioning::FollowHand { hand, lerp } => ( + app.input_state.pointers[hand].pose * self.get_transform(), + lerp, + ), + _ => return, + }; + + self.transform = match lerp { + 1.0 => target_transform, + lerp => { + let scale = target_transform.matrix3.x_axis.length(); + + let rot_from = Quat::from_mat3a(&self.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 = self + .transform + .translation + .slerp(target_transform.translation, lerp); + + Affine3A::from_scale_rotation_translation( + Vec3::ONE * scale, + rotation, + translation.into(), + ) + } + }; + + self.dirty = true; } pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) { + let parent_transform = match self.positioning { + Positioning::Floating + | Positioning::FollowHead { .. } + | Positioning::FollowHeadPaused { .. } => app.input_state.hmd, + Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => { + app.input_state.pointers[hand].pose + } + Positioning::Anchored => app.anchor, + Positioning::Static => return, + }; + if hard_reset { self.saved_transform = None; } - self.transform = self - .parent_transform(app) - .unwrap_or_else(|| self.get_anchor(app)) - * self.get_transform(); + self.transform = parent_transform * self.get_transform(); if self.grabbable && hard_reset { self.realign(&app.input_state.hmd); @@ -163,6 +183,24 @@ impl OverlayState { self.dirty = true; } + pub fn save_transform(&mut self, app: &mut AppState) -> bool { + let parent_transform = match self.positioning { + Positioning::Floating => snap_upright(app.input_state.hmd, Vec3A::Y), + Positioning::FollowHead { .. } | Positioning::FollowHeadPaused { .. } => { + app.input_state.hmd + } + Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => { + app.input_state.pointers[hand].pose + } + Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y), + Positioning::Static => return false, + }; + + self.saved_transform = Some(parent_transform.inverse() * self.transform); + + true + } + pub fn realign(&mut self, hmd: &Affine3A) { let to_hmd = hmd.translation - self.transform.translation; let up_dir: Vec3A; @@ -218,7 +256,10 @@ where .copied(); } - if matches!(self.state.relative_to, RelativeTo::None) { + if matches!( + self.state.positioning, + Positioning::Floating | Positioning::Anchored + ) { let hard_reset; if let Some(transform) = app .session @@ -321,16 +362,22 @@ impl OverlayRenderer for FallbackRenderer { // Boilerplate and dummies #[derive(Clone, Copy, Debug, Default)] -pub enum RelativeTo { - /// Stays in place unless rencentered +pub enum Positioning { + /// Stays in place unless recentered, recenters relative to HMD #[default] - None, - /// Stays in position relative to HMD - Head, - /// Stays in position relative to hand - Hand(usize), + Floating, + /// Stays in place unless recentered, recenters relative to anchor + Anchored, + /// Following HMD + FollowHead { lerp: f32 }, + /// Normally follows HMD, but paused due to interaction + FollowHeadPaused { lerp: f32 }, + /// Following hand + FollowHand { hand: usize, lerp: f32 }, + /// Normally follows hand, but paused due to interaction + FollowHandPaused { hand: usize, lerp: f32 }, /// Stays in place, no recentering - Stage, + Static, } pub struct SplitOverlayBackend { diff --git a/src/config_wayvr.rs b/src/config_wayvr.rs index 7cba0d7..c1b1507 100644 --- a/src/config_wayvr.rs +++ b/src/config_wayvr.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use crate::{ backend::{ - overlay::RelativeTo, + overlay::Positioning, task::{TaskContainer, TaskType}, wayvr, }, @@ -33,13 +33,14 @@ pub enum AttachTo { } impl AttachTo { - pub const fn get_relative_to(&self) -> RelativeTo { + // TODO: adjustable lerp factor + pub const fn get_positioning(&self) -> Positioning { match self { - Self::None => RelativeTo::None, - Self::HandLeft => RelativeTo::Hand(0), - Self::HandRight => RelativeTo::Hand(1), - Self::Stage => RelativeTo::Stage, - Self::Head => RelativeTo::Head, + Self::None => Positioning::Floating, + Self::HandLeft => Positioning::FollowHand { hand: 0, lerp: 1.0 }, + Self::HandRight => Positioning::FollowHand { hand: 1, lerp: 1.0 }, + Self::Stage => Positioning::Static, + Self::Head => Positioning::FollowHead { lerp: 1.0 }, } } diff --git a/src/gui/modular/button.rs b/src/gui/modular/button.rs index 09e008c..44bd062 100644 --- a/src/gui/modular/button.rs +++ b/src/gui/modular/button.rs @@ -13,7 +13,7 @@ use crate::{ backend::{ common::OverlaySelector, input::PointerMode, - overlay::RelativeTo, + overlay::Positioning, task::{ColorChannel, SystemTask, TaskType}, }, config::{save_layout, save_settings, AStrSetExt}, @@ -626,15 +626,15 @@ fn run_watch(data: &WatchAction, app: &mut AppState) { app.tasks.enqueue(TaskType::Overlay( OverlaySelector::Name(WATCH_NAME.into()), Box::new(|app, o| { - if matches!(o.relative_to, RelativeTo::Hand(0)) { - o.relative_to = RelativeTo::Hand(1); + if matches!(o.positioning, Positioning::FollowHand { hand: 0, .. }) { + o.positioning = Positioning::FollowHand { hand: 1, lerp: 1.0 }; o.spawn_rotation = app.session.config.watch_rot * Quat::from_rotation_x(PI) * Quat::from_rotation_z(PI); o.spawn_point = app.session.config.watch_pos; o.spawn_point.x *= -1.; } else { - o.relative_to = RelativeTo::Hand(0); + o.positioning = Positioning::FollowHand { hand: 0, lerp: 1.0 }; o.spawn_rotation = app.session.config.watch_rot; o.spawn_point = app.session.config.watch_pos; } diff --git a/src/overlays/anchor.rs b/src/overlays/anchor.rs index e893ee6..a473763 100644 --- a/src/overlays/anchor.rs +++ b/src/overlays/anchor.rs @@ -1,7 +1,7 @@ use glam::Vec3A; use std::sync::{Arc, LazyLock}; -use crate::backend::overlay::{OverlayData, OverlayState, RelativeTo, Z_ORDER_ANCHOR}; +use crate::backend::overlay::{OverlayData, OverlayState, Positioning, Z_ORDER_ANCHOR}; use crate::config::{load_known_yaml, ConfigType}; use crate::gui::modular::{modular_canvas, ModularUiConfig}; use crate::state::AppState; @@ -23,7 +23,7 @@ where z_order: Z_ORDER_ANCHOR, spawn_scale: config.width, spawn_point: Vec3A::NEG_Z * 0.5, - relative_to: RelativeTo::Stage, + positioning: Positioning::Static, ..Default::default() }, backend: Box::new(modular_canvas(config.size, &config.elements, state)?), diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index d346e2b..aa32415 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -9,7 +9,8 @@ use crate::{ backend::{ input::{InteractionHandler, PointerMode}, overlay::{ - FrameMeta, OverlayBackend, OverlayData, OverlayRenderer, OverlayState, ShouldRender, + FrameMeta, OverlayBackend, OverlayData, OverlayRenderer, OverlayState, Positioning, + ShouldRender, }, }, config::{self, ConfigType}, @@ -222,7 +223,7 @@ where name: KEYBOARD_NAME.into(), grabbable: true, recenter: true, - anchored: true, + positioning: Positioning::Anchored, interactable: true, spawn_scale: width, spawn_point: vec3a(0., -0.5, 0.), diff --git a/src/overlays/screen.rs b/src/overlays/screen.rs index d7c2478..69598f7 100644 --- a/src/overlays/screen.rs +++ b/src/overlays/screen.rs @@ -50,7 +50,10 @@ use glam::{vec2, vec3a, Affine2, Affine3A, Quat, Vec2, Vec3}; use crate::{ backend::{ input::{Haptics, InteractionHandler, PointerHit, PointerMode}, - overlay::{FrameMeta, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend}, + overlay::{ + FrameMeta, OverlayRenderer, OverlayState, Positioning, ShouldRender, + SplitOverlayBackend, + }, }, config::{def_pw_tokens, GeneralConfig, PwTokenMap}, graphics::{fourcc_to_vk, CommandBuffers, WlxGraphics, WlxPipeline, WlxUploadsBuffer}, @@ -718,7 +721,7 @@ fn create_screen_state( keyboard_focus: Some(KeyboardFocus::PhysicalScreen), grabbable: true, recenter: true, - anchored: true, + positioning: Positioning::Anchored, interactable: true, spawn_scale: 1.5 * session.config.desktop_view_scale, spawn_point: vec3a(0., 0.5, 0.), diff --git a/src/overlays/toast.rs b/src/overlays/toast.rs index 7ff24bf..7ed0893 100644 --- a/src/overlays/toast.rs +++ b/src/overlays/toast.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use crate::{ backend::{ common::OverlaySelector, - overlay::{OverlayBackend, OverlayState, RelativeTo, Z_ORDER_TOAST}, + overlay::{OverlayBackend, OverlayState, Positioning, Z_ORDER_TOAST}, task::TaskType, }, gui::{canvas::builder::CanvasBuilder, color_parse}, @@ -125,18 +125,22 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box return None, - DisplayMethod::Center => (vec3a(0., -0.2, -0.5), Quat::IDENTITY, RelativeTo::Head), + DisplayMethod::Center => ( + vec3a(0., -0.2, -0.5), + Quat::IDENTITY, + Positioning::FollowHead { lerp: 0.1 }, + ), DisplayMethod::Watch => { let mut watch_pos = app.session.config.watch_pos + vec3a(-0.005, -0.05, 0.02); let mut watch_rot = app.session.config.watch_rot; let relative_to = match app.session.config.watch_hand { - LeftRight::Left => RelativeTo::Hand(0), + LeftRight::Left => Positioning::FollowHand { hand: 0, lerp: 1.0 }, LeftRight::Right => { watch_pos.x = -watch_pos.x; watch_rot = watch_rot * Quat::from_rotation_x(PI) * Quat::from_rotation_z(PI); - RelativeTo::Hand(1) + Positioning::FollowHand { hand: 1, lerp: 1.0 } } }; (watch_pos, watch_rot, relative_to) @@ -202,7 +206,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box(ConfigType::Watch); - let relative_to = RelativeTo::Hand(state.session.config.watch_hand as usize); + let positioning = Positioning::FollowHand { + hand: state.session.config.watch_hand as _, + lerp: 1.0, + }; Ok(OverlayData { state: OverlayState { @@ -30,7 +33,7 @@ where spawn_point: state.session.config.watch_pos, spawn_rotation: state.session.config.watch_rot, interaction_transform: ui_transform(config.size), - relative_to, + positioning, ..Default::default() }, backend: Box::new(create_watch_canvas(Some(config), state)?), diff --git a/src/overlays/wayvr.rs b/src/overlays/wayvr.rs index 9926f27..2604126 100644 --- a/src/overlays/wayvr.rs +++ b/src/overlays/wayvr.rs @@ -402,7 +402,7 @@ where .insert(disp_handle, overlay.state.id); if let Some(attach_to) = &conf_display.attach_to { - overlay.state.relative_to = attach_to.get_relative_to(); + overlay.state.positioning = attach_to.get_positioning(); } if let Some(rot) = &conf_display.rotation {