diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index e8091cb7..e489df25 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -104,6 +104,9 @@ "SETS_ON_WATCH": "Sets on watch", "SKYBOX": "Skybox", "SKYMAP_ALREADY_DOWNLOADED": "This skymap is already downloaded. Select desired action.", + "SPACE_DRAG_FLING_STRENGTH": "Fling strength", + "SPACE_DRAG_DAMPING": "Damping", + "SPACE_DRAG_GRAVITY": "Gravity", "SPACE_DRAG_MULTIPLIER": "Space drag multiplier", "SPACE_DRAG_UNLOCKED": "Allow space drag on all axes", "SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index eb66a6dc..d16f54df 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -276,6 +276,9 @@ enum SettingType { ScrollSpeed, EnableWatch, SetsOnWatch, + SpaceDragFlingStrength, + SpaceDragDamping, + SpaceDragGravity, SpaceDragMultiplier, SpaceDragUnlocked, SpaceRotateUnlocked, @@ -341,6 +344,9 @@ impl SettingType { Self::LongPressDuration => &mut config.long_press_duration, Self::XrClickSensitivity => &mut config.xr_click_sensitivity, Self::XrClickSensitivityRelease => &mut config.xr_click_sensitivity_release, + Self::SpaceDragFlingStrength => &mut config.space_drag_fling_strength, + Self::SpaceDragDamping => &mut config.space_drag_damping, + Self::SpaceDragGravity => &mut config.space_drag_gravity, Self::SpaceDragMultiplier => &mut config.space_drag_multiplier, Self::PointerLerpFactor => &mut config.pointer_lerp_factor, Self::GridOpacity => &mut config.grid_opacity, @@ -439,6 +445,9 @@ impl SettingType { Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"), Self::EnableWatch => Ok("APP_SETTINGS.ENABLE_WATCH"), Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"), + Self::SpaceDragFlingStrength => Ok("APP_SETTINGS.SPACE_DRAG_FLING_STRENGTH"), + Self::SpaceDragDamping => Ok("APP_SETTINGS.SPACE_DRAG_DAMPING"), + Self::SpaceDragGravity => Ok("APP_SETTINGS.SPACE_DRAG_GRAVITY"), Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"), Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"), Self::SpaceRotateUnlocked => Ok("APP_SETTINGS.SPACE_ROTATE_UNLOCKED"), diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 266a7334..042152e0 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -17,6 +17,9 @@ impl State { // monado or openvr options_checkbox(par.mp, c, SettingType::SpaceDragUnlocked)?; options_slider_f32(par.mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5)?; + options_slider_f32(par.mp, c, SettingType::SpaceDragGravity, 0.0, 10.0, 0.5)?; + options_slider_f32(par.mp, c, SettingType::SpaceDragDamping, 0.1, 1.0, 0.01)?; + options_slider_f32(par.mp, c, SettingType::SpaceDragFlingStrength, 0.0, 3.0, 0.1)?; } if par.feats.monado { // openvr can only ever rotate yaw diff --git a/wayvr/src/backend/mod.rs b/wayvr/src/backend/mod.rs index b352a879..b1ded4f3 100644 --- a/wayvr/src/backend/mod.rs +++ b/wayvr/src/backend/mod.rs @@ -1,4 +1,5 @@ pub mod input; +pub mod playspace_common; #[cfg(feature = "openvr")] pub mod openvr; diff --git a/wayvr/src/backend/openvr/mod.rs b/wayvr/src/backend/openvr/mod.rs index d1e3870d..a44696b1 100644 --- a/wayvr/src/backend/openvr/mod.rs +++ b/wayvr/src/backend/openvr/mod.rs @@ -146,8 +146,12 @@ pub fn openvr_run( let mut lines = LinePool::new(app.gfx.clone())?; let pointer_lines = [lines.allocate(), lines.allocate()]; let mut current_lines = Vec::with_capacity(2); + let mut last_frame_time = Instant::now(); 'main_loop: loop { + let now = Instant::now(); + app.delta_time = (now.duration_since(last_frame_time).as_secs_f32()).clamp(0.001, 0.2); // 5 - 1000 fps + last_frame_time = now; let _ = overlay_mgr.wait_frame_sync(frame_timeout); if !RUNNING.load(Ordering::Relaxed) { diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index 75294aea..ceecfc74 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -94,7 +94,7 @@ pub fn openxr_run( app.monado_state_init(); - let mut playspace = app.monado_state.as_mut().and_then(|m| { + let mut playspace_mover = app.monado_state.as_mut().and_then(|m| { playspace::PlayspaceMover::new(&mut m.ipc) .map_err(|e| log::warn!("Will not use Monado playspace mover: {e}")) .ok() @@ -155,8 +155,12 @@ pub fn openxr_run( let mut main_session_visible = false; let mut environment_blend_mode = modes[0]; + let mut last_frame_time = Instant::now(); 'main_loop: loop { + let now = Instant::now(); + app.delta_time = (now.duration_since(last_frame_time).as_secs_f32()).clamp(0.001, 0.2); // 5 - 1000 fps + last_frame_time = now; let cur_frame = FRAME_COUNTER.fetch_add(1, Ordering::Relaxed); if !RUNNING.load(Ordering::Relaxed) { @@ -296,8 +300,8 @@ pub fn openxr_run( .enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard)); } - if let Some(ref mut space_mover) = playspace { - space_mover.update(&mut overlays, &mut app); + if let Some(ref mut playspace_mover) = playspace_mover { + playspace_mover.update(&mut overlays, &mut app); } for o in overlays.values_mut() { @@ -484,8 +488,8 @@ pub fn openxr_run( overlays.handle_task(&mut app, task)?; } TaskType::Playspace(task) => { - if let Some(playspace) = playspace.as_mut() { - playspace.handle_task(&mut app, task); + if let Some(playspace_mover) = playspace_mover.as_mut() { + playspace_mover.handle_task(&mut app, task); } } TaskType::OpenXR(task) => { diff --git a/wayvr/src/backend/openxr/playspace.rs b/wayvr/src/backend/openxr/playspace.rs index 0f6d95b5..3b75b214 100644 --- a/wayvr/src/backend/openxr/playspace.rs +++ b/wayvr/src/backend/openxr/playspace.rs @@ -3,7 +3,11 @@ use libmonado::{MndResult, Monado, Pose, ReferenceSpaceType}; use wgui::log::LogErr; use crate::{ - backend::{input::InputState, task::PlayspaceTask}, + backend::{ + input::InputState, + playspace_common::{SpaceGravity, SpaceGravityUpdateParams}, + task::PlayspaceTask, + }, state::AppState, windowing::manager::OverlayWindowManager, }; @@ -19,6 +23,7 @@ struct MoverData { pub(super) struct PlayspaceMover { drag: Option>, rotate: Option>, + gravity: SpaceGravity, } impl PlayspaceMover { @@ -35,6 +40,7 @@ impl PlayspaceMover { Ok(Self { drag: None, rotate: None, + gravity: SpaceGravity::new(), }) } @@ -140,21 +146,27 @@ impl PlayspaceMover { } if let Some(mut data) = self.drag.take() { - let pointer = &app.input_state.pointers[data.hand]; - if !pointer.now.space_drag { - log::info!("End space drag"); - return; - } - let new_hand = data .pose .transform_point3a(app.input_state.pointers[data.hand].raw_pose.translation); - let relative_pos = if app.session.config.space_drag_unlocked { new_hand - data.hand_pose } else { vec3a(0., new_hand.y - data.hand_pose.y, 0.) } * app.session.config.space_drag_multiplier; + let pointer = &app.input_state.pointers[data.hand]; + + if !pointer.now.space_drag { + self.gravity.mark_end_drag( + &app.session.config, + relative_pos, + data.pose.translation, + app.delta_time, + ); + + log::info!("End space drag"); + return; + } if relative_pos.length_squared() > 1000.0 { log::warn!("Space drag too fast, ignoring"); @@ -207,6 +219,17 @@ impl PlayspaceMover { } } } + + if let Some(playspace_pos) = self.gravity.update(SpaceGravityUpdateParams { + dt: app.delta_time, + dragging: self.drag.is_some(), + config: &app.session.config, + }) { + apply_offset( + Affine3A::from_translation(playspace_pos.into()), + &mut monado.ipc, + ); + } } pub fn recenter(&mut self, input: &InputState, monado: &mut Monado) { diff --git a/wayvr/src/backend/playspace_common.rs b/wayvr/src/backend/playspace_common.rs new file mode 100644 index 00000000..60b7d80e --- /dev/null +++ b/wayvr/src/backend/playspace_common.rs @@ -0,0 +1,53 @@ +use glam::Vec3A; +use wlx_common::config::GeneralConfig; + +pub struct SpaceGravityUpdateParams<'a> { + pub dt: f32, + pub dragging: bool, + pub config: &'a GeneralConfig, +} + +pub struct SpaceGravity { + velocity: Vec3A, + space_pos: Vec3A, +} + +impl SpaceGravity { + pub fn new() -> Self { + Self { + velocity: Vec3A::default(), + space_pos: Vec3A::default(), + } + } + + pub fn mark_end_drag( + &mut self, + config: &GeneralConfig, + hand_pos_diff: Vec3A, + space_pos: Vec3A, + dt: f32, + ) { + self.velocity = hand_pos_diff * config.space_drag_fling_strength / dt; + self.space_pos = space_pos; + } + + pub fn update(&mut self, par: SpaceGravityUpdateParams) -> Option { + if !par.dragging { + self.velocity.y += par.config.space_drag_gravity * par.dt; + // terminal velocity + self.velocity.y = self.velocity.y.min(200.0); + + self.velocity *= (par.config.space_drag_damping).powf(par.dt * 10.0); + self.space_pos += self.velocity * par.dt; + + self.space_pos.y = self.space_pos.y.min(0.0); + + if self.velocity.length_squared() > 0.00003 { + // log::info!("velocity {}", self.velocity); + return Some(self.space_pos); + } + } + + None + } +} diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index c440fd26..4727e9d1 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -167,6 +167,9 @@ pub struct AutoSettings { pub pointer_lerp_factor: f32, pub space_drag_unlocked: bool, pub space_rotate_unlocked: bool, + pub space_drag_gravity: f32, + pub space_drag_damping: f32, + pub space_drag_fling_strength: f32, pub clock_12h: bool, pub hide_username: bool, pub opaque_background: bool, @@ -223,6 +226,9 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { pointer_lerp_factor: config.pointer_lerp_factor, space_drag_unlocked: config.space_drag_unlocked, space_rotate_unlocked: config.space_rotate_unlocked, + space_drag_gravity: config.space_drag_gravity, + space_drag_damping: config.space_drag_damping, + space_drag_fling_strength: config.space_drag_fling_strength, clock_12h: config.clock_12h, hide_username: config.hide_username, opaque_background: config.opaque_background, diff --git a/wayvr/src/res/config.yaml b/wayvr/src/res/config.yaml index b9fd51af..5bff83f6 100644 --- a/wayvr/src/res/config.yaml +++ b/wayvr/src/res/config.yaml @@ -120,6 +120,15 @@ ## can rotate in any axis. Imagine horizon mode². #space_rotate_unlocked: false +## Space gravity: downward acceleration speed +#space_drag_gravity: 2.0 + +## Space gravity: velocity damping (0.98 = gentle slowdown, 0.5 = heavy drag) +#space_drag_damping: 0.98 + +## Space gravity: multiplier for "throwing" yourself via space drag momentum +#space_drag_fling_strength: 1.0 + ## Monado/WiVRn only. Use passthrough camera if the headset supports it. ## If disabled, the skybox will be shown. #use_passthrough: true diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index 49a5f3f1..ae4323f6 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -71,6 +71,8 @@ pub struct AppState { #[cfg(feature = "openxr")] pub monado_state: Option, + + pub delta_time: f32, } #[allow(unused_mut)] @@ -188,6 +190,8 @@ impl AppState { #[cfg(feature = "openxr")] monado_state: None, + + delta_time: 1.0 / 120.0, }; if let Some(error_toast) = hid_error { diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 1770356f..49f36edd 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -165,6 +165,10 @@ const fn def_point3() -> f32 { 0.3 } +const fn def_point98() -> f32 { + 0.98 +} + const fn def_osc_port() -> u16 { 9000 } @@ -339,6 +343,15 @@ pub struct GeneralConfig { #[serde(default = "def_false")] pub space_rotate_unlocked: bool, + #[serde(default = "def_one")] + pub space_drag_gravity: f32, + + #[serde(default = "def_point98")] + pub space_drag_damping: f32, + + #[serde(default = "def_one")] + pub space_drag_fling_strength: f32, + #[serde(default)] pub alt_click_down: Vec,