pasture_core/math/bounds.rs
1use std::iter::FromIterator;
2
3use float_ord::FloatOrd;
4use nalgebra::{ClosedSub, Point3, Scalar, Vector3};
5
6/// 3D axis-aligned bounding box
7#[derive(Debug, Clone, Copy, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct AABB<T: Scalar + PartialOrd> {
10 min: Point3<T>,
11 max: Point3<T>,
12}
13
14impl<T: Scalar + ClosedSub + PartialOrd + Copy> AABB<T> {
15 /// Creates a new AABB from the given minimum and maximum coordinates. Panics if the minimum position is
16 /// not less than or equal to the maximum position
17 /// ```
18 /// # use pasture_core::math::AABB;
19 /// let bounds = AABB::from_min_max(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
20 /// ```
21 pub fn from_min_max(min: Point3<T>, max: Point3<T>) -> Self {
22 if min.x > max.x || min.y > max.y || min.z > max.z {
23 panic!("AABB::from_min_max: Minimum position must be <= maximum position!");
24 }
25 Self { min, max }
26 }
27
28 /// Creates a new AABB from the given minimum and maximum coordinates. Similar to [from_min_max](AABB::from_min_max)
29 /// but performs no checks that min <= max. If you know that min <= max, prefer this function over [from_min_max](AABB::from_min_max)
30 /// ```
31 /// # use pasture_core::math::AABB;
32 /// let bounds = AABB::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
33 /// ```
34 pub fn from_min_max_unchecked(min: Point3<T>, max: Point3<T>) -> Self {
35 Self { min, max }
36 }
37
38 /// Returns the minimum point of this AABB
39 /// ```
40 /// # use pasture_core::math::AABB;
41 /// let bounds = AABB::from_min_max_unchecked(nalgebra::Point3::new(-1.0, -1.0, -1.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
42 /// assert_eq!(*bounds.min(), nalgebra::Point3::new(-1.0, -1.0, -1.0));
43 /// ```
44 pub fn min(&self) -> &Point3<T> {
45 &self.min
46 }
47
48 /// Returns the maximum point of this AABB
49 /// ```
50 /// # use pasture_core::math::AABB;
51 /// let bounds = AABB::from_min_max_unchecked(nalgebra::Point3::new(-1.0, -1.0, -1.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
52 /// assert_eq!(*bounds.max(), nalgebra::Point3::new(1.0, 1.0, 1.0));
53 /// ```
54 pub fn max(&self) -> &Point3<T> {
55 &self.max
56 }
57
58 /// Returns the extent of this AABB. The extent is the size between the minimum and maximum position of this AABB
59 /// ```
60 /// # use pasture_core::math::AABB;
61 /// let bounds = AABB::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
62 /// assert_eq!(bounds.extent(), nalgebra::Vector3::new(1.0, 1.0, 1.0));
63 /// ```
64 pub fn extent(&self) -> Vector3<T> {
65 self.max - self.min
66 }
67
68 /// Performs an intersection test between this AABB and the given AABB. Returns true if the two
69 /// bounding boxes intersect. If one of the boxes is fully contained within the other, this also
70 /// counts as an intersection
71 /// ```
72 /// # use pasture_core::math::AABB;
73 /// let bounds_a = AABB::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
74 /// let bounds_b = AABB::from_min_max_unchecked(nalgebra::Point3::new(0.5, 0.5, 0.5), nalgebra::Point3::new(1.5, 1.5, 1.5));
75 /// assert!(bounds_a.intersects(&bounds_b));
76 /// ```
77 pub fn intersects(&self, other: &AABB<T>) -> bool {
78 (self.min.x <= other.max.x && self.max.x >= other.min.x)
79 && (self.min.y <= other.max.y && self.max.y >= other.min.y)
80 && (self.min.z <= other.max.z && self.max.z >= other.min.z)
81 }
82
83 /// Returns true if the given point is contained within this AABB. Points right on the boundary
84 /// of this AABB (e.g. point.x == self.max.x or self.min.x) will return true as well.
85 /// ```
86 /// # use pasture_core::math::AABB;
87 /// let bounds = AABB::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
88 /// assert!(bounds.contains(&nalgebra::Point3::new(0.5, 0.5, 0.5)));
89 /// ```
90 pub fn contains(&self, point: &Point3<T>) -> bool {
91 point.x >= self.min.x
92 && point.x <= self.max.x
93 && point.y >= self.min.y
94 && point.y <= self.max.y
95 && point.z >= self.min.z
96 && point.z <= self.max.z
97 }
98
99 /// Computes the union of the given bounding boxes. The union of two bounding boxes a and b is defined as the
100 /// smallest AABB that fully contains both a and b.
101 /// ```
102 /// # use pasture_core::math::AABB;
103 /// let bounds_a = AABB::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
104 /// let bounds_b = AABB::from_min_max_unchecked(nalgebra::Point3::new(2.0, 2.0, 2.0), nalgebra::Point3::new(3.0, 3.0, 3.0));
105 /// let merged_bounds = AABB::union(&bounds_a, &bounds_b);
106 /// assert_eq!(*merged_bounds.min(), nalgebra::Point3::new(0.0, 0.0, 0.0));
107 /// assert_eq!(*merged_bounds.max(), nalgebra::Point3::new(3.0, 3.0, 3.0));
108 /// ```
109 pub fn union(a: &AABB<T>, b: &AABB<T>) -> Self {
110 let min_x = if a.min.x < b.min.x { a.min.x } else { b.min.x };
111 let min_y = if a.min.y < b.min.y { a.min.y } else { b.min.y };
112 let min_z = if a.min.z < b.min.z { a.min.z } else { b.min.z };
113
114 let max_x = if a.max.x > b.max.x { a.max.x } else { b.max.x };
115 let max_y = if a.max.y > b.max.y { a.max.y } else { b.max.y };
116 let max_z = if a.max.z > b.max.z { a.max.z } else { b.max.z };
117
118 Self {
119 min: Point3::new(min_x, min_y, min_z),
120 max: Point3::new(max_x, max_y, max_z),
121 }
122 }
123
124 /// Extends the given AABB so that it contains the given point.
125 /// ```
126 /// # use pasture_core::math::AABB;
127 /// let bounds = AABB::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 1.0, 1.0));
128 /// let extended_bounds = AABB::extend_with_point(&bounds, &nalgebra::Point3::new(2.0, 2.0, 2.0));
129 /// assert_eq!(*extended_bounds.min(), nalgebra::Point3::new(0.0, 0.0, 0.0));
130 /// assert_eq!(*extended_bounds.max(), nalgebra::Point3::new(2.0, 2.0, 2.0));
131 /// ```
132 pub fn extend_with_point(bounds: &AABB<T>, point: &Point3<T>) -> AABB<T> {
133 let min_x = if bounds.min.x < point.x {
134 bounds.min.x
135 } else {
136 point.x
137 };
138 let min_y = if bounds.min.y < point.y {
139 bounds.min.y
140 } else {
141 point.y
142 };
143 let min_z = if bounds.min.z < point.z {
144 bounds.min.z
145 } else {
146 point.z
147 };
148
149 let max_x = if bounds.max.x > point.x {
150 bounds.max.x
151 } else {
152 point.x
153 };
154 let max_y = if bounds.max.y > point.y {
155 bounds.max.y
156 } else {
157 point.y
158 };
159 let max_z = if bounds.max.z > point.z {
160 bounds.max.z
161 } else {
162 point.z
163 };
164
165 Self {
166 min: Point3::new(min_x, min_y, min_z),
167 max: Point3::new(max_x, max_y, max_z),
168 }
169 }
170}
171
172impl AABB<f32> {
173 /// Returns the center point of this AABB.
174 /// ```
175 /// # use pasture_core::math::AABB;
176 /// let bounds = AABB::<f32>::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 2.0, 3.0));
177 /// assert_eq!(bounds.center(), nalgebra::Point3::new(0.5, 1.0, 1.5));
178 /// ```
179 pub fn center(&self) -> Point3<f32> {
180 Point3::new(
181 (self.min.x + self.max.x) / 2.0,
182 (self.min.y + self.max.y) / 2.0,
183 (self.min.z + self.max.z) / 2.0,
184 )
185 }
186
187 /// Returns a cubic version of the associated `AABB`. For this, the shortest two axes of the bounds
188 /// are elongated symmetrically from the center of the bounds so that all axis are of equal length
189 /// ```
190 /// # use pasture_core::math::AABB;
191 /// let bounds = AABB::<f32>::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 2.0, 4.0));
192 /// let cubic_bounds = AABB::<f32>::from_min_max_unchecked(nalgebra::Point3::new(-1.5, -1.0, 0.0), nalgebra::Point3::new(2.5, 3.0, 4.0));
193 /// assert_eq!(bounds.as_cubic().min(), cubic_bounds.min());
194 /// assert_eq!(bounds.as_cubic().max(), cubic_bounds.max());
195 /// ```
196 pub fn as_cubic(&self) -> AABB<f32> {
197 let extent = self.extent();
198 let max_axis = std::cmp::max(
199 FloatOrd(extent.x),
200 std::cmp::max(FloatOrd(extent.y), FloatOrd(extent.z)),
201 )
202 .0;
203 let max_axis_half = max_axis / 2.0;
204 let center = self.center();
205 Self {
206 min: center - Vector3::new(max_axis_half, max_axis_half, max_axis_half),
207 max: center + Vector3::new(max_axis_half, max_axis_half, max_axis_half),
208 }
209 }
210}
211
212impl AABB<f64> {
213 /// Returns the center point of this AABB.
214 /// ```
215 /// # use pasture_core::math::AABB;
216 /// let bounds = AABB::<f64>::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 2.0, 3.0));
217 /// assert_eq!(bounds.center(), nalgebra::Point3::new(0.5, 1.0, 1.5));
218 /// ```
219 pub fn center(&self) -> Point3<f64> {
220 Point3::new(
221 (self.min.x + self.max.x) / 2.0,
222 (self.min.y + self.max.y) / 2.0,
223 (self.min.z + self.max.z) / 2.0,
224 )
225 }
226
227 /// Like `contains`, but performs epsilon comparison on floating point values using the given `epsilon` value
228 pub fn contains_approx(&self, point: &Point3<f64>, epsilon: f64) -> bool {
229 let dx_min = point.x - self.min.x;
230 let dx_max = point.x - self.max.x;
231 let dy_min = point.y - self.min.y;
232 let dy_max = point.y - self.max.y;
233 let dz_min = point.z - self.min.z;
234 let dz_max = point.z - self.max.z;
235
236 !(dx_min < -epsilon
237 || dx_max > epsilon
238 || dy_min < -epsilon
239 || dy_max > epsilon
240 || dz_min < -epsilon
241 || dz_max > epsilon)
242 }
243
244 /// Returns a cubic version of the associated `AABB`. For this, the shortest two axes of the bounds
245 /// are elongated symmetrically from the center of the bounds so that all axis are of equal length
246 /// ```
247 /// # use pasture_core::math::AABB;
248 /// let bounds = AABB::<f64>::from_min_max_unchecked(nalgebra::Point3::new(0.0, 0.0, 0.0), nalgebra::Point3::new(1.0, 2.0, 4.0));
249 /// let cubic_bounds = AABB::<f64>::from_min_max_unchecked(nalgebra::Point3::new(-1.5, -1.0, 0.0), nalgebra::Point3::new(2.5, 3.0, 4.0));
250 /// assert_eq!(bounds.as_cubic().min(), cubic_bounds.min());
251 /// assert_eq!(bounds.as_cubic().max(), cubic_bounds.max());
252 /// ```
253 pub fn as_cubic(&self) -> AABB<f64> {
254 let extent = self.extent();
255 let max_axis = std::cmp::max(
256 FloatOrd(extent.x),
257 std::cmp::max(FloatOrd(extent.y), FloatOrd(extent.z)),
258 )
259 .0;
260 let max_axis_half = max_axis / 2.0;
261 let center = self.center();
262 Self {
263 min: center - Vector3::new(max_axis_half, max_axis_half, max_axis_half),
264 max: center + Vector3::new(max_axis_half, max_axis_half, max_axis_half),
265 }
266 }
267}
268
269impl<U: Into<Point3<f64>>> FromIterator<U> for AABB<f64> {
270 fn from_iter<V: IntoIterator<Item = U>>(iter: V) -> Self {
271 let mut min = Point3::new(f64::MAX, f64::MAX, f64::MAX);
272 let mut max = Point3::new(f64::MIN, f64::MIN, f64::MIN);
273 for point in iter.into_iter() {
274 let point: Point3<f64> = point.into();
275 min.x = if min.x < point.x { min.x } else { point.x };
276 min.y = if min.y < point.y { min.y } else { point.y };
277 min.z = if min.z < point.z { min.z } else { point.z };
278
279 max.x = if max.x > point.x { max.x } else { point.x };
280 max.y = if max.y > point.y { max.y } else { point.y };
281 max.z = if max.z > point.z { max.z } else { point.z };
282 }
283 Self::from_min_max(min, max)
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn aabb_contains_approx() {
293 let bounds =
294 AABB::from_min_max_unchecked(Point3::new(1.0, 1.0, 1.0), Point3::new(2.0, 2.0, 2.0));
295 let p1 = Point3::new(0.99, 0.99, 0.99);
296 let p2 = Point3::new(2.001, 1.999, 2.0);
297
298 assert!(bounds.contains_approx(&p1, 0.015));
299 assert!(!bounds.contains_approx(&p1, 0.001));
300 assert!(bounds.contains_approx(&p2, 0.0015));
301 assert!(!bounds.contains_approx(&p2, 0.0001));
302 }
303
304 #[test]
305 fn aabb_from_iter() {
306 let points = vec![
307 Vector3::new(0.0, 0.0, 0.0),
308 Vector3::new(1.0, 1.0, 1.0),
309 Vector3::new(-1.0, -1.0, -1.0),
310 ];
311 let bounds: AABB<f64> = points.into_iter().collect();
312 let expected_bounds =
313 AABB::from_min_max(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0));
314 assert_eq!(expected_bounds, bounds);
315 }
316}