mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Merge webxr repository (#35228)
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
64b40ea700
commit
534e78db53
30 changed files with 7303 additions and 2 deletions
25
components/webxr/openxr/graphics.rs
Normal file
25
components/webxr/openxr/graphics.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use euclid::{Size2D, UnknownUnit};
|
||||
use openxr::{ExtensionSet, FrameStream, FrameWaiter, Graphics, Instance, Session, SystemId};
|
||||
use surfman::Context as SurfmanContext;
|
||||
use surfman::Device as SurfmanDevice;
|
||||
use surfman::Error as SurfmanError;
|
||||
use surfman::SurfaceTexture;
|
||||
use webxr_api::Error;
|
||||
|
||||
pub enum GraphicsProvider {}
|
||||
|
||||
pub trait GraphicsProviderMethods<G: Graphics> {
|
||||
fn enable_graphics_extensions(exts: &mut ExtensionSet);
|
||||
fn pick_format(formats: &[u32]) -> u32;
|
||||
fn create_session(
|
||||
device: &SurfmanDevice,
|
||||
instance: &Instance,
|
||||
system: SystemId,
|
||||
) -> Result<(Session<G>, FrameWaiter, FrameStream<G>), Error>;
|
||||
fn surface_texture_from_swapchain_texture(
|
||||
image: <G as Graphics>::SwapchainImage,
|
||||
device: &mut SurfmanDevice,
|
||||
context: &mut SurfmanContext,
|
||||
size: &Size2D<i32, UnknownUnit>,
|
||||
) -> Result<SurfaceTexture, SurfmanError>;
|
||||
}
|
139
components/webxr/openxr/graphics_d3d11.rs
Normal file
139
components/webxr/openxr/graphics_d3d11.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use std::{mem, ptr};
|
||||
|
||||
use euclid::{Size2D, UnknownUnit};
|
||||
use log::warn;
|
||||
use openxr::d3d::{Requirements, SessionCreateInfoD3D11, D3D11};
|
||||
use openxr::{
|
||||
ExtensionSet, FormFactor, FrameStream, FrameWaiter, Graphics, Instance, Session, SystemId,
|
||||
};
|
||||
use surfman::Adapter as SurfmanAdapter;
|
||||
use surfman::Context as SurfmanContext;
|
||||
use surfman::Device as SurfmanDevice;
|
||||
use surfman::Error as SurfmanError;
|
||||
use surfman::SurfaceTexture;
|
||||
use webxr_api::Error;
|
||||
use winapi::shared::winerror::{DXGI_ERROR_NOT_FOUND, S_OK};
|
||||
use winapi::shared::{dxgi, dxgiformat};
|
||||
use winapi::um::d3d11::ID3D11Texture2D;
|
||||
use winapi::Interface;
|
||||
use wio::com::ComPtr;
|
||||
|
||||
use crate::openxr::graphics::{GraphicsProvider, GraphicsProviderMethods};
|
||||
use crate::openxr::{create_instance, AppInfo};
|
||||
|
||||
pub type Backend = D3D11;
|
||||
|
||||
impl GraphicsProviderMethods<D3D11> for GraphicsProvider {
|
||||
fn enable_graphics_extensions(exts: &mut ExtensionSet) {
|
||||
exts.khr_d3d11_enable = true;
|
||||
}
|
||||
|
||||
fn pick_format(formats: &[u32]) -> u32 {
|
||||
// TODO: extract the format from surfman's device and pick a matching
|
||||
// valid format based on that. For now, assume that eglChooseConfig will
|
||||
// gravitate to B8G8R8A8.
|
||||
warn!("Available formats: {:?}", formats);
|
||||
for format in formats {
|
||||
match *format {
|
||||
dxgiformat::DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => return *format,
|
||||
dxgiformat::DXGI_FORMAT_B8G8R8A8_UNORM => return *format,
|
||||
//dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM => return *format,
|
||||
f => {
|
||||
warn!("Backend requested unsupported format {:?}", f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!("No formats supported amongst {:?}", formats);
|
||||
}
|
||||
|
||||
fn create_session(
|
||||
device: &SurfmanDevice,
|
||||
instance: &Instance,
|
||||
system: SystemId,
|
||||
) -> Result<(Session<D3D11>, FrameWaiter, FrameStream<D3D11>), Error> {
|
||||
// Get the current surfman device and extract its D3D device. This will ensure
|
||||
// that the OpenXR runtime's texture will be shareable with surfman's surfaces.
|
||||
let native_device = device.native_device();
|
||||
let d3d_device = native_device.d3d11_device;
|
||||
|
||||
// FIXME: we should be using these graphics requirements to drive the actual
|
||||
// d3d device creation, rather than assuming the device that surfman
|
||||
// already created is appropriate. OpenXR returns a validation error
|
||||
// unless we call this method, so we call it and ignore the results
|
||||
// in the short term.
|
||||
let _requirements = D3D11::requirements(&instance, system)
|
||||
.map_err(|e| Error::BackendSpecific(format!("D3D11::requirements {:?}", e)))?;
|
||||
|
||||
unsafe {
|
||||
instance
|
||||
.create_session::<D3D11>(
|
||||
system,
|
||||
&SessionCreateInfoD3D11 {
|
||||
device: d3d_device as *mut _,
|
||||
},
|
||||
)
|
||||
.map_err(|e| Error::BackendSpecific(format!("Instance::create_session {:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
fn surface_texture_from_swapchain_texture(
|
||||
image: <D3D11 as Graphics>::SwapchainImage,
|
||||
device: &mut SurfmanDevice,
|
||||
context: &mut SurfmanContext,
|
||||
size: &Size2D<i32, UnknownUnit>,
|
||||
) -> Result<SurfaceTexture, SurfmanError> {
|
||||
unsafe {
|
||||
let image = ComPtr::from_raw(image as *mut ID3D11Texture2D);
|
||||
image.AddRef();
|
||||
device.create_surface_texture_from_texture(context, size, image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_matching_adapter(
|
||||
requirements: &Requirements,
|
||||
) -> Result<ComPtr<dxgi::IDXGIAdapter1>, String> {
|
||||
unsafe {
|
||||
let mut factory_ptr: *mut dxgi::IDXGIFactory1 = ptr::null_mut();
|
||||
let result = dxgi::CreateDXGIFactory1(
|
||||
&dxgi::IDXGIFactory1::uuidof(),
|
||||
&mut factory_ptr as *mut _ as *mut _,
|
||||
);
|
||||
assert_eq!(result, S_OK);
|
||||
let factory = ComPtr::from_raw(factory_ptr);
|
||||
|
||||
let index = 0;
|
||||
loop {
|
||||
let mut adapter_ptr = ptr::null_mut();
|
||||
let result = factory.EnumAdapters1(index, &mut adapter_ptr);
|
||||
if result == DXGI_ERROR_NOT_FOUND {
|
||||
return Err("No matching adapter".to_owned());
|
||||
}
|
||||
assert_eq!(result, S_OK);
|
||||
let adapter = ComPtr::from_raw(adapter_ptr);
|
||||
let mut adapter_desc = mem::zeroed();
|
||||
let result = adapter.GetDesc1(&mut adapter_desc);
|
||||
assert_eq!(result, S_OK);
|
||||
let adapter_luid = &adapter_desc.AdapterLuid;
|
||||
if adapter_luid.LowPart == requirements.adapter_luid.LowPart
|
||||
&& adapter_luid.HighPart == requirements.adapter_luid.HighPart
|
||||
{
|
||||
return Ok(adapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn create_surfman_adapter() -> Option<SurfmanAdapter> {
|
||||
let instance = create_instance(false, false, false, &AppInfo::default()).ok()?;
|
||||
let system = instance
|
||||
.instance
|
||||
.system(FormFactor::HEAD_MOUNTED_DISPLAY)
|
||||
.ok()?;
|
||||
|
||||
let requirements = D3D11::requirements(&instance.instance, system).ok()?;
|
||||
let adapter = get_matching_adapter(&requirements).ok()?;
|
||||
Some(SurfmanAdapter::from_dxgi_adapter(adapter.up()))
|
||||
}
|
743
components/webxr/openxr/input.rs
Normal file
743
components/webxr/openxr/input.rs
Normal file
|
@ -0,0 +1,743 @@
|
|||
use std::ffi::c_void;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use euclid::RigidTransform3D;
|
||||
use log::debug;
|
||||
use openxr::sys::{
|
||||
HandJointLocationsEXT, HandJointsLocateInfoEXT, HandTrackingAimStateFB,
|
||||
FB_HAND_TRACKING_AIM_EXTENSION_NAME,
|
||||
};
|
||||
use openxr::{
|
||||
self, Action, ActionSet, Binding, FrameState, Graphics, Hand as HandEnum, HandJoint,
|
||||
HandJointLocation, HandTracker, HandTrackingAimFlagsFB, Instance, Path, Posef, Session, Space,
|
||||
SpaceLocationFlags, HAND_JOINT_COUNT,
|
||||
};
|
||||
use webxr_api::Finger;
|
||||
use webxr_api::Hand;
|
||||
use webxr_api::Handedness;
|
||||
use webxr_api::Input;
|
||||
use webxr_api::InputFrame;
|
||||
use webxr_api::InputId;
|
||||
use webxr_api::InputSource;
|
||||
use webxr_api::JointFrame;
|
||||
use webxr_api::Native;
|
||||
use webxr_api::SelectEvent;
|
||||
use webxr_api::TargetRayMode;
|
||||
use webxr_api::Viewer;
|
||||
|
||||
use super::interaction_profiles::InteractionProfile;
|
||||
use super::IDENTITY_POSE;
|
||||
|
||||
use crate::ext_string;
|
||||
use crate::openxr::interaction_profiles::INTERACTION_PROFILES;
|
||||
|
||||
/// Number of frames to wait with the menu gesture before
|
||||
/// opening the menu.
|
||||
const MENU_GESTURE_SUSTAIN_THRESHOLD: u8 = 60;
|
||||
|
||||
/// Helper macro for binding action paths in an interaction profile entry
|
||||
macro_rules! bind_inputs {
|
||||
($actions:expr, $paths:expr, $hand:expr, $instance:expr, $ret:expr) => {
|
||||
$actions.iter().enumerate().for_each(|(i, action)| {
|
||||
let action_path = $paths[i];
|
||||
if action_path != "" {
|
||||
let path = $instance
|
||||
.string_to_path(&format!("/user/hand/{}/input/{}", $hand, action_path))
|
||||
.expect(&format!(
|
||||
"Failed to create path for /user/hand/{}/input/{}",
|
||||
$hand, action_path
|
||||
));
|
||||
let binding = Binding::new(action, path);
|
||||
$ret.push(binding);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum ClickState {
|
||||
Clicking,
|
||||
Done,
|
||||
}
|
||||
|
||||
/// All the information on a single input frame
|
||||
pub struct Frame {
|
||||
pub frame: InputFrame,
|
||||
pub select: Option<SelectEvent>,
|
||||
pub squeeze: Option<SelectEvent>,
|
||||
pub menu_selected: bool,
|
||||
}
|
||||
|
||||
impl ClickState {
|
||||
fn update_from_action<G: Graphics>(
|
||||
&mut self,
|
||||
action: &Action<bool>,
|
||||
session: &Session<G>,
|
||||
menu_selected: bool,
|
||||
) -> (/* is_active */ bool, Option<SelectEvent>) {
|
||||
let click = action.state(session, Path::NULL).unwrap();
|
||||
|
||||
let select_event =
|
||||
self.update_from_value(click.current_state, click.is_active, menu_selected);
|
||||
|
||||
(click.is_active, select_event)
|
||||
}
|
||||
|
||||
fn update_from_value(
|
||||
&mut self,
|
||||
current_state: bool,
|
||||
is_active: bool,
|
||||
menu_selected: bool,
|
||||
) -> Option<SelectEvent> {
|
||||
if is_active {
|
||||
match (current_state, *self) {
|
||||
(_, ClickState::Clicking) if menu_selected => {
|
||||
*self = ClickState::Done;
|
||||
// Cancel the select, we're showing a menu
|
||||
Some(SelectEvent::End)
|
||||
}
|
||||
(true, ClickState::Done) => {
|
||||
*self = ClickState::Clicking;
|
||||
Some(SelectEvent::Start)
|
||||
}
|
||||
(false, ClickState::Clicking) => {
|
||||
*self = ClickState::Done;
|
||||
Some(SelectEvent::Select)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else if *self == ClickState::Clicking {
|
||||
*self = ClickState::Done;
|
||||
// Cancel the select, we lost tracking
|
||||
Some(SelectEvent::End)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenXRInput {
|
||||
id: InputId,
|
||||
action_aim_pose: Action<Posef>,
|
||||
action_aim_space: Space,
|
||||
action_grip_pose: Action<Posef>,
|
||||
action_grip_space: Space,
|
||||
action_click: Action<bool>,
|
||||
action_squeeze: Action<bool>,
|
||||
handedness: Handedness,
|
||||
click_state: ClickState,
|
||||
squeeze_state: ClickState,
|
||||
menu_gesture_sustain: u8,
|
||||
#[allow(unused)]
|
||||
hand_tracker: Option<HandTracker>,
|
||||
action_buttons_common: Vec<Action<f32>>,
|
||||
action_buttons_left: Vec<Action<f32>>,
|
||||
action_buttons_right: Vec<Action<f32>>,
|
||||
action_axes_common: Vec<Action<f32>>,
|
||||
use_alternate_input_source: bool,
|
||||
}
|
||||
|
||||
fn hand_str(h: Handedness) -> &'static str {
|
||||
match h {
|
||||
Handedness::Right => "right",
|
||||
Handedness::Left => "left",
|
||||
_ => panic!("We don't support unknown handedness in openxr"),
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenXRInput {
|
||||
pub fn new<G: Graphics>(
|
||||
id: InputId,
|
||||
handedness: Handedness,
|
||||
action_set: &ActionSet,
|
||||
session: &Session<G>,
|
||||
needs_hands: bool,
|
||||
supported_interaction_profiles: Vec<&'static str>,
|
||||
) -> Self {
|
||||
let hand = hand_str(handedness);
|
||||
let action_aim_pose: Action<Posef> = action_set
|
||||
.create_action(
|
||||
&format!("{}_hand_aim", hand),
|
||||
&format!("{} hand aim", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let action_aim_space = action_aim_pose
|
||||
.create_space(session.clone(), Path::NULL, IDENTITY_POSE)
|
||||
.unwrap();
|
||||
let action_grip_pose: Action<Posef> = action_set
|
||||
.create_action(
|
||||
&format!("{}_hand_grip", hand),
|
||||
&format!("{} hand grip", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let action_grip_space = action_grip_pose
|
||||
.create_space(session.clone(), Path::NULL, IDENTITY_POSE)
|
||||
.unwrap();
|
||||
let action_click: Action<bool> = action_set
|
||||
.create_action(
|
||||
&format!("{}_hand_click", hand),
|
||||
&format!("{} hand click", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let action_squeeze: Action<bool> = action_set
|
||||
.create_action(
|
||||
&format!("{}_hand_squeeze", hand),
|
||||
&format!("{} hand squeeze", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let hand_tracker = if needs_hands {
|
||||
let hand = match handedness {
|
||||
Handedness::Left => HandEnum::LEFT,
|
||||
Handedness::Right => HandEnum::RIGHT,
|
||||
_ => panic!("We don't support unknown handedness in openxr"),
|
||||
};
|
||||
session.create_hand_tracker(hand).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let action_buttons_common: Vec<Action<f32>> = {
|
||||
let button1: Action<f32> = action_set
|
||||
.create_action(
|
||||
&format!("{}_trigger", hand),
|
||||
&format!("{}_trigger", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let button2: Action<f32> = action_set
|
||||
.create_action(&format!("{}_grip", hand), &format!("{}_grip", hand), &[])
|
||||
.unwrap();
|
||||
let button3: Action<f32> = action_set
|
||||
.create_action(
|
||||
&format!("{}_touchpad_click", hand),
|
||||
&format!("{}_touchpad_click", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let button4: Action<f32> = action_set
|
||||
.create_action(
|
||||
&format!("{}_thumbstick_click", hand),
|
||||
&format!("{}_thumbstick_click", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
vec![button1, button2, button3, button4]
|
||||
};
|
||||
|
||||
let action_buttons_left = {
|
||||
let button1: Action<f32> = action_set
|
||||
.create_action(&format!("{}_x", hand), &format!("{}_x", hand), &[])
|
||||
.unwrap();
|
||||
let button2: Action<f32> = action_set
|
||||
.create_action(&format!("{}_y", hand), &format!("{}_y", hand), &[])
|
||||
.unwrap();
|
||||
vec![button1, button2]
|
||||
};
|
||||
|
||||
let action_buttons_right = {
|
||||
let button1: Action<f32> = action_set
|
||||
.create_action(&format!("{}_a", hand), &format!("{}_a", hand), &[])
|
||||
.unwrap();
|
||||
let button2: Action<f32> = action_set
|
||||
.create_action(&format!("{}_b", hand), &format!("{}_b", hand), &[])
|
||||
.unwrap();
|
||||
vec![button1, button2]
|
||||
};
|
||||
|
||||
let action_axes_common: Vec<Action<f32>> = {
|
||||
let axis1: Action<f32> = action_set
|
||||
.create_action(
|
||||
&format!("{}_touchpad_x", hand),
|
||||
&format!("{}_touchpad_x", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let axis2: Action<f32> = action_set
|
||||
.create_action(
|
||||
&format!("{}_touchpad_y", hand),
|
||||
&format!("{}_touchpad_y", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let axis3: Action<f32> = action_set
|
||||
.create_action(
|
||||
&format!("{}_thumbstick_x", hand),
|
||||
&format!("{}_thumbstick_x", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let axis4: Action<f32> = action_set
|
||||
.create_action(
|
||||
&format!("{}_thumbstick_y", hand),
|
||||
&format!("{}_thumbstick_y", hand),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
vec![axis1, axis2, axis3, axis4]
|
||||
};
|
||||
|
||||
let use_alternate_input_source = supported_interaction_profiles
|
||||
.contains(&ext_string!(FB_HAND_TRACKING_AIM_EXTENSION_NAME));
|
||||
|
||||
Self {
|
||||
id,
|
||||
action_aim_pose,
|
||||
action_aim_space,
|
||||
action_grip_pose,
|
||||
action_grip_space,
|
||||
action_click,
|
||||
action_squeeze,
|
||||
handedness,
|
||||
click_state: ClickState::Done,
|
||||
squeeze_state: ClickState::Done,
|
||||
menu_gesture_sustain: 0,
|
||||
hand_tracker,
|
||||
action_buttons_common,
|
||||
action_axes_common,
|
||||
action_buttons_left,
|
||||
action_buttons_right,
|
||||
use_alternate_input_source,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_inputs<G: Graphics>(
|
||||
instance: &Instance,
|
||||
session: &Session<G>,
|
||||
needs_hands: bool,
|
||||
supported_interaction_profiles: Vec<&'static str>,
|
||||
) -> (ActionSet, Self, Self) {
|
||||
let action_set = instance.create_action_set("hands", "Hands", 0).unwrap();
|
||||
let right_hand = OpenXRInput::new(
|
||||
InputId(0),
|
||||
Handedness::Right,
|
||||
&action_set,
|
||||
&session,
|
||||
needs_hands,
|
||||
supported_interaction_profiles.clone(),
|
||||
);
|
||||
let left_hand = OpenXRInput::new(
|
||||
InputId(1),
|
||||
Handedness::Left,
|
||||
&action_set,
|
||||
&session,
|
||||
needs_hands,
|
||||
supported_interaction_profiles.clone(),
|
||||
);
|
||||
|
||||
for profile in INTERACTION_PROFILES {
|
||||
if let Some(extension_name) = profile.required_extension {
|
||||
if !supported_interaction_profiles.contains(&ext_string!(extension_name)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if profile.path.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let select = profile.standard_buttons[0];
|
||||
let squeeze = Option::from(profile.standard_buttons[1]).filter(|&s| !s.is_empty());
|
||||
let mut bindings = right_hand.get_bindings(instance, select, squeeze, &profile);
|
||||
bindings.extend(
|
||||
left_hand
|
||||
.get_bindings(instance, select, squeeze, &profile)
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
let path_controller = instance
|
||||
.string_to_path(profile.path)
|
||||
.expect(format!("Invalid interaction profile path: {}", profile.path).as_str());
|
||||
if let Err(_) =
|
||||
instance.suggest_interaction_profile_bindings(path_controller, &bindings)
|
||||
{
|
||||
debug!(
|
||||
"Interaction profile path not available for this runtime: {:?}",
|
||||
profile.path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
session.attach_action_sets(&[&action_set]).unwrap();
|
||||
|
||||
(action_set, right_hand, left_hand)
|
||||
}
|
||||
|
||||
fn get_bindings(
|
||||
&self,
|
||||
instance: &Instance,
|
||||
select_name: &str,
|
||||
squeeze_name: Option<&str>,
|
||||
interaction_profile: &InteractionProfile,
|
||||
) -> Vec<Binding> {
|
||||
let hand = hand_str(self.handedness);
|
||||
let path_aim_pose = instance
|
||||
.string_to_path(&format!("/user/hand/{}/input/aim/pose", hand))
|
||||
.expect(&format!(
|
||||
"Failed to create path for /user/hand/{}/input/aim/pose",
|
||||
hand
|
||||
));
|
||||
let binding_aim_pose = Binding::new(&self.action_aim_pose, path_aim_pose);
|
||||
let path_grip_pose = instance
|
||||
.string_to_path(&format!("/user/hand/{}/input/grip/pose", hand))
|
||||
.expect(&format!(
|
||||
"Failed to create path for /user/hand/{}/input/grip/pose",
|
||||
hand
|
||||
));
|
||||
let binding_grip_pose = Binding::new(&self.action_grip_pose, path_grip_pose);
|
||||
let path_click = instance
|
||||
.string_to_path(&format!("/user/hand/{}/input/{}", hand, select_name))
|
||||
.expect(&format!(
|
||||
"Failed to create path for /user/hand/{}/input/{}",
|
||||
hand, select_name
|
||||
));
|
||||
let binding_click = Binding::new(&self.action_click, path_click);
|
||||
|
||||
let mut ret = vec![binding_aim_pose, binding_grip_pose, binding_click];
|
||||
if let Some(squeeze_name) = squeeze_name {
|
||||
let path_squeeze = instance
|
||||
.string_to_path(&format!("/user/hand/{}/input/{}", hand, squeeze_name))
|
||||
.expect(&format!(
|
||||
"Failed to create path for /user/hand/{}/input/{}",
|
||||
hand, squeeze_name
|
||||
));
|
||||
let binding_squeeze = Binding::new(&self.action_squeeze, path_squeeze);
|
||||
ret.push(binding_squeeze);
|
||||
}
|
||||
|
||||
bind_inputs!(
|
||||
self.action_buttons_common,
|
||||
interaction_profile.standard_buttons,
|
||||
hand,
|
||||
instance,
|
||||
ret
|
||||
);
|
||||
|
||||
if !interaction_profile.left_buttons.is_empty() && hand == "left" {
|
||||
bind_inputs!(
|
||||
self.action_buttons_left,
|
||||
interaction_profile.left_buttons,
|
||||
hand,
|
||||
instance,
|
||||
ret
|
||||
);
|
||||
} else if !interaction_profile.right_buttons.is_empty() && hand == "right" {
|
||||
bind_inputs!(
|
||||
self.action_buttons_right,
|
||||
interaction_profile.right_buttons,
|
||||
hand,
|
||||
instance,
|
||||
ret
|
||||
);
|
||||
}
|
||||
|
||||
bind_inputs!(
|
||||
self.action_axes_common,
|
||||
interaction_profile.standard_axes,
|
||||
hand,
|
||||
instance,
|
||||
ret
|
||||
);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn frame<G: Graphics>(
|
||||
&mut self,
|
||||
session: &Session<G>,
|
||||
frame_state: &FrameState,
|
||||
base_space: &Space,
|
||||
viewer: &RigidTransform3D<f32, Viewer, Native>,
|
||||
) -> Frame {
|
||||
use euclid::Vector3D;
|
||||
let mut target_ray_origin = pose_for(&self.action_aim_space, frame_state, base_space);
|
||||
|
||||
let grip_origin = pose_for(&self.action_grip_space, frame_state, base_space);
|
||||
|
||||
let mut menu_selected = false;
|
||||
// Check if the palm is facing up. This is our "menu" gesture.
|
||||
if let Some(grip_origin) = grip_origin {
|
||||
// The X axis of the grip is perpendicular to the palm, however its
|
||||
// direction is the opposite for each hand
|
||||
//
|
||||
// We obtain a unit vector pointing out of the palm
|
||||
let x_dir = if let Handedness::Left = self.handedness {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
// Rotate it by the grip to obtain the desired vector
|
||||
let grip_x = grip_origin
|
||||
.rotation
|
||||
.transform_vector3d(Vector3D::new(x_dir, 0.0, 0.0));
|
||||
let gaze = viewer
|
||||
.rotation
|
||||
.transform_vector3d(Vector3D::new(0., 0., 1.));
|
||||
|
||||
// If the angle is close enough to 0, its cosine will be
|
||||
// close to 1
|
||||
// check if the user's gaze is parallel to the palm
|
||||
if gaze.dot(grip_x) > 0.95 {
|
||||
let input_relative = (viewer.translation - grip_origin.translation).normalize();
|
||||
// if so, check if the user is actually looking at the palm
|
||||
if gaze.dot(input_relative) > 0.95 {
|
||||
self.menu_gesture_sustain += 1;
|
||||
if self.menu_gesture_sustain > MENU_GESTURE_SUSTAIN_THRESHOLD {
|
||||
menu_selected = true;
|
||||
self.menu_gesture_sustain = 0;
|
||||
}
|
||||
} else {
|
||||
self.menu_gesture_sustain = 0
|
||||
}
|
||||
} else {
|
||||
self.menu_gesture_sustain = 0;
|
||||
}
|
||||
} else {
|
||||
self.menu_gesture_sustain = 0;
|
||||
}
|
||||
|
||||
let hand = hand_str(self.handedness);
|
||||
let click = self.action_click.state(session, Path::NULL).unwrap();
|
||||
let squeeze = self.action_squeeze.state(session, Path::NULL).unwrap();
|
||||
let (button_values, buttons_changed) = {
|
||||
let mut changed = false;
|
||||
let mut values = Vec::<f32>::new();
|
||||
let mut sync_buttons = |actions: &Vec<Action<f32>>| {
|
||||
let buttons = actions
|
||||
.iter()
|
||||
.map(|action| {
|
||||
let state = action.state(session, Path::NULL).unwrap();
|
||||
changed = changed || state.changed_since_last_sync;
|
||||
state.current_state
|
||||
})
|
||||
.collect::<Vec<f32>>();
|
||||
values.extend_from_slice(&buttons);
|
||||
};
|
||||
sync_buttons(&self.action_buttons_common);
|
||||
if hand == "left" {
|
||||
sync_buttons(&self.action_buttons_left);
|
||||
} else if hand == "right" {
|
||||
sync_buttons(&self.action_buttons_right);
|
||||
}
|
||||
(values, changed)
|
||||
};
|
||||
|
||||
let (axis_values, axes_changed) = {
|
||||
let mut changed = false;
|
||||
let values = self
|
||||
.action_axes_common
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, action)| {
|
||||
let state = action.state(session, Path::NULL).unwrap();
|
||||
changed = changed || state.changed_since_last_sync;
|
||||
// Invert input from y axes
|
||||
state.current_state * if i % 2 == 1 { -1.0 } else { 1.0 }
|
||||
})
|
||||
.collect::<Vec<f32>>();
|
||||
(values, changed)
|
||||
};
|
||||
|
||||
let input_changed = buttons_changed || axes_changed;
|
||||
|
||||
let (click_is_active, mut click_event) = if !self.use_alternate_input_source {
|
||||
self.click_state
|
||||
.update_from_action(&self.action_click, session, menu_selected)
|
||||
} else {
|
||||
(true, None)
|
||||
};
|
||||
let (squeeze_is_active, squeeze_event) =
|
||||
self.squeeze_state
|
||||
.update_from_action(&self.action_squeeze, session, menu_selected);
|
||||
|
||||
let mut aim_state: Option<HandTrackingAimStateFB> = None;
|
||||
let hand = self.hand_tracker.as_ref().and_then(|tracker| {
|
||||
locate_hand(
|
||||
base_space,
|
||||
tracker,
|
||||
frame_state,
|
||||
self.use_alternate_input_source,
|
||||
session,
|
||||
&mut aim_state,
|
||||
)
|
||||
});
|
||||
|
||||
let mut pressed = click_is_active && click.current_state;
|
||||
let squeezed = squeeze_is_active && squeeze.current_state;
|
||||
|
||||
if let Some(state) = aim_state {
|
||||
target_ray_origin.replace(super::transform(&state.aim_pose));
|
||||
let index_pinching = state
|
||||
.status
|
||||
.intersects(HandTrackingAimFlagsFB::INDEX_PINCHING);
|
||||
click_event = self
|
||||
.click_state
|
||||
.update_from_value(index_pinching, true, menu_selected);
|
||||
pressed = index_pinching;
|
||||
}
|
||||
|
||||
let input_frame = InputFrame {
|
||||
target_ray_origin,
|
||||
id: self.id,
|
||||
pressed,
|
||||
squeezed,
|
||||
grip_origin,
|
||||
hand,
|
||||
button_values,
|
||||
axis_values,
|
||||
input_changed,
|
||||
};
|
||||
|
||||
Frame {
|
||||
frame: input_frame,
|
||||
select: click_event,
|
||||
squeeze: squeeze_event,
|
||||
menu_selected,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input_source(&self) -> InputSource {
|
||||
let hand_support = if self.hand_tracker.is_some() {
|
||||
// openxr runtimes must always support all or none joints
|
||||
Some(Hand::<()>::default().map(|_, _| Some(())))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
InputSource {
|
||||
handedness: self.handedness,
|
||||
id: self.id,
|
||||
target_ray_mode: TargetRayMode::TrackedPointer,
|
||||
supports_grip: true,
|
||||
profiles: vec![],
|
||||
hand_support,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pose_for(
|
||||
action_space: &Space,
|
||||
frame_state: &FrameState,
|
||||
base_space: &Space,
|
||||
) -> Option<RigidTransform3D<f32, Input, Native>> {
|
||||
let location = action_space
|
||||
.locate(base_space, frame_state.predicted_display_time)
|
||||
.unwrap();
|
||||
let pose_valid = location
|
||||
.location_flags
|
||||
.intersects(SpaceLocationFlags::POSITION_VALID | SpaceLocationFlags::ORIENTATION_VALID);
|
||||
if pose_valid {
|
||||
Some(super::transform(&location.pose))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn locate_hand<G: Graphics>(
|
||||
base_space: &Space,
|
||||
tracker: &HandTracker,
|
||||
frame_state: &FrameState,
|
||||
use_alternate_input_source: bool,
|
||||
session: &Session<G>,
|
||||
aim_state: &mut Option<HandTrackingAimStateFB>,
|
||||
) -> Option<Box<Hand<JointFrame>>> {
|
||||
let mut state = HandTrackingAimStateFB::out(std::ptr::null_mut());
|
||||
let locations = {
|
||||
if !use_alternate_input_source {
|
||||
base_space.locate_hand_joints(tracker, frame_state.predicted_display_time)
|
||||
} else {
|
||||
let locate_info = HandJointsLocateInfoEXT {
|
||||
ty: HandJointsLocateInfoEXT::TYPE,
|
||||
next: std::ptr::null(),
|
||||
base_space: base_space.as_raw(),
|
||||
time: frame_state.predicted_display_time,
|
||||
};
|
||||
|
||||
let mut locations = MaybeUninit::<[HandJointLocation; HAND_JOINT_COUNT]>::uninit();
|
||||
let mut location_info = HandJointLocationsEXT {
|
||||
ty: HandJointLocationsEXT::TYPE,
|
||||
next: &mut state as *mut _ as *mut c_void,
|
||||
is_active: false.into(),
|
||||
joint_count: HAND_JOINT_COUNT as u32,
|
||||
joint_locations: locations.as_mut_ptr() as _,
|
||||
};
|
||||
|
||||
// Check if hand tracking is supported by the session instance
|
||||
let raw_hand_tracker = session.instance().exts().ext_hand_tracking.as_ref()?;
|
||||
|
||||
unsafe {
|
||||
Ok(
|
||||
match (raw_hand_tracker.locate_hand_joints)(
|
||||
tracker.as_raw(),
|
||||
&locate_info,
|
||||
&mut location_info,
|
||||
) {
|
||||
openxr::sys::Result::SUCCESS if location_info.is_active.into() => {
|
||||
aim_state.replace(state.assume_init());
|
||||
Some(locations.assume_init())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
let locations = if let Ok(Some(ref locations)) = locations {
|
||||
Hand {
|
||||
wrist: Some(&locations[HandJoint::WRIST]),
|
||||
thumb_metacarpal: Some(&locations[HandJoint::THUMB_METACARPAL]),
|
||||
thumb_phalanx_proximal: Some(&locations[HandJoint::THUMB_PROXIMAL]),
|
||||
thumb_phalanx_distal: Some(&locations[HandJoint::THUMB_DISTAL]),
|
||||
thumb_phalanx_tip: Some(&locations[HandJoint::THUMB_TIP]),
|
||||
index: Finger {
|
||||
metacarpal: Some(&locations[HandJoint::INDEX_METACARPAL]),
|
||||
phalanx_proximal: Some(&locations[HandJoint::INDEX_PROXIMAL]),
|
||||
phalanx_intermediate: Some(&locations[HandJoint::INDEX_INTERMEDIATE]),
|
||||
phalanx_distal: Some(&locations[HandJoint::INDEX_DISTAL]),
|
||||
phalanx_tip: Some(&locations[HandJoint::INDEX_TIP]),
|
||||
},
|
||||
middle: Finger {
|
||||
metacarpal: Some(&locations[HandJoint::MIDDLE_METACARPAL]),
|
||||
phalanx_proximal: Some(&locations[HandJoint::MIDDLE_PROXIMAL]),
|
||||
phalanx_intermediate: Some(&locations[HandJoint::MIDDLE_INTERMEDIATE]),
|
||||
phalanx_distal: Some(&locations[HandJoint::MIDDLE_DISTAL]),
|
||||
phalanx_tip: Some(&locations[HandJoint::MIDDLE_TIP]),
|
||||
},
|
||||
ring: Finger {
|
||||
metacarpal: Some(&locations[HandJoint::RING_METACARPAL]),
|
||||
phalanx_proximal: Some(&locations[HandJoint::RING_PROXIMAL]),
|
||||
phalanx_intermediate: Some(&locations[HandJoint::RING_INTERMEDIATE]),
|
||||
phalanx_distal: Some(&locations[HandJoint::RING_DISTAL]),
|
||||
phalanx_tip: Some(&locations[HandJoint::RING_TIP]),
|
||||
},
|
||||
little: Finger {
|
||||
metacarpal: Some(&locations[HandJoint::LITTLE_METACARPAL]),
|
||||
phalanx_proximal: Some(&locations[HandJoint::LITTLE_PROXIMAL]),
|
||||
phalanx_intermediate: Some(&locations[HandJoint::LITTLE_INTERMEDIATE]),
|
||||
phalanx_distal: Some(&locations[HandJoint::LITTLE_DISTAL]),
|
||||
phalanx_tip: Some(&locations[HandJoint::LITTLE_TIP]),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(Box::new(locations.map(|loc, _| {
|
||||
loc.and_then(|location| {
|
||||
let pose_valid = location.location_flags.intersects(
|
||||
SpaceLocationFlags::POSITION_VALID | SpaceLocationFlags::ORIENTATION_VALID,
|
||||
);
|
||||
if pose_valid {
|
||||
Some(JointFrame {
|
||||
pose: super::transform(&location.pose),
|
||||
radius: location.radius,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})))
|
||||
}
|
444
components/webxr/openxr/interaction_profiles.rs
Normal file
444
components/webxr/openxr/interaction_profiles.rs
Normal file
|
@ -0,0 +1,444 @@
|
|||
use openxr::{
|
||||
sys::{
|
||||
BD_CONTROLLER_INTERACTION_EXTENSION_NAME, EXT_HAND_INTERACTION_EXTENSION_NAME,
|
||||
EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME,
|
||||
EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME, FB_HAND_TRACKING_AIM_EXTENSION_NAME,
|
||||
FB_TOUCH_CONTROLLER_PRO_EXTENSION_NAME,
|
||||
HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME,
|
||||
HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME,
|
||||
META_TOUCH_CONTROLLER_PLUS_EXTENSION_NAME, ML_ML2_CONTROLLER_INTERACTION_EXTENSION_NAME,
|
||||
},
|
||||
ExtensionSet,
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ext_string {
|
||||
($ext_name:expr) => {
|
||||
std::str::from_utf8($ext_name).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum InteractionProfileType {
|
||||
KhrSimpleController,
|
||||
BytedancePicoNeo3Controller,
|
||||
BytedancePico4Controller,
|
||||
BytedancePicoG3Controller,
|
||||
GoogleDaydreamController,
|
||||
HpMixedRealityController,
|
||||
HtcViveController,
|
||||
HtcViveCosmosController,
|
||||
HtcViveFocus3Controller,
|
||||
MagicLeap2Controller,
|
||||
MicrosoftMixedRealityMotionController,
|
||||
OculusGoController,
|
||||
OculusTouchController,
|
||||
FacebookTouchControllerPro,
|
||||
MetaTouchPlusController,
|
||||
MetaTouchControllerRiftCv1,
|
||||
MetaTouchControllerQuest1RiftS,
|
||||
MetaTouchControllerQuest2,
|
||||
SamsungOdysseyController,
|
||||
ValveIndexController,
|
||||
ExtHandInteraction,
|
||||
FbHandTrackingAim,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct InteractionProfile<'a> {
|
||||
pub profile_type: InteractionProfileType,
|
||||
/// The interaction profile path
|
||||
pub path: &'static str,
|
||||
/// The OpenXR extension, if any, required to use this profile
|
||||
pub required_extension: Option<&'a [u8]>,
|
||||
/// Trigger, Grip, Touchpad, Thumbstick
|
||||
pub standard_buttons: &'a [&'a str],
|
||||
/// Touchpad X, Touchpad Y, Thumbstick X, Thumbstick Y
|
||||
pub standard_axes: &'a [&'a str],
|
||||
/// Any additional buttons on the left controller
|
||||
pub left_buttons: &'a [&'a str],
|
||||
/// Any additional buttons on the right controller
|
||||
pub right_buttons: &'a [&'a str],
|
||||
/// The corresponding WebXR Input Profile names
|
||||
pub profiles: &'a [&'a str],
|
||||
}
|
||||
|
||||
pub static KHR_SIMPLE_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::KhrSimpleController,
|
||||
path: "/interaction_profiles/khr/simple_controller",
|
||||
required_extension: None,
|
||||
standard_buttons: &["select/click", "", "", ""],
|
||||
standard_axes: &["", "", "", ""],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
profiles: &["generic-trigger"],
|
||||
};
|
||||
|
||||
pub static BYTEDANCE_PICO_NEO3_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::BytedancePicoNeo3Controller,
|
||||
path: "/interaction_profiles/bytedance/pico_neo3_controller",
|
||||
required_extension: Some(BD_CONTROLLER_INTERACTION_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &["pico-neo3", "generic-trigger-squeeze-thumbstick"],
|
||||
};
|
||||
|
||||
pub static BYTEDANCE_PICO_4_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::BytedancePico4Controller,
|
||||
path: "/interaction_profiles/bytedance/pico4_controller",
|
||||
required_extension: Some(BD_CONTROLLER_INTERACTION_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &["pico-4", "generic-trigger-squeeze-thumbstick"],
|
||||
};
|
||||
|
||||
pub static BYTEDANCE_PICO_G3_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::BytedancePicoG3Controller,
|
||||
path: "/interaction_profiles/bytedance/pico_g3_controller",
|
||||
required_extension: Some(BD_CONTROLLER_INTERACTION_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "", "", "thumbstick/click"],
|
||||
// Note: X/Y components not listed in the OpenXR spec currently due to vendor error.
|
||||
// See <https://github.com/KhronosGroup/OpenXR-Docs/issues/158>
|
||||
// It also uses the thumbstick path despite clearly being a touchpad, so
|
||||
// move those values into the touchpad axes slots
|
||||
standard_axes: &["thumbstick/x", "thumbstick/y", "", ""],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
// Note: There is no corresponding WebXR Input profile for the Pico G3,
|
||||
// but the controller seems identical to the G2, so use that instead.
|
||||
profiles: &["pico-g2", "generic-trigger-touchpad"],
|
||||
};
|
||||
|
||||
pub static GOOGLE_DAYDREAM_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::GoogleDaydreamController,
|
||||
path: "/interaction_profiles/google/daydream_controller",
|
||||
required_extension: None,
|
||||
standard_buttons: &["select/click", "", "trackpad/click", ""],
|
||||
standard_axes: &["trackpad/x", "trackpad/y", "", ""],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
profiles: &["google-daydream", "generic-touchpad"],
|
||||
};
|
||||
|
||||
pub static HP_MIXED_REALITY_MOTION_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::HpMixedRealityController,
|
||||
path: "/interaction_profiles/hp/mixed_reality_controller",
|
||||
required_extension: Some(EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &[
|
||||
"hp-mixed-reality",
|
||||
"oculus-touch",
|
||||
"generic-trigger-squeeze-thumbstick",
|
||||
],
|
||||
};
|
||||
|
||||
pub static HTC_VIVE_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::HtcViveController,
|
||||
path: "/interaction_profiles/htc/vive_controller",
|
||||
required_extension: None,
|
||||
standard_buttons: &["trigger/value", "squeeze/click", "trackpad/click", ""],
|
||||
standard_axes: &["trackpad/x", "trackpad/y", "", ""],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
profiles: &["htc-vive", "generic-trigger-squeeze-touchpad"],
|
||||
};
|
||||
|
||||
pub static HTC_VIVE_COSMOS_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::HtcViveCosmosController,
|
||||
path: "/interaction_profiles/htc/vive_cosmos_controller",
|
||||
required_extension: Some(HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "squeeze/click", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &["htc-vive-cosmos", "generic-trigger-squeeze-thumbstick"],
|
||||
};
|
||||
|
||||
pub static HTC_VIVE_FOCUS3_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::HtcViveFocus3Controller,
|
||||
path: "/interaction_profiles/htc/vive_focus3_controller",
|
||||
required_extension: Some(HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &["htc-vive-focus-3", "generic-trigger-squeeze-thumbstick"],
|
||||
};
|
||||
|
||||
pub static MAGIC_LEAP_2_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::MagicLeap2Controller,
|
||||
path: "/interaction_profiles/ml/ml2_controller",
|
||||
required_extension: Some(ML_ML2_CONTROLLER_INTERACTION_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "", "trackpad/click", ""],
|
||||
standard_axes: &["trackpad/x", "trackpad/y", "", ""],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
// Note: There is no corresponding WebXR Input profile for the Magic Leap 2,
|
||||
// but the controller seems mostly identical to the 1, so use that instead.
|
||||
profiles: &["magicleap-one", "generic-trigger-squeeze-touchpad"],
|
||||
};
|
||||
|
||||
pub static MICROSOFT_MIXED_REALITY_MOTION_CONTROLLER_PROFILE: InteractionProfile =
|
||||
InteractionProfile {
|
||||
profile_type: InteractionProfileType::MicrosoftMixedRealityMotionController,
|
||||
path: "/interaction_profiles/microsoft/motion_controller",
|
||||
required_extension: None,
|
||||
standard_buttons: &[
|
||||
"trigger/value",
|
||||
"squeeze/click",
|
||||
"trackpad/click",
|
||||
"thumbstick/click",
|
||||
],
|
||||
standard_axes: &["trackpad/x", "trackpad/y", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
profiles: &[
|
||||
"microsoft-mixed-reality",
|
||||
"generic-trigger-squeeze-touchpad-thumbstick",
|
||||
],
|
||||
};
|
||||
|
||||
pub static OCULUS_GO_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::OculusGoController,
|
||||
path: "/interaction_profiles/oculus/go_controller",
|
||||
required_extension: None,
|
||||
standard_buttons: &["trigger/click", "", "trackpad/click", ""],
|
||||
standard_axes: &["trackpad/x", "trackpad/y", "", ""],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
profiles: &["oculus-go", "generic-trigger-touchpad"],
|
||||
};
|
||||
|
||||
pub static OCULUS_TOUCH_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::OculusTouchController,
|
||||
path: "/interaction_profiles/oculus/touch_controller",
|
||||
required_extension: None,
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &[
|
||||
"oculus-touch-v3",
|
||||
"oculus-touch-v2",
|
||||
"oculus-touch",
|
||||
"generic-trigger-squeeze-thumbstick",
|
||||
],
|
||||
};
|
||||
|
||||
pub static FACEBOOK_TOUCH_CONTROLLER_PRO_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::FacebookTouchControllerPro,
|
||||
path: "/interaction_profiles/facebook/touch_controller_pro",
|
||||
required_extension: Some(FB_TOUCH_CONTROLLER_PRO_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &[
|
||||
"meta-quest-touch-pro",
|
||||
"oculus-touch-v2",
|
||||
"oculus-touch",
|
||||
"generic-trigger-squeeze-thumbstick",
|
||||
],
|
||||
};
|
||||
|
||||
pub static META_TOUCH_CONTROLLER_PLUS_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::MetaTouchPlusController,
|
||||
path: "/interaction_profiles/meta/touch_controller_plus",
|
||||
required_extension: Some(META_TOUCH_CONTROLLER_PLUS_EXTENSION_NAME),
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &[
|
||||
"meta-quest-touch-plus",
|
||||
"oculus-touch-v3",
|
||||
"oculus-touch",
|
||||
"generic-trigger-squeeze-thumbstick",
|
||||
],
|
||||
};
|
||||
|
||||
pub static META_TOUCH_CONTROLLER_RIFT_CV1_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::MetaTouchControllerRiftCv1,
|
||||
path: "/interaction_profiles/meta/touch_controller_rift_cv1",
|
||||
required_extension: None,
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &["oculus-touch", "generic-trigger-squeeze-thumbstick"],
|
||||
};
|
||||
|
||||
pub static META_TOUCH_CONTROLLER_QUEST_1_RIFT_S_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::MetaTouchControllerQuest1RiftS,
|
||||
path: "/interaction_profiles/meta/touch_controller_quest_1_rift_s",
|
||||
required_extension: None,
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &[
|
||||
"oculus-touch-v2",
|
||||
"oculus-touch",
|
||||
"generic-trigger-squeeze-thumbstick",
|
||||
],
|
||||
};
|
||||
|
||||
pub static META_TOUCH_CONTROLLER_QUEST_2_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::MetaTouchControllerQuest2,
|
||||
path: "/interaction_profiles/meta/touch_controller_quest_2",
|
||||
required_extension: None,
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["", "", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["x/click", "y/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &[
|
||||
"oculus-touch-v3",
|
||||
"oculus-touch-v2",
|
||||
"oculus-touch",
|
||||
"generic-trigger-squeeze-thumbstick",
|
||||
],
|
||||
};
|
||||
|
||||
pub static SAMSUNG_ODYSSEY_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::SamsungOdysseyController,
|
||||
path: "/interaction_profiles/samsung/odyssey_controller",
|
||||
required_extension: Some(EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME),
|
||||
standard_buttons: &[
|
||||
"trigger/value",
|
||||
"squeeze/click",
|
||||
"trackpad/click",
|
||||
"thumbstick/click",
|
||||
],
|
||||
standard_axes: &["trackpad/x", "trackpad/y", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
profiles: &[
|
||||
"samsung-odyssey",
|
||||
"microsoft-mixed-reality",
|
||||
"generic-trigger-squeeze-touchpad-thumbstick",
|
||||
],
|
||||
};
|
||||
|
||||
pub static VALVE_INDEX_CONTROLLER_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::ValveIndexController,
|
||||
path: "/interaction_profiles/valve/index_controller",
|
||||
required_extension: None,
|
||||
standard_buttons: &["trigger/value", "squeeze/value", "", "thumbstick/click"],
|
||||
standard_axes: &["trackpad/x", "trackpad/y", "thumbstick/x", "thumbstick/y"],
|
||||
left_buttons: &["a/click", "b/click"],
|
||||
right_buttons: &["a/click", "b/click"],
|
||||
profiles: &["valve-index", "generic-trigger-squeeze-touchpad-thumbstick"],
|
||||
};
|
||||
|
||||
pub static EXT_HAND_INTERACTION_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::ExtHandInteraction,
|
||||
path: "/interaction_profiles/ext/hand_interaction_ext",
|
||||
required_extension: Some(EXT_HAND_INTERACTION_EXTENSION_NAME),
|
||||
standard_buttons: &["pinch_ext/value", "", "", ""],
|
||||
standard_axes: &["", "", "", ""],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
profiles: &["generic-hand-select", "generic-hand"],
|
||||
};
|
||||
|
||||
pub static FB_HAND_TRACKING_AIM_PROFILE: InteractionProfile = InteractionProfile {
|
||||
profile_type: InteractionProfileType::FbHandTrackingAim,
|
||||
path: "",
|
||||
required_extension: Some(FB_HAND_TRACKING_AIM_EXTENSION_NAME),
|
||||
standard_buttons: &["", "", "", ""],
|
||||
standard_axes: &["", "", "", ""],
|
||||
left_buttons: &[],
|
||||
right_buttons: &[],
|
||||
profiles: &["generic-hand-select", "generic-hand"],
|
||||
};
|
||||
|
||||
pub static INTERACTION_PROFILES: [InteractionProfile; 22] = [
|
||||
KHR_SIMPLE_CONTROLLER_PROFILE,
|
||||
BYTEDANCE_PICO_NEO3_CONTROLLER_PROFILE,
|
||||
BYTEDANCE_PICO_4_CONTROLLER_PROFILE,
|
||||
BYTEDANCE_PICO_G3_CONTROLLER_PROFILE,
|
||||
GOOGLE_DAYDREAM_CONTROLLER_PROFILE,
|
||||
HP_MIXED_REALITY_MOTION_CONTROLLER_PROFILE,
|
||||
HTC_VIVE_CONTROLLER_PROFILE,
|
||||
HTC_VIVE_COSMOS_CONTROLLER_PROFILE,
|
||||
HTC_VIVE_FOCUS3_CONTROLLER_PROFILE,
|
||||
MAGIC_LEAP_2_CONTROLLER_PROFILE,
|
||||
MICROSOFT_MIXED_REALITY_MOTION_CONTROLLER_PROFILE,
|
||||
OCULUS_GO_CONTROLLER_PROFILE,
|
||||
OCULUS_TOUCH_CONTROLLER_PROFILE,
|
||||
FACEBOOK_TOUCH_CONTROLLER_PRO_PROFILE,
|
||||
META_TOUCH_CONTROLLER_PLUS_PROFILE,
|
||||
META_TOUCH_CONTROLLER_RIFT_CV1_PROFILE,
|
||||
META_TOUCH_CONTROLLER_QUEST_1_RIFT_S_PROFILE,
|
||||
META_TOUCH_CONTROLLER_QUEST_2_PROFILE,
|
||||
SAMSUNG_ODYSSEY_CONTROLLER_PROFILE,
|
||||
VALVE_INDEX_CONTROLLER_PROFILE,
|
||||
EXT_HAND_INTERACTION_PROFILE,
|
||||
FB_HAND_TRACKING_AIM_PROFILE,
|
||||
];
|
||||
|
||||
pub fn get_profiles_from_path(path: String) -> &'static [&'static str] {
|
||||
INTERACTION_PROFILES
|
||||
.iter()
|
||||
.find(|profile| profile.path == path)
|
||||
.map_or(&[], |profile| profile.profiles)
|
||||
}
|
||||
|
||||
pub fn get_supported_interaction_profiles(
|
||||
supported_extensions: &ExtensionSet,
|
||||
enabled_extensions: &mut ExtensionSet,
|
||||
) -> Vec<&'static str> {
|
||||
let mut extensions = Vec::new();
|
||||
if supported_extensions.bd_controller_interaction {
|
||||
extensions.push(ext_string!(BD_CONTROLLER_INTERACTION_EXTENSION_NAME));
|
||||
enabled_extensions.bd_controller_interaction = true;
|
||||
}
|
||||
if supported_extensions.ext_hp_mixed_reality_controller {
|
||||
extensions.push(ext_string!(EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME));
|
||||
enabled_extensions.ext_hp_mixed_reality_controller = true;
|
||||
}
|
||||
if supported_extensions.ext_samsung_odyssey_controller {
|
||||
extensions.push(ext_string!(EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME));
|
||||
enabled_extensions.ext_samsung_odyssey_controller = true;
|
||||
}
|
||||
if supported_extensions.ml_ml2_controller_interaction {
|
||||
extensions.push(ext_string!(ML_ML2_CONTROLLER_INTERACTION_EXTENSION_NAME));
|
||||
enabled_extensions.ml_ml2_controller_interaction = true;
|
||||
}
|
||||
if supported_extensions.htc_vive_cosmos_controller_interaction {
|
||||
extensions.push(ext_string!(
|
||||
HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME
|
||||
));
|
||||
enabled_extensions.htc_vive_cosmos_controller_interaction = true;
|
||||
}
|
||||
if supported_extensions.htc_vive_focus3_controller_interaction {
|
||||
extensions.push(ext_string!(
|
||||
HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME
|
||||
));
|
||||
enabled_extensions.htc_vive_focus3_controller_interaction = true;
|
||||
}
|
||||
if supported_extensions.fb_touch_controller_pro {
|
||||
extensions.push(ext_string!(FB_TOUCH_CONTROLLER_PRO_EXTENSION_NAME));
|
||||
enabled_extensions.fb_touch_controller_pro = true;
|
||||
}
|
||||
if supported_extensions.meta_touch_controller_plus {
|
||||
extensions.push(ext_string!(META_TOUCH_CONTROLLER_PLUS_EXTENSION_NAME));
|
||||
enabled_extensions.meta_touch_controller_plus = true;
|
||||
}
|
||||
if supported_extensions.ext_hand_interaction {
|
||||
extensions.push(ext_string!(EXT_HAND_INTERACTION_EXTENSION_NAME));
|
||||
enabled_extensions.ext_hand_interaction = true;
|
||||
}
|
||||
if supported_extensions.fb_hand_tracking_aim {
|
||||
extensions.push(ext_string!(FB_HAND_TRACKING_AIM_EXTENSION_NAME));
|
||||
enabled_extensions.fb_hand_tracking_aim = true;
|
||||
}
|
||||
extensions
|
||||
}
|
1594
components/webxr/openxr/mod.rs
Normal file
1594
components/webxr/openxr/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue