Skip to content
Open
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
151 changes: 16 additions & 135 deletions crates/bevy_camera/src/primitives.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use core::borrow::Borrow;

use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{component::Component, entity::EntityHashMap, reflect::ReflectComponent};
use bevy_math::{
bounding::{Aabb3d, BoundingVolume},
Affine3A, Mat3A, Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles,
primitives::{HalfSpace, ViewFrustum},
Affine3A, Mat3A, Vec3, Vec3A,
};
use bevy_mesh::{Mesh, VertexAttributeValues};
use bevy_reflect::prelude::*;
Expand Down Expand Up @@ -195,74 +197,6 @@ impl Sphere {
}
}

/// A region of 3D space, specifically an open set whose border is a bisecting 2D plane.
///
/// This bisecting plane partitions 3D space into two infinite regions,
/// the half-space is one of those regions and excludes the bisecting plane.
///
/// Each instance of this type is characterized by:
/// - the bisecting plane's unit normal, normalized and pointing "inside" the half-space,
/// - the signed distance along the normal from the bisecting plane to the origin of 3D space.
///
/// The distance can also be seen as:
/// - the distance along the inverse of the normal from the origin of 3D space to the bisecting plane,
/// - the opposite of the distance along the normal from the origin of 3D space to the bisecting plane.
///
/// Any point `p` is considered to be within the `HalfSpace` when the length of the projection
/// of p on the normal is greater or equal than the opposite of the distance,
/// meaning: if the equation `normal.dot(p) + distance > 0.` is satisfied.
///
/// For example, the half-space containing all the points with a z-coordinate lesser
/// or equal than `8.0` would be defined by: `HalfSpace::new(Vec3::NEG_Z.extend(-8.0))`.
/// It includes all the points from the bisecting plane towards `NEG_Z`, and the distance
/// from the plane to the origin is `-8.0` along `NEG_Z`.
///
/// It is used to define a [`Frustum`], but is also a useful mathematical primitive for rendering tasks such as light computation.
#[derive(Clone, Copy, Reflect, Debug, Default)]
pub struct HalfSpace {
normal_d: Vec4,
}

impl HalfSpace {
/// Constructs a `HalfSpace` from a 4D vector whose first 3 components
/// represent the bisecting plane's unit normal, and the last component is
/// the signed distance along the normal from the plane to the origin.
/// The constructor ensures the normal vector is normalized and the distance is appropriately scaled.
#[inline]
pub fn new(normal_d: Vec4) -> Self {
Self {
normal_d: normal_d * normal_d.xyz().length_recip(),
}
}

/// Returns the unit normal vector of the bisecting plane that characterizes the `HalfSpace`.
#[inline]
pub fn normal(&self) -> Vec3A {
Vec3A::from_vec4(self.normal_d)
}

/// Returns the signed distance from the bisecting plane to the origin along
/// the plane's unit normal vector.
#[inline]
pub fn d(&self) -> f32 {
self.normal_d.w
}

/// Returns the bisecting plane's unit normal vector and the signed distance
/// from the plane to the origin.
#[inline]
pub fn normal_d(&self) -> Vec4 {
self.normal_d
}
}

/// A region of 3D space defined by the intersection of 6 [`HalfSpace`]s.
///
/// Frustums are typically an apex-truncated square pyramid (a pyramid without the top) or a cuboid.
///
/// Half spaces are ordered left, right, top, bottom, near, far. The normal vectors
/// of the half-spaces point towards the interior of the frustum.
///
/// A frustum component is used on an entity with a [`Camera`] component to
/// determine which entities will be considered for rendering by this camera.
/// All entities with an [`Aabb`] component that are not contained by (or crossing
Expand All @@ -282,72 +216,19 @@ impl HalfSpace {
/// [`GlobalTransform`]: bevy_transform::components::GlobalTransform
/// [`Camera2d`]: crate::Camera2d
/// [`Camera3d`]: crate::Camera3d
#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct Frustum {
pub half_spaces: [HalfSpace; 6],
}
pub struct Frustum(pub ViewFrustum);

This comment was marked as resolved.

Copy link
Contributor

Choose a reason for hiding this comment

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

why is that even a thing at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don’t actually know, but I’m not curious/courageous enough to try removing it
Trying to be as minimally invasive for this PR as possible

Copy link
Contributor

Choose a reason for hiding this comment

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

dunno #22693


