Skip to main content

fast_mesh_bvh/
aabb.rs

1use std::cmp::Ordering;
2
3/// Axis-aligned bounding box in mesh-local coordinates.
4///
5/// [`Aabb::EMPTY`] (min = +∞, max = −∞) contains nothing and overlaps nothing.
6/// Non-finite coordinates are tolerated: `f32::min`/`max` ignore NaN, so a NaN
7/// coordinate never widens a box.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct Aabb {
10    pub min: [f32; 3],
11    pub max: [f32; 3],
12}
13
14impl Aabb {
15    pub const EMPTY: Aabb = Aabb {
16        min: [f32::INFINITY; 3],
17        max: [f32::NEG_INFINITY; 3],
18    };
19
20    pub fn new(min: [f32; 3], max: [f32; 3]) -> Self {
21        Self { min, max }
22    }
23
24    /// Box of one triangle, given slot-aligned positions.
25    pub fn of_triangle(positions: &[[f32; 3]], tri: [u32; 3]) -> Self {
26        let mut b = Self::EMPTY;
27        for v in tri {
28            b.grow_point(positions[v as usize]);
29        }
30        b
31    }
32
33    pub fn grow_point(&mut self, p: [f32; 3]) {
34        for (k, c) in p.into_iter().enumerate() {
35            self.min[k] = self.min[k].min(c);
36            self.max[k] = self.max[k].max(c);
37        }
38    }
39
40    pub fn grow(&mut self, other: &Aabb) {
41        for k in 0..3 {
42            self.min[k] = self.min[k].min(other.min[k]);
43            self.max[k] = self.max[k].max(other.max[k]);
44        }
45    }
46
47    pub fn union(mut self, other: &Aabb) -> Aabb {
48        self.grow(other);
49        self
50    }
51
52    /// True when any axis has `min > max` (or a NaN bound).
53    pub fn is_empty(&self) -> bool {
54        (0..3).any(|k| {
55            !matches!(
56                self.min[k].partial_cmp(&self.max[k]),
57                Some(Ordering::Less | Ordering::Equal)
58            )
59        })
60    }
61
62    /// True when `other` lies inside this box. An empty `other` is always contained.
63    pub fn contains(&self, other: &Aabb) -> bool {
64        other.is_empty()
65            || (0..3).all(|k| self.min[k] <= other.min[k] && other.max[k] <= self.max[k])
66    }
67
68    /// True when the boxes share at least one point (touching counts).
69    pub fn overlaps(&self, other: &Aabb) -> bool {
70        !self.is_empty()
71            && !other.is_empty()
72            && (0..3).all(|k| self.min[k] <= other.max[k] && other.min[k] <= self.max[k])
73    }
74
75    /// Surface area; 0 for an empty box.
76    pub fn surface_area(&self) -> f32 {
77        if self.is_empty() {
78            return 0.0;
79        }
80        let d = [0, 1, 2].map(|k| self.max[k] - self.min[k]);
81        2.0 * (d[0] * d[1] + d[1] * d[2] + d[2] * d[0])
82    }
83
84    /// Box center; components that are not finite (empty or infinite boxes) read 0.
85    pub fn center(&self) -> [f32; 3] {
86        [0, 1, 2].map(|k| {
87            let c = (self.min[k] + self.max[k]) * 0.5;
88            if c.is_finite() { c } else { 0.0 }
89        })
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn empty_box_contains_and_overlaps_nothing() {
99        let unit = Aabb::new([0.0; 3], [1.0; 3]);
100        assert!(Aabb::EMPTY.is_empty());
101        assert!(!Aabb::EMPTY.overlaps(&unit));
102        assert!(unit.contains(&Aabb::EMPTY));
103        assert!(!Aabb::EMPTY.contains(&unit));
104        assert_eq!(Aabb::EMPTY.surface_area(), 0.0);
105    }
106
107    #[test]
108    fn touching_boxes_overlap() {
109        let a = Aabb::new([0.0; 3], [1.0; 3]);
110        let b = Aabb::new([1.0, 0.0, 0.0], [2.0, 1.0, 1.0]);
111        assert!(a.overlaps(&b));
112        let c = Aabb::new([1.5, 0.0, 0.0], [2.0, 1.0, 1.0]);
113        assert!(!a.overlaps(&c));
114    }
115
116    #[test]
117    fn triangle_box_and_area() {
118        let positions = [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 3.0, 1.0]];
119        let b = Aabb::of_triangle(&positions, [0, 1, 2]);
120        assert_eq!(b, Aabb::new([0.0, 0.0, 0.0], [2.0, 3.0, 1.0]));
121        assert_eq!(b.surface_area(), 2.0 * (6.0 + 3.0 + 2.0));
122        assert_eq!(b.center(), [1.0, 1.5, 0.5]);
123    }
124
125    #[test]
126    fn nan_coordinates_never_widen_a_box() {
127        let positions = [[f32::NAN, 0.0, 0.0], [1.0, 1.0, 1.0], [2.0, f32::NAN, 2.0]];
128        let b = Aabb::of_triangle(&positions, [0, 1, 2]);
129        assert_eq!(b, Aabb::new([1.0, 0.0, 0.0], [2.0, 1.0, 2.0]));
130        let all_nan = Aabb::of_triangle(&[[f32::NAN; 3]], [0, 0, 0]);
131        assert!(all_nan.is_empty());
132        assert_eq!(all_nan.center(), [0.0; 3]);
133    }
134}