Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs
6598 views
1
//! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives).
2
3
use crate::{
4
bounding::{Bounded2d, BoundingCircle, BoundingVolume},
5
ops,
6
primitives::{
7
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Segment3d,
8
Sphere, Torus, Triangle2d, Triangle3d,
9
},
10
Isometry2d, Isometry3d, Mat3, Vec2, Vec3, Vec3A,
11
};
12
13
#[cfg(feature = "alloc")]
14
use crate::primitives::Polyline3d;
15
16
use super::{Aabb3d, Bounded3d, BoundingSphere};
17
18
impl Bounded3d for Sphere {
19
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
20
let isometry = isometry.into();
21
Aabb3d::new(isometry.translation, Vec3::splat(self.radius))
22
}
23
24
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
25
let isometry = isometry.into();
26
BoundingSphere::new(isometry.translation, self.radius)
27
}
28
}
29
30
impl Bounded3d for InfinitePlane3d {
31
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
32
let isometry = isometry.into();
33
34
let normal = isometry.rotation * *self.normal;
35
let facing_x = normal == Vec3::X || normal == Vec3::NEG_X;
36
let facing_y = normal == Vec3::Y || normal == Vec3::NEG_Y;
37
let facing_z = normal == Vec3::Z || normal == Vec3::NEG_Z;
38
39
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
40
// like growing or shrinking the AABB without breaking things.
41
let half_width = if facing_x { 0.0 } else { f32::MAX / 2.0 };
42
let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };
43
let half_depth = if facing_z { 0.0 } else { f32::MAX / 2.0 };
44
let half_size = Vec3A::new(half_width, half_height, half_depth);
45
46
Aabb3d::new(isometry.translation, half_size)
47
}
48
49
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
50
let isometry = isometry.into();
51
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
52
}
53
}
54
55
impl Bounded3d for Line3d {
56
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
57
let isometry = isometry.into();
58
let direction = isometry.rotation * *self.direction;
59
60
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
61
// like growing or shrinking the AABB without breaking things.
62
let max = f32::MAX / 2.0;
63
let half_width = if direction.x == 0.0 { 0.0 } else { max };
64
let half_height = if direction.y == 0.0 { 0.0 } else { max };
65
let half_depth = if direction.z == 0.0 { 0.0 } else { max };
66
let half_size = Vec3A::new(half_width, half_height, half_depth);
67
68
Aabb3d::new(isometry.translation, half_size)
69
}
70
71
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
72
let isometry = isometry.into();
73
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
74
}
75
}
76
77
impl Bounded3d for Segment3d {
78
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
79
Aabb3d::from_point_cloud(isometry, [self.point1(), self.point2()].iter().copied())
80
}
81
82
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
83
let isometry = isometry.into();
84
let local_sphere = BoundingSphere::new(self.center(), self.length() / 2.);
85
local_sphere.transformed_by(isometry.translation, isometry.rotation)
86
}
87
}
88
89
#[cfg(feature = "alloc")]
90
impl Bounded3d for Polyline3d {
91
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
92
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
93
}
94
95
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
96
BoundingSphere::from_point_cloud(isometry, &self.vertices)
97
}
98
}
99
100
impl Bounded3d for Cuboid {
101
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
102
let isometry = isometry.into();
103
104
// Compute the AABB of the rotated cuboid by transforming the half-size
105
// by an absolute rotation matrix.
106
let rot_mat = Mat3::from_quat(isometry.rotation);
107
let abs_rot_mat = Mat3::from_cols(
108
rot_mat.x_axis.abs(),
109
rot_mat.y_axis.abs(),
110
rot_mat.z_axis.abs(),
111
);
112
let half_size = abs_rot_mat * self.half_size;
113
114
Aabb3d::new(isometry.translation, half_size)
115
}
116
117
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
118
let isometry = isometry.into();
119
BoundingSphere::new(isometry.translation, self.half_size.length())
120
}
121
}
122
123
impl Bounded3d for Cylinder {
124
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
125
// Reference: http://iquilezles.org/articles/diskbbox/
126
127
let isometry = isometry.into();
128
129
let segment_dir = isometry.rotation * Vec3A::Y;
130
let top = segment_dir * self.half_height;
131
let bottom = -top;
132
133
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
134
let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
135
136
Aabb3d {
137
min: isometry.translation + (top - half_size).min(bottom - half_size),
138
max: isometry.translation + (top + half_size).max(bottom + half_size),
139
}
140
}
141
142
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
143
let isometry = isometry.into();
144
let radius = ops::hypot(self.radius, self.half_height);
145
BoundingSphere::new(isometry.translation, radius)
146
}
147
}
148
149
impl Bounded3d for Capsule3d {
150
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
151
let isometry = isometry.into();
152
153
// Get the line segment between the hemispheres of the rotated capsule
154
let segment_dir = isometry.rotation * Vec3A::Y;
155
let top = segment_dir * self.half_length;
156
let bottom = -top;
157
158
// Expand the line segment by the capsule radius to get the capsule half-extents
159
let min = bottom.min(top) - Vec3A::splat(self.radius);
160
let max = bottom.max(top) + Vec3A::splat(self.radius);
161
162
Aabb3d {
163
min: min + isometry.translation,
164
max: max + isometry.translation,
165
}
166
}
167
168
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
169
let isometry = isometry.into();
170
BoundingSphere::new(isometry.translation, self.radius + self.half_length)
171
}
172
}
173
174
impl Bounded3d for Cone {
175
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
176
// Reference: http://iquilezles.org/articles/diskbbox/
177
178
let isometry = isometry.into();
179
180
let segment_dir = isometry.rotation * Vec3A::Y;
181
let top = segment_dir * 0.5 * self.height;
182
let bottom = -top;
183
184
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
185
let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
186
187
Aabb3d {
188
min: isometry.translation + top.min(bottom - self.radius * half_extents),
189
max: isometry.translation + top.max(bottom + self.radius * half_extents),
190
}
191
}
192
193
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
194
let isometry = isometry.into();
195
196
// Get the triangular cross-section of the cone.
197
let half_height = 0.5 * self.height;
198
let triangle = Triangle2d::new(
199
half_height * Vec2::Y,
200
Vec2::new(-self.radius, -half_height),
201
Vec2::new(self.radius, -half_height),
202
);
203
204
// Because of circular symmetry, we can use the bounding circle of the triangle
205
// for the bounding sphere of the cone.
206
let BoundingCircle { circle, center } = triangle.bounding_circle(Isometry2d::IDENTITY);
207
208
BoundingSphere::new(
209
isometry.rotation * Vec3A::from(center.extend(0.0)) + isometry.translation,
210
circle.radius,
211
)
212
}
213
}
214
215
impl Bounded3d for ConicalFrustum {
216
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
217
// Reference: http://iquilezles.org/articles/diskbbox/
218
219
let isometry = isometry.into();
220
221
let segment_dir = isometry.rotation * Vec3A::Y;
222
let top = segment_dir * 0.5 * self.height;
223
let bottom = -top;
224
225
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
226
let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
227
228
Aabb3d {
229
min: isometry.translation
230
+ (top - self.radius_top * half_extents)
231
.min(bottom - self.radius_bottom * half_extents),
232
max: isometry.translation
233
+ (top + self.radius_top * half_extents)
234
.max(bottom + self.radius_bottom * half_extents),
235
}
236
}
237
238
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
239
let isometry = isometry.into();
240
let half_height = 0.5 * self.height;
241
242
// To compute the bounding sphere, we'll get the center and radius of the circumcircle
243
// passing through all four vertices of the trapezoidal cross-section of the conical frustum.
244
//
245
// If the circumcenter is inside the trapezoid, we can use that for the bounding sphere.
246
// Otherwise, we clamp it to the longer parallel side to get a more tightly fitting bounding sphere.
247
//
248
// The circumcenter is at the intersection of the bisectors perpendicular to the sides.
249
// For the isosceles trapezoid, the X coordinate is zero at the center, so a single bisector is enough.
250
//
251
// A
252
// *-------*
253
// / | \
254
// / | \
255
// AB / \ | / \
256
// / \ | / \
257
// / C \
258
// *-------------------*
259
// B
260
261
let a = Vec2::new(-self.radius_top, half_height);
262
let b = Vec2::new(-self.radius_bottom, -half_height);
263
let ab = a - b;
264
let ab_midpoint = b + 0.5 * ab;
265
let bisector = ab.perp();
266
267
// Compute intersection between bisector and vertical line at x = 0.
268
//
269
// x = ab_midpoint.x + t * bisector.x = 0
270
// y = ab_midpoint.y + t * bisector.y = ?
271
//
272
// Because ab_midpoint.y = 0 for our conical frustum, we get:
273
// y = t * bisector.y
274
//
275
// Solve x for t:
276
// t = -ab_midpoint.x / bisector.x
277
//
278
// Substitute t to solve for y:
279
// y = -ab_midpoint.x / bisector.x * bisector.y
280
let circumcenter_y = -ab_midpoint.x / bisector.x * bisector.y;
281
282
// If the circumcenter is outside the trapezoid, the bounding circle is too large.
283
// In those cases, we clamp it to the longer parallel side.
284
let (center, radius) = if circumcenter_y <= -half_height {
285
(Vec2::new(0.0, -half_height), self.radius_bottom)
286
} else if circumcenter_y >= half_height {
287
(Vec2::new(0.0, half_height), self.radius_top)
288
} else {
289
let circumcenter = Vec2::new(0.0, circumcenter_y);
290
// We can use the distance from an arbitrary vertex because they all lie on the circumcircle.
291
(circumcenter, a.distance(circumcenter))
292
};
293
294
BoundingSphere::new(
295
isometry.translation + isometry.rotation * Vec3A::from(center.extend(0.0)),
296
radius,
297
)
298
}
299
}
300
301
impl Bounded3d for Torus {
302
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
303
let isometry = isometry.into();
304
305
// Compute the AABB of a flat disc with the major radius of the torus.
306
// Reference: http://iquilezles.org/articles/diskbbox/
307
let normal = isometry.rotation * Vec3A::Y;
308
let e = (Vec3A::ONE - normal * normal).max(Vec3A::ZERO);
309
let disc_half_size =
310
self.major_radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
311
312
// Expand the disc by the minor radius to get the torus half-size
313
let half_size = disc_half_size + Vec3A::splat(self.minor_radius);
314
315
Aabb3d::new(isometry.translation, half_size)
316
}
317
318
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
319
let isometry = isometry.into();
320
BoundingSphere::new(isometry.translation, self.outer_radius())
321
}
322
}
323
324
impl Bounded3d for Triangle3d {
325
/// Get the bounding box of the triangle.
326
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
327
let isometry = isometry.into();
328
let [a, b, c] = self.vertices;
329
330
let a = isometry.rotation * a;
331
let b = isometry.rotation * b;
332
let c = isometry.rotation * c;
333
334
let min = Vec3A::from(a.min(b).min(c));
335
let max = Vec3A::from(a.max(b).max(c));
336
337
let bounding_center = (max + min) / 2.0 + isometry.translation;
338
let half_extents = (max - min) / 2.0;
339
340
Aabb3d::new(bounding_center, half_extents)
341
}
342
343
/// Get the bounding sphere of the triangle.
344
///
345
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
346
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
347
/// that contains the largest side of the triangle.
348
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
349
let isometry = isometry.into();
350
351
if self.is_degenerate() || self.is_obtuse() {
352
let (p1, p2) = self.largest_side();
353
let (p1, p2) = (Vec3A::from(p1), Vec3A::from(p2));
354
let mid_point = (p1 + p2) / 2.0;
355
let radius = mid_point.distance(p1);
356
BoundingSphere::new(mid_point + isometry.translation, radius)
357
} else {
358
let [a, _, _] = self.vertices;
359
360
let circumcenter = self.circumcenter();
361
let radius = circumcenter.distance(a);
362
BoundingSphere::new(Vec3A::from(circumcenter) + isometry.translation, radius)
363
}
364
}
365
}
366
367
#[cfg(test)]
368
mod tests {
369
use crate::{bounding::BoundingVolume, ops, Isometry3d};
370
use glam::{Quat, Vec3, Vec3A};
371
372
use crate::{
373
bounding::Bounded3d,
374
primitives::{
375
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
376
Segment3d, Sphere, Torus, Triangle3d,
377
},
378
Dir3,
379
};
380
381
#[test]
382
fn sphere() {
383
let sphere = Sphere { radius: 1.0 };
384
let translation = Vec3::new(2.0, 1.0, 0.0);
385
386
let aabb = sphere.aabb_3d(translation);
387
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
388
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
389
390
let bounding_sphere = sphere.bounding_sphere(translation);
391
assert_eq!(bounding_sphere.center, translation.into());
392
assert_eq!(bounding_sphere.radius(), 1.0);
393
}
394
395
#[test]
396
fn plane() {
397
let translation = Vec3::new(2.0, 1.0, 0.0);
398
399
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(translation);
400
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, -f32::MAX / 2.0));
401
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, f32::MAX / 2.0));
402
403
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(translation);
404
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, -f32::MAX / 2.0));
405
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, f32::MAX / 2.0));
406
407
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(translation);
408
assert_eq!(aabb3.min, Vec3A::new(-f32::MAX / 2.0, -f32::MAX / 2.0, 0.0));
409
assert_eq!(aabb3.max, Vec3A::new(f32::MAX / 2.0, f32::MAX / 2.0, 0.0));
410
411
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(translation);
412
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
413
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
414
415
let bounding_sphere = InfinitePlane3d::new(Vec3::Y).bounding_sphere(translation);
416
assert_eq!(bounding_sphere.center, translation.into());
417
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
418
}
419
420
#[test]
421
fn line() {
422
let translation = Vec3::new(2.0, 1.0, 0.0);
423
424
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(translation);
425
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, 0.0));
426
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, 0.0));
427
428
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(translation);
429
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, 0.0));
430
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, 0.0));
431
432
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(translation);
433
assert_eq!(aabb3.min, Vec3A::new(2.0, 1.0, -f32::MAX / 2.0));
434
assert_eq!(aabb3.max, Vec3A::new(2.0, 1.0, f32::MAX / 2.0));
435
436
let aabb4 = Line3d {
437
direction: Dir3::from_xyz(1.0, 1.0, 1.0).unwrap(),
438
}
439
.aabb_3d(translation);
440
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
441
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
442
443
let bounding_sphere = Line3d { direction: Dir3::Y }.bounding_sphere(translation);
444
assert_eq!(bounding_sphere.center, translation.into());
445
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
446
}
447
448
#[test]
449
fn segment() {
450
let segment = Segment3d::new(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0));
451
let translation = Vec3::new(2.0, 1.0, 0.0);
452
453
let aabb = segment.aabb_3d(translation);
454
assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0));
455
assert_eq!(aabb.max, Vec3A::new(3.0, 1.5, 0.0));
456
457
let bounding_sphere = segment.bounding_sphere(translation);
458
assert_eq!(bounding_sphere.center, translation.into());
459
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
460
}
461
462
#[test]
463
fn polyline() {
464
let polyline = Polyline3d::new([
465
Vec3::ONE,
466
Vec3::new(-1.0, 1.0, 1.0),
467
Vec3::NEG_ONE,
468
Vec3::new(1.0, -1.0, -1.0),
469
]);
470
let translation = Vec3::new(2.0, 1.0, 0.0);
471
472
let aabb = polyline.aabb_3d(translation);
473
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
474
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
475
476
let bounding_sphere = polyline.bounding_sphere(translation);
477
assert_eq!(bounding_sphere.center, translation.into());
478
assert_eq!(
479
bounding_sphere.radius(),
480
ops::hypot(ops::hypot(1.0, 1.0), 1.0)
481
);
482
}
483
484
#[test]
485
fn cuboid() {
486
let cuboid = Cuboid::new(2.0, 1.0, 1.0);
487
let translation = Vec3::new(2.0, 1.0, 0.0);
488
489
let aabb = cuboid.aabb_3d(Isometry3d::new(
490
translation,
491
Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
492
));
493
let expected_half_size = Vec3A::new(1.0606601, 1.0606601, 0.5);
494
assert_eq!(aabb.min, Vec3A::from(translation) - expected_half_size);
495
assert_eq!(aabb.max, Vec3A::from(translation) + expected_half_size);
496
497
let bounding_sphere = cuboid.bounding_sphere(translation);
498
assert_eq!(bounding_sphere.center, translation.into());
499
assert_eq!(
500
bounding_sphere.radius(),
501
ops::hypot(ops::hypot(1.0, 0.5), 0.5)
502
);
503
}
504
505
#[test]
506
fn cylinder() {
507
let cylinder = Cylinder::new(0.5, 2.0);
508
let translation = Vec3::new(2.0, 1.0, 0.0);
509
510
let aabb = cylinder.aabb_3d(translation);
511
assert_eq!(
512
aabb.min,
513
Vec3A::from(translation) - Vec3A::new(0.5, 1.0, 0.5)
514
);
515
assert_eq!(
516
aabb.max,
517
Vec3A::from(translation) + Vec3A::new(0.5, 1.0, 0.5)
518
);
519
520
let bounding_sphere = cylinder.bounding_sphere(translation);
521
assert_eq!(bounding_sphere.center, translation.into());
522
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
523
}
524
525
#[test]
526
fn capsule() {
527
let capsule = Capsule3d::new(0.5, 2.0);
528
let translation = Vec3::new(2.0, 1.0, 0.0);
529
530
let aabb = capsule.aabb_3d(translation);
531
assert_eq!(
532
aabb.min,
533
Vec3A::from(translation) - Vec3A::new(0.5, 1.5, 0.5)
534
);
535
assert_eq!(
536
aabb.max,
537
Vec3A::from(translation) + Vec3A::new(0.5, 1.5, 0.5)
538
);
539
540
let bounding_sphere = capsule.bounding_sphere(translation);
541
assert_eq!(bounding_sphere.center, translation.into());
542
assert_eq!(bounding_sphere.radius(), 1.5);
543
}
544
545
#[test]
546
fn cone() {
547
let cone = Cone {
548
radius: 1.0,
549
height: 2.0,
550
};
551
let translation = Vec3::new(2.0, 1.0, 0.0);
552
553
let aabb = cone.aabb_3d(translation);
554
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
555
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
556
557
let bounding_sphere = cone.bounding_sphere(translation);
558
assert_eq!(
559
bounding_sphere.center,
560
Vec3A::from(translation) + Vec3A::NEG_Y * 0.25
561
);
562
assert_eq!(bounding_sphere.radius(), 1.25);
563
}
564
565
#[test]
566
fn conical_frustum() {
567
let conical_frustum = ConicalFrustum {
568
radius_top: 0.5,
569
radius_bottom: 1.0,
570
height: 2.0,
571
};
572
let translation = Vec3::new(2.0, 1.0, 0.0);
573
574
let aabb = conical_frustum.aabb_3d(translation);
575
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
576
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
577
578
let bounding_sphere = conical_frustum.bounding_sphere(translation);
579
assert_eq!(
580
bounding_sphere.center,
581
Vec3A::from(translation) + Vec3A::NEG_Y * 0.1875
582
);
583
assert_eq!(bounding_sphere.radius(), 1.2884705);
584
}
585
586
#[test]
587
fn wide_conical_frustum() {
588
let conical_frustum = ConicalFrustum {
589
radius_top: 0.5,
590
radius_bottom: 5.0,
591
height: 1.0,
592
};
593
let translation = Vec3::new(2.0, 1.0, 0.0);
594
595
let aabb = conical_frustum.aabb_3d(translation);
596
assert_eq!(aabb.min, Vec3A::new(-3.0, 0.5, -5.0));
597
assert_eq!(aabb.max, Vec3A::new(7.0, 1.5, 5.0));
598
599
// For wide conical frusta like this, the circumcenter can be outside the frustum,
600
// so the center and radius should be clamped to the longest side.
601
let bounding_sphere = conical_frustum.bounding_sphere(translation);
602
assert_eq!(
603
bounding_sphere.center,
604
Vec3A::from(translation) + Vec3A::NEG_Y * 0.5
605
);
606
assert_eq!(bounding_sphere.radius(), 5.0);
607
}
608
609
#[test]
610
fn torus() {
611
let torus = Torus {
612
minor_radius: 0.5,
613
major_radius: 1.0,
614
};
615
let translation = Vec3::new(2.0, 1.0, 0.0);
616
617
let aabb = torus.aabb_3d(translation);
618
assert_eq!(aabb.min, Vec3A::new(0.5, 0.5, -1.5));
619
assert_eq!(aabb.max, Vec3A::new(3.5, 1.5, 1.5));
620
621
let bounding_sphere = torus.bounding_sphere(translation);
622
assert_eq!(bounding_sphere.center, translation.into());
623
assert_eq!(bounding_sphere.radius(), 1.5);
624
}
625
626
#[test]
627
fn triangle3d() {
628
let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
629
630
let br = zero_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
631
assert_eq!(
632
br.center(),
633
Vec3::ZERO.into(),
634
"incorrect bounding box center"
635
);
636
assert_eq!(
637
br.half_size(),
638
Vec3::ZERO.into(),
639
"incorrect bounding box half extents"
640
);
641
642
let bs = zero_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
643
assert_eq!(
644
bs.center,
645
Vec3::ZERO.into(),
646
"incorrect bounding sphere center"
647
);
648
assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius");
649
650
let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
651
let bs = dup_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
652
assert_eq!(
653
bs.center,
654
Vec3::new(0.5, 0.0, 0.0).into(),
655
"incorrect bounding sphere center"
656
);
657
assert_eq!(bs.sphere.radius, 0.5, "incorrect bounding sphere radius");
658
let br = dup_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
659
assert_eq!(
660
br.center(),
661
Vec3::new(0.5, 0.0, 0.0).into(),
662
"incorrect bounding box center"
663
);
664
assert_eq!(
665
br.half_size(),
666
Vec3::new(0.5, 0.0, 0.0).into(),
667
"incorrect bounding box half extents"
668
);
669
670
let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
671
let bs = collinear_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
672
assert_eq!(
673
bs.center,
674
Vec3::ZERO.into(),
675
"incorrect bounding sphere center"
676
);
677
assert_eq!(bs.sphere.radius, 1.0, "incorrect bounding sphere radius");
678
let br = collinear_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
679
assert_eq!(
680
br.center(),
681
Vec3::ZERO.into(),
682
"incorrect bounding box center"
683
);
684
assert_eq!(
685
br.half_size(),
686
Vec3::new(1.0, 0.0, 0.0).into(),
687
"incorrect bounding box half extents"
688
);
689
}
690
}
691
692