somewhat working actions
This commit is contained in:
@@ -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::<Self>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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::<Jump>()
|
||||
.run();
|
||||
}
|
||||
|
||||
@@ -15,7 +40,15 @@ fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
actions: Res<XrActions>,
|
||||
instance: Res<XrInstance>,
|
||||
) {
|
||||
let TypedAction::Bool(action) = actions.get(&TypeId::of::<Jump>()).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<ActionState<Jump>>
|
||||
) {
|
||||
info!("{}", state.pressed())
|
||||
}
|
||||
|
||||
|
||||
@@ -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::<XrSession>),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_action_sets(
|
||||
instance: Res<XrInstance>,
|
||||
action_list: Res<ActionList>,
|
||||
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<XrActionSet>, session: Res<XrSession>) {
|
||||
session
|
||||
.attach_action_sets(&[&action_set])
|
||||
.expect("Failed to attach action sets");
|
||||
action_set.attach();
|
||||
}
|
||||
|
||||
pub fn sync_actions(session: Res<XrSession>, action_set: Res<XrActionSet>) {
|
||||
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<A: Action>(PhantomData<A>);
|
||||
|
||||
impl<A> Plugin for XrActionUpdatePlugin<A>
|
||||
where
|
||||
A: Action,
|
||||
A::ActionType: XrActionTy,
|
||||
{
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(PreUpdate, update_action_state::<A>.in_set(InputSystem).run_if(session_running));
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> Default for XrActionUpdatePlugin<A> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait XrActionTy: Sized {
|
||||
fn get_action_state(
|
||||
action: &TypedAction,
|
||||
session: &XrSession,
|
||||
subaction_path: Option<openxr::Path>,
|
||||
) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl XrActionTy for bool {
|
||||
fn get_action_state(
|
||||
action: &TypedAction,
|
||||
session: &XrSession,
|
||||
subaction_path: Option<openxr::Path>,
|
||||
) -> Option<Self> {
|
||||
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<openxr::Path>,
|
||||
) -> Option<Self> {
|
||||
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<openxr::Path>,
|
||||
) -> Option<Self> {
|
||||
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<A>(
|
||||
mut action_state: ResMut<ActionState<A>>,
|
||||
session: Res<XrSession>,
|
||||
actions: Res<XrActions>,
|
||||
) where
|
||||
A: Action,
|
||||
A::ActionType: XrActionTy,
|
||||
{
|
||||
if let Some(action) = actions.get(&TypeId::of::<A>()) {
|
||||
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::<A>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ActionApp {
|
||||
fn register_action<A>(&mut self) -> &mut Self
|
||||
where
|
||||
A: Action,
|
||||
A::ActionType: XrActionTy;
|
||||
}
|
||||
|
||||
impl ActionApp for App {
|
||||
fn register_action<A>(&mut self) -> &mut Self
|
||||
where
|
||||
A: Action,
|
||||
A::ActionType: XrActionTy,
|
||||
{
|
||||
self.add_plugins((
|
||||
ActionPlugin::<A>::default(),
|
||||
XrActionUpdatePlugin::<A>::default(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
||||
})
|
||||
.add(XrRenderPlugin)
|
||||
.add(XrCameraPlugin)
|
||||
.add(XrActionPlugin)
|
||||
.set(WindowPlugin {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
primary_window: Some(Window {
|
||||
|
||||
@@ -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<bool>),
|
||||
Float(openxr::Action<f32>),
|
||||
Vector(openxr::Action<openxr::Vector2f>),
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone, Deref)]
|
||||
pub struct XrActions(pub HashMap<TypeId, TypedAction>);
|
||||
@@ -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<A: Action>(PhantomData<A>);
|
||||
|
||||
@@ -15,14 +14,16 @@ impl<A: Action> Default for ActionPlugin<A> {
|
||||
|
||||
impl<A: Action> Plugin for ActionPlugin<A> {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(First, reset_action_state::<A>)
|
||||
.init_resource::<Actions>();
|
||||
app.world.resource_mut::<Actions>().0.push(A::INFO);
|
||||
app.init_resource::<ActionList>()
|
||||
.init_resource::<ActionState<A>>();
|
||||
app.world.resource_mut::<ActionList>().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<ActionInfo>);
|
||||
pub struct ActionList(pub Vec<ActionInfo>);
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
#[derive(Resource)]
|
||||
pub struct ActionState<A: Action> {
|
||||
previous_state: A::ActionType,
|
||||
current_state: A::ActionType,
|
||||
}
|
||||
|
||||
impl<A: Action> Default for ActionState<A> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
previous_state: Default::default(),
|
||||
current_state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> ActionState<A> {
|
||||
pub fn current_state(&self) -> A::ActionType {
|
||||
self.current_state
|
||||
@@ -62,6 +81,10 @@ impl<A: Action> ActionState<A> {
|
||||
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<A: Action<ActionType = bool>> ActionState<A> {
|
||||
@@ -81,17 +104,3 @@ impl<A: Action<ActionType = bool>> ActionState<A> {
|
||||
self.current_state = true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_action_state<A: Action>(mut action_state: ResMut<ActionState<A>>) {
|
||||
action_state.previous_state = std::mem::take(&mut action_state.current_state);
|
||||
}
|
||||
|
||||
pub trait ActionApp {
|
||||
fn register_action<A: Action>(&mut self) -> &mut Self;
|
||||
}
|
||||
|
||||
impl ActionApp for App {
|
||||
fn register_action<A: Action>(&mut self) -> &mut Self {
|
||||
self.add_plugins(ActionPlugin::<A>::default())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod actions;
|
||||
pub mod camera;
|
||||
pub mod session;
|
||||
pub mod types;
|
||||
6
crates/bevy_xr/src/types.rs
Normal file
6
crates/bevy_xr/src/types.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use bevy::math::{Quat, Vec3};
|
||||
|
||||
pub struct Pose {
|
||||
pub position: Vec3,
|
||||
pub orientation: Quat
|
||||
}
|
||||
Reference in New Issue
Block a user