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#[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
36pub trait Bounded3d {
38 fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d;
40 fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere;
42}
43
44#[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 pub min: Vec3A,
59 pub max: Vec3A,
61}
62
63impl Aabb3d {
64 #[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 #[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 #[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 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 #[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 #[inline]
125 pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
126 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 #[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 #[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 #[inline]
256 fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
257 self.rotate_by(rotation);
258 self
259 }
260
261 #[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#[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 pub center: Vec3A,
515 pub sphere: Sphere,
517}
518
519impl BoundingSphere {
520 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 #[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 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 #[inline]
556 pub fn radius(&self) -> f32 {
557 self.sphere.radius
558 }
559
560 #[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 #[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 point
582 } else {
583 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 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 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 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 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}