Rename Plane struct to HalfSpace (#8744)

# Objective

- Rename the `render::primitives::Plane` struct as to not confuse it
with `bevy_render::mesh::shape::Plane`
- Fixes https://github.com/bevyengine/bevy/issues/8730

## Solution

- Refactor the `render::primitives::Plane` struct to
`render::primitives::HalfSpace`
- Modify documentation to reflect this change

## Changelog

- Renamed `Plane` to `HalfSpace` to more accurately represent it's use
- Renamed `planes` member in `Frustum` to `half_spaces` to reflect
changes

## Migration Guide

- `Plane` has been renamed to `HalfSpace`
- `planes` member in `Frustum` has been renamed to `half_spaces`

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
This commit is contained in:
lelo 2023-06-12 15:27:41 -04:00 committed by GitHub
parent a78c4d78d5
commit 278daab6ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 64 deletions

View file

@ -8,7 +8,7 @@ use bevy_render::{
color::Color, color::Color,
extract_resource::ExtractResource, extract_resource::ExtractResource,
prelude::Projection, prelude::Projection,
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Plane, Sphere}, primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere},
render_resource::BufferBindingType, render_resource::BufferBindingType,
renderer::RenderDevice, renderer::RenderDevice,
view::{ComputedVisibility, RenderLayers, VisibleEntities}, view::{ComputedVisibility, RenderLayers, VisibleEntities},
@ -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 view_x = clip_to_view(inverse_projection, Vec4::new(x_pos, 0.0, 1.0, 1.0)).x;
let normal = Vec3::X; let normal = Vec3::X;
let d = view_x * normal.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; let y_slices = clusters.dimensions.y as f32;
@ -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 view_y = clip_to_view(inverse_projection, Vec4::new(0.0, y_pos, 1.0, 1.0)).y;
let normal = Vec3::Y; let normal = Vec3::Y;
let d = view_y * normal.y; let d = view_y * normal.y;
y_planes.push(Plane::new(normal.extend(d))); y_planes.push(HalfSpace::new(normal.extend(d)));
} }
} else { } else {
let x_slices = clusters.dimensions.x as f32; let x_slices = clusters.dimensions.x as f32;
@ -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 nt = clip_to_view(inverse_projection, Vec4::new(x_pos, 1.0, 1.0, 1.0)).xyz();
let normal = nb.cross(nt); let normal = nb.cross(nt);
let d = nb.dot(normal); 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; let y_slices = clusters.dimensions.y as f32;
@ -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 nr = clip_to_view(inverse_projection, Vec4::new(1.0, y_pos, 1.0, 1.0)).xyz();
let normal = nr.cross(nl); let normal = nr.cross(nl);
let d = nr.dot(normal); let d = nr.dot(normal);
y_planes.push(Plane::new(normal.extend(d))); y_planes.push(HalfSpace::new(normal.extend(d)));
} }
} }
@ -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 view_z = z_slice_to_view_z(first_slice_depth, far_z, z_slices, z, is_orthographic);
let normal = -Vec3::Z; let normal = -Vec3::Z;
let d = view_z * normal.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>| { let mut update_from_light_intersections = |visible_lights: &mut Vec<Entity>| {
@ -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 // 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 { if is_orthographic {
point.x - plane.d() point.x - plane.d()
} else { } else {
@ -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 // 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 // p = sphere center
// n = plane normal // n = plane normal
// d = n.p if p is in the plane // d = n.p if p is in the plane
@ -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 // 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 { let distance_to_plane = if is_orthographic {
y_plane.d() - y_light.center.y y_plane.d() - y_light.center.y
} else { } else {

View file

@ -81,21 +81,21 @@ impl Sphere {
} }
} }
/// A plane defined by a unit normal and distance from the origin along the normal /// A bisecting plane that partitions 3D space into two regions.
/// 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 /// Each instance of this type is characterized by the bisecting plane's unit normal and distance from the origin along the normal.
/// the positive side (inside) of the plane. /// 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)] #[derive(Clone, Copy, Debug, Default)]
pub struct Plane { pub struct HalfSpace {
normal_d: Vec4, normal_d: Vec4,
} }
impl Plane { impl HalfSpace {
/// Constructs a `Plane` from a 4D vector whose first 3 components /// Constructs a `HalfSpace` from a 4D vector whose first 3 components
/// are the normal and whose last component is the distance along the normal /// represent the bisecting plane's unit normal, and the last component signifies
/// from the origin. /// the distance from the origin to the plane along the normal.
/// This constructor ensures that the normal is normalized and the distance is /// The constructor ensures the normal vector is normalized and the distance is appropriately scaled.
/// scaled accordingly so it represents the signed distance from the origin.
#[inline] #[inline]
pub fn new(normal_d: Vec4) -> Self { pub fn new(normal_d: Vec4) -> Self {
Self { Self {
@ -103,35 +103,34 @@ impl Plane {
} }
} }
/// `Plane` unit normal /// Returns the unit normal vector of the bisecting plane that characterizes the `HalfSpace`.
#[inline] #[inline]
pub fn normal(&self) -> Vec3A { pub fn normal(&self) -> Vec3A {
Vec3A::from(self.normal_d) Vec3A::from(self.normal_d)
} }
/// Signed distance from the origin along the unit normal such that n.p + d = 0 for point p in /// Returns the distance from the origin to the bisecting plane along the plane's unit normal vector.
/// the `Plane` /// This distance helps determine the position of a point `p` on the bisecting plane, as per the equation `n.p + d = 0`.
#[inline] #[inline]
pub fn d(&self) -> f32 { pub fn d(&self) -> f32 {
self.normal_d.w self.normal_d.w
} }
/// `Plane` unit normal and signed distance from the origin such that n.p + d = 0 for point p /// Returns the bisecting plane's unit normal vector and the distance from the origin to the plane.
/// in the `Plane`
#[inline] #[inline]
pub fn normal_d(&self) -> Vec4 { pub fn normal_d(&self) -> Vec4 {
self.normal_d self.normal_d
} }
} }
/// A frustum defined by the 6 containing planes /// A frustum made up of the 6 defining half spaces.
/// Planes are ordered left, right, top, bottom, near, far /// Half spaces are ordered left, right, top, bottom, near, far.
/// Normals point into the contained volume /// The normal vectors of the half spaces point towards the interior of the frustum.
#[derive(Component, Clone, Copy, Debug, Default, Reflect)] #[derive(Component, Clone, Copy, Debug, Default, Reflect)]
#[reflect(Component)] #[reflect(Component)]
pub struct Frustum { pub struct Frustum {
#[reflect(ignore)] #[reflect(ignore)]
pub planes: [Plane; 6], pub half_spaces: [HalfSpace; 6],
} }
impl Frustum { impl Frustum {
@ -139,12 +138,12 @@ impl Frustum {
#[inline] #[inline]
pub fn from_view_projection(view_projection: &Mat4) -> Self { pub fn from_view_projection(view_projection: &Mat4) -> Self {
let mut frustum = Frustum::from_view_projection_no_far(view_projection); 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 frustum
} }
/// Returns a frustum derived from `view_projection`, but with a custom /// Returns a frustum derived from `view_projection`,
/// far plane. /// but with a custom far plane.
#[inline] #[inline]
pub fn from_view_projection_custom_far( pub fn from_view_projection_custom_far(
view_projection: &Mat4, view_projection: &Mat4,
@ -154,39 +153,44 @@ impl Frustum {
) -> Self { ) -> Self {
let mut frustum = Frustum::from_view_projection_no_far(view_projection); let mut frustum = Frustum::from_view_projection_no_far(view_projection);
let far_center = *view_translation - far * *view_backward; 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 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 // projection matrix is from Foundations of Game Engine Development 2
// Rendering by Lengyel. // Rendering by Lengyel.
/// Returns a frustum derived from `view_projection`,
/// without a far plane.
fn from_view_projection_no_far(view_projection: &Mat4) -> Self { fn from_view_projection_no_far(view_projection: &Mat4) -> Self {
let row3 = view_projection.row(3); let row3 = view_projection.row(3);
let mut planes = [Plane::default(); 6]; let mut half_spaces = [HalfSpace::default(); 6];
for (i, plane) in planes.iter_mut().enumerate().take(5) { for (i, half_space) in half_spaces.iter_mut().enumerate().take(5) {
let row = view_projection.row(i / 2); 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 row3 + row
} else { } else {
row3 - row row3 - row
}); });
} }
Self { planes } Self { half_spaces }
} }
/// Checks if a sphere intersects the frustum.
#[inline] #[inline]
pub fn intersects_sphere(&self, sphere: &Sphere, intersect_far: bool) -> bool { pub fn intersects_sphere(&self, sphere: &Sphere, intersect_far: bool) -> bool {
let sphere_center = sphere.center.extend(1.0); let sphere_center = sphere.center.extend(1.0);
let max = if intersect_far { 6 } else { 5 }; let max = if intersect_far { 6 } else { 5 };
for plane in &self.planes[..max] { for half_space in &self.half_spaces[..max] {
if plane.normal_d().dot(sphere_center) + sphere.radius <= 0.0 { if half_space.normal_d().dot(sphere_center) + sphere.radius <= 0.0 {
return false; return false;
} }
} }
true true
} }
/// Checks if an Oriented Bounding Box (obb) intersects the frustum.
#[inline] #[inline]
pub fn intersects_obb( pub fn intersects_obb(
&self, &self,
@ -202,16 +206,16 @@ impl Frustum {
Vec3A::from(model_to_world.z_axis), 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 { if idx == 4 && !intersect_near {
continue; continue;
} }
if idx == 5 && !intersect_far { if idx == 5 && !intersect_far {
continue; continue;
} }
let p_normal = plane.normal(); let p_normal = half_space.normal();
let relative_radius = aabb.relative_radius(&p_normal, &axes); 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; return false;
} }
} }
@ -249,13 +253,13 @@ mod tests {
// A big, offset frustum // A big, offset frustum
fn big_frustum() -> Frustum { fn big_frustum() -> Frustum {
Frustum { Frustum {
planes: [ half_spaces: [
Plane::new(Vec4::new(-0.9701, -0.2425, -0.0000, 7.7611)), HalfSpace::new(Vec4::new(-0.9701, -0.2425, -0.0000, 7.7611)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 4.0000)), HalfSpace::new(Vec4::new(-0.0000, 1.0000, -0.0000, 4.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, -0.9701, 2.9104)), HalfSpace::new(Vec4::new(-0.0000, -0.2425, -0.9701, 2.9104)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 4.0000)), HalfSpace::new(Vec4::new(-0.0000, -1.0000, -0.0000, 4.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, 0.9701, 2.9104)), HalfSpace::new(Vec4::new(-0.0000, -0.2425, 0.9701, 2.9104)),
Plane::new(Vec4::new(0.9701, -0.2425, -0.0000, -1.9403)), HalfSpace::new(Vec4::new(0.9701, -0.2425, -0.0000, -1.9403)),
], ],
} }
} }
@ -285,13 +289,13 @@ mod tests {
// A frustum // A frustum
fn frustum() -> Frustum { fn frustum() -> Frustum {
Frustum { Frustum {
planes: [ half_spaces: [
Plane::new(Vec4::new(-0.9701, -0.2425, -0.0000, 0.7276)), HalfSpace::new(Vec4::new(-0.9701, -0.2425, -0.0000, 0.7276)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 1.0000)), HalfSpace::new(Vec4::new(-0.0000, 1.0000, -0.0000, 1.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, -0.9701, 0.7276)), HalfSpace::new(Vec4::new(-0.0000, -0.2425, -0.9701, 0.7276)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 1.0000)), HalfSpace::new(Vec4::new(-0.0000, -1.0000, -0.0000, 1.0000)),
Plane::new(Vec4::new(-0.0000, -0.2425, 0.9701, 0.7276)), HalfSpace::new(Vec4::new(-0.0000, -0.2425, 0.9701, 0.7276)),
Plane::new(Vec4::new(0.9701, -0.2425, -0.0000, 0.7276)), HalfSpace::new(Vec4::new(0.9701, -0.2425, -0.0000, 0.7276)),
], ],
} }
} }
@ -365,13 +369,13 @@ mod tests {
// A long frustum. // A long frustum.
fn long_frustum() -> Frustum { fn long_frustum() -> Frustum {
Frustum { Frustum {
planes: [ half_spaces: [
Plane::new(Vec4::new(-0.9998, -0.0222, -0.0000, -1.9543)), HalfSpace::new(Vec4::new(-0.9998, -0.0222, -0.0000, -1.9543)),
Plane::new(Vec4::new(-0.0000, 1.0000, -0.0000, 45.1249)), HalfSpace::new(Vec4::new(-0.0000, 1.0000, -0.0000, 45.1249)),
Plane::new(Vec4::new(-0.0000, -0.0168, -0.9999, 2.2718)), HalfSpace::new(Vec4::new(-0.0000, -0.0168, -0.9999, 2.2718)),
Plane::new(Vec4::new(-0.0000, -1.0000, -0.0000, 45.1249)), HalfSpace::new(Vec4::new(-0.0000, -1.0000, -0.0000, 45.1249)),
Plane::new(Vec4::new(-0.0000, -0.0168, 0.9999, 2.2718)), HalfSpace::new(Vec4::new(-0.0000, -0.0168, 0.9999, 2.2718)),
Plane::new(Vec4::new(0.9998, -0.0222, -0.0000, 7.9528)), HalfSpace::new(Vec4::new(0.9998, -0.0222, -0.0000, 7.9528)),
], ],
} }
} }