Skip to content

Commit 9b36e77

Browse files
mlodato517alice-i-cecileIceSentrymockersfnicopap
committed
Recalculate Aabbs when meshes change
**This Commit** Adds an `update_bounds` system that ensures entities have updated `Aabb`s after they're assigned new mesh handles or if their mesh is directly mutated. **Why?** The `calculate_bounds` system is responsible for taking entities with meshes, calculating their Aabbs, and attaching it to the entities. If an entity is assigned a new mesh, however, nothing updates the entity's Aabb to match the new mesh. Additionally, if an entity's mesh is mutated to be a different shape, the attached Aabb isn't updated. Fixes #4294. **Notes** - While it doesn't seem possible now there remains the possibility that systems are updated such that an entity is added to its mesh's list in the mapping twice. This could result in some duplicate Aabb assignment but is considered worth the risk since it should be very rare. - Updating the Aabb for entities assigned to a mesh could introduce performance problems if meshes are frequently mutated or there are lots of entities assigned with a mesh that's mutated. To help with this, there is a new component `NoAabbUpdate` that can be assigned to entities so that their Aabbs are not updated as their mesh changes. - We may decide to have mesh-update-events contain the list of entities affected in the future. That may simplify these systems. See [this comment][0] for more details. [0]: #4944 (comment) Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Charles <[email protected]> Co-authored-by: François <[email protected]> Co-authored-by: Nicola Papale <[email protected]> Co-authored-by: Robert Swain <[email protected]>
1 parent 9c116d5 commit 9b36e77

File tree

1 file changed

+157
-38
lines changed
  • crates/bevy_render/src/view/visibility

1 file changed

+157
-38
lines changed

crates/bevy_render/src/view/visibility/mod.rs

Lines changed: 157 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
mod render_layers;
22

3+
use smallvec::SmallVec;
4+
35
pub use render_layers::*;
46

57
use bevy_app::{CoreStage, Plugin};
6-
use bevy_asset::{Assets, Handle};
8+
use bevy_asset::{AssetEvent, Assets, Handle};
79
use bevy_ecs::prelude::*;
810
use bevy_hierarchy::{Children, Parent};
911
use bevy_reflect::std_traits::ReflectDefault;
1012
use bevy_reflect::Reflect;
1113
use bevy_transform::components::GlobalTransform;
1214
use bevy_transform::TransformSystem;
15+
use bevy_utils::HashMap;
1316
use std::cell::Cell;
1417
use thread_local::ThreadLocal;
1518

@@ -106,6 +109,11 @@ pub struct VisibilityBundle {
106109
#[derive(Component)]
107110
pub struct NoFrustumCulling;
108111

112+
/// Use this component to opt-out of built-in [`Aabb`] updating for Mesh entities (i.e. to opt out
113+
/// of [`update_bounds`]).
114+
#[derive(Component)]
115+
pub struct NoAabbUpdate;
116+
109117
/// Collection of entities visible from the current view.
110118
///
111119
/// This component contains all entities which are visible from the currently
@@ -139,9 +147,59 @@ impl VisibleEntities {
139147
}
140148
}
141149

