1use std::cmp::Ordering;
2
3#[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 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 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 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 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 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 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}