Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Recalculate entity aabbs when meshes change #4944

Closed
wants to merge 1 commit into from
Closed

[Merged by Bors] - Recalculate entity aabbs when meshes change #4944

wants to merge 1 commit into from

Conversation

mlodato517
Copy link
Contributor

@mlodato517 mlodato517 commented Jun 6, 2022

Objective

Update the calculate_bounds system to update Aabbs
for entities who've either:

  • gotten a new mesh
  • had their mesh mutated

Fixes #4294.

Solution

There are two commits here to address the two issues above:

Commit 1

This Commit
Updates the calculate_bounds system to operate not only on entities
without Aabbs but also on entities whose Handle<Mesh> has changed.

Why?
So if an entity gets a new mesh, its associated Aabb is properly
recalculated.

Questions

  • This type is getting pretty gnarly - should I extract some types?
  • This system is public - should I add some quick docs while I'm here?

Commit 2

This Commit
Updates calculate_bounds to update Aabbs of entities whose meshes
have been directly mutated.

Why?
So if an entity's mesh gets updated, its associated Aabb is properly
recalculated.

Questions

  • I think we should be using ahash. Do we want to do that with a
    direct hashbrown dependency or an ahash dependency that we
    configure the HashMap with?
  • There is an edge case of duplicates with Vec<Entity> in the
    HashMap. If an entity gets its mesh handle changed and changed back
    again it'll be added to the list twice. Do we want to use a HashSet
    to avoid that? Or do a check in the list first (assuming iterating
    over the Vec is faster and this edge case is rare)?
  • There is an edge case where, if an entity gets a new mesh handle and
    then its old mesh is updated, we'll update the entity's Aabb to the
    new geometry of the old mesh. Do we want to remove items from the
    Local<HashMap> when handles change? Does the Changed event give us
    the old mesh handle? If not we might need to have a
    HashMap<Entity, Handle<Mesh>> or something so we can unlink entities
    from mesh handles when the handle changes.
  • I did the zip() with the two HashMap gets assuming those would
    be faster than calculating the Aabb of the mesh (otherwise we could do
    meshes.get(mesh_handle).and_then(Mesh::compute_aabb).zip(entity_mesh_map...)
    or something). Is that assumption way off?

Testing

I originally tried testing this with bevy_mod_raycast as mentioned in the
original issue but it seemed to work (maybe they are currently manually
updating the Aabbs?). I then tried doing it in 2D but it looks like
Handle<Mesh> is just for 3D. So I took this example
and added some systems to mutate/assign meshes:

Test Script
use bevy::prelude::*;
use bevy::render::camera::ScalingMode;
use bevy::render::primitives::Aabb;

/// Make sure we only mutate one mesh once.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct MutateMeshState(bool);

/// Let's have a few global meshes that we can cycle between.
/// This way we can be assigned a new mesh, mutate the old one, and then get the old one assigned.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct Meshes(Vec<Handle<Mesh>>);

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_resource::<MutateMeshState>()
        .init_resource::<Meshes>()
        .add_startup_system(setup)
        .add_system(assign_new_mesh)
        .add_system(show_aabbs.after(assign_new_mesh))
        .add_system(mutate_meshes.after(show_aabbs))
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut global_meshes: ResMut<Meshes>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let m1 = meshes.add(Mesh::from(shape::Icosphere::default()));
    let m2 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.90,
        ..Default::default()
    }));
    let m3 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.80,
        ..Default::default()
    }));
    global_meshes.0.push(m1.clone());
    global_meshes.0.push(m2);
    global_meshes.0.push(m3);

    // add entities to the world
    // sphere
    commands.spawn_bundle(PbrBundle {
        mesh: m1,
        material: materials.add(StandardMaterial {
            base_color: Color::hex("ffd891").unwrap(),
            ..default()
        }),
        ..default()
    });
    // new 3d camera
    commands.spawn_bundle(Camera3dBundle {
        projection: OrthographicProjection {
            scale: 3.0,
            scaling_mode: ScalingMode::FixedVertical(1.0),
            ..default()
        }
        .into(),
        ..default()
    });

    // old 3d camera
    // commands.spawn_bundle(OrthographicCameraBundle {
    //     transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
    //     orthographic_projection: OrthographicProjection {
    //         scale: 0.01,
    //         ..default()
    //     },
    //     ..OrthographicCameraBundle::new_3d()
    // });
}
fn show_aabbs(query: Query<(Entity, &Handle<Mesh>, &Aabb)>) {
    for thing in query.iter() {
        println!("{thing:?}");
    }
}

/// For testing the second part - mutating a mesh.
///
/// Without the fix we should see this mutate an old mesh and it affects the new mesh that the
/// entity currently has.
/// With the fix, the mutation doesn't affect anything until the entity is reassigned the old mesh.
fn mutate_meshes(
    mut meshes: ResMut<Assets<Mesh>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
    mut mutate_mesh_state: ResMut<MutateMeshState>,
) {
    let mutated = mutate_mesh_state.0;
    if time.seconds_since_startup() > 4.5 && !mutated {
        println!("Mutating {:?}", global_meshes.0[0]);
        let m = meshes.get_mut(&global_meshes.0[0]).unwrap();
        let mut p = m.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().clone();
        use bevy::render::mesh::VertexAttributeValues;
        match &mut p {
            VertexAttributeValues::Float32x3(v) => {
                v[0] = [10.0, 10.0, 10.0];
            }
            _ => unreachable!(),
        }
        m.insert_attribute(Mesh::ATTRIBUTE_POSITION, p);
        mutate_mesh_state.0 = true;
    }
}

