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 box grown by `by` on every side; a negative `by` shrinks it,
115 /// and never past a point — the centre holds.
116 ///
117 /// ```
118 /// use arris_math::Aabb;
119 ///
120 /// let b = Aabb { min: [0.0; 3], max: [2.0; 3] };
121 /// assert_eq!(b.inflated(0.5).min, [-0.5; 3]);
122 /// assert_eq!(b.inflated(-5.0).extent(), [0.0; 3], "never past a point");
123 /// ```
124 pub fn inflated(&self, by: f64) -> Aabb {
125 let mut out = *self;
126 for i in 0..3 {
127 let centre = 0.5 * (self.min[i] + self.max[i]);
128 out.min[i] = (self.min[i] - by).min(centre);
129 out.max[i] = (self.max[i] + by).max(centre);
130 }
131 out
132 }
133}