Skip to content
This repository was archived by the owner on Feb 16, 2025. It is now read-only.

Commit 1bec1a8

Browse files
committed
Add a gesture to open up a context menu; fix quitting
1 parent 72b30f2 commit 1bec1a8

File tree

2 files changed

+172
-22
lines changed

2 files changed

+172
-22
lines changed

webxr/openxr/input.rs

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,28 @@ pub struct OpenXRInput {
7575
action_grip_space: Space,
7676
action_click: Action<bool>,
7777
action_squeeze: Action<bool>,
78-
hand: &'static str,
78+
handedness: Handedness,
7979
click_state: ClickState,
8080
squeeze_state: ClickState,
81+
menu_gesture_sustain: u8,
82+
}
83+
84+
fn hand_str(h: Handedness) -> &'static str {
85+
match h {
86+
Handedness::Right => "right",
87+
Handedness::Left => "left",
88+
_ => panic!("We don't support unknown handedness in openxr"),
89+
}
8190
}
8291

8392
impl OpenXRInput {
8493
pub fn new(
8594
id: InputId,
86-
hand: Handedness,
95+
handedness: Handedness,
8796
action_set: &ActionSet,
8897
session: &Session<D3D11>,
8998
) -> Self {
90-
let hand = match hand {
91-
Handedness::Right => "right",
92-
Handedness::Left => "left",
93-
_ => panic!("We don't support unknown handedness in openxr"),
94-
};
99+
let hand = hand_str(handedness);
95100
let action_aim_pose: Action<Posef> = action_set
96101
.create_action(
97102
&format!("{}_hand_aim", hand),
@@ -134,9 +139,10 @@ impl OpenXRInput {
134139
action_grip_space,
135140
action_click,
136141
action_squeeze,
137-
hand,
142+
handedness,
138143
click_state: ClickState::Done,
139144
squeeze_state: ClickState::Done,
145+
menu_gesture_sustain: 0,
140146
}
141147
}
142148

@@ -182,23 +188,24 @@ impl OpenXRInput {
182188
select_name: &str,
183189
squeeze_name: Option<&str>,
184190
) -> Vec<Binding> {
191+
let hand = hand_str(self.handedness);
185192
let path_aim_pose = instance
186-
.string_to_path(&format!("/user/hand/{}/input/aim/pose", self.hand))
193+
.string_to_path(&format!("/user/hand/{}/input/aim/pose", hand))
187194
.unwrap();
188195
let binding_aim_pose = Binding::new(&self.action_aim_pose, path_aim_pose);
189196
let path_grip_pose = instance
190-
.string_to_path(&format!("/user/hand/{}/input/grip/pose", self.hand))
197+
.string_to_path(&format!("/user/hand/{}/input/grip/pose", hand))
191198
.unwrap();
192199
let binding_grip_pose = Binding::new(&self.action_grip_pose, path_grip_pose);
193200
let path_click = instance
194-
.string_to_path(&format!("/user/hand/{}/input/{}", self.hand, select_name))
201+
.string_to_path(&format!("/user/hand/{}/input/{}", hand, select_name))
195202
.unwrap();
196203
let binding_click = Binding::new(&self.action_click, path_click);
197204

198205
let mut ret = vec![binding_aim_pose, binding_grip_pose, binding_click];
199206
if let Some(squeeze_name) = squeeze_name {
200207
let path_squeeze = instance
201-
.string_to_path(&format!("/user/hand/{}/input/{}", self.hand, squeeze_name))
208+
.string_to_path(&format!("/user/hand/{}/input/{}", hand, squeeze_name))
202209
.unwrap();
203210
let binding_squeeze = Binding::new(&self.action_squeeze, path_squeeze);
204211
ret.push(binding_squeeze);
@@ -211,11 +218,53 @@ impl OpenXRInput {
211218
session: &Session<D3D11>,
212219
frame_state: &FrameState,
213220
base_space: &Space,
214-
) -> (InputFrame, Option<SelectEvent>, Option<SelectEvent>) {
221+
) -> (
222+
InputFrame,
223+
/* clicked */ Option<SelectEvent>,
224+
/* squeezed */ Option<SelectEvent>,
225+
/* menu_selected */ bool,
226+
) {
227+
use euclid::Vector3D;
215228
let target_ray_origin = pose_for(&self.action_aim_space, frame_state, base_space);
216229

217230
let grip_origin = pose_for(&self.action_grip_space, frame_state, base_space);
218231

232+
let mut menu_selected = false;
233+
// Check if the palm is facing up. This is our "menu" gesture.
234+
if let Some(grip_origin) = grip_origin {
235+
// The X axis of the grip is perpendicular to the palm, however it's
236+
// direction is the opposite for each hand
237+
//
238+
// We obtain a unit vector poking out of the palm
239+
let x_dir = if let Handedness::Left = self.handedness {
240+
1.0
241+
} else {
242+
-1.0
243+
};
244+
245+
// Rotate it by the grip to btain the desired vector
246+
let grip_x = grip_origin
247+
.rotation
248+
.transform_vector3d(Vector3D::new(x_dir, 0.0, 0.0));
249+
250+
// Dot product it with the "up" vector to see if it's pointing up
251+
let angle = grip_x.dot(Vector3D::new(0.0, 1.0, 0.0));
252+
253+
// If the angle is close enough to 0, its cosine will be
254+
// close to 1
255+
if angle > 0.9 {
256+
self.menu_gesture_sustain += 1;
257+
if self.menu_gesture_sustain > 60 {
258+
menu_selected = true;
259+
self.menu_gesture_sustain = 0;
260+
}
261+
} else {
262+
self.menu_gesture_sustain = 0;
263+
}
264+
} else {
265+
self.menu_gesture_sustain = 0;
266+
}
267+
219268
let click = self.action_click.state(session, Path::NULL).unwrap();
220269
let squeeze = self.action_squeeze.state(session, Path::NULL).unwrap();
221270

@@ -232,7 +281,12 @@ impl OpenXRInput {
232281
grip_origin,
233282
};
234283

235-
(input_frame, click_select_event, squeeze_select_event)
284+
(
285+
input_frame,
286+
click_select_event,
287+
squeeze_select_event,
288+
menu_selected,
289+
)
236290
}
237291
}
238292

webxr/openxr/mod.rs

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,58 @@ pub trait SurfaceProviderRegistration: Send {
6565
fn clone(&self) -> Box<dyn SurfaceProviderRegistration>;
6666
}
6767

68+
///
69+
pub trait ContextMenuProvider: Send {
70+
/// Open a context menu, return a way to poll for the result
71+
fn open_context_menu(&self) -> Box<dyn ContextMenuFuture>;
72+
/// Clone self as a trait object
73+
fn clone_object(&self) -> Box<dyn ContextMenuProvider>;
74+
}
75+
76+
/// A way to poll for the result of the context menu request
77+
pub trait ContextMenuFuture {
78+
fn poll(&self) -> ContextMenuResult;
79+
}
80+
81+
/// The result of polling on a context menu request
82+
pub enum ContextMenuResult {
83+
/// Session should exit
84+
ExitSession,
85+
/// Dialog was dismissed
86+
Dismissed,
87+
/// User has not acted on dialog
88+
Pending,
89+
}
90+
91+
impl Drop for OpenXrDevice {
92+
fn drop(&mut self) {
93+
// This should be happening automatically in the destructors,
94+
// but it isn't, presumably because there's an extra handle floating
95+
// around somewhere
96+
// XXXManishearth find out where that extra handle is
97+
unsafe {
98+
(self.instance.fp().destroy_session)(self.session.as_raw());
99+
(self.instance.fp().destroy_instance)(self.instance.as_raw());
100+
}
101+
}
102+
}
103+
68104
pub struct OpenXrDiscovery {
69105
gl_thread: Box<dyn GlThread>,
70106
provider_registration: Box<dyn SurfaceProviderRegistration>,
107+
context_menu_provider: Box<dyn ContextMenuProvider>,
71108
}
72109

73110
impl OpenXrDiscovery {
74111
pub fn new(
75112
gl_thread: Box<dyn GlThread>,
76113
provider_registration: Box<dyn SurfaceProviderRegistration>,
114+
context_menu_provider: Box<dyn ContextMenuProvider>,
77115
) -> Self {
78116
Self {
79117
gl_thread,
80118
provider_registration,
119+
context_menu_provider,
81120
}
82121
}
83122
}
@@ -132,13 +171,15 @@ impl DiscoveryAPI<SwapChains> for OpenXrDiscovery {
132171
let provider_registration = self.provider_registration.clone();
133172
let granted_features = init.validate(mode, &["local-floor".into()])?;
134173
let id = xr.id();
174+
let context_menu_provider = self.context_menu_provider.clone_object();
135175
xr.spawn(move || {
136176
OpenXrDevice::new(
137177
gl_thread,
138178
provider_registration,
139179
instance,
140180
granted_features,
141181
id,
182+
context_menu_provider,
142183
)
143184
})
144185
} else {
@@ -152,9 +193,9 @@ impl DiscoveryAPI<SwapChains> for OpenXrDiscovery {
152193
}
153194

154195
struct OpenXrDevice {
196+
session: Session<D3D11>,
155197
instance: Instance,
156198
events: EventBuffer,
157-
session: Session<D3D11>,
158199
frame_waiter: FrameWaiter,
159200
shared_data: Arc<Mutex<SharedData>>,
160201
viewer_space: Space,
@@ -167,6 +208,8 @@ struct OpenXrDevice {
167208
right_hand: OpenXRInput,
168209
left_hand: OpenXRInput,
169210
granted_features: Vec<String>,
211+
context_menu_provider: Box<dyn ContextMenuProvider>,
212+
context_menu_future: Option<Box<dyn ContextMenuFuture>>,
170213
}
171214

172215
/// Data that is shared between the openxr thread and the
@@ -368,6 +411,7 @@ impl OpenXrDevice {
368411
instance: Instance,
369412
granted_features: Vec<String>,
370413
id: SessionId,
414+
context_menu_provider: Box<dyn ContextMenuProvider>,
371415
) -> Result<OpenXrDevice, Error> {
372416
let (device_tx, device_rx) = crossbeam_channel::unbounded();
373417
let (provider_tx, provider_rx) = crossbeam_channel::unbounded();
@@ -538,6 +582,8 @@ impl OpenXrDevice {
538582
right_hand,
539583
left_hand,
540584
granted_features,
585+
context_menu_provider,
586+
context_menu_future: None,
541587
})
542588
}
543589

@@ -550,7 +596,8 @@ impl OpenXrDevice {
550596
match event {
551597
Some(SessionStateChanged(session_change)) => match session_change.state() {
552598
openxr::SessionState::EXITING | openxr::SessionState::LOSS_PENDING => {
553-
break;
599+
self.events.callback(Event::SessionEnd);
600+
return false;
554601
}
555602
openxr::SessionState::STOPPING => {
556603
self.events
@@ -581,7 +628,8 @@ impl OpenXrDevice {
581628
}
582629
},
583630
Some(InstanceLossPending(_)) => {
584-
break;
631+
self.events.callback(Event::SessionEnd);
632+
return false;
585633
}
586634
Some(_) => {
587635
// FIXME: Handle other events
@@ -657,6 +705,17 @@ impl DeviceAPI<Surface> for OpenXrDevice {
657705
// Session is not running anymore.
658706
return None;
659707
}
708+
if let Some(ref context_menu_future) = self.context_menu_future {
709+
match context_menu_future.poll() {
710+
ContextMenuResult::ExitSession => {
711+
self.quit();
712+
return None;
713+
}
714+
ContextMenuResult::Dismissed => self.context_menu_future = None,
715+
ContextMenuResult::Pending => (),
716+
}
717+
}
718+
660719
let mut data = self.shared_data.lock().unwrap();
661720
data.frame_state = self.frame_waiter.wait().expect("error waiting for frame");
662721
let time_ns = time::precise_time_ns();
@@ -685,10 +744,10 @@ impl DeviceAPI<Surface> for OpenXrDevice {
685744

686745
self.session.sync_actions(&[active_action_set]).unwrap();
687746

688-
let (right_input_frame, right_select, right_squeeze) =
747+
let (mut right_input_frame, mut right_select, mut right_squeeze, right_menu) =
689748
self.right_hand
690749
.frame(&self.session, &data.frame_state, &data.space);
691-
let (left_input_frame, left_select, left_squeeze) =
750+
let (mut left_input_frame, mut left_select, mut left_squeeze, left_menu) =
692751
self.left_hand
693752
.frame(&self.session, &data.frame_state, &data.space);
694753

@@ -701,6 +760,22 @@ impl DeviceAPI<Surface> for OpenXrDevice {
701760
vec![]
702761
};
703762

763+
if (left_menu || right_menu) && self.context_menu_future.is_none() {
764+
self.context_menu_future = Some(self.context_menu_provider.open_context_menu());
765+
}
766+
767+
// Do not surface input info whilst the context menu is open
768+
if self.context_menu_future.is_some() {
769+
right_input_frame.target_ray_origin = None;
770+
right_input_frame.grip_origin = None;
771+
left_input_frame.target_ray_origin = None;
772+
left_input_frame.grip_origin = None;
773+
right_select = None;
774+
right_squeeze = None;
775+
left_select = None;
776+
left_squeeze = None;
777+
}
778+
704779
let frame = Frame {
705780
transform,
706781
inputs: vec![right_input_frame, left_input_frame],
@@ -779,12 +854,33 @@ impl DeviceAPI<Surface> for OpenXrDevice {
779854

780855
fn quit(&mut self) {
781856
self.session.request_exit().unwrap();
857+
loop {
858+
let mut buffer = openxr::EventDataBuffer::new();
859+
let event = self.instance.poll_event(&mut buffer).unwrap();
860+
match event {
861+
Some(openxr::Event::SessionStateChanged(session_change)) => {
862+
match session_change.state() {
863+
openxr::SessionState::EXITING => {
864+
self.events.callback(Event::SessionEnd);
865+
break;
866+
}
867+
openxr::SessionState::STOPPING => {
868+
self.session
869+
.end()
870+
.expect("Session failed to end on STOPPING");
871+
}
872+
_ => (),
873+
}
874+
}
875+
_ => (),
876+
}
877+
thread::sleep(Duration::from_millis(30));
878+
}
782879
}
783880

784881
fn set_quitter(&mut self, _: Quitter) {
785-
// Glwindow currently doesn't have any way to end its own session
786-
// XXXManishearth add something for this that listens for the window
787-
// being closed
882+
// the quitter is only needed if we have anything from outside the render
883+
// thread that can signal a quit. We don't.
788884
}
789885

790886
fn update_clip_planes(&mut self, near: f32, far: f32) {

0 commit comments

Comments
 (0)