1use crate::{BBox2D, Point3D, f64_max, f64_min};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct BBox3D {
11 pub min_x: f64,
13 pub min_y: f64,
15 pub min_z: f64,
17 pub max_x: f64,
19 pub max_y: f64,
21 pub max_z: f64,
23}
24
25impl BBox3D {
26 #[must_use]
28 #[inline]
29 pub const fn new(
30 min_x: f64,
31 min_y: f64,
32 min_z: f64,
33 max_x: f64,
34 max_y: f64,
35 max_z: f64,
36 ) -> Self {
37 Self {
38 min_x,
39 min_y,
40 min_z,
41 max_x,
42 max_y,
43 max_z,
44 }
45 }
46
47 #[must_use]
49 #[inline]
50 pub fn is_valid(&self) -> bool {
51 self.min_x <= self.max_x && self.min_y <= self.max_y && self.min_z <= self.max_z
52 }
53
54 #[must_use]
56 #[inline]
57 pub fn contains_point(&self, p: &Point3D) -> bool {
58 p.x >= self.min_x
59 && p.x <= self.max_x
60 && p.y >= self.min_y
61 && p.y <= self.max_y
62 && p.z >= self.min_z
63 && p.z <= self.max_z
64 }
65
66 #[must_use]
68 #[inline]
69 pub fn intersects(&self, other: &BBox3D) -> bool {
70 self.min_x <= other.max_x
71 && self.max_x >= other.min_x
72 && self.min_y <= other.max_y
73 && self.max_y >= other.min_y
74 && self.min_z <= other.max_z
75 && self.max_z >= other.min_z
76 }
77
78 #[must_use]
80 #[inline]
81 pub fn union(&self, other: &BBox3D) -> BBox3D {
82 BBox3D::new(
83 f64_min(self.min_x, other.min_x),
84 f64_min(self.min_y, other.min_y),
85 f64_min(self.min_z, other.min_z),
86 f64_max(self.max_x, other.max_x),
87 f64_max(self.max_y, other.max_y),
88 f64_max(self.max_z, other.max_z),
89 )
90 }
91
92 #[must_use]
96 #[inline]
97 pub fn volume(&self) -> f64 {
98 let w = self.max_x - self.min_x;
99 let h = self.max_y - self.min_y;
100 let d = self.max_z - self.min_z;
101 if w < 0.0 || h < 0.0 || d < 0.0 {
102 0.0
103 } else {
104 w * h * d
105 }
106 }
107
108 #[must_use]
110 #[inline]
111 pub fn center(&self) -> Point3D {
112 Point3D::new(
113 (self.min_x + self.max_x) * 0.5,
114 (self.min_y + self.max_y) * 0.5,
115 (self.min_z + self.max_z) * 0.5,
116 )
117 }
118
119 #[must_use]
123 #[inline]
124 pub fn expand(&self, amount: f64) -> BBox3D {
125 BBox3D::new(
126 self.min_x - amount,
127 self.min_y - amount,
128 self.min_z - amount,
129 self.max_x + amount,
130 self.max_y + amount,
131 self.max_z + amount,
132 )
133 }
134
135 #[must_use]
139 #[inline]
140 pub fn surface_area(&self) -> f64 {
141 let w = self.max_x - self.min_x;
142 let h = self.max_y - self.min_y;
143 let d = self.max_z - self.min_z;
144 if w < 0.0 || h < 0.0 || d < 0.0 {
145 0.0
146 } else {
147 2.0 * (w * h + h * d + w * d)
148 }
149 }
150
151 #[must_use]
153 #[inline]
154 pub fn to_2d(&self) -> BBox2D {
155 BBox2D::new(self.min_x, self.min_y, self.max_x, self.max_y)
156 }
157
158 #[must_use]
160 pub fn diagonal(&self) -> f64 {
161 let dx = self.max_x - self.min_x;
162 let dy = self.max_y - self.min_y;
163 let dz = self.max_z - self.min_z;
164 crate::libm_sqrt(dx * dx + dy * dy + dz * dz)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_new_and_valid() {
174 let bbox = BBox3D::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
175 assert!(bbox.is_valid());
176 }
177
178 #[test]
179 fn test_invalid() {
180 let bbox = BBox3D::new(1.0, 0.0, 0.0, 0.0, 1.0, 1.0);
181 assert!(!bbox.is_valid());
182 }
183
184 #[test]
185 fn test_contains_point_inside() {
186 let bbox = BBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
187 assert!(bbox.contains_point(&Point3D::new(5.0, 5.0, 5.0)));
188 }
189
190 #[test]
191 fn test_contains_point_on_boundary() {
192 let bbox = BBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
193 assert!(bbox.contains_point(&Point3D::new(0.0, 0.0, 0.0)));
194 assert!(bbox.contains_point(&Point3D::new(10.0, 10.0, 10.0)));
195 }
196
197 #[test]
198 fn test_contains_point_outside() {
199 let bbox = BBox3D::new(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
200 assert!(!bbox.contains_point(&Point3D::new(-1.0, 5.0, 5.0)));
201 assert!(!bbox.contains_point(&Point3D::new(5.0, 11.0, 5.0)));
202 assert!(!bbox.contains_point(&Point3D::new(5.0, 5.0, -0.1)));
203 }
204
205 #[test]
206 fn test_intersects() {
207 let a = BBox3D::new(0.0, 0.0, 0.0, 5.0, 5.0, 5.0);
208 let b = BBox3D::new(3.0, 3.0, 3.0, 8.0, 8.0, 8.0);
209 assert!(a.intersects(&b));
210 assert!(b.intersects(&a));
211 }
212
213 #[test]
214 fn test_no_intersect() {
215 let a = BBox3D::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
216 let b = BBox3D::new(2.0, 2.0, 2.0, 3.0, 3.0, 3.0);
217 assert!(!a.intersects(&b));
218 }
219
220 #[test]
221 fn test_union() {
222 let a = BBox3D::new(0.0, 0.0, 0.0, 5.0, 5.0, 5.0);
223 let b = BBox3D::new(3.0, -1.0, 2.0, 8.0, 4.0, 10.0);
224 let u = a.union(&b);
225 assert!((u.min_x - 0.0).abs() < f64::EPSILON);
226 assert!((u.min_y - (-1.0)).abs() < f64::EPSILON);
227 assert!((u.min_z - 0.0).abs() < f64::EPSILON);
228 assert!((u.max_x - 8.0).abs() < f64::EPSILON);
229 assert!((u.max_y - 5.0).abs() < f64::EPSILON);
230 assert!((u.max_z - 10.0).abs() < f64::EPSILON);
231 }
232
233 #[test]
234 fn test_volume() {
235 let bbox = BBox3D::new(0.0, 0.0, 0.0, 2.0, 3.0, 4.0);
236 assert!((bbox.volume() - 24.0).abs() < f64::EPSILON);
237 }
238
239 #[test]
240 fn test_volume_zero() {
241 let bbox = BBox3D::new(0.0, 0.0, 0.0, 0.0, 3.0, 4.0);
242 assert!((bbox.volume() - 0.0).abs() < f64::EPSILON);
243 }
244
245 #[test]
246 fn test_volume_invalid() {
247 let bbox = BBox3D::new(1.0, 0.0, 0.0, 0.0, 1.0, 1.0);
248 assert!((bbox.volume() - 0.0).abs() < f64::EPSILON);
249 }
250
251 #[test]
252 fn test_center() {
253 let bbox = BBox3D::new(0.0, 2.0, 4.0, 10.0, 8.0, 12.0);
254 let c = bbox.center();
255 assert!((c.x - 5.0).abs() < f64::EPSILON);
256 assert!((c.y - 5.0).abs() < f64::EPSILON);
257 assert!((c.z - 8.0).abs() < f64::EPSILON);
258 }
259
260 #[test]
261 fn test_expand() {
262 let bbox = BBox3D::new(1.0, 1.0, 1.0, 3.0, 3.0, 3.0);
263 let expanded = bbox.expand(0.5);
264 assert!((expanded.min_x - 0.5).abs() < f64::EPSILON);
265 assert!((expanded.max_z - 3.5).abs() < f64::EPSILON);
266 assert!((expanded.volume() - 27.0).abs() < 1e-10);
267 }
268
269 #[test]
270 fn test_surface_area() {
271 let bbox = BBox3D::new(0.0, 0.0, 0.0, 2.0, 3.0, 4.0);
272 assert!((bbox.surface_area() - 52.0).abs() < f64::EPSILON);
274 }
275
276 #[test]
277 fn test_to_2d() {
278 let bbox3 = BBox3D::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
279 let bbox2 = bbox3.to_2d();
280 assert!((bbox2.min_x - 1.0).abs() < f64::EPSILON);
281 assert!((bbox2.min_y - 2.0).abs() < f64::EPSILON);
282 assert!((bbox2.max_x - 4.0).abs() < f64::EPSILON);
283 assert!((bbox2.max_y - 5.0).abs() < f64::EPSILON);
284 }
285
286 #[test]
287 fn test_diagonal() {
288 let bbox = BBox3D::new(0.0, 0.0, 0.0, 3.0, 4.0, 0.0);
289 assert!((bbox.diagonal() - 5.0).abs() < 1e-10);
291 }
292
293 #[test]
294 fn test_diagonal_cube() {
295 let bbox = BBox3D::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
296 assert!((bbox.diagonal() - 1.732_050_808_068_872_4).abs() < 1e-8);
298 }
299
300 #[test]
301 fn test_intersects_touching() {
302 let a = BBox3D::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
303 let b = BBox3D::new(1.0, 1.0, 1.0, 2.0, 2.0, 2.0);
304 assert!(a.intersects(&b)); }
306}