relative positioning rework

This commit is contained in:
galister
2025-05-02 02:39:29 +09:00
parent 96a05d0835
commit fbb16eccf6
10 changed files with 177 additions and 106 deletions

View File

@@ -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<O>(
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<O>(
&mut self,
overlay: &mut OverlayData<O>,
hmd: &Affine3A,
anchor: &Affine3A,
tasks: &mut TaskContainer,
config: &mut GeneralConfig,
) where
fn handle_grabbed<O>(idx: usize, overlay: &mut OverlayData<O>, 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);
}
}

View File

@@ -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<KeyboardFocus>,
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<Affine3A>,
pub relative_to: RelativeTo,
pub positioning: Positioning,
pub curvature: Option<f32>,
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<Affine3A> {
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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<dyn
.copied()
.unwrap_or(DisplayMethod::Hide);
let (spawn_point, spawn_rotation, relative_to) = match current_method {
let (spawn_point, spawn_rotation, positioning) = match current_method {
DisplayMethod::Hide => 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<dyn
spawn_rotation,
spawn_point,
z_order: Z_ORDER_TOAST,
relative_to,
positioning,
..Default::default()
};
let backend = Box::new(canvas.build());

View File

@@ -1,7 +1,7 @@
use glam::Vec3A;
use crate::{
backend::overlay::{ui_transform, OverlayData, OverlayState, RelativeTo, Z_ORDER_WATCH},
backend::overlay::{ui_transform, OverlayData, OverlayState, Positioning, Z_ORDER_WATCH},
config::{load_known_yaml, ConfigType},
gui::{
canvas::Canvas,
@@ -18,7 +18,10 @@ where
{
let config = load_known_yaml::<ModularUiConfig>(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)?),

View File

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