Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion crates/bevy_camera/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
22 changes: 19 additions & 3 deletions crates/bevy_camera/src/visibility/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<Assets<Mesh>>,
without_aabb: Query<(Entity, &Mesh3d), (Without<Aabb>, Without<NoFrustumCulling>)>,
without_aabb: Query<
(Entity, &Mesh3d),
(
Without<Aabb>,
Without<NoFrustumCulling>,
Without<SkinnedMesh>,
),
>,
) {
for (entity, mesh_handle) in &without_aabb {
if let Some(mesh) = meshes.get(mesh_handle)
Expand Down
40 changes: 27 additions & 13 deletions crates/bevy_gltf/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -968,6 +974,7 @@ impl GltfLoader {
#[cfg(feature = "bevy_animation")]
None,
&gltf.document,
&skinned_meshes,
convert_coordinates,
);
if result.is_err() {
Expand Down Expand Up @@ -1412,6 +1419,7 @@ fn load_node(
#[cfg(feature = "bevy_animation")] animation_roots: &HashSet<usize>,
#[cfg(feature = "bevy_animation")] mut animation_context: Option<AnimationContext>,
document: &Document,
skinned_meshes: &HashSet<usize>,
convert_coordinates: bool,
) -> Result<(), GltfError> {
let mut gltf_error = None;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1693,6 +1706,7 @@ fn load_node(
#[cfg(feature = "bevy_animation")]
animation_context.clone(),
document,
skinned_meshes,
convert_coordinates,
) {
gltf_error = Some(err);
Expand Down