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