/// For testing the first part - assigning a new handle.
fn assign_new_mesh(
    mut query: Query<&mut Handle<Mesh>, With<Aabb>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
) {
    let s = time.seconds_since_startup() as usize;
    let idx = s % global_meshes.0.len();
    for mut handle in query.iter_mut() {
        *handle = global_meshes.0[idx].clone_weak();
    }
}

Changelog

Fixed

Entity Aabbs not updating when meshes are mutated or re-assigned.

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-Math Fundamental domain-agnostic mathematical operations labels Jun 6, 2022
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
@alice-i-cecile
Copy link
Member

This is a fantastic contribution; thank you!

This type is getting pretty gnarly - should I extract some types?

I don't think so; it's not reused, and it'll result in indirections. You get good at reading query signatures with time. My main gripe with it is the double negative Without<NoFrustumCulling>, but that's not your fault.

This system is public - should I add some quick docs while I'm here?

Yes please!

I think we should be using ahash. Do we want to do that with a
direct hashbrown dependency or an ahash dependency that we
configure the HashMap with?

bevy_utils::HashMap does exactly this; use it instead.

There is an edge case of duplicates with Vec<Entity> in the
HashMap. If an entity gets its mesh handle changed and changed back
again it'll be added to the list twice. Do we want to use a HashSet
to avoid that? Or do a check in the list first (assuming iterating
over the Vec is faster and this edge case is rare)?

I would expect this edge case to be very rare, and the costs of using the HashSet non-trivial when we're modifying large numbers of mesh handles at once. IMO we should just repeat the work.

Please leave a comment to this effect :)

There is an edge case where, if an entity gets a new mesh handle and
then its old mesh is updated, we'll update the entity's Aabb to the
new geometry of the old mesh. Do we want to remove items from the
Local<HashMap> when handles change? Does the Changed event give us
the old mesh handle? If not we might need to have a
HashMap<Entity, Handle<Mesh>> or something so we can unlink entities
from mesh handles when the handle changes.

The Changed information won't cache the old data. I'll need to chew on what should be done here.

I did the zip() with the two HashMap gets assuming those would
be faster than calculating the Aabb of the mesh (otherwise we could do
meshes.get(mesh_handle).and_then(Mesh::compute_aabb).zip(entity_mesh_map...)
or something). Is that assumption way off?

I have no good intuition for this particular question.

@mlodato517 mlodato517 marked this pull request as ready for review June 6, 2022 14:18
Copy link
Contributor

@nicopap nicopap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the question on the edge case. I think you are right. We'd need to keep a HashMap<Entity, Handle<Mesh>>. So that it's possible to remove the Entity from right entry when things gets updated or removed (I think it might be less complex and more performant this way) (also check RemovedComponents for the case when an entity is despawned or have their Handle<Mesh> removed).

You can also have a update_bounds systems that update in-place the Aabbs with a Query<&mut Aabb>, this way, the update logic is separated from the create logic and the Aabbs are updated as soon as the update_bounds system is complete. but I don't think it's a big deal.

crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
@superdump
Copy link
Contributor

As the original author of the frustum culling code, I’d like to review this when I have time.

@mlodato517
Copy link
Contributor Author

mlodato517 commented Jun 6, 2022

Updated some of the easy stuff. Will tackle the more complicated stuff later today hopefully!

I'll want to squash this before merging and add co-authors but for now hopefully this makes review easier.

Copy link
Contributor

@superdump superdump left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much looks good to me, just a comment about what I perceive to be unnecessary documentation. Maybe @alice-i-cecile disagrees?

crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
@mlodato517
Copy link
Contributor Author

We'd need to keep a HashMap<Entity, Handle<Mesh>>. So that it's possible to remove the Entity from right entry when things gets updated or removed (I think it might be less complex and more performant this way) (also check RemovedComponents for the case when an entity is despawned or have their Handle<Mesh> removed).

You can also have a update_bounds systems that update in-place the Aabbs with a Query<&mut Aabb>, this way, the update logic is separated from the create logic and the Aabbs are updated as soon as the update_bounds system is complete. but I don't think it's a big deal.

I'm a little confused here. I like the idea of the separate update_bounds system because it sounds much cleaner but it won't have access to calculate_bound's Local<HashMap>. If we want to move the entire Local<HashMap> to update_bounds then I think it'll be hard to tell when to add an entity to a mesh handle's bucket.

Here's the pseudocode I was imagining - lemme know if that was the wrong read:

