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.
|
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||||
|
|
||||||
|
use std::any::TypeId;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
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() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(add_xr_plugins(DefaultPlugins))
|
.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();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,7 +40,15 @@ fn setup(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
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
|
// circular base
|
||||||
commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
mesh: meshes.add(Circle::new(4.0)),
|
mesh: meshes.add(Circle::new(4.0)),
|
||||||
@@ -49,3 +82,10 @@ fn setup(
|
|||||||
// ..default()
|
// ..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::{
|
use bevy::{
|
||||||
app::{PluginGroup, PluginGroupBuilder},
|
app::{PluginGroup, PluginGroupBuilder},
|
||||||
render::{pipelined_rendering::PipelinedRenderingPlugin, RenderPlugin},
|
render::{pipelined_rendering::PipelinedRenderingPlugin, RenderPlugin},
|
||||||
@@ -36,6 +37,7 @@ pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
|||||||
})
|
})
|
||||||
.add(XrRenderPlugin)
|
.add(XrRenderPlugin)
|
||||||
.add(XrCameraPlugin)
|
.add(XrCameraPlugin)
|
||||||
|
.add(XrActionPlugin)
|
||||||
.set(WindowPlugin {
|
.set(WindowPlugin {
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::any::TypeId;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ use crate::layer_builder::CompositionLayer;
|
|||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::extract_resource::ExtractResource;
|
use bevy::render::extract_resource::ExtractResource;
|
||||||
|
use bevy::utils::HashMap;
|
||||||
use openxr::AnyGraphics;
|
use openxr::AnyGraphics;
|
||||||
|
|
||||||
#[derive(Deref, Clone)]
|
#[derive(Deref, Clone)]
|
||||||
@@ -298,3 +300,30 @@ pub struct XrRootTransform(pub GlobalTransform);
|
|||||||
#[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)]
|
#[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)]
|
||||||
/// This is inserted into the world to signify if the session should be cleaned up.
|
/// This is inserted into the world to signify if the session should be cleaned up.
|
||||||
pub struct XrCleanupSession(pub bool);
|
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::{
|
use bevy::app::{App, Plugin};
|
||||||
app::{App, First, Plugin},
|
use bevy::ecs::system::Resource;
|
||||||
ecs::system::{ResMut, Resource},
|
use bevy::math::Vec2;
|
||||||
};
|
|
||||||
|
|
||||||
pub struct ActionPlugin<A: Action>(PhantomData<A>);
|
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> {
|
impl<A: Action> Plugin for ActionPlugin<A> {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(First, reset_action_state::<A>)
|
app.init_resource::<ActionList>()
|
||||||
.init_resource::<Actions>();
|
.init_resource::<ActionState<A>>();
|
||||||
app.world.resource_mut::<Actions>().0.push(A::INFO);
|
app.world.resource_mut::<ActionList>().0.push(A::info());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ActionType {
|
pub enum ActionType {
|
||||||
Bool,
|
Bool,
|
||||||
|
Float,
|
||||||
|
Vector,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ActionTy: Send + Sync + Default + Clone + Copy {
|
pub trait ActionTy: Send + Sync + Default + Clone + Copy {
|
||||||
@@ -33,27 +34,45 @@ impl ActionTy for bool {
|
|||||||
const TYPE: ActionType = ActionType::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 {
|
pub trait Action: Send + Sync + 'static {
|
||||||
type ActionType: ActionTy;
|
type ActionType: ActionTy;
|
||||||
|
|
||||||
const INFO: ActionInfo;
|
fn info() -> ActionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ActionInfo {
|
pub struct ActionInfo {
|
||||||
pub pretty_name: &'static str,
|
pub pretty_name: &'static str,
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub action_type: ActionType,
|
pub action_type: ActionType,
|
||||||
|
pub type_id: TypeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[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> {
|
pub struct ActionState<A: Action> {
|
||||||
previous_state: A::ActionType,
|
previous_state: A::ActionType,
|
||||||
current_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> {
|
impl<A: Action> ActionState<A> {
|
||||||
pub fn current_state(&self) -> A::ActionType {
|
pub fn current_state(&self) -> A::ActionType {
|
||||||
self.current_state
|
self.current_state
|
||||||
@@ -62,6 +81,10 @@ impl<A: Action> ActionState<A> {
|
|||||||
pub fn previous_state(&self) -> A::ActionType {
|
pub fn previous_state(&self) -> A::ActionType {
|
||||||
self.previous_state
|
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> {
|
impl<A: Action<ActionType = bool>> ActionState<A> {
|
||||||
@@ -81,17 +104,3 @@ impl<A: Action<ActionType = bool>> ActionState<A> {
|
|||||||
self.current_state = true
|
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 actions;
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod session;
|
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
|
||||||
|
}
|
||||||
@@ -70,6 +70,9 @@
|
|||||||
# wayland
|
# wayland
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
wayland
|
wayland
|
||||||
|
# xr
|
||||||
|
openxr-loader
|
||||||
|
libGL
|
||||||
])
|
])
|
||||||
++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
|
++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
|
||||||
pkgs.darwin.apple_sdk.frameworks.Cocoa
|
pkgs.darwin.apple_sdk.frameworks.Cocoa
|
||||||
|
|||||||
Reference in New Issue
Block a user