fix: getting bounding boxes before model is fully loaded now waits until the model and bounding boxes are loaded

Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
Schmarni
2025-10-31 01:25:20 +01:00
parent b70a188e67
commit 5f152df9f7
3 changed files with 72 additions and 29 deletions

View File

@@ -16,7 +16,7 @@ use bevy::{
pbr::{ExtendedMaterial, MaterialExtension}, pbr::{ExtendedMaterial, MaterialExtension},
prelude::*, prelude::*,
render::{ render::{
mesh::{Indices, MeshAabb, PrimitiveTopology}, mesh::{Indices, PrimitiveTopology, VertexAttributeValues},
primitives::Aabb, primitives::Aabb,
render_resource::{AsBindGroup, ShaderRef}, render_resource::{AsBindGroup, ShaderRef},
view::VisibilitySystems, view::VisibilitySystems,
@@ -28,6 +28,7 @@ use std::sync::{
Arc, OnceLock, Arc, OnceLock,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
}; };
use tokio::sync::Notify;
type LineMaterial = ExtendedMaterial<BevyMaterial, LineExtension>; type LineMaterial = ExtendedMaterial<BevyMaterial, LineExtension>;
const LINE_SHADER_HANDLE: Handle<Shader> = weak_handle!("7d28aa5a-3abd-43bb-b0e9-0de8b81b650d"); const LINE_SHADER_HANDLE: Handle<Shader> = weak_handle!("7d28aa5a-3abd-43bb-b0e9-0de8b81b650d");
@@ -91,10 +92,8 @@ fn build_line_mesh(
mut materials: ResMut<Assets<LineMaterial>>, mut materials: ResMut<Assets<LineMaterial>>,
query: Query<(&GlobalTransform, &InheritedVisibility)>, query: Query<(&GlobalTransform, &InheritedVisibility)>,
) { ) {
for lines in LINES_REGISTRY for lines in LINES_REGISTRY.get_valid_contents().into_iter()
.get_valid_contents() // .filter(|l| l.gen_mesh.load(Ordering::Relaxed))
.into_iter()
// .filter(|l| l.gen_mesh.load(Ordering::Relaxed))
{ {
lines.gen_mesh.store(false, Ordering::Relaxed); lines.gen_mesh.store(false, Ordering::Relaxed);
let mut vertex_positions = Vec::<Vec3>::new(); let mut vertex_positions = Vec::<Vec3>::new();
@@ -107,7 +106,8 @@ fn build_line_mesh(
continue; continue;
}; };
if lines_data.is_empty() { if lines_data.is_empty() {
*lines.bounds.lock() = Aabb::default(); *lines.bounds.lock() = Some(Aabb::default());
lines.setup_complete.notify_waiters();
match lines.entity.get() { match lines.entity.get() {
Some(e) => cmds.entity(**e), Some(e) => cmds.entity(**e),
None => { None => {
@@ -261,9 +261,21 @@ fn build_line_mesh(
e e
} }
}; };
if let Some(aabb) = mesh.compute_aabb() { if let Some(VertexAttributeValues::Float32x3(values)) =
*lines.bounds.lock() = aabb; mesh.attribute(Mesh::ATTRIBUTE_POSITION)
entity.insert(aabb); {
let global_to_local = transform.affine().inverse();
let local_aabb = Aabb::enclosing(
values
.iter()
.map(|p| global_to_local.transform_point3(Vec3::from_slice(p))),
)
.unwrap_or_default();
let global_aabb =
Aabb::enclosing(values.iter().map(|p| Vec3::from_slice(p))).unwrap_or_default();
*lines.bounds.lock() = Some(local_aabb);
lines.setup_complete.notify_waiters();
entity.insert(global_aabb);
} }
entity entity
.insert(Mesh3d(meshes.add(mesh))) .insert(Mesh3d(meshes.add(mesh)))
@@ -313,7 +325,8 @@ pub struct Lines {
data: Mutex<Vec<Line>>, data: Mutex<Vec<Line>>,
gen_mesh: AtomicBool, gen_mesh: AtomicBool,
entity: OnceLock<EntityHandle>, entity: OnceLock<EntityHandle>,
bounds: Mutex<Aabb>, bounds: Mutex<Option<Aabb>>,
setup_complete: Notify,
} }
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>> {
@@ -322,10 +335,19 @@ impl Lines {
.unwrap() .unwrap()
.bounding_box_calc .bounding_box_calc
.set(|node| { .set(|node| {
node.get_aspect::<Lines>() Box::pin(async {
.ok() let Ok(lines) = node.get_aspect::<Lines>() else {
.map(|v| *v.bounds.lock()) return Default::default();
.unwrap_or_default() };
let bounds = *lines.bounds.lock();
match bounds {
Some(aabb) => aabb,
None => {
lines.setup_complete.notified().await;
lines.bounds.lock().unwrap_or_default()
}
}
})
}); });
let lines = LINES_REGISTRY.add(Lines { let lines = LINES_REGISTRY.add(Lines {
@@ -333,7 +355,8 @@ 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()), bounds: Mutex::new(Some(Aabb::default())),
setup_complete: Notify::new(),
}); });
node.add_aspect_raw(lines.clone()); node.add_aspect_raw(lines.clone());

View File

@@ -25,6 +25,7 @@ use std::hash::{Hash, Hasher};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, OnceLock, Weak};
use tokio::sync::Notify;
static LOAD_MODEL: BevyChannel<(Arc<Model>, PathBuf)> = BevyChannel::new(); static LOAD_MODEL: BevyChannel<(Arc<Model>, PathBuf)> = BevyChannel::new();
@@ -242,10 +243,12 @@ fn gen_model_parts(
) )
.unwrap_or_default(); .unwrap_or_default();
_ = spatial.bounding_box_calc.set(move |n| { _ = spatial.bounding_box_calc.set(move |n| {
n.get_aspect::<ModelPart>() Box::pin(async {
.ok() n.get_aspect::<ModelPart>()
.and_then(|v| v.bounds.get().copied()) .ok()
.unwrap_or_default() .and_then(|v| v.bounds.get().copied())
.unwrap_or_default()
})
}); });
let _ = spatial.set_spatial_parent(&parent_spatial); let _ = spatial.set_spatial_parent(&parent_spatial);
spatial.set_local_transform(transform.compute_matrix()); spatial.set_local_transform(transform.compute_matrix());
@@ -271,6 +274,8 @@ fn gen_model_parts(
model model
.spatial .spatial
.set_entity(model.bevy_scene_entity.get().unwrap().clone()); .set_entity(model.bevy_scene_entity.get().unwrap().clone());
model.setup_complete.store(true, Ordering::Relaxed);
model.setup_complete_notify.notify_waiters();
} }
} }
@@ -534,6 +539,8 @@ pub struct Model {
bevy_scene_entity: OnceLock<EntityHandle>, bevy_scene_entity: OnceLock<EntityHandle>,
parts: OnceLock<Vec<Arc<ModelPart>>>, parts: OnceLock<Vec<Arc<ModelPart>>>,
pre_bound_parts: Mutex<Vec<Arc<ModelPart>>>, pre_bound_parts: Mutex<Vec<Arc<ModelPart>>>,
setup_complete: AtomicBool,
setup_complete_notify: Notify,
} }
impl Model { impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> { pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
@@ -550,6 +557,18 @@ impl Model {
bevy_scene_entity: OnceLock::new(), bevy_scene_entity: OnceLock::new(),
pre_bound_parts: Mutex::default(), pre_bound_parts: Mutex::default(),
parts: OnceLock::new(), parts: OnceLock::new(),
setup_complete_notify: Notify::new(),
setup_complete: AtomicBool::new(false),
});
model.spatial.bounding_box_calc.set(|n| {
Box::pin(async {
if let Ok(model) = n.get_aspect::<Model>()
&& !model.setup_complete.load(Ordering::Relaxed)
{
model.setup_complete_notify.notified().await;
}
Aabb::default()
})
}); });
LOAD_MODEL LOAD_MODEL
.send((model.clone(), pending_model_path)) .send((model.clone(), pending_model_path))