pseudocode
fn update_bounds(
    meshes: Res<Assets<Mesh>>,
    mesh_events: EventReader<AssetEvent<Mesh>>,
    mut to_update: Query<
        (Entity, &mut Aabb, &Handle<Mesh>),
        With<Changed<Handle<Mesh>>>,
    >,
    mut entities_with_mesh: Local<HashMap<Mesh, Entities>>,
    mut mesh_for_entity: Local<HashMap<Entity, Mesh>>,
    mut entities_with_removed_mesh: RemovedComponents<Handle<Mesh>>,
) {
    // If an entity no longer has a mesh, remove it from the list
    // of entities with that mesh.
    for entity in entities_with_removed_mesh {
        let mesh = &mesh_for_entity[entity];
        let entities_with_mesh = &mut entities_with_mesh[mesh];
        entities_with_mesh.swap_remove(get_idx(entity));
    }

    // If a mesh was mutated, update the Aabb for all entities with that mesh
    for event in mesh_events {
        let entities_with_mesh = &entities_with_mesh[event.mesh];
        let mesh = &meshes[event.mesh];
        let aabb = mesh.aabb();
        entities_with_mesh.iter_mut().for_each(|e| e.aabb = aabb.clone());
    }

    // Update Aabb for entities that got a new mesh
    for (entity, &mut aabb, mesh) in to_update {
        let mesh = meshes[mesh];
        let new_aabb = mesh.aabb();
        *aabb = new_aabb;

        // Does it go here? Does `Changed` work when things are added
        // for the first time? If not we'll probably fail on some mesh mutation
        // events. Maybe we can use `ChangeTrackers` to get changed _and_ added?
        entities_with_mesh.insert(mesh, entity);
    }
}

@nicopap
Copy link
Contributor

nicopap commented Jun 7, 2022

@mlodato517 You would need to have the HashMap<Mesh, Entities> and HashMap<Entity, Mesh> in a Res shared between the two systems, probably create a new struct and have it inserted as a resource.

@mlodato517
Copy link
Contributor Author

Added the maps in a resource in 5ee488a and added an update_bounds system. Hopefully this is somewhere along the lines of what y'all were thinking 🤞

@mlodato517
Copy link
Contributor Author

Huh the rerequest button doesn’t seem to be working for nicopap 🤷

@james7132
Copy link
Member

james7132 commented Jun 9, 2022

It's not something I want us to address in this PR, but skinned meshes are an extreme example of this, where the AABB both out of date and inaccessible basically every frame. We probably will need an exception for skinned meshes, and precompute a "extremes" bound for them using asset preprocessing of some kind.

Copy link
Contributor

@nicopap nicopap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Fixes #4294 alright. I think the comments on documentation should be addressed and then it's good for me.

@nicopap
Copy link
Contributor

nicopap commented Jun 9, 2022

It's not something I want us to address in this PR, but skinned meshes are an extreme example of this, where the AABB both out of date and inaccessible basically every frame. We probably will need an exception for skinned meshes, and precompute a "extremes" bound for them using asset preprocessing of some kind.

@james7132 I opened an issue so that it's possible to discuss this independently in #4971

Copy link
Contributor

@superdump superdump left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically LGTM. :)

crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
@alice-i-cecile alice-i-cecile added this to the Bevy 0.8 milestone Jul 18, 2022
Copy link
Contributor

@superdump superdump left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still basically looks good to me but spotted another thing. :) Thanks for your patience and persistence. I think we should be able to merge this very soon.

CoreStage::PostUpdate,
calculate_bounds.label(CalculateBounds),
)
.add_system_to_stage(CoreStage::PostUpdate, update_bounds.label(UpdateBounds))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_visibility must be run after this has run. Please add this to the list of check_visibility.after() systems below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent catch! Added in 9b36e77.

**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 <alice.i.cecile@gmail.com>
Co-authored-by: Charles <c.giguere42@gmail.com>
Co-authored-by: François <mockersf@gmail.com>
Co-authored-by: Nicola Papale <nico@nicopap.ch>
Co-authored-by: Robert Swain <robert.swain@gmail.com>
@superdump
Copy link
Contributor

@alice-i-cecile @mockersf you previously reviewed this and left comments. Do you want to review again?

@mockersf mockersf added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Jul 20, 2022
Copy link
Contributor

@superdump superdump left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bors r+

bors bot pushed a commit that referenced this pull request Jul 20, 2022
# Objective

Update the `calculate_bounds` system to update `Aabb`s
for entities who've either:
- gotten a new mesh
- had their mesh mutated

Fixes #4294.

## Solution

There are two commits here to address the two issues above:

### Commit 1

**This Commit**
Updates the `calculate_bounds` system to operate not only on entities
without `Aabb`s but also on entities whose `Handle<Mesh>` has changed.

**Why?**
So if an entity gets a new mesh, its associated `Aabb` is properly
recalculated.

**Questions**
- This type is getting pretty gnarly - should I extract some types?
- This system is public - should I add some quick docs while I'm here?

### Commit 2

**This Commit**
Updates `calculate_bounds` to update `Aabb`s of entities whose meshes
have been directly mutated.

**Why?**
So if an entity's mesh gets updated, its associated `Aabb` is properly
recalculated.

**Questions**
- I think we should be using `ahash`. Do we want to do that with a
  direct `hashbrown` dependency or an `ahash` dependency that we
  configure the `HashMap` with?
- There is an edge case of duplicates with `Vec<Entity>` in the
  `HashMap`. If an entity gets its mesh handle changed and changed back
  again it'll be added to the list twice. Do we want to use a `HashSet`
  to avoid that? Or do a check in the list first (assuming iterating
  over the `Vec` is faster and this edge case is rare)?
- There is an edge case where, if an entity gets a new mesh handle and
  then its old mesh is updated, we'll update the entity's `Aabb` to the
  new geometry of the _old_ mesh. Do we want to remove items from the
  `Local<HashMap>` when handles change? Does the `Changed` event give us
  the old mesh handle? If not we might need to have a
  `HashMap<Entity, Handle<Mesh>>` or something so we can unlink entities
  from mesh handles when the handle changes.
