1use glam::{Mat4, Vec3A, Vec4Swizzles};
2use std::f32;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct Aabb {
10 pub min: Vec3A,
11 pub max: Vec3A,
12}
13
14impl Aabb {
15 #[inline]
22 pub fn new(min: impl Into<Vec3A>, max: impl Into<Vec3A>) -> Self {
23 Self {
24 min: min.into(),
25 max: max.into(),
26 }
27 }
28
29 #[inline]
31 pub fn empty() -> Self {
32 Self {
33 min: Vec3A::splat(f32::INFINITY),
34 max: Vec3A::splat(f32::NEG_INFINITY),
35 }
36 }
37
38 #[inline]
40 pub fn from_center_half_extents(
41 center: impl Into<Vec3A>,
42 half_extents: impl Into<Vec3A>,
43 ) -> Self {
44 let c = center.into();
45 let h = half_extents.into();
46 Self {
47 min: c - h,
48 max: c + h,
49 }
50 }
51
52 #[inline]
55 pub fn from_points(points: impl IntoIterator<Item = impl Into<Vec3A>>) -> Self {
56 let mut aabb = Self::empty();
57 for p in points {
58 aabb.extend(p);
59 }
60 aabb
61 }
62
63 #[inline]
72 pub fn is_empty(self) -> bool {
73 self.min.x > self.max.x || self.min.y > self.max.y || self.min.z > self.max.z
74 }
75
76 #[inline]
79 pub fn is_degenerate(self) -> bool {
80 self.min.cmpge(self.max).any()
81 }
82
83 #[inline]
85 pub fn is_valid(self) -> bool {
86 !self.is_empty()
87 }
88
89 #[inline]
95 pub fn center(self) -> Vec3A {
96 (self.min + self.max) * 0.5
97 }
98
99 #[inline]
101 pub fn half_extents(self) -> Vec3A {
102 (self.max - self.min) * 0.5
103 }
104
105 #[inline]
107 pub fn size(self) -> Vec3A {
108 self.max - self.min
109 }
110
111 #[inline]
114 pub fn volume(self) -> f32 {
115 if self.is_empty() {
116 return 0.0;
117 }
118 let s = self.size();
119 s.x * s.y * s.z
120 }
121
122 #[inline]
126 pub fn surface_area(self) -> f32 {
127 if self.is_empty() {
128 return 0.0;
129 }
130 let s = self.size();
131 2.0 * (s.x * s.y + s.y * s.z + s.z * s.x)
132 }
133
134 #[inline]
136 pub fn diagonal(self) -> f32 {
137 self.size().length()
138 }
139
140 #[inline]
146 pub fn contains_point(self, pt: impl Into<Vec3A>) -> bool {
147 let p = pt.into();
148 p.cmpge(self.min).all() && p.cmple(self.max).all()
149 }
150
151 #[inline]
153 pub fn contains_aabb(self, other: Self) -> bool {
154 if self.is_empty() || other.is_empty() {
155 return false;
156 }
157 other.min.cmpge(self.min).all() && other.max.cmple(self.max).all()
158 }
159
160 #[inline]
162 pub fn closest_point(self, pt: impl Into<Vec3A>) -> Vec3A {
163 pt.into().clamp(self.min, self.max)
164 }
165
166 #[inline]
169 pub fn distance_sq_to_point(self, pt: impl Into<Vec3A>) -> f32 {
170 let p = pt.into();
171 let closest = self.closest_point(p);
172 (p - closest).length_squared()
173 }
174
175 #[inline]
178 pub fn distance_to_point(self, pt: impl Into<Vec3A>) -> f32 {
179 self.distance_sq_to_point(pt).sqrt()
180 }
181
182 #[inline]
189 pub fn intersects(self, other: Self) -> bool {
190 if self.is_empty() || other.is_empty() {
191 return false;
192 }
193 self.min.cmple(other.max).all() && self.max.cmpge(other.min).all()
194 }
195
196 #[inline]
199 pub fn intersects_exclusive(self, other: Self) -> bool {
200 if self.is_empty() || other.is_empty() {
201 return false;
202 }
203 self.min.cmplt(other.max).all() && self.max.cmpgt(other.min).all()
204 }
205
206 #[inline]
209 pub fn intersection(self, other: Self) -> Option<Self> {
210 if !self.intersects(other) {
211 return None;
212 }
213 Some(Self {
214 min: self.min.max(other.min),
215 max: self.max.min(other.max),
216 })
217 }
218
219 #[inline]
225 pub fn extend(&mut self, pt: impl Into<Vec3A>) {
226 let p = pt.into();
227 self.min = self.min.min(p);
228 self.max = self.max.max(p);
229 }
230
231 #[inline]
233 pub fn merge(self, other: Self) -> Self {
234 Self {
235 min: self.min.min(other.min),
236 max: self.max.max(other.max),
237 }
238 }
239
240 #[inline]
243 pub fn expand(self, amount: f32) -> Self {
244 let delta = Vec3A::splat(amount);
245 let new_min = self.min - delta;
246 let new_max = self.max + delta;
247 if new_min.cmpgt(new_max).any() {
248 Self::empty()
249 } else {
250 Self {
251 min: new_min,
252 max: new_max,
253 }
254 }
255 }
256
257 #[inline]
269 pub fn transform(self, mat: &Mat4) -> Self {
270 if self.is_empty() {
271 return self;
272 }
273
274 let translation = Vec3A::from(mat.w_axis.xyz());
276 let mut new_min = translation;
277 let mut new_max = translation;
278
279 let cols = [
282 Vec3A::from(mat.x_axis.xyz()),
283 Vec3A::from(mat.y_axis.xyz()),
284 Vec3A::from(mat.z_axis.xyz()),
285 ];
286 let bounds = [self.min, self.max];
287
288 for (col_idx, col) in cols.iter().enumerate() {
289 for axis in 0..3 {
291 let col_val = col[axis];
292 let (lo, hi) = if col_val >= 0.0 {
293 (bounds[0][col_idx] * col_val, bounds[1][col_idx] * col_val)
294 } else {
295 (bounds[1][col_idx] * col_val, bounds[0][col_idx] * col_val)
296 };
297 new_min[axis] += lo;
298 new_max[axis] += hi;
299 }
300 }
301
302 Self {
303 min: new_min,
304 max: new_max,
305 }
306 }
307
308 #[inline]
315 pub fn corners(self) -> [Vec3A; 8] {
316 let (mn, mx) = (self.min, self.max);
317 [
318 Vec3A::new(mn.x, mn.y, mn.z),
319 Vec3A::new(mn.x, mx.y, mn.z),
320 Vec3A::new(mx.x, mn.y, mn.z),
321 Vec3A::new(mx.x, mx.y, mn.z),
322 Vec3A::new(mn.x, mn.y, mx.z),
323 Vec3A::new(mn.x, mx.y, mx.z),
324 Vec3A::new(mx.x, mn.y, mx.z),
325 Vec3A::new(mx.x, mx.y, mx.z),
326 ]
327 }
328}
329
330impl Default for Aabb {
335 #[inline]
336 fn default() -> Self {
337 Self::empty()
338 }
339}
340
341impl std::fmt::Display for Aabb {
342 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343 write!(
344 f,
345 "Aabb(min: [{:.3}, {:.3}, {:.3}], max: [{:.3}, {:.3}, {:.3}])",
346 self.min.x, self.min.y, self.min.z, self.max.x, self.max.y, self.max.z,
347 )
348 }
349}
350
351#[cfg(test)]
356mod tests {
357 use super::*;
358 use glam::Vec3;
359
360 const EPS: f32 = 1e-5;
361
362 fn approx_eq(a: Vec3A, b: Vec3A) -> bool {
363 (a - b).length() < EPS
364 }
365
366 #[test]
369 fn test_empty() {
370 let a = Aabb::empty();
371 assert!(a.is_empty());
372 assert!(!a.is_valid());
373 assert!(a.is_degenerate());
374 }
375
376 #[test]
377 fn test_degenerate_point() {
378 let a = Aabb::new(Vec3::ZERO, Vec3::ZERO);
379 assert!(!a.is_empty()); assert!(a.is_valid()); assert!(a.is_degenerate()); }
383
384 #[test]
385 fn test_from_center_half_extents() {
386 let a = Aabb::from_center_half_extents(Vec3::ZERO, Vec3::ONE);
387 assert!(approx_eq(a.min, Vec3A::splat(-1.0)));
388 assert!(approx_eq(a.max, Vec3A::splat(1.0)));
389 assert!(approx_eq(a.center(), Vec3A::ZERO));
390 }
391
392 #[test]
393 fn test_from_points_empty() {
394 let a = Aabb::from_points(std::iter::empty::<Vec3A>());
395 assert!(a.is_empty());
396 }
397
398 #[test]
399 fn test_from_points() {
400 let pts = [Vec3A::new(1.0, -2.0, 3.0), Vec3A::new(-1.0, 4.0, 0.0)];
401 let a = Aabb::from_points(pts);
402 assert!(approx_eq(a.min, Vec3A::new(-1.0, -2.0, 0.0)));
403 assert!(approx_eq(a.max, Vec3A::new(1.0, 4.0, 3.0)));
404 }
405
406 #[test]
409 fn test_center_size_half_extents() {
410 let a = Aabb::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(4.0, 6.0, 8.0));
411 assert!(approx_eq(a.center(), Vec3A::new(2.0, 3.0, 4.0)));
412 assert!(approx_eq(a.size(), Vec3A::new(4.0, 6.0, 8.0)));
413 assert!(approx_eq(a.half_extents(), Vec3A::new(2.0, 3.0, 4.0)));
414 }
415
416 #[test]
417 fn test_volume() {
418 let a = Aabb::new(Vec3::ZERO, Vec3::new(2.0, 3.0, 4.0));
419 assert!((a.volume() - 24.0).abs() < EPS);
420 assert!((Aabb::empty().volume()).abs() < EPS);
421 }
422
423 #[test]
424 fn test_surface_area() {
425 let a = Aabb::new(Vec3::ZERO, Vec3::new(1.0, 2.0, 3.0));
426 assert!((a.surface_area() - 22.0).abs() < EPS);
428 }
429
430 #[test]
433 fn test_contains_point() {
434 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
435 assert!(a.contains_point(Vec3A::splat(0.5)));
436 assert!(a.contains_point(Vec3A::ZERO)); assert!(a.contains_point(Vec3A::ONE)); assert!(!a.contains_point(Vec3A::splat(1.1)));
439 assert!(!a.contains_point(Vec3A::splat(-0.1)));
440 }
441
442 #[test]
443 fn test_contains_aabb() {
444 let outer = Aabb::new(Vec3::ZERO, Vec3::splat(10.0));
445 let inner = Aabb::new(Vec3::ONE, Vec3::splat(9.0));
446 assert!(outer.contains_aabb(inner));
447 assert!(!inner.contains_aabb(outer));
448 }
449
450 #[test]
451 fn test_closest_point_inside() {
452 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
453 let p = Vec3A::splat(0.5);
454 assert!(approx_eq(a.closest_point(p), p)); }
456
457 #[test]
458 fn test_closest_point_outside() {
459 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
460 let p = Vec3A::new(2.0, 0.5, 0.5);
461 assert!(approx_eq(a.closest_point(p), Vec3A::new(1.0, 0.5, 0.5)));
462 }
463
464 #[test]
465 fn test_distance_sq_to_point() {
466 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
467 assert!((a.distance_sq_to_point(Vec3A::splat(0.5))).abs() < EPS); assert!((a.distance_sq_to_point(Vec3A::new(2.0, 0.5, 0.5)) - 1.0).abs() < EPS);
469 }
470
471 #[test]
474 fn test_intersects_overlapping() {
475 let a = Aabb::new(Vec3::ZERO, Vec3::splat(2.0));
476 let b = Aabb::new(Vec3::ONE, Vec3::splat(3.0));
477 assert!(a.intersects(b));
478 assert!(b.intersects(a));
479 }
480
481 #[test]
482 fn test_intersects_disjoint() {
483 let a = Aabb::new(Vec3::ZERO, Vec3::splat(1.0));
484 let b = Aabb::new(Vec3::splat(2.0), Vec3::splat(3.0));
485 assert!(!a.intersects(b));
486 }
487
488 #[test]
489 fn test_intersects_edge_touching() {
490 let a = Aabb::new(Vec3::ZERO, Vec3::splat(1.0));
491 let b = Aabb::new(Vec3::new(1.0, 0.0, 0.0), Vec3::new(2.0, 1.0, 1.0));
492 assert!(a.intersects(b)); assert!(!a.intersects_exclusive(b)); }
495
496 #[test]
497 fn test_intersects_empty() {
498 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
499 let e = Aabb::empty();
500 assert!(!a.intersects(e));
501 assert!(!e.intersects(a));
502 }
503
504 #[test]
505 fn test_intersection_volume() {
506 let a = Aabb::new(Vec3::ZERO, Vec3::splat(2.0));
507 let b = Aabb::new(Vec3::ONE, Vec3::splat(3.0));
508 let i = a.intersection(b).expect("should intersect");
509 assert!(approx_eq(i.min, Vec3A::ONE));
510 assert!(approx_eq(i.max, Vec3A::splat(2.0)));
511 }
512
513 #[test]
514 fn test_intersection_none() {
515 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
516 let b = Aabb::new(Vec3::splat(2.0), Vec3::splat(3.0));
517 assert!(a.intersection(b).is_none());
518 }
519
520 #[test]
523 fn test_extend() {
524 let mut a = Aabb::new(Vec3::ZERO, Vec3::splat(2.0));
525 a.extend(Vec3A::new(3.0, -1.0, 1.0));
526 assert!(approx_eq(a.min, Vec3A::new(0.0, -1.0, 0.0)));
527 assert!(approx_eq(a.max, Vec3A::new(3.0, 2.0, 2.0)));
528 }
529
530 #[test]
531 fn test_extend_empty() {
532 let mut a = Aabb::empty();
533 let pt = Vec3A::new(-5.0, 10.0, 3.0);
534 a.extend(pt);
535 assert!(approx_eq(a.min, pt));
536 assert!(approx_eq(a.max, pt));
537
538 let pt2 = Vec3A::new(5.0, -10.0, 6.0);
539 a.extend(pt2);
540 assert!(approx_eq(a.min, Vec3A::new(-5.0, -10.0, 3.0)));
541 assert!(approx_eq(a.max, Vec3A::new(5.0, 10.0, 6.0)));
542 }
543
544 #[test]
545 fn test_merge() {
546 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
547 let b = Aabb::new(Vec3::splat(-1.0), Vec3::splat(0.5));
548 let m = a.merge(b);
549 assert!(approx_eq(m.min, Vec3A::splat(-1.0)));
550 assert!(approx_eq(m.max, Vec3A::ONE));
551 }
552
553 #[test]
554 fn test_expand() {
555 let a = Aabb::new(Vec3::ZERO, Vec3::splat(2.0));
556 let expanded = a.expand(1.0);
557 assert!(approx_eq(expanded.min, Vec3A::splat(-1.0)));
558 assert!(approx_eq(expanded.max, Vec3A::splat(3.0)));
559
560 let shrunk = a.expand(-0.5);
561 assert!(approx_eq(shrunk.min, Vec3A::splat(0.5)));
562 assert!(approx_eq(shrunk.max, Vec3A::splat(1.5)));
563
564 let over = a.expand(-10.0);
566 assert!(over.is_empty());
567 }
568
569 #[test]
572 fn test_transform_identity() {
573 let a = Aabb::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(4.0, 5.0, 6.0));
574 let result = a.transform(&Mat4::IDENTITY);
575 assert!(approx_eq(result.min, a.min));
576 assert!(approx_eq(result.max, a.max));
577 }
578
579 #[test]
580 fn test_transform_translation() {
581 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
582 let t = Mat4::from_translation(glam::Vec3::new(1.0, 2.0, 3.0));
583 let result = a.transform(&t);
584 assert!(approx_eq(result.min, Vec3A::new(1.0, 2.0, 3.0)));
585 assert!(approx_eq(result.max, Vec3A::new(2.0, 3.0, 4.0)));
586 }
587
588 #[test]
589 fn test_transform_uniform_scale() {
590 let a = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::ONE);
591 let s = Mat4::from_scale(glam::Vec3::splat(2.0));
592 let result = a.transform(&s);
593 assert!(approx_eq(result.min, Vec3A::splat(-2.0)));
594 assert!(approx_eq(result.max, Vec3A::splat(2.0)));
595 }
596
597 #[test]
598 fn test_transform_rotation_90_deg() {
599 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
601 let r = Mat4::from_rotation_z(std::f32::consts::FRAC_PI_2);
602 let result = a.transform(&r);
603 assert!(result.is_valid());
605 assert!((result.surface_area() - a.surface_area()).abs() < 1e-4);
606 }
607
608 #[test]
609 fn test_transform_empty() {
610 let e = Aabb::empty();
611 let result = e.transform(&Mat4::IDENTITY);
612 assert!(result.is_empty());
613 }
614
615 #[test]
618 fn test_corners_count_and_bounds() {
619 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
620 let corners = a.corners();
621 assert_eq!(corners.len(), 8);
622 for c in corners {
623 assert!(a.contains_point(c));
624 }
625 }
626
627 #[test]
630 fn test_default_is_empty() {
631 let a = Aabb::default();
632 assert!(a.is_empty());
633 }
634
635 #[test]
636 fn test_display() {
637 let a = Aabb::new(Vec3::ZERO, Vec3::ONE);
638 let s = format!("{}", a);
639 assert!(s.contains("min") && s.contains("max"));
640 }
641}