Bevy Rewrite, attempt 2 #46

Merged
Schmarni-Dev merged 18 commits from bevy_for_real_this_time into dev 2025-06-30 05:04:00 -04:00
10 changed files with 411 additions and 240 deletions
Showing only changes of commit 20d2917847 - Show all commits

154
Cargo.lock generated
View File

@@ -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]]

View File

@@ -24,7 +24,8 @@ path = "src/main.rs"
default = []
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
coderabbitai[bot] commented 2025-06-28 14:53:14 -04:00 (Migrated from github.com)
Review

⚠️ Potential issue

Breaking change: Wayland no longer in default features.

Removing wayland from default features might break existing users. Consider documenting this change prominently or keeping it as default if it's commonly used.

🤖 Prompt for AI Agents
In Cargo.toml at line 24, the default features list has been changed to exclude
"wayland," which is a breaking change. To fix this, either add "wayland" back to
the default features array if it is commonly used or update the documentation
clearly highlighting this removal so users are aware of the change and can
adjust their dependencies accordingly.
_⚠️ Potential issue_ **Breaking change: Wayland no longer in default features.** Removing `wayland` from default features might break existing users. Consider documenting this change prominently or keeping it as default if it's commonly used. <details> <summary>🤖 Prompt for AI Agents</summary> ``` In Cargo.toml at line 24, the default features list has been changed to exclude "wayland," which is a breaking change. To fix this, either add "wayland" back to the default features array if it is commonly used or update the documentation clearly highlighting this removal so users are aware of the change and can adjust their dependencies accordingly. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
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]

View File

@@ -7,3 +7,4 @@ pub mod registry;
pub mod resource;
pub mod scenegraph;
pub mod task;
pub mod color;

View File

@@ -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::<ScheduleRunnerPlugin>()
.build().add(DiagnosticsPlugin)
.add(TransformPlugin)
.add(InputPlugin)
/* .add(AccessibilityPlugin) */;
@@ -354,6 +351,7 @@ fn bevy_loop(
.disable::<OxrActionAttachingPlugin>()
.disable::<OxrActionSyncingPlugin>(),
);
// 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::<Font>().init_asset_loader::<FontLoader>();
app.init_asset::<Font>().init_asset_loader::<FontLoader>();
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();
}

View File

