Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/bounding/bounded3d/mod.rs
9356 views
1
mod extrusion;
2
mod primitive_impls;
3
4
use glam::Mat3;
5
6
use super::{BoundingVolume, IntersectsVolume};
7
use crate::{
8
ops::{self, FloatPow},
9
primitives::Cuboid,
10
Isometry3d, Quat, Vec3A,
11
};
12
13
#[cfg(feature = "bevy_reflect")]
14
use bevy_reflect::Reflect;
15
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
16
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
17
#[cfg(feature = "serialize")]
18
use serde::{Deserialize, Serialize};
19
20
pub use extrusion::BoundedExtrusion;
21
22
/// Computes the geometric center of the given set of points.
23
#[inline]
24
fn 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.
37
pub 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
)]
56
pub 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
63
impl 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
131
impl 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
140
impl 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
276
impl 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
283
impl 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)]
294
mod 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
498
use 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
)]
512
pub struct BoundingSphere {
513
/// The center of the bounding sphere
514
pub center: Vec3A,
515
/// The sphere
516
pub sphere: Sphere,
517
}
518
519
impl 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
591
impl 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
678
impl 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
687
impl IntersectsVolume<Aabb3d> for BoundingSphere {
688
#[inline]
689
fn intersects(&self, aabb: &Aabb3d) -> bool {
690
aabb.intersects(self)
691
}
692
}
693
694
#[cfg(test)]
695
mod 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
}
826
827