refactor: very close to compiling, controllers should almost compile
Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
use bevy::{
|
use bevy::{
|
||||||
app::MainScheduleOrder, asset::embedded_asset, ecs::schedule::ScheduleLabel,
|
app::MainScheduleOrder,
|
||||||
math::bounding::Aabb3d, prelude::*,
|
ecs::schedule::{ExecutorKind, ScheduleLabel},
|
||||||
|
math::bounding::Aabb3d,
|
||||||
|
prelude::*,
|
||||||
};
|
};
|
||||||
use bevy_mod_openxr::session::OxrSession;
|
use bevy_mod_openxr::session::OxrSession;
|
||||||
use bevy_mod_xr::session::{session_available, XrSessionCreated};
|
use bevy_mod_xr::session::{session_available, XrSessionCreated};
|
||||||
@@ -11,6 +13,11 @@ use crate::objects::Inputs;
|
|||||||
|
|
||||||
pub struct StardustBevyPlugin;
|
pub struct StardustBevyPlugin;
|
||||||
|
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
pub struct DbusConnection(pub zbus::Connection);
|
||||||
|
|
||||||
|
#[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct InputUpdate;
|
||||||
impl Plugin for StardustBevyPlugin {
|
impl Plugin for StardustBevyPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_schedule(StardustExtract);
|
app.init_schedule(StardustExtract);
|
||||||
@@ -19,7 +26,9 @@ impl Plugin for StardustBevyPlugin {
|
|||||||
labels.insert(labels.len() - 2, StardustExtract.intern());
|
labels.insert(labels.len() - 2, StardustExtract.intern());
|
||||||
app.add_systems(Startup, spawn_camera.run_if(not(session_available)));
|
app.add_systems(Startup, spawn_camera.run_if(not(session_available)));
|
||||||
app.add_systems(XrSessionCreated, make_view_space);
|
app.add_systems(XrSessionCreated, make_view_space);
|
||||||
embedded_asset!(app, "src/objects/input", "objects/input/cursor.glb");
|
let mut schedule = Schedule::new(InputUpdate);
|
||||||
|
schedule.set_executor_kind(ExecutorKind::Simple);
|
||||||
|
app.add_schedule(schedule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
96
src/main.rs
96
src/main.rs
@@ -4,15 +4,16 @@ mod core;
|
|||||||
mod nodes;
|
mod nodes;
|
||||||
mod objects;
|
mod objects;
|
||||||
pub mod oxr_render_plugin;
|
pub mod oxr_render_plugin;
|
||||||
|
pub mod oxr_sync_actionset;
|
||||||
mod session;
|
mod session;
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
mod wayland;
|
mod wayland;
|
||||||
|
|
||||||
use crate::core::destroy_queue;
|
use crate::core::destroy_queue;
|
||||||
use crate::nodes::items::camera;
|
// use crate::nodes::items::camera;
|
||||||
use crate::nodes::{audio, drawable, input};
|
use crate::nodes::{audio, drawable, input};
|
||||||
|
|
||||||
use bevy::app::{App, Startup, Update};
|
use bevy::app::{App, PluginGroup, Startup, Update};
|
||||||
use bevy::asset::{AssetServer, Handle};
|
use bevy::asset::{AssetServer, Handle};
|
||||||
use bevy::core_pipeline::Skybox;
|
use bevy::core_pipeline::Skybox;
|
||||||
use bevy::image::Image;
|
use bevy::image::Image;
|
||||||
@@ -24,7 +25,7 @@ use bevy::prelude::{
|
|||||||
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
||||||
use bevy::time::Time;
|
use bevy::time::Time;
|
||||||
use bevy::DefaultPlugins;
|
use bevy::DefaultPlugins;
|
||||||
use bevy_mod_openxr::add_xr_plugins;
|
use bevy_mod_openxr::action_set_syncing::{OxrActionSyncingPlugin, OxrSyncActionSet};
|
||||||
use bevy_mod_openxr::exts::OxrExtensions;
|
use bevy_mod_openxr::exts::OxrExtensions;
|
||||||
use bevy_mod_openxr::features::overlay::{OxrOverlaySessionEvent, OxrOverlaySettings};
|
use bevy_mod_openxr::features::overlay::{OxrOverlaySessionEvent, OxrOverlaySettings};
|
||||||
use bevy_mod_openxr::init::{should_run_frame_loop, OxrInitPlugin};
|
use bevy_mod_openxr::init::{should_run_frame_loop, OxrInitPlugin};
|
||||||
@@ -32,13 +33,18 @@ use bevy_mod_openxr::render::{update_cameras, OxrRenderPlugin};
|
|||||||
use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrGraphicsInfo};
|
use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrGraphicsInfo};
|
||||||
use bevy_mod_openxr::session::OxrSession;
|
use bevy_mod_openxr::session::OxrSession;
|
||||||
use bevy_mod_openxr::types::{AppInfo, Version};
|
use bevy_mod_openxr::types::{AppInfo, Version};
|
||||||
|
use bevy_mod_openxr::{add_xr_plugins, openxr_session_running};
|
||||||
use bevy_mod_xr::session::{XrFirst, XrSessionCreated, XrSessionPlugin};
|
use bevy_mod_xr::session::{XrFirst, XrSessionCreated, XrSessionPlugin};
|
||||||
use bevy_plugin::StardustBevyPlugin;
|
use bevy_plugin::{DbusConnection, InputUpdate, StardustBevyPlugin};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use core::client::Client;
|
use core::client::Client;
|
||||||
use core::task;
|
use core::task;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
|
use nodes::audio::StardustSoundPlugin;
|
||||||
use nodes::drawable::lines::BevyLinesPlugin;
|
use nodes::drawable::lines::BevyLinesPlugin;
|
||||||
|
use nodes::drawable::model::StardustModelPlugin;
|
||||||
|
use nodes::drawable::text::StardustTextPlugin;
|
||||||
|
use objects::input::sk_controller::StardustControllerPlugin;
|
||||||
use objects::ServerObjects;
|
use objects::ServerObjects;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use openxr::OverlaySessionCreateFlagsEXTX;
|
use openxr::OverlaySessionCreateFlagsEXTX;
|
||||||
@@ -52,7 +58,7 @@ use std::time::Duration;
|
|||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
use tracing::metadata::LevelFilter;
|
use tracing::metadata::LevelFilter;
|
||||||
use tracing::{debug_span, error, info};
|
use tracing::{debug_span, error, info, warn};
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
use zbus::fdo::ObjectManager;
|
use zbus::fdo::ObjectManager;
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
@@ -209,7 +215,9 @@ fn stereokit_loop(
|
|||||||
object_registry: ObjectRegistry,
|
object_registry: ObjectRegistry,
|
||||||
) {
|
) {
|
||||||
let mut bevy_app = App::new();
|
let mut bevy_app = App::new();
|
||||||
let base = DefaultPlugins::build(DefaultPlugins).disable::<PipelinedRenderingPlugin>();
|
let base = (DefaultPlugins)
|
||||||
|
.build()
|
||||||
|
.disable::<PipelinedRenderingPlugin>();
|
||||||
if args.flatscreen {
|
if args.flatscreen {
|
||||||
bevy_app.add_plugins(base);
|
bevy_app.add_plugins(base);
|
||||||
} else {
|
} else {
|
||||||
@@ -236,6 +244,7 @@ fn stereokit_loop(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.disable::<OxrRenderPlugin>()
|
.disable::<OxrRenderPlugin>()
|
||||||
|
.disable::<OxrActionSyncingPlugin>()
|
||||||
.add_after::<XrSessionPlugin>(StardustOxrRenderPlugin),
|
.add_after::<XrSessionPlugin>(StardustOxrRenderPlugin),
|
||||||
);
|
);
|
||||||
if let Some(priority) = args.overlay_priority {
|
if let Some(priority) = args.overlay_priority {
|
||||||
@@ -244,22 +253,32 @@ fn stereokit_loop(
|
|||||||
flags: OverlaySessionCreateFlagsEXTX::EMPTY,
|
flags: OverlaySessionCreateFlagsEXTX::EMPTY,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
bevy_app.add_event::<OxrSyncActionSet>();
|
||||||
}
|
}
|
||||||
bevy_app.add_plugins(StardustBevyPlugin);
|
bevy_app.add_plugins(StardustBevyPlugin);
|
||||||
bevy_app.add_plugins(BevyLinesPlugin);
|
bevy_app.add_plugins((
|
||||||
|
BevyLinesPlugin,
|
||||||
|
StardustModelPlugin,
|
||||||
|
StardustTextPlugin,
|
||||||
|
StardustSoundPlugin,
|
||||||
|
StardustControllerPlugin,
|
||||||
|
));
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct SkyTexture(Handle<Image>);
|
struct SkyTexture(Handle<Image>);
|
||||||
// Skytex/light stuff
|
// Skytex/light stuff
|
||||||
bevy_app.add_systems(Startup, |assests: Res<AssetServer>, mut cmds: Commands| {
|
bevy_app.add_systems(
|
||||||
if let Some(sky) = project_dirs
|
Startup,
|
||||||
.as_ref()
|
move |assests: Res<AssetServer>, mut cmds: Commands| {
|
||||||
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
|
if let Some(sky) = project_dirs
|
||||||
.filter(|f| f.exists())
|
.as_ref()
|
||||||
.map(|p| assests.load(p))
|
.map(|dirs| dirs.config_dir().join("skytex.hdr"))
|
||||||
{
|
.filter(|f| f.exists())
|
||||||
cmds.insert_resource(SkyTexture(sky));
|
.map(|p| assests.load(p))
|
||||||
}
|
{
|
||||||
});
|
cmds.insert_resource(SkyTexture(sky));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct RenderBackground(bool);
|
struct RenderBackground(bool);
|
||||||
fn update_background(
|
fn update_background(
|
||||||
@@ -291,6 +310,7 @@ fn stereokit_loop(
|
|||||||
Update,
|
Update,
|
||||||
update_background.run_if(on_event::<OxrOverlaySessionEvent>),
|
update_background.run_if(on_event::<OxrOverlaySessionEvent>),
|
||||||
);
|
);
|
||||||
|
bevy_app.insert_resource(DbusConnection(dbus_connection.clone()));
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
|
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
|
||||||
@@ -300,9 +320,21 @@ fn stereokit_loop(
|
|||||||
info!("Stardust ready!");
|
info!("Stardust ready!");
|
||||||
|
|
||||||
let mut objects = ServerObjects::new(dbus_connection.clone());
|
let mut objects = ServerObjects::new(dbus_connection.clone());
|
||||||
|
fn sync_sets(session: Res<OxrSession>, mut events: EventReader<OxrSyncActionSet>) {
|
||||||
|
let sets = events
|
||||||
|
.read()
|
||||||
|
.map(|v| &v.0)
|
||||||
|
.map(openxr::ActiveActionSet::new)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if sets.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = session.sync_actions(&sets) {
|
||||||
|
warn!("error while syncing actionsets: {}", err.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_frame_delta = Duration::ZERO;
|
|
||||||
let mut sleep_duration = Duration::ZERO;
|
|
||||||
debug_span!("bevy").in_scope(|| loop {
|
debug_span!("bevy").in_scope(|| loop {
|
||||||
let _span = debug_span!("Bevy step");
|
let _span = debug_span!("Bevy step");
|
||||||
let _span = _span.enter();
|
let _span = _span.enter();
|
||||||
@@ -313,33 +345,43 @@ fn stereokit_loop(
|
|||||||
destroy_queue::clear();
|
destroy_queue::clear();
|
||||||
|
|
||||||
let world = bevy_app.world_mut();
|
let world = bevy_app.world_mut();
|
||||||
let session = world.remove_resource::<OxrSession>();
|
let time = world.get_resource_mut::<OxrFrameState>().map(|mut s| {
|
||||||
let time = world.get_resource::<OxrFrameState>().map(|s| {
|
let t = openxr::Time::from_nanos(
|
||||||
openxr::Time::from_nanos(
|
|
||||||
s.predicted_display_time.as_nanos() + s.predicted_display_period.as_nanos(),
|
s.predicted_display_time.as_nanos() + s.predicted_display_period.as_nanos(),
|
||||||
)
|
);
|
||||||
|
s.predicted_display_time = t;
|
||||||
|
t
|
||||||
});
|
});
|
||||||
|
world.run_schedule(XrFirst);
|
||||||
|
if world
|
||||||
|
.run_system_cached(openxr_session_running)
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
world.run_system_cached(sync_sets);
|
||||||
|
}
|
||||||
|
world.run_schedule(InputUpdate);
|
||||||
|
let session = world.remove_resource::<OxrSession>();
|
||||||
objects.update(session.as_deref(), time);
|
objects.update(session.as_deref(), time);
|
||||||
|
if let Some(session) = session {
|
||||||
|
world.insert_resource(session);
|
||||||
|
}
|
||||||
input::process_input();
|
input::process_input();
|
||||||
nodes::root::Root::send_frame_events(world.resource::<Time>().delta_secs_f64());
|
nodes::root::Root::send_frame_events(world.resource::<Time>().delta_secs_f64());
|
||||||
world.run_schedule(XrFirst);
|
|
||||||
if world
|
if world
|
||||||
.run_system_cached(should_run_frame_loop)
|
.run_system_cached(should_run_frame_loop)
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
let waiter = world.remove_resource::<OxrFrameWaiter>().unwrap();
|
let mut waiter = world.remove_resource::<OxrFrameWaiter>().unwrap();
|
||||||
let state = waiter.wait().unwrap();
|
let state = waiter.wait().unwrap();
|
||||||
world.insert_resource(OxrFrameState(state));
|
world.insert_resource(OxrFrameState(state));
|
||||||
world.run_system_cached(update_cameras);
|
world.run_system_cached(update_cameras);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
wayland.update();
|
wayland.update();
|
||||||
drawable::draw(token);
|
|
||||||
bevy_app.update();
|
bevy_app.update();
|
||||||
if let Some(exit) = bevy_app.should_exit() {
|
if let Some(exit) = bevy_app.should_exit() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
audio::update();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ use crate::nodes::AspectIdentifier;
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
|
core::{client::Client, registry::Registry, scenegraph::MethodResponseSender},
|
||||||
nodes::{
|
nodes::{
|
||||||
drawable::{
|
drawable::{model::ModelPart, shaders::UNLIT_SHADER_BYTES},
|
||||||
model::{MaterialWrapper, ModelPart},
|
|
||||||
shaders::UNLIT_SHADER_BYTES,
|
|
||||||
},
|
|
||||||
items::TypeInfo,
|
items::TypeInfo,
|
||||||
spatial::{Spatial, Transform},
|
spatial::{Spatial, Transform},
|
||||||
Message, Node,
|
Message, Node,
|
||||||
@@ -25,20 +22,8 @@ use parking_lot::Mutex;
|
|||||||
|
|
||||||
use stardust_xr::schemas::flex::{deserialize, serialize};
|
use stardust_xr::schemas::flex::{deserialize, serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use stereokit_rust::{
|
|
||||||
material::{Material, Transparency},
|
|
||||||
shader::Shader,
|
|
||||||
sk::MainThreadToken,
|
|
||||||
system::Renderer,
|
|
||||||
tex::{Tex, TexFormat, TexType},
|
|
||||||
util::Color128,
|
|
||||||
};
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
pub struct TexWrapper(pub Tex);
|
|
||||||
unsafe impl Send for TexWrapper {}
|
|
||||||
unsafe impl Sync for TexWrapper {}
|
|
||||||
|
|
||||||
stardust_xr_server_codegen::codegen_item_camera_protocol!();
|
stardust_xr_server_codegen::codegen_item_camera_protocol!();
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
|
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
|
||||||
@@ -68,8 +53,8 @@ struct FrameInfo {
|
|||||||
pub struct CameraItem {
|
pub struct CameraItem {
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
frame_info: Mutex<FrameInfo>,
|
frame_info: Mutex<FrameInfo>,
|
||||||
sk_tex: OnceCell<TexWrapper>,
|
sk_tex: OnceCell<()>,
|
||||||
sk_mat: OnceCell<Arc<MaterialWrapper>>,
|
sk_mat: OnceCell<Arc<()>>,
|
||||||
applied_to: Registry<ModelPart>,
|
applied_to: Registry<ModelPart>,
|
||||||
apply_to: Registry<ModelPart>,
|
apply_to: Registry<ModelPart>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
pub mod camera;
|
// TODO: reimplement with bevy
|
||||||
|
// pub mod camera;
|
||||||
pub mod panel;
|
pub mod panel;
|
||||||
|
|
||||||
use self::camera::CameraItem;
|
// use self::camera::CameraItem;
|
||||||
use self::panel::PanelItemTrait;
|
use self::panel::PanelItemTrait;
|
||||||
use super::alias::AliasList;
|
use super::alias::AliasList;
|
||||||
use super::fields::{Field, FIELD_ALIAS_INFO};
|
use super::fields::{Field, FIELD_ALIAS_INFO};
|
||||||
@@ -134,19 +135,19 @@ impl Drop for Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum ItemType {
|
pub enum ItemType {
|
||||||
Camera(Arc<CameraItem>),
|
// Camera(CameraItem),
|
||||||
Panel(Arc<dyn PanelItemTrait>),
|
Panel(Arc<dyn PanelItemTrait>),
|
||||||
}
|
}
|
||||||
impl ItemType {
|
impl ItemType {
|
||||||
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||||
match self {
|
match self {
|
||||||
ItemType::Camera(c) => c.send_ui_item_created(node, item),
|
// ItemType::Camera(c) => c.send_ui_item_created(node, item),
|
||||||
ItemType::Panel(p) => p.send_ui_item_created(node, item),
|
ItemType::Panel(p) => p.send_ui_item_created(node, item),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
|
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>) {
|
||||||
match self {
|
match self {
|
||||||
ItemType::Camera(c) => c.send_acceptor_item_created(node, item),
|
// ItemType::Camera(c) => c.send_acceptor_item_created(node, item),
|
||||||
ItemType::Panel(p) => p.send_acceptor_item_created(node, item),
|
ItemType::Panel(p) => p.send_acceptor_item_created(node, item),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::{get_sorted_handlers, CaptureManager};
|
use super::{get_sorted_handlers, CaptureManager};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
bevy_plugin::DbusConnection,
|
||||||
core::client::INTERNAL_CLIENT,
|
core::client::INTERNAL_CLIENT,
|
||||||
nodes::{
|
nodes::{
|
||||||
fields::{Field, FieldTrait},
|
fields::{Field, FieldTrait},
|
||||||
@@ -10,13 +11,34 @@ use crate::{
|
|||||||
objects::{ObjectHandle, SpatialRef},
|
objects::{ObjectHandle, SpatialRef},
|
||||||
DefaultMaterial,
|
DefaultMaterial,
|
||||||
};
|
};
|
||||||
use bevy::{asset::Handle, prelude::Mesh};
|
use bevy::{
|
||||||
use bevy_mod_xr::hands::HandSide;
|
app::{App, Plugin},
|
||||||
|
asset::{embedded_asset, AssetServer, Assets, Handle},
|
||||||
|
color::LinearRgba,
|
||||||
|
gltf::GltfAssetLabel,
|
||||||
|
pbr::MeshMaterial3d,
|
||||||
|
prelude::{Children, Commands, Component, Mesh, Query, Res, ResMut, Transform},
|
||||||
|
scene::SceneRoot,
|
||||||
|
};
|
||||||
|
use bevy_mod_openxr::{
|
||||||
|
helper_traits::{ToQuat, ToVec2, ToVec3},
|
||||||
|
resources::OxrFrameState,
|
||||||
|
session::OxrSession,
|
||||||
|
spaces::{OxrSpaceExt, OxrSpaceLocationFlags},
|
||||||
|
};
|
||||||
|
use bevy_mod_xr::{
|
||||||
|
hands::HandSide,
|
||||||
|
session::XrSessionCreated,
|
||||||
|
spaces::{XrPrimaryReferenceSpace, XrSpace},
|
||||||
|
};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use glam::{Mat4, Vec2, Vec3};
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use openxr::{ActionSet, Posef};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use stardust_xr::values::Datamap;
|
use stardust_xr::values::Datamap;
|
||||||
use std::sync::Arc;
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
use tracing::error;
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||||
@@ -28,96 +50,269 @@ struct ControllerDatamap {
|
|||||||
scroll: Vec2,
|
scroll: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct StardustControllerPlugin;
|
||||||
|
impl Plugin for StardustControllerPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
embedded_asset!(app, "src/objects/input", "cursor.glb");
|
||||||
|
app.add_systems(XrSessionCreated, spawn_controllers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_controllers(
|
||||||
|
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||||
|
mut query: Query<(&SkController, &mut Transform)>,
|
||||||
|
time: Res<OxrFrameState>,
|
||||||
|
base_space: Res<XrPrimaryReferenceSpace>,
|
||||||
|
session: ResMut<OxrSession>,
|
||||||
|
) {
|
||||||
|
for (controller, mut transform) in &mut query {
|
||||||
|
let input_node = controller.input.spatial.node().unwrap();
|
||||||
|
let location = (|| {
|
||||||
|
let location = match session.locate_space(
|
||||||
|
&XrSpace::from_raw_openxr_space(controller.space.as_raw()),
|
||||||
|
&base_space,
|
||||||
|
time.predicted_display_time,
|
||||||
|
) {
|
||||||
|
Err(err) => {
|
||||||
|
error!("issues locating controller space: {err}");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Ok(val) => val,
|
||||||
|
};
|
||||||
|
let flags = OxrSpaceLocationFlags(location.location_flags);
|
||||||
|
|
||||||
|
input_node.set_enabled(flags.pos_tracked() && flags.rot_tracked());
|
||||||
|
if flags.pos_valid() && flags.rot_valid() {
|
||||||
|
Some(Mat4::from_rotation_translation(
|
||||||
|
location.pose.orientation.to_quat(),
|
||||||
|
location.pose.position.to_vec3(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.unwrap_or(Mat4::IDENTITY);
|
||||||
|
if input_node.enabled() {
|
||||||
|
let world_transform = location;
|
||||||
|
if let Some(mat) = controller.material.get().and_then(|v| mats.get_mut(v)) {
|
||||||
|
mat.base_color = if controller.capture.is_none() {
|
||||||
|
LinearRgba::rgb(1.0, 1.0, 1.0)
|
||||||
|
} else {
|
||||||
|
LinearRgba::rgb(0.0, 1.0, 0.75)
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
*transform =
|
||||||
|
Transform::from_matrix(world_transform * Mat4::from_scale(Vec3::ONE * 0.02));
|
||||||
|
controller
|
||||||
|
.input
|
||||||
|
.spatial
|
||||||
|
.set_local_transform(world_transform);
|
||||||
|
}
|
||||||
|
controller.datamap.select = controller
|
||||||
|
.actions
|
||||||
|
.trigger
|
||||||
|
.state(&session, openxr::Path::NULL)
|
||||||
|
.map(|v| v.current_state)
|
||||||
|
.unwrap_or_default();
|
||||||
|
controller.datamap.grab = controller
|
||||||
|
.actions
|
||||||
|
.grip
|
||||||
|
.state(&session, openxr::Path::NULL)
|
||||||
|
.map(|v| v.current_state)
|
||||||
|
.unwrap_or_default();
|
||||||
|
controller.datamap.scroll = controller
|
||||||
|
.actions
|
||||||
|
.stick
|
||||||
|
.state(&session, openxr::Path::NULL)
|
||||||
|
.map(|v| v.current_state.to_vec2())
|
||||||
|
.unwrap_or_default();
|
||||||
|
*controller.input.datamap.lock() = Datamap::from_typed(&controller.datamap).unwrap();
|
||||||
|
|
||||||
|
// remove the capture when it's removed from captures list
|
||||||
|
if let Some(capture) = &controller.capture {
|
||||||
|
if !controller
|
||||||
|
.input
|
||||||
|
.capture_requests
|
||||||
|
.get_valid_contents()
|
||||||
|
.contains(capture)
|
||||||
|
{
|
||||||
|
controller.capture.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add the capture that's the closest if we don't have one
|
||||||
|
if controller.capture.is_none() {
|
||||||
|
controller.capture = controller
|
||||||
|
.input
|
||||||
|
.capture_requests
|
||||||
|
.get_valid_contents()
|
||||||
|
.into_iter()
|
||||||
|
.map(|handler| {
|
||||||
|
(
|
||||||
|
handler.clone(),
|
||||||
|
handler
|
||||||
|
.field
|
||||||
|
.distance(&controller.input.spatial, [0.0; 3].into())
|
||||||
|
.abs(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.reduce(|(handlers_a, distance_a), (handlers_b, distance_b)| {
|
||||||
|
if distance_a < distance_b {
|
||||||
|
(handlers_a, distance_a)
|
||||||
|
} else {
|
||||||
|
(handlers_b, distance_b)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(rx, _)| rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that if something is captured only send input to it
|
||||||
|
controller.input.captures.clear();
|
||||||
|
if let Some(capture) = &controller.capture {
|
||||||
|
controller.input.set_handler_order([capture].into_iter());
|
||||||
|
controller.input.captures.add_raw(capture);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send input to all the input handlers that are the closest to the ray as possible
|
||||||
|
controller.input.set_handler_order(
|
||||||
|
INPUT_HANDLER_REGISTRY
|
||||||
|
.get_valid_contents()
|
||||||
|
.into_iter()
|
||||||
|
// filter out all the disabled handlers
|
||||||
|
.filter(|handler| {
|
||||||
|
let Some(node) = handler.spatial.node() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
node.enabled()
|
||||||
|
})
|
||||||
|
// filter out all the fields with disabled handlers
|
||||||
|
.filter(|handler| {
|
||||||
|
let Some(node) = handler.field.spatial.node() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
node.enabled()
|
||||||
|
})
|
||||||
|
// get the unsigned distance to the handler's field (unsigned so giant fields won't always eat input)
|
||||||
|
.map(|handler| {
|
||||||
|
(
|
||||||
|
vec![handler.clone()],
|
||||||
|
handler
|
||||||
|
.field
|
||||||
|
.distance(&controller.input.spatial, [0.0; 3].into())
|
||||||
|
.abs(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// now collect all handlers that are same distance if they're the closest
|
||||||
|
.reduce(|(mut handlers_a, distance_a), (handlers_b, distance_b)| {
|
||||||
|
if (distance_a - distance_b).abs() < 0.001 {
|
||||||
|
// distance is basically the same (within 1mm)
|
||||||
|
handlers_a.extend(handlers_b);
|
||||||
|
(handlers_a, distance_a)
|
||||||
|
} else if distance_a < distance_b {
|
||||||
|
(handlers_a, distance_a)
|
||||||
|
} else {
|
||||||
|
(handlers_b, distance_b)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(rx, _)| rx)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_controllers(
|
||||||
|
connection: Res<DbusConnection>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
session: Res<OxrSession>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
) {
|
||||||
|
let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset("embedded://cursor.glb"));
|
||||||
|
for handed in [HandSide::Left, HandSide::Right] {
|
||||||
|
let side = match handed {
|
||||||
|
HandSide::Left => "left",
|
||||||
|
HandSide::Right => "right",
|
||||||
|
};
|
||||||
|
let (spatial, object_handle) = SpatialRef::create(
|
||||||
|
&connection,
|
||||||
|
&("/org/stardustxr/Controller/".to_string() + side),
|
||||||
|
);
|
||||||
|
let tip = InputDataType::Tip(Tip::default());
|
||||||
|
let Ok(input) = (|| -> color_eyre::Result<Arc<InputMethod>> {
|
||||||
|
Ok(InputMethod::add_to(
|
||||||
|
&spatial.node().unwrap(),
|
||||||
|
tip,
|
||||||
|
Datamap::from_typed(ControllerDatamap::default())?,
|
||||||
|
)?)
|
||||||
|
})() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let actions = {
|
||||||
|
let set = session
|
||||||
|
.instance()
|
||||||
|
.create_action_set(
|
||||||
|
&format!("controller-{side}"),
|
||||||
|
&format!("{side} controller"),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
Actions {
|
||||||
|
set: set.clone(),
|
||||||
|
trigger: set
|
||||||
|
.create_action(&format!("trigger-{side}"), &format!("{side} trigger"), &[])
|
||||||
|
.unwrap(),
|
||||||
|
grip: set
|
||||||
|
.create_action(&format!("grip-{side}"), &format!("{side} grip"), &[])
|
||||||
|
.unwrap(),
|
||||||
|
stick: set
|
||||||
|
.create_action(&format!("stick-{side}"), &format!("{side} stick"), &[])
|
||||||
|
.unwrap(),
|
||||||
|
pose: set
|
||||||
|
.create_action(&format!("pose-{side}"), &format!("{side} pose"), &[])
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cmds.spawn((
|
||||||
|
SceneRoot(handle.clone()),
|
||||||
|
SkController {
|
||||||
|
object_handle,
|
||||||
|
input,
|
||||||
|
handed,
|
||||||
|
material: OnceCell::new(),
|
||||||
|
capture: None,
|
||||||
|
datamap: Default::default(),
|
||||||
|
space: actions
|
||||||
|
.pose
|
||||||
|
.create_space(
|
||||||
|
session.deref().deref().clone(),
|
||||||
|
openxr::Path::NULL,
|
||||||
|
Posef::IDENTITY,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
actions,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
#[require(Transform)]
|
||||||
pub struct SkController {
|
pub struct SkController {
|
||||||
object_handle: ObjectHandle<SpatialRef>,
|
object_handle: ObjectHandle<SpatialRef>,
|
||||||
input: Arc<InputMethod>,
|
input: Arc<InputMethod>,
|
||||||
handed: HandSide,
|
handed: HandSide,
|
||||||
color: bevy::color::Color,
|
material: OnceCell<Handle<DefaultMaterial>>,
|
||||||
capture: Option<Arc<InputHandler>>,
|
capture: Option<Arc<InputHandler>>,
|
||||||
datamap: ControllerDatamap,
|
datamap: ControllerDatamap,
|
||||||
|
space: openxr::Space,
|
||||||
|
actions: Actions,
|
||||||
}
|
}
|
||||||
impl SkController {
|
struct Actions {
|
||||||
pub fn new(connection: &Connection, handed: Handed) -> Result<Self> {
|
set: openxr::ActionSet,
|
||||||
let (spatial, object_handle) = SpatialRef::create(
|
trigger: openxr::Action<f32>,
|
||||||
connection,
|
grip: openxr::Action<f32>,
|
||||||
&("/org/stardustxr/Controller/".to_string()
|
stick: openxr::Action<openxr::Vector2f>,
|
||||||
+ match handed {
|
pose: openxr::Action<openxr::Posef>,
|
||||||
Handed::Left => "left",
|
|
||||||
_ => "right",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let model = Model::copy(Model::from_memory(
|
|
||||||
"cursor.glb",
|
|
||||||
include_bytes!("cursor.glb"),
|
|
||||||
None,
|
|
||||||
)?);
|
|
||||||
let model_nodes = model.get_nodes();
|
|
||||||
let mut model_node = model_nodes.visuals().next().unwrap();
|
|
||||||
let material = Material::copy(&model_node.get_material().unwrap());
|
|
||||||
model_node.material(&material);
|
|
||||||
let tip = InputDataType::Tip(Tip::default());
|
|
||||||
let input = InputMethod::add_to(
|
|
||||||
&spatial.node().unwrap(),
|
|
||||||
tip,
|
|
||||||
Datamap::from_typed(ControllerDatamap::default())?,
|
|
||||||
)?;
|
|
||||||
Ok(SkController {
|
|
||||||
object_handle,
|
|
||||||
input,
|
|
||||||
handed,
|
|
||||||
model,
|
|
||||||
material,
|
|
||||||
capture_manager: CaptureManager::default(),
|
|
||||||
datamap: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn update(&mut self, token: &MainThreadToken) {
|
|
||||||
let controller = Input::controller(self.handed);
|
|
||||||
let input_node = self.input.spatial.node().unwrap();
|
|
||||||
input_node.set_enabled(controller.tracked.is_active());
|
|
||||||
if input_node.enabled() {
|
|
||||||
let world_transform = Mat4::from_rotation_translation(
|
|
||||||
controller.aim.orientation.into(),
|
|
||||||
controller.aim.position.into(),
|
|
||||||
);
|
|
||||||
self.material
|
|
||||||
.color_tint(if self.capture_manager.capture.is_none() {
|
|
||||||
Color128::new_rgb(1.0, 1.0, 1.0)
|
|
||||||
} else {
|
|
||||||
Color128::new_rgb(0.0, 1.0, 0.75)
|
|
||||||
});
|
|
||||||
self.model.draw(
|
|
||||||
token,
|
|
||||||
world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
self.input.spatial.set_local_transform(world_transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.datamap = ControllerDatamap {
|
|
||||||
select: controller.trigger,
|
|
||||||
middle: controller.stick_click.is_active() as u32 as f32,
|
|
||||||
context: controller.is_x2_pressed() as u32 as f32,
|
|
||||||
grab: controller.grip,
|
|
||||||
scroll: controller.stick.into(),
|
|
||||||
};
|
|
||||||
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).unwrap();
|
|
||||||
|
|
||||||
let distance_calculator = |space: &Arc<Spatial>, _data: &InputDataType, field: &Field| {
|
|
||||||
Some(field.distance(space, [0.0; 3].into()).abs())
|
|
||||||
};
|
|
||||||
|
|
||||||
self.capture_manager.update_capture(&self.input);
|
|
||||||
self.capture_manager
|
|
||||||
.set_new_capture(&self.input, distance_calculator);
|
|
||||||
self.capture_manager.apply_capture(&self.input);
|
|
||||||
|
|
||||||
if self.capture_manager.capture.is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sorted_handlers = get_sorted_handlers(&self.input, distance_calculator);
|
|
||||||
self.input.set_handler_order(sorted_handlers.iter());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user