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