diff --git a/crates/bevy_openxr/examples/android/Cargo.toml b/crates/bevy_openxr/examples/android/Cargo.toml index fdd2821..d66df4f 100644 --- a/crates/bevy_openxr/examples/android/Cargo.toml +++ b/crates/bevy_openxr/examples/android/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] bevy_openxr.path = "../.." bevy.workspace = true +bevy_xr_utils.path = "../../../bevy_xr_utils" [lib] diff --git a/crates/bevy_openxr/examples/android/src/lib.rs b/crates/bevy_openxr/examples/android/src/lib.rs index 6f60261..6ac9124 100644 --- a/crates/bevy_openxr/examples/android/src/lib.rs +++ b/crates/bevy_openxr/examples/android/src/lib.rs @@ -1,12 +1,31 @@ //! A simple 3D scene with light shining over a cube sitting on a plane. use bevy::prelude::*; -use bevy_openxr::add_xr_plugins; +use bevy_openxr::{ + add_xr_plugins, features::handtracking::HandTrackingPlugin, init::OxrInitPlugin, + reference_space::OxrReferenceSpacePlugin, types::OxrExtensions, +}; #[bevy_main] fn main() { App::new() - .add_plugins(add_xr_plugins(DefaultPlugins)) + .add_plugins(add_xr_plugins(DefaultPlugins).set(OxrInitPlugin { + app_info: default(), + exts: { + let mut exts = OxrExtensions::default(); + exts.enable_fb_passthrough(); + exts.enable_hand_tracking(); + exts + }, + blend_modes: default(), + backends: default(), + formats: default(), + resolutions: default(), + synchronous_pipeline_compilation: default(), + })) + .add_plugins(HandTrackingPlugin::default()) + .add_plugins(OxrReferenceSpacePlugin::default()) + .add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin) .add_systems(Startup, setup) .insert_resource(ClearColor(Color::NONE)) .run(); diff --git a/crates/bevy_openxr/src/openxr/features/handtracking.rs b/crates/bevy_openxr/src/openxr/features/handtracking.rs new file mode 100644 index 0000000..069e0c0 --- /dev/null +++ b/crates/bevy_openxr/src/openxr/features/handtracking.rs @@ -0,0 +1,176 @@ +use bevy::prelude::*; +use bevy_xr::{ + hands::{HandBone, HandBoneRadius}, + session::{session_running, status_changed_to, XrStatus}, +}; +use openxr::SpaceLocationFlags; + +use crate::{ + init::OxrTrackingRoot, reference_space::{OxrPrimaryReferenceSpace, OxrReferenceSpace}, resources::{OxrSession, OxrTime} +}; + +pub struct HandTrackingPlugin { + default_hands: bool, +} +impl Default for HandTrackingPlugin { + fn default() -> Self { + Self { + default_hands: true, + } + } +} + +impl Plugin for HandTrackingPlugin { + fn build(&self, app: &mut App) { + app.add_systems(PreUpdate, locate_hands.run_if(session_running)); + if self.default_hands { + app.add_systems( + PreUpdate, + clean_up_default_hands.run_if(status_changed_to(XrStatus::Exiting)), + ); + app.add_systems( + PostUpdate, + spawn_default_hands.run_if(status_changed_to(XrStatus::Ready)), + ); + } + } +} + +fn spawn_default_hands( + mut cmds: Commands, + session: Res, + root: Query>, +) { + info!("spawning hands"); + let Ok(root) = root.get_single() else { + error!("unable to get tracking root, skipping hand creation"); + return; + }; + let tracker_left = match session.create_hand_tracker(openxr::HandEXT::LEFT) { + Ok(t) => t, + Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { + warn!("Handtracking Extension not loaded, Unable to create Handtracker!"); + return; + } + Err(err) => { + warn!("Error while creating Handtracker: {}", err.to_string()); + return; + } + }; + let tracker_right = match session.create_hand_tracker(openxr::HandEXT::RIGHT) { + Ok(t) => t, + Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { + warn!("Handtracking Extension not loaded, Unable to create Handtracker!"); + return; + } + Err(err) => { + warn!("Error while creating Handtracker: {}", err.to_string()); + return; + } + }; + let mut left_bones = [Entity::PLACEHOLDER; 26]; + let mut right_bones = [Entity::PLACEHOLDER; 26]; + for bone in HandBone::get_all_bones() { + let bone_left = cmds + .spawn((SpatialBundle::default(), bone, HandBoneRadius(0.0))) + .id(); + let bone_right = cmds + .spawn((SpatialBundle::default(), bone, HandBoneRadius(0.0))) + .id(); + cmds.entity(root).push_children(&[bone_right]); + left_bones[bone as usize] = bone_left; + right_bones[bone as usize] = bone_right; + } + cmds.spawn(( + DefaultHandTracker, + OxrHandTracker(tracker_left), + OxrHandBoneEntities(left_bones), + )); + cmds.spawn(( + DefaultHandTracker, + OxrHandTracker(tracker_right), + OxrHandBoneEntities(right_bones), + )); +} + +#[derive(Component)] +struct DefaultHandTracker; +#[derive(Component)] +struct DefaultHandBones; + +#[allow(clippy::type_complexity)] +fn clean_up_default_hands( + mut cmds: Commands, + query: Query, With)>>, +) { + for e in &query { + cmds.entity(e).despawn(); + } +} + +#[derive(Deref, DerefMut, Component, Clone, Copy)] +pub struct OxrHandBoneEntities(pub [Entity; 26]); + +#[derive(Deref, DerefMut, Component)] +pub struct OxrHandTracker(pub openxr::HandTracker); + +fn locate_hands( + default_ref_space: Res, + time: Res, + tracker_query: Query<( + &OxrHandTracker, + Option<&OxrReferenceSpace>, + &OxrHandBoneEntities, + )>, + mut bone_query: Query<(&HandBone, &mut HandBoneRadius, &mut Transform)>, +) { + info!("updating hands 0 "); + for (tracker, ref_space, hand_entities) in &tracker_query { + info!("updating hands 1 "); + 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(tracker, **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 bone_entities = match bone_query.get_many_mut(hand_entities.0) { + Ok(v) => v, + Err(err) => { + warn!("unable to get entities, {}", err); + continue; + } + }; + for (bone, mut bone_radius, mut transform) in bone_entities { + info!("updating hands"); + let joint = joints[*bone as usize]; + **bone_radius = joint.radius; + + if joint + .location_flags + .contains(SpaceLocationFlags::POSITION_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) + { + 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; + } + } + } +} diff --git a/crates/bevy_openxr/src/openxr/features/mod.rs b/crates/bevy_openxr/src/openxr/features/mod.rs index eb46003..f68b573 100644 --- a/crates/bevy_openxr/src/openxr/features/mod.rs +++ b/crates/bevy_openxr/src/openxr/features/mod.rs @@ -1,2 +1,3 @@ #[cfg(feature = "passthrough")] pub mod passthrough; +pub mod handtracking; diff --git a/crates/bevy_openxr/src/openxr/helper_traits.rs b/crates/bevy_openxr/src/openxr/helper_traits.rs new file mode 100644 index 0000000..ab8d865 --- /dev/null +++ b/crates/bevy_openxr/src/openxr/helper_traits.rs @@ -0,0 +1,90 @@ +use bevy::prelude::*; + +pub trait ToPosef { + fn to_posef(&self) -> openxr::Posef; +} +pub trait ToTransform { + fn to_transform(&self) -> Transform; +} +pub trait ToQuaternionf { + fn to_quaternionf(&self) -> openxr::Quaternionf; +} +pub trait ToQuat { + fn to_quat(&self) -> Quat; +} +pub trait ToVector3f { + fn to_vector3f(&self) -> openxr::Vector3f; +} +pub trait ToVec3 { + fn to_vec3(&self) -> Vec3; +} +pub trait ToVector2f { + fn to_vector2f(&self) -> openxr::Vector2f; +} +pub trait ToVec2 { + fn to_vec2(&self) -> Vec2; +} +impl ToPosef for Transform { + fn to_posef(&self) -> openxr::Posef { + openxr::Posef { + orientation: self.rotation.to_quaternionf(), + position: self.translation.to_vector3f(), + } + } +} +impl ToTransform for openxr::Posef { + fn to_transform(&self) -> Transform { + Transform::from_translation(self.position.to_vec3()) + .with_rotation(self.orientation.to_quat()) + } +} + +impl ToQuaternionf for Quat { + fn to_quaternionf(&self) -> openxr::Quaternionf { + openxr::Quaternionf { + x: self.x, + y: self.y, + z: self.z, + w: self.w, + } + } +} +impl ToQuat for openxr::Quaternionf { + fn to_quat(&self) -> Quat { + Quat::from_xyzw(self.x, self.y, self.z, self.w) + } +} +impl ToVector3f for Vec3 { + fn to_vector3f(&self) -> openxr::Vector3f { + openxr::Vector3f { + x: self.x, + y: self.y, + z: self.z, + } + } +} +impl ToVec3 for openxr::Vector3f { + fn to_vec3(&self) -> Vec3 { + Vec3 { + x: self.x, + y: self.y, + z: self.z, + } + } +} +impl ToVector2f for Vec2 { + fn to_vector2f(&self) -> openxr::Vector2f { + openxr::Vector2f { + x: self.x, + y: self.y, + } + } +} +impl ToVec2 for openxr::Vector2f { + fn to_vec2(&self) -> Vec2 { + Vec2 { + x: self.x, + y: self.y, + } + } +} diff --git a/crates/bevy_openxr/src/openxr/init.rs b/crates/bevy_openxr/src/openxr/init.rs index a85f5e9..ae72bcd 100644 --- a/crates/bevy_openxr/src/openxr/init.rs +++ b/crates/bevy_openxr/src/openxr/init.rs @@ -28,6 +28,7 @@ use bevy_xr::session::XrStatusChanged; use crate::error::OxrError; use crate::graphics::*; +use crate::reference_space::OxrPrimaryReferenceSpace; use crate::resources::*; use crate::types::*; @@ -39,6 +40,8 @@ pub fn session_started(started: Option>) -> bool { pub enum OxrPreUpdateSet { PollEvents, HandleEvents, + UpdateCriticalComponents, + UpdateNonCriticalComponents, } pub struct OxrInitPlugin { @@ -90,7 +93,7 @@ impl Plugin for OxrInitPlugin { )) .add_systems(First, reset_per_frame_resources) .add_systems( - PreUpdate, + First, ( poll_events .run_if(session_available) @@ -167,7 +170,9 @@ impl Plugin for OxrInitPlugin { ( OxrPreUpdateSet::PollEvents.before(handle_session), OxrPreUpdateSet::HandleEvents.after(handle_session), - ), + OxrPreUpdateSet::UpdateCriticalComponents, + OxrPreUpdateSet::UpdateNonCriticalComponents, + ).chain(), ); let session_started = OxrSessionStarted::default(); @@ -293,7 +298,6 @@ fn init_xr_session( OxrSwapchain, OxrSwapchainImages, OxrGraphicsInfo, - OxrStage, )> { let (session, frame_waiter, frame_stream) = unsafe { instance.create_session(system_id, graphics_info)? }; @@ -395,11 +399,6 @@ fn init_xr_session( } .ok_or(OxrError::NoAvailableBackend)?; - let stage = OxrStage( - session - .create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)? - .into(), - ); let graphics_info = OxrGraphicsInfo { blend_mode, @@ -414,7 +413,6 @@ fn init_xr_session( swapchain, images, graphics_info, - stage, )) } @@ -426,7 +424,6 @@ struct OxrRenderResources { swapchain: OxrSwapchain, images: OxrSwapchainImages, graphics_info: OxrGraphicsInfo, - stage: OxrStage, } pub fn create_xr_session( @@ -442,19 +439,17 @@ pub fn create_xr_session( **system_id, create_info.clone(), ) { - Ok((session, frame_waiter, frame_stream, swapchain, images, graphics_info, stage)) => { + Ok((session, frame_waiter, frame_stream, swapchain, images, graphics_info)) => { commands.insert_resource(session.clone()); commands.insert_resource(frame_waiter); commands.insert_resource(images.clone()); commands.insert_resource(graphics_info.clone()); - commands.insert_resource(stage.clone()); commands.insert_resource(OxrRenderResources { session, frame_stream, swapchain, images, graphics_info, - stage, }); } Err(e) => error!("Failed to initialize XrSession: {e}"), @@ -483,7 +478,6 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut(); commands.remove_resource::(); commands.remove_resource::(); - commands.remove_resource::(); + commands.remove_resource::(); commands.insert_resource(OxrCleanupSession(true)); } pub fn destroy_xr_session_render(world: &mut World) { world.remove_resource::(); world.remove_resource::(); - world.remove_resource::(); + world.remove_resource::(); world.remove_resource::(); world.remove_resource::(); world.remove_resource::(); diff --git a/crates/bevy_openxr/src/openxr/layer_builder.rs b/crates/bevy_openxr/src/openxr/layer_builder.rs index a9a89d0..d3601ae 100644 --- a/crates/bevy_openxr/src/openxr/layer_builder.rs +++ b/crates/bevy_openxr/src/openxr/layer_builder.rs @@ -4,6 +4,7 @@ use bevy::ecs::world::World; use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di, Space}; use crate::graphics::graphics_match; +use crate::reference_space::OxrPrimaryReferenceSpace; use crate::resources::*; pub trait LayerProvider { @@ -16,7 +17,7 @@ pub struct PassthroughLayer; impl LayerProvider for ProjectionLayer { fn get<'a>(&self, world: &'a World) -> Box + 'a> { - let stage = world.resource::(); + let stage = world.resource::(); let openxr_views = world.resource::(); let swapchain = world.resource::(); let graphics_info = world.resource::(); diff --git a/crates/bevy_openxr/src/openxr/mod.rs b/crates/bevy_openxr/src/openxr/mod.rs index 7ff8b60..923f5fb 100644 --- a/crates/bevy_openxr/src/openxr/mod.rs +++ b/crates/bevy_openxr/src/openxr/mod.rs @@ -23,6 +23,8 @@ pub mod resources; pub mod types; pub mod action_binding; pub mod action_set_attaching; +pub mod reference_space; +pub mod helper_traits; pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { plugins @@ -50,17 +52,14 @@ pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { .add(action_binding::OxrActionBindingPlugin) // .add(XrActionPlugin) .set(WindowPlugin { - #[cfg(not(target_os = "android"))] primary_window: Some(Window { transparent: true, present_mode: PresentMode::AutoNoVsync, // title: self.app_info.name.clone(), ..default() }), - #[cfg(target_os = "android")] - primary_window: None, // ? - #[cfg(target_os = "android")] - exit_condition: bevy::window::ExitCondition::DontExit, + // #[cfg(target_os = "android")] + // exit_condition: bevy::window::ExitCondition::DontExit, #[cfg(target_os = "android")] close_when_requested: true, ..default() diff --git a/crates/bevy_openxr/src/openxr/reference_space.rs b/crates/bevy_openxr/src/openxr/reference_space.rs new file mode 100644 index 0000000..03d1807 --- /dev/null +++ b/crates/bevy_openxr/src/openxr/reference_space.rs @@ -0,0 +1,60 @@ +use std::sync::Arc; + +use bevy::{ + prelude::*, + render::extract_resource::{ExtractResource, ExtractResourcePlugin}, +}; +use bevy_xr::session::{status_changed_to, XrStatus}; + +use crate::{init::OxrPreUpdateSet, resources::OxrSession}; + +pub struct OxrReferenceSpacePlugin { + default_primary_ref_space: openxr::ReferenceSpaceType, +} +impl Default for OxrReferenceSpacePlugin { + fn default() -> Self { + Self { + default_primary_ref_space: openxr::ReferenceSpaceType::LOCAL_FLOOR_EXT, + } + } +} + +#[derive(Resource)] +struct OxrPrimaryReferenceSpaceType(openxr::ReferenceSpaceType); +// TODO: this will keep the session alive so we need to remove this in the render world too +/// The Default Reference space used for locating things +#[derive(Resource, Deref, ExtractResource, Clone)] +pub struct OxrPrimaryReferenceSpace(pub Arc); + +/// The Reference space used for locating spaces on this entity +#[derive(Component)] +pub struct OxrReferenceSpace(pub openxr::Space); + +impl Plugin for OxrReferenceSpacePlugin { + fn build(&self, app: &mut App) { + app.insert_resource(OxrPrimaryReferenceSpaceType(self.default_primary_ref_space)); + app.add_plugins(ExtractResourcePlugin::::default()); + app.add_systems( + PreUpdate, + set_primary_ref_space + .run_if(status_changed_to(XrStatus::Ready)) + .in_set(OxrPreUpdateSet::UpdateCriticalComponents), + ); + } +} + +fn set_primary_ref_space( + session: Res, + space_type: Res, + mut cmds: Commands, +) { + match session.create_reference_space(space_type.0, openxr::Posef::IDENTITY) { + Ok(space) => { + cmds.insert_resource(OxrPrimaryReferenceSpace(Arc::new(space))); + } + Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { + error!("Required Extension for Reference Space not loaded"); + } + Err(err) => error!("Error while creating reference space: {}", err.to_string()), + }; +} diff --git a/crates/bevy_openxr/src/openxr/render.rs b/crates/bevy_openxr/src/openxr/render.rs index d9172c7..a5d52ae 100644 --- a/crates/bevy_openxr/src/openxr/render.rs +++ b/crates/bevy_openxr/src/openxr/render.rs @@ -12,7 +12,7 @@ use bevy::{ use bevy_xr::camera::{XrCamera, XrCameraBundle, XrProjection}; use openxr::ViewStateFlags; -use crate::resources::*; +use crate::{reference_space::OxrPrimaryReferenceSpace, resources::*}; use crate::{ init::{session_started, OxrPreUpdateSet}, layer_builder::ProjectionLayer, @@ -112,7 +112,7 @@ pub fn wait_frame(mut frame_waiter: ResMut, mut commands: Comman pub fn locate_views( session: Res, - stage: Res, + ref_space: Res, time: Res, mut openxr_views: ResMut, ) { @@ -121,7 +121,7 @@ pub fn locate_views( .locate_views( openxr::ViewConfigurationType::PRIMARY_STEREO, **time, - &stage, + &ref_space, ) .expect("Failed to locate views"); if openxr_views.len() != xr_views.len() { diff --git a/crates/bevy_openxr/src/openxr/resources.rs b/crates/bevy_openxr/src/openxr/resources.rs index b51d283..c6a59c5 100644 --- a/crates/bevy_openxr/src/openxr/resources.rs +++ b/crates/bevy_openxr/src/openxr/resources.rs @@ -377,8 +377,8 @@ impl OxrSwapchain { pub struct OxrSwapchainImages(pub Arc>); /// Thread safe wrapper around [openxr::Space] representing the stage. -#[derive(Deref, Clone, Resource)] -pub struct OxrStage(pub Arc); +// #[derive(Deref, Clone, Resource)] +// pub struct OxrStage(pub Arc); /// Stores the latest generated [OxrViews] #[derive(Clone, Resource, ExtractResource, Deref, DerefMut)] diff --git a/crates/bevy_xr/Cargo.toml b/crates/bevy_xr/Cargo.toml index d15550c..25ca19b 100644 --- a/crates/bevy_xr/Cargo.toml +++ b/crates/bevy_xr/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" [dependencies] bevy.workspace = true -bevy_xr_macros.path = "macros" \ No newline at end of file +bevy_xr_macros.path = "macros" diff --git a/crates/bevy_xr/src/hands.rs b/crates/bevy_xr/src/hands.rs new file mode 100644 index 0000000..b74f7cd --- /dev/null +++ b/crates/bevy_xr/src/hands.rs @@ -0,0 +1,129 @@ +use bevy::{ecs::component::Component, math::bool, prelude::{Deref, DerefMut}}; + +#[repr(transparent)] +#[derive(Clone, Copy, Component, Debug,DerefMut,Deref)] +pub struct HandBoneRadius(pub f32); + +#[repr(u8)] +#[derive(Clone, Copy, Component, Debug)] +pub enum HandBone { + Palm = 0, + Wrist = 1, + ThumbMetacarpal = 2, + ThumbProximal = 3, + ThumbDistal = 4, + ThumbTip = 5, + IndexMetacarpal = 6, + IndexProximal = 7, + IndexIntermediate = 8, + IndexDistal = 9, + IndexTip = 10, + MiddleMetacarpal = 11, + MiddleProximal = 12, + MiddleIntermediate = 13, + MiddleDistal = 14, + MiddleTip = 15, + RingMetacarpal = 16, + RingProximal = 17, + RingIntermediate = 18, + RingDistal = 19, + RingTip = 20, + LittleMetacarpal = 21, + LittleProximal = 22, + LittleIntermediate = 23, + LittleDistal = 24, + LittleTip = 25, +} + +impl HandBone { + pub const fn is_metacarpal(&self) -> bool { + matches!( + self, + HandBone::ThumbMetacarpal + | HandBone::IndexMetacarpal + | HandBone::MiddleMetacarpal + | HandBone::RingMetacarpal + | HandBone::LittleMetacarpal + ) + } + pub const fn is_thumb(&self) -> bool { + matches!( + self, + HandBone::ThumbMetacarpal + | HandBone::ThumbProximal + | HandBone::ThumbDistal + | HandBone::ThumbTip + ) + } + pub const fn is_index(&self) -> bool { + matches!( + self, + HandBone::IndexMetacarpal + | HandBone::IndexProximal + | HandBone::IndexIntermediate + | HandBone::IndexDistal + | HandBone::IndexTip + ) + } + pub const fn is_middle(&self) -> bool { + matches!( + self, + HandBone::MiddleMetacarpal + | HandBone::MiddleProximal + | HandBone::MiddleIntermediate + | HandBone::MiddleDistal + | HandBone::MiddleTip + ) + } + pub const fn is_ring(&self) -> bool { + matches!( + self, + HandBone::RingMetacarpal + | HandBone::RingProximal + | HandBone::RingIntermediate + | HandBone::RingDistal + | HandBone::RingTip + ) + } + pub const fn is_little(&self) -> bool { + matches!( + self, + HandBone::LittleMetacarpal + | HandBone::LittleProximal + | HandBone::LittleIntermediate + | HandBone::LittleDistal + | HandBone::LittleTip + ) + } + + pub const fn get_all_bones() -> [HandBone; 26] { + [ + HandBone::Palm, + HandBone::Wrist, + HandBone::ThumbMetacarpal, + HandBone::ThumbProximal, + HandBone::ThumbDistal, + HandBone::ThumbTip, + HandBone::IndexMetacarpal, + HandBone::IndexProximal, + HandBone::IndexIntermediate, + HandBone::IndexDistal, + HandBone::IndexTip, + HandBone::MiddleMetacarpal, + HandBone::MiddleProximal, + HandBone::MiddleIntermediate, + HandBone::MiddleDistal, + HandBone::MiddleTip, + HandBone::RingMetacarpal, + HandBone::RingProximal, + HandBone::RingIntermediate, + HandBone::RingDistal, + HandBone::RingTip, + HandBone::LittleMetacarpal, + HandBone::LittleProximal, + HandBone::LittleIntermediate, + HandBone::LittleDistal, + HandBone::LittleTip, + ] + } +} diff --git a/crates/bevy_xr/src/lib.rs b/crates/bevy_xr/src/lib.rs index 2ed5d51..b415641 100644 --- a/crates/bevy_xr/src/lib.rs +++ b/crates/bevy_xr/src/lib.rs @@ -1,4 +1,5 @@ pub mod actions; pub mod camera; pub mod session; -pub mod types; \ No newline at end of file +pub mod types; +pub mod hands; diff --git a/crates/bevy_xr_utils/Cargo.toml b/crates/bevy_xr_utils/Cargo.toml new file mode 100644 index 0000000..dfaffaf --- /dev/null +++ b/crates/bevy_xr_utils/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bevy_xr_utils" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bevy.workspace = true +bevy_xr.path = "../bevy_xr" diff --git a/crates/bevy_xr_utils/src/hand_gizmos.rs b/crates/bevy_xr_utils/src/hand_gizmos.rs new file mode 100644 index 0000000..f284480 --- /dev/null +++ b/crates/bevy_xr_utils/src/hand_gizmos.rs @@ -0,0 +1,34 @@ +use bevy::{prelude::*, transform::TransformSystem}; +use bevy_xr::hands::{HandBone, HandBoneRadius}; +pub struct HandGizmosPlugin; +impl Plugin for HandGizmosPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + PostUpdate, + draw_hand_gizmos.after(TransformSystem::TransformPropagate), + ); + } +} +fn draw_hand_gizmos( + mut gizmos: Gizmos, + query: Query<(&GlobalTransform, &HandBone, &HandBoneRadius)>, +) { + for (transform, bone, radius) in &query { + let pose = transform.compute_transform(); + gizmos.sphere(pose.translation, pose.rotation, **radius, gizmo_color(bone)); + } +} + +fn gizmo_color(bone: &HandBone) -> Color { + match bone { + HandBone::Palm => Color::WHITE, + HandBone::Wrist => Color::GRAY, + b if b.is_thumb() => Color::RED, + b if b.is_index() => Color::ORANGE, + b if b.is_middle() => Color::YELLOW, + b if b.is_ring() => Color::GREEN, + b if b.is_little() => Color::BLUE, + // should be impossible to hit + _ => Color::rgb(1.0, 0.0, 1.0), + } +} diff --git a/crates/bevy_xr_utils/src/lib.rs b/crates/bevy_xr_utils/src/lib.rs new file mode 100644 index 0000000..5c9d6f8 --- /dev/null +++ b/crates/bevy_xr_utils/src/lib.rs @@ -0,0 +1,3 @@ +// use bevy::prelude::*; +pub mod hand_gizmos; +