Skip to main content

bevy_shape/bounding/bounded3d/
mod.rs

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