diff --git a/crates/bevy_openxr/src/openxr/features/handtracking.rs b/crates/bevy_openxr/src/openxr/features/handtracking.rs index df5141e..d56f6cd 100644 --- a/crates/bevy_openxr/src/openxr/features/handtracking.rs +++ b/crates/bevy_openxr/src/openxr/features/handtracking.rs @@ -1,17 +1,18 @@ use bevy::prelude::*; use bevy_mod_xr::hands::{LeftHand, RightHand, XrHandBoneEntities, HAND_JOINT_COUNT}; use bevy_mod_xr::session::{XrPreDestroySession, XrSessionCreated, XrTrackingRoot}; -use bevy_mod_xr::spaces::{XrPrimaryReferenceSpace, XrReferenceSpace}; +use bevy_mod_xr::spaces::{XrPrimaryReferenceSpace, XrReferenceSpace, XrVelocity}; use bevy_mod_xr::{ hands::{HandBone, HandBoneRadius}, session::session_running, }; use openxr::SpaceLocationFlags; -use crate::init::create_xr_session; +use crate::helper_traits::ToVec3; use crate::resources::OxrFrameState; use crate::resources::Pipelined; use crate::session::OxrSession; +use crate::spaces::{OxrSpaceLocationFlags, OxrSpaceVelocityFlags}; pub struct HandTrackingPlugin { default_hands: bool, @@ -43,7 +44,12 @@ pub fn spawn_hand_bones( #[allow(clippy::needless_range_loop)] for bone in HandBone::get_all_bones().into_iter() { bones[bone as usize] = cmds - .spawn((SpatialBundle::default(), bone, HandBoneRadius(0.0))) + .spawn(( + SpatialBundle::default(), + bone, + HandBoneRadius(0.0), + OxrSpaceLocationFlags(openxr::SpaceLocationFlags::default()), + )) .insert(bundle.clone()) .id(); } @@ -101,9 +107,9 @@ fn spawn_default_hands( } #[derive(Component, Clone, Copy)] -struct DefaultHandTracker; +pub struct DefaultHandTracker; #[derive(Component, Clone, Copy)] -struct DefaultHandBone; +pub struct DefaultHandBone; #[allow(clippy::type_complexity)] fn clean_up_default_hands( @@ -128,34 +134,60 @@ fn locate_hands( &XrHandBoneEntities, )>, session: Res, - mut bone_query: Query<(&HandBone, &mut HandBoneRadius, &mut Transform)>, + mut bone_query: Query<( + &HandBone, + &mut HandBoneRadius, + &mut Transform, + Option<&mut XrVelocity>, + &mut OxrSpaceLocationFlags, + Option<&mut OxrSpaceVelocityFlags>, + )>, pipelined: Option>, ) { for (tracker, ref_space, hand_entities) in &tracker_query { + let wants_velocities = hand_entities + .0 + .iter() + .filter_map(|e| bone_query.get(*e).ok()) + .any(|v| v.3.is_some()); + let time = if pipelined.is_some() { + openxr::Time::from_nanos( + frame_state.predicted_display_time.as_nanos() + + frame_state.predicted_display_period.as_nanos(), + ) + } else { + frame_state.predicted_display_time + }; let ref_space = ref_space.map(|v| &v.0).unwrap_or(&default_ref_space.0); - // relate_hand_joints also provides velocities - let joints = match session.locate_hand_joints( - tracker, - ref_space, - if pipelined.is_some() { - openxr::Time::from_nanos( - frame_state.predicted_display_time.as_nanos() - + frame_state.predicted_display_period.as_nanos(), - ) - } else { - frame_state.predicted_display_time - }, - ) { - Ok(Some(v)) => v, - Ok(None) => continue, - Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { - error!("HandTracking Extension not loaded"); - continue; - } - Err(err) => { - warn!("Error while locating hand joints: {}", err.to_string()); - continue; - } + let (joints, vels) = if wants_velocities { + let (loc, vel) = + match session.locate_hand_joints_with_velocities(tracker, ref_space, time) { + Ok(Some(v)) => v, + Ok(None) => continue, + Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { + error!("HandTracking Extension not loaded"); + continue; + } + Err(err) => { + warn!("Error while locating hand joints: {}", err.to_string()); + continue; + } + }; + (loc, Some(vel)) + } else { + let space = match session.locate_hand_joints(tracker, ref_space, time) { + Ok(Some(v)) => v, + Ok(None) => continue, + Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { + error!("HandTracking Extension not loaded"); + continue; + } + Err(err) => { + warn!("Error while locating hand joints: {}", err.to_string()); + continue; + } + }; + (space, None) }; let bone_entities = match bone_query.get_many_mut(hand_entities.0) { Ok(v) => v, @@ -164,28 +196,45 @@ fn locate_hands( continue; } }; - for (bone, mut bone_radius, mut transform) in bone_entities { + for (bone, mut bone_radius, mut transform, velocity, mut location_flags, velocity_flags) in + bone_entities + { let joint = joints[*bone as usize]; - **bone_radius = joint.radius; + if let Some(mut velocity) = velocity { + let Some(vels) = vels.as_ref() else { + error!("somehow got a hand bone with an XrVelocity component, but there are no velocities"); + continue; + }; + let Some(mut vel_flags) = velocity_flags else { + error!("somehow got a hand bone with an XrVelocity component, but without velocity flags"); + continue; + }; + let vel = vels[*bone as usize]; + let flags = OxrSpaceVelocityFlags(vel.velocity_flags); + if flags.linear_valid() { + velocity.linear = vel.linear_velocity.to_vec3(); + } + if flags.angular_valid() { + velocity.angular = vel.angular_velocity.to_vec3(); + } + *vel_flags = flags; + } - if joint - .location_flags - .contains(SpaceLocationFlags::POSITION_VALID) - { + **bone_radius = joint.radius; + let flags = OxrSpaceLocationFlags(joint.location_flags); + if flags.pos_valid() { transform.translation.x = joint.pose.position.x; transform.translation.y = joint.pose.position.y; transform.translation.z = joint.pose.position.z; } - if joint - .location_flags - .contains(SpaceLocationFlags::ORIENTATION_VALID) - { + if flags.rot_valid() { transform.rotation.x = joint.pose.orientation.x; transform.rotation.y = joint.pose.orientation.y; transform.rotation.z = joint.pose.orientation.z; transform.rotation.w = joint.pose.orientation.w; } + *location_flags = flags; } } } diff --git a/crates/bevy_openxr/src/openxr/init.rs b/crates/bevy_openxr/src/openxr/init.rs index 41a1943..cd7778d 100644 --- a/crates/bevy_openxr/src/openxr/init.rs +++ b/crates/bevy_openxr/src/openxr/init.rs @@ -15,6 +15,7 @@ use bevy::render::RenderPlugin; use bevy::winit::UpdateMode; use bevy::winit::WinitSettings; use bevy_mod_xr::session::*; +use openxr::Event; use crate::error::OxrError; use crate::features::overlay::OxrOverlaySessionEvent; @@ -80,6 +81,7 @@ impl Default for OxrInitPlugin { impl Plugin for OxrInitPlugin { fn build(&self, app: &mut App) { + app.add_event::(); match self.init_xr() { Ok(( instance, @@ -260,12 +262,15 @@ impl OxrInitPlugin { )) } } +#[derive(Event, Clone, Copy, Debug, Default)] +pub struct OxrInteractionProfileChanged; /// Polls any OpenXR events and handles them accordingly pub fn poll_events( instance: Res, mut status: ResMut, mut changed_event: EventWriter, + mut interaction_profile_changed_event: EventWriter, mut overlay_writer: Option>>, ) { let _span = info_span!("xr_poll_events"); @@ -313,6 +318,10 @@ pub fn poll_events( warn!("Overlay Event Recieved without the OverlayPlugin being added!"); } } + // we might want to check if this is the correct session? + Event::InteractionProfileChanged(_) => { + interaction_profile_changed_event.send_default(); + } _ => {} } } diff --git a/crates/bevy_openxr/src/openxr/spaces.rs b/crates/bevy_openxr/src/openxr/spaces.rs index 666ab7e..028cc42 100644 --- a/crates/bevy_openxr/src/openxr/spaces.rs +++ b/crates/bevy_openxr/src/openxr/spaces.rs @@ -3,12 +3,12 @@ use std::{mem::MaybeUninit, ptr, sync::Mutex}; use bevy::{prelude::*, utils::hashbrown::HashSet}; use bevy_mod_xr::{ session::{session_available, session_running, XrFirst, XrHandleEvents}, - spaces::{XrDestroySpace, XrPrimaryReferenceSpace, XrReferenceSpace, XrSpace, XrSpatialOffset}, + spaces::{XrDestroySpace, XrPrimaryReferenceSpace, XrReferenceSpace, XrSpace, XrVelocity}, types::XrPose, }; use openxr::{ sys, HandJointLocation, HandJointLocations, HandJointVelocities, HandJointVelocity, - ReferenceSpaceType, SpaceLocationFlags, HAND_JOINT_COUNT, + ReferenceSpaceType, SpaceLocationFlags, SpaceVelocityFlags, HAND_JOINT_COUNT, }; use crate::{ @@ -27,6 +27,7 @@ impl Plugin for OxrSpacePatchingPlugin { app.add_systems(Startup, patch_destroy_space.run_if(session_available)); } } + pub struct OxrSpatialPlugin; impl Plugin for OxrSpatialPlugin { fn build(&self, app: &mut App) { @@ -42,10 +43,29 @@ impl Plugin for OxrSpatialPlugin { update_space_transforms .in_set(OxrSpaceSyncSet) .run_if(session_running), - ); + ) + .observe(add_location_flags) + .observe(add_velocity_flags); } } +fn add_velocity_flags(event: Trigger, mut cmds: Commands) { + if event.entity() == Entity::PLACEHOLDER { + error!("called add_location_flags observer without entity"); + return; + } + cmds.entity(event.entity()) + .insert(OxrSpaceLocationFlags(openxr::SpaceLocationFlags::default())); +} +fn add_location_flags(event: Trigger, mut cmds: Commands) { + if event.entity() == Entity::PLACEHOLDER { + error!("called add_location_flags observer without entity"); + return; + } + cmds.entity(event.entity()) + .insert(OxrSpaceLocationFlags(openxr::SpaceLocationFlags::default())); +} + fn destroy_space_event(instance: Res, mut events: EventReader) { for space in events.read() { match instance.destroy_space(space.0) { @@ -92,6 +112,34 @@ unsafe extern "system" fn patched_destroy_space(space: openxr::sys::Space) -> op } } +#[derive(Clone, Copy, Component)] +pub struct OxrSpaceLocationFlags(pub openxr::SpaceLocationFlags); +impl OxrSpaceLocationFlags { + pub fn pos_valid(&self) -> bool { + self.0.contains(SpaceLocationFlags::POSITION_VALID) + } + pub fn pos_tracked(&self) -> bool { + self.0.contains(SpaceLocationFlags::POSITION_TRACKED) + } + pub fn rot_valid(&self) -> bool { + self.0.contains(SpaceLocationFlags::ORIENTATION_VALID) + } + pub fn rot_tracked(&self) -> bool { + self.0.contains(SpaceLocationFlags::ORIENTATION_TRACKED) + } +} +#[derive(Clone, Copy, Component)] +pub struct OxrSpaceVelocityFlags(pub openxr::SpaceVelocityFlags); +impl OxrSpaceVelocityFlags { + pub fn linear_valid(&self) -> bool { + self.0.contains(SpaceVelocityFlags::LINEAR_VALID) + } + pub fn angular_valid(&self) -> bool { + self.0.contains(SpaceVelocityFlags::ANGULAR_VALID) + } +} + +#[allow(clippy::type_complexity)] fn update_space_transforms( session: Res, default_ref_space: Res, @@ -100,39 +148,61 @@ fn update_space_transforms( mut query: Query<( &mut Transform, &XrSpace, - Option<&XrSpatialOffset>, + Option<&mut XrVelocity>, Option<&XrReferenceSpace>, + &mut OxrSpaceLocationFlags, + Option<&mut OxrSpaceVelocityFlags>, )>, ) { - for (mut transform, space, offset, ref_space) in &mut query { - let offset = offset.copied().unwrap_or_default(); + for ( + mut transform, + space, + velocity, + ref_space, + mut space_location_flags, + space_velocity_flags, + ) in &mut query + { let ref_space = ref_space.unwrap_or(&default_ref_space); - if let Ok(space_location) = session.locate_space( - space, - ref_space, - if pipelined.is_some() { - openxr::Time::from_nanos( - frame_state.predicted_display_time.as_nanos() - + frame_state.predicted_display_period.as_nanos(), - ) - } else { - frame_state.predicted_display_time - }, - ) { - if space_location - .location_flags - .contains(SpaceLocationFlags::POSITION_VALID) - { - transform.translation = offset - .to_transform() - .transform_point(space_location.pose.position.to_vec3()) + let time = if pipelined.is_some() { + openxr::Time::from_nanos( + frame_state.predicted_display_time.as_nanos() + + frame_state.predicted_display_period.as_nanos(), + ) + } else { + frame_state.predicted_display_time + }; + let space_location = if let Some(mut velocity) = velocity { + match session.locate_space_with_velocity(space, ref_space, time) { + Ok((location, space_velocity)) => { + let flags = OxrSpaceVelocityFlags(space_velocity.velocity_flags); + if flags.linear_valid() { + velocity.linear = space_velocity.linear_velocity.to_vec3(); + } + if flags.linear_valid() { + velocity.linear = space_velocity.linear_velocity.to_vec3(); + } + let Some(mut vel_flags) = space_velocity_flags else { + error!("XrVelocity without OxrSpaceVelocityFlags"); + return; + }; + *vel_flags = flags; + Ok(location) + } + Err(err) => Err(err), } - if space_location - .location_flags - .contains(SpaceLocationFlags::ORIENTATION_VALID) - { - transform.rotation = offset.rotation * space_location.pose.orientation.to_quat(); + } else { + session.locate_space(space, ref_space, time) + }; + if let Ok(space_location) = space_location { + let flags = OxrSpaceLocationFlags(space_location.location_flags); + if flags.pos_valid() { + transform.translation = space_location.pose.position.to_vec3(); } + if flags.rot_valid() { + transform.rotation = space_location.pose.orientation.to_quat(); + } + *space_location_flags = flags; } } } diff --git a/crates/bevy_xr/src/spaces.rs b/crates/bevy_xr/src/spaces.rs index 9755ee5..cc3d157 100644 --- a/crates/bevy_xr/src/spaces.rs +++ b/crates/bevy_xr/src/spaces.rs @@ -3,19 +3,29 @@ use bevy::{ render::{extract_component::ExtractComponent, extract_resource::ExtractResource}, }; -use crate::types::XrPose; - /// Any Spaces will be invalid after the owning session exits #[repr(transparent)] #[derive(Clone, Copy, Hash, PartialEq, Eq, Reflect, Debug, Component, ExtractComponent)] pub struct XrSpace(u64); -// Does repr(transparent) even make sense here? -#[repr(transparent)] -#[derive( - Clone, Copy, PartialEq, Reflect, Debug, Component, ExtractComponent, Default, Deref, DerefMut, -)] -pub struct XrSpatialOffset(pub XrPose); +#[derive(Clone, Copy, Reflect, Debug, Component, ExtractComponent, Default)] +pub struct XrVelocity { + /// Velocity of a space relative to it's reference space + pub linear: Vec3, + /// Angular Velocity of a space relative to it's reference space + /// the direction of the vector is parrelel to the axis of rotation, + /// the magnitude is the relative angular speed in radians per second + /// the vector follows the right-hand rule for torque/rotation + pub angular: Vec3, +} +impl XrVelocity { + pub const fn new() -> XrVelocity { + XrVelocity { + linear: Vec3::ZERO, + angular: Vec3::ZERO, + } + } +} #[derive(Event, Clone, Copy, Deref, DerefMut)] pub struct XrDestroySpace(pub XrSpace);