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}