@@ -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) {

View File

@@ -1,6 +1,7 @@
use super::{MODEL_PART_ASPECT_ALIAS_INFO, MaterialParameter, ModelAspect, ModelPartAspect};
coderabbitai[bot] commented 2025-06-28 14:53:15 -04:00 (Migrated from github.com)
Review

⚠️ Potential issue

Fix early return in iterator that prevents proper cleanup.

Using return inside an iterator skips remaining entities. Should use continue instead.

 let Some(model) = model_node.0.upgrade() else {
     cmds.entity(entity).despawn();
-    return;
+    continue;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

		let Some(model) = model_node.0.upgrade() else {
			cmds.entity(entity).despawn();
			continue;
		};
🤖 Prompt for AI Agents
In src/nodes/drawable/model.rs around lines 127 to 130, the use of `return`
inside the iterator prematurely exits the entire iteration, preventing cleanup
of remaining entities. Replace the `return` statement with `continue` to skip
the current iteration and allow the loop to proceed with other entities,
ensuring proper cleanup.
_⚠️ Potential issue_ **Fix early return in iterator that prevents proper cleanup.** Using `return` inside an iterator skips remaining entities. Should use `continue` instead. ```diff let Some(model) = model_node.0.upgrade() else { cmds.entity(entity).despawn(); - return; + continue; }; ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion let Some(model) = model_node.0.upgrade() else { cmds.entity(entity).despawn(); continue; }; ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/drawable/model.rs around lines 127 to 130, the use of `return` inside the iterator prematurely exits the entire iteration, preventing cleanup of remaining entities. Replace the `return` statement with `continue` to skip the current iteration and allow the loop to proceed with other entities, ensuring proper cleanup. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
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::<MaterialRegistry>();
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<T>(mpsc::UnboundedReceiver<T>);
#[derive(Component)]
struct ModelNode(Weak<Model>);
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::<Children>(Visibility::Visible);
}
false => {
cmds.entity(entity)
.insert_recursive::<Children>(Visibility::Hidden);
}
}
}
}
fn load_models(
asset_server: Res<AssetServer>,
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<PbrMaterial>>,
mut material_registry: Local<MaterialRegistry>,
mut material_registry: ResMut<MaterialRegistry>,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<PbrMaterial>>,
) -> bevy::prelude::Result {
@@ -69,7 +92,9 @@ fn apply_materials(
.filter_map(|p| p.parts.get())
.flatten()
{
coderabbitai[bot] commented 2025-06-28 14:53:15 -04:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Handle GLTF files with multiple scenes.

The code assumes scene index 0 exists, which may not be true for all GLTF files. Consider making this configurable or loading the default scene.

-let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path));
+// Use the default scene if available, otherwise scene 0
+let handle = asset_server.load::<Scene>(path.clone());

Alternatively, load the GLTF asset first to inspect available scenes:

let gltf_handle = asset_server.load::<Gltf>(path);
// Then in another system, check gltf.default_scene or iterate gltf.scenes
🤖 Prompt for AI Agents
In src/nodes/drawable/model.rs at line 68, the code currently loads a GLTF asset
assuming scene index 0 exists, which may not be valid for all GLTF files. To fix
this, first load the GLTF asset without specifying a scene, then in a separate
system or step, inspect the loaded GLTF to determine the default scene or
available scenes. Adjust the scene index dynamically based on this inspection
rather than hardcoding 0.
_🛠️ Refactor suggestion_ **Handle GLTF files with multiple scenes.** The code assumes scene index 0 exists, which may not be true for all GLTF files. Consider making this configurable or loading the default scene. ```diff -let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path)); +// Use the default scene if available, otherwise scene 0 +let handle = asset_server.load::<Scene>(path.clone()); ``` Alternatively, load the GLTF asset first to inspect available scenes: ```rust let gltf_handle = asset_server.load::<Gltf>(path); // Then in another system, check gltf.default_scene or iterate gltf.scenes ``` <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/drawable/model.rs at line 68, the code currently loads a GLTF asset assuming scene index 0 exists, which may not be valid for all GLTF files. To fix this, first load the GLTF asset without specifying a scene, then in a separate system or step, inspect the loaded GLTF to determine the default scene or available scenes. Adjust the scene index dynamically based on this inspection rather than hardcoding 0. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
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<HashedPbrMaterial, Handle<PbrMaterial>>);
#[derive(Default, Resource)]
pub struct MaterialRegistry(FxHashMap<HashedPbrMaterial, Handle<PbrMaterial>>);
impl MaterialRegistry {
/// returns strong handle for PbrMaterial elminitating duplications
fn get_handle(
pub fn get_handle(
&mut self,
material: PbrMaterial,
materials: &mut ResMut<Assets<PbrMaterial>>,
@@ -500,7 +522,7 @@ impl MaterialRegistry {
}
pub struct Model {
space: Arc<Spatial>,
spatial: Arc<Spatial>,
_resource_id: ResourceID,
bevy_scene_entity: OnceLock<Entity>,
parts: OnceLock<Vec<Arc<ModelPart>>>,
@@ -516,7 +538,7 @@ impl Model {
.ok_or_else(|| eyre!("Resource not found"))?;
let model = Arc::new(Model {
space: node.get_aspect::<Spatial>().unwrap().clone(),
spatial: node.get_aspect::<Spatial>().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(),

View File

@@ -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<mpsc::UnboundedSender<Arc<Text>>> = OnceLock::new();
static TEXT_REGISTRY: Registry<Text> = Registry::new();
#[derive(Resource)]
struct MpscReceiver<T>(mpsc::UnboundedReceiver<T>);
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::<Font>().init_asset_loader::<FontLoader>();
// 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::<FontSettings>()
.font_system
.db_mut()
.load_system_fonts();
let (tx, rx) = mpsc::unbounded_channel();
SPAWN_TEXT.set(tx).unwrap();
app.init_resource::<MaterialRegistry>();
app.insert_resource(MpscReceiver(rx));
app.add_systems(Update, (spawn_text, update_visibillity).chain());
}
}
pub struct Text {
space: Arc<Spatial>,
font_path: Option<PathBuf>,
style: OnceLock<SkTextStyle>,
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::<Children>(Visibility::Visible);
}
false => {
cmds.entity(entity)
.insert_recursive::<Children>(Visibility::Hidden);
}
}
}
}
fn spawn_text(
mut mpsc: ResMut<MpscReceiver<Arc<Text>>>,
mut cmds: Commands,
mut font_settings: ResMut<FontSettings>,
mut material_registry: ResMut<MaterialRegistry>,
mut materials: ResMut<Assets<PbrMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
mut font_registry: Local<FontDatabaseRegistry>,
) {
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,
coderabbitai[bot] commented 2025-06-28 14:53:15 -04:00 (Migrated from github.com)
Review

⚠️ Potential issue

Fix incorrect height bounds calculation.

The max_height is incorrectly using bounds.x instead of bounds.y.

 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 max_height = style.bounds.as_ref().map(|v| v.bounds.y);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        let max_width = style.bounds.as_ref().map(|v| v.bounds.x);
        let max_height = style.bounds.as_ref().map(|v| v.bounds.y);
🤖 Prompt for AI Agents
In src/nodes/drawable/text.rs around lines 104 to 105, the max_height variable
is incorrectly assigned using bounds.x instead of bounds.y. Change the
max_height assignment to use bounds.y to correctly reflect the height bounds
from the style.
_⚠️ Potential issue_ **Fix incorrect height bounds calculation.** The max_height is incorrectly using bounds.x instead of bounds.y. ```diff 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 max_height = style.bounds.as_ref().map(|v| v.bounds.y); ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion let max_width = style.bounds.as_ref().map(|v| v.bounds.x); let max_height = style.bounds.as_ref().map(|v| v.bounds.y); ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/drawable/text.rs around lines 104 to 105, the max_height variable is incorrectly assigned using bounds.x instead of bounds.y. Change the max_height assignment to use bounds.y to correctly reflect the height bounds from the style. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
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::<Vec<_>>();
let entity = cmds
.spawn((SpatialNode(Arc::downgrade(&text.spatial)),))
.add_children(&letters)
.id();
text.entity.lock().replace(entity);
}
}
#[derive(Default)]
struct FontDatabaseRegistry(HashMap<PathBuf, cosmic_text::fontdb::Database>);
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<Text> = Registry::new();
pub struct Text {
spatial: Arc<Spatial>,
font_path: Option<PathBuf>,
entity: Mutex<Option<Entity>>,
text: Mutex<String>,
data: Mutex<TextStyle>,
}
@@ -50,88 +225,20 @@ impl Text {
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
let text = TEXT_REGISTRY.add(Text {
space: node.get_aspect::<Spatial>().unwrap().clone(),
spatial: node.get_aspect::<Spatial>().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::<Text>()?;
this_text.data.lock().character_height = height;
_ = SPAWN_TEXT.get().unwrap().send(this_text.clone());
Ok(())
}
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
let this_text = node.get_aspect::<Text>()?;
*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);
}
}
}
}

View File

@@ -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 {

View File

@@ -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};
coderabbitai[bot] commented 2025-06-28 14:53:15 -04:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Consider implementing entity cleanup for dead spatial references.

The comment raises a valid concern. When the spatial weak reference can't be upgraded, the entity is orphaned and should be despawned to prevent resource leaks.

 let Some(spatial) = spatial_node.0.upgrade() else {
-    // should we despawn the entity?
+    cmds.entity(entity).despawn_recursive();
     return;
 };

Note: This would require adding Commands to the system parameters.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/nodes/spatial/mod.rs around lines 41 to 44, the code currently returns
early when the spatial weak reference cannot be upgraded, potentially leaving
orphaned entities. To fix this, modify the system to accept Commands as a
parameter and use it to despawn the entity when the spatial reference is dead,
ensuring proper cleanup and preventing resource leaks.
_🛠️ Refactor suggestion_ **Consider implementing entity cleanup for dead spatial references.** The comment raises a valid concern. When the spatial weak reference can't be upgraded, the entity is orphaned and should be despawned to prevent resource leaks. ```diff let Some(spatial) = spatial_node.0.upgrade() else { - // should we despawn the entity? + cmds.entity(entity).despawn_recursive(); return; }; ``` Note: This would require adding `Commands` to the system parameters. > Committable suggestion skipped: line range outside the PR's diff. <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/spatial/mod.rs around lines 41 to 44, the code currently returns early when the spatial weak reference cannot be upgraded, potentially leaving orphaned entities. To fix this, modify the system to accept Commands as a parameter and use it to despawn the entity when the spatial reference is dead, ensuring proper cleanup and preventing resource leaks. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
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<Disabled>)>,
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<SpatialNode>>, mut cmds: Commands) {
for (entity, parent) in &query {
cmds.entity(entity)
.insert(NonSpatialChildOf(parent.0))
.remove::<ChildOf>();
}
}
#[derive(Component, Default, Debug, PartialEq, Eq)]
#[relationship_target(relationship = NonSpatialChildOf, linked_spawn)]
pub struct NonSpatialChildren(Vec<Entity>);
#[derive(Component, Debug, PartialEq, Eq)]
#[relationship(relationship_target = NonSpatialChildren)]
pub struct NonSpatialChildOf(Entity);
#[derive(Clone, Component, Debug)]
#[require(BevyTransform)]
pub struct SpatialNode(pub Weak<Spatial>);

View File

@@ -154,9 +154,7 @@ fn update(
ref_space: Option<Res<XrPrimaryReferenceSpace>>,
state: Option<Res<OxrFrameState>>,
) {
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));