diff --git a/crates/bevy_openxr/examples/actions.rs b/crates/bevy_openxr/examples/actions.rs index cf09c3a..aac0063 100644 --- a/crates/bevy_openxr/examples/actions.rs +++ b/crates/bevy_openxr/examples/actions.rs @@ -18,14 +18,8 @@ fn main() { create_action_entities.before(XRUtilsActionSystemSet::CreateEvents), ) .add_plugins(XRUtilsActionsPlugin) - .add_systems( - Update, - read_action_with_marker_component.after(XRUtilsActionSystemSet::SyncActionStates), - ) - .add_systems( - Update, - handle_flight_input.after(XRUtilsActionSystemSet::SyncActionStates), - ) + .add_systems(Update, read_action_with_marker_component) + .add_systems(Update, handle_flight_input) // Realtime lighting is expensive, use ambient light instead .insert_resource(AmbientLight { color: Default::default(), @@ -89,16 +83,22 @@ fn create_action_entities(mut commands: Commands) { .id(); //create a binding - let binding = commands + let binding_index = commands .spawn(XRUtilsBinding { profile: "/interaction_profiles/valve/index_controller".into(), binding: "/user/hand/right/input/thumbstick".into(), }) .id(); - + let binding_touch = commands + .spawn(XRUtilsBinding { + profile: "/interaction_profiles/oculus/touch_controller".into(), + binding: "/user/hand/right/input/thumbstick".into(), + }) + .id(); //add action to set, this isnt the best //TODO look into a better system - commands.entity(action).add_child(binding); + commands.entity(action).add_child(binding_index); + commands.entity(action).add_child(binding_touch); commands.entity(set).add_child(action); } diff --git a/crates/bevy_openxr/examples/raw_actions.rs b/crates/bevy_openxr/examples/raw_actions.rs new file mode 100644 index 0000000..d1f268f --- /dev/null +++ b/crates/bevy_openxr/examples/raw_actions.rs @@ -0,0 +1,160 @@ +use std::ops::Deref; + +use bevy::prelude::*; +use bevy_openxr::{ + action_binding::{OxrSendActionBindings, OxrSuggestActionBinding}, + action_set_attaching::OxrAttachActionSet, + action_set_syncing::{OxrActionSetSyncSet, OxrSyncActionSet}, + add_xr_plugins, + init::OxrTrackingRoot, + resources::OxrInstance, + session::OxrSession, + spaces::OxrSpaceExt, +}; +use bevy_xr::{ + session::{session_available, XrSessionCreated}, + spaces::{XrSpace, XrSpatialTransform}, + types::XrPose, +}; +use openxr::Posef; + +fn main() { + let mut app = App::new(); + app.add_plugins(add_xr_plugins(DefaultPlugins)); + app.add_systems(XrSessionCreated, spawn_hands); + app.add_systems(XrSessionCreated, attach_set); + app.add_systems(PreUpdate, sync_actions.before(OxrActionSetSyncSet)); + app.add_systems(OxrSendActionBindings, suggest_action_bindings); + app.add_systems(Startup, create_actions.run_if(session_available)); + app.add_systems(Startup, setup); + + app.run(); +} + +fn attach_set(actions: Res, mut attach: EventWriter) { + attach.send(OxrAttachActionSet(actions.set.clone())); +} + +#[derive(Resource)] +struct ControllerActions { + set: openxr::ActionSet, + left: openxr::Action, + right: openxr::Action, +} +fn sync_actions(actions: Res, mut sync: EventWriter) { + sync.send(OxrSyncActionSet(actions.set.clone())); +} +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // circular base + commands.spawn(PbrBundle { + mesh: meshes.add(Circle::new(4.0)), + material: materials.add(Color::WHITE), + transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)), + ..default() + }); + // cube + commands.spawn(PbrBundle { + mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)), + material: materials.add(Color::rgb_u8(124, 144, 255)), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }); + // light + commands.spawn(PointLightBundle { + point_light: PointLight { + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} +fn suggest_action_bindings( + actions: Res, + mut bindings: EventWriter, +) { + bindings.send(OxrSuggestActionBinding { + action: actions.left.as_raw(), + interaction_profile: "/interaction_profiles/oculus/touch_controller".into(), + bindings: vec!["/user/hand/left/input/grip/pose".into()], + }); + bindings.send(OxrSuggestActionBinding { + action: actions.right.as_raw(), + interaction_profile: "/interaction_profiles/oculus/touch_controller".into(), + bindings: vec!["/user/hand/right/input/grip/pose".into()], + }); +} +fn create_actions(instance: Res, mut cmds: Commands) { + let set = instance.create_action_set("hands", "Hands", 0).unwrap(); + let left = set + .create_action("left_pose", "Left Hand Grip Pose", &[]) + .unwrap(); + let right = set + .create_action("right_pose", "Right Hand Grip Pose", &[]) + .unwrap(); + + cmds.insert_resource(ControllerActions { set, left, right }) +} + +fn spawn_hands( + actions: Res, + mut cmds: Commands, + root: Query>, + session: Res, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + let l = actions + .left + .create_space( + session.deref().deref().clone(), + openxr::Path::NULL, + Posef::IDENTITY, + ) + .unwrap(); + let left_space = XrSpace::from_openxr_space(l); + // let left_space = session + // .create_action_space(&actions.left, openxr::Path::NULL, XrPose::IDENTITY) + // .unwrap(); + let right_space = session + .create_action_space(&actions.right, openxr::Path::NULL, XrPose::IDENTITY) + .unwrap(); + let left = cmds + .spawn(( + PbrBundle { + mesh: meshes.add(Cuboid::new(0.1, 0.1, 0.05)), + material: materials.add(Color::rgb_u8(124, 144, 255)), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }, + XrSpatialTransform::from_space(left_space), + Controller, + )) + .id(); + let right = cmds + .spawn(( + PbrBundle { + mesh: meshes.add(Cuboid::new(0.1, 0.1, 0.05)), + material: materials.add(Color::rgb_u8(124, 144, 255)), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }, + XrSpatialTransform::from_space(right_space), + Controller, + )) + .id(); + + cmds.entity(root.single()).push_children(&[left, right]); +} + +#[derive(Component)] +struct Controller; diff --git a/crates/bevy_openxr/src/openxr/action_set_attaching.rs b/crates/bevy_openxr/src/openxr/action_set_attaching.rs index 272e8b0..53efcd1 100644 --- a/crates/bevy_openxr/src/openxr/action_set_attaching.rs +++ b/crates/bevy_openxr/src/openxr/action_set_attaching.rs @@ -1,4 +1,7 @@ -use crate::{action_binding::run_action_binding_sugestion, session::{OxrSession, OxrSessionStatusEvent}}; +use crate::{ + action_binding::run_action_binding_sugestion, + session::{OxrSession, OxrSessionStatusEvent}, +}; use bevy::prelude::*; impl Plugin for OxrActionAttachingPlugin { diff --git a/crates/bevy_openxr/src/openxr/features/handtracking.rs b/crates/bevy_openxr/src/openxr/features/handtracking.rs index c60750c..d5cedba 100644 --- a/crates/bevy_openxr/src/openxr/features/handtracking.rs +++ b/crates/bevy_openxr/src/openxr/features/handtracking.rs @@ -1,5 +1,6 @@ use bevy::prelude::*; use bevy_xr::hands::{LeftHand, RightHand}; +use bevy_xr::spaces::{XrPrimaryReferenceSpace, XrReferenceSpace}; use bevy_xr::{ hands::{HandBone, HandBoneRadius}, session::{session_running, XrSessionCreated, XrSessionExiting}, @@ -9,7 +10,6 @@ use openxr::SpaceLocationFlags; use crate::resources::Pipelined; use crate::{ init::OxrTrackingRoot, - reference_space::{OxrPrimaryReferenceSpace, OxrReferenceSpace}, resources::OxrFrameState, session::OxrSession, }; @@ -130,21 +130,23 @@ pub struct OxrHandBoneEntities(pub [Entity; 26]); pub struct OxrHandTracker(pub openxr::HandTracker); fn locate_hands( - default_ref_space: Res, + default_ref_space: Res, frame_state: Res, tracker_query: Query<( &OxrHandTracker, - Option<&OxrReferenceSpace>, + Option<&XrReferenceSpace>, &OxrHandBoneEntities, )>, + session: Res, mut bone_query: Query<(&HandBone, &mut HandBoneRadius, &mut Transform)>, pipelined: Option>, ) { for (tracker, ref_space, hand_entities) in &tracker_query { let ref_space = ref_space.map(|v| &v.0).unwrap_or(&default_ref_space.0); // relate_hand_joints also provides velocities - let joints = match ref_space.locate_hand_joints( + 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() diff --git a/crates/bevy_openxr/src/openxr/helper_traits.rs b/crates/bevy_openxr/src/openxr/helper_traits.rs index ab8d865..c9ffe41 100644 --- a/crates/bevy_openxr/src/openxr/helper_traits.rs +++ b/crates/bevy_openxr/src/openxr/helper_traits.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use bevy_xr::types::XrPose; pub trait ToPosef { fn to_posef(&self) -> openxr::Posef; @@ -6,6 +7,9 @@ pub trait ToPosef { pub trait ToTransform { fn to_transform(&self) -> Transform; } +pub trait ToXrPose { + fn to_xr_pose(&self) -> XrPose; +} pub trait ToQuaternionf { fn to_quaternionf(&self) -> openxr::Quaternionf; } @@ -38,6 +42,22 @@ impl ToTransform for openxr::Posef { .with_rotation(self.orientation.to_quat()) } } +impl ToXrPose for openxr::Posef { + fn to_xr_pose(&self) -> XrPose { + XrPose { + position: self.position.to_vec3(), + rotation: self.orientation.to_quat(), + } + } +} +impl ToPosef for XrPose { + fn to_posef(&self) -> openxr::Posef { + openxr::Posef { + orientation: self.rotation.to_quaternionf(), + position: self.position.to_vector3f(), + } + } +} impl ToQuaternionf for Quat { fn to_quaternionf(&self) -> openxr::Quaternionf { @@ -51,7 +71,14 @@ impl ToQuaternionf for Quat { } impl ToQuat for openxr::Quaternionf { fn to_quat(&self) -> Quat { - Quat::from_xyzw(self.x, self.y, self.z, self.w) + let mut quat = Quat::from_xyzw(self.x, self.y, self.z, self.w); + if quat.length() == 0.0 { + quat = Quat::IDENTITY; + } + if !quat.is_normalized() { + quat = quat.normalize(); + } + quat } } impl ToVector3f for Vec3 { diff --git a/crates/bevy_openxr/src/openxr/layer_builder.rs b/crates/bevy_openxr/src/openxr/layer_builder.rs index 1042c67..a8ae794 100644 --- a/crates/bevy_openxr/src/openxr/layer_builder.rs +++ b/crates/bevy_openxr/src/openxr/layer_builder.rs @@ -1,11 +1,12 @@ use std::mem; use bevy::ecs::world::World; -use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di, Space}; +use bevy_xr::spaces::{XrPrimaryReferenceSpace, XrSpace}; +use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di}; use crate::graphics::graphics_match; -use crate::reference_space::OxrPrimaryReferenceSpace; use crate::resources::*; +use crate::spaces::OxrSpaceExt as _; pub trait LayerProvider { fn get<'a>(&'a self, world: &'a World) -> Option>; @@ -17,7 +18,7 @@ pub struct PassthroughLayer; impl LayerProvider for ProjectionLayer { fn get<'a>(&self, world: &'a World) -> Option + 'a>> { - let stage = world.get_resource::()?; + let stage = world.get_resource::()?; let openxr_views = world.get_resource::()?; let swapchain = world.get_resource::()?; let graphics_info = world.get_resource::()?; @@ -205,8 +206,8 @@ impl<'a> CompositionLayerProjection<'a> { self } #[inline] - pub fn space(mut self, value: &'a Space) -> Self { - self.inner.space = value.as_raw(); + pub fn space(mut self, value: &XrSpace) -> Self { + self.inner.space = value.as_raw_openxr_space(); self } #[inline] diff --git a/crates/bevy_openxr/src/openxr/mod.rs b/crates/bevy_openxr/src/openxr/mod.rs index 95fb677..8ccc780 100644 --- a/crates/bevy_openxr/src/openxr/mod.rs +++ b/crates/bevy_openxr/src/openxr/mod.rs @@ -32,6 +32,7 @@ pub mod render; pub mod resources; pub mod session; pub mod types; +pub mod spaces; pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { plugins @@ -50,6 +51,7 @@ pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { .add(action_binding::OxrActionBindingPlugin) .add(action_set_syncing::OxrActionSyncingPlugin) .add(features::overlay::OxrOverlayPlugin) + .add(spaces::OxrSpatialPlugin) // .add(XrActionPlugin) // we should probably handle the exiting ourselfs so that we can correctly end the // session and instance diff --git a/crates/bevy_openxr/src/openxr/reference_space.rs b/crates/bevy_openxr/src/openxr/reference_space.rs index a3001ec..af76ff2 100644 --- a/crates/bevy_openxr/src/openxr/reference_space.rs +++ b/crates/bevy_openxr/src/openxr/reference_space.rs @@ -7,7 +7,10 @@ use bevy::{ RenderApp, }, }; -use bevy_xr::session::{XrSessionCreated, XrSessionExiting}; +use bevy_xr::{ + session::{XrSessionCreated, XrSessionExiting}, + spaces::{XrPrimaryReferenceSpace, XrReferenceSpace}, +}; use crate::session::OxrSession; @@ -25,19 +28,19 @@ impl Default for OxrReferenceSpacePlugin { #[derive(Resource)] struct OxrDefaultPrimaryReferenceSpaceType(openxr::ReferenceSpaceType); /// The Default Reference space used for locating things -#[derive(Resource, Deref, ExtractResource, Clone)] -pub struct OxrPrimaryReferenceSpace(pub Arc); +// #[derive(Resource, Deref, ExtrctResource, Clone)] +// pub struct OxrPrimaryReferenceSpace(pub Arc); /// The Reference space used for locating spaces on this entity -#[derive(Component)] -pub struct OxrReferenceSpace(pub openxr::Space); +// #[derive(Component)] +// pub struct OxrReferenceSpace(pub openxr::Space); impl Plugin for OxrReferenceSpacePlugin { fn build(&self, app: &mut App) { app.insert_resource(OxrDefaultPrimaryReferenceSpaceType( self.default_primary_ref_space, )); - app.add_plugins(ExtractResourcePlugin::::default()); + app.add_plugins(ExtractResourcePlugin::::default()); app.add_systems(XrSessionCreated, set_primary_ref_space); app.add_systems(XrSessionExiting, cleanup); app.sub_app_mut(RenderApp) @@ -45,10 +48,10 @@ impl Plugin for OxrReferenceSpacePlugin { } } -fn cleanup(mut cmds: Commands, query: Query>) { - cmds.remove_resource::(); +fn cleanup(mut cmds: Commands, query: Query>) { + cmds.remove_resource::(); for e in &query { - cmds.entity(e).remove::(); + cmds.entity(e).remove::(); } } @@ -57,9 +60,9 @@ fn set_primary_ref_space( space_type: Res, mut cmds: Commands, ) { - match session.create_reference_space(space_type.0, openxr::Posef::IDENTITY) { + match session.create_reference_space(space_type.0, Transform::IDENTITY) { Ok(space) => { - cmds.insert_resource(OxrPrimaryReferenceSpace(Arc::new(space))); + cmds.insert_resource(XrPrimaryReferenceSpace(space)); } Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { error!("Required Extension for Reference Space not loaded"); diff --git a/crates/bevy_openxr/src/openxr/render.rs b/crates/bevy_openxr/src/openxr/render.rs index 89dd188..6dce2f3 100644 --- a/crates/bevy_openxr/src/openxr/render.rs +++ b/crates/bevy_openxr/src/openxr/render.rs @@ -15,15 +15,17 @@ use bevy::{ use bevy_xr::{ camera::{XrCamera, XrCameraBundle, XrProjection}, session::{session_running, XrSessionExiting}, + spaces::XrPrimaryReferenceSpace, }; use openxr::ViewStateFlags; +use crate::resources::*; +use crate::spaces::OxrSpaceExt as _; use crate::{ init::{session_started, OxrHandleEvents, OxrLast, OxrTrackingRoot}, layer_builder::ProjectionLayer, session::OxrSession, }; -use crate::{reference_space::OxrPrimaryReferenceSpace, resources::*}; #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] pub struct OxrRenderBegin; @@ -273,7 +275,7 @@ pub fn update_cameras( pub fn locate_views( session: Res, - ref_space: Res, + ref_space: Res, frame_state: Res, mut openxr_views: ResMut, pipelined: Option>, diff --git a/crates/bevy_openxr/src/openxr/session.rs b/crates/bevy_openxr/src/openxr/session.rs index 1ce507a..c739d64 100644 --- a/crates/bevy_openxr/src/openxr/session.rs +++ b/crates/bevy_openxr/src/openxr/session.rs @@ -6,11 +6,14 @@ use crate::resources::{ OxrCleanupSession, OxrPassthrough, OxrPassthroughLayer, OxrSessionStarted, OxrSwapchain, }; use crate::types::{Result, SwapchainCreateInfo}; +use bevy::app::AppExit; use bevy::ecs::event::ManualEventReader; use bevy::ecs::system::RunSystemOnce; use bevy::prelude::*; use bevy::render::RenderApp; -use bevy_xr::session::{status_changed_to, XrSessionCreated, XrSessionExiting, XrStatus}; +use bevy_xr::session::{ + session_running, status_changed_to, XrSessionCreated, XrSessionExiting, XrStatus, +}; use openxr::AnyGraphics; use crate::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap}; @@ -28,6 +31,12 @@ impl Plugin for OxrSessionPlugin { app.init_non_send_resource::(); app.add_event::(); app.add_systems(OxrLast, run_session_status_schedules.after(OxrHandleEvents)); + app.add_systems( + OxrLast, + exits_session_on_app_exit + .before(OxrHandleEvents) + .run_if(on_event::().and_then(session_running)), + ); app.add_systems(XrSessionExiting, clean_session); app.sub_app_mut(RenderApp) .add_systems(XrSessionExiting, |mut cmds: Commands| { @@ -39,6 +48,9 @@ impl Plugin for OxrSessionPlugin { ); } } +fn exits_session_on_app_exit(session: Res) { + session.request_exit().unwrap() +} fn handle_stopping_state(session: Res, mut session_started: ResMut) { session.end().expect("Failed to end session"); diff --git a/crates/bevy_openxr/src/openxr/spaces.rs b/crates/bevy_openxr/src/openxr/spaces.rs new file mode 100644 index 0000000..120c1d5 --- /dev/null +++ b/crates/bevy_openxr/src/openxr/spaces.rs @@ -0,0 +1,518 @@ +use std::{mem::MaybeUninit, ptr, sync::Mutex}; + +use bevy::{prelude::*, utils::hashbrown::HashSet}; +use bevy_xr::{ + session::{session_available, session_running}, + spaces::{ + XrDestroySpace, XrPrimaryReferenceSpace, XrReferenceSpace, XrSpace, XrSpatialTransform, + }, + types::XrPose, +}; +use openxr::{ + sys, HandJointLocation, HandJointLocations, HandJointVelocities, HandJointVelocity, + ReferenceSpaceType, SpaceLocationFlags, HAND_JOINT_COUNT, +}; + +use crate::{ + helper_traits::{ToPosef, ToQuat, ToVec3}, + init::{OxrHandleEvents, OxrLast}, + resources::{OxrFrameState, OxrInstance, Pipelined}, + session::OxrSession, +}; + +pub struct OxrSpatialPlugin; +impl Plugin for OxrSpatialPlugin { + fn build(&self, app: &mut App) { + app.add_event::(); + app.add_systems(PreUpdate, update_spatial_transforms.run_if(session_running)); + app.add_systems(Startup, patch_destroy_space.run_if(session_available)); + app.add_systems(OxrLast, destroy_space_event.before(OxrHandleEvents)); + } +} + +fn destroy_space_event(instance: Res, mut events: EventReader) { + for space in events.read() { + unsafe { + (instance.fp().destroy_space)(space.as_raw_openxr_space()); + } + } +} + +pub static OXR_DO_NOT_CALL_DESTOY_SPACE_FOR: Mutex>> = Mutex::new(None); +pub static OXR_ORIGINAL_DESTOY_SPACE: Mutex> = + Mutex::new(None); + +fn patch_destroy_space(instance: ResMut) { + OXR_DO_NOT_CALL_DESTOY_SPACE_FOR + .lock() + .unwrap() + .replace(HashSet::new()); + let raw_instance_ptr = instance.fp() as *const _ as *mut openxr::raw::Instance; + unsafe { + OXR_ORIGINAL_DESTOY_SPACE + .lock() + .unwrap() + .replace((*raw_instance_ptr).destroy_space); + + (*raw_instance_ptr).destroy_space = patched_destroy_space; + } +} +unsafe extern "system" fn patched_destroy_space(space: openxr::sys::Space) -> openxr::sys::Result { + if !OXR_DO_NOT_CALL_DESTOY_SPACE_FOR + .lock() + .unwrap() + .as_ref() + .unwrap() + .contains(&space.into_raw()) + { + OXR_ORIGINAL_DESTOY_SPACE + .lock() + .unwrap() + .expect("has to be initialized")(space) + } else { + info!("Inject Worked, not destroying space"); + openxr::sys::Result::SUCCESS + } +} + +fn update_spatial_transforms( + session: Res, + default_ref_space: Res, + pipelined: Option>, + frame_state: Res, + mut query: Query<( + &mut Transform, + &XrSpatialTransform, + Option<&XrReferenceSpace>, + )>, +) { + for (mut transform, spatial, ref_space) in &mut query { + let ref_space = ref_space.unwrap_or(&default_ref_space); + if let Ok(space_location) = session.locate_space( + &spatial.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 = spatial + .offset + .transform_point(space_location.pose.position.to_vec3()) + } + if space_location + .location_flags + .contains(SpaceLocationFlags::ORIENTATION_VALID) + { + transform.rotation = + spatial.offset.rotation * space_location.pose.orientation.to_quat(); + } + } + } +} + +impl OxrSession { + pub fn create_action_space( + &self, + action: &openxr::Action, + subaction_path: openxr::Path, + pose_in_space: XrPose, + ) -> openxr::Result { + let info = sys::ActionSpaceCreateInfo { + ty: sys::ActionSpaceCreateInfo::TYPE, + next: ptr::null(), + action: action.as_raw(), + subaction_path, + pose_in_action_space: pose_in_space.to_posef(), + }; + let mut out = sys::Space::NULL; + unsafe { + cvt((self.instance().fp().create_action_space)( + self.as_raw(), + &info, + &mut out, + ))?; + Ok(XrSpace::from_raw(out.into_raw())) + } + } + pub fn create_reference_space( + &self, + ref_space_type: ReferenceSpaceType, + pose_in_ref_space: Transform, + ) -> openxr::Result { + let info = sys::ReferenceSpaceCreateInfo { + ty: sys::ReferenceSpaceCreateInfo::TYPE, + next: ptr::null(), + reference_space_type: ref_space_type, + pose_in_reference_space: pose_in_ref_space.to_posef(), + }; + let mut out = sys::Space::NULL; + unsafe { + cvt((self.instance().fp().create_reference_space)( + self.as_raw(), + &info, + &mut out, + ))?; + Ok(XrReferenceSpace(XrSpace::from_raw(out.into_raw()))) + } + } +} +fn locate_space( + instance: &openxr::Instance, + space: &XrSpace, + base: &XrSpace, + time: openxr::Time, +) -> openxr::Result { + unsafe { + let mut x = sys::SpaceLocation::out(ptr::null_mut()); + cvt((instance.fp().locate_space)( + space.as_raw_openxr_space(), + base.as_raw_openxr_space(), + time, + x.as_mut_ptr(), + ))?; + Ok(create_space_location(&x)) + } +} +fn locate_space_with_velocity( + instance: &openxr::Instance, + space: &XrSpace, + base: &XrSpace, + time: openxr::Time, +) -> openxr::Result<(openxr::SpaceLocation, openxr::SpaceVelocity)> { + unsafe { + let mut velocity = sys::SpaceVelocity::out(ptr::null_mut()); + let mut location = sys::SpaceLocation::out(&mut velocity as *mut _ as _); + cvt((instance.fp().locate_space)( + space.as_raw_openxr_space(), + base.as_raw_openxr_space(), + time, + location.as_mut_ptr(), + ))?; + Ok(( + create_space_location(&location), + create_space_velocity(&velocity), + )) + } +} +pub fn locate_hand_joints( + instance: &openxr::Instance, + tracker: &openxr::HandTracker, + base: &XrSpace, + time: openxr::Time, +) -> openxr::Result> { + unsafe { + let locate_info = sys::HandJointsLocateInfoEXT { + ty: sys::HandJointsLocateInfoEXT::TYPE, + next: ptr::null(), + base_space: base.as_raw_openxr_space(), + time, + }; + let mut locations = + MaybeUninit::<[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]>::uninit(); + let mut location_info = sys::HandJointLocationsEXT { + ty: sys::HandJointLocationsEXT::TYPE, + next: ptr::null_mut(), + is_active: false.into(), + joint_count: openxr::HAND_JOINT_COUNT as u32, + joint_locations: locations.as_mut_ptr() as _, + }; + cvt((instance + .exts() + .ext_hand_tracking + .as_ref() + .expect("Somehow created HandTracker without XR_EXT_hand_tracking being enabled") + .locate_hand_joints)( + tracker.as_raw(), + &locate_info, + &mut location_info, + ))?; + Ok(if location_info.is_active.into() { + Some(locations.assume_init()) + } else { + None + }) + } +} +pub fn locate_hand_joints_with_velocities( + instance: &openxr::Instance, + tracker: &openxr::HandTracker, + base: &XrSpace, + time: openxr::Time, +) -> openxr::Result> { + unsafe { + let locate_info = sys::HandJointsLocateInfoEXT { + ty: sys::HandJointsLocateInfoEXT::TYPE, + next: ptr::null(), + base_space: base.as_raw_openxr_space(), + time, + }; + let mut velocities = MaybeUninit::<[HandJointVelocity; HAND_JOINT_COUNT]>::uninit(); + let mut velocity_info = sys::HandJointVelocitiesEXT { + ty: sys::HandJointVelocitiesEXT::TYPE, + next: ptr::null_mut(), + joint_count: HAND_JOINT_COUNT as u32, + joint_velocities: velocities.as_mut_ptr() as _, + }; + let mut locations = MaybeUninit::<[HandJointLocation; HAND_JOINT_COUNT]>::uninit(); + let mut location_info = sys::HandJointLocationsEXT { + ty: sys::HandJointLocationsEXT::TYPE, + next: &mut velocity_info as *mut _ as _, + is_active: false.into(), + joint_count: HAND_JOINT_COUNT as u32, + joint_locations: locations.as_mut_ptr() as _, + }; + cvt((instance + .exts() + .ext_hand_tracking + .as_ref() + .expect("Somehow created HandTracker without XR_EXT_hand_tracking being enabled") + .locate_hand_joints)( + tracker.as_raw(), + &locate_info, + &mut location_info, + ))?; + Ok(if location_info.is_active.into() { + Some((locations.assume_init(), velocities.assume_init())) + } else { + None + }) + } +} +pub fn destroy_space(instance: &openxr::Instance, space: sys::Space) -> openxr::Result { + let result = unsafe { (instance.fp().destroy_space)(space) }; + cvt(result) +} +impl OxrSession { + pub fn destroy_space(&self, space: XrSpace) { + let _ = destroy_space(self.instance(), space.as_raw_openxr_space()); + } + pub fn destroy_openxr_space(&self, space: openxr::Space) { + let _ = destroy_space(self.instance(), space.as_raw()); + } + pub fn locate_views( + &self, + view_configuration_type: openxr::ViewConfigurationType, + display_time: openxr::Time, + ref_space: &XrReferenceSpace, + ) -> openxr::Result<(openxr::ViewStateFlags, Vec)> { + let info = sys::ViewLocateInfo { + ty: sys::ViewLocateInfo::TYPE, + next: ptr::null(), + view_configuration_type, + display_time, + space: ref_space.as_raw_openxr_space(), + }; + let (flags, raw) = unsafe { + let mut out = sys::ViewState::out(ptr::null_mut()); + let raw = get_arr_init(sys::View::out(ptr::null_mut()), |cap, count, buf| { + (self.instance().fp().locate_views)( + self.as_raw(), + &info, + out.as_mut_ptr(), + cap, + count, + buf as _, + ) + })?; + (out.assume_init().view_state_flags, raw) + }; + Ok(( + flags, + raw.into_iter() + .map(|x| unsafe { create_view(flags, &x) }) + .collect(), + )) + } + pub fn locate_space( + &self, + space: &XrSpace, + base: &XrSpace, + time: openxr::Time, + ) -> openxr::Result { + locate_space(self.instance(), space, base, time) + } + pub fn locate_space_with_velocity( + &self, + space: &XrSpace, + base: &XrSpace, + time: openxr::Time, + ) -> openxr::Result<(openxr::SpaceLocation, openxr::SpaceVelocity)> { + locate_space_with_velocity(self.instance(), space, base, time) + } + pub fn locate_hand_joints( + &self, + tracker: &openxr::HandTracker, + base: &XrSpace, + time: openxr::Time, + ) -> openxr::Result> { + locate_hand_joints(self.instance(), tracker, base, time) + } + pub fn locate_hand_joints_with_velocities( + &self, + tracker: &openxr::HandTracker, + base: &XrSpace, + time: openxr::Time, + ) -> openxr::Result> { + locate_hand_joints_with_velocities(self.instance(), tracker, base, time) + } +} +impl OxrInstance { + pub fn locate_space( + &self, + space: &XrSpace, + base: &XrSpace, + time: openxr::Time, + ) -> openxr::Result { + locate_space(self, space, base, time) + } + pub fn locate_space_with_velocity( + &self, + space: &XrSpace, + base: &XrSpace, + time: openxr::Time, + ) -> openxr::Result<(openxr::SpaceLocation, openxr::SpaceVelocity)> { + locate_space_with_velocity(self, space, base, time) + } + pub fn locate_hand_joints( + &self, + tracker: &openxr::HandTracker, + base: &XrSpace, + time: openxr::Time, + ) -> openxr::Result> { + locate_hand_joints(self, tracker, base, time) + } + pub fn locate_hand_joints_with_velocities( + &self, + tracker: &openxr::HandTracker, + base: &XrSpace, + time: openxr::Time, + ) -> openxr::Result> { + locate_hand_joints_with_velocities(self, tracker, base, time) + } +} + +/// # Safety +/// This is an Extension trait. DO NOT IMPLEMENT IT! +pub unsafe trait OxrSpaceExt { + fn as_raw_openxr_space(&self) -> sys::Space; + fn from_raw_openxr_space(space: sys::Space) -> Self; + fn from_openxr_space(space: openxr::Space) -> Self; + /// # Safety + /// Session has to be the session from which the space is from + unsafe fn to_openxr_space(self, session: &openxr::Session) -> openxr::Space; +} + +unsafe impl OxrSpaceExt for XrSpace { + fn as_raw_openxr_space(&self) -> sys::Space { + sys::Space::from_raw(self.as_raw()) + } + + fn from_raw_openxr_space(space: sys::Space) -> Self { + let raw = space.into_raw(); + OXR_DO_NOT_CALL_DESTOY_SPACE_FOR + .lock() + .unwrap() + .as_mut() + .unwrap() + .insert(raw); + unsafe { XrSpace::from_raw(raw) } + } + + fn from_openxr_space(space: openxr::Space) -> Self { + Self::from_raw_openxr_space(space.as_raw()) + } + + unsafe fn to_openxr_space(self, session: &openxr::Session) -> openxr::Space { + unsafe { openxr::Space::reference_from_raw(session.clone(), self.as_raw_openxr_space()) } + } +} + +fn cvt(x: sys::Result) -> openxr::Result { + if x.into_raw() >= 0 { + Ok(x) + } else { + Err(x) + } +} +unsafe fn create_view(flags: openxr::ViewStateFlags, raw: &MaybeUninit) -> openxr::View { + // Applications *must* not read invalid parts of a poses, i.e. they may be uninitialized + let ptr = raw.as_ptr(); + openxr::View { + pose: openxr::Posef { + orientation: flags + .contains(sys::ViewStateFlags::ORIENTATION_VALID) + .then(|| *ptr::addr_of!((*ptr).pose.orientation)) + .unwrap_or_default(), + position: flags + .contains(sys::ViewStateFlags::POSITION_VALID) + .then(|| *ptr::addr_of!((*ptr).pose.position)) + .unwrap_or_default(), + }, + fov: *ptr::addr_of!((*ptr).fov), + } +} +unsafe fn create_space_location(raw: &MaybeUninit) -> openxr::SpaceLocation { + // Applications *must* not read invalid parts of a pose, i.e. they may be uninitialized + let ptr = raw.as_ptr(); + let flags = *ptr::addr_of!((*ptr).location_flags); + openxr::SpaceLocation { + location_flags: flags, + pose: openxr::Posef { + orientation: flags + .contains(sys::SpaceLocationFlags::ORIENTATION_VALID) + .then(|| *ptr::addr_of!((*ptr).pose.orientation)) + .unwrap_or_default(), + position: flags + .contains(sys::SpaceLocationFlags::POSITION_VALID) + .then(|| *ptr::addr_of!((*ptr).pose.position)) + .unwrap_or_default(), + }, + } +} +unsafe fn create_space_velocity(raw: &MaybeUninit) -> openxr::SpaceVelocity { + // Applications *must* not read invalid velocities, i.e. they may be uninitialized + let ptr = raw.as_ptr(); + let flags = *ptr::addr_of!((*ptr).velocity_flags); + openxr::SpaceVelocity { + velocity_flags: flags, + linear_velocity: flags + .contains(sys::SpaceVelocityFlags::LINEAR_VALID) + .then(|| *ptr::addr_of!((*ptr).linear_velocity)) + .unwrap_or_default(), + angular_velocity: flags + .contains(sys::SpaceVelocityFlags::ANGULAR_VALID) + .then(|| *ptr::addr_of!((*ptr).angular_velocity)) + .unwrap_or_default(), + } +} +fn get_arr_init( + init: T, + mut getter: impl FnMut(u32, &mut u32, *mut T) -> sys::Result, +) -> openxr::Result> { + let mut output = 0; + cvt(getter(0, &mut output, std::ptr::null_mut()))?; + let mut buffer = vec![init; output as usize]; + loop { + match cvt(getter(output, &mut output, buffer.as_mut_ptr() as _)) { + Ok(_) => { + buffer.truncate(output as usize); + return Ok(buffer); + } + Err(sys::Result::ERROR_SIZE_INSUFFICIENT) => { + buffer.resize(output as usize, init); + } + Err(e) => { + return Err(e); + } + } + } +} diff --git a/crates/bevy_xr/src/lib.rs b/crates/bevy_xr/src/lib.rs index de0bfef..7e95137 100644 --- a/crates/bevy_xr/src/lib.rs +++ b/crates/bevy_xr/src/lib.rs @@ -3,3 +3,4 @@ pub mod camera; pub mod hands; pub mod session; pub mod types; +pub mod spaces; diff --git a/crates/bevy_xr/src/spaces.rs b/crates/bevy_xr/src/spaces.rs new file mode 100644 index 0000000..eba5f33 --- /dev/null +++ b/crates/bevy_xr/src/spaces.rs @@ -0,0 +1,46 @@ +use bevy::{ + prelude::*, + render::{extract_component::ExtractComponent, extract_resource::ExtractResource}, +}; + +/// Any Spaces will be invalid after the owning session exits +#[repr(transparent)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, Reflect, Debug)] +pub struct XrSpace(u64); + +#[derive(Clone, Copy, PartialEq, Reflect, Debug, Component, ExtractComponent)] +pub struct XrSpatialTransform { + pub space: XrSpace, + pub offset: Transform, +} +impl XrSpatialTransform { + pub const fn from_space(space: XrSpace) -> Self { + Self { space, offset: Transform::IDENTITY } + } +} + +#[derive(Event, Clone, Copy, Deref, DerefMut)] +pub struct XrDestroySpace(pub XrSpace); + +#[repr(transparent)] +#[derive( + Clone, Copy, Hash, PartialEq, Eq, Reflect, Debug, Component, Deref, DerefMut, ExtractComponent, +)] +pub struct XrReferenceSpace(pub XrSpace); + +#[repr(transparent)] +#[derive( + Clone, Copy, Hash, PartialEq, Eq, Reflect, Debug, Resource, Deref, DerefMut, ExtractResource, +)] +pub struct XrPrimaryReferenceSpace(pub XrReferenceSpace); + +impl XrSpace { + /// # Safety + /// only call with known valid handles + pub unsafe fn from_raw(handle: u64) -> Self { + Self(handle) + } + pub fn as_raw(&self) -> u64 { + self.0 + } +} diff --git a/crates/bevy_xr/src/types.rs b/crates/bevy_xr/src/types.rs index 7cedb64..6372fff 100644 --- a/crates/bevy_xr/src/types.rs +++ b/crates/bevy_xr/src/types.rs @@ -1,6 +1,13 @@ use bevy::math::{Quat, Vec3}; -pub struct Pose { +pub struct XrPose { pub position: Vec3, - pub orientation: Quat, + pub rotation: Quat, +} + +impl XrPose { + pub const IDENTITY: XrPose = XrPose { + position: Vec3::ZERO, + rotation: Quat::IDENTITY, + }; } diff --git a/crates/bevy_xr_utils/src/xr_utils_actions.rs b/crates/bevy_xr_utils/src/xr_utils_actions.rs index 2c45c1d..051440c 100644 --- a/crates/bevy_xr_utils/src/xr_utils_actions.rs +++ b/crates/bevy_xr_utils/src/xr_utils_actions.rs @@ -56,42 +56,59 @@ use bevy::prelude::*; use bevy_openxr::{ action_binding::OxrSuggestActionBinding, action_set_attaching::OxrAttachActionSet, + action_set_syncing::OxrActionSetSyncSet, action_set_syncing::OxrSyncActionSet, resources::OxrInstance, session::OxrSession, }; -use openxr::{ActiveActionSet, Path, Vector2f}; +use bevy_xr::session::{session_available, session_running}; +use openxr::{Path, Vector2f}; use std::borrow::Cow; pub struct XRUtilsActionsPlugin; impl Plugin for XRUtilsActionsPlugin { fn build(&self, app: &mut App) { + app.configure_sets( + Startup, + XRUtilsActionSystemSet::CreateEvents.run_if(session_available), + ); + app.configure_sets( + PreUpdate, + XRUtilsActionSystemSet::SyncActionStates.run_if(session_running), + ); app.add_systems( Startup, create_openxr_events.in_set(XRUtilsActionSystemSet::CreateEvents), ); +<<<<<<< HEAD app.add_systems( Update, sync_active_action_sets.run_if(resource_exists::), ); +======= +>>>>>>> 47b64a4 (add new XrSpace and impl that) app.add_systems( - Update, + PreUpdate, + sync_active_action_sets.before(OxrActionSetSyncSet), + ); + app.add_systems( + PreUpdate, sync_and_update_action_states_f32 .run_if(resource_exists::) .in_set(XRUtilsActionSystemSet::SyncActionStates) - .after(sync_active_action_sets), + .after(OxrActionSetSyncSet), ); app.add_systems( - Update, + PreUpdate, sync_and_update_action_states_bool .run_if(resource_exists::) .in_set(XRUtilsActionSystemSet::SyncActionStates) - .after(sync_active_action_sets), + .after(OxrActionSetSyncSet), ); app.add_systems( - Update, + PreUpdate, sync_and_update_action_states_vector .run_if(resource_exists::) .in_set(XRUtilsActionSystemSet::SyncActionStates) - .after(sync_active_action_sets), + .after(OxrActionSetSyncSet), ); } } @@ -147,9 +164,9 @@ fn create_openxr_events( //interaction profile //get the binding entity and stuff let create_binding = bindings_query.get(bind).unwrap(); - let profile = Cow::from(create_binding.profile.clone()); + let profile = create_binding.profile.clone(); //bindings - let binding = vec![Cow::from(create_binding.binding.clone())]; + let binding = vec![create_binding.binding.clone()]; let sugestion = OxrSuggestActionBinding { action: action.as_raw(), interaction_profile: profile, @@ -186,9 +203,9 @@ fn create_openxr_events( //interaction profile //get the binding entity and stuff let create_binding = bindings_query.get(bind).unwrap(); - let profile = Cow::from(create_binding.profile.clone()); + let profile = create_binding.profile.clone(); //bindings - let binding = vec![Cow::from(create_binding.binding.clone())]; + let binding = vec![create_binding.binding.clone()]; let sugestion = OxrSuggestActionBinding { action: action.as_raw(), interaction_profile: profile, @@ -225,9 +242,9 @@ fn create_openxr_events( //interaction profile //get the binding entity and stuff let create_binding = bindings_query.get(bind).unwrap(); - let profile = Cow::from(create_binding.profile.clone()); + let profile = create_binding.profile.clone(); //bindings - let binding = vec![Cow::from(create_binding.binding.clone())]; + let binding = vec![create_binding.binding.clone()]; let sugestion = OxrSuggestActionBinding { action: action.as_raw(), interaction_profile: profile, @@ -245,9 +262,10 @@ fn create_openxr_events( } fn sync_active_action_sets( - session: Res, + mut sync_set: EventWriter, active_action_set_query: Query<&XRUtilsActionSetReference, With>, ) { +<<<<<<< HEAD let active_sets = active_action_set_query .iter() .map(|v| ActiveActionSet::from(&v.0)) @@ -256,6 +274,10 @@ fn sync_active_action_sets( match sync { Ok(_) => (), Err(_) => error!("sync error"), +======= + for set in &active_action_set_query { + sync_set.send(OxrSyncActionSet(set.0.clone())); +>>>>>>> 47b64a4 (add new XrSpace and impl that) } } @@ -336,7 +358,9 @@ fn sync_and_update_action_states_vector( #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] pub enum XRUtilsActionSystemSet { + /// Runs in Startup CreateEvents, + /// Runs in PreUpdate SyncActionStates, }