bevy_math/bounding/bounded3d/
mod.rs

1mod extrusion;
2mod primitive_impls;
3
4use glam::Mat3;
5
6use super::{BoundingVolume, IntersectsVolume};
7use crate::{
8    ops::{self, FloatPow},
9    Isometry3d, Quat, Vec3A,
10};
11
12#[cfg(feature = "bevy_reflect")]
13use bevy_reflect::Reflect;
14#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
15use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
16#[cfg(feature = "serialize")]
17use serde::{Deserialize, Serialize};
18
19pub use extrusion::BoundedExtrusion;
20
21/// Computes the geometric center of the given set of points.
22#[inline(always)]
23fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3A {
24    let (acc, len) = points.fold((Vec3A::ZERO, 0), |(acc, len), point| {
25        (acc + point.into(), len + 1)
26    });
27
28    assert!(
29        len > 0,
30        "cannot compute the center of an empty set of points"
31    );
32    acc / len as f32
33}
34
35/// A trait with methods that return 3D bounding volumes for a shape.
36pub trait Bounded3d {
37    /// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
38    fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d;
39    /// Get a bounding sphere for the shape translated and rotated by the given isometry.
40    fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere;
41}
42
43/// A 3D axis-aligned bounding box
44#[derive(Clone, Copy, Debug, PartialEq)]
45#[cfg_attr(
46    feature = "bevy_reflect",
47    derive(Reflect),
48    reflect(Debug, PartialEq, Clone)
49)]
50#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
51#[cfg_attr(
52    all(feature = "serialize", feature = "bevy_reflect"),
53    reflect(Serialize, Deserialize)
54)]
55pub struct Aabb3d {
56    /// The minimum point of the box
57    pub min: Vec3A,
58    /// The maximum point of the box
59    pub max: Vec3A,
60}
61
62impl Aabb3d {
63    /// Constructs an AABB from its center and half-size.
64    #[inline(always)]
65    pub fn new(center: impl Into<Vec3A>, half_size: impl Into<Vec3A>) -> Self {
66        let (center, half_size) = (center.into(), half_size.into());
67        debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0 && half_size.z >= 0.0);
68        Self {
69            min: center - half_size,
70            max: center + half_size,
71        }
72    }
73
74    /// Computes the smallest [`Aabb3d`] containing the given set of points,
75    /// transformed by the rotation and translation of the given isometry.
76    ///
77    /// # Panics
78    ///
79    /// Panics if the given set of points is empty.
80    #[inline(always)]
81    pub fn from_point_cloud(
82        isometry: impl Into<Isometry3d>,
83        points: impl Iterator<Item = impl Into<Vec3A>>,
84    ) -> Aabb3d {
85        let isometry = isometry.into();
86
87        // Transform all points by rotation
88        let mut iter = points.map(|point| isometry.rotation * point.into());
89
90        let first = iter
91            .next()
92            .expect("point cloud must contain at least one point for Aabb3d construction");
93
94        let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
95            (point.min(prev_min), point.max(prev_max))
96        });
97
98        Aabb3d {
99            min: min + isometry.translation,
100            max: max + isometry.translation,
101        }
102    }
103
104    /// Computes the smallest [`BoundingSphere`] containing this [`Aabb3d`].
105    #[inline(always)]
106    pub fn bounding_sphere(&self) -> BoundingSphere {
107        let radius = self.min.distance(self.max) / 2.0;
108        BoundingSphere::new(self.center(), radius)
109    }
110
111    /// Finds the point on the AABB that is closest to the given `point`.
112    ///
113    /// If the point is outside the AABB, the returned point will be on the surface of the AABB.
114    /// Otherwise, it will be inside the AABB and returned as is.
115    #[inline(always)]
116    pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
117        // Clamp point coordinates to the AABB
118        point.into().clamp(self.min, self.max)
119    }
120}
121
122impl BoundingVolume for Aabb3d {
123    type Translation = Vec3A;
124    type Rotation = Quat;
125    type HalfSize = Vec3A;
126
127    #[inline(always)]
128    fn center(&self) -> Self::Translation {
129        (self.min + self.max) / 2.
130    }
131
132    #[inline(always)]
133    fn half_size(&self) -> Self::HalfSize {
134        (self.max - self.min) / 2.
135    }
136
137    #[inline(always)]
138    fn visible_area(&self) -> f32 {
139        let b = self.max - self.min;
140        b.x * (b.y + b.z) + b.y * b.z
141    }
142
143    #[inline(always)]
144    fn contains(&self, other: &Self) -> bool {
145        other.min.cmpge(self.min).all() && other.max.cmple(self.max).all()
146    }
147
148    #[inline(always)]
149    fn merge(&self, other: &Self) -> Self {
150        Self {
151            min: self.min.min(other.min),
152            max: self.max.max(other.max),
153        }
154    }
155
156    #[inline(always)]
157    fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
158        let amount = amount.into();
159        let b = Self {
160            min: self.min - amount,
161            max: self.max + amount,
162        };
163        debug_assert!(b.min.cmple(b.max).all());
164        b
165    }
166
167    #[inline(always)]
168    fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
169        let amount = amount.into();
170        let b = Self {
171            min: self.min + amount,
172            max: self.max - amount,
173        };
174        debug_assert!(b.min.cmple(b.max).all());
175        b
176    }
177
178    #[inline(always)]
179    fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
180        let scale = scale.into();
181        let b = Self {
182            min: self.center() - (self.half_size() * scale),
183            max: self.center() + (self.half_size() * scale),
184        };
185        debug_assert!(b.min.cmple(b.max).all());
186        b
187    }
188
189    /// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
190    ///
191    /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
192    ///
193    /// Note that the result may not be as tightly fitting as the original, and repeated rotations
194    /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
195    /// and consider storing the original AABB and rotating that every time instead.
196    #[inline(always)]
197    fn transformed_by(
198        mut self,
199        translation: impl Into<Self::Translation>,
200        rotation: impl Into<Self::Rotation>,
201    ) -> Self {
202        self.transform_by(translation, rotation);
203        self
204    }
205
206    /// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
207    ///
208    /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
209    ///
210    /// Note that the result may not be as tightly fitting as the original, and repeated rotations
211    /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
212    /// and consider storing the original AABB and rotating that every time instead.
213    #[inline(always)]
214    fn transform_by(
215        &mut self,
216        translation: impl Into<Self::Translation>,
217        rotation: impl Into<Self::Rotation>,
218    ) {
219        self.rotate_by(rotation);
220        self.translate_by(translation);
221    }
222
223    #[inline(always)]
224    fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
225        let translation = translation.into();
226        self.min += translation;
227        self.max += translation;
228    }
229
230    /// Rotates the bounding volume around the origin by the given rotation.
231    ///
232    /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
233    ///
234    /// Note that the result may not be as tightly fitting as the original, and repeated rotations
235    /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
236    /// and consider storing the original AABB and rotating that every time instead.
237    #[inline(always)]
238    fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
239        self.rotate_by(rotation);
240        self
241    }
242
243    /// Rotates the bounding volume around the origin by the given rotation.
244    ///
245    /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
246    ///
247    /// Note that the result may not be as tightly fitting as the original, and repeated rotations
248    /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
249    /// and consider storing the original AABB and rotating that every time instead.
250    #[inline(always)]
251    fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
252        let rot_mat = Mat3::from_quat(rotation.into());
253        let half_size = rot_mat.abs() * self.half_size();
254        *self = Self::new(rot_mat * self.center(), half_size);
255    }
256}
257
258impl IntersectsVolume<Self> for Aabb3d {
259    #[inline(always)]
260    fn intersects(&self, other: &Self) -> bool {
261        self.min.cmple(other.max).all() && self.max.cmpge(other.min).all()
262    }
263}
264
265impl IntersectsVolume<BoundingSphere> for Aabb3d {
266    #[inline(always)]
267    fn intersects(&self, sphere: &BoundingSphere) -> bool {
268        let closest_point = self.closest_point(sphere.center);
269        let distance_squared = sphere.center.distance_squared(closest_point);
270        let radius_squared = sphere.radius().squared();
271        distance_squared <= radius_squared
272    }
273}
274
275#[cfg(test)]
276mod aabb3d_tests {
277    use approx::assert_relative_eq;
278
279    use super::Aabb3d;
280    use crate::{
281        bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
282        ops, Quat, Vec3, Vec3A,
283    };
284
285    #[test]
286    fn center() {
287        let aabb = Aabb3d {
288            min: Vec3A::new(-0.5, -1., -0.5),
289            max: Vec3A::new(1., 1., 2.),
290        };
291        assert!((aabb.center() - Vec3A::new(0.25, 0., 0.75)).length() < f32::EPSILON);
292        let aabb = Aabb3d {
293            min: Vec3A::new(5., 5., -10.),
294            max: Vec3A::new(10., 10., -5.),
295        };
296        assert!((aabb.center() - Vec3A::new(7.5, 7.5, -7.5)).length() < f32::EPSILON);
297    }
298
299    #[test]
300    fn half_size() {
301        let aabb = Aabb3d {
302            min: Vec3A::new(-0.5, -1., -0.5),
303            max: Vec3A::new(1., 1., 2.),
304        };
305        assert!((aabb.half_size() - Vec3A::new(0.75, 1., 1.25)).length() < f32::EPSILON);
306    }
307
308    #[test]
309    fn area() {
310        let aabb = Aabb3d {
311            min: Vec3A::new(-1., -1., -1.),
312            max: Vec3A::new(1., 1., 1.),
313        };
314        assert!(ops::abs(aabb.visible_area() - 12.) < f32::EPSILON);
315        let aabb = Aabb3d {
316            min: Vec3A::new(0., 0., 0.),
317            max: Vec3A::new(1., 0.5, 0.25),
318        };
319        assert!(ops::abs(aabb.visible_area() - 0.875) < f32::EPSILON);
320    }
321
322    #[test]
323    fn contains() {
324        let a = Aabb3d {
325            min: Vec3A::new(-1., -1., -1.),
326            max: Vec3A::new(1., 1., 1.),
327        };
328        let b = Aabb3d {
329            min: Vec3A::new(-2., -1., -1.),
330            max: Vec3A::new(1., 1., 1.),
331        };
332        assert!(!a.contains(&b));
333        let b = Aabb3d {
334            min: Vec3A::new(-0.25, -0.8, -0.9),
335            max: Vec3A::new(1., 1., 0.9),
336        };
337        assert!(a.contains(&b));
338    }
339
340    #[test]
341    fn merge() {
342        let a = Aabb3d {
343            min: Vec3A::new(-1., -1., -1.),
344            max: Vec3A::new(1., 0.5, 1.),
345        };
346        let b = Aabb3d {
347            min: Vec3A::new(-2., -0.5, -0.),
348            max: Vec3A::new(0.75, 1., 2.),
349        };
350        let merged = a.merge(&b);
351        assert!((merged.min - Vec3A::new(-2., -1., -1.)).length() < f32::EPSILON);
352        assert!((merged.max - Vec3A::new(1., 1., 2.)).length() < f32::EPSILON);
353        assert!(merged.contains(&a));
354        assert!(merged.contains(&b));
355        assert!(!a.contains(&merged));
356        assert!(!b.contains(&merged));
357    }
358
359    #[test]
360    fn grow() {
361        let a = Aabb3d {
362            min: Vec3A::new(-1., -1., -1.),
363            max: Vec3A::new(1., 1., 1.),
364        };
365        let padded = a.grow(Vec3A::ONE);
366        assert!((padded.min - Vec3A::new(-2., -2., -2.)).length() < f32::EPSILON);
367        assert!((padded.max - Vec3A::new(2., 2., 2.)).length() < f32::EPSILON);
368        assert!(padded.contains(&a));
369        assert!(!a.contains(&padded));
370    }
371
372    #[test]
373    fn shrink() {
374        let a = Aabb3d {
375            min: Vec3A::new(-2., -2., -2.),
376            max: Vec3A::new(2., 2., 2.),
377        };
378        let shrunk = a.shrink(Vec3A::ONE);
379        assert!((shrunk.min - Vec3A::new(-1., -1., -1.)).length() < f32::EPSILON);
380        assert!((shrunk.max - Vec3A::new(1., 1., 1.)).length() < f32::EPSILON);
381        assert!(a.contains(&shrunk));
382        assert!(!shrunk.contains(&a));
383    }
384
385    #[test]
386    fn scale_around_center() {
387        let a = Aabb3d {
388            min: Vec3A::NEG_ONE,
389            max: Vec3A::ONE,
390        };
391        let scaled = a.scale_around_center(Vec3A::splat(2.));
392        assert!((scaled.min - Vec3A::splat(-2.)).length() < f32::EPSILON);
393        assert!((scaled.max - Vec3A::splat(2.)).length() < f32::EPSILON);
394        assert!(!a.contains(&scaled));
395        assert!(scaled.contains(&a));
396    }
397
398    #[test]
399    fn rotate() {
400        use core::f32::consts::PI;
401        let a = Aabb3d {
402            min: Vec3A::new(-2.0, -2.0, -2.0),
403            max: Vec3A::new(2.0, 2.0, 2.0),
404        };
405        let rotation = Quat::from_euler(glam::EulerRot::XYZ, PI, PI, 0.0);
406        let rotated = a.rotated_by(rotation);
407        assert_relative_eq!(rotated.min, a.min);
408        assert_relative_eq!(rotated.max, a.max);
409    }
410
411    #[test]
412    fn transform() {
413        let a = Aabb3d {
414            min: Vec3A::new(-2.0, -2.0, -2.0),
415            max: Vec3A::new(2.0, 2.0, 2.0),
416        };
417        let transformed = a.transformed_by(
418            Vec3A::new(2.0, -2.0, 4.0),
419            Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
420        );
421        let half_length = ops::hypot(2.0, 2.0);
422        assert_eq!(
423            transformed.min,
424            Vec3A::new(2.0 - half_length, -half_length - 2.0, 2.0)
425        );
426        assert_eq!(
427            transformed.max,
428            Vec3A::new(2.0 + half_length, half_length - 2.0, 6.0)
429        );
430    }
431
432    #[test]
433    fn closest_point() {
434        let aabb = Aabb3d {
435            min: Vec3A::NEG_ONE,
436            max: Vec3A::ONE,
437        };
438        assert_eq!(aabb.closest_point(Vec3A::X * 10.0), Vec3A::X);
439        assert_eq!(aabb.closest_point(Vec3A::NEG_ONE * 10.0), Vec3A::NEG_ONE);
440        assert_eq!(
441            aabb.closest_point(Vec3A::new(0.25, 0.1, 0.3)),
442            Vec3A::new(0.25, 0.1, 0.3)
443        );
444    }
445
446    #[test]
447    fn intersect_aabb() {
448        let aabb = Aabb3d {
449            min: Vec3A::NEG_ONE,
450            max: Vec3A::ONE,
451        };
452        assert!(aabb.intersects(&aabb));
453        assert!(aabb.intersects(&Aabb3d {
454            min: Vec3A::splat(0.5),
455            max: Vec3A::splat(2.0),
456        }));
457        assert!(aabb.intersects(&Aabb3d {
458            min: Vec3A::splat(-2.0),
459            max: Vec3A::splat(-0.5),
460        }));
461        assert!(!aabb.intersects(&Aabb3d {
462            min: Vec3A::new(1.1, 0.0, 0.0),
463            max: Vec3A::new(2.0, 0.5, 0.25),
464        }));
465    }
466
467    #[test]
468    fn intersect_bounding_sphere() {
469        let aabb = Aabb3d {
470            min: Vec3A::NEG_ONE,
471            max: Vec3A::ONE,
472        };
473        assert!(aabb.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
474        assert!(aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.5, 1.0)));
475        assert!(aabb.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.5, 1.0)));
476        assert!(!aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.75, 1.0)));
477    }
478}
479
480use crate::primitives::Sphere;
481
482/// A bounding sphere
483#[derive(Clone, Copy, Debug, PartialEq)]
484#[cfg_attr(
485    feature = "bevy_reflect",
486    derive(Reflect),
487    reflect(Debug, PartialEq, Clone)
488)]
489#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
490#[cfg_attr(
491    all(feature = "serialize", feature = "bevy_reflect"),
492    reflect(Serialize, Deserialize)
493)]
494pub struct BoundingSphere {
495    /// The center of the bounding sphere
496    pub center: Vec3A,
497    /// The sphere
498    pub sphere: Sphere,
499}
500
501impl BoundingSphere {
502    /// Constructs a bounding sphere from its center and radius.
503    pub fn new(center: impl Into<Vec3A>, radius: f32) -> Self {
504        debug_assert!(radius >= 0.);
505        Self {
506            center: center.into(),
507            sphere: Sphere { radius },
508        }
509    }
510
511    /// Computes a [`BoundingSphere`] containing the given set of points,
512    /// transformed by the rotation and translation of the given isometry.
513    ///
514    /// The bounding sphere is not guaranteed to be the smallest possible.
515    #[inline(always)]
516    pub fn from_point_cloud(
517        isometry: impl Into<Isometry3d>,
518        points: &[impl Copy + Into<Vec3A>],
519    ) -> BoundingSphere {
520        let isometry = isometry.into();
521
522        let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));
523        let mut radius_squared: f32 = 0.0;
524
525        for point in points {
526            // Get squared version to avoid unnecessary sqrt calls
527            let distance_squared = Into::<Vec3A>::into(*point).distance_squared(center);
528            if distance_squared > radius_squared {
529                radius_squared = distance_squared;
530            }
531        }
532
533        BoundingSphere::new(isometry * center, ops::sqrt(radius_squared))
534    }
535
536    /// Get the radius of the bounding sphere
537    #[inline(always)]
538    pub fn radius(&self) -> f32 {
539        self.sphere.radius
540    }
541
542    /// Computes the smallest [`Aabb3d`] containing this [`BoundingSphere`].
543    #[inline(always)]
544    pub fn aabb_3d(&self) -> Aabb3d {
545        Aabb3d {
546            min: self.center - self.radius(),
547            max: self.center + self.radius(),
548        }
549    }
550
551    /// Finds the point on the bounding sphere that is closest to the given `point`.
552    ///
553    /// If the point is outside the sphere, the returned point will be on the surface of the sphere.
554    /// Otherwise, it will be inside the sphere and returned as is.
555    #[inline(always)]
556    pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
557        let point = point.into();
558        let radius = self.radius();
559        let distance_squared = (point - self.center).length_squared();
560
561        if distance_squared <= radius.squared() {
562            // The point is inside the sphere.
563            point
564        } else {
565            // The point is outside the sphere.
566            // Find the closest point on the surface of the sphere.
567            let dir_to_point = point / ops::sqrt(distance_squared);
568            self.center + radius * dir_to_point
569        }
570    }
571}
572
573impl BoundingVolume for BoundingSphere {
574    type Translation = Vec3A;
575    type Rotation = Quat;
576    type HalfSize = f32;
577
578    #[inline(always)]
579    fn center(&self) -> Self::Translation {
580        self.center
581    }
582
583    #[inline(always)]
584    fn half_size(&self) -> Self::HalfSize {
585        self.radius()
586    }
587
588    #[inline(always)]
589    fn visible_area(&self) -> f32 {
590        2. * core::f32::consts::PI * self.radius() * self.radius()
591    }
592
593    #[inline(always)]
594    fn contains(&self, other: &Self) -> bool {
595        let diff = self.radius() - other.radius();
596        self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)
597    }
598
599    #[inline(always)]
600    fn merge(&self, other: &Self) -> Self {
601        let diff = other.center - self.center;
602        let length = diff.length();
603        if self.radius() >= length + other.radius() {
604            return *self;
605        }
606        if other.radius() >= length + self.radius() {
607            return *other;
608        }
609        let dir = diff / length;
610        Self::new(
611            (self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
612            (length + self.radius() + other.radius()) / 2.,
613        )
614    }
615
616    #[inline(always)]
617    fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
618        let amount = amount.into();
619        debug_assert!(amount >= 0.);
620        Self {
621            center: self.center,
622            sphere: Sphere {
623                radius: self.radius() + amount,
624            },
625        }
626    }
627
628    #[inline(always)]
629    fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
630        let amount = amount.into();
631        debug_assert!(amount >= 0.);
632        debug_assert!(self.radius() >= amount);
633        Self {
634            center: self.center,
635            sphere: Sphere {
636                radius: self.radius() - amount,
637            },
638        }
639    }
640
641    #[inline(always)]
642    fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
643        let scale = scale.into();
644        debug_assert!(scale >= 0.);
645        Self::new(self.center, self.radius() * scale)
646    }
647
648    #[inline(always)]
649    fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
650        self.center += translation.into();
651    }
652
653    #[inline(always)]
654    fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
655        let rotation: Quat = rotation.into();
656        self.center = rotation * self.center;
657    }
658}
659
660impl IntersectsVolume<Self> for BoundingSphere {
661    #[inline(always)]
662    fn intersects(&self, other: &Self) -> bool {
663        let center_distance_squared = self.center.distance_squared(other.center);
664        let radius_sum_squared = (self.radius() + other.radius()).squared();
665        center_distance_squared <= radius_sum_squared
666    }
667}
668
669impl IntersectsVolume<Aabb3d> for BoundingSphere {
670    #[inline(always)]
671    fn intersects(&self, aabb: &Aabb3d) -> bool {
672        aabb.intersects(self)
673    }
674}
675
676#[cfg(test)]
677mod bounding_sphere_tests {
678    use approx::assert_relative_eq;
679
680    use super::BoundingSphere;
681    use crate::{
682        bounding::{BoundingVolume, IntersectsVolume},
683        ops, Quat, Vec3, Vec3A,
684    };
685
686    #[test]
687    fn area() {
688        let sphere = BoundingSphere::new(Vec3::ONE, 5.);
689        // Since this number is messy we check it with a higher threshold
690        assert!(ops::abs(sphere.visible_area() - 157.0796) < 0.001);
691    }
692
693    #[test]
694    fn contains() {
695        let a = BoundingSphere::new(Vec3::ONE, 5.);
696        let b = BoundingSphere::new(Vec3::new(5.5, 1., 1.), 1.);
697        assert!(!a.contains(&b));
698        let b = BoundingSphere::new(Vec3::new(1., -3.5, 1.), 0.5);
699        assert!(a.contains(&b));
700    }
701
702    #[test]
703    fn contains_identical() {
704        let a = BoundingSphere::new(Vec3::ONE, 5.);
705        assert!(a.contains(&a));
706    }
707
708    #[test]
709    fn merge() {
710        // When merging two circles that don't contain each other, we find a center position that
711        // contains both
712        let a = BoundingSphere::new(Vec3::ONE, 5.);
713        let b = BoundingSphere::new(Vec3::new(1., 1., -4.), 1.);
714        let merged = a.merge(&b);
715        assert!((merged.center - Vec3A::new(1., 1., 0.5)).length() < f32::EPSILON);
716        assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);
717        assert!(merged.contains(&a));
718        assert!(merged.contains(&b));
719        assert!(!a.contains(&merged));
720        assert!(!b.contains(&merged));
721
722        // When one circle contains the other circle, we use the bigger circle
723        let b = BoundingSphere::new(Vec3::ZERO, 3.);
724        assert!(a.contains(&b));
725        let merged = a.merge(&b);
726        assert_eq!(merged.center, a.center);
727        assert_eq!(merged.radius(), a.radius());
728
729        // When two circles are at the same point, we use the bigger radius
730        let b = BoundingSphere::new(Vec3::ONE, 6.);
731        let merged = a.merge(&b);
732        assert_eq!(merged.center, a.center);
733        assert_eq!(merged.radius(), b.radius());
734    }
735
736    #[test]
737    fn merge_identical() {
738        let a = BoundingSphere::new(Vec3::ONE, 5.);
739        let merged = a.merge(&a);
740        assert_eq!(merged.center, a.center);
741        assert_eq!(merged.radius(), a.radius());
742    }
743
744    #[test]
745    fn grow() {
746        let a = BoundingSphere::new(Vec3::ONE, 5.);
747        let padded = a.grow(1.25);
748        assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);
749        assert!(padded.contains(&a));
750        assert!(!a.contains(&padded));
751    }
752
753    #[test]
754    fn shrink() {
755        let a = BoundingSphere::new(Vec3::ONE, 5.);
756        let shrunk = a.shrink(0.5);
757        assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);
758        assert!(a.contains(&shrunk));
759        assert!(!shrunk.contains(&a));
760    }
761
762    #[test]
763    fn scale_around_center() {
764        let a = BoundingSphere::new(Vec3::ONE, 5.);
765        let scaled = a.scale_around_center(2.);
766        assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);
767        assert!(!a.contains(&scaled));
768        assert!(scaled.contains(&a));
769    }
770
771    #[test]
772    fn transform() {
773        let a = BoundingSphere::new(Vec3::ONE, 5.0);
774        let transformed = a.transformed_by(
775            Vec3::new(2.0, -2.0, 4.0),
776            Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
777        );
778        assert_relative_eq!(
779            transformed.center,
780            Vec3A::new(2.0, core::f32::consts::SQRT_2 - 2.0, 5.0)
781        );
782        assert_eq!(transformed.radius(), 5.0);
783    }
784
785    #[test]
786    fn closest_point() {
787        let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
788        assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3A::X);
789        assert_eq!(
790            sphere.closest_point(Vec3::NEG_ONE * 10.0),
791            Vec3A::NEG_ONE.normalize()
792        );
793        assert_eq!(
794            sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
795            Vec3A::new(0.25, 0.1, 0.3)
796        );
797    }
798
799    #[test]
800    fn intersect_bounding_sphere() {
801        let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
802        assert!(sphere.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
803        assert!(sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.1, 1.0)));
804        assert!(sphere.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.1, 1.0)));
805        assert!(!sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.2, 1.0)));
806    }
807}