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
6 changed files with 142 additions and 118 deletions
Showing only changes of commit 880e187dc6 - Show all commits

View File

@@ -16,6 +16,9 @@ use bevy::app::{App, TerminalCtrlCHandlerPlugin};
use bevy::asset::{AssetMetaCheck, UnapprovedPathMode}; use bevy::asset::{AssetMetaCheck, UnapprovedPathMode};
use bevy::audio::AudioPlugin; use bevy::audio::AudioPlugin;
use bevy::core_pipeline::CorePipelinePlugin; use bevy::core_pipeline::CorePipelinePlugin;
use bevy::core_pipeline::oit::{
OrderIndependentTransparencyPlugin, OrderIndependentTransparencySettings,
};
use bevy::diagnostic::DiagnosticsPlugin; use bevy::diagnostic::DiagnosticsPlugin;
use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel}; use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel};
use bevy::gizmos::GizmoPlugin; use bevy::gizmos::GizmoPlugin;
@@ -38,6 +41,7 @@ use bevy_mod_openxr::reference_space::OxrReferenceSpacePlugin;
use bevy_mod_openxr::render::{OxrRenderPlugin, OxrWaitFrameSystem}; use bevy_mod_openxr::render::{OxrRenderPlugin, OxrWaitFrameSystem};
use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrSessionConfig}; use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrSessionConfig};
use bevy_mod_openxr::types::AppInfo; use bevy_mod_openxr::types::AppInfo;
use bevy_mod_xr::camera::XrProjection;
use bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin; use bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin;
use bevy_mod_xr::session::{XrFirst, XrHandleEvents}; use bevy_mod_xr::session::{XrFirst, XrHandleEvents};
use clap::Parser; use clap::Parser;
@@ -56,6 +60,7 @@ use openxr::{EnvironmentBlendMode, ReferenceSpaceType};
use session::{launch_start, save_session}; use session::{launch_start, save_session};
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry; use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
use stardust_xr::server; use stardust_xr::server;
use std::ops::DerefMut as _;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use std::time::Duration; use std::time::Duration;
@@ -359,7 +364,7 @@ fn bevy_loop(
.disable::<OxrActionAttachingPlugin>() .disable::<OxrActionAttachingPlugin>()
.disable::<OxrActionSyncingPlugin>(), .disable::<OxrActionSyncingPlugin>(),
); );
// font size is in meters
app.add_plugins(( app.add_plugins((
bevy_sk::hand::HandPlugin, bevy_sk::hand::HandPlugin,
bevy_sk::vr_materials::SkMaterialPlugin { bevy_sk::vr_materials::SkMaterialPlugin {
@@ -403,6 +408,7 @@ fn bevy_loop(
app.add_systems(PostStartup, move || { app.add_systems(PostStartup, move || {
ready_notifier.notify_waiters(); ready_notifier.notify_waiters();
}); });
app.add_systems(Update, update_cameras);
app.add_systems( app.add_systems(
XrFirst, XrFirst,
xr_step xr_step
@@ -412,6 +418,30 @@ fn bevy_loop(
app.run(); app.run();
} }
fn update_cameras(
mut camera: Query<
&mut Projection,
(
With<Camera3d>,
),
>,
) {
for mut projection in &mut camera {
match projection.deref_mut() {
Projection::Perspective(perspective_projection) => perspective_projection.near = 0.003,
Projection::Orthographic(orthographic_projection) => {
orthographic_projection.near = 0.003
}
Projection::Custom(custom_projection) => {
if let Some(xr) = custom_projection.get_mut::<XrProjection>() {
xr.near = 0.003
} else {
error_once!("unknown custom camera projection");
}
}
}
}
}
fn xr_step(world: &mut World) { fn xr_step(world: &mut World) {
// camera::update(token); // camera::update(token);

View File

@@ -9,20 +9,17 @@ use crate::{
use bevy::{ use bevy::{
asset::RenderAssetUsages, asset::RenderAssetUsages,
prelude::*, prelude::*,
render::mesh::{Indices, PrimitiveTopology}, render::{
}; mesh::{Indices, MeshAabb, PrimitiveTopology},
use bevy_sk::vr_materials::PbrMaterial; primitives::Aabb,
use glam::{FloatExt, Vec3};
use parking_lot::Mutex;
use std::{
collections::VecDeque,
sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering},
}, },
}; };
use stereokit_rust::{ use bevy_sk::vr_materials::PbrMaterial;
maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128, use glam::Vec3;
use parking_lot::Mutex;
use std::sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering},
}; };
pub struct LinesNodePlugin; pub struct LinesNodePlugin;
@@ -32,16 +29,6 @@ impl Plugin for LinesNodePlugin {
app.add_systems(Update, build_line_mesh); app.add_systems(Update, build_line_mesh);
} }
} }
const POINTS: [Vec3; 8] = [
Vec3::X,
Vec3::new(1., 0., 1.),
Vec3::Z,
Vec3::new(-1., 0., 1.),
Vec3::NEG_X,
Vec3::new(-1., 0., -1.),
Vec3::NEG_Z,
Vec3::new(1., 0., -1.),
];
fn build_line_mesh( fn build_line_mesh(
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
@@ -62,25 +49,30 @@ fn build_line_mesh(
if lines_data.is_empty() { if lines_data.is_empty() {
continue; continue;
} }
let mut idk = 0; let mut indecies_set = 0;
for line in lines_data.iter() { for line in lines_data.iter() {
let start_set = indecies_set;
let optional_points = { let optional_points = {
let mut out = Vec::new(); let mut out = Vec::new();
let mut last = line.cyclic.then(|| line.points.last()).flatten(); let mut last = line.cyclic.then(|| line.points.last()).flatten();
let mut peekable = line.points.iter().peekable(); let mut peekable = line.points.iter().peekable();
while let Some(curr) = peekable.next() { while let Some(curr) = peekable.next() {
let mut end = false;
let next = match peekable.peek() { let next = match peekable.peek() {
Some(v) => Some(*v), Some(v) => Some(*v),
None => line.cyclic.then(|| line.points.first()).flatten(), None => {
end = true;
line.cyclic.then(|| line.points.first()).flatten()
}
}; };
out.push((last, curr, next)); out.push((last, curr, next, end));
last = Some(curr); last = Some(curr);
} }
out out
}; };
for (last, curr, next) in optional_points { for (last, curr, next, end) in optional_points {
let last_quat = last.map(|v| { let last_quat = last.map(|v| {
Quat::from_rotation_arc( Quat::from_rotation_arc(
Vec3::Y, Vec3::Y,
@@ -115,10 +107,18 @@ fn build_line_mesh(
vertex_normals.extend(normals); vertex_normals.extend(normals);
vertex_positions.extend(points); vertex_positions.extend(points);
vertex_colors.extend([curr.color.to_bevy().to_srgba().to_f32_array(); 8]); vertex_colors.extend([curr.color.to_bevy().to_srgba().to_f32_array(); 8]);
idk += 1; if !end {
vertex_indecies.extend(indecies(indecies_set));
}
indecies_set += 1;
}
if line.cyclic {
vertex_indecies.extend(cyclic_indecies(start_set, indecies_set - 1));
} else {
vertex_indecies.extend(cap_indecies(start_set, false));
vertex_indecies.extend(cap_indecies(indecies_set - 1, true));
} }
} }
let vertex_indecies = (0..idk - 1).flat_map(indecies).collect::<Vec<_>>();
let mut mesh = Mesh::new( let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList, PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD, RenderAssetUsages::RENDER_WORLD,
@@ -126,7 +126,10 @@ fn build_line_mesh(
mesh.insert_indices(Indices::U32(vertex_indecies)); mesh.insert_indices(Indices::U32(vertex_indecies));
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors); mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions.clone());
if let Some(aabb) = mesh.compute_aabb() {
*lines.bounds.lock() = aabb;
}
match lines.entity.get() { match lines.entity.get() {
Some(e) => cmds.entity(*e), Some(e) => cmds.entity(*e),
@@ -137,23 +140,38 @@ fn build_line_mesh(
MeshMaterial3d(materials.add(PbrMaterial { MeshMaterial3d(materials.add(PbrMaterial {
color: Color::WHITE, color: Color::WHITE,
roughness: 1.0, roughness: 1.0,
alpha_mode: AlphaMode::Blend, alpha_mode: AlphaMode::Opaque,
..default() ..default()
})), })),
)); ));
} }
} }
const END_CAP_INDECIES: [u32; 18] = [0, 1, 7, 7, 1, 2, 7, 2, 6, 6, 2, 3, 6, 3, 5, 5, 3, 4];
fn cap_indecies(set: u32, flip: bool) -> [u32; END_CAP_INDECIES.len()] {
let mut out = END_CAP_INDECIES.map(|v| v + (set * 8));
if flip {
out.reverse();
}
out
}
// const BASE: [u16; 6] = [0, 8, 1, 8, 9, 1]; // const BASE: [u16; 6] = [0, 8, 1, 8, 9, 1];
const INDECIES: [u32; 48] = [ const INDECIES: [u32; 48] = [
0, 8, 1, 8, 9, 1, 1, 9, 2, 9, 10, 2, 2, 10, 3, 10, 11, 3, 3, 11, 4, 11, 12, 4, 4, 12, 5, 12, 0, 8, 1, 8, 9, 1, 1, 9, 2, 9, 10, 2, 2, 10, 3, 10, 11, 3, 3, 11, 4, 11, 12, 4, 4, 12, 5, 12,
13, 5, 5, 13, 6, 13, 14, 6, 6, 14, 7, 14, 15, 7, 7, 15, 0, 15, 8, 0, 13, 5, 5, 13, 6, 13, 14, 6, 6, 14, 7, 14, 15, 7, 7, 15, 0, 15, 8, 0,
]; ];
fn indecies(base: u32) -> [u32; INDECIES.len()] { fn indecies(set: u32) -> [u32; INDECIES.len()] {
INDECIES.map(|v| v + (base * 8)) INDECIES.map(|v| v + (set * 8))
} }
fn cyclic_indecies(base: u32) -> [u32; INDECIES.len()] { fn cyclic_indecies(start_set: u32, end_set: u32) -> [u32; INDECIES.len()] {
let mut out = INDECIES.map(|v| if v >= 8 { v + (base * 8) } else { v }); let mut out = INDECIES.map(|v| {
if v < 8 {
v + ((start_set) * 8)
} else {
v + ((end_set - 1) * 8)
}
});
out.reverse(); out.reverse();
out out
} }
@@ -165,6 +183,7 @@ pub struct Lines {
data: Mutex<Vec<Line>>, data: Mutex<Vec<Line>>,
gen_mesh: AtomicBool, gen_mesh: AtomicBool,
entity: OnceLock<Entity>, entity: OnceLock<Entity>,
bounds: Mutex<Aabb>,
} }
impl Lines { impl Lines {
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> { pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
@@ -173,15 +192,10 @@ impl Lines {
.unwrap() .unwrap()
.bounding_box_calc .bounding_box_calc
.set(|node| { .set(|node| {
let mut bounds = Bounds::default(); node.get_aspect::<Lines>()
if let Ok(lines) = node.get_aspect::<Lines>() { .ok()
for line in &*lines.data.lock() { .map(|v| v.bounds.lock().clone())
for point in &line.points { .unwrap_or_default()
bounds.grown_point(Vec3::from(point.point));
}
}
}
bounds
}); });
info!("line::add_to"); info!("line::add_to");
@@ -190,48 +204,12 @@ impl Lines {
data: Mutex::new(lines), data: Mutex::new(lines),
gen_mesh: AtomicBool::new(true), gen_mesh: AtomicBool::new(true),
entity: OnceLock::new(), entity: OnceLock::new(),
bounds: Mutex::new(Aabb::default()),
}); });
node.add_aspect_raw(lines.clone()); node.add_aspect_raw(lines.clone());
Ok(lines) Ok(lines)
} }
fn draw(&self, token: &MainThreadToken) {
let transform_mat = self.spatial.global_transform();
let data = self.data.lock().clone();
for line in &data {
let mut points: VecDeque<SkLinePoint> = line
.points
.iter()
.map(|p| SkLinePoint {
pt: transform_mat.transform_point3(Vec3::from(p.point)).into(),
thickness: p.thickness,
color: Color128::new(p.color.c.r, p.color.c.g, p.color.c.b, p.color.a).into(),
})
.collect();
if line.cyclic && !points.is_empty() {
let first = line.points.first().unwrap();
let last = line.points.last().unwrap();
let color = Color128 {
r: first.color.c.r.lerp(last.color.c.r, 0.5),
g: first.color.c.g.lerp(last.color.c.g, 0.5),
b: first.color.c.b.lerp(last.color.c.b, 0.5),
a: first.color.a.lerp(last.color.a, 0.5),
};
let connect_point = SkLinePoint {
pt: transform_mat
.transform_point3(Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5))
.into(),
thickness: (first.thickness + last.thickness) * 0.5,
color: color.into(),
};
points.push_front(connect_point);
points.push_back(connect_point);
}
stereokit_rust::system::Lines::add_list(token, points.make_contiguous());
}
}
} }
impl LinesAspect for Lines { impl LinesAspect for Lines {
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> { fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
@@ -247,13 +225,3 @@ impl Drop for Lines {
LINES_REGISTRY.remove(self); LINES_REGISTRY.remove(self);
} }
} }
pub fn draw_all(token: &MainThreadToken) {
for lines in LINES_REGISTRY.get_valid_contents() {
if let Some(node) = lines.spatial.node() {
if node.enabled() {
lines.draw(token);
}
}
}
}

View File

@@ -22,7 +22,6 @@ use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap};
// #[instrument(level = "debug", skip(sk))] // #[instrument(level = "debug", skip(sk))]
pub fn draw(token: &MainThreadToken) { pub fn draw(token: &MainThreadToken) {
lines::draw_all(token);
match QUEUED_SKYTEX.lock().take() { match QUEUED_SKYTEX.lock().take() {
Some(Some(skytex)) => { Some(Some(skytex)) => {
if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) { if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) {

View File

@@ -9,6 +9,7 @@ use crate::nodes::Node;
use crate::nodes::alias::{Alias, AliasList}; use crate::nodes::alias::{Alias, AliasList};
use crate::nodes::spatial::{Spatial, SpatialNode}; use crate::nodes::spatial::{Spatial, SpatialNode};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::primitives::Aabb;
use bevy_sk::vr_materials::PbrMaterial; use bevy_sk::vr_materials::PbrMaterial;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use parking_lot::Mutex; use parking_lot::Mutex;
@@ -18,7 +19,6 @@ use std::ffi::OsStr;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, OnceLock, Weak};
use stereokit_rust::maths::Bounds;
use tokio::sync::mpsc; use tokio::sync::mpsc;
static LOAD_MODEL: OnceLock<mpsc::UnboundedSender<(Arc<Model>, PathBuf)>> = OnceLock::new(); static LOAD_MODEL: OnceLock<mpsc::UnboundedSender<(Arc<Model>, PathBuf)>> = OnceLock::new();
@@ -121,6 +121,7 @@ fn gen_model_parts(
query: Query<(Entity, &SceneRoot, &ModelNode, &Children)>, query: Query<(Entity, &SceneRoot, &ModelNode, &Children)>,
children_query: Query<&Children>, children_query: Query<&Children>,
part_query: Query<(&Name, Option<&Children>, &Transform), Without<Mesh3d>>, part_query: Query<(&Name, Option<&Children>, &Transform), Without<Mesh3d>>,
part_mesh_query: Query<(&Transform, &Aabb), With<Mesh3d>>,
has_mesh: Query<Has<Mesh3d>>, has_mesh: Query<Has<Mesh3d>>,
mut cmds: Commands, mut cmds: Commands,
) { ) {
@@ -145,7 +146,7 @@ fn gen_model_parts(
entity, entity,
&part_query, &part_query,
None, None,
&mut |entity, name, transform, parent| { &mut |entity, name, transform, parent, children| {
let path = parent let path = parent
.as_ref() .as_ref()
.map(|p| format!("{}/{}", &p.path, name.as_str())) .map(|p| format!("{}/{}", &p.path, name.as_str()))
@@ -175,6 +176,7 @@ fn gen_model_parts(
pending_material_parameters: Mutex::default(), pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(), pending_material_replacement: Mutex::default(),
aliases: AliasList::default(), aliases: AliasList::default(),
bounds: OnceLock::new(),
}); });
(spatial, model_part) (spatial, model_part)
} }
@@ -185,9 +187,24 @@ fn gen_model_parts(
(part.space.clone(), part.clone()) (part.space.clone(), part.clone())
} }
}; };
_ = spatial.bounding_box_calc.set(|_| { let aabb = Aabb::enclosing(
// TODO: actually impl aabb children
Bounds::default() .iter()
.flat_map(|v| v.iter())
.filter_map(|e| part_mesh_query.get(e).ok())
.flat_map(|(transform, aabb)| {
[
transform.transform_point(aabb.min().into()),
transform.transform_point(aabb.max().into()),
]
}),
)
.unwrap_or_default();
_ = spatial.bounding_box_calc.set(move |n| {
n.get_aspect::<ModelPart>()
.ok()
.and_then(|v| v.bounds.get().copied())
.unwrap_or_default()
}); });
cmds.entity(entity) cmds.entity(entity)
.insert(SpatialNode(Arc::downgrade(&spatial))); .insert(SpatialNode(Arc::downgrade(&spatial)));
@@ -196,6 +213,7 @@ fn gen_model_parts(
.iter() .iter()
.flat_map(|v| v.iter()) .flat_map(|v| v.iter())
.find(|e| has_mesh.get(*e).unwrap_or(false))?; .find(|e| has_mesh.get(*e).unwrap_or(false))?;
_ = model_part.bounds.set(aabb);
_ = model_part.entity.set(entity); _ = model_part.entity.set(entity);
_ = model_part.mesh_entity.set(mesh_entity); _ = model_part.mesh_entity.set(mesh_entity);
parts.push(model_part.clone()); parts.push(model_part.clone());
@@ -216,12 +234,13 @@ fn gen_path(
&Name, &Name,
&Transform, &Transform,
Option<Arc<ModelPart>>, Option<Arc<ModelPart>>,
Option<&Children>,
) -> Option<Arc<ModelPart>>, ) -> Option<Arc<ModelPart>>,
) { ) {
let Ok((name, children, transform)) = part_query.get(current_entity) else { let Ok((name, children, transform)) = part_query.get(current_entity) else {
return; return;
}; };
let Some(parent) = func(current_entity, name, transform, parent) else { let Some(parent) = func(current_entity, name, transform, parent, children) else {
return; return;
}; };
for e in children.iter().flat_map(|c| c.iter()) { for e in children.iter().flat_map(|c| c.iter()) {
@@ -449,6 +468,7 @@ pub struct ModelPart {
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>, pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Material>>, pending_material_replacement: Mutex<Option<Material>>,
aliases: AliasList, aliases: AliasList,
bounds: OnceLock<Aabb>,
} }
impl ModelPart { impl ModelPart {
pub fn replace_material(&self, replacement: Material) { pub fn replace_material(&self, replacement: Material) {
@@ -589,6 +609,7 @@ impl Model {
pending_material_parameters: Mutex::default(), pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(), pending_material_replacement: Mutex::default(),
aliases: AliasList::default(), aliases: AliasList::default(),
bounds: OnceLock::new(),
}); });
self.pre_bound_parts.lock().push(part.clone()); self.pre_bound_parts.lock().push(part.clone());
part part

View File

@@ -11,6 +11,7 @@ use crate::core::registry::Registry;
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO}; use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use bevy::prelude::Transform as BevyTransform; use bevy::prelude::Transform as BevyTransform;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::primitives::Aabb;
use color_eyre::eyre::OptionExt; use color_eyre::eyre::OptionExt;
use glam::{Mat4, Quat, Vec3, vec3a}; use glam::{Mat4, Quat, Vec3, vec3a};
use mint::Vector3; use mint::Vector3;
@@ -19,7 +20,6 @@ use rustc_hash::FxHashMap;
use std::fmt::Debug; use std::fmt::Debug;
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::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, OnceLock, Weak};
use std::{f32, ptr}; use std::{f32, ptr};
use stereokit_rust::maths::Bounds;
pub struct SpatialNodePlugin; pub struct SpatialNodePlugin;
impl Plugin for SpatialNodePlugin { impl Plugin for SpatialNodePlugin {
@@ -102,7 +102,7 @@ pub struct Spatial {
transform: Mutex<Mat4>, transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>, zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>, children: Registry<Spatial>,
pub bounding_box_calc: OnceLock<fn(&Node) -> Bounds>, pub bounding_box_calc: OnceLock<fn(&Node) -> Aabb>,
} }
impl Spatial { impl Spatial {
@@ -146,9 +146,9 @@ impl Spatial {
} }
// the output bounds are probably way bigger than they need to be // the output bounds are probably way bigger than they need to be
pub fn get_bounding_box(&self) -> Bounds { pub fn get_bounding_box(&self) -> Aabb {
let Some(node) = self.node() else { let Some(node) = self.node() else {
return Bounds::default(); return Aabb::default();
}; };
let mut bounds = self let mut bounds = self
.bounding_box_calc .bounding_box_calc
@@ -156,7 +156,15 @@ impl Spatial {
.map(|b| (b)(&node)) .map(|b| (b)(&node))
.unwrap_or_default(); .unwrap_or_default();
for child in self.children.get_valid_contents() { for child in self.children.get_valid_contents() {
bounds.grown_box(child.get_bounding_box(), child.local_transform()); let mat = child.local_transform();
let child_aabb = child.get_bounding_box();
bounds = Aabb::enclosing([
bounds.min().into(),
bounds.max().into(),
mat.transform_point3(child_aabb.min().into()),
mat.transform_point3(child_aabb.max().into()),
])
.unwrap();
} }
bounds bounds
} }
@@ -398,7 +406,7 @@ impl SpatialRefAspect for SpatialRef {
Ok(BoundingBox { Ok(BoundingBox {
center: Vec3::from(bounds.center).into(), center: Vec3::from(bounds.center).into(),
size: Vec3::from(bounds.dimensions).into(), size: Vec3::from(bounds.half_extents * 2.0).into(),
}) })
} }
@@ -409,20 +417,17 @@ impl SpatialRefAspect for SpatialRef {
) -> Result<BoundingBox> { ) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?; let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?; let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)) let mat = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial));
.transform_point3([0.0; 3].into()); let bb = this_spatial.get_bounding_box();
let mut bounds = Bounds { let bounds = Aabb::enclosing([
center: center.into(), mat.transform_point3(bb.min().into()),
dimensions: [0.0; 3].into(), mat.transform_point3(bb.max().into()),
}; ])
bounds.grown_box( .unwrap();
this_spatial.get_bounding_box(),
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)),
);
Ok(BoundingBox { Ok(BoundingBox {
center: Vec3::from(bounds.center).into(), center: Vec3::from(bounds.center).into(),
size: Vec3::from(bounds.dimensions).into(), size: Vec3::from(bounds.half_extents * 2.0).into(),
}) })
} }

