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

Rename Plane struct to HalfSpace #8744

Merged
merged 13 commits into from
Jun 12, 2023
22 changes: 13 additions & 9 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bevy_render::{
color::Color,
extract_resource::ExtractResource,
prelude::Projection,
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Plane, Sphere},
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere},
render_resource::BufferBindingType,
renderer::RenderDevice,
view::{ComputedVisibility, RenderLayers, VisibleEntities},
Expand Down Expand Up @@ -1457,7 +1457,7 @@ pub(crate) fn assign_lights_to_clusters(
let view_x = clip_to_view(inverse_projection, Vec4::new(x_pos, 0.0, 1.0, 1.0)).x;
let normal = Vec3::X;
let d = view_x * normal.x;
x_planes.push(Plane::new(normal.extend(d)));
x_planes.push(HalfSpace::new(normal.extend(d)));
}

let y_slices = clusters.dimensions.y as f32;
Expand All @@ -1467,7 +1467,7 @@ pub(crate) fn assign_lights_to_clusters(
let view_y = clip_to_view(inverse_projection, Vec4::new(0.0, y_pos, 1.0, 1.0)).y;
let normal = Vec3::Y;
let d = view_y * normal.y;
y_planes.push(Plane::new(normal.extend(d)));
y_planes.push(HalfSpace::new(normal.extend(d)));
}
} else {
let x_slices = clusters.dimensions.x as f32;
Expand All @@ -1478,7 +1478,7 @@ pub(crate) fn assign_lights_to_clusters(
let nt = clip_to_view(inverse_projection, Vec4::new(x_pos, 1.0, 1.0, 1.0)).xyz();
let normal = nb.cross(nt);
let d = nb.dot(normal);
x_planes.push(Plane::new(normal.extend(d)));
x_planes.push(HalfSpace::new(normal.extend(d)));
}

let y_slices = clusters.dimensions.y as f32;
Expand All @@ -1489,7 +1489,7 @@ pub(crate) fn assign_lights_to_clusters(
let nr = clip_to_view(inverse_projection, Vec4::new(1.0, y_pos, 1.0, 1.0)).xyz();
let normal = nr.cross(nl);
let d = nr.dot(normal);
y_planes.push(Plane::new(normal.extend(d)));
y_planes.push(HalfSpace::new(normal.extend(d)));
}
}

Expand All @@ -1498,7 +1498,7 @@ pub(crate) fn assign_lights_to_clusters(
let view_z = z_slice_to_view_z(first_slice_depth, far_z, z_slices, z, is_orthographic);
let normal = -Vec3::Z;
let d = view_z * normal.z;
z_planes.push(Plane::new(normal.extend(d)));
z_planes.push(HalfSpace::new(normal.extend(d)));
}

