qualia_core_db/render/physics/
aabb.rs1use crate::render::pga::{sandwich_point, Motor};
8
9#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct Aabb {
12 pub min: [f32; 3],
13 pub max: [f32; 3],
14}
15
16impl Aabb {
17 #[inline]
18 pub fn new(min: [f32; 3], max: [f32; 3]) -> Self {
19 Aabb { min, max }
20 }
21
22 pub fn from_points(points: &[[f32; 3]]) -> Option<Aabb> {
24 let first = *points.first()?;
25 let mut min = first;
26 let mut max = first;
27 for p in &points[1..] {
28 for k in 0..3 {
29 if p[k] < min[k] {
30 min[k] = p[k];
31 }
32 if p[k] > max[k] {
33 max[k] = p[k];
34 }
35 }
36 }
37 Some(Aabb { min, max })
38 }
39
40 #[inline]
41 pub fn center(&self) -> [f32; 3] {
42 [
43 0.5 * (self.min[0] + self.max[0]),
44 0.5 * (self.min[1] + self.max[1]),
45 0.5 * (self.min[2] + self.max[2]),
46 ]
47 }
48
49 #[inline]
51 pub fn extent(&self) -> [f32; 3] {
52 [
53 self.max[0] - self.min[0],
54 self.max[1] - self.min[1],
55 self.max[2] - self.min[2],
56 ]
57 }
58
59 #[inline]
61 pub fn volume(&self) -> f32 {
62 let e = self.extent();
63 e[0] * e[1] * e[2]
64 }
65
66 #[inline]
67 pub fn contains_point(&self, p: [f32; 3]) -> bool {
68 (0..3).all(|k| p[k] >= self.min[k] && p[k] <= self.max[k])
69 }
70
71 #[inline]
73 pub fn contains(&self, other: &Aabb) -> bool {
74 (0..3).all(|k| other.min[k] >= self.min[k] && other.max[k] <= self.max[k])
75 }
76
77 pub fn transformed(&self, motor: Motor, scale: [f32; 3]) -> Aabb {
80 let c = self.center();
81 let mut min = [f32::INFINITY; 3];
82 let mut max = [f32::NEG_INFINITY; 3];
83 for ix in 0..8u8 {
84 let corner = [
85 if ix & 1 == 0 {
86 self.min[0]
87 } else {
88 self.max[0]
89 },
90 if ix & 2 == 0 {
91 self.min[1]
92 } else {
93 self.max[1]
94 },
95 if ix & 4 == 0 {
96 self.min[2]
97 } else {
98 self.max[2]
99 },
100 ];
101 let scaled = [
102 c[0] + (corner[0] - c[0]) * scale[0],
103 c[1] + (corner[1] - c[1]) * scale[1],
104 c[2] + (corner[2] - c[2]) * scale[2],
105 ];
106 let w = sandwich_point(motor, scaled);
107 for k in 0..3 {
108 if w[k] < min[k] {
109 min[k] = w[k];
110 }
111 if w[k] > max[k] {
112 max[k] = w[k];
113 }
114 }
115 }
116 Aabb { min, max }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 fn unit() -> Aabb {
125 Aabb::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0])
126 }
127
128 #[test]
129 fn extent_volume_center() {
130 let b = unit();
131 assert_eq!(b.extent(), [2.0, 2.0, 2.0]);
132 assert_eq!(b.volume(), 8.0);
133 assert_eq!(b.center(), [0.0, 0.0, 0.0]);
134 }
135
136 #[test]
137 fn identity_transform_is_noop() {
138 let b = unit();
139 let out = b.transformed(Motor::identity(), [1.0, 1.0, 1.0]);
140 for k in 0..3 {
141 assert!((out.min[k] - b.min[k]).abs() < 1e-5);
142 assert!((out.max[k] - b.max[k]).abs() < 1e-5);
143 }
144 }
145
146 #[test]
147 fn scale_shrinks_extent_about_centre() {
148 let out = unit().transformed(Motor::identity(), [0.5, 0.5, 0.5]);
149 assert!((out.extent()[0] - 1.0).abs() < 1e-5); assert_eq!(out.center(), [0.0, 0.0, 0.0]); }
152
153 #[test]
154 fn contains_and_from_points() {
155 let b = Aabb::from_points(&[[0.0, 0.0, 0.0], [1.0, 2.0, 3.0], [-1.0, 0.0, 0.0]]).unwrap();
156 assert_eq!(b.min, [-1.0, 0.0, 0.0]);
157 assert_eq!(b.max, [1.0, 2.0, 3.0]);
158 assert!(b.contains_point([0.0, 1.0, 1.0]));
159 assert!(!b.contains_point([5.0, 0.0, 0.0]));
160 assert!(Aabb::from_points(&[]).is_none());
161 }
162}