diff --git a/crates/bevy_camera/src/primitives.rs b/crates/bevy_camera/src/primitives.rs index 1f3abe97ed6f3..6d30cf0e74fae 100644 --- a/crates/bevy_camera/src/primitives.rs +++ b/crates/bevy_camera/src/primitives.rs @@ -43,13 +43,23 @@ impl MeshAabb for Mesh { /// It will be added automatically by the systems in [`CalculateBounds`] to entities that: /// - could be subject to frustum culling, for example with a [`Mesh3d`] /// or `Sprite` component, -/// - don't have the [`NoFrustumCulling`] component. +/// - don't have the [`NoFrustumCulling`] component, +/// - and don't have the [`SkinnedMesh`] component. /// /// It won't be updated automatically if the space occupied by the entity changes, /// for example if the vertex positions of a [`Mesh3d`] are updated. /// +/// Bevy doesn't add the [`Aabb`] component to skinned meshes automatically, +/// because skins can deform meshes arbitrarily and therefore there's no +/// [`Aabb`] that can be automatically determined for them. You can, however, +/// add an [`Aabb`] component yourself if you can guarantee to Bevy that no +/// vertex in your skinned mesh will ever be positioned outside the boundaries +/// of that AABB. This will allow your skinned meshes to participate in frustum +/// and occlusion culling. +/// /// [`Camera`]: crate::Camera /// [`NoFrustumCulling`]: crate::visibility::NoFrustumCulling +/// [`SkinnedMesh`]: bevy_mesh::skinning::SkinnedMesh /// [`CalculateBounds`]: crate::visibility::VisibilitySystems::CalculateBounds /// [`Mesh3d`]: bevy_mesh::Mesh #[derive(Component, Clone, Copy, Debug, Default, Reflect, PartialEq)] diff --git a/crates/bevy_camera/src/visibility/mod.rs b/crates/bevy_camera/src/visibility/mod.rs index 76d3f3db02122..9062f4f40497a 100644 --- a/crates/bevy_camera/src/visibility/mod.rs +++ b/crates/bevy_camera/src/visibility/mod.rs @@ -13,6 +13,7 @@ pub use render_layers::*; use bevy_app::{Plugin, PostUpdate}; use bevy_asset::Assets; use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*}; +use bevy_mesh::skinning::SkinnedMesh; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::{components::GlobalTransform, TransformSystems}; use bevy_utils::{Parallel, TypeIdMap}; @@ -384,14 +385,29 @@ impl Plugin for VisibilityPlugin { } } -/// Computes and adds an [`Aabb`] component to entities with a -/// [`Mesh3d`] component and without a [`NoFrustumCulling`] component. +/// Computes and adds an [`Aabb`] component to entities with a [`Mesh3d`] +/// component and that have neither a [`NoFrustumCulling`] component nor a +/// [`SkinnedMesh`] component. +/// +/// Bevy doesn't automatically calculate bounding boxes for meshes that are +/// skinned because, in general, the skin may deform the mesh arbitrarily. If +/// you want Bevy to frustum cull skinned meshes, you can manually add an +/// [`Aabb`] component to those meshes. If you do so, you're promising to Bevy +/// that the deformed vertices of that mesh will never go outside the bounds of +/// that AABB. /// /// This system is used in system set [`VisibilitySystems::CalculateBounds`]. pub fn calculate_bounds( mut commands: Commands, meshes: Res>, - without_aabb: Query<(Entity, &Mesh3d), (Without, Without)>, + without_aabb: Query< + (Entity, &Mesh3d), + ( + Without, + Without, + Without, + ), + >, ) { for (entity, mesh_handle) in &without_aabb { if let Some(mesh) = meshes.get(mesh_handle) diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index 7c6c0eaa56ca9..734dd167c71e9 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -861,6 +861,11 @@ impl GltfLoader { // Then check for cycles. check_for_cycles(&gltf)?; + // Record which meshes are skinned. We need to do this so that we don't + // generate AABBs for them, which might cause them to be incorrectly + // culled. + let mut skinned_meshes = HashSet::new(); + // Now populate the nodes. for node in gltf.nodes() { let skin = node.skin().map(|skin| { @@ -907,10 +912,11 @@ impl GltfLoader { .map(|child| nodes.get(&child.index()).unwrap().clone()) .collect(); - let mesh = node - .mesh() - .map(|mesh| mesh.index()) - .and_then(|i| meshes.get(i).cloned()); + let maybe_mesh_index = node.mesh().map(|mesh| mesh.index()); + let mesh = maybe_mesh_index.and_then(|i| meshes.get(i).cloned()); + if let Some(mesh_index) = maybe_mesh_index { + skinned_meshes.insert(mesh_index); + } let gltf_node = GltfNode::new( &node, @@ -968,6 +974,7 @@ impl GltfLoader { #[cfg(feature = "bevy_animation")] None, &gltf.document, + &skinned_meshes, convert_coordinates, ); if result.is_err() { @@ -1412,6 +1419,7 @@ fn load_node( #[cfg(feature = "bevy_animation")] animation_roots: &HashSet, #[cfg(feature = "bevy_animation")] mut animation_context: Option, document: &Document, + skinned_meshes: &HashSet, convert_coordinates: bool, ) -> Result<(), GltfError> { let mut gltf_error = None; @@ -1559,18 +1567,23 @@ fn load_node( mesh_entity.insert(MeshMorphWeights::new(weights).unwrap()); } - let mut bounds_min = Vec3::from_slice(&bounds.min); - let mut bounds_max = Vec3::from_slice(&bounds.max); + // Add an AABB if this mesh isn't skinned. (If it is skinned, we + // don't add the AABB because skinned meshes can be deformed + // arbitrarily.) + if !skinned_meshes.contains(&mesh.index()) { + let mut bounds_min = Vec3::from_slice(&bounds.min); + let mut bounds_max = Vec3::from_slice(&bounds.max); - if convert_coordinates { - let converted_min = bounds_min.convert_coordinates(); - let converted_max = bounds_max.convert_coordinates(); + if convert_coordinates { + let converted_min = bounds_min.convert_coordinates(); + let converted_max = bounds_max.convert_coordinates(); - bounds_min = converted_min.min(converted_max); - bounds_max = converted_min.max(converted_max); - } + bounds_min = converted_min.min(converted_max); + bounds_max = converted_min.max(converted_max); + } - mesh_entity.insert(Aabb::from_min_max(bounds_min, bounds_max)); + mesh_entity.insert(Aabb::from_min_max(bounds_min, bounds_max)); + } if let Some(extras) = primitive.extras() { mesh_entity.insert(GltfExtras { @@ -1693,6 +1706,7 @@ fn load_node( #[cfg(feature = "bevy_animation")] animation_context.clone(), document, + skinned_meshes, convert_coordinates, ) { gltf_error = Some(err);