Skip to main content

arris_math/
aabb.rs

1//! Axis-aligned bounding boxes: the cheap reject a boolean's face pairs
2//! and a renderer's camera fit are both over.
3
4use crate::Point3;
5
6/// The axis-aligned box around a set of points: `min ≤ max` on every axis,
7/// both finite. A box is never empty; an empty point set has no box.
8///
9/// ```
10/// use arris_math::Aabb;
11///
12/// let b = Aabb::of_points(&[[0.0, 0.0, 0.0], [2.0, -1.0, 3.0]]).unwrap();
13/// assert_eq!(b.min, [0.0, -1.0, 0.0]);
14/// assert_eq!(b.max, [2.0, 0.0, 3.0]);
15/// assert_eq!(b.extent(), [2.0, 1.0, 3.0]);
16/// assert_eq!(b.center(), [1.0, -0.5, 1.5]);
17/// ```
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct Aabb {
20    /// The smallest coordinate on each axis.
21    pub min: [f64; 3],
22    /// The largest coordinate on each axis.
23    pub max: [f64; 3],
24}
25
26impl Aabb {
27    /// The box around `points`, or `None` for no points.
28    pub fn of_points<'a>(points: impl IntoIterator<Item = &'a [f64; 3]>) -> Option<Aabb> {
29        let mut it = points.into_iter();
30        let first = *it.next()?;
31        let mut b = Aabb {
32            min: first,
33            max: first,
34        };
35        for p in it {
36            b = b.union(Aabb { min: *p, max: *p });
37        }
38        Some(b)
39    }
40
41    /// The smallest box containing both.
42    pub fn union(self, other: Aabb) -> Aabb {
43        let mut b = self;
44        for ((lo, hi), (olo, ohi)) in b
45            .min
46            .iter_mut()
47            .zip(b.max.iter_mut())
48            .zip(other.min.iter().zip(other.max.iter()))
49        {
50            *lo = lo.min(*olo);
51            *hi = hi.max(*ohi);
52        }
53        b
54    }
55
56    /// `max - min` per axis.
57    pub fn extent(&self) -> [f64; 3] {
58        [
59            self.max[0] - self.min[0],
60            self.max[1] - self.min[1],
61            self.max[2] - self.min[2],
62        ]
63    }
64
65    /// The midpoint.
66    pub fn center(&self) -> [f64; 3] {
67        [
68            0.5 * (self.min[0] + self.max[0]),
69            0.5 * (self.min[1] + self.max[1]),
70            0.5 * (self.min[2] + self.max[2]),
71        ]
72    }
73
74    /// The length of the box's diagonal: the scale of what it contains.
75    pub fn diagonal(&self) -> f64 {
76        let e = self.extent();
77        (e[0] * e[0] + e[1] * e[1] + e[2] * e[2]).sqrt()
78    }
79
80    /// The box holding one point, of zero extent.
81    ///
82    /// ```
83    /// use arris_math::{Aabb, Point3};
84    ///
85    /// let b = Aabb::of_point(Point3::new(1.0, 2.0, 3.0));
86    /// assert_eq!(b.min, b.max);
87    /// assert_eq!(b.extent(), [0.0; 3]);
88    /// ```
89    pub fn of_point(p: Point3) -> Aabb {
90        Aabb {
91            min: [p.x, p.y, p.z],
92            max: [p.x, p.y, p.z],
93        }
94    }
95
96    /// `true` when the two boxes share a point, touching included: the
97    /// cheap reject before a face pair is intersected. Boxes that only
98    /// touch on a face intersect, so a caller that needs a margin
99    /// [`Aabb::inflated`] one of them by its tolerance first.
100    ///
101    /// ```
102    /// use arris_math::Aabb;
103    ///
104    /// let a = Aabb { min: [0.0; 3], max: [1.0; 3] };
105    /// let touching = Aabb { min: [1.0, 0.0, 0.0], max: [2.0, 1.0, 1.0] };
106    /// let clear = Aabb { min: [1.5, 0.0, 0.0], max: [2.0, 1.0, 1.0] };
107    /// assert!(a.intersects(&touching));
108    /// assert!(!a.intersects(&clear));
109    /// ```
110    pub fn intersects(&self, other: &Aabb) -> bool {
111        (0..3).all(|i| self.min[i] <= other.max[i] && other.min[i] <= self.max[i])
112    }
113
114    /// The distance between the two boxes: the length of the shortest
115    /// segment from a point of one to a point of the other, `0.0` when
116    /// they overlap or touch. It is a lower bound on the distance between
117    /// anything the boxes hold.
118    ///
119    /// ```
120    /// use arris_math::Aabb;
121    ///
122    /// let a = Aabb { min: [0.0; 3], max: [1.0; 3] };
123    /// let b = Aabb { min: [4.0, 5.0, 0.0], max: [5.0, 6.0, 1.0] };
124    /// assert_eq!(a.distance(&b), 5.0); // 3 along x, 4 along y
125    /// assert_eq!(a.distance(&a), 0.0);
126    /// ```
127    pub fn distance(&self, other: &Aabb) -> f64 {
128        let mut sq = 0.0;
129        for i in 0..3 {
130            let gap = (self.min[i] - other.max[i])
131                .max(other.min[i] - self.max[i])
132                .max(0.0);
133            sq += gap * gap;
134        }
135        sq.sqrt()
136    }
137
138    /// The box grown by `by` on every side; a negative `by` shrinks it,
139    /// and never past a point — the centre holds.
140    ///
141    /// ```
142    /// use arris_math::Aabb;
143    ///
144    /// let b = Aabb { min: [0.0; 3], max: [2.0; 3] };
145    /// assert_eq!(b.inflated(0.5).min, [-0.5; 3]);
146    /// assert_eq!(b.inflated(-5.0).extent(), [0.0; 3], "never past a point");
147    /// ```
148    pub fn inflated(&self, by: f64) -> Aabb {
149        let mut out = *self;
150        for i in 0..3 {
151            let centre = 0.5 * (self.min[i] + self.max[i]);
152            out.min[i] = (self.min[i] - by).min(centre);
153            out.max[i] = (self.max[i] + by).max(centre);
154        }
155        out
156    }
157}