150+
/// Tracks which [`Entities`](Entity) have which meshes for entities whose [`Aabb`]s are managed by
151+
/// the [`calculate_bounds`] and [`update_bounds`] systems. This is needed because `update_bounds`
152+
/// recomputes `Aabb`s for entities whose mesh has been mutated. These mutations are visible via
153+
/// [`AssetEvent<Mesh>`](AssetEvent) which tells us which mesh was changed but not which entities
154+
/// have that mesh.
155+
#[derive(Debug, Default, Clone)]
156+
pub struct EntityMeshMap {
157+
entities_with_mesh: HashMap<Handle<Mesh>, SmallVec<[Entity; 1]>>,
158+
mesh_for_entity: HashMap<Entity, Handle<Mesh>>,
159+
}
160+
161+
impl EntityMeshMap {
162+
/// Register the passed `entity` as having the passed `mesh_handle`.
163+
fn register(&mut self, entity: Entity, mesh_handle: &Handle<Mesh>) {
164+
// Note that this list can have duplicates if an entity is registered for a mesh multiple
165+
// times. This should be rare and only cause an additional `Aabb.clone()` in
166+
// `update_bounds` so it is preferable to a `HashSet` for now.
167+
self.entities_with_mesh
168+
.entry(mesh_handle.clone_weak())
169+
.or_default()
170+
.push(entity);
171+
self.mesh_for_entity
172+
.insert(entity, mesh_handle.clone_weak());
173+
}
174+
175+
/// Deregisters the mapping between an `Entity` and `Mesh`. Used so [`update_bounds`] can
176+
/// track which mappings are still active so `Aabb`s are updated correctly.
177+
fn deregister(&mut self, entity: Entity) {
178+
let mut inner = || {
179+
let mesh = self.mesh_for_entity.remove(&entity)?;
180+
181+
// This lookup failing is _probably_ an error.
182+
let entities = self.entities_with_mesh.get_mut(&mesh)?;
183+
184+
// There could be duplicate entries in here if an entity was registered with a mesh
185+
// multiple times. It's important to remove all references so that if an entity gets a
186+
// new mesh and its old mesh is mutated, the entity doesn't get its old mesh's new
187+
// `Aabb`. Note that there _should_ only be one entity.
188+
for i in (0..entities.len()).rev() {
189+
if entities[i] == entity {
190+
entities.swap_remove(i);
191+
}
192+
}
193+
Some(())
194+
};
195+
inner();
196+
}
197+
}
198+
142199
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
143200
pub enum VisibilitySystems {
144201
CalculateBounds,
202+
UpdateBounds,
145203
UpdateOrthographicFrusta,
146204
UpdatePerspectiveFrusta,
147205
UpdateProjectionFrusta,
@@ -157,60 +215,121 @@ impl Plugin for VisibilityPlugin {
157215
fn build(&self, app: &mut bevy_app::App) {
158216
use VisibilitySystems::*;
159217

160-
app.add_system_to_stage(
161-
CoreStage::PostUpdate,
162-
calculate_bounds.label(CalculateBounds),
163-
)
164-
.add_system_to_stage(
165-
CoreStage::PostUpdate,
166-
update_frusta::<OrthographicProjection>
167-
.label(UpdateOrthographicFrusta)
168-
.after(TransformSystem::TransformPropagate),
169-
)
170-
.add_system_to_stage(
171-
CoreStage::PostUpdate,
172-
update_frusta::<PerspectiveProjection>
173-
.label(UpdatePerspectiveFrusta)
174-
.after(TransformSystem::TransformPropagate),
175-
)
176-
.add_system_to_stage(
177-
CoreStage::PostUpdate,
178-
update_frusta::<Projection>
179-
.label(UpdateProjectionFrusta)
180-
.after(TransformSystem::TransformPropagate),
181-
)
182-
.add_system_to_stage(
183-
CoreStage::PostUpdate,
184-
visibility_propagate_system.label(VisibilityPropagate),
185-
)
186-
.add_system_to_stage(
187-
CoreStage::PostUpdate,
188-
check_visibility
189-
.label(CheckVisibility)
190-
.after(CalculateBounds)
191-
.after(UpdateOrthographicFrusta)
192-
.after(UpdatePerspectiveFrusta)
193-
.after(UpdateProjectionFrusta)
194-
.after(VisibilityPropagate)
195-
.after(TransformSystem::TransformPropagate),
196-
);
218+
app.init_resource::<EntityMeshMap>()
219+
.add_system_to_stage(
220+
CoreStage::PostUpdate,
221+
calculate_bounds.label(CalculateBounds),
222+
)
223+
.add_system_to_stage(CoreStage::PostUpdate, update_bounds.label(UpdateBounds))
224+
.add_system_to_stage(
225+
CoreStage::PostUpdate,
226+
update_frusta::<OrthographicProjection>
227+
.label(UpdateOrthographicFrusta)
228+
.after(TransformSystem::TransformPropagate),
229+
)
230+
.add_system_to_stage(
231+
CoreStage::PostUpdate,
232+
update_frusta::<PerspectiveProjection>
233+
.label(UpdatePerspectiveFrusta)
234+
.after(TransformSystem::TransformPropagate),
235+
)
236+
.add_system_to_stage(
237+
CoreStage::PostUpdate,
238+
update_frusta::<Projection>
239+
.label(UpdateProjectionFrusta)
240+
.after(TransformSystem::TransformPropagate),
241+
)
242+
.add_system_to_stage(
243+
CoreStage::PostUpdate,
244+
visibility_propagate_system.label(VisibilityPropagate),
245+
)
246+
.add_system_to_stage(
247+
CoreStage::PostUpdate,
248+
check_visibility
249+
.label(CheckVisibility)
250+
.after(CalculateBounds)
251+
.after(UpdateBounds)
252+
.after(UpdateOrthographicFrusta)
253+
.after(UpdatePerspectiveFrusta)
254+
.after(UpdateProjectionFrusta)
255+
.after(VisibilityPropagate)
256+
.after(TransformSystem::TransformPropagate),
257+
);
197258
}
198259
}
199260

261+
/// Calculates [`Aabb`]s for [`Entities`](Entity) with [`Mesh`]es.
200262
pub fn calculate_bounds(
201263
mut commands: Commands,
202264
meshes: Res<Assets<Mesh>>,
203265
without_aabb: Query<(Entity, &Handle<Mesh>), (Without<Aabb>, Without<NoFrustumCulling>)>,
266+
mut entity_mesh_map: ResMut<EntityMeshMap>,
204267
) {
205268
for (entity, mesh_handle) in &without_aabb {
206269
if let Some(mesh) = meshes.get(mesh_handle) {
207270
if let Some(aabb) = mesh.compute_aabb() {
271+
entity_mesh_map.register(entity, mesh_handle);
208272
commands.entity(entity).insert(aabb);
209273
}
210274
}
211275
}
212276
}
213277

278+
/// Updates [`Aabb`]s for [`Entities`](Entity) with [`Mesh`]es. This includes `Entities` that have
279+
/// been assigned new `Mesh`es as well as `Entities` whose `Mesh` has been directly mutated.
280+
///
281+
/// To opt out of bound calculation for an `Entity`, give it the [`NoAabbUpdate`] component.
282+
///
283+
/// NOTE: This system needs to remove entities from their collection in
284+
/// [`EntityMeshMap`] whenever a mesh handle is reassigned or an entity's mesh handle is
285+
/// removed. This may impact performance if meshes with many entities are frequently
286+
/// reassigned/removed.
287+
pub fn update_bounds(
288+
mut commands: Commands,
289+
meshes: Res<Assets<Mesh>>,
290+
mut mesh_reassigned: Query<
291+
(Entity, &Handle<Mesh>, &mut Aabb),
292+
(
293+
Changed<Handle<Mesh>>,
294+
Without<NoFrustumCulling>,
295+
Without<NoAabbUpdate>,
296+
),
297+
>,
298+
mut entity_mesh_map: ResMut<EntityMeshMap>,
299+
mut mesh_events: EventReader<AssetEvent<Mesh>>,
300+
entities_lost_mesh: RemovedComponents<Handle<Mesh>>,
301+
) {
302+
for entity in entities_lost_mesh.iter() {
303+
entity_mesh_map.deregister(entity);
304+
}
305+
306+
for (entity, mesh_handle, mut aabb) in mesh_reassigned.iter_mut() {
307+
entity_mesh_map.deregister(entity);
308+
if let Some(mesh) = meshes.get(mesh_handle) {
309+
if let Some(new_aabb) = mesh.compute_aabb() {
310+
entity_mesh_map.register(entity, mesh_handle);
311+
*aabb = new_aabb;
312+
}
313+
}
314+
}
315+
316+
let to_update = |event: &AssetEvent<Mesh>| {
317+
let handle = match event {
318+
AssetEvent::Modified { handle } => handle,
319+
_ => return None,
320+
};
321+
let mesh = meshes.get(handle)?;
322+
let entities_with_handle = entity_mesh_map.entities_with_mesh.get(handle)?;
323+
let aabb = mesh.compute_aabb()?;
324+
Some((aabb, entities_with_handle))
325+
};
326+
for (aabb, entities_with_handle) in mesh_events.iter().filter_map(to_update) {
327+
for entity in entities_with_handle {
328+
commands.entity(*entity).insert(aabb.clone());
329+
}
330+
}
331+
}
332+
214333
pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
215334
mut views: Query<(&GlobalTransform, &T, &mut Frustum)>,
216335
) {

0 commit comments

Comments
 (0)