anchor grab

This commit is contained in:
galister
2025-11-19 15:48:06 +09:00
parent 127cb5c8d0
commit 5b40032bc3
6 changed files with 177 additions and 131 deletions

View File

@@ -11,7 +11,7 @@ use crate::state::{AppSession, AppState};
use crate::subsystem::hid::WheelDelta;
use crate::subsystem::input::KeyboardFocus;
use crate::windowing::manager::OverlayWindowManager;
use crate::windowing::window::{OverlayWindowData, OverlayWindowState, Positioning};
use crate::windowing::window::{realign, OverlayWindowData, OverlayWindowState, Positioning};
use crate::windowing::{OverlayID, OverlaySelector};
use super::task::{TaskContainer, TaskType};
@@ -274,8 +274,7 @@ struct RayHit {
pub struct GrabData {
pub offset: Vec3A,
pub grabbed_id: OverlayID,
pub old_curvature: Option<f32>,
pub grab_all: bool,
pub grab_anchor: bool,
}
#[repr(u8)]
@@ -327,7 +326,7 @@ where
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(idx, grabbed, app);
handle_grabbed(idx, grabbed, app);
} else {
log::warn!("Grabbed overlay {:?} does not exist", grab_data.grabbed_id);
pointer.interaction.grabbed = None;
@@ -356,6 +355,8 @@ where
}
overlays.edit_overlay(hit.overlay, true, app);
let edit_mode = overlays.get_edit_mode();
let Some(hovered) = overlays.mut_by_id(hit.overlay) else {
log::warn!("Hit overlay {:?} does not exist", hit.overlay);
return (0.0, None); // no hit
@@ -384,7 +385,7 @@ where
&mut app.hid_provider.keyboard_focus,
hovered.config.keyboard_focus,
);
pointer.start_grab(hit.overlay, hovered_state, &mut app.tasks);
start_grab(idx, hit.overlay, hovered_state, app, edit_mode);
log::debug!("Hand {}: grabbed {}", hit.pointer, hovered.config.name);
return (
hit.dist,
@@ -581,84 +582,128 @@ where
(None, None)
}
impl Pointer {
fn start_grab(
&mut self,
idx: usize,
id: OverlayID,
state: &mut OverlayWindowState,
tasks: &mut TaskContainer,
app: &mut AppState,
edit_mode: bool,
) {
let offset = self
let pointer = &mut app.input_state.pointers[idx];
// Grab anchor if:
// - grabbed overlay is Anchored
// - not in editmode
// - grabbing with one hand. (grabbing with the 2nd hand will grab the individual overlay instead)
let grab_anchor =
!edit_mode && !app.anchor_grabbed && matches!(state.positioning, Positioning::Anchored);
let relative_grab_point = if grab_anchor {
app.anchor.translation
} else {
state.transform.translation
};
let offset = pointer
.pose
.inverse()
.transform_point3a(state.transform.translation);
.transform_point3a(relative_grab_point);
self.interaction.grabbed = Some(GrabData {
app.anchor_grabbed = grab_anchor;
pointer.interaction.grabbed = Some(GrabData {
offset,
grabbed_id: id,
old_curvature: state.curvature,
grab_all: matches!(self.interaction.mode, PointerMode::Right),
grab_anchor,
});
state.positioning = match state.positioning {
Positioning::FollowHand { hand, lerp } => Positioning::FollowHandPaused { hand, lerp },
Positioning::FollowHead { lerp } => Positioning::FollowHeadPaused { lerp },
Positioning::Anchored if !grab_anchor => Positioning::AnchoredPaused,
x => x,
};
// Show anchor
tasks.enqueue(TaskType::Overlay(
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(ANCHOR_NAME.clone()),
Box::new(|app, o| {
o.activate_static(app.anchor * Affine3A::from_scale(Vec3::ONE * 0.1));
o.activate(app);
}),
));
}
fn handle_scale(transform: &mut Affine3A, scroll_y: f32) {
let cur_scale = transform.x_axis.length();
if cur_scale < 0.1 && scroll_y > 0.0 {
return;
}
if cur_scale > 20. && scroll_y < 0.0 {
return;
}
transform.matrix3 = transform
.matrix3
.mul_scalar(0.025f32.mul_add(-scroll_y, 1.0));
}
fn handle_grabbed<O>(idx: usize, overlay: &mut OverlayWindowData<O>, app: &mut AppState)
where
O: Default,
{
let pointer = &mut app.input_state.pointers[idx];
let Some(grab_data) = pointer.interaction.grabbed.as_mut() else {
log::error!("Grabbed overlay does not exist");
return;
};
let grab_anchor = grab_data.grab_anchor;
let Some(overlay_state) = overlay.config.active_state.as_mut() else {
return;
};
let pointer = &mut app.input_state.pointers[idx];
if pointer.now.grab {
if let Some(grab_data) = pointer.interaction.grabbed.as_mut() {
if grab_anchor {
if pointer.now.click {
pointer.interaction.mode = PointerMode::Special;
let cur_scale = overlay_state.transform.x_axis.length();
if cur_scale < 0.1 && pointer.now.scroll_y > 0.0 {
return;
}
if cur_scale > 20. && pointer.now.scroll_y < 0.0 {
return;
}
overlay_state.transform.matrix3 = overlay_state
.transform
.matrix3
.mul_scalar(0.025f32.mul_add(-pointer.now.scroll_y, 1.0));
handle_scale(&mut app.anchor, pointer.now.scroll_y);
} else if app.session.config.allow_sliding && pointer.now.scroll_y.is_finite() {
// single grab push/pull
grab_data.offset.z -= pointer.now.scroll_y * 0.05;
}
overlay_state.transform.translation =
pointer.pose.transform_point3a(grab_data.offset);
overlay.config.realign(&app.input_state.hmd);
app.anchor.translation = pointer.pose.transform_point3a(grab_data.offset);
realign(&mut app.anchor, &app.input_state.hmd);
} else {
log::error!("Grabbed overlay does not exist");
pointer.interaction.grabbed = None;
// single grab resize
if pointer.now.click {
pointer.interaction.mode = PointerMode::Special;
handle_scale(&mut overlay_state.transform, pointer.now.scroll_y);
} else if app.session.config.allow_sliding && pointer.now.scroll_y.is_finite() {
// single grab push/pull
grab_data.offset.z -= pointer.now.scroll_y * 0.05;
}
overlay_state.transform.translation = pointer.pose.transform_point3a(grab_data.offset);
realign(&mut overlay_state.transform, &app.input_state.hmd);
overlay.config.dirty = true;
}
} else {
// not now.grab
pointer.interaction.grabbed = None;
if grab_anchor {
app.anchor_grabbed = false;
} else {
// single grab released
overlay_state.positioning = match overlay_state.positioning {
Positioning::FollowHandPaused { hand, lerp } => {
Positioning::FollowHand { hand, lerp }
}
Positioning::FollowHeadPaused { lerp } => Positioning::FollowHead { lerp },
Positioning::AnchoredPaused => Positioning::Anchored,
x => x,
};
pointer.interaction.grabbed = None;
overlay_state.save_transform(app);
}
// Hide anchor
app.tasks.enqueue(TaskType::Overlay(
@@ -670,7 +715,6 @@ impl Pointer {
log::debug!("Hand {}: dropped {}", idx, overlay.config.name);
}
}
}
fn ray_test(
ray_origin: &Affine3A,

View File

@@ -3,8 +3,8 @@ use std::sync::{Arc, LazyLock};
use crate::gui::panel::GuiPanel;
use crate::state::AppState;
use crate::windowing::Z_ORDER_ANCHOR;
use crate::windowing::window::{OverlayWindowConfig, OverlayWindowState, Positioning};
use crate::windowing::Z_ORDER_ANCHOR;
pub static ANCHOR_NAME: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("anchor"));
@@ -18,11 +18,11 @@ pub fn create_anchor(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig>
default_state: OverlayWindowState {
interactable: false,
grabbable: false,
positioning: Positioning::Static,
positioning: Positioning::Anchored,
transform: Affine3A::from_scale_rotation_translation(
Vec3::ONE * 0.1,
Quat::IDENTITY,
Vec3::NEG_Z * 0.5,
Vec3::ZERO, // Vec3::NEG_Z * 0.5,
),
..OverlayWindowState::default()
},

View File

@@ -19,16 +19,16 @@ use wgui::{
use crate::{backend::task::TaskType, windowing::OverlaySelector};
use crate::{
backend::{input::HoverResult, task::TaskContainer},
gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS},
gui::panel::{button::BUTTON_EVENTS, GuiPanel, NewGuiPanelParams, OnCustomAttribFunc},
overlays::edit::{
lock::InteractLockHandler, pos::PositioningHandler, tab::ButtonPaneTabSwitcher,
},
state::AppState,
subsystem::hid::WheelDelta,
windowing::{
OverlayID,
backend::{DummyBackend, OverlayBackend, RenderResources, ShouldRender},
window::OverlayWindowConfig,
OverlayID,
},
};

View File

@@ -131,7 +131,7 @@ fn key_to_pos(key: &str) -> Positioning {
const fn pos_to_key(pos: Positioning) -> &'static str {
match pos {
Positioning::Static => "static",
Positioning::Anchored => "anchored",
Positioning::Anchored | Positioning::AnchoredPaused => "anchored",
Positioning::Floating => "floating",
Positioning::FollowHead { .. } | Positioning::FollowHeadPaused { .. } => "hmd",
Positioning::FollowHand {

View File

@@ -42,6 +42,7 @@ pub struct AppState {
pub input_state: InputState,
pub screens: SmallVec<[ScreenMeta; 8]>,
pub anchor: Affine3A,
pub anchor_grabbed: bool,
pub toast_sound: &'static [u8],
pub wgui_globals: WguiGlobals,
@@ -94,6 +95,7 @@ impl AppState {
input_state: InputState::new(),
screens: smallvec![],
anchor: Affine3A::IDENTITY,
anchor_grabbed: false,
toast_sound: toast_sound_wav,
wgui_globals: WguiGlobals::new(
Box::new(gui::asset::GuiAsset {}),

View File

@@ -16,8 +16,10 @@ pub enum Positioning {
/// Stays in place, recenters relative to HMD
#[default]
Floating,
/// Stays in place, recenters relative to anchor
/// Stays in place, recenters relative to anchor. Follows anchor during anchor grab.
Anchored,
/// Same as anchor but paused due to interaction
AnchoredPaused,
/// Stays in place, no recentering
Static,
/// Following HMD
@@ -151,15 +153,17 @@ impl OverlayWindowConfig {
.saved_transform
.unwrap_or(self.default_state.transform);
let (target_transform, lerp) = match state.positioning {
Positioning::FollowHead { lerp } => (app.input_state.hmd * cur_transform, lerp),
Positioning::FollowHand { hand, lerp } => (
app.input_state.pointers[hand as usize].pose * cur_transform,
lerp,
),
let (parent_transform, lerp) = match state.positioning {
Positioning::FollowHead { lerp } => (app.input_state.hmd, lerp),
Positioning::FollowHand { hand, lerp } => {
(app.input_state.pointers[hand as usize].pose, lerp)
}
Positioning::Anchored => (app.anchor, 1.0),
_ => return,
};
let target_transform = parent_transform * cur_transform;
state.transform = match lerp {
1.0 => target_transform,
lerp => {
@@ -201,7 +205,7 @@ impl OverlayWindowConfig {
Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => {
app.input_state.pointers[hand as usize].pose
}
Positioning::Anchored => app.anchor,
Positioning::Anchored | Positioning::AnchoredPaused => app.anchor,
Positioning::Static => return,
};
@@ -212,17 +216,14 @@ impl OverlayWindowConfig {
state.transform = parent_transform * cur_transform;
if state.grabbable && hard_reset {
self.realign(&app.input_state.hmd);
realign(&mut state.transform, &app.input_state.hmd);
}
self.dirty = true;
}
}
pub fn realign(&mut self, hmd: &Affine3A) {
let Some(state) = self.active_state.as_mut() else {
return;
};
let to_hmd = hmd.translation - state.transform.translation;
pub fn realign(transform: &mut Affine3A, hmd: &Affine3A) {
let to_hmd = hmd.translation - transform.translation;
let up_dir: Vec3A;
if hmd.x_axis.dot(Vec3A::Y).abs() > 0.2 {
@@ -231,36 +232,33 @@ impl OverlayWindowConfig {
} else {
let dot = to_hmd.normalize().dot(hmd.z_axis);
let z_dist = to_hmd.length();
let y_dist = (state.transform.translation.y - hmd.translation.y).abs();
let y_dist = (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 - state.transform.translation).normalize();
up_dir = (up_point - 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 = (state.transform.translation - dn_point).normalize();
up_dir = (transform.translation - dn_point).normalize();
} else {
// perfectly upright
up_dir = Vec3A::Y;
}
}
let scale = state.transform.x_axis.length();
let scale = transform.x_axis.length();
let col_z = (state.transform.translation - hmd.translation).normalize();
let col_z = (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(Quat::from_axis_angle(Vec3::Y, PI));
state.transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot;
self.dirty = true;
}
transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot;
}
// Contains the window state for a given set
@@ -302,7 +300,9 @@ impl OverlayWindowState {
Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => {
app.input_state.pointers[hand as usize].pose
}
Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y),
Positioning::Anchored | Positioning::AnchoredPaused => {
snap_upright(app.anchor, Vec3A::Y)
}
Positioning::Static => return false,
};