diff --git a/Cargo.toml b/Cargo.toml index 600a0a3..610a4a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,14 @@ path = "src/main.rs" profile_tokio = ["dep:console-subscriber", "tokio/tracing"] profile_app = ["dep:tracing-tracy"] +# Enable a small amount of optimization in the dev profile. +[profile.dev] +opt-level = 1 + +# Enable a large amount of optimization in the dev profile for dependencies. +[profile.dev.package."*"] +opt-level = 3 + [package.metadata.appimage] auto_link = true auto_link_exclude_list = [ diff --git a/src/bevy_plugin.rs b/src/bevy_plugin.rs index 31f9c91..3434006 100644 --- a/src/bevy_plugin.rs +++ b/src/bevy_plugin.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use bevy::{ app::MainScheduleOrder, ecs::schedule::{ExecutorKind, ScheduleLabel}, @@ -6,13 +8,25 @@ use bevy::{ }; use bevy_mod_openxr::session::OxrSession; use bevy_mod_xr::session::{session_available, XrFirst, XrSessionCreated}; +use once_cell::sync::OnceCell; use openxr::ReferenceSpaceType; use stardust_xr::values::color::{color_space::LinearRgb, AlphaColor, Rgb}; -use crate::objects::Inputs; - pub struct StardustBevyPlugin; +pub static DESTROY_ENTITY: DestroySender = DestroySender(OnceCell::new()); + +pub struct DestroySender(OnceCell>); +impl Deref for DestroySender { + type Target = crossbeam_channel::Sender; + + fn deref(&self) -> &Self::Target { + self.0.get().unwrap() + } +} +#[derive(Resource, Deref)] +struct DestroyEntityReader(crossbeam_channel::Receiver); + #[derive(Resource, Deref)] pub struct DbusConnection(pub zbus::Connection); @@ -22,6 +36,12 @@ pub struct InputUpdate; pub struct StardustFirst; impl Plugin for StardustBevyPlugin { fn build(&self, app: &mut App) { + let (tx, rx) = crossbeam_channel::unbounded(); + DESTROY_ENTITY + .0 + .set(tx) + .expect("unable to set destroy entity sender, yell at schmarni pls thx"); + app.insert_resource(DestroyEntityReader(rx)); app.init_schedule(StardustExtract); let labels = &mut app.world_mut().resource_mut::().labels; info!("test: {labels:?}"); @@ -42,9 +62,24 @@ impl Plugin for StardustBevyPlugin { panic!("first schedule was not XrFirst!"); } labels.insert(0, (StardustFirst).intern()); + app.add_systems(First, yeet_entities); } } +fn yeet_entities( + mut cmds: Commands, + query: Query>, + reader: Res, +) { + query + .iter() + .for_each(|e| cmds.entity(e).despawn_recursive()); + reader + .0 + .try_iter() + .for_each(|e| cmds.entity(e).despawn_recursive()); +} + fn make_view_space(mut cmds: Commands, session: Res) { // idk what errors this function returns let view_space = session @@ -58,17 +93,6 @@ fn make_view_space(mut cmds: Commands, session: Res) { fn spawn_camera(mut cmds: Commands) { cmds.spawn((Camera3d::default(), ViewLocation)); } - -#[derive(Deref, DerefMut, Resource)] -pub struct BevyToStardustEvents(pub Vec); -pub enum BevyToStardustEvent { - InputsCreated(Inputs), - SessionDestroyed, - SessionEnding, - SessionCreated(OxrSession), - MainSessionVisible(bool), -} - pub trait StardustAabb3dExt { fn grown_box(&self, aabb: &Self, opt_box_transform: Option>) -> Self; fn grown_point(&self, pt: impl Into) -> Self; diff --git a/src/main.rs b/src/main.rs index 8ae6e12..b607fbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,15 +12,16 @@ use crate::core::destroy_queue; // use crate::nodes::items::camera; use crate::nodes::{audio, drawable, input}; -use bevy::app::{App, PluginGroup, PluginsState, Startup, Update}; +use bevy::app::{App, PluginGroup, PluginsState, PostUpdate, Startup, Update}; use bevy::asset::{AssetServer, Handle}; +use bevy::color::Color; use bevy::core_pipeline::Skybox; use bevy::image::Image; use bevy::log::LogPlugin; use bevy::pbr::StandardMaterial; use bevy::prelude::{ on_event, resource_added, Camera3d, ClearColor, Commands, Entity, EventReader, - IntoSystemConfigs, Local, Query, Res, Resource, With, World, + IntoSystemConfigs, Local, Query, Res, ResMut, Resource, Transform, With, World, }; use bevy::render::pipelined_rendering::PipelinedRenderingPlugin; use bevy::time::Time; @@ -34,9 +35,11 @@ use bevy_mod_openxr::init::{should_run_frame_loop, OxrInitPlugin}; use bevy_mod_openxr::render::{update_cameras, OxrRenderPlugin}; use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrGraphicsInfo}; use bevy_mod_openxr::session::OxrSession; +use bevy_mod_openxr::spaces::OxrSpaceExt; 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, XrPreDestroySession, XrSessionCreated, XrSessionPlugin}; +use bevy_mod_xr::spaces::XrPrimaryReferenceSpace; use bevy_plugin::{DbusConnection, InputUpdate, StardustBevyPlugin, StardustFirst}; use clap::Parser; use core::client::Client; @@ -47,6 +50,7 @@ use nodes::drawable::lines::BevyLinesPlugin; use nodes::drawable::model::StardustModelPlugin; use nodes::drawable::text::StardustTextPlugin; use objects::input::sk_controller::StardustControllerPlugin; +use objects::input::sk_hand::StardustHandPlugin; use objects::ServerObjects; use once_cell::sync::OnceCell; use openxr::OverlaySessionCreateFlagsEXTX; @@ -260,7 +264,7 @@ fn stereokit_loop( exts }, blend_modes: Some(vec![ - // openxr::EnvironmentBlendMode::ALPHA_BLEND, + openxr::EnvironmentBlendMode::ALPHA_BLEND, openxr::EnvironmentBlendMode::OPAQUE, ]), synchronous_pipeline_compilation: false, @@ -283,6 +287,7 @@ fn stereokit_loop( bevy_app.add_plugins(( BevyLinesPlugin, StardustModelPlugin, + StardustHandPlugin, StardustTextPlugin, StardustSoundPlugin, StardustControllerPlugin, @@ -326,10 +331,36 @@ fn stereokit_loop( cams.iter().for_each(|e| { cmds.entity(e).remove::(); }); + cmds.insert_resource(ClearColor(Color::NONE)); } *last_hidden = env_hidden; } bevy_app.add_systems(XrSessionCreated, update_background); + bevy_app.add_systems( + PostUpdate, + (|mut objects: ResMut, + ref_space: Res, + session: Res| { + objects + .ref_space + .replace(unsafe { ref_space.as_openxr_space(&session) }); + objects.view_space.replace( + session + .deref() + .deref() + .create_reference_space( + openxr::ReferenceSpaceType::VIEW, + openxr::Posef::IDENTITY, + ) + .unwrap(), + ); + }) + .run_if(on_event::), + ); + bevy_app.add_systems(XrPreDestroySession, |mut objetcs: ResMut| { + objetcs.ref_space = None; + objetcs.view_space = None; + }); bevy_app.add_systems( Update, update_background.run_if(on_event::), diff --git a/src/nodes/audio.rs b/src/nodes/audio.rs index 369f748..5224f18 100644 --- a/src/nodes/audio.rs +++ b/src/nodes/audio.rs @@ -163,8 +163,18 @@ impl Drop for Sound { } } +<<<<<<< HEAD +======= +<<<<<<< HEAD +create_interface!(AudioInterface); +>>>>>>> 0ec4c0f (refactor: get models fully working) struct AudioInterface; impl InterfaceAspect for AudioInterface { +======= +pub fn update() {} + +impl InterfaceAspect for Interface { +>>>>>>> 2f5c92d (refactor: get models fully working) #[doc = "Create a sound node. WAV and MP3 are supported."] fn create_sound( _node: Arc, diff --git a/src/nodes/drawable/lines.rs b/src/nodes/drawable/lines.rs index 4d3fa77..e522a29 100644 --- a/src/nodes/drawable/lines.rs +++ b/src/nodes/drawable/lines.rs @@ -169,7 +169,7 @@ pub fn draw_all( let mat_handle = materials.add(material); for lines in LINES_REGISTRY.get_valid_contents() { if let Some(node) = lines.space.node() { - if node.enabled() { + if node.enabled() && !lines.data.lock().is_empty() { // Does this rebuild the mesh every frame? yes, is this problematic? probably, // would a shader work better? yes, do i care? not right now let mut mesh = Mesh::new( diff --git a/src/nodes/drawable/model.rs b/src/nodes/drawable/model.rs index c03e518..5c58cc7 100644 --- a/src/nodes/drawable/model.rs +++ b/src/nodes/drawable/model.rs @@ -1,6 +1,6 @@ use super::{MaterialParameter, ModelAspect, ModelPartAspect, MODEL_PART_ASPECT_ALIAS_INFO}; use crate::bail; -use crate::bevy_plugin::MainWorldEntity; +use crate::bevy_plugin::{MainWorldEntity, DESTROY_ENTITY}; use crate::core::client::Client; use crate::core::error::Result; use crate::core::registry::Registry; @@ -8,8 +8,8 @@ use crate::core::resource::get_resource_file; use crate::nodes::alias::{Alias, AliasList}; use crate::nodes::spatial::Spatial; use crate::nodes::Node; -use crate::DefaultMaterial; -use bevy::app::{Plugin, PostUpdate}; +use crate::{DefaultMaterial, TOKIO}; +use bevy::app::{Plugin, PostUpdate, PreUpdate, Update}; use bevy::asset::{AssetServer, Assets}; use bevy::color::{Alpha, Color, LinearRgba, Srgba}; use bevy::core::Name; @@ -18,23 +18,27 @@ use bevy::math::bounding::Aabb3d; use bevy::pbr::MeshMaterial3d; use bevy::prelude::AlphaMode; use bevy::prelude::{ - Children, Commands, Component, Deref, Entity, HierarchyQueryExt, Mesh3d, Parent, Query, Res, - ResMut, Resource, Transform, Visibility, With, Without, + BuildChildrenTransformExt, Children, Commands, Component, Deref, Entity, Has, + HierarchyQueryExt, Mesh3d, Parent, Query, Res, ResMut, Resource, Transform, Visibility, With, + Without, }; use bevy::reflect::{GetField, PartialReflect, Reflect}; use bevy::render::primitives::Aabb; use bevy::scene::SceneRoot; use color_eyre::eyre::eyre; -use glam::{Vec2, Vec3}; +use bevy::tasks::futures_lite::FutureExt; +use glam::{Mat4, Vec2, Vec3}; use once_cell::sync::OnceCell; use parking_lot::Mutex; use rustc_hash::FxHashMap; use stardust_xr::values::ResourceID; -use tracing::{error, warn}; +use tokio::sync::Notify; +use tracing::{error, info, warn}; use std::ffi::OsStr; +use std::future::IntoFuture; use std::ops::Deref; -use std::path::PathBuf; +use std::path::{self, PathBuf}; use std::sync::{Arc, Weak}; static MODEL_REGISTRY: Registry = Registry::new(); @@ -132,7 +136,7 @@ impl MaterialParameter { } pub struct ModelPart { - entity: MainWorldEntity, + entity: OnceCell, path: String, space: Arc, model: Weak, @@ -142,7 +146,7 @@ pub struct ModelPart { } #[derive(Component, Clone)] -pub struct StardustModel(Arc); +pub struct StardustModel(Weak); #[derive(Component, Clone)] pub struct UnprocessedModel; pub struct StardustModelPlugin; @@ -151,7 +155,10 @@ impl Plugin for StardustModelPlugin { let (tx, rx) = crossbeam_channel::unbounded(); LOAD_MODEL_SENDER.set(tx); app.insert_resource(LoadModelReader(rx)); - app.add_systems(PostUpdate, create_model_parts_for_loaded_models); + app.add_systems(Update, create_model_parts_for_loaded_models); + app.add_systems(PreUpdate, load_models); + app.add_systems(PostUpdate, update_models); + app.add_systems(PostUpdate, update_model_parts); } } static LOAD_MODEL_SENDER: OnceCell)>> = @@ -161,8 +168,11 @@ struct LoadModelReader(crossbeam_channel::Receiver<(PathBuf, Arc)>); fn update_models(mut query: Query<(&StardustModel, &mut Visibility, &mut Transform)>) { for (model, mut vis, mut transform) in query.iter_mut() { - *transform = Transform::from_matrix(model.0.space.global_transform()); - if let Some(node) = model.0.space.node() { + let Some(model) = model.0.upgrade() else { + continue; + }; + *transform = Transform::from_matrix(model.space.global_transform()); + if let Some(node) = model.space.node() { *vis = match node.enabled() { true => Visibility::Inherited, false => Visibility::Hidden, @@ -177,7 +187,7 @@ fn load_models(rx: Res, mut cmds: Commands, asset_server: Res, mut cmds: Commands, asset_server: Res, + models: Query<&StardustModel, Without>, mut mats: ResMut>, mut part_query: Query<( &mut Transform, &mut MeshMaterial3d, - &mut Mesh3d, + Has, )>, + mut cmds: Commands, ) { for model in &models { - let model = &model.0; + let Some(model) = model.0.upgrade() else { + continue; + }; for part in model.parts.lock().iter() { - let Ok((mut transform, mut mat, mut _mesh)) = part_query.get_mut(*part.entity) else { + let Some((entity, (mut transform, mut mat, has_parent))) = part + .entity + .get() + .and_then(|e| Some((*e, part_query.get_mut(*e).ok()?))) + else { continue; }; - *transform = Transform::from_matrix(Spatial::space_to_space_matrix( - Some(&part.space), - Some(&model.space), - )); - if let Some(material_replacement) = part.pending_material_replacement.lock().take() { - let material = material_replacement.deref().clone(); - let mat_handle = mats.add(material); - mat.0 = mat_handle; + if has_parent { + cmds.entity(entity).remove_parent_in_place(); } + *transform = Transform::from_matrix(part.space.global_transform()); + // todo: find all materials with identical parameters and batch them into 1 material again 'mat_params: { let mut material_parameters = part.pending_material_parameters.lock(); @@ -237,36 +250,41 @@ fn update_model_parts( fn get_path( entity: Entity, - query: &Query<(Entity, Option<&Parent>, &Transform, &Name, &Aabb), Without>, -) -> Option { - let (_, parent, _, name, _) = query.get(entity).ok()?; - let next = parent.and_then(|p| get_path(p.get(), query)); - match next { - Some(next) => Some(format!("{name}/{next}")), - None => Some(name.to_string()), - } + query: &Query<(&Parent, &Name), Without>, + mut in_vec: Vec, +) -> Vec { + let Ok((parent, name)) = query.get(entity) else { + return in_vec; + }; + in_vec.push(name.to_string()); + get_path(parent.get(), query, in_vec) } fn create_model_parts_for_loaded_models( - query: Query<(Entity, &StardustModel), With>, + query: Query<(Entity, &StardustModel), (With, With)>, children: Query<&Children>, - gltf_model_parts: Query< - (Entity, Option<&Parent>, &Transform, &Name, &Aabb), - Without, - >, + gltf_model_parts: Query<(Entity, &Transform, &Aabb), Without>, + name_query: Query<(&Parent, &Name), Without>, mut cmds: Commands, ) { for (entity, model) in &query { - let model = &model.0; - let mut parts = model.parts.lock(); - cmds.entity(entity).remove::(); - for (entity, parent, transform, name, aabb) in children - .iter_descendants(entity) + info!("creating parts!"); + let Some(model) = model.0.upgrade() else { + continue; + }; + // let mut parts = model.parts.lock(); + let mut parts = Vec::>::new(); + for (entity, transform, aabb) in children + .iter_descendants_depth_first(entity) .filter_map(|e| gltf_model_parts.get(e).ok()) { - let parent_part = parent - .and_then(|e| gltf_model_parts.get(e.get()).ok()) - .and_then(|(e, _, _, _, _)| parts.iter().find(|v| v.entity.0 == e)); + let mut path_parts = get_path(entity, &name_query, Vec::new()); + path_parts.remove(0); + path_parts.reverse(); + let part_path = path_parts.join("/"); + path_parts.pop(); + let parent_path = path_parts.join("/"); + let parent_part = parts.iter().find(|v| v.path == parent_path); let Some(stardust_model_part) = model.space.node() else { continue; @@ -274,22 +292,51 @@ fn create_model_parts_for_loaded_models( let Some(client) = stardust_model_part.get_client() else { continue; }; - let part_path = get_path(entity, &gltf_model_parts).unwrap_or_else(|| name.to_string()); + let model_part = model + .parts + .lock() + .iter() + .find(|v| v.path == part_path) + .cloned() + .map(|v| { + *v.space.bounding_box_calc.lock() = Aabb3d::new(aabb.center, aabb.half_extents); + if v.entity.set(entity).is_err() { + error!( + "trying to set entity for already init model part?! + please yell at schmarni if you see this" + ); + }; - let node = client.scenegraph.add_node(Node::generate(&client, false)); - let spatial_parent = parent_part - .map(|n| n.space.clone()) - .unwrap_or_else(|| model.space.clone()); + if let Err(err) = v.space.set_spatial_parent(Some( + parent_part.map(|n| &n.space).unwrap_or_else(|| { + info!("model is spatial parent"); + &model.space + }), + )) { + error!("error setting spatial parent for existing model part: {err}"); + } + v.space.set_local_transform(transform.compute_matrix()); + info!("not fresh {}", &v.path); + v + }) + .unwrap_or_else(|| { + let node = client.scenegraph.add_node(Node::generate(&client, false)); + let spatial_parent = + parent_part.map(|n| n.space.clone()).unwrap_or_else(|| { + info!("model is spatial parent"); + model.space.clone() + }); - let space = Spatial::add_to( - &node, - Some(spatial_parent), - transform.compute_matrix(), - false, - ); + let space = Spatial::add_to( + &node, + Some(spatial_parent), + transform.compute_matrix(), + false, + ); - *space.bounding_box_calc.lock() = Aabb3d::new(aabb.center, aabb.half_extents); + *space.bounding_box_calc.lock() = Aabb3d::new(aabb.center, aabb.half_extents); +<<<<<<< HEAD let model_part = Arc::new(ModelPart { entity: MainWorldEntity(entity), path: part_path, @@ -300,8 +347,26 @@ fn create_model_parts_for_loaded_models( aliases: AliasList::default(), }); node.add_aspect_raw(model_part.clone()); +======= + let model_part = Arc::new(ModelPart { + entity: OnceCell::from(entity), + path: part_path, + space, + model: Arc::downgrade(&model), + pending_material_parameters: Mutex::new(FxHashMap::default()), + pending_material_replacement: Mutex::new(None), + aliases: AliasList::default(), + }); + node.add_aspect_raw(model_part.clone()); + info!("fresh {}", &model_part.path); + model_part + }); +>>>>>>> 0ec4c0f (refactor: get models fully working) parts.push(model_part.clone()); } + cmds.entity(entity).remove::(); + info!("created parts! {}", parts.len()); + *model.parts.lock() = parts; } } @@ -375,11 +440,43 @@ impl ModelAspect for Model { part_path: String, ) -> Result<()> { let model = node.get_aspect::()?; +<<<<<<< HEAD let parts = model.parts.lock(); let Some(part) = parts.iter().find(|p| p.path == part_path) else { let paths = parts.iter().map(|p| &p.path).collect::>(); bail!("Couldn't find model part at path {part_path}, all available paths: {paths:?}",); }; +======= + let mut parts = model.parts.lock(); + let part = + parts + .iter() + .find(|p| p.path == part_path) + .cloned() + .unwrap_or_else(|| { + let paths = parts.iter().map(|p| &p.path).collect::>(); + error!("Couldn't find model part at path {part_path}, all available paths: {paths:?}"); + + let node = calling_client + .scenegraph + .add_node(Node::generate(&calling_client, false)); + + let space = Spatial::add_to(&node, None, Mat4::IDENTITY, false); + + let model_part = Arc::new(ModelPart { + entity: OnceCell::new(), + path: part_path, + space, + model: Arc::downgrade(&model), + pending_material_parameters: Mutex::new(FxHashMap::default()), + pending_material_replacement: Mutex::new(None), + aliases: AliasList::default(), + }); + node.add_aspect_raw(model_part.clone()); + parts.push(model_part.clone()); + model_part + }); +>>>>>>> 0ec4c0f (refactor: get models fully working) Alias::create_with_id( &part.space.node().unwrap(), &calling_client, @@ -390,8 +487,18 @@ impl ModelAspect for Model { Ok(()) } } +impl Drop for ModelPart { + fn drop(&mut self) { + if let Some(e) = self.entity.get() { + _ = DESTROY_ENTITY.send(*e); + } + } +} impl Drop for Model { fn drop(&mut self) { + if let Some(e) = self.entity.get() { + _ = DESTROY_ENTITY.send(*e); + } MODEL_REGISTRY.remove(self); } } diff --git a/src/objects/input/sk_hand.rs b/src/objects/input/sk_hand.rs index 2a64d6a..143c928 100644 --- a/src/objects/input/sk_hand.rs +++ b/src/objects/input/sk_hand.rs @@ -10,6 +10,7 @@ use crate::nodes::{ }; use crate::objects::{ObjectHandle, SpatialRef}; use crate::DefaultMaterial; +use bevy::app::{Plugin, PostUpdate}; use bevy::asset::{AssetServer, Assets, Handle}; use bevy::prelude::{Commands, Component, Entity, Query, Res, ResMut}; use bevy_mod_openxr::helper_traits::{ToQuat, ToVec3}; @@ -17,6 +18,7 @@ use bevy_mod_openxr::resources::OxrFrameState; use bevy_mod_openxr::session::OxrSession; use bevy_mod_openxr::spaces::OxrSpaceLocationFlags; use bevy_mod_xr::hands::{HandBone, HandSide}; +use bevy_mod_xr::session::XrSessionCreated; use bevy_mod_xr::spaces::XrPrimaryReferenceSpace; use color_eyre::eyre::Result; use glam::{Mat4, Quat, Vec3}; @@ -34,6 +36,14 @@ fn update_joint(joint: &mut Joint, oxr_joint: openxr::HandJointLocation) { } } +pub struct StardustHandPlugin; +impl Plugin for StardustHandPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + app.add_systems(XrSessionCreated, create_hands); + app.add_systems(PostUpdate, update_hands); + } +} + fn update_hands( mut mats: ResMut>, mut query: Query<&mut SkHand>, @@ -76,7 +86,7 @@ fn update_hands( update_joint(&mut finger.distal, joints[finger_index + 3]); update_joint(&mut finger.intermediate, joints[finger_index + 2]); update_joint(&mut finger.proximal, joints[finger_index + 1]); - update_joint(&mut finger.metacarpal, joints[finger_index + 0]); + update_joint(&mut finger.metacarpal, joints[finger_index]); // Why? finger.tip.radius = 0.0; } diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 430bab7..52aecee 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -20,6 +20,7 @@ use openxr::SpaceLocationFlags; use play_space::PlaySpaceBounds; use stardust_xr::schemas::dbus::object_registry::ObjectRegistry; use std::{marker::PhantomData, sync::Arc}; +use tracing::info; use zbus::{interface, object_server::Interface, zvariant::OwnedObjectPath, Connection}; pub mod input; @@ -54,8 +55,8 @@ pub struct ServerObjects { hmd: (Arc, ObjectHandle), play_space: Option<(Arc, ObjectHandle)>, inputs: Option, - view_space: Option, - ref_space: Option, + pub view_space: Option, + pub ref_space: Option, } pub struct TrackingRefs { @@ -125,7 +126,7 @@ impl ServerObjects { } } - pub fn update( + pub(crate) fn update( &mut self, session: Option<&openxr::Session>, time: Option,