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#[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
35pub trait Bounded3d {
37 fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d;
39 fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere;
41}
42
43#[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 pub min: Vec3A,
58 pub max: Vec3A,
60}
61
62impl Aabb3d {
63 #[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 #[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 #[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 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 #[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 #[inline]
124 pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
125 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 #[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 #[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 #[inline]
255 fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
256 self.rotate_by(rotation);
257 self
258 }
259
260 #[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#[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 pub center: Vec3A,
509 pub sphere: Sphere,
511}
512
513impl BoundingSphere {
514 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 #[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 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 #[inline]
550 pub const fn radius(&self) -> f32 {
551 self.sphere.radius
552 }
553
554 #[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 #[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 point
576 } else {
577 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 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 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 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 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}