From 42dc01dc6af03e79ebfb2cd0de0d43f936ba373e Mon Sep 17 00:00:00 2001 From: awtterpip Date: Sun, 24 Mar 2024 20:59:21 -0500 Subject: [PATCH] somewhat working actions --- crates/bevy_openxr/examples/3d_scene.rs | 44 ++++- crates/bevy_openxr/src/actions.rs | 205 ++++++++++++++++++++++++ crates/bevy_openxr/src/lib.rs | 2 + crates/bevy_openxr/src/resources.rs | 29 ++++ crates/bevy_xr/src/actions.rs | 59 ++++--- crates/bevy_xr/src/lib.rs | 1 + crates/bevy_xr/src/types.rs | 6 + flake.nix | 3 + 8 files changed, 322 insertions(+), 27 deletions(-) create mode 100644 crates/bevy_xr/src/types.rs diff --git a/crates/bevy_openxr/examples/3d_scene.rs b/crates/bevy_openxr/examples/3d_scene.rs index bbc2873..6cb939d 100644 --- a/crates/bevy_openxr/examples/3d_scene.rs +++ b/crates/bevy_openxr/examples/3d_scene.rs @@ -1,12 +1,37 @@ //! A simple 3D scene with light shining over a cube sitting on a plane. +use std::any::TypeId; + use bevy::prelude::*; -use bevy_openxr::add_xr_plugins; +use bevy_openxr::{ + actions::{create_action_sets, ActionApp}, + add_xr_plugins, resources::{TypedAction, XrActions, XrInstance}, +}; +use bevy_xr::actions::{Action, ActionInfo, ActionState, ActionType}; +use openxr::Binding; + +pub struct Jump; + +impl Action for Jump { + type ActionType = bool; + + fn info() -> ActionInfo { + ActionInfo { + pretty_name: "jump", + name: "jump", + action_type: ActionType::Bool, + type_id: TypeId::of::(), + } + } +} + fn main() { App::new() .add_plugins(add_xr_plugins(DefaultPlugins)) - .add_systems(Startup, setup) + .add_systems(Startup, setup.after(create_action_sets)) + .add_systems(Update, read_action_state) + .register_action::() .run(); } @@ -15,7 +40,15 @@ fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, + actions: Res, + instance: Res, ) { + let TypedAction::Bool(action) = actions.get(&TypeId::of::()).unwrap() else { + unreachable!() + }; + instance.suggest_interaction_profile_bindings(instance.string_to_path("/interaction_profiles/oculus/touch_controller").unwrap(), &[ + Binding::new(action, instance.string_to_path("/user/hand/right/input/a/click").unwrap()) + ]).unwrap(); // circular base commands.spawn(PbrBundle { mesh: meshes.add(Circle::new(4.0)), @@ -49,3 +82,10 @@ fn setup( // ..default() // }); } + +fn read_action_state( + state: Res> +) { + info!("{}", state.pressed()) +} + diff --git a/crates/bevy_openxr/src/actions.rs b/crates/bevy_openxr/src/actions.rs index e69de29..12770f1 100644 --- a/crates/bevy_openxr/src/actions.rs +++ b/crates/bevy_openxr/src/actions.rs @@ -0,0 +1,205 @@ +use std::any::TypeId; +use std::marker::PhantomData; + +use crate::init::XrPreUpdateSet; +use crate::resources::*; +use crate::types::*; +use bevy::app::{App, Plugin, PreUpdate, Startup}; +use bevy::ecs::schedule::common_conditions::resource_added; +use bevy::ecs::schedule::IntoSystemConfigs; +use bevy::ecs::system::{Commands, Res, ResMut}; +use bevy::input::InputSystem; +use bevy::log::error; +use bevy::math::{vec2, Vec2}; +use bevy::utils::hashbrown::HashMap; +use bevy_xr::actions::ActionPlugin; +use bevy_xr::actions::{Action, ActionList, ActionState}; +use bevy_xr::session::session_available; +use bevy_xr::session::session_running; + +pub struct XrActionPlugin; + +impl Plugin for XrActionPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Startup, create_action_sets.run_if(session_available)) + .add_systems( + PreUpdate, + sync_actions.run_if(session_running).before(InputSystem), + ) + .add_systems( + PreUpdate, + attach_action_sets + .after(XrPreUpdateSet::HandleEvents) + .run_if(resource_added::), + ); + } +} + +pub fn create_action_sets( + instance: Res, + action_list: Res, + mut commands: Commands, +) { + let (action_set, actions) = + initialize_action_sets(&instance, &action_list).expect("Failed to initialize action set"); + + commands.insert_resource(action_set); + commands.insert_resource(actions); +} + +pub fn attach_action_sets(mut action_set: ResMut, session: Res) { + session + .attach_action_sets(&[&action_set]) + .expect("Failed to attach action sets"); + action_set.attach(); +} + +pub fn sync_actions(session: Res, action_set: Res) { + session + .sync_actions(&[openxr::ActiveActionSet::new(&action_set)]) + .expect("Failed to sync actions"); +} + +fn initialize_action_sets( + instance: &XrInstance, + action_info: &ActionList, +) -> Result<(XrActionSet, XrActions)> { + let action_set = instance.create_action_set("actions", "actions", 0)?; + let mut actions = HashMap::new(); + for action_info in action_info.0.iter() { + use bevy_xr::actions::ActionType::*; + let action = match action_info.action_type { + Bool => TypedAction::Bool(action_set.create_action( + action_info.name, + action_info.pretty_name, + &[], + )?), + Float => TypedAction::Float(action_set.create_action( + action_info.name, + action_info.pretty_name, + &[], + )?), + Vector => TypedAction::Vector(action_set.create_action( + action_info.name, + action_info.pretty_name, + &[], + )?), + }; + actions.insert(action_info.type_id, action); + } + Ok((XrActionSet::new(action_set), XrActions(actions))) +} + +pub struct XrActionUpdatePlugin(PhantomData); + +impl Plugin for XrActionUpdatePlugin +where + A: Action, + A::ActionType: XrActionTy, +{ + fn build(&self, app: &mut App) { + app.add_systems(PreUpdate, update_action_state::.in_set(InputSystem).run_if(session_running)); + } +} + +impl Default for XrActionUpdatePlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +pub trait XrActionTy: Sized { + fn get_action_state( + action: &TypedAction, + session: &XrSession, + subaction_path: Option, + ) -> Option; +} + +impl XrActionTy for bool { + fn get_action_state( + action: &TypedAction, + session: &XrSession, + subaction_path: Option, + ) -> Option { + match action { + TypedAction::Bool(action) => action + .state(session, subaction_path.unwrap_or(openxr::Path::NULL)) + .ok() + .map(|state| state.current_state), + _ => None, + } + } +} + +impl XrActionTy for f32 { + fn get_action_state( + action: &TypedAction, + session: &XrSession, + subaction_path: Option, + ) -> Option { + match action { + TypedAction::Float(action) => action + .state(session, subaction_path.unwrap_or(openxr::Path::NULL)) + .ok() + .map(|state| state.current_state), + _ => None, + } + } +} + +impl XrActionTy for Vec2 { + fn get_action_state( + action: &TypedAction, + session: &XrSession, + subaction_path: Option, + ) -> Option { + match action { + TypedAction::Vector(action) => action + .state(session, subaction_path.unwrap_or(openxr::Path::NULL)) + .ok() + .map(|state| vec2(state.current_state.x, state.current_state.y)), + _ => None, + } + } +} + +pub fn update_action_state( + mut action_state: ResMut>, + session: Res, + actions: Res, +) where + A: Action, + A::ActionType: XrActionTy, +{ + if let Some(action) = actions.get(&TypeId::of::()) { + if let Some(state) = A::ActionType::get_action_state(action, &session, None) { + action_state.set(state); + } else { + error!( + "Failed to update value for action '{}'", + std::any::type_name::() + ); + } + } +} + +pub trait ActionApp { + fn register_action(&mut self) -> &mut Self + where + A: Action, + A::ActionType: XrActionTy; +} + +impl ActionApp for App { + fn register_action(&mut self) -> &mut Self + where + A: Action, + A::ActionType: XrActionTy, + { + self.add_plugins(( + ActionPlugin::::default(), + XrActionUpdatePlugin::::default(), + )) + } +} diff --git a/crates/bevy_openxr/src/lib.rs b/crates/bevy_openxr/src/lib.rs index 2567062..fb8289d 100644 --- a/crates/bevy_openxr/src/lib.rs +++ b/crates/bevy_openxr/src/lib.rs @@ -1,3 +1,4 @@ +use actions::XrActionPlugin; use bevy::{ app::{PluginGroup, PluginGroupBuilder}, render::{pipelined_rendering::PipelinedRenderingPlugin, RenderPlugin}, @@ -36,6 +37,7 @@ pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { }) .add(XrRenderPlugin) .add(XrCameraPlugin) + .add(XrActionPlugin) .set(WindowPlugin { #[cfg(not(target_os = "android"))] primary_window: Some(Window { diff --git a/crates/bevy_openxr/src/resources.rs b/crates/bevy_openxr/src/resources.rs index 7f35b50..882a799 100644 --- a/crates/bevy_openxr/src/resources.rs +++ b/crates/bevy_openxr/src/resources.rs @@ -1,3 +1,4 @@ +use std::any::TypeId; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -7,6 +8,7 @@ use crate::layer_builder::CompositionLayer; use crate::types::*; use bevy::prelude::*; use bevy::render::extract_resource::ExtractResource; +use bevy::utils::HashMap; use openxr::AnyGraphics; #[derive(Deref, Clone)] @@ -298,3 +300,30 @@ pub struct XrRootTransform(pub GlobalTransform); #[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)] /// This is inserted into the world to signify if the session should be cleaned up. pub struct XrCleanupSession(pub bool); + +#[derive(Resource, Clone, Deref)] +pub struct XrActionSet(#[deref] pub openxr::ActionSet, bool); + +impl XrActionSet { + pub fn new(action_set: openxr::ActionSet) -> Self { + Self(action_set, false) + } + + pub fn attach(&mut self) { + self.1 = true; + } + + pub fn is_attached(&self) -> bool { + self.1 + } +} + +#[derive(Clone)] +pub enum TypedAction { + Bool(openxr::Action), + Float(openxr::Action), + Vector(openxr::Action), +} + +#[derive(Resource, Clone, Deref)] +pub struct XrActions(pub HashMap); \ No newline at end of file diff --git a/crates/bevy_xr/src/actions.rs b/crates/bevy_xr/src/actions.rs index 288c8b5..bde0def 100644 --- a/crates/bevy_xr/src/actions.rs +++ b/crates/bevy_xr/src/actions.rs @@ -1,9 +1,8 @@ -use std::marker::PhantomData; +use std::{any::TypeId, marker::PhantomData}; -use bevy::{ - app::{App, First, Plugin}, - ecs::system::{ResMut, Resource}, -}; +use bevy::app::{App, Plugin}; +use bevy::ecs::system::Resource; +use bevy::math::Vec2; pub struct ActionPlugin(PhantomData); @@ -15,14 +14,16 @@ impl Default for ActionPlugin { impl Plugin for ActionPlugin { fn build(&self, app: &mut App) { - app.add_systems(First, reset_action_state::) - .init_resource::(); - app.world.resource_mut::().0.push(A::INFO); + app.init_resource::() + .init_resource::>(); + app.world.resource_mut::().0.push(A::info()); } } pub enum ActionType { Bool, + Float, + Vector, } pub trait ActionTy: Send + Sync + Default + Clone + Copy { @@ -33,27 +34,45 @@ impl ActionTy for bool { const TYPE: ActionType = ActionType::Bool; } +impl ActionTy for f32 { + const TYPE: ActionType = ActionType::Float; +} + +impl ActionTy for Vec2 { + const TYPE: ActionType = ActionType::Float; +} + pub trait Action: Send + Sync + 'static { type ActionType: ActionTy; - const INFO: ActionInfo; + fn info() -> ActionInfo; } pub struct ActionInfo { pub pretty_name: &'static str, pub name: &'static str, pub action_type: ActionType, + pub type_id: TypeId, } #[derive(Resource, Default)] -pub struct Actions(pub Vec); +pub struct ActionList(pub Vec); -#[derive(Resource, Default)] +#[derive(Resource)] pub struct ActionState { previous_state: A::ActionType, current_state: A::ActionType, } +impl Default for ActionState { + fn default() -> Self { + Self { + previous_state: Default::default(), + current_state: Default::default(), + } + } +} + impl ActionState { pub fn current_state(&self) -> A::ActionType { self.current_state @@ -62,6 +81,10 @@ impl ActionState { pub fn previous_state(&self) -> A::ActionType { self.previous_state } + + pub fn set(&mut self, state: A::ActionType) { + self.previous_state = std::mem::replace(&mut self.current_state, state); + } } impl> ActionState { @@ -81,17 +104,3 @@ impl> ActionState { self.current_state = true } } - -pub fn reset_action_state(mut action_state: ResMut>) { - action_state.previous_state = std::mem::take(&mut action_state.current_state); -} - -pub trait ActionApp { - fn register_action(&mut self) -> &mut Self; -} - -impl ActionApp for App { - fn register_action(&mut self) -> &mut Self { - self.add_plugins(ActionPlugin::::default()) - } -} diff --git a/crates/bevy_xr/src/lib.rs b/crates/bevy_xr/src/lib.rs index 551636d..2ed5d51 100644 --- a/crates/bevy_xr/src/lib.rs +++ b/crates/bevy_xr/src/lib.rs @@ -1,3 +1,4 @@ pub mod actions; pub mod camera; pub mod session; +pub mod types; \ No newline at end of file diff --git a/crates/bevy_xr/src/types.rs b/crates/bevy_xr/src/types.rs new file mode 100644 index 0000000..4241ae8 --- /dev/null +++ b/crates/bevy_xr/src/types.rs @@ -0,0 +1,6 @@ +use bevy::math::{Quat, Vec3}; + +pub struct Pose { + pub position: Vec3, + pub orientation: Quat +} \ No newline at end of file diff --git a/flake.nix b/flake.nix index 6a082d6..db1b68e 100644 --- a/flake.nix +++ b/flake.nix @@ -70,6 +70,9 @@ # wayland libxkbcommon wayland + # xr + openxr-loader + libGL ]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ pkgs.darwin.apple_sdk.frameworks.Cocoa