Skip to main content

bevy_math/primitives/
half_space.rs

1use crate::{ops, Vec3, Vec3A, Vec4, Vec4Swizzles};
2
3#[cfg(feature = "bevy_reflect")]
4use bevy_reflect::{std_traits::ReflectDefault, Reflect};
5#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
6use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
7
8/// A region of 3D space, specifically an open set whose border is a bisecting 2D plane.
9///
10/// This bisecting plane partitions 3D space into two infinite regions,
11/// the half-space is one of those regions and excludes the bisecting plane.
12///
13/// Each instance of this type is characterized by:
14/// - the bisecting plane's unit normal, normalized and pointing "inside" the half-space,
15/// - the signed distance along the normal from the bisecting plane to the origin of 3D space.
16///
17/// The distance can also be seen as:
18/// - the distance along the inverse of the normal from the origin of 3D space to the bisecting plane,
19/// - the opposite of the distance along the normal from the origin of 3D space to the bisecting plane.
20///
21/// Any point `p` is considered to be within the `HalfSpace` when the length of the projection
22/// of p on the normal is greater or equal than the opposite of the distance,
23/// meaning: if the equation `normal.dot(p) + distance > 0.` is satisfied.
24///
25/// For example, the half-space containing all the points with a z-coordinate lesser
26/// or equal than `8.0` would be defined by: `HalfSpace::new(Vec3::NEG_Z.extend(-8.0))`.
27/// It includes all the points from the bisecting plane towards `NEG_Z`, and the distance
28/// from the plane to the origin is `-8.0` along `NEG_Z`.
29///
30/// It is used to define a [`ViewFrustum`](crate::primitives::ViewFrustum),
31/// but is also a useful mathematical primitive for rendering tasks such as  light computation.
32#[derive(Clone, Copy, Debug, Default, PartialEq)]
33#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
34#[cfg_attr(
35    feature = "bevy_reflect",
36    derive(Reflect),
37    reflect(Clone, Debug, Default, PartialEq)
38)]
39#[cfg_attr(
40    all(feature = "serialize", feature = "bevy_reflect"),
41    reflect(Serialize, Deserialize)
42)]
43pub struct HalfSpace {
44    normal_d: Vec4,
45}
46
47impl HalfSpace {
48    /// Constructs a `HalfSpace` from a 4D vector whose first 3 components
49    /// represent the bisecting plane's unit normal, and the last component is
50    /// the signed distance along the normal from the plane to the origin.
51    /// The constructor ensures the normal vector is normalized and the distance is appropriately scaled.
52    #[inline]
53    pub fn new(normal_d: Vec4) -> Self {
54        Self {
55            normal_d: normal_d * normal_d.xyz().length_recip(),
56        }
57    }
58
59    /// Returns the unit normal vector of the bisecting plane that characterizes the `HalfSpace`.
60    #[inline]
61    pub fn normal(&self) -> Vec3A {
62        Vec3A::from_vec4(self.normal_d)
63    }
64
65    /// Returns the signed distance from the bisecting plane to the origin along
66    /// the plane's unit normal vector.
67    #[inline]
68    pub fn d(&self) -> f32 {
69        self.normal_d.w
70    }
71
72    /// Returns the bisecting plane's unit normal vector and the signed distance
73    /// from the plane to the origin.
74    #[inline]
75    pub fn normal_d(&self) -> Vec4 {
76        self.normal_d
77    }
78
79    /// Returns the intersection point if the three halfspaces all intersect at a single point.
80    #[inline]
81    pub fn intersection_point(a: HalfSpace, b: HalfSpace, c: HalfSpace) -> Option<Vec3> {
82        let an = a.normal();
83        let bn = b.normal();
84        let cn = c.normal();
85
86        let x = Vec3A::new(an.x, bn.x, cn.x);
87        let y = Vec3A::new(an.y, bn.y, cn.y);
88        let z = Vec3A::new(an.z, bn.z, cn.z);
89
90        let d = -Vec3A::new(a.d(), b.d(), c.d());
91
92        let u = y.cross(z);
93        let v = x.cross(d);
94
95        let denom = x.dot(u);
96
97        if ops::abs(denom) < f32::EPSILON {
98            return None;
99        }
100
101        Some(Vec3::new(d.dot(u), z.dot(v), -y.dot(v)) / denom)
102    }
103}
104
105#[cfg(test)]
106mod half_space_tests {
107    use core::f32;
108
109    use approx::assert_relative_eq;
110
111    use super::HalfSpace;
112    use crate::{Vec3, Vec4};
113
114    #[test]
115    fn intersection_point() {
116        // Intersection of shifted xy, xz, and yz planes
117        let xy_at_z_3 = HalfSpace {
118            normal_d: Vec4::new(0., 0., -1., 3.),
119        };
120        let xz_at_y_2 = HalfSpace {
121            normal_d: Vec4::new(0., 1., 0., -2.),
122        };
123        let yz_at_x_1 = HalfSpace {
124            normal_d: Vec4::new(1., 0., 0., -1.),
125        };
126        assert_relative_eq!(
127            HalfSpace::intersection_point(xy_at_z_3, xz_at_y_2, yz_at_x_1).unwrap(),
128            Vec3::new(1., 2., 3.),
129            epsilon = 2e-7
130        );
131
132        // Three planes that do not simultaneously intersect
133        let xz_at_y_3 = HalfSpace {
134            normal_d: Vec4::new(0., 1., 0., -3.),
135        };
136        assert!(HalfSpace::intersection_point(xy_at_z_3, xz_at_y_2, xz_at_y_3).is_none());
137
138        // Three planes that intersect at a line
139        let other_xz_at_y_2 = HalfSpace {
140            normal_d: Vec4::new(0., -1., 0., 3.),
141        };
142        assert!(HalfSpace::intersection_point(xy_at_z_3, xz_at_y_2, other_xz_at_y_2).is_none());
143
144        // Three identical planes
145        assert!(HalfSpace::intersection_point(xz_at_y_2, xz_at_y_2, other_xz_at_y_2).is_none());
146
147        // ill-defined halfspace
148        let ill_defined = HalfSpace {
149            normal_d: Vec4::new(0., 0., 0., f32::INFINITY),
150        };
151        assert!(HalfSpace::intersection_point(xy_at_z_3, xz_at_y_2, ill_defined).is_none());
152    }
153}