View File

@@ -20,6 +20,7 @@ use mint::Vector3;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::pin::Pin;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, OnceLock, Weak};
use std::{f32, ptr}; use std::{f32, ptr};
@@ -142,7 +143,8 @@ pub struct Spatial {
transform: RwLock<Mat4>, transform: RwLock<Mat4>,
zone: RwLock<Weak<Zone>>, zone: RwLock<Weak<Zone>>,
children: Registry<Spatial>, children: Registry<Spatial>,
pub bounding_box_calc: OnceLock<fn(&Node) -> Aabb>, pub bounding_box_calc:
OnceLock<for<'a> fn(&'a Node) -> Pin<Box<dyn Future<Output = Aabb> + 'a + Send + Sync>>>,
} }
impl Spatial { impl Spatial {
@@ -199,18 +201,17 @@ 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) -> Aabb { pub async fn get_bounding_box(&self) -> Aabb {
let Some(node) = self.node() else { let Some(node) = self.node() else {
return Aabb::default(); return Aabb::default();
}; };
let mut bounds = self let mut bounds = match self.bounding_box_calc.get() {
.bounding_box_calc Some(f) => f(&node).await,
.get() None => Aabb::default(),
.map(|b| (b)(&node)) };
.unwrap_or_default();
for child in self.children.get_valid_contents() { for child in self.children.get_valid_contents() {
let mat = child.local_transform(); let mat = child.local_transform();
let child_aabb = child.get_bounding_box(); let child_aabb = Box::pin(child.get_bounding_box()).await;
bounds = Aabb::enclosing([ bounds = Aabb::enclosing([
bounds.min().into(), bounds.min().into(),
bounds.max().into(), bounds.max().into(),
@@ -480,7 +481,7 @@ impl SpatialRefAspect for SpatialRef {
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
) -> Result<BoundingBox> { ) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?; let this_spatial = node.get_aspect::<Spatial>()?;
let bounds = this_spatial.get_bounding_box(); let bounds = this_spatial.get_bounding_box().await;
Ok(BoundingBox { Ok(BoundingBox {
center: Vec3::from(bounds.center).into(), center: Vec3::from(bounds.center).into(),
@@ -496,7 +497,7 @@ impl SpatialRefAspect for SpatialRef {
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 mat = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)); let mat = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial));
let bb = this_spatial.get_bounding_box(); let bb = this_spatial.get_bounding_box().await;
let bounds = Aabb::enclosing([ let bounds = Aabb::enclosing([
mat.transform_point3(bb.min().into()), mat.transform_point3(bb.min().into()),
mat.transform_point3(bb.max().into()), mat.transform_point3(bb.max().into()),