wayvr: Space gravity (wip)

This commit is contained in:
Aleksander 2026-05-19 22:46:43 +02:00
parent 9806027e19
commit 103442e2be
12 changed files with 145 additions and 13 deletions

View File

@ -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",

View File

@ -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"),

View File

@ -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

View File

@ -1,4 +1,5 @@
pub mod input;
pub mod playspace_common;
#[cfg(feature = "openvr")]
pub mod openvr;

View File

@ -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) {

View File

@ -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) => {

View File

@ -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<T> {
pub(super) struct PlayspaceMover {
drag: Option<MoverData<Vec3A>>,
rotate: Option<MoverData<Quat>>,
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) {

View File

@ -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<Vec3A> {
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
}
}

View File

@ -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,

View File

@ -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

View File

@ -71,6 +71,8 @@ pub struct AppState {
#[cfg(feature = "openxr")]
pub monado_state: Option<backend::openxr::monado_state::MonadoState>,
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 {

View File

@ -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<String>,