- I did the `zip()` with the two `HashMap` gets assuming those would
  be faster than calculating the Aabb of the mesh (otherwise we could do
  `meshes.get(mesh_handle).and_then(Mesh::compute_aabb).zip(entity_mesh_map...)`
  or something). Is that assumption way off?

## Testing

I originally tried testing this with `bevy_mod_raycast` as mentioned in the
original issue but it seemed to work (maybe they are currently manually
updating the Aabbs?). I then tried doing it in 2D but it looks like
`Handle<Mesh>` is just for 3D. So I took [this example](https://github.com/bevyengine/bevy/blob/main/examples/3d/pbr.rs)
and added some systems to mutate/assign meshes:

<details>
<summary>Test Script</summary>

```rust
use bevy::prelude::*;
use bevy::render::camera::ScalingMode;
use bevy::render::primitives::Aabb;

/// Make sure we only mutate one mesh once.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct MutateMeshState(bool);

/// Let's have a few global meshes that we can cycle between.
/// This way we can be assigned a new mesh, mutate the old one, and then get the old one assigned.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct Meshes(Vec<Handle<Mesh>>);

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_resource::<MutateMeshState>()
        .init_resource::<Meshes>()
        .add_startup_system(setup)
        .add_system(assign_new_mesh)
        .add_system(show_aabbs.after(assign_new_mesh))
        .add_system(mutate_meshes.after(show_aabbs))
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut global_meshes: ResMut<Meshes>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let m1 = meshes.add(Mesh::from(shape::Icosphere::default()));
    let m2 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.90,
        ..Default::default()
    }));
    let m3 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.80,
        ..Default::default()
    }));
    global_meshes.0.push(m1.clone());
    global_meshes.0.push(m2);
    global_meshes.0.push(m3);

    // add entities to the world
    // sphere
    commands.spawn_bundle(PbrBundle {
        mesh: m1,
        material: materials.add(StandardMaterial {
            base_color: Color::hex("ffd891").unwrap(),
            ..default()
        }),
        ..default()
    });
    // new 3d camera
    commands.spawn_bundle(Camera3dBundle {
        projection: OrthographicProjection {
            scale: 3.0,
            scaling_mode: ScalingMode::FixedVertical(1.0),
            ..default()
        }
        .into(),
        ..default()
    });

    // old 3d camera
    // commands.spawn_bundle(OrthographicCameraBundle {
    //     transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
    //     orthographic_projection: OrthographicProjection {
    //         scale: 0.01,
    //         ..default()
    //     },
    //     ..OrthographicCameraBundle::new_3d()
    // });
}
fn show_aabbs(query: Query<(Entity, &Handle<Mesh>, &Aabb)>) {
    for thing in query.iter() {
        println!("{thing:?}");
    }
}

/// For testing the second part - mutating a mesh.
///
/// Without the fix we should see this mutate an old mesh and it affects the new mesh that the
/// entity currently has.
/// With the fix, the mutation doesn't affect anything until the entity is reassigned the old mesh.
fn mutate_meshes(
    mut meshes: ResMut<Assets<Mesh>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
    mut mutate_mesh_state: ResMut<MutateMeshState>,
) {
    let mutated = mutate_mesh_state.0;
    if time.seconds_since_startup() > 4.5 && !mutated {
        println!("Mutating {:?}", global_meshes.0[0]);
        let m = meshes.get_mut(&global_meshes.0[0]).unwrap();
        let mut p = m.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().clone();
        use bevy::render::mesh::VertexAttributeValues;
        match &mut p {
            VertexAttributeValues::Float32x3(v) => {
                v[0] = [10.0, 10.0, 10.0];
            }
            _ => unreachable!(),
        }
        m.insert_attribute(Mesh::ATTRIBUTE_POSITION, p);
        mutate_mesh_state.0 = true;
    }
}

/// For testing the first part - assigning a new handle.
fn assign_new_mesh(
    mut query: Query<&mut Handle<Mesh>, With<Aabb>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
) {
    let s = time.seconds_since_startup() as usize;
    let idx = s % global_meshes.0.len();
    for mut handle in query.iter_mut() {
        *handle = global_meshes.0[idx].clone_weak();
    }
}
```

</details>

## Changelog

### Fixed
Entity `Aabb`s not updating when meshes are mutated or re-assigned.
@bors bors bot changed the title Recalculate entity aabbs when meshes change [Merged by Bors] - Recalculate entity aabbs when meshes change Jul 20, 2022
@bors bors bot closed this Jul 20, 2022
cart added a commit to cart/bevy that referenced this pull request Jul 29, 2022
bors bot pushed a commit that referenced this pull request Jul 29, 2022
# Objective

Sadly, #4944 introduces a serious exponential despawn behavior, which cannot be included in 0.8. [Handling AABBs properly is a controversial topic](#5423 (comment)) and one that deserves more time than the day we have left before release.

## Solution

This reverts commit c2b332f.
inodentry pushed a commit to IyesGames/bevy that referenced this pull request Aug 8, 2022
**Why?**
The `value_parser` `clap` attribute was added in
[version 3.2.0][0]. With the current version of `3.1.12` users can get
errors like:

```
error: unexpected attribute: value_parser
  --> tools/spancmp/src/main.rs:18:25
   |
18 |     #[clap(short, long, value_parser, default_value_t = 0.0)]
   |                         ^^^^^^^^^^^^
```

See bevyengine#4944 (comment) for more details.

[0]: https://github.com/clap-rs/clap/blob/master/CHANGELOG.md#320---2022-06-13
inodentry pushed a commit to IyesGames/bevy that referenced this pull request Aug 8, 2022
# Objective

Update the `calculate_bounds` system to update `Aabb`s
for entities who've either:
- gotten a new mesh
- had their mesh mutated

Fixes bevyengine#4294.

## Solution

There are two commits here to address the two issues above:

### Commit 1

**This Commit**
Updates the `calculate_bounds` system to operate not only on entities
without `Aabb`s but also on entities whose `Handle<Mesh>` has changed.

**Why?**
So if an entity gets a new mesh, its associated `Aabb` is properly
recalculated.

**Questions**
- This type is getting pretty gnarly - should I extract some types?
- This system is public - should I add some quick docs while I'm here?

### Commit 2

**This Commit**
Updates `calculate_bounds` to update `Aabb`s of entities whose meshes
have been directly mutated.

**Why?**
So if an entity's mesh gets updated, its associated `Aabb` is properly
recalculated.

**Questions**
- I think we should be using `ahash`. Do we want to do that with a
  direct `hashbrown` dependency or an `ahash` dependency that we
  configure the `HashMap` with?
- There is an edge case of duplicates with `Vec<Entity>` in the
  `HashMap`. If an entity gets its mesh handle changed and changed back
  again it'll be added to the list twice. Do we want to use a `HashSet`
  to avoid that? Or do a check in the list first (assuming iterating
  over the `Vec` is faster and this edge case is rare)?
- There is an edge case where, if an entity gets a new mesh handle and
  then its old mesh is updated, we'll update the entity's `Aabb` to the
  new geometry of the _old_ mesh. Do we want to remove items from the
  `Local<HashMap>` when handles change? Does the `Changed` event give us
  the old mesh handle? If not we might need to have a
  `HashMap<Entity, Handle<Mesh>>` or something so we can unlink entities
  from mesh handles when the handle changes.
- I did the `zip()` with the two `HashMap` gets assuming those would
  be faster than calculating the Aabb of the mesh (otherwise we could do
  `meshes.get(mesh_handle).and_then(Mesh::compute_aabb).zip(entity_mesh_map...)`
  or something). Is that assumption way off?

## Testing

I originally tried testing this with `bevy_mod_raycast` as mentioned in the
original issue but it seemed to work (maybe they are currently manually
updating the Aabbs?). I then tried doing it in 2D but it looks like
`Handle<Mesh>` is just for 3D. So I took [this example](https://github.com/bevyengine/bevy/blob/main/examples/3d/pbr.rs)
and added some systems to mutate/assign meshes:

<details>
<summary>Test Script</summary>

```rust
use bevy::prelude::*;
use bevy::render::camera::ScalingMode;
use bevy::render::primitives::Aabb;

/// Make sure we only mutate one mesh once.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct MutateMeshState(bool);

/// Let's have a few global meshes that we can cycle between.
/// This way we can be assigned a new mesh, mutate the old one, and then get the old one assigned.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct Meshes(Vec<Handle<Mesh>>);

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_resource::<MutateMeshState>()
        .init_resource::<Meshes>()
        .add_startup_system(setup)
        .add_system(assign_new_mesh)
        .add_system(show_aabbs.after(assign_new_mesh))
        .add_system(mutate_meshes.after(show_aabbs))
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut global_meshes: ResMut<Meshes>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let m1 = meshes.add(Mesh::from(shape::Icosphere::default()));
    let m2 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.90,
        ..Default::default()
    }));
    let m3 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.80,
        ..Default::default()
    }));
    global_meshes.0.push(m1.clone());
    global_meshes.0.push(m2);
    global_meshes.0.push(m3);

    // add entities to the world
    // sphere
    commands.spawn_bundle(PbrBundle {
        mesh: m1,
        material: materials.add(StandardMaterial {
            base_color: Color::hex("ffd891").unwrap(),
            ..default()
        }),
        ..default()
    });
    // new 3d camera
    commands.spawn_bundle(Camera3dBundle {
        projection: OrthographicProjection {
            scale: 3.0,
            scaling_mode: ScalingMode::FixedVertical(1.0),
            ..default()
        }
        .into(),
        ..default()
    });

    // old 3d camera
    // commands.spawn_bundle(OrthographicCameraBundle {
    //     transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
    //     orthographic_projection: OrthographicProjection {
    //         scale: 0.01,
    //         ..default()
    //     },
    //     ..OrthographicCameraBundle::new_3d()
    // });
}
fn show_aabbs(query: Query<(Entity, &Handle<Mesh>, &Aabb)>) {
    for thing in query.iter() {
        println!("{thing:?}");
    }
}

/// For testing the second part - mutating a mesh.
///
/// Without the fix we should see this mutate an old mesh and it affects the new mesh that the
/// entity currently has.
/// With the fix, the mutation doesn't affect anything until the entity is reassigned the old mesh.
fn mutate_meshes(
    mut meshes: ResMut<Assets<Mesh>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
    mut mutate_mesh_state: ResMut<MutateMeshState>,
) {
    let mutated = mutate_mesh_state.0;
    if time.seconds_since_startup() > 4.5 && !mutated {
        println!("Mutating {:?}", global_meshes.0[0]);
        let m = meshes.get_mut(&global_meshes.0[0]).unwrap();
        let mut p = m.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().clone();
        use bevy::render::mesh::VertexAttributeValues;
        match &mut p {
            VertexAttributeValues::Float32x3(v) => {
                v[0] = [10.0, 10.0, 10.0];
            }
            _ => unreachable!(),
        }
        m.insert_attribute(Mesh::ATTRIBUTE_POSITION, p);
        mutate_mesh_state.0 = true;
    }
}

/// For testing the first part - assigning a new handle.
fn assign_new_mesh(
    mut query: Query<&mut Handle<Mesh>, With<Aabb>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
) {
    let s = time.seconds_since_startup() as usize;
    let idx = s % global_meshes.0.len();
    for mut handle in query.iter_mut() {
        *handle = global_meshes.0[idx].clone_weak();
    }
}
```

</details>

## Changelog

### Fixed
Entity `Aabb`s not updating when meshes are mutated or re-assigned.
inodentry pushed a commit to IyesGames/bevy that referenced this pull request Aug 8, 2022
bevyengine#5489)

# Objective

Sadly, bevyengine#4944 introduces a serious exponential despawn behavior, which cannot be included in 0.8. [Handling AABBs properly is a controversial topic](bevyengine#5423 (comment)) and one that deserves more time than the day we have left before release.

## Solution

This reverts commit c2b332f.
james7132 pushed a commit to james7132/bevy that referenced this pull request Oct 28, 2022
**Why?**
The `value_parser` `clap` attribute was added in
[version 3.2.0][0]. With the current version of `3.1.12` users can get
errors like:

```
error: unexpected attribute: value_parser
  --> tools/spancmp/src/main.rs:18:25
   |
18 |     #[clap(short, long, value_parser, default_value_t = 0.0)]
   |                         ^^^^^^^^^^^^
```

See bevyengine#4944 (comment) for more details.

[0]: https://github.com/clap-rs/clap/blob/master/CHANGELOG.md#320---2022-06-13
james7132 pushed a commit to james7132/bevy that referenced this pull request Oct 28, 2022
# Objective

Update the `calculate_bounds` system to update `Aabb`s
for entities who've either:
- gotten a new mesh
- had their mesh mutated

Fixes bevyengine#4294.

## Solution

There are two commits here to address the two issues above:

### Commit 1

**This Commit**
Updates the `calculate_bounds` system to operate not only on entities
without `Aabb`s but also on entities whose `Handle<Mesh>` has changed.

**Why?**
So if an entity gets a new mesh, its associated `Aabb` is properly
recalculated.

**Questions**
- This type is getting pretty gnarly - should I extract some types?
- This system is public - should I add some quick docs while I'm here?

### Commit 2

**This Commit**
Updates `calculate_bounds` to update `Aabb`s of entities whose meshes
have been directly mutated.

**Why?**
So if an entity's mesh gets updated, its associated `Aabb` is properly
recalculated.

**Questions**
- I think we should be using `ahash`. Do we want to do that with a
  direct `hashbrown` dependency or an `ahash` dependency that we
  configure the `HashMap` with?
- There is an edge case of duplicates with `Vec<Entity>` in the
  `HashMap`. If an entity gets its mesh handle changed and changed back
  again it'll be added to the list twice. Do we want to use a `HashSet`
  to avoid that? Or do a check in the list first (assuming iterating
  over the `Vec` is faster and this edge case is rare)?
- There is an edge case where, if an entity gets a new mesh handle and
  then its old mesh is updated, we'll update the entity's `Aabb` to the
  new geometry of the _old_ mesh. Do we want to remove items from the
  `Local<HashMap>` when handles change? Does the `Changed` event give us
  the old mesh handle? If not we might need to have a
  `HashMap<Entity, Handle<Mesh>>` or something so we can unlink entities
  from mesh handles when the handle changes.
- I did the `zip()` with the two `HashMap` gets assuming those would
  be faster than calculating the Aabb of the mesh (otherwise we could do
  `meshes.get(mesh_handle).and_then(Mesh::compute_aabb).zip(entity_mesh_map...)`
  or something). Is that assumption way off?

## Testing

I originally tried testing this with `bevy_mod_raycast` as mentioned in the
original issue but it seemed to work (maybe they are currently manually
updating the Aabbs?). I then tried doing it in 2D but it looks like
`Handle<Mesh>` is just for 3D. So I took [this example](https://github.com/bevyengine/bevy/blob/main/examples/3d/pbr.rs)
and added some systems to mutate/assign meshes:

<details>
<summary>Test Script</summary>

```rust
use bevy::prelude::*;
use bevy::render::camera::ScalingMode;
use bevy::render::primitives::Aabb;

/// Make sure we only mutate one mesh once.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct MutateMeshState(bool);

/// Let's have a few global meshes that we can cycle between.
/// This way we can be assigned a new mesh, mutate the old one, and then get the old one assigned.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct Meshes(Vec<Handle<Mesh>>);

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_resource::<MutateMeshState>()
        .init_resource::<Meshes>()
        .add_startup_system(setup)
        .add_system(assign_new_mesh)
        .add_system(show_aabbs.after(assign_new_mesh))
        .add_system(mutate_meshes.after(show_aabbs))
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut global_meshes: ResMut<Meshes>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let m1 = meshes.add(Mesh::from(shape::Icosphere::default()));
    let m2 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.90,
        ..Default::default()
    }));
    let m3 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.80,
        ..Default::default()
    }));
    global_meshes.0.push(m1.clone());
    global_meshes.0.push(m2);
    global_meshes.0.push(m3);

    // add entities to the world
    // sphere
    commands.spawn_bundle(PbrBundle {
        mesh: m1,
        material: materials.add(StandardMaterial {
            base_color: Color::hex("ffd891").unwrap(),
            ..default()
        }),
        ..default()
    });
    // new 3d camera
    commands.spawn_bundle(Camera3dBundle {
        projection: OrthographicProjection {
            scale: 3.0,
            scaling_mode: ScalingMode::FixedVertical(1.0),
            ..default()
        }
        .into(),
        ..default()
    });

    // old 3d camera
    // commands.spawn_bundle(OrthographicCameraBundle {
    //     transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
    //     orthographic_projection: OrthographicProjection {
    //         scale: 0.01,
    //         ..default()
    //     },
    //     ..OrthographicCameraBundle::new_3d()
    // });
}
fn show_aabbs(query: Query<(Entity, &Handle<Mesh>, &Aabb)>) {
    for thing in query.iter() {
        println!("{thing:?}");
    }
}

/// For testing the second part - mutating a mesh.
///
/// Without the fix we should see this mutate an old mesh and it affects the new mesh that the
/// entity currently has.
/// With the fix, the mutation doesn't affect anything until the entity is reassigned the old mesh.
fn mutate_meshes(
    mut meshes: ResMut<Assets<Mesh>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
    mut mutate_mesh_state: ResMut<MutateMeshState>,
) {
    let mutated = mutate_mesh_state.0;
    if time.seconds_since_startup() > 4.5 && !mutated {
        println!("Mutating {:?}", global_meshes.0[0]);
        let m = meshes.get_mut(&global_meshes.0[0]).unwrap();
        let mut p = m.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().clone();
        use bevy::render::mesh::VertexAttributeValues;
        match &mut p {
            VertexAttributeValues::Float32x3(v) => {
                v[0] = [10.0, 10.0, 10.0];
            }
            _ => unreachable!(),
        }
        m.insert_attribute(Mesh::ATTRIBUTE_POSITION, p);
        mutate_mesh_state.0 = true;
    }
}

/// For testing the first part - assigning a new handle.
fn assign_new_mesh(
    mut query: Query<&mut Handle<Mesh>, With<Aabb>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
) {
    let s = time.seconds_since_startup() as usize;
    let idx = s % global_meshes.0.len();
    for mut handle in query.iter_mut() {
        *handle = global_meshes.0[idx].clone_weak();
    }
}
```

</details>

## Changelog

### Fixed
Entity `Aabb`s not updating when meshes are mutated or re-assigned.
james7132 pushed a commit to james7132/bevy that referenced this pull request Oct 28, 2022
bevyengine#5489)

# Objective

Sadly, bevyengine#4944 introduces a serious exponential despawn behavior, which cannot be included in 0.8. [Handling AABBs properly is a controversial topic](bevyengine#5423 (comment)) and one that deserves more time than the day we have left before release.

## Solution

This reverts commit c2b332f.
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
**Why?**
The `value_parser` `clap` attribute was added in
[version 3.2.0][0]. With the current version of `3.1.12` users can get
errors like:

```
error: unexpected attribute: value_parser
  --> tools/spancmp/src/main.rs:18:25
   |
18 |     #[clap(short, long, value_parser, default_value_t = 0.0)]
   |                         ^^^^^^^^^^^^
```

See bevyengine#4944 (comment) for more details.

[0]: https://github.com/clap-rs/clap/blob/master/CHANGELOG.md#320---2022-06-13
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective

Update the `calculate_bounds` system to update `Aabb`s
for entities who've either:
- gotten a new mesh
- had their mesh mutated

Fixes bevyengine#4294.

## Solution

There are two commits here to address the two issues above:

### Commit 1

**This Commit**
Updates the `calculate_bounds` system to operate not only on entities
without `Aabb`s but also on entities whose `Handle<Mesh>` has changed.

**Why?**
So if an entity gets a new mesh, its associated `Aabb` is properly
recalculated.

**Questions**
- This type is getting pretty gnarly - should I extract some types?
- This system is public - should I add some quick docs while I'm here?

### Commit 2

**This Commit**
Updates `calculate_bounds` to update `Aabb`s of entities whose meshes
have been directly mutated.

**Why?**
So if an entity's mesh gets updated, its associated `Aabb` is properly
recalculated.

**Questions**
- I think we should be using `ahash`. Do we want to do that with a
  direct `hashbrown` dependency or an `ahash` dependency that we
  configure the `HashMap` with?
- There is an edge case of duplicates with `Vec<Entity>` in the
  `HashMap`. If an entity gets its mesh handle changed and changed back
  again it'll be added to the list twice. Do we want to use a `HashSet`
  to avoid that? Or do a check in the list first (assuming iterating
  over the `Vec` is faster and this edge case is rare)?
- There is an edge case where, if an entity gets a new mesh handle and
  then its old mesh is updated, we'll update the entity's `Aabb` to the
  new geometry of the _old_ mesh. Do we want to remove items from the
  `Local<HashMap>` when handles change? Does the `Changed` event give us
  the old mesh handle? If not we might need to have a
  `HashMap<Entity, Handle<Mesh>>` or something so we can unlink entities
  from mesh handles when the handle changes.
- I did the `zip()` with the two `HashMap` gets assuming those would
  be faster than calculating the Aabb of the mesh (otherwise we could do
  `meshes.get(mesh_handle).and_then(Mesh::compute_aabb).zip(entity_mesh_map...)`
  or something). Is that assumption way off?

## Testing

I originally tried testing this with `bevy_mod_raycast` as mentioned in the
original issue but it seemed to work (maybe they are currently manually
updating the Aabbs?). I then tried doing it in 2D but it looks like
`Handle<Mesh>` is just for 3D. So I took [this example](https://github.com/bevyengine/bevy/blob/main/examples/3d/pbr.rs)
and added some systems to mutate/assign meshes:

<details>
<summary>Test Script</summary>

```rust
use bevy::prelude::*;
use bevy::render::camera::ScalingMode;
use bevy::render::primitives::Aabb;

/// Make sure we only mutate one mesh once.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct MutateMeshState(bool);

/// Let's have a few global meshes that we can cycle between.
/// This way we can be assigned a new mesh, mutate the old one, and then get the old one assigned.
#[derive(Eq, PartialEq, Clone, Debug, Default)]
struct Meshes(Vec<Handle<Mesh>>);

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_resource::<MutateMeshState>()
        .init_resource::<Meshes>()
        .add_startup_system(setup)
        .add_system(assign_new_mesh)
        .add_system(show_aabbs.after(assign_new_mesh))
        .add_system(mutate_meshes.after(show_aabbs))
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut global_meshes: ResMut<Meshes>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let m1 = meshes.add(Mesh::from(shape::Icosphere::default()));
    let m2 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.90,
        ..Default::default()
    }));
    let m3 = meshes.add(Mesh::from(shape::Icosphere {
        radius: 0.80,
        ..Default::default()
    }));
    global_meshes.0.push(m1.clone());
    global_meshes.0.push(m2);
    global_meshes.0.push(m3);

    // add entities to the world
    // sphere
    commands.spawn_bundle(PbrBundle {
        mesh: m1,
        material: materials.add(StandardMaterial {
            base_color: Color::hex("ffd891").unwrap(),
            ..default()
        }),
        ..default()
    });
    // new 3d camera
    commands.spawn_bundle(Camera3dBundle {
        projection: OrthographicProjection {
            scale: 3.0,
            scaling_mode: ScalingMode::FixedVertical(1.0),
            ..default()
        }
        .into(),
        ..default()
    });

    // old 3d camera
    // commands.spawn_bundle(OrthographicCameraBundle {
    //     transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
    //     orthographic_projection: OrthographicProjection {
    //         scale: 0.01,
    //         ..default()
    //     },
    //     ..OrthographicCameraBundle::new_3d()
    // });
}
fn show_aabbs(query: Query<(Entity, &Handle<Mesh>, &Aabb)>) {
    for thing in query.iter() {
        println!("{thing:?}");
    }
}

/// For testing the second part - mutating a mesh.
///
/// Without the fix we should see this mutate an old mesh and it affects the new mesh that the
/// entity currently has.
/// With the fix, the mutation doesn't affect anything until the entity is reassigned the old mesh.
fn mutate_meshes(
    mut meshes: ResMut<Assets<Mesh>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
    mut mutate_mesh_state: ResMut<MutateMeshState>,
) {
    let mutated = mutate_mesh_state.0;
    if time.seconds_since_startup() > 4.5 && !mutated {
        println!("Mutating {:?}", global_meshes.0[0]);
        let m = meshes.get_mut(&global_meshes.0[0]).unwrap();
        let mut p = m.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().clone();
        use bevy::render::mesh::VertexAttributeValues;
        match &mut p {
            VertexAttributeValues::Float32x3(v) => {
                v[0] = [10.0, 10.0, 10.0];
            }
            _ => unreachable!(),
        }
        m.insert_attribute(Mesh::ATTRIBUTE_POSITION, p);
        mutate_mesh_state.0 = true;
    }
}

/// For testing the first part - assigning a new handle.
fn assign_new_mesh(
    mut query: Query<&mut Handle<Mesh>, With<Aabb>>,
    time: Res<Time>,
    global_meshes: Res<Meshes>,
) {
    let s = time.seconds_since_startup() as usize;
    let idx = s % global_meshes.0.len();
    for mut handle in query.iter_mut() {
        *handle = global_meshes.0[idx].clone_weak();
    }
}
```

</details>

## Changelog

### Fixed
Entity `Aabb`s not updating when meshes are mutated or re-assigned.
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
bevyengine#5489)

# Objective

Sadly, bevyengine#4944 introduces a serious exponential despawn behavior, which cannot be included in 0.8. [Handling AABBs properly is a controversial topic](bevyengine#5423 (comment)) and one that deserves more time than the day we have left before release.

## Solution

This reverts commit c2b332f.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Math Fundamental domain-agnostic mathematical operations C-Bug An unexpected or incorrect behavior S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Mesh AABBs are never updated
8 participants