diff --git a/Cargo.lock b/Cargo.lock index 765814f..df997bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1394,6 +1394,7 @@ dependencies = [ "bevy_mod_openxr", "bevy_mod_xr", "openxr", + "openxr_mndx_xdev_space", ] [[package]] @@ -3904,6 +3905,15 @@ dependencies = [ "mint", ] +[[package]] +name = "openxr_mndx_xdev_space" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1589b0afd8fdf8d5d143457d1d3edf763c6f0e79254435351245a39975711f25" +dependencies = [ + "openxr", +] + [[package]] name = "orbclient" version = "0.3.48" diff --git a/crates/bevy_xr_utils/Cargo.toml b/crates/bevy_xr_utils/Cargo.toml index 00d6107..092020e 100644 --- a/crates/bevy_xr_utils/Cargo.toml +++ b/crates/bevy_xr_utils/Cargo.toml @@ -13,8 +13,12 @@ bevy = { workspace = true, features = ["bevy_gizmos"] } bevy_mod_xr.workspace = true bevy_mod_openxr.workspace = true +[dev-dependencies] +bevy = { workspace = true, default-features = true } + [target.'cfg(not(target_family = "wasm"))'.dependencies] openxr.workspace = true +openxr_mndx_xdev_space = "0.1.0" [lints.clippy] too_many_arguments = "allow" diff --git a/crates/bevy_xr_utils/examples/mndx_xdev_spaces.rs b/crates/bevy_xr_utils/examples/mndx_xdev_spaces.rs new file mode 100644 index 0000000..cd0ac1e --- /dev/null +++ b/crates/bevy_xr_utils/examples/mndx_xdev_spaces.rs @@ -0,0 +1,67 @@ +use bevy::prelude::*; +use bevy_mod_openxr::{add_xr_plugins, exts::OxrExtensions, init::OxrInitPlugin, resources::OxrSessionConfig}; +use bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin; +use bevy_xr_utils::{ + generic_tracker::GenericTrackerGizmoPlugin, mndx_xdev_spaces_trackers::MonadoXDevSpacesPlugin, +}; +use openxr::EnvironmentBlendMode; +fn main() -> AppExit { + App::new() + .add_plugins(add_xr_plugins(DefaultPlugins).set(OxrInitPlugin { + exts: { + let mut exts = OxrExtensions::default(); + exts.enable_hand_tracking(); + exts.other.push("XR_MNDX_xdev_space".to_string()); + exts + }, + ..Default::default() + })) + .insert_resource(OxrSessionConfig { + blend_mode_preference: vec![ + EnvironmentBlendMode::ALPHA_BLEND, + EnvironmentBlendMode::ADDITIVE, + EnvironmentBlendMode::OPAQUE, + ], + ..default() + }) + .insert_resource(ClearColor(Color::NONE)) + .add_plugins(( + HandGizmosPlugin, + GenericTrackerGizmoPlugin, + MonadoXDevSpacesPlugin, + )) + .add_systems(Startup, setup) + .run() +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // circular base + commands.spawn(( + Mesh3d(meshes.add(Circle::new(4.0))), + MeshMaterial3d(materials.add(Color::WHITE)), + Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)), + )); + // cube + commands.spawn(( + Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))), + MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))), + Transform::from_xyz(0.0, 0.5, 0.0), + )); + // light + commands.spawn(( + PointLight { + shadows_enabled: true, + ..default() + }, + Transform::from_xyz(4.0, 8.0, 4.0), + )); + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), + )); +} diff --git a/crates/bevy_xr_utils/src/generic_tracker.rs b/crates/bevy_xr_utils/src/generic_tracker.rs new file mode 100644 index 0000000..59617de --- /dev/null +++ b/crates/bevy_xr_utils/src/generic_tracker.rs @@ -0,0 +1,23 @@ +use bevy::{color::palettes::css, prelude::*}; + +#[derive(Clone, Copy, Component)] +#[require(Transform)] +pub struct GenericTracker; + +pub struct GenericTrackerGizmoPlugin; + +impl Plugin for GenericTrackerGizmoPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + PostUpdate, + draw_gizmos.after(TransformSystem::TransformPropagate), + ); + } +} + +fn draw_gizmos(query: Query<&GlobalTransform, With>, mut gizmos: Gizmos) { + for transform in query { + gizmos.axes(*transform, 0.05); + gizmos.sphere(transform.to_isometry(), 0.05, css::PINK); + } +} diff --git a/crates/bevy_xr_utils/src/lib.rs b/crates/bevy_xr_utils/src/lib.rs index 78db581..2882843 100644 --- a/crates/bevy_xr_utils/src/lib.rs +++ b/crates/bevy_xr_utils/src/lib.rs @@ -4,3 +4,5 @@ pub mod tracking_utils; pub mod transform_utils; #[cfg(not(target_family = "wasm"))] pub mod xr_utils_actions; +pub mod generic_tracker; +pub mod mndx_xdev_spaces_trackers; diff --git a/crates/bevy_xr_utils/src/mndx_xdev_spaces_trackers.rs b/crates/bevy_xr_utils/src/mndx_xdev_spaces_trackers.rs new file mode 100644 index 0000000..16eede6 --- /dev/null +++ b/crates/bevy_xr_utils/src/mndx_xdev_spaces_trackers.rs @@ -0,0 +1,119 @@ +use std::convert::identity; + +use bevy::prelude::*; +use bevy_mod_openxr::{ + resources::{OxrInstance, OxrSystemId}, + session::OxrSession, + spaces::OxrSpaceExt, +}; +use bevy_mod_xr::{ + session::{XrPreDestroySession, XrSessionCreated}, + spaces::XrSpace, +}; +use openxr_mndx_xdev_space::{InstanceXDevExtensionMNDX, SessionXDevExtensionMNDX, XDev, XDevList}; + +use crate::generic_tracker::GenericTracker; + +pub struct MonadoXDevSpacesPlugin; +impl Plugin for MonadoXDevSpacesPlugin { + fn build(&self, _app: &mut App) {} + fn finish(&self, app: &mut App) { + let Some((instance, system_id)) = + app.world() + .get_resource::() + .and_then(|instance| { + app.world() + .get_resource::() + .map(|system_id| (instance, system_id)) + }) + else { + return; + }; + if !instance + .supports_mndx_xdev_spaces(**system_id) + .is_ok_and(identity) + { + return; + } + app.add_systems(XrSessionCreated, session_created); + app.add_systems(PreUpdate, update_xdev_list); + app.add_systems( + XrPreDestroySession, + (despawn_xdev_trackers, |mut cmds: Commands| { + cmds.remove_resource::() + }), + ); + } +} + +fn update_xdev_list(mut xdev_list: ResMut, mut cmds: Commands) { + let Ok(new_gen) = xdev_list + .get_generation() + .inspect_err(|err| error!("unable to get xdev list generation: {err}")) + else { + return; + }; + if new_gen != xdev_list.generation { + xdev_list.generation = new_gen; + cmds.run_system_cached(despawn_xdev_trackers); + cmds.run_system_cached(create_xdev_trackers); + } +} + +fn session_created(session: Res, mut cmds: Commands) { + let list = match session.get_xdev_list() { + Ok(v) => v, + Err(err) => { + error!("unable to create xdev list: {err}"); + return; + } + }; + cmds.insert_resource(PrimaryXDevList { + generation: list.get_generation().unwrap(), + list, + }); + cmds.run_system_cached(despawn_xdev_trackers); + cmds.run_system_cached(create_xdev_trackers); +} + +fn despawn_xdev_trackers( + xdev_query: Query<(Entity, &XrSpace), With>, + mut cmds: Commands, + session: Res, +) { + for (e, space) in &xdev_query { + cmds.entity(e).despawn(); + if let Err(err) = session.destroy_space(*space) { + error!("unable to destroy xdev XrSpace: {err}"); + }; + } +} + +fn create_xdev_trackers(xdev_list: Res, mut cmds: Commands) { + let xdevs = match xdev_list.enumerate_xdevs() { + Err(err) => { + error!("Unable to enumerate xdevs: {err}"); + return; + } + Ok(v) => v, + }; + for xdev in xdevs + .into_iter() + .filter(XDev::can_create_space) + .filter(|v| v.name().contains("Tracker")) + { + info!("new XDev Tracker: {}", xdev.name()); + let xr_space = + XrSpace::from_openxr_space(xdev.create_space(openxr::Posef::IDENTITY).unwrap()); + cmds.spawn((xr_space, GenericTracker, XDevTracker)); + } +} + +#[derive(Clone, Copy, Component, Debug)] +pub struct XDevTracker; +#[derive(Deref, DerefMut, Resource)] +pub struct PrimaryXDevList { + #[deref] + pub list: XDevList, + pub generation: u64, +}