impl Frustum {
pub const NEAR_PLANE_IDX: usize = 4;
const FAR_PLANE_IDX: usize = 5;
const INACTIVE_HALF_SPACE: Vec4 = Vec4::new(0.0, 0.0, 0.0, f32::INFINITY);

/// Returns a frustum derived from `clip_from_world`.
#[inline]
pub fn from_clip_from_world(clip_from_world: &Mat4) -> Self {
let mut frustum = Frustum::from_clip_from_world_no_far(clip_from_world);
frustum.half_spaces[Self::FAR_PLANE_IDX] = HalfSpace::new(clip_from_world.row(2));
frustum
}

/// Returns a frustum derived from `clip_from_world`,
/// but with a custom far plane.
#[inline]
pub fn from_clip_from_world_custom_far(
clip_from_world: &Mat4,
view_translation: &Vec3,
view_backward: &Vec3,
far: f32,
) -> Self {
let mut frustum = Frustum::from_clip_from_world_no_far(clip_from_world);
let far_center = *view_translation - far * *view_backward;
frustum.half_spaces[Self::FAR_PLANE_IDX] =
HalfSpace::new(view_backward.extend(-view_backward.dot(far_center)));
frustum
}

// NOTE: This approach of extracting the frustum half-space from the view
// projection matrix is from Foundations of Game Engine Development 2
// Rendering by Lengyel.
/// Returns a frustum derived from `view_projection`,
/// without a far plane.
fn from_clip_from_world_no_far(clip_from_world: &Mat4) -> Self {
let row0 = clip_from_world.row(0);
let row1 = clip_from_world.row(1);
let row2 = clip_from_world.row(2);
let row3 = clip_from_world.row(3);

Self {
half_spaces: [
HalfSpace::new(row3 + row0),
HalfSpace::new(row3 - row0),
HalfSpace::new(row3 + row1),
HalfSpace::new(row3 - row1),
HalfSpace::new(row3 + row2),
HalfSpace::new(Self::INACTIVE_HALF_SPACE),
],
}
}

/// Checks if a sphere intersects the frustum.
#[inline]
pub fn intersects_sphere(&self, sphere: &Sphere, intersect_far: bool) -> bool {
let sphere_center = sphere.center.extend(1.0);
let max = if intersect_far {
Self::FAR_PLANE_IDX
ViewFrustum::FAR_PLANE_IDX
} else {
Self::NEAR_PLANE_IDX
ViewFrustum::NEAR_PLANE_IDX
};
for half_space in &self.half_spaces[..=max] {
if half_space.normal_d().dot(sphere_center) + sphere.radius <= 0.0 {
Expand All @@ -369,8 +250,8 @@ impl Frustum {
let aabb_center_world = world_from_local.transform_point3a(aabb.center).extend(1.0);

for (idx, half_space) in self.half_spaces.into_iter().enumerate() {
if (idx == Self::NEAR_PLANE_IDX && !intersect_near)
|| (idx == Self::FAR_PLANE_IDX && !intersect_far)
if (idx == ViewFrustum::NEAR_PLANE_IDX && !intersect_near)
|| (idx == ViewFrustum::FAR_PLANE_IDX && !intersect_far)
{
continue;
}
Expand Down Expand Up @@ -541,7 +422,7 @@ pub struct CascadesFrusta {
mod tests {
use core::f32::consts::PI;

use bevy_math::{ops, Quat};
use bevy_math::{ops, Quat, Vec4};
use bevy_transform::components::GlobalTransform;

use crate::{CameraProjection, PerspectiveProjection};
Expand All @@ -550,7 +431,7 @@ mod tests {

// A big, offset frustum
fn big_frustum() -> Frustum {
Frustum {
Frustum(ViewFrustum {
half_spaces: [
HalfSpace::new(Vec4::new(-0.9701, -0.2425, -0.0000, 7.7611)),
HalfSpace::new(Vec4::new(-0.0000, 1.0000, -0.0000, 4.0000)),
Expand All @@ -559,7 +440,7 @@ mod tests {
HalfSpace::new(Vec4::new(-0.0000, -0.2425, 0.9701, 2.9104)),
HalfSpace::new(Vec4::new(0.9701, -0.2425, -0.0000, -1.9403)),
],
}
})
}

#[test]
Expand All @@ -586,7 +467,7 @@ mod tests {

// A frustum
fn frustum() -> Frustum {
Frustum {
Frustum(ViewFrustum {
half_spaces: [
HalfSpace::new(Vec4::new(-0.9701, -0.2425, -0.0000, 0.7276)),
HalfSpace::new(Vec4::new(-0.0000, 1.0000, -0.0000, 1.0000)),
Expand All @@ -595,7 +476,7 @@ mod tests {
HalfSpace::new(Vec4::new(-0.0000, -0.2425, 0.9701, 0.7276)),
HalfSpace::new(Vec4::new(0.9701, -0.2425, -0.0000, 0.7276)),
],
}
})
}

#[test]
Expand Down Expand Up @@ -666,7 +547,7 @@ mod tests {

// A long frustum.
fn long_frustum() -> Frustum {
Frustum {
Frustum(ViewFrustum {
half_spaces: [
HalfSpace::new(Vec4::new(-0.9998, -0.0222, -0.0000, -1.9543)),
HalfSpace::new(Vec4::new(-0.0000, 1.0000, -0.0000, 45.1249)),
Expand All @@ -675,7 +556,7 @@ mod tests {
HalfSpace::new(Vec4::new(-0.0000, -0.0168, 0.9999, 2.2718)),
HalfSpace::new(Vec4::new(0.9998, -0.0222, -0.0000, 7.9528)),
],
}
})
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_camera/src/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::ops::{Deref, DerefMut};
use crate::{primitives::Frustum, visibility::VisibilitySystems};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::prelude::*;
use bevy_math::{ops, vec4, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
use bevy_math::{ops, primitives::ViewFrustum, vec4, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_transform::{components::GlobalTransform, TransformSystems};
use derive_more::derive::From;
Expand Down Expand Up @@ -69,12 +69,12 @@ pub trait CameraProjection {
/// for each camera to update its frustum.
fn compute_frustum(&self, camera_transform: &GlobalTransform) -> Frustum {
let clip_from_world = self.get_clip_from_view() * camera_transform.affine().inverse();
Frustum::from_clip_from_world_custom_far(
Frustum(ViewFrustum::from_clip_from_world_custom_far(
&clip_from_world,
&camera_transform.translation(),
&camera_transform.back(),
self.far(),
)
))
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_light/src/cluster/assign.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Assigning objects to clusters.

use bevy_camera::{
primitives::{Aabb, Frustum, HalfSpace, Sphere},
primitives::{Aabb, Frustum, Sphere},
visibility::{RenderLayers, ViewVisibility},
Camera,
};
Expand All @@ -12,6 +12,7 @@ use bevy_ecs::{
};
use bevy_math::{
ops::{self, sin_cos},
primitives::HalfSpace,
Mat4, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles as _, Vec4, Vec4Swizzles as _,
};
use bevy_transform::components::GlobalTransform;
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_light/src/directional_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bevy_camera::{
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::primitives::ViewFrustum;
use bevy_reflect::prelude::*;
use bevy_transform::components::Transform;
use tracing::warn;
Expand Down Expand Up @@ -237,7 +238,7 @@ pub fn update_directional_light_frusta(
*view,
cascades
.iter()
.map(|c| Frustum::from_clip_from_world(&c.clip_from_world))
.map(|c| Frustum(ViewFrustum::from_clip_from_world(&c.clip_from_world)))
.collect::<Vec<_>>(),
)
})
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_light/src/point_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy_camera::{
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::Mat4;
use bevy_math::{primitives::ViewFrustum, Mat4};
use bevy_reflect::prelude::*;
use bevy_transform::components::{GlobalTransform, Transform};

Expand Down Expand Up @@ -238,12 +238,12 @@ pub fn update_point_light_frusta(
let world_from_view = view_translation * *view_rotation;
let clip_from_world = clip_from_view * world_from_view.compute_affine().inverse();

*frustum = Frustum::from_clip_from_world_custom_far(
*frustum = Frustum(ViewFrustum::from_clip_from_world_custom_far(
&clip_from_world,
&transform.translation(),
&view_backward,
point_light.range,
);
));
}
}
}
6 changes: 3 additions & 3 deletions crates/bevy_light/src/spot_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy_camera::{
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::{Affine3A, Dir3, Mat3, Mat4, Vec3};
use bevy_math::{primitives::ViewFrustum, Affine3A, Dir3, Mat3, Mat4, Vec3};
use bevy_reflect::prelude::*;
use bevy_transform::components::{GlobalTransform, Transform};

Expand Down Expand Up @@ -232,11 +232,11 @@ pub fn update_spot_light_frusta(
spot_light_clip_from_view(spot_light.outer_angle, spot_light.shadow_map_near_z);
let clip_from_world = spot_clip_from_view * spot_world_from_view.inverse();

*frustum = Frustum::from_clip_from_world_custom_far(
*frustum = Frustum(ViewFrustum::from_clip_from_world_custom_far(
&clip_from_world,
&transform.translation(),
&view_backward,
spot_light.range,
);
));
}
}
Loading