diff --git a/Cargo.lock b/Cargo.lock index f621a25..102fd51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,6 +618,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b8369c16b7c017437021341521f8b4a0d98e1c70113fb358c3258ae7d661d79" dependencies = [ "bevy_internal", + "tracing", +] + +[[package]] +name = "bevy-mesh-text-3d" +version = "0.1.0" +source = "git+https://github.com/terhechte/bevy-mesh-text-3d#77d8ae6935af636bcb85251b1debbc7f3d618f74" +dependencies = [ + "bevy", + "cosmic-text 0.14.2", + "lyon", + "thiserror 2.0.12", ] [[package]] @@ -686,6 +698,7 @@ dependencies = [ "downcast-rs 2.0.1", "log", "thiserror 2.0.12", + "tracing", "variadics_please", "wasm-bindgen", "web-sys", @@ -861,6 +874,7 @@ dependencies = [ "serde", "smallvec", "thiserror 2.0.12", + "tracing", "variadics_please", ] @@ -1092,9 +1106,11 @@ dependencies = [ "bevy_ecs", "bevy_utils", "tracing", + "tracing-error", "tracing-log", "tracing-oslog", "tracing-subscriber", + "tracing-tracy", "tracing-wasm", ] @@ -1165,16 +1181,6 @@ dependencies = [ "glam", ] -[[package]] -name = "bevy_mod_meshtext" -version = "0.1.0" -source = "git+https://github.com/Schmarni-Dev/bevy_mod_meshtext#b623971b5a4d0ee0a93a2f89096726b9b7c9c87a" -dependencies = [ - "atomicow", - "bevy", - "meshtext", -] - [[package]] name = "bevy_mod_openxr" version = "0.3.0" @@ -1388,11 +1394,13 @@ dependencies = [ "naga_oil", "nonmax", "offset-allocator", + "profiling", "send_wrapper", "serde", "smallvec", "thiserror 2.0.12", "tracing", + "tracy-client", "variadics_please", "wasm-bindgen", "web-sys", @@ -1542,7 +1550,7 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "cosmic-text", + "cosmic-text 0.13.2", "serde", "smallvec", "sys-locale", @@ -1913,16 +1921,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cdt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91b872294f63ef586b32aa94141561681aa35ca2d703960cca4f661f4e18184" -dependencies = [ - "geometry-predicates", - "thiserror 1.0.69", -] - [[package]] name = "cesu8" version = "1.1.0" @@ -2283,6 +2281,29 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cosmic-text" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8" +dependencies = [ + "bitflags 2.9.1", + "fontdb", + "log", + "rangemap", + "rustc-hash 1.1.0", + "rustybuzz", + "self_cell", + "smol_str", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "cpal" version = "0.15.3" @@ -2809,6 +2830,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fnv" version = "1.0.7" @@ -2975,12 +3002,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "geometry-predicates" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dba84198cf199c11b83f1cb9243eaeb70dc50b719d2835ebf34bf2481bca010" - [[package]] name = "gethostname" version = "0.4.3" @@ -3861,6 +3882,58 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0047f508cd7a85ad6bad9518f68cce7b1bf6b943fb71f6da0ee3bc1e8cb75f25" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + [[package]] name = "mach2" version = "0.4.2" @@ -3930,18 +4003,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "meshtext" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c68541f06674ef4bb1f84e3dca654998135ff1a8bb5a60cfbfe5b8615f8e555" -dependencies = [ - "cdt", - "glam", - "owned_ttf_parser", - "ttf-parser 0.25.1", -] - [[package]] name = "metal" version = "0.31.0" @@ -4959,6 +5020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", + "tracing", ] [[package]] @@ -5729,13 +5791,14 @@ name = "stardust-xr-server" version = "0.45.0" dependencies = [ "bevy", - "bevy_mod_meshtext", + "bevy-mesh-text-3d", "bevy_mod_openxr", "bevy_mod_xr", "bevy_sk", "clap", "color-eyre", "console-subscriber", + "cosmic-text 0.14.2", "dashmap", "directories", "glam", @@ -6339,8 +6402,7 @@ dependencies = [ [[package]] name = "tracing-tracy" version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" +source = "git+https://github.com/nagisa/rust_tracy_client?tag=tracy-client-v0.18.1#7d084307f54959999ca3729c42bd4f4f3f0df54d" dependencies = [ "tracing-core", "tracing-subscriber", @@ -6361,8 +6423,7 @@ dependencies = [ [[package]] name = "tracy-client" version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3927832d93178f979a970d26deed7b03510586e328f31b0f9ad7a73985b8332a" +source = "git+https://github.com/nagisa/rust_tracy_client?tag=tracy-client-v0.18.1#7d084307f54959999ca3729c42bd4f4f3f0df54d" dependencies = [ "loom", "once_cell", @@ -6372,11 +6433,10 @@ dependencies = [ [[package]] name = "tracy-client-sys" version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c032d68a49d25d9012a864fef1c64ac17aee43c87e0477bf7301d8ae8bfea7b7" +source = "git+https://github.com/nagisa/rust_tracy_client?tag=tracy-client-v0.18.1#7d084307f54959999ca3729c42bd4f4f3f0df54d" dependencies = [ "cc", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index df31df4..392f90a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,8 @@ path = "src/main.rs" default = [] wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"] profile_tokio = ["dep:console-subscriber", "tokio/tracing"] -profile_app = ["dep:tracing-tracy"] +profile_app = ["dep:tracing-tracy", "bevy/trace_tracy"] +change_tracking = ["bevy/track_location"] [package.metadata.appimage] auto_link = true @@ -81,9 +82,10 @@ tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] } bevy = { version = "0.16", features = ["wayland", "bevy_remote"] } bevy_mod_xr = "0.3" bevy_mod_openxr = "0.3" -bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext" +# bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext" # bevy_sk.git = "https://github.com/MalekiRe/bevy_sk" # bevy_sk = { git = "https://github.com/Schmarni-Dev/bevy_sk", branch = "fix_mat_stuff" } +bevy-mesh-text-3d.git = "https://github.com/terhechte/bevy-mesh-text-3d" bevy_sk.path = "../bevy_sk" openxr = "0.19" @@ -97,6 +99,7 @@ xkbcommon-rs = "0.1.0" # wayland wayland-backend = { version = "0.3.7", optional = true, default-features = false } wayland-scanner = { version = "0.31.4", optional = true } +cosmic-text = "0.14.2" [dependencies.smithay] diff --git a/src/core/mod.rs b/src/core/mod.rs index 594fe29..d2385de 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -7,3 +7,4 @@ pub mod registry; pub mod resource; pub mod scenegraph; pub mod task; +pub mod color; diff --git a/src/main.rs b/src/main.rs index 970a8db..2edb0bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,28 +12,25 @@ use crate::nodes::{audio, drawable, input}; use bevy::MinimalPlugins; use bevy::a11y::AccessibilityPlugin; -use bevy::app::{App, MainScheduleOrder, ScheduleRunnerPlugin, TerminalCtrlCHandlerPlugin}; +use bevy::app::{App, TerminalCtrlCHandlerPlugin}; use bevy::asset::{AssetMetaCheck, UnapprovedPathMode}; use bevy::audio::AudioPlugin; use bevy::core_pipeline::CorePipelinePlugin; +use bevy::diagnostic::DiagnosticsPlugin; use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel}; use bevy::gizmos::GizmoPlugin; use bevy::gltf::GltfPlugin; -use bevy::input::{InputPlugin, InputSystem}; +use bevy::input::InputPlugin; use bevy::pbr::PbrPlugin; use bevy::remote::RemotePlugin; use bevy::remote::http::RemoteHttpPlugin; use bevy::render::{RenderDebugFlags, RenderPlugin}; use bevy::scene::ScenePlugin; use bevy::text::FontLoader; -use bevy::winit::{WakeUp, WinitPlugin}; -use bevy_mod_meshtext::MeshTextPlugin; -use bevy_mod_openxr::action_binding::OxrActionBindingPlugin; use bevy_mod_openxr::action_set_attaching::OxrActionAttachingPlugin; use bevy_mod_openxr::action_set_syncing::OxrActionSyncingPlugin; use bevy_mod_openxr::add_xr_plugins; use bevy_mod_openxr::exts::OxrExtensions; -use bevy_mod_openxr::features::handtracking::HandTrackingPlugin; use bevy_mod_openxr::features::overlay::OxrOverlaySettings; use bevy_mod_openxr::features::passthrough::OxrPassthroughPlugin; use bevy_mod_openxr::init::{OxrInitPlugin, should_run_frame_loop}; @@ -42,12 +39,13 @@ use bevy_mod_openxr::render::{OxrRenderPlugin, OxrWaitFrameSystem}; use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrSessionConfig}; use bevy_mod_openxr::types::AppInfo; use bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin; -use bevy_mod_xr::session::{XrFirst, XrHandleEvents, session_running}; +use bevy_mod_xr::session::{XrFirst, XrHandleEvents}; use clap::Parser; use core::client::{Client, tick_internal_client}; use core::task; use directories::ProjectDirs; use nodes::drawable::model::ModelNodePlugin; +use nodes::drawable::text::TextNodePlugin; use nodes::spatial::SpatialNodePlugin; use objects::ServerObjects; use objects::input::sk_controller::ControllerPlugin; @@ -272,8 +270,7 @@ fn bevy_loop( ..default() }); let mut plugins = MinimalPlugins - .build() - // .disable::() + .build().add(DiagnosticsPlugin) .add(TransformPlugin) .add(InputPlugin) /* .add(AccessibilityPlugin) */; @@ -354,6 +351,7 @@ fn bevy_loop( .disable::() .disable::(), ); + // font size is in meters app.add_plugins(( bevy_sk::hand::HandPlugin, bevy_sk::vr_materials::SkMaterialPlugin { @@ -363,7 +361,7 @@ fn bevy_loop( )); app.add_plugins(HandGizmosPlugin); // app.add_plugins(MeshTextPlugin); - // app.init_asset::().init_asset_loader::(); + app.init_asset::().init_asset_loader::(); if let Some(priority) = args.overlay_priority { app.insert_resource(OxrOverlaySettings { session_layer_placement: priority, @@ -388,6 +386,7 @@ fn bevy_loop( app.add_plugins(( SpatialNodePlugin, ModelNodePlugin, + TextNodePlugin, PlaySpacePlugin, HandPlugin, ControllerPlugin, @@ -401,6 +400,17 @@ fn bevy_loop( .in_set(OxrWaitFrameSystem) .in_set(XrHandleEvents::FrameLoop), ); + use std::process::Command; + + let output = Command::new("fc-match") + .arg("-f") + .arg("%{file}") + .output() + .expect("Failed to run fc-match"); + + let font_path = String::from_utf8_lossy(&output.stdout); + info!("Default font path: {}", font_path); + app.run(); } diff --git a/src/nodes/drawable/mod.rs b/src/nodes/drawable/mod.rs index 58f64d6..a1ec88e 100644 --- a/src/nodes/drawable/mod.rs +++ b/src/nodes/drawable/mod.rs @@ -23,7 +23,6 @@ use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap}; // #[instrument(level = "debug", skip(sk))] pub fn draw(token: &MainThreadToken) { lines::draw_all(token); - text::draw_all(token); match QUEUED_SKYTEX.lock().take() { Some(Some(skytex)) => { if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) { diff --git a/src/nodes/drawable/model.rs b/src/nodes/drawable/model.rs index e893e05..2671156 100644 --- a/src/nodes/drawable/model.rs +++ b/src/nodes/drawable/model.rs @@ -1,6 +1,7 @@ use super::{MODEL_PART_ASPECT_ALIAS_INFO, MaterialParameter, ModelAspect, ModelPartAspect}; use crate::bail; use crate::core::client::Client; +use crate::core::color::ColorConvert as _; use crate::core::error::Result; use crate::core::registry::Registry; use crate::core::resource::get_resource_file; @@ -27,9 +28,13 @@ impl Plugin for ModelNodePlugin { fn build(&self, app: &mut App) { let (tx, rx) = mpsc::unbounded_channel(); LOAD_MODEL.set(tx).unwrap(); + app.init_resource::(); app.insert_resource(MpscReceiver(rx)); app.add_systems(Update, load_models); - app.add_systems(PostUpdate, (gen_model_parts, apply_materials).chain()); + app.add_systems( + PostUpdate, + (gen_model_parts, apply_materials, update_visibillity).chain(), + ); } } @@ -38,6 +43,24 @@ struct MpscReceiver(mpsc::UnboundedReceiver); #[derive(Component)] struct ModelNode(Weak); +fn update_visibillity(mut cmds: Commands) { + for model in MODEL_REGISTRY.get_valid_contents().into_iter() { + let Some(entity) = model.bevy_scene_entity.get().copied() else { + continue; + }; + match model.spatial.node().map(|n| n.enabled()).unwrap_or(false) { + true => { + cmds.entity(entity) + .insert_recursive::(Visibility::Visible); + } + false => { + cmds.entity(entity) + .insert_recursive::(Visibility::Hidden); + } + } + } +} + fn load_models( asset_server: Res, mut cmds: Commands, @@ -50,7 +73,7 @@ fn load_models( .spawn(( SceneRoot(handle), ModelNode(Arc::downgrade(&model)), - SpatialNode(Arc::downgrade(&model.space)), + SpatialNode(Arc::downgrade(&model.spatial)), )) .id(); model.bevy_scene_entity.set(entity).unwrap(); @@ -59,7 +82,7 @@ fn load_models( fn apply_materials( mut query: Query<&mut MeshMaterial3d>, - mut material_registry: Local, + mut material_registry: ResMut, asset_server: Res, mut materials: ResMut>, ) -> bevy::prelude::Result { @@ -69,7 +92,9 @@ fn apply_materials( .filter_map(|p| p.parts.get()) .flatten() { - let mut mesh_mat = query.get_mut(*model_part.mesh_entity.get().unwrap())?; + let Ok(mut mesh_mat) = query.get_mut(*model_part.mesh_entity.get().unwrap()) else { + continue; + }; if let Some(material) = model_part.pending_material_replacement.lock().take() { let pbr_mat = material.to_pbr_mat(&asset_server); let handle = material_registry.get_handle(pbr_mat, &mut materials); @@ -128,8 +153,8 @@ fn gen_model_parts( let parent_spatial = parent .as_ref() .map(|p| p.space.clone()) - .unwrap_or_else(|| model.space.clone()); - let client = model.space.node()?.get_client()?; + .unwrap_or_else(|| model.spatial.clone()); + let client = model.spatial.node()?.get_client()?; let (spatial, model_part) = match model.pre_bound_parts.lock().iter().find(|v| v.path == path) { None => { @@ -341,16 +366,13 @@ impl MaterialParameter { MaterialParameter::Vec3(_val) => { // nothing uses a Vec3 } - MaterialParameter::Color(val) => { - let rgba = Srgba::new(val.c.r, val.c.g, val.c.b, val.a); - match parameter_name { - "color" => mat.color = rgba.into(), - "emission_factor" => mat.emission_factor = rgba.into(), - v => { - error!("unknown param_name ({v}) for color") - } + MaterialParameter::Color(color) => match parameter_name { + "color" => mat.color = color.to_bevy(), + "emission_factor" => mat.emission_factor = color.to_bevy(), + v => { + error!("unknown param_name ({v}) for color") } - } + }, MaterialParameter::Texture(resource) => { let Some(texture_path) = get_resource_file(resource, client, &[OsStr::new("png"), OsStr::new("jpg")]) @@ -474,11 +496,11 @@ impl ModelPartAspect for ModelPart { Ok(()) } } -#[derive(Default)] -struct MaterialRegistry(FxHashMap>); +#[derive(Default, Resource)] +pub struct MaterialRegistry(FxHashMap>); impl MaterialRegistry { /// returns strong handle for PbrMaterial elminitating duplications - fn get_handle( + pub fn get_handle( &mut self, material: PbrMaterial, materials: &mut ResMut>, @@ -500,7 +522,7 @@ impl MaterialRegistry { } pub struct Model { - space: Arc, + spatial: Arc, _resource_id: ResourceID, bevy_scene_entity: OnceLock, parts: OnceLock>>, @@ -516,7 +538,7 @@ impl Model { .ok_or_else(|| eyre!("Resource not found"))?; let model = Arc::new(Model { - space: node.get_aspect::().unwrap().clone(), + spatial: node.get_aspect::().unwrap().clone(), _resource_id: resource_id, bevy_scene_entity: OnceLock::new(), pre_bound_parts: Mutex::default(), @@ -553,10 +575,14 @@ impl Model { } None => { // TODO: this could be a denail of service vector - let client = self.space.node().unwrap().get_client().unwrap(); + let client = self.spatial.node().unwrap().get_client().unwrap(); let part_node = client.scenegraph.add_node(Node::generate(&client, false)); - let spatial = - Spatial::add_to(&part_node, Some(self.space.clone()), Mat4::IDENTITY, false); + let spatial = Spatial::add_to( + &part_node, + Some(self.spatial.clone()), + Mat4::IDENTITY, + false, + ); let part = part_node.add_aspect(ModelPart { entity: OnceLock::new(), mesh_entity: OnceLock::new(), diff --git a/src/nodes/drawable/text.rs b/src/nodes/drawable/text.rs index 282553e..b205feb 100644 --- a/src/nodes/drawable/text.rs +++ b/src/nodes/drawable/text.rs @@ -1,48 +1,223 @@ use crate::{ core::{ - client::Client, destroy_queue, error::Result, registry::Registry, + client::Client, color::ColorConvert, error::Result, registry::Registry, resource::get_resource_file, }, - nodes::{Node, spatial::Spatial}, + nodes::{ + Node, + drawable::XAlign, + spatial::{Spatial, SpatialNode}, + }, }; +use bevy::{platform::collections::HashMap, prelude::*}; +use bevy_mesh_text_3d::{ + Align, Attrs, MeshTextPlugin, Settings as FontSettings, generate_meshes, + text_glyphs::TextGlyphs, +}; +use bevy_sk::vr_materials::PbrMaterial; use color_eyre::eyre::eyre; -use glam::{Mat4, Vec2, vec3}; +use core::f32; +use cosmic_text::Metrics; use parking_lot::Mutex; use std::{ ffi::OsStr, + mem, path::PathBuf, sync::{Arc, OnceLock}, }; -use stereokit_rust::{ - font::Font, - sk::MainThreadToken, - system::{TextAlign, TextFit, TextStyle as SkTextStyle}, - util::{Color32, Color128}, -}; +use tokio::sync::mpsc; -use super::{TextAspect, TextStyle}; +static SPAWN_TEXT: OnceLock>> = OnceLock::new(); -static TEXT_REGISTRY: Registry = Registry::new(); +#[derive(Resource)] +struct MpscReceiver(mpsc::UnboundedReceiver); -fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign { - match (x_align, y_align) { - (super::XAlign::Left, super::YAlign::Top) => TextAlign::TopLeft, - (super::XAlign::Left, super::YAlign::Center) => TextAlign::CenterLeft, - (super::XAlign::Left, super::YAlign::Bottom) => TextAlign::BottomLeft, - (super::XAlign::Center, super::YAlign::Top) => TextAlign::Center, - (super::XAlign::Center, super::YAlign::Center) => TextAlign::Center, - (super::XAlign::Center, super::YAlign::Bottom) => TextAlign::BottomCenter, - (super::XAlign::Right, super::YAlign::Top) => TextAlign::TopRight, - (super::XAlign::Right, super::YAlign::Center) => TextAlign::CenterRight, - (super::XAlign::Right, super::YAlign::Bottom) => TextAlign::BottomRight, +pub struct TextNodePlugin; + +impl Plugin for TextNodePlugin { + fn build(&self, app: &mut App) { + // Text init stuff + // app.init_asset::().init_asset_loader::(); + // load_internal_binary_asset!( + // app, + // Handle::default(), + // "assets/FiraMono-subset.ttf", + // |bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() } + // ); + // 1.0 for font size in meters + app.add_plugins(MeshTextPlugin::new(1.0)); + app.world_mut() + .resource_mut::() + .font_system + .db_mut() + .load_system_fonts(); + + let (tx, rx) = mpsc::unbounded_channel(); + SPAWN_TEXT.set(tx).unwrap(); + app.init_resource::(); + app.insert_resource(MpscReceiver(rx)); + app.add_systems(Update, (spawn_text, update_visibillity).chain()); } } -pub struct Text { - space: Arc, - font_path: Option, - style: OnceLock, +fn update_visibillity(mut cmds: Commands) { + for text in TEXT_REGISTRY.get_valid_contents().into_iter() { + let Some(entity) = text.entity.lock().as_ref().copied() else { + continue; + }; + match text.spatial.node().map(|n| n.enabled()).unwrap_or(false) { + true => { + cmds.entity(entity) + .insert_recursive::(Visibility::Visible); + } + false => { + cmds.entity(entity) + .insert_recursive::(Visibility::Hidden); + } + } + } +} +fn spawn_text( + mut mpsc: ResMut>>, + mut cmds: Commands, + mut font_settings: ResMut, + mut material_registry: ResMut, + mut materials: ResMut>, + mut meshes: ResMut>, + mut font_registry: Local, +) { + while let Ok(text) = mpsc.0.try_recv() { + if let Some(entity) = text.entity.lock().take() { + cmds.entity(entity).despawn(); + } + let style = text.data.lock(); + let old_db = text.font_path.clone().map(|p| { + let db = font_registry.get(p); + mem::swap(font_settings.font_system.db_mut(), db); + db + }); + let attrs = Attrs::new().weight(cosmic_text::Weight::BOLD); + let alignment = Some(match style.text_align_x { + super::XAlign::Left => Align::Right, + super::XAlign::Center => Align::Center, + super::XAlign::Right => Align::Left, + }); + let text_string = text.text.lock().clone(); + let mut text_glyphs = TextGlyphs::new( + Metrics { + font_size: style.character_height, + line_height: style.character_height, + }, + [(text_string.as_str(), attrs.clone())], + &attrs, + &mut font_settings.font_system, + alignment, + ); + let max_width = style.bounds.as_ref().map(|v| v.bounds.x); + let max_height = style.bounds.as_ref().map(|v| v.bounds.x); + let (width, height) = + text_glyphs.measure(max_width, max_height, &mut font_settings.font_system); + info!(width, height, ?style.text_align_x); + let meshes = generate_meshes( + bevy_mesh_text_3d::InputText::Simple { + text: text_string, + material: material_registry.get_handle( + PbrMaterial { + color: style.color.to_bevy(), + emission_factor: Color::WHITE, + metallic: 0.0, + roughness: 1.0, + // If alpha is supported on text we need to change this + alpha_mode: AlphaMode::Opaque, + double_sided: false, + ..default() + }, + &mut materials, + ), + attrs, + }, + &mut font_settings, + bevy_mesh_text_3d::Parameters { + extrusion_depth: 0.0, + font_size: style.character_height, + line_height: style.character_height, + alignment, + max_width, + max_height, + }, + &mut meshes, + ); + if let Some(db) = old_db { + mem::swap(font_settings.font_system.db_mut(), db); + } + let Ok(meshes) = meshes.inspect_err(|err| error!("unable to create text meshes: {err}")) + else { + continue; + }; + let dist = meshes.iter().fold(f32::MAX, |dist, v| { + dist.min(v.transform.translation.x) + // if dist > v.transform.translation.x { + // v.transform.translation.x + // } else { + // dist + // } + }); + // TODO: text align + let letters = meshes + .into_iter() + .map(|v| { + // info!("{:?}", v.transform); + cmds.spawn(( + Mesh3d(v.mesh), + MeshMaterial3d(v.material), + // rotation is sus, might be related to the gltf coordinate system + Transform::from_rotation(Quat::from_rotation_y(f32::consts::PI)) + * Transform::from_xyz( + -dist + + match style.bounds.as_ref().map(|v| v.anchor_align_x) { + Some(XAlign::Center) => width * -0.5, + Some(XAlign::Right) => width * -1.0, + Some(XAlign::Left) => 0.0, + None => 0.0, + }, + 0.0, + 0.0, + ) * v.transform, + )) + .id() + }) + .collect::>(); + let entity = cmds + .spawn((SpatialNode(Arc::downgrade(&text.spatial)),)) + .add_children(&letters) + .id(); + text.entity.lock().replace(entity); + } +} + +#[derive(Default)] +struct FontDatabaseRegistry(HashMap); +impl FontDatabaseRegistry { + fn get(&mut self, path: PathBuf) -> &mut cosmic_text::fontdb::Database { + self.0.entry(path).or_insert_with_key(|path| { + let mut db = cosmic_text::fontdb::Database::new(); + if let Err(err) = db.load_font_file(path) { + error!("unable to load font file {} {err}", path.to_string_lossy()); + }; + db + }) + } +} + +use super::{TextAspect, TextStyle, model::MaterialRegistry}; + +static TEXT_REGISTRY: Registry = Registry::new(); + +pub struct Text { + spatial: Arc, + font_path: Option, + entity: Mutex>, text: Mutex, data: Mutex, } @@ -50,88 +225,20 @@ impl Text { pub fn add_to(node: &Arc, text: String, style: TextStyle) -> Result> { let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?; let text = TEXT_REGISTRY.add(Text { - space: node.get_aspect::().unwrap().clone(), + spatial: node.get_aspect::().unwrap().clone(), font_path: style.font.as_ref().and_then(|res| { get_resource_file(res, &client, &[OsStr::new("ttf"), OsStr::new("otf")]) }), - style: OnceLock::new(), + entity: Mutex::new(None), text: Mutex::new(text), data: Mutex::new(style), }); node.add_aspect_raw(text.clone()); + _ = SPAWN_TEXT.get().unwrap().send(text.clone()); Ok(text) } - - fn draw(&self, token: &MainThreadToken) { - let style = self.style.get_or_init(|| { - let font = self - .font_path - .as_deref() - .and_then(|path| Font::from_file(path).ok()) - .unwrap_or_default(); - SkTextStyle::from_font(font, 1.0, Color32::WHITE) - }); - - let text = self.text.lock(); - let data = self.data.lock(); - let transform = self.space.global_transform() - * Mat4::from_scale(vec3( - data.character_height, - data.character_height, - data.character_height, - )); - if let Some(bounds) = &data.bounds { - stereokit_rust::system::Text::add_in( - token, - &*text, - transform, - Vec2::from(bounds.bounds) / data.character_height, - match bounds.fit { - super::TextFit::Wrap => TextFit::Wrap, - super::TextFit::Clip => TextFit::Clip, - super::TextFit::Squeeze => TextFit::Squeeze, - super::TextFit::Exact => TextFit::Exact, - super::TextFit::Overflow => TextFit::Overflow, - }, - Some(*style), - Some(Color128::new( - data.color.c.r, - data.color.c.g, - data.color.c.b, - data.color.a, - )), - data.bounds - .as_ref() - .map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)), - Some(convert_align(data.text_align_x, data.text_align_y)), - None, - None, - None, - ); - } else { - stereokit_rust::system::Text::add_at( - token, - &*text, - transform, - Some(*style), - Some(Color128::new( - data.color.c.r, - data.color.c.g, - data.color.c.b, - data.color.a, - )), - data.bounds - .as_ref() - .map(|b| convert_align(b.anchor_align_x, b.anchor_align_y)), - Some(convert_align(data.text_align_x, data.text_align_y)), - None, - None, - None, - ); - } - } } impl TextAspect for Text { fn set_character_height( @@ -141,30 +248,19 @@ impl TextAspect for Text { ) -> Result<()> { let this_text = node.get_aspect::()?; this_text.data.lock().character_height = height; + _ = SPAWN_TEXT.get().unwrap().send(this_text.clone()); Ok(()) } fn set_text(node: Arc, _calling_client: Arc, text: String) -> Result<()> { let this_text = node.get_aspect::()?; *this_text.text.lock() = text; + _ = SPAWN_TEXT.get().unwrap().send(this_text.clone()); Ok(()) } } impl Drop for Text { fn drop(&mut self) { - if let Some(style) = self.style.take() { - destroy_queue::add(style); - } TEXT_REGISTRY.remove(self); } } - -pub fn draw_all(token: &MainThreadToken) { - for text in TEXT_REGISTRY.get_valid_contents() { - if let Some(node) = text.space.node() { - if node.enabled() { - text.draw(token); - } - } - } -} diff --git a/src/nodes/input/mod.rs b/src/nodes/input/mod.rs index 2ae32a6..5ff119e 100644 --- a/src/nodes/input/mod.rs +++ b/src/nodes/input/mod.rs @@ -8,6 +8,7 @@ mod tip; pub use handler::*; pub use method::*; +use tracing::debug_span; use super::Aspect; use super::AspectIdentifier; @@ -120,6 +121,7 @@ pub fn process_input() { node.enabled() }); for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() { + let _span = debug_span!("handle input handler").entered(); for method_alias in handler.method_aliases.get_aliases() { method_alias.set_enabled(false); } @@ -136,6 +138,7 @@ pub fn process_input() { } }; + let ser_span = debug_span!("serializing input").entered(); let (methods, datas) = methods .clone() // filter out methods without the handler in their handler order @@ -161,7 +164,9 @@ pub fn process_input() { // serialize the data .map(|(a, m)| (a.clone(), m.serialize(a.get_id(), &handler))) .unzip::<_, _, Vec<_>, Vec<_>>(); + drop(ser_span); + let _span = debug_span!("client input").entered(); let _ = input_handler_client::input(&handler_node, &methods, &datas); } for method in methods { diff --git a/src/nodes/spatial/mod.rs b/src/nodes/spatial/mod.rs index b310619..343ad21 100644 --- a/src/nodes/spatial/mod.rs +++ b/src/nodes/spatial/mod.rs @@ -9,7 +9,6 @@ use crate::core::client::Client; use crate::core::error::Result; use crate::core::registry::Registry; use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO}; -use bevy::ecs::entity_disabling::Disabled; use bevy::prelude::Transform as BevyTransform; use bevy::prelude::*; use color_eyre::eyre::OptionExt; @@ -18,7 +17,6 @@ use mint::Vector3; use parking_lot::Mutex; use rustc_hash::FxHashMap; use std::fmt::Debug; -use std::sync::atomic::Ordering; use std::sync::{Arc, OnceLock, Weak}; use std::{f32, ptr}; use stereokit_rust::maths::Bounds; @@ -28,62 +26,39 @@ impl Plugin for SpatialNodePlugin { fn build(&self, app: &mut App) { app.add_systems( PostUpdate, - (replace_childof, update_spatial_nodes).before(TransformSystem::TransformPropagate), + update_spatial_nodes.before(TransformSystem::TransformPropagate), ); } } fn update_spatial_nodes( - mut query: Query<(Entity, &mut BevyTransform, &SpatialNode, Has)>, - cmds: ParallelCommands, + mut query: Query<(&mut BevyTransform, &SpatialNode, Option<&ChildOf>)>, + parent_query: Query<&GlobalTransform>, ) { query .par_iter_mut() - .for_each(|(entity, mut transform, spatial_node, disabled)| { + .for_each(|(mut transform, spatial_node, child_of)| { let Some(spatial) = spatial_node.0.upgrade() else { + // should we despawn the entity? return; }; - if !spatial - .node() - .map(|n| n.enabled.load(Ordering::Relaxed)) - .unwrap_or(true) - { - // cmds.command_scope(|mut cmds| { - // cmds.entity(entity).insert(Visibility::Hidden); - // }); - // return; - } let mat4 = spatial.global_transform(); - - let scale_zero = mat4.to_scale_rotation_translation().0.length_squared() > 0.0; - if scale_zero { - // cmds.command_scope(|mut cmds| { - // cmds.entity(entity).insert(Visibility::Hidden); - // }); - } else if disabled { - // cmds.command_scope(|mut cmds| { - // cmds.entity(entity).insert(Visibility::Visible); - // }); + match child_of { + Some(child_of) => { + let Ok(parent) = parent_query.get(child_of.0) else { + warn!("SpatialNode bevy Parent doesn't have global transform"); + return; + }; + *transform = + BevyTransform::from_matrix(parent.compute_matrix().inverse() * mat4); + } + None => { + *transform = BevyTransform::from_matrix(mat4); + } } - *transform = BevyTransform::from_matrix(mat4); }); } -fn replace_childof(query: Query<(Entity, &ChildOf), With>, mut cmds: Commands) { - for (entity, parent) in &query { - cmds.entity(entity) - .insert(NonSpatialChildOf(parent.0)) - .remove::(); - } -} - -#[derive(Component, Default, Debug, PartialEq, Eq)] -#[relationship_target(relationship = NonSpatialChildOf, linked_spawn)] -pub struct NonSpatialChildren(Vec); -#[derive(Component, Debug, PartialEq, Eq)] -#[relationship(relationship_target = NonSpatialChildren)] -pub struct NonSpatialChildOf(Entity); - #[derive(Clone, Component, Debug)] #[require(BevyTransform)] pub struct SpatialNode(pub Weak); diff --git a/src/objects/input/sk_controller.rs b/src/objects/input/sk_controller.rs index d953ece..31da994 100644 --- a/src/objects/input/sk_controller.rs +++ b/src/objects/input/sk_controller.rs @@ -154,9 +154,7 @@ fn update( ref_space: Option>, state: Option>, ) { - info!("calling update"); let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else { - info!("something is missing"); controllers.left.set_enabled(false); controllers.right.set_enabled(false); return; @@ -288,6 +286,7 @@ impl SkController { let tracked = Tracked::new(connection, &path); let tip = InputDataType::Tip(Tip::default()); let node = spatial.node().unwrap(); + node.set_enabled(false); let model = Model::add_to(&node, ResourceID::Direct(CURSOR_MODEL_PATH.into())).unwrap(); let model_part = model.get_model_part("Cursor".to_string()).unwrap(); let input = InputMethod::add_to( @@ -324,7 +323,6 @@ impl SkController { ref_space: XrReferenceSpace, ) { let Some(space) = self.space.as_ref() else { - info!("no space 3:"); return; }; let Ok(location) = session @@ -339,10 +337,8 @@ impl SkController { | SpaceLocationFlags::ORIENTATION_VALID | SpaceLocationFlags::ORIENTATION_TRACKED, ); - info!("{:#?}", location.location_flags); self.set_enabled(enabled); if enabled { - info!("update"); let world_transform = Mat4::from(Affine3A::from(location.pose.to_xr_pose())); self.model_part .set_material_parameter("roughness".to_string(), MaterialParameter::Float(1.0));