let mut update_from_light_intersections = |visible_lights: &mut Vec<Entity>| {
Expand Down Expand Up @@ -1737,7 +1737,7 @@ pub(crate) fn assign_lights_to_clusters(
}

// NOTE: This exploits the fact that a x-plane normal has only x and z components
fn get_distance_x(plane: Plane, point: Vec3A, is_orthographic: bool) -> f32 {
fn get_distance_x(plane: HalfSpace, point: Vec3A, is_orthographic: bool) -> f32 {
if is_orthographic {
point.x - plane.d()
} else {
Expand All @@ -1750,7 +1750,7 @@ fn get_distance_x(plane: Plane, point: Vec3A, is_orthographic: bool) -> f32 {
}

// NOTE: This exploits the fact that a z-plane normal has only a z component
fn project_to_plane_z(z_light: Sphere, z_plane: Plane) -> Option<Sphere> {
fn project_to_plane_z(z_light: Sphere, z_plane: HalfSpace) -> Option<Sphere> {
// p = sphere center
// n = plane normal
// d = n.p if p is in the plane
Expand All @@ -1772,7 +1772,11 @@ fn project_to_plane_z(z_light: Sphere, z_plane: Plane) -> Option<Sphere> {
}

// NOTE: This exploits the fact that a y-plane normal has only y and z components
fn project_to_plane_y(y_light: Sphere, y_plane: Plane, is_orthographic: bool) -> Option<Sphere> {
fn project_to_plane_y(
y_light: Sphere,
y_plane: HalfSpace,
is_orthographic: bool,
) -> Option<Sphere> {
let distance_to_plane = if is_orthographic {
y_plane.d() - y_light.center.y
} else {
Expand Down
114 changes: 59 additions & 55 deletions crates/bevy_render/src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,70 +81,69 @@ impl Sphere {
}
}

/// A plane defined by a unit normal and distance from the origin along the normal
/// Any point `p` is in the plane if `n.p + d = 0`
/// For planes defining half-spaces such as for frusta, if `n.p + d > 0` then `p` is on
/// the positive side (inside) of the plane.
/// A bisecting plane that partitions 3D space into two regions.
///
/// Each instance of this type is characterized by the bisecting plane's unit normal and distance from the origin along the normal.
/// Any point `p` is considered to be within the `HalfSpace` when the distance is positive,
/// meaning: if the equation `n.p + d > 0` is satisfied.
#[derive(Clone, Copy, Debug, Default)]
pub struct Plane {
pub struct HalfSpace {
normal_d: Vec4,
}

impl Plane {
/// Constructs a `Plane` from a 4D vector whose first 3 components
/// are the normal and whose last component is the distance along the normal
/// from the origin.
/// This constructor ensures that the normal is normalized and the distance is
/// scaled accordingly so it represents the signed distance from the origin.
impl HalfSpace {
/// Constructs a `HalfSpace` from a 4D vector whose first 3 components
/// represent the bisecting plane's unit normal, and the last component signifies
/// the distance from the origin to the plane along the normal.
/// The constructor ensures the normal vector is normalized and the distance is appropriately scaled.
Comment on lines +95 to +98
Copy link
Contributor

Choose a reason for hiding this comment

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

Props to you, I like the phrasing 👍

#[inline]
pub fn new(normal_d: Vec4) -> Self {
Self {
normal_d: normal_d * normal_d.xyz().length_recip(),
}
}

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

/// Signed distance from the origin along the unit normal such that n.p + d = 0 for point p in
/// the `Plane`
/// Returns the distance from the origin to the bisecting plane along the plane's unit normal vector.
/// This distance helps determine the position of a point `p` on the bisecting plane, as per the equation `n.p + d = 0`.
#[inline]
pub fn d(&self) -> f32 {
self.normal_d.w
}

/// `Plane` unit normal and signed distance from the origin such that n.p + d = 0 for point p
/// in the `Plane`
/// Returns the bisecting plane's unit normal vector and the distance from the origin to the plane.
#[inline]
pub fn normal_d(&self) -> Vec4 {
self.normal_d
}
}

/// A frustum defined by the 6 containing planes
/// Planes are ordered left, right, top, bottom, near, far
/// Normals point into the contained volume
/// A frustum made up of the 6 defining half spaces.
/// Half spaces are ordered left, right, top, bottom, near, far.
/// The normal vectors of the half spaces point towards the interior of the frustum.
#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct Frustum {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see the doc string for Frustrum mention planes. Could you reword it as well?

#[reflect(ignore)]
pub planes: [Plane; 6],
pub half_spaces: [HalfSpace; 6],
}

impl Frustum {
/// Returns a frustum derived from `view_projection`.
#[inline]
pub fn from_view_projection(view_projection: &Mat4) -> Self {
let mut frustum = Frustum::from_view_projection_no_far(view_projection);
frustum.planes[5] = Plane::new(view_projection.row(2));
frustum.half_spaces[5] = HalfSpace::new(view_projection.row(2));
frustum
}

/// Returns a frustum derived from `view_projection`, but with a custom
/// far plane.
/// Returns a frustum derived from `view_projection`,
/// but with a custom far plane.
#[inline]
pub fn from_view_projection_custom_far(
view_projection: &Mat4,
Expand All @@ -154,39 +153,44 @@ impl Frustum {
) -> Self {
let mut frustum = Frustum::from_view_projection_no_far(view_projection);
let far_center = *view_translation - far * *view_backward;
frustum.planes[5] = Plane::new(view_backward.extend(-view_backward.dot(far_center)));
frustum.half_spaces[5] =
HalfSpace::new(view_backward.extend(-view_backward.dot(far_center)));
frustum
}

// NOTE: This approach of extracting the frustum planes from the view
// 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_view_projection_no_far(view_projection: &Mat4) -> Self {
let row3 = view_projection.row(3);
let mut planes = [Plane::default(); 6];
for (i, plane) in planes.iter_mut().enumerate().take(5) {
let mut half_spaces = [HalfSpace::default(); 6];
for (i, half_space) in half_spaces.iter_mut().enumerate().take(5) {
let row = view_projection.row(i / 2);
*plane = Plane::new(if (i & 1) == 0 && i != 4 {
*half_space = HalfSpace::new(if (i & 1) == 0 && i != 4 {
row3 + row
} else {
row3 - row
});
}
Self { planes }
Self { half_spaces }
}

/// 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 { 6 } else { 5 };
for plane in &self.planes[..max] {
if plane.normal_d().dot(sphere_center) + sphere.radius <= 0.0 {
for half_space in &self.half_spaces[..max] {
if half_space.normal_d().dot(sphere_center) + sphere.radius <= 0.0 {
return false;
}
}
true
}

/// Checks if an Oriented Bounding Box (obb) intersects the frustum.
#[inline]
pub fn intersects_obb(
&self,
Expand All @@ -202,16 +206,16 @@ impl Frustum {
Vec3A::from(model_to_world.z_axis),
];

for (idx, plane) in self.planes.into_iter().enumerate() {
for (idx, half_space) in self.half_spaces.into_iter().enumerate() {
if idx == 4 && !intersect_near {
continue;
}
if idx == 5 && !intersect_far {
continue;
}
let p_normal = plane.normal();
let p_normal = half_space.normal();
let relative_radius = aabb.relative_radius(&p_normal, &axes);
if plane.normal_d().dot(aabb_center_world) + relative_radius <= 0.0 {
if half_space.normal_d().dot(aabb_center_world) + relative_radius <= 0.0 {
return false;
}
}
Expand Down Expand Up @@ -249,13 +253,13 @@ mod tests {
// A big, offset frustum
fn big_frustum() -> Frustum {
Frustum {
planes: [
Plane::new(Vec4::new(-0.9701, -0.2425, -0.0000, 7.7611)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 4.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, -0.9701, 2.9104)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 4.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, 0.9701, 2.9104)),
Plane::new(Vec4::new(0.9701, -0.2425, -0.0000, -1.9403)),
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)),
HalfSpace::new(Vec4::new(-0.0000, -0.2425, -0.9701, 2.9104)),
HalfSpace::new(Vec4::new(-0.0000, -1.0000, -0.0000, 4.0000)),
HalfSpace::new(Vec4::new(-0.0000, -0.2425, 0.9701, 2.9104)),
HalfSpace::new(Vec4::new(0.9701, -0.2425, -0.0000, -1.9403)),
],
}
}
Expand Down Expand Up @@ -285,13 +289,13 @@ mod tests {
// A frustum
fn frustum() -> Frustum {
Frustum {
planes: [
Plane::new(Vec4::new(-0.9701, -0.2425, -0.0000, 0.7276)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 1.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, -0.9701, 0.7276)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 1.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, 0.9701, 0.7276)),
Plane::new(Vec4::new(0.9701, -0.2425, -0.0000, 0.7276)),
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)),
HalfSpace::new(Vec4::new(-0.0000, -0.2425, -0.9701, 0.7276)),
HalfSpace::new(Vec4::new(-0.0000, -1.0000, -0.0000, 1.0000)),
HalfSpace::new(Vec4::new(-0.0000, -0.2425, 0.9701, 0.7276)),
HalfSpace::new(Vec4::new(0.9701, -0.2425, -0.0000, 0.7276)),
],
}
}
Expand Down Expand Up @@ -365,13 +369,13 @@ mod tests {
// A long frustum.
fn long_frustum() -> Frustum {
Frustum {
planes: [
Plane::new(Vec4::new(-0.9998, -0.0222, -0.0000, -1.9543)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 45.1249)),
Plane::new(Vec4::new(-0.0000, -0.0168, -0.9999, 2.2718)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 45.1249)),
Plane::new(Vec4::new(-0.0000, -0.0168, 0.9999, 2.2718)),
Plane::new(Vec4::new(0.9998, -0.0222, -0.0000, 7.9528)),
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)),
HalfSpace::new(Vec4::new(-0.0000, -0.0168, -0.9999, 2.2718)),
HalfSpace::new(Vec4::new(-0.0000, -1.0000, -0.0000, 45.1249)),
HalfSpace::new(Vec4::new(-0.0000, -0.0168, 0.9999, 2.2718)),
HalfSpace::new(Vec4::new(0.9998, -0.0222, -0.0000, 7.9528)),
],
}
}
Expand Down