View File

@@ -307,6 +307,9 @@ impl SkController {
}) })
} }
pub fn set_enabled(&self, enabled: bool) { pub fn set_enabled(&self, enabled: bool) {
if let Some(node) = self.input.spatial.node() {
node.set_enabled(enabled);
}
tokio::spawn({ tokio::spawn({
// this is suboptimal since it probably allocates a fresh string every frame // this is suboptimal since it probably allocates a fresh string every frame
let handle = self.tracked.clone(); let handle = self.tracked.clone();
@@ -342,8 +345,6 @@ impl SkController {
let world_transform = Mat4::from(Affine3A::from(location.pose.to_xr_pose())); let world_transform = Mat4::from(Affine3A::from(location.pose.to_xr_pose()));
self.model_part self.model_part
.set_material_parameter("roughness".to_string(), MaterialParameter::Float(1.0)); .set_material_parameter("roughness".to_string(), MaterialParameter::Float(1.0));
// self.model_part
// .set_material_parameter("metallic".to_string(), MaterialParameter::Float(0.0));
self.model_part.set_material_parameter( self.model_part.set_material_parameter(
"color".to_string(), "color".to_string(),
MaterialParameter::Color(stardust_xr::values::Color::new( MaterialParameter::Color(stardust_xr::values::Color::new(