1mod extrusion;
2mod primitive_impls;
3
4use glam::Mat3;
5
6use super::{BoundingVolume, IntersectsVolume};
7use crate::{Quat, Vec3, Vec3A};
8
9#[cfg(feature = "bevy_reflect")]
10use bevy_reflect::Reflect;
11pub use extrusion::BoundedExtrusion;
12
13#[inline(always)]
15fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3A {
16 let (acc, len) = points.fold((Vec3A::ZERO, 0), |(acc, len), point| {
17 (acc + point.into(), len + 1)
18 });
19
20 assert!(
21 len > 0,
22 "cannot compute the center of an empty set of points"
23 );
24 acc / len as f32
25}
26
27pub trait Bounded3d {
29 fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d;
31 fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere;
33}
34
35#[derive(Clone, Copy, Debug)]
37#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
38pub struct Aabb3d {
39 pub min: Vec3A,
41 pub max: Vec3A,
43}
44
45impl Aabb3d {
46 #[inline(always)]
48 pub fn new(center: impl Into<Vec3A>, half_size: impl Into<Vec3A>) -> Self {
49 let (center, half_size) = (center.into(), half_size.into());
50 debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0 && half_size.z >= 0.0);
51 Self {
52 min: center - half_size,
53 max: center + half_size,
54 }
55 }
56
57 #[inline(always)]
64 pub fn from_point_cloud(
65 translation: impl Into<Vec3A>,
66 rotation: Quat,
67 points: impl Iterator<Item = impl Into<Vec3A>>,
68 ) -> Aabb3d {
69 let mut iter = points.map(|point| rotation * point.into());
71
72 let first = iter
73 .next()
74 .expect("point cloud must contain at least one point for Aabb3d construction");
75
76 let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
77 (point.min(prev_min), point.max(prev_max))
78 });
79
80 let translation = translation.into();
81 Aabb3d {
82 min: min + translation,
83 max: max + translation,
84 }
85 }
86
87 #[inline(always)]
89 pub fn bounding_sphere(&self) -> BoundingSphere {
90 let radius = self.min.distance(self.max) / 2.0;
91 BoundingSphere::new(self.center(), radius)
92 }
93
94 #[inline(always)]
99 pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
100 point.into().clamp(self.min, self.max)
102 }
103}
104
105impl BoundingVolume for Aabb3d {
106 type Translation = Vec3A;
107 type Rotation = Quat;
108 type HalfSize = Vec3A;
109
110 #[inline(always)]
111 fn center(&self) -> Self::Translation {
112 (self.min + self.max) / 2.
113 }
114
115 #[inline(always)]
116 fn half_size(&self) -> Self::HalfSize {
117 (self.max - self.min) / 2.
118 }
119
120 #[inline(always)]
121 fn visible_area(&self) -> f32 {
122 let b = self.max - self.min;
123 b.x * (b.y + b.z) + b.y * b.z
124 }
125
126 #[inline(always)]
127 fn contains(&self, other: &Self) -> bool {
128 other.min.cmpge(self.min).all() && other.max.cmple(self.max).all()
129 }
130
131 #[inline(always)]
132 fn merge(&self, other: &Self) -> Self {
133 Self {
134 min: self.min.min(other.min),
135 max: self.max.max(other.max),
136 }
137 }
138
139 #[inline(always)]
140 fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
141 let amount = amount.into();
142 let b = Self {
143 min: self.min - amount,
144 max: self.max + amount,
145 };
146 debug_assert!(b.min.cmple(b.max).all());
147 b
148 }
149
150 #[inline(always)]
151 fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
152 let amount = amount.into();
153 let b = Self {
154 min: self.min + amount,
155 max: self.max - amount,
156 };
157 debug_assert!(b.min.cmple(b.max).all());
158 b
159 }
160
161 #[inline(always)]
162 fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
163 let scale = scale.into();
164 let b = Self {
165 min: self.center() - (self.half_size() * scale),
166 max: self.center() + (self.half_size() * scale),
167 };
168 debug_assert!(b.min.cmple(b.max).all());
169 b
170 }
171
172 #[inline(always)]
180 fn transformed_by(
181 mut self,
182 translation: impl Into<Self::Translation>,
183 rotation: impl Into<Self::Rotation>,
184 ) -> Self {
185 self.transform_by(translation, rotation);
186 self
187 }
188
189 #[inline(always)]
197 fn transform_by(
198 &mut self,
199 translation: impl Into<Self::Translation>,
200 rotation: impl Into<Self::Rotation>,
201 ) {
202 self.rotate_by(rotation);
203 self.translate_by(translation);
204 }
205
206 #[inline(always)]
207 fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
208 let translation = translation.into();
209 self.min += translation;
210 self.max += translation;
211 }
212
213 #[inline(always)]
221 fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
222 self.rotate_by(rotation);
223 self
224 }
225
226 #[inline(always)]
234 fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
235 let rot_mat = Mat3::from_quat(rotation.into());
236 let abs_rot_mat = Mat3::from_cols(
237 rot_mat.x_axis.abs(),
238 rot_mat.y_axis.abs(),
239 rot_mat.z_axis.abs(),
240 );
241 let half_size = abs_rot_mat * self.half_size();
242 *self = Self::new(rot_mat * self.center(), half_size);
243 }
244}
245
246impl IntersectsVolume<Self> for Aabb3d {
247 #[inline(always)]
248 fn intersects(&self, other: &Self) -> bool {
249 self.min.cmple(other.max).all() && self.max.cmpge(other.min).all()
250 }
251}
252
253impl IntersectsVolume<BoundingSphere> for Aabb3d {
254 #[inline(always)]
255 fn intersects(&self, sphere: &BoundingSphere) -> bool {
256 let closest_point = self.closest_point(sphere.center);
257 let distance_squared = sphere.center.distance_squared(closest_point);
258 let radius_squared = sphere.radius().powi(2);
259 distance_squared <= radius_squared
260 }
261}
262
263#[cfg(test)]
264mod aabb3d_tests {
265 use super::Aabb3d;
266 use crate::{
267 bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
268 Quat, Vec3, Vec3A,
269 };
270
271 #[test]
272 fn center() {
273 let aabb = Aabb3d {
274 min: Vec3A::new(-0.5, -1., -0.5),
275 max: Vec3A::new(1., 1., 2.),
276 };
277 assert!((aabb.center() - Vec3A::new(0.25, 0., 0.75)).length() < f32::EPSILON);
278 let aabb = Aabb3d {
279 min: Vec3A::new(5., 5., -10.),
280 max: Vec3A::new(10., 10., -5.),
281 };
282 assert!((aabb.center() - Vec3A::new(7.5, 7.5, -7.5)).length() < f32::EPSILON);
283 }
284
285 #[test]
286 fn half_size() {
287 let aabb = Aabb3d {
288 min: Vec3A::new(-0.5, -1., -0.5),
289 max: Vec3A::new(1., 1., 2.),
290 };
291 assert!((aabb.half_size() - Vec3A::new(0.75, 1., 1.25)).length() < f32::EPSILON);
292 }
293
294 #[test]
295 fn area() {
296 let aabb = Aabb3d {
297 min: Vec3A::new(-1., -1., -1.),
298 max: Vec3A::new(1., 1., 1.),
299 };
300 assert!((aabb.visible_area() - 12.).abs() < f32::EPSILON);
301 let aabb = Aabb3d {
302 min: Vec3A::new(0., 0., 0.),
303 max: Vec3A::new(1., 0.5, 0.25),
304 };
305 assert!((aabb.visible_area() - 0.875).abs() < f32::EPSILON);
306 }
307
308 #[test]
309 fn contains() {
310 let a = Aabb3d {
311 min: Vec3A::new(-1., -1., -1.),
312 max: Vec3A::new(1., 1., 1.),
313 };
314 let b = Aabb3d {
315 min: Vec3A::new(-2., -1., -1.),
316 max: Vec3A::new(1., 1., 1.),
317 };
318 assert!(!a.contains(&b));
319 let b = Aabb3d {
320 min: Vec3A::new(-0.25, -0.8, -0.9),
321 max: Vec3A::new(1., 1., 0.9),
322 };
323 assert!(a.contains(&b));
324 }
325
326 #[test]
327 fn merge() {
328 let a = Aabb3d {
329 min: Vec3A::new(-1., -1., -1.),
330 max: Vec3A::new(1., 0.5, 1.),
331 };
332 let b = Aabb3d {
333 min: Vec3A::new(-2., -0.5, -0.),
334 max: Vec3A::new(0.75, 1., 2.),
335 };
336 let merged = a.merge(&b);
337 assert!((merged.min - Vec3A::new(-2., -1., -1.)).length() < f32::EPSILON);
338 assert!((merged.max - Vec3A::new(1., 1., 2.)).length() < f32::EPSILON);
339 assert!(merged.contains(&a));
340 assert!(merged.contains(&b));
341 assert!(!a.contains(&merged));
342 assert!(!b.contains(&merged));
343 }
344
345 #[test]
346 fn grow() {
347 let a = Aabb3d {
348 min: Vec3A::new(-1., -1., -1.),
349 max: Vec3A::new(1., 1., 1.),
350 };
351 let padded = a.grow(Vec3A::ONE);
352 assert!((padded.min - Vec3A::new(-2., -2., -2.)).length() < f32::EPSILON);
353 assert!((padded.max - Vec3A::new(2., 2., 2.)).length() < f32::EPSILON);
354 assert!(padded.contains(&a));
355 assert!(!a.contains(&padded));
356 }
357
358 #[test]
359 fn shrink() {
360 let a = Aabb3d {
361 min: Vec3A::new(-2., -2., -2.),
362 max: Vec3A::new(2., 2., 2.),
363 };
364 let shrunk = a.shrink(Vec3A::ONE);
365 assert!((shrunk.min - Vec3A::new(-1., -1., -1.)).length() < f32::EPSILON);
366 assert!((shrunk.max - Vec3A::new(1., 1., 1.)).length() < f32::EPSILON);
367 assert!(a.contains(&shrunk));
368 assert!(!shrunk.contains(&a));
369 }
370
371 #[test]
372 fn scale_around_center() {
373 let a = Aabb3d {
374 min: Vec3A::NEG_ONE,
375 max: Vec3A::ONE,
376 };
377 let scaled = a.scale_around_center(Vec3A::splat(2.));
378 assert!((scaled.min - Vec3A::splat(-2.)).length() < f32::EPSILON);
379 assert!((scaled.max - Vec3A::splat(2.)).length() < f32::EPSILON);
380 assert!(!a.contains(&scaled));
381 assert!(scaled.contains(&a));
382 }
383
384 #[test]
385 fn transform() {
386 let a = Aabb3d {
387 min: Vec3A::new(-2.0, -2.0, -2.0),
388 max: Vec3A::new(2.0, 2.0, 2.0),
389 };
390 let transformed = a.transformed_by(
391 Vec3A::new(2.0, -2.0, 4.0),
392 Quat::from_rotation_z(std::f32::consts::FRAC_PI_4),
393 );
394 let half_length = 2_f32.hypot(2.0);
395 assert_eq!(
396 transformed.min,
397 Vec3A::new(2.0 - half_length, -half_length - 2.0, 2.0)
398 );
399 assert_eq!(
400 transformed.max,
401 Vec3A::new(2.0 + half_length, half_length - 2.0, 6.0)
402 );
403 }
404
405 #[test]
406 fn closest_point() {
407 let aabb = Aabb3d {
408 min: Vec3A::NEG_ONE,
409 max: Vec3A::ONE,
410 };
411 assert_eq!(aabb.closest_point(Vec3A::X * 10.0), Vec3A::X);
412 assert_eq!(aabb.closest_point(Vec3A::NEG_ONE * 10.0), Vec3A::NEG_ONE);
413 assert_eq!(
414 aabb.closest_point(Vec3A::new(0.25, 0.1, 0.3)),
415 Vec3A::new(0.25, 0.1, 0.3)
416 );
417 }
418
419 #[test]
420 fn intersect_aabb() {
421 let aabb = Aabb3d {
422 min: Vec3A::NEG_ONE,
423 max: Vec3A::ONE,
424 };
425 assert!(aabb.intersects(&aabb));
426 assert!(aabb.intersects(&Aabb3d {
427 min: Vec3A::splat(0.5),
428 max: Vec3A::splat(2.0),
429 }));
430 assert!(aabb.intersects(&Aabb3d {
431 min: Vec3A::splat(-2.0),
432 max: Vec3A::splat(-0.5),
433 }));
434 assert!(!aabb.intersects(&Aabb3d {
435 min: Vec3A::new(1.1, 0.0, 0.0),
436 max: Vec3A::new(2.0, 0.5, 0.25),
437 }));
438 }
439
440 #[test]
441 fn intersect_bounding_sphere() {
442 let aabb = Aabb3d {
443 min: Vec3A::NEG_ONE,
444 max: Vec3A::ONE,
445 };
446 assert!(aabb.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
447 assert!(aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.5, 1.0)));
448 assert!(aabb.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.5, 1.0)));
449 assert!(!aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.75, 1.0)));
450 }
451}
452
453use crate::primitives::Sphere;
454
455#[derive(Clone, Copy, Debug)]
457#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
458pub struct BoundingSphere {
459 pub center: Vec3A,
461 pub sphere: Sphere,
463}
464
465impl BoundingSphere {
466 pub fn new(center: impl Into<Vec3A>, radius: f32) -> Self {
468 debug_assert!(radius >= 0.);
469 Self {
470 center: center.into(),
471 sphere: Sphere { radius },
472 }
473 }
474
475 #[inline(always)]
480 pub fn from_point_cloud(
481 translation: impl Into<Vec3A>,
482 rotation: Quat,
483 points: &[impl Copy + Into<Vec3A>],
484 ) -> BoundingSphere {
485 let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));
486 let mut radius_squared: f32 = 0.0;
487
488 for point in points {
489 let distance_squared = Into::<Vec3A>::into(*point).distance_squared(center);
491 if distance_squared > radius_squared {
492 radius_squared = distance_squared;
493 }
494 }
495
496 BoundingSphere::new(
497 rotation * center + translation.into(),
498 radius_squared.sqrt(),
499 )
500 }
501
502 #[inline(always)]
504 pub fn radius(&self) -> f32 {
505 self.sphere.radius
506 }
507
508 #[inline(always)]
510 pub fn aabb_3d(&self) -> Aabb3d {
511 Aabb3d {
512 min: self.center - self.radius(),
513 max: self.center + self.radius(),
514 }
515 }
516
517 #[inline(always)]
522 pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
523 let point = point.into();
524 let radius = self.radius();
525 let distance_squared = (point - self.center).length_squared();
526
527 if distance_squared <= radius.powi(2) {
528 point
530 } else {
531 let dir_to_point = point / distance_squared.sqrt();
534 self.center + radius * dir_to_point
535 }
536 }
537}
538
539impl BoundingVolume for BoundingSphere {
540 type Translation = Vec3A;
541 type Rotation = Quat;
542 type HalfSize = f32;
543
544 #[inline(always)]
545 fn center(&self) -> Self::Translation {
546 self.center
547 }
548
549 #[inline(always)]
550 fn half_size(&self) -> Self::HalfSize {
551 self.radius()
552 }
553
554 #[inline(always)]
555 fn visible_area(&self) -> f32 {
556 2. * std::f32::consts::PI * self.radius() * self.radius()
557 }
558
559 #[inline(always)]
560 fn contains(&self, other: &Self) -> bool {
561 let diff = self.radius() - other.radius();
562 self.center.distance_squared(other.center) <= diff.powi(2).copysign(diff)
563 }
564
565 #[inline(always)]
566 fn merge(&self, other: &Self) -> Self {
567 let diff = other.center - self.center;
568 let length = diff.length();
569 if self.radius() >= length + other.radius() {
570 return *self;
571 }
572 if other.radius() >= length + self.radius() {
573 return *other;
574 }
575 let dir = diff / length;
576 Self::new(
577 (self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
578 (length + self.radius() + other.radius()) / 2.,
579 )
580 }
581
582 #[inline(always)]
583 fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
584 let amount = amount.into();
585 debug_assert!(amount >= 0.);
586 Self {
587 center: self.center,
588 sphere: Sphere {
589 radius: self.radius() + amount,
590 },
591 }
592 }
593
594 #[inline(always)]
595 fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
596 let amount = amount.into();
597 debug_assert!(amount >= 0.);
598 debug_assert!(self.radius() >= amount);
599 Self {
600 center: self.center,
601 sphere: Sphere {
602 radius: self.radius() - amount,
603 },
604 }
605 }
606
607 #[inline(always)]
608 fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
609 let scale = scale.into();
610 debug_assert!(scale >= 0.);
611 Self::new(self.center, self.radius() * scale)
612 }
613
614 #[inline(always)]
615 fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
616 self.center += translation.into();
617 }
618
619 #[inline(always)]
620 fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
621 let rotation: Quat = rotation.into();
622 self.center = rotation * self.center;
623 }
624}
625
626impl IntersectsVolume<Self> for BoundingSphere {
627 #[inline(always)]
628 fn intersects(&self, other: &Self) -> bool {
629 let center_distance_squared = self.center.distance_squared(other.center);
630 let radius_sum_squared = (self.radius() + other.radius()).powi(2);
631 center_distance_squared <= radius_sum_squared
632 }
633}
634
635impl IntersectsVolume<Aabb3d> for BoundingSphere {
636 #[inline(always)]
637 fn intersects(&self, aabb: &Aabb3d) -> bool {
638 aabb.intersects(self)
639 }
640}
641
642#[cfg(test)]
643mod bounding_sphere_tests {
644 use approx::assert_relative_eq;
645
646 use super::BoundingSphere;
647 use crate::{
648 bounding::{BoundingVolume, IntersectsVolume},
649 Quat, Vec3, Vec3A,
650 };
651
652 #[test]
653 fn area() {
654 let sphere = BoundingSphere::new(Vec3::ONE, 5.);
655 assert!((sphere.visible_area() - 157.0796).abs() < 0.001);
657 }
658
659 #[test]
660 fn contains() {
661 let a = BoundingSphere::new(Vec3::ONE, 5.);
662 let b = BoundingSphere::new(Vec3::new(5.5, 1., 1.), 1.);
663 assert!(!a.contains(&b));
664 let b = BoundingSphere::new(Vec3::new(1., -3.5, 1.), 0.5);
665 assert!(a.contains(&b));
666 }
667
668 #[test]
669 fn contains_identical() {
670 let a = BoundingSphere::new(Vec3::ONE, 5.);
671 assert!(a.contains(&a));
672 }
673
674 #[test]
675 fn merge() {
676 let a = BoundingSphere::new(Vec3::ONE, 5.);
679 let b = BoundingSphere::new(Vec3::new(1., 1., -4.), 1.);
680 let merged = a.merge(&b);
681 assert!((merged.center - Vec3A::new(1., 1., 0.5)).length() < f32::EPSILON);
682 assert!((merged.radius() - 5.5).abs() < f32::EPSILON);
683 assert!(merged.contains(&a));
684 assert!(merged.contains(&b));
685 assert!(!a.contains(&merged));
686 assert!(!b.contains(&merged));
687
688 let b = BoundingSphere::new(Vec3::ZERO, 3.);
690 assert!(a.contains(&b));
691 let merged = a.merge(&b);
692 assert_eq!(merged.center, a.center);
693 assert_eq!(merged.radius(), a.radius());
694
695 let b = BoundingSphere::new(Vec3::ONE, 6.);
697 let merged = a.merge(&b);
698 assert_eq!(merged.center, a.center);
699 assert_eq!(merged.radius(), b.radius());
700 }
701
702 #[test]
703 fn merge_identical() {
704 let a = BoundingSphere::new(Vec3::ONE, 5.);
705 let merged = a.merge(&a);
706 assert_eq!(merged.center, a.center);
707 assert_eq!(merged.radius(), a.radius());
708 }
709
710 #[test]
711 fn grow() {
712 let a = BoundingSphere::new(Vec3::ONE, 5.);
713 let padded = a.grow(1.25);
714 assert!((padded.radius() - 6.25).abs() < f32::EPSILON);
715 assert!(padded.contains(&a));
716 assert!(!a.contains(&padded));
717 }
718
719 #[test]
720 fn shrink() {
721 let a = BoundingSphere::new(Vec3::ONE, 5.);
722 let shrunk = a.shrink(0.5);
723 assert!((shrunk.radius() - 4.5).abs() < f32::EPSILON);
724 assert!(a.contains(&shrunk));
725 assert!(!shrunk.contains(&a));
726 }
727
728 #[test]
729 fn scale_around_center() {
730 let a = BoundingSphere::new(Vec3::ONE, 5.);
731 let scaled = a.scale_around_center(2.);
732 assert!((scaled.radius() - 10.).abs() < f32::EPSILON);
733 assert!(!a.contains(&scaled));
734 assert!(scaled.contains(&a));
735 }
736
737 #[test]
738 fn transform() {
739 let a = BoundingSphere::new(Vec3::ONE, 5.0);
740 let transformed = a.transformed_by(
741 Vec3::new(2.0, -2.0, 4.0),
742 Quat::from_rotation_z(std::f32::consts::FRAC_PI_4),
743 );
744 assert_relative_eq!(
745 transformed.center,
746 Vec3A::new(2.0, std::f32::consts::SQRT_2 - 2.0, 5.0)
747 );
748 assert_eq!(transformed.radius(), 5.0);
749 }
750
751 #[test]
752 fn closest_point() {
753 let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
754 assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3A::X);
755 assert_eq!(
756 sphere.closest_point(Vec3::NEG_ONE * 10.0),
757 Vec3A::NEG_ONE.normalize()
758 );
759 assert_eq!(
760 sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
761 Vec3A::new(0.25, 0.1, 0.3)
762 );
763 }
764
765 #[test]
766 fn intersect_bounding_sphere() {
767 let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
768 assert!(sphere.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
769 assert!(sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.1, 1.0)));
770 assert!(sphere.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.1, 1.0)));
771 assert!(!sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.2, 1.0)));
772 }
773}