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}