add support for hand tracking and configurable reference spaces
This commit is contained in:
@@ -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,31 @@
|
|||||||
//! 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, features::handtracking::HandTrackingPlugin, init::OxrInitPlugin,
|
||||||
|
reference_space::OxrReferenceSpacePlugin, 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(HandTrackingPlugin::default())
|
||||||
|
.add_plugins(OxrReferenceSpacePlugin::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();
|
||||||
|
|||||||
176
crates/bevy_openxr/src/openxr/features/handtracking.rs
Normal file
176
crates/bevy_openxr/src/openxr/features/handtracking.rs
Normal file
@@ -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<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)>,
|
||||||
|
) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
#[cfg(feature = "passthrough")]
|
#[cfg(feature = "passthrough")]
|
||||||
pub mod passthrough;
|
pub mod passthrough;
|
||||||
|
pub mod handtracking;
|
||||||
|
|||||||
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 {
|
||||||
@@ -90,7 +93,7 @@ impl Plugin for OxrInitPlugin {
|
|||||||
))
|
))
|
||||||
.add_systems(First, reset_per_frame_resources)
|
.add_systems(First, reset_per_frame_resources)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PreUpdate,
|
First,
|
||||||
(
|
(
|
||||||
poll_events
|
poll_events
|
||||||
.run_if(session_available)
|
.run_if(session_available)
|
||||||
@@ -167,7 +170,9 @@ 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 +298,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,11 +399,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,
|
||||||
@@ -414,7 +413,6 @@ fn init_xr_session(
|
|||||||
swapchain,
|
swapchain,
|
||||||
images,
|
images,
|
||||||
graphics_info,
|
graphics_info,
|
||||||
stage,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +424,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 +439,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 +478,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 +488,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 +540,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>();
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ pub mod resources;
|
|||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod action_binding;
|
pub mod action_binding;
|
||||||
pub mod action_set_attaching;
|
pub mod action_set_attaching;
|
||||||
|
pub mod reference_space;
|
||||||
|
pub mod helper_traits;
|
||||||
|
|
||||||
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
||||||
plugins
|
plugins
|
||||||
@@ -50,17 +52,14 @@ pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
|||||||
.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 {
|
||||||
|
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<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()),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ use bevy::{
|
|||||||
use bevy_xr::camera::{XrCamera, XrCameraBundle, XrProjection};
|
use bevy_xr::camera::{XrCamera, XrCameraBundle, XrProjection};
|
||||||
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,
|
||||||
@@ -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