Merge pull request #100 from Schmarni-Dev/handtracking
add support for hand tracking and configurable reference spaces
This commit is contained in:
@@ -8,6 +8,9 @@ default = ["vulkan", "passthrough"]
|
|||||||
vulkan = ["dep:ash"]
|
vulkan = ["dep:ash"]
|
||||||
passthrough = []
|
passthrough = []
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
bevy_xr_utils.path = "../bevy_xr_utils"
|
||||||
|
|
||||||
# bevy can't be placed behind target or proc macros won't work properly
|
# bevy can't be placed behind target or proc macros won't work properly
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy.workspace = true
|
bevy.workspace = true
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use bevy_openxr::add_xr_plugins;
|
|||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(add_xr_plugins(DefaultPlugins))
|
.add_plugins(add_xr_plugins(DefaultPlugins))
|
||||||
|
.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
@@ -38,5 +39,8 @@ fn setup(
|
|||||||
},
|
},
|
||||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||||
..default()
|
..default()
|
||||||
|
}); commands.spawn(Camera3dBundle {
|
||||||
|
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_openxr.path = "../.."
|
bevy_openxr.path = "../.."
|
||||||
bevy.workspace = true
|
bevy.workspace = true
|
||||||
|
bevy_xr_utils.path = "../../../bevy_xr_utils"
|
||||||
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_openxr::add_xr_plugins;
|
use bevy_openxr::{add_xr_plugins, init::OxrInitPlugin, types::OxrExtensions};
|
||||||
|
|
||||||
#[bevy_main]
|
#[bevy_main]
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
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(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.insert_resource(ClearColor(Color::NONE))
|
.insert_resource(ClearColor(Color::NONE))
|
||||||
.run();
|
.run();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ impl Plugin for OxrActionAttachingPlugin {
|
|||||||
|
|
||||||
fn attach_sets(session: Res<OxrSession>, mut events: EventReader<OxrAttachActionSet>) {
|
fn attach_sets(session: Res<OxrSession>, mut events: EventReader<OxrAttachActionSet>) {
|
||||||
let sets = events.read().map(|v| &v.0).collect::<Vec<_>>();
|
let sets = events.read().map(|v| &v.0).collect::<Vec<_>>();
|
||||||
|
if sets.is_empty() {return;}
|
||||||
info!("attaching {} sessions", sets.len());
|
info!("attaching {} sessions", sets.len());
|
||||||
match session.attach_action_sets(&sets) {
|
match session.attach_action_sets(&sets) {
|
||||||
Ok(_) => {info!("attached sessions!")}
|
Ok(_) => {info!("attached sessions!")}
|
||||||
|
|||||||
175
crates/bevy_openxr/src/openxr/features/handtracking.rs
Normal file
175
crates/bevy_openxr/src/openxr/features/handtracking.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
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<OxrSession>,
|
||||||
|
root: Query<Entity, With<OxrTrackingRoot>>,
|
||||||
|
) {
|
||||||
|
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<Entity, Or<(With<DefaultHandTracker>, With<DefaultHandBones>)>>,
|
||||||
|
) {
|
||||||
|
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<OxrPrimaryReferenceSpace>,
|
||||||
|
time: Res<OxrTime>,
|
||||||
|
tracker_query: Query<(
|
||||||
|
&OxrHandTracker,
|
||||||
|
Option<&OxrReferenceSpace>,
|
||||||
|
&OxrHandBoneEntities,
|
||||||
|
)>,
|
||||||
|
mut bone_query: Query<(&HandBone, &mut HandBoneRadius, &mut Transform)>,
|
||||||
|
) {
|
||||||
|
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(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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
#[cfg(feature = "passthrough")]
|
#[cfg(feature = "passthrough")]
|
||||||
pub mod passthrough;
|
pub mod passthrough;
|
||||||
|
pub mod handtracking;
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ pub fn create_passthrough(
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn supports_passthrough(instance: &OxrInstance, system: OxrSystemId) -> Result<bool> {
|
pub fn supports_passthrough(instance: &OxrInstance, system: OxrSystemId) -> Result<bool> {
|
||||||
|
if instance.exts().fb_passthrough.is_none() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut hand = openxr::sys::SystemPassthroughProperties2FB {
|
let mut hand = openxr::sys::SystemPassthroughProperties2FB {
|
||||||
ty: SystemPassthroughProperties2FB::TYPE,
|
ty: SystemPassthroughProperties2FB::TYPE,
|
||||||
|
|||||||
90
crates/bevy_openxr/src/openxr/helper_traits.rs
Normal file
90
crates/bevy_openxr/src/openxr/helper_traits.rs
Normal file
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ use bevy_xr::session::XrStatusChanged;
|
|||||||
|
|
||||||
use crate::error::OxrError;
|
use crate::error::OxrError;
|
||||||
use crate::graphics::*;
|
use crate::graphics::*;
|
||||||
|
use crate::reference_space::OxrPrimaryReferenceSpace;
|
||||||
use crate::resources::*;
|
use crate::resources::*;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ pub fn session_started(started: Option<Res<OxrSessionStarted>>) -> bool {
|
|||||||
pub enum OxrPreUpdateSet {
|
pub enum OxrPreUpdateSet {
|
||||||
PollEvents,
|
PollEvents,
|
||||||
HandleEvents,
|
HandleEvents,
|
||||||
|
UpdateCriticalComponents,
|
||||||
|
UpdateNonCriticalComponents,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OxrInitPlugin {
|
pub struct OxrInitPlugin {
|
||||||
@@ -58,6 +61,24 @@ pub struct OxrInitPlugin {
|
|||||||
/// Passed into the render plugin when added to the app.
|
/// Passed into the render plugin when added to the app.
|
||||||
pub synchronous_pipeline_compilation: bool,
|
pub synchronous_pipeline_compilation: bool,
|
||||||
}
|
}
|
||||||
|
impl Default for OxrInitPlugin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
app_info: default(),
|
||||||
|
exts: {
|
||||||
|
let mut exts = OxrExtensions::default();
|
||||||
|
exts.enable_fb_passthrough();
|
||||||
|
exts.enable_hand_tracking();
|
||||||
|
exts
|
||||||
|
},
|
||||||
|
blend_modes: default(),
|
||||||
|
backends: default(),
|
||||||
|
formats: Some(vec![wgpu::TextureFormat::Rgba8UnormSrgb]),
|
||||||
|
resolutions: default(),
|
||||||
|
synchronous_pipeline_compilation: default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct OxrTrackingRoot;
|
pub struct OxrTrackingRoot;
|
||||||
@@ -167,7 +188,10 @@ impl Plugin for OxrInitPlugin {
|
|||||||
(
|
(
|
||||||
OxrPreUpdateSet::PollEvents.before(handle_session),
|
OxrPreUpdateSet::PollEvents.before(handle_session),
|
||||||
OxrPreUpdateSet::HandleEvents.after(handle_session),
|
OxrPreUpdateSet::HandleEvents.after(handle_session),
|
||||||
),
|
OxrPreUpdateSet::UpdateCriticalComponents,
|
||||||
|
OxrPreUpdateSet::UpdateNonCriticalComponents,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let session_started = OxrSessionStarted::default();
|
let session_started = OxrSessionStarted::default();
|
||||||
@@ -293,7 +317,6 @@ fn init_xr_session(
|
|||||||
OxrSwapchain,
|
OxrSwapchain,
|
||||||
OxrSwapchainImages,
|
OxrSwapchainImages,
|
||||||
OxrGraphicsInfo,
|
OxrGraphicsInfo,
|
||||||
OxrStage,
|
|
||||||
)> {
|
)> {
|
||||||
let (session, frame_waiter, frame_stream) =
|
let (session, frame_waiter, frame_stream) =
|
||||||
unsafe { instance.create_session(system_id, graphics_info)? };
|
unsafe { instance.create_session(system_id, graphics_info)? };
|
||||||
@@ -395,12 +418,6 @@ fn init_xr_session(
|
|||||||
}
|
}
|
||||||
.ok_or(OxrError::NoAvailableBackend)?;
|
.ok_or(OxrError::NoAvailableBackend)?;
|
||||||
|
|
||||||
let stage = OxrStage(
|
|
||||||
session
|
|
||||||
.create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)?
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let graphics_info = OxrGraphicsInfo {
|
let graphics_info = OxrGraphicsInfo {
|
||||||
blend_mode,
|
blend_mode,
|
||||||
resolution,
|
resolution,
|
||||||
@@ -414,7 +431,6 @@ fn init_xr_session(
|
|||||||
swapchain,
|
swapchain,
|
||||||
images,
|
images,
|
||||||
graphics_info,
|
graphics_info,
|
||||||
stage,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +442,6 @@ struct OxrRenderResources {
|
|||||||
swapchain: OxrSwapchain,
|
swapchain: OxrSwapchain,
|
||||||
images: OxrSwapchainImages,
|
images: OxrSwapchainImages,
|
||||||
graphics_info: OxrGraphicsInfo,
|
graphics_info: OxrGraphicsInfo,
|
||||||
stage: OxrStage,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_xr_session(
|
pub fn create_xr_session(
|
||||||
@@ -442,19 +457,17 @@ pub fn create_xr_session(
|
|||||||
**system_id,
|
**system_id,
|
||||||
create_info.clone(),
|
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(session.clone());
|
||||||
commands.insert_resource(frame_waiter);
|
commands.insert_resource(frame_waiter);
|
||||||
commands.insert_resource(images.clone());
|
commands.insert_resource(images.clone());
|
||||||
commands.insert_resource(graphics_info.clone());
|
commands.insert_resource(graphics_info.clone());
|
||||||
commands.insert_resource(stage.clone());
|
|
||||||
commands.insert_resource(OxrRenderResources {
|
commands.insert_resource(OxrRenderResources {
|
||||||
session,
|
session,
|
||||||
frame_stream,
|
frame_stream,
|
||||||
swapchain,
|
swapchain,
|
||||||
images,
|
images,
|
||||||
graphics_info,
|
graphics_info,
|
||||||
stage,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to initialize XrSession: {e}"),
|
Err(e) => error!("Failed to initialize XrSession: {e}"),
|
||||||
@@ -483,7 +496,6 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld
|
|||||||
swapchain,
|
swapchain,
|
||||||
images,
|
images,
|
||||||
graphics_info,
|
graphics_info,
|
||||||
stage,
|
|
||||||
}) = world.remove_resource()
|
}) = world.remove_resource()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
@@ -494,7 +506,6 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld
|
|||||||
commands.insert_resource(swapchain);
|
commands.insert_resource(swapchain);
|
||||||
commands.insert_resource(images);
|
commands.insert_resource(images);
|
||||||
commands.insert_resource(graphics_info);
|
commands.insert_resource(graphics_info);
|
||||||
commands.insert_resource(stage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Polls any OpenXR events and handles them accordingly
|
/// Polls any OpenXR events and handles them accordingly
|
||||||
@@ -547,14 +558,14 @@ pub fn destroy_xr_session(mut commands: Commands) {
|
|||||||
commands.remove_resource::<OxrFrameWaiter>();
|
commands.remove_resource::<OxrFrameWaiter>();
|
||||||
commands.remove_resource::<OxrSwapchainImages>();
|
commands.remove_resource::<OxrSwapchainImages>();
|
||||||
commands.remove_resource::<OxrGraphicsInfo>();
|
commands.remove_resource::<OxrGraphicsInfo>();
|
||||||
commands.remove_resource::<OxrStage>();
|
commands.remove_resource::<OxrPrimaryReferenceSpace>();
|
||||||
commands.insert_resource(OxrCleanupSession(true));
|
commands.insert_resource(OxrCleanupSession(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy_xr_session_render(world: &mut World) {
|
pub fn destroy_xr_session_render(world: &mut World) {
|
||||||
world.remove_resource::<OxrSwapchain>();
|
world.remove_resource::<OxrSwapchain>();
|
||||||
world.remove_resource::<OxrFrameStream>();
|
world.remove_resource::<OxrFrameStream>();
|
||||||
world.remove_resource::<OxrStage>();
|
world.remove_resource::<OxrPrimaryReferenceSpace>();
|
||||||
world.remove_resource::<OxrSwapchainImages>();
|
world.remove_resource::<OxrSwapchainImages>();
|
||||||
world.remove_resource::<OxrGraphicsInfo>();
|
world.remove_resource::<OxrGraphicsInfo>();
|
||||||
world.remove_resource::<OxrSession>();
|
world.remove_resource::<OxrSession>();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use bevy::ecs::world::World;
|
|||||||
use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di, Space};
|
use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di, Space};
|
||||||
|
|
||||||
use crate::graphics::graphics_match;
|
use crate::graphics::graphics_match;
|
||||||
|
use crate::reference_space::OxrPrimaryReferenceSpace;
|
||||||
use crate::resources::*;
|
use crate::resources::*;
|
||||||
|
|
||||||
pub trait LayerProvider {
|
pub trait LayerProvider {
|
||||||
@@ -16,7 +17,7 @@ pub struct PassthroughLayer;
|
|||||||
|
|
||||||
impl LayerProvider for ProjectionLayer {
|
impl LayerProvider for ProjectionLayer {
|
||||||
fn get<'a>(&self, world: &'a World) -> Box<dyn CompositionLayer<'a> + 'a> {
|
fn get<'a>(&self, world: &'a World) -> Box<dyn CompositionLayer<'a> + 'a> {
|
||||||
let stage = world.resource::<OxrStage>();
|
let stage = world.resource::<OxrPrimaryReferenceSpace>();
|
||||||
let openxr_views = world.resource::<OxrViews>();
|
let openxr_views = world.resource::<OxrViews>();
|
||||||
let swapchain = world.resource::<OxrSwapchain>();
|
let swapchain = world.resource::<OxrSwapchain>();
|
||||||
let graphics_info = world.resource::<OxrGraphicsInfo>();
|
let graphics_info = world.resource::<OxrGraphicsInfo>();
|
||||||
|
|||||||
@@ -10,19 +10,25 @@ use bevy_xr::session::XrSessionPlugin;
|
|||||||
use init::OxrInitPlugin;
|
use init::OxrInitPlugin;
|
||||||
use render::OxrRenderPlugin;
|
use render::OxrRenderPlugin;
|
||||||
|
|
||||||
use self::{exts::OxrExtensions, features::passthrough::OxrPassthroughPlugin};
|
use self::{
|
||||||
|
exts::OxrExtensions,
|
||||||
|
features::{handtracking::HandTrackingPlugin, passthrough::OxrPassthroughPlugin},
|
||||||
|
reference_space::OxrReferenceSpacePlugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod action_binding;
|
||||||
|
pub mod action_set_attaching;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod exts;
|
mod exts;
|
||||||
pub mod features;
|
pub mod features;
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
|
pub mod helper_traits;
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod layer_builder;
|
pub mod layer_builder;
|
||||||
|
pub mod reference_space;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod resources;
|
pub mod resources;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod action_binding;
|
|
||||||
pub mod action_set_attaching;
|
|
||||||
|
|
||||||
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
||||||
plugins
|
plugins
|
||||||
@@ -30,37 +36,24 @@ pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
|||||||
.disable::<RenderPlugin>()
|
.disable::<RenderPlugin>()
|
||||||
.disable::<PipelinedRenderingPlugin>()
|
.disable::<PipelinedRenderingPlugin>()
|
||||||
.add_before::<RenderPlugin, _>(XrSessionPlugin)
|
.add_before::<RenderPlugin, _>(XrSessionPlugin)
|
||||||
.add_before::<RenderPlugin, _>(OxrInitPlugin {
|
.add_before::<RenderPlugin, _>(OxrInitPlugin::default())
|
||||||
app_info: default(),
|
.add(OxrReferenceSpacePlugin::default())
|
||||||
exts: {
|
|
||||||
let mut exts = OxrExtensions::default();
|
|
||||||
exts.enable_fb_passthrough();
|
|
||||||
exts
|
|
||||||
},
|
|
||||||
blend_modes: default(),
|
|
||||||
backends: default(),
|
|
||||||
formats: Some(vec![wgpu::TextureFormat::Rgba8UnormSrgb]),
|
|
||||||
resolutions: default(),
|
|
||||||
synchronous_pipeline_compilation: default(),
|
|
||||||
})
|
|
||||||
.add(OxrRenderPlugin)
|
.add(OxrRenderPlugin)
|
||||||
.add(OxrPassthroughPlugin)
|
.add(OxrPassthroughPlugin)
|
||||||
|
.add(HandTrackingPlugin::default())
|
||||||
.add(XrCameraPlugin)
|
.add(XrCameraPlugin)
|
||||||
.add(action_set_attaching::OxrActionAttachingPlugin)
|
.add(action_set_attaching::OxrActionAttachingPlugin)
|
||||||
.add(action_binding::OxrActionBindingPlugin)
|
.add(action_binding::OxrActionBindingPlugin)
|
||||||
// .add(XrActionPlugin)
|
// .add(XrActionPlugin)
|
||||||
.set(WindowPlugin {
|
.set(WindowPlugin {
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
transparent: true,
|
transparent: true,
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
// title: self.app_info.name.clone(),
|
// title: self.app_info.name.clone(),
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
#[cfg(target_os = "android")]
|
// #[cfg(target_os = "android")]
|
||||||
primary_window: None, // ?
|
// exit_condition: bevy::window::ExitCondition::DontExit,
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
exit_condition: bevy::window::ExitCondition::DontExit,
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
close_when_requested: true,
|
close_when_requested: true,
|
||||||
..default()
|
..default()
|
||||||
|
|||||||
60
crates/bevy_openxr/src/openxr/reference_space.rs
Normal file
60
crates/bevy_openxr/src/openxr/reference_space.rs
Normal file
@@ -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 {
|
||||||
|
pub default_primary_ref_space: openxr::ReferenceSpaceType,
|
||||||
|
}
|
||||||
|
impl Default for OxrReferenceSpacePlugin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
default_primary_ref_space: openxr::ReferenceSpaceType::STAGE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<openxr::Space>);
|
||||||
|
|
||||||
|
/// 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::<OxrPrimaryReferenceSpace>::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<OxrSession>,
|
||||||
|
space_type: Res<OxrPrimaryReferenceSpaceType>,
|
||||||
|
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()),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,10 +9,10 @@ use bevy::{
|
|||||||
},
|
},
|
||||||
transform::TransformSystem,
|
transform::TransformSystem,
|
||||||
};
|
};
|
||||||
use bevy_xr::camera::{XrCamera, XrCameraBundle, XrProjection};
|
use bevy_xr::{camera::{XrCamera, XrCameraBundle, XrProjection}, session::session_running};
|
||||||
use openxr::ViewStateFlags;
|
use openxr::ViewStateFlags;
|
||||||
|
|
||||||
use crate::resources::*;
|
use crate::{reference_space::OxrPrimaryReferenceSpace, resources::*};
|
||||||
use crate::{
|
use crate::{
|
||||||
init::{session_started, OxrPreUpdateSet},
|
init::{session_started, OxrPreUpdateSet},
|
||||||
layer_builder::ProjectionLayer,
|
layer_builder::ProjectionLayer,
|
||||||
@@ -28,17 +28,17 @@ impl Plugin for OxrRenderPlugin {
|
|||||||
(
|
(
|
||||||
init_views.run_if(resource_added::<OxrGraphicsInfo>),
|
init_views.run_if(resource_added::<OxrGraphicsInfo>),
|
||||||
wait_frame.run_if(session_started),
|
wait_frame.run_if(session_started),
|
||||||
locate_views.run_if(session_started),
|
locate_views.run_if(session_running),
|
||||||
update_views.run_if(session_started),
|
update_views.run_if(session_running),
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.after(OxrPreUpdateSet::HandleEvents),
|
.after(OxrPreUpdateSet::UpdateNonCriticalComponents),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(locate_views, update_views)
|
(locate_views, update_views)
|
||||||
.chain()
|
.chain()
|
||||||
.run_if(session_started)
|
.run_if(session_running)
|
||||||
.before(TransformSystem::TransformPropagate),
|
.before(TransformSystem::TransformPropagate),
|
||||||
);
|
);
|
||||||
app.sub_app_mut(RenderApp)
|
app.sub_app_mut(RenderApp)
|
||||||
@@ -47,7 +47,7 @@ impl Plugin for OxrRenderPlugin {
|
|||||||
(
|
(
|
||||||
(
|
(
|
||||||
insert_texture_views,
|
insert_texture_views,
|
||||||
locate_views,
|
locate_views.run_if(resource_exists::<OxrPrimaryReferenceSpace>),
|
||||||
update_views_render_world,
|
update_views_render_world,
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
@@ -112,7 +112,7 @@ pub fn wait_frame(mut frame_waiter: ResMut<OxrFrameWaiter>, mut commands: Comman
|
|||||||
|
|
||||||
pub fn locate_views(
|
pub fn locate_views(
|
||||||
session: Res<OxrSession>,
|
session: Res<OxrSession>,
|
||||||
stage: Res<OxrStage>,
|
ref_space: Res<OxrPrimaryReferenceSpace>,
|
||||||
time: Res<OxrTime>,
|
time: Res<OxrTime>,
|
||||||
mut openxr_views: ResMut<OxrViews>,
|
mut openxr_views: ResMut<OxrViews>,
|
||||||
) {
|
) {
|
||||||
@@ -121,7 +121,7 @@ pub fn locate_views(
|
|||||||
.locate_views(
|
.locate_views(
|
||||||
openxr::ViewConfigurationType::PRIMARY_STEREO,
|
openxr::ViewConfigurationType::PRIMARY_STEREO,
|
||||||
**time,
|
**time,
|
||||||
&stage,
|
&ref_space,
|
||||||
)
|
)
|
||||||
.expect("Failed to locate views");
|
.expect("Failed to locate views");
|
||||||
if openxr_views.len() != xr_views.len() {
|
if openxr_views.len() != xr_views.len() {
|
||||||
|
|||||||
@@ -377,8 +377,8 @@ impl OxrSwapchain {
|
|||||||
pub struct OxrSwapchainImages(pub Arc<Vec<wgpu::Texture>>);
|
pub struct OxrSwapchainImages(pub Arc<Vec<wgpu::Texture>>);
|
||||||
|
|
||||||
/// Thread safe wrapper around [openxr::Space] representing the stage.
|
/// Thread safe wrapper around [openxr::Space] representing the stage.
|
||||||
#[derive(Deref, Clone, Resource)]
|
// #[derive(Deref, Clone, Resource)]
|
||||||
pub struct OxrStage(pub Arc<openxr::Space>);
|
// pub struct OxrStage(pub Arc<openxr::Space>);
|
||||||
|
|
||||||
/// Stores the latest generated [OxrViews]
|
/// Stores the latest generated [OxrViews]
|
||||||
#[derive(Clone, Resource, ExtractResource, Deref, DerefMut)]
|
#[derive(Clone, Resource, ExtractResource, Deref, DerefMut)]
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy.workspace = true
|
bevy.workspace = true
|
||||||
bevy_xr_macros.path = "macros"
|
bevy_xr_macros.path = "macros"
|
||||||
|
|||||||
129
crates/bevy_xr/src/hands.rs
Normal file
129
crates/bevy_xr/src/hands.rs
Normal file
@@ -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,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod actions;
|
pub mod actions;
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod hands;
|
||||||
|
|||||||
10
crates/bevy_xr_utils/Cargo.toml
Normal file
10
crates/bevy_xr_utils/Cargo.toml
Normal file
@@ -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"
|
||||||
34
crates/bevy_xr_utils/src/hand_gizmos.rs
Normal file
34
crates/bevy_xr_utils/src/hand_gizmos.rs
Normal file
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
3
crates/bevy_xr_utils/src/lib.rs
Normal file
3
crates/bevy_xr_utils/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// use bevy::prelude::*;
|
||||||
|
pub mod hand_gizmos;
|
||||||
|
|
||||||
Reference in New Issue
Block a user