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
6598 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
Isometry3d, Quat, Vec3A,
10
};
11
12
#[cfg(feature = "bevy_reflect")]
13
use bevy_reflect::Reflect;
14
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
15
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
16
#[cfg(feature = "serialize")]
17
use serde::{Deserialize, Serialize};
18
19
pub use extrusion::BoundedExtrusion;
20
21
/// Computes the geometric center of the given set of points.
22
#[inline(always)]
23
fn 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.
36
pub 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
)]
55
pub 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
62
impl Aabb3d {
63
/// Constructs an AABB from its center and half-size.
64
#[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
/// Computes the smallest [`Aabb3d`] containing the given set of points,
75
/// transformed by the rotation and translation of the given isometry.
76
///
77
/// # Panics
78
///
79
/// Panics if the given set of points is empty.
80
#[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
// Transform all points by rotation
88
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
/// Computes the smallest [`BoundingSphere`] containing this [`Aabb3d`].
105
#[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
/// Finds the point on the AABB that is closest to the given `point`.
112
///
113
/// If the point is outside the AABB, the returned point will be on the surface of the AABB.
114
/// Otherwise, it will be inside the AABB and returned as is.
115
#[inline(always)]
116
pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
117
// Clamp point coordinates to the AABB
118
point.into().clamp(self.min, self.max)
119
}
120
}
121
122
impl 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).max(Vec3A::ZERO);
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
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
190
///
191
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
192
///
193
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
194
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
195
/// and consider storing the original AABB and rotating that every time instead.
196
#[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
/// 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(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
/// Rotates the bounding volume around the origin by the given rotation.
231
///
232
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
233
///
234
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
235
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
236
/// and consider storing the original AABB and rotating that every time instead.
237
#[inline(always)]
238
fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
239
self.rotate_by(rotation);
240
self
241
}
242
243
/// Rotates the bounding volume around the origin by the given rotation.
244
///
245
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
246
///
247
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
248
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
249
/// and consider storing the original AABB and rotating that every time instead.
250
#[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
258
impl 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
265
impl 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)]
276
mod 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
480
use crate::primitives::Sphere;
481
482
/// A bounding sphere
483
#[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
)]
494
pub struct BoundingSphere {
495
/// The center of the bounding sphere
496
pub center: Vec3A,
497
/// The sphere
498
pub sphere: Sphere,
499
}
500
501
impl BoundingSphere {
502
/// Constructs a bounding sphere from its center and radius.
503
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
/// Computes a [`BoundingSphere`] containing the given set of points,
512
/// transformed by the rotation and translation of the given isometry.
513
///
514
/// The bounding sphere is not guaranteed to be the smallest possible.
515
#[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
// Get squared version to avoid unnecessary sqrt calls
527
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
/// Get the radius of the bounding sphere
537
#[inline(always)]
538
pub fn radius(&self) -> f32 {
539
self.sphere.radius
540
}
541
542
/// Computes the smallest [`Aabb3d`] containing this [`BoundingSphere`].
543
#[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
/// Finds the point on the bounding sphere that is closest to the given `point`.
552
///
553
/// If the point is outside the sphere, the returned point will be on the surface of the sphere.
554
/// Otherwise, it will be inside the sphere and returned as is.
555
#[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
// The point is inside the sphere.
563
point
564
} else {
565
// The point is outside the sphere.
566
// Find the closest point on the surface of the sphere.
567
let dir_to_point = point / ops::sqrt(distance_squared);
568
self.center + radius * dir_to_point
569
}
570
}
571
}
572
573
impl 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
660
impl 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
669
impl IntersectsVolume<Aabb3d> for BoundingSphere {
670
#[inline(always)]
671
fn intersects(&self, aabb: &Aabb3d) -> bool {
672
aabb.intersects(self)
673
}
674
}
675
676
#[cfg(test)]
677
mod 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
// Since this number is messy we check it with a higher threshold
690
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
// When merging two circles that don't contain each other, we find a center position that
711
// contains both
712
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
// When one circle contains the other circle, we use the bigger circle
723
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
// When two circles are at the same point, we use the bigger radius
730
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
}
808
809