Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/bounding/bounded2d/mod.rs
6598 views
1
mod primitive_impls;
2
3
use super::{BoundingVolume, IntersectsVolume};
4
use crate::{
5
ops,
6
prelude::{Mat2, Rot2, Vec2},
7
FloatPow, Isometry2d,
8
};
9
10
#[cfg(feature = "bevy_reflect")]
11
use bevy_reflect::Reflect;
12
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
13
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
14
#[cfg(feature = "serialize")]
15
use serde::{Deserialize, Serialize};
16
17
/// Computes the geometric center of the given set of points.
18
#[inline(always)]
19
fn point_cloud_2d_center(points: &[Vec2]) -> Vec2 {
20
assert!(
21
!points.is_empty(),
22
"cannot compute the center of an empty set of points"
23
);
24
25
let denom = 1.0 / points.len() as f32;
26
points.iter().fold(Vec2::ZERO, |acc, point| acc + *point) * denom
27
}
28
29
/// A trait with methods that return 2D bounding volumes for a shape.
30
pub trait Bounded2d {
31
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
32
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d;
33
/// Get a bounding circle for the shape translated and rotated by the given isometry.
34
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle;
35
}
36
37
/// A 2D axis-aligned bounding box, or bounding rectangle
38
#[doc(alias = "BoundingRectangle")]
39
#[derive(Clone, Copy, Debug, PartialEq)]
40
#[cfg_attr(
41
feature = "bevy_reflect",
42
derive(Reflect),
43
reflect(Debug, PartialEq, Clone)
44
)]
45
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
46
#[cfg_attr(
47
all(feature = "serialize", feature = "bevy_reflect"),
48
reflect(Serialize, Deserialize)
49
)]
50
pub struct Aabb2d {
51
/// The minimum, conventionally bottom-left, point of the box
52
pub min: Vec2,
53
/// The maximum, conventionally top-right, point of the box
54
pub max: Vec2,
55
}
56
57
impl Aabb2d {
58
/// Constructs an AABB from its center and half-size.
59
#[inline(always)]
60
pub fn new(center: Vec2, half_size: Vec2) -> Self {
61
debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0);
62
Self {
63
min: center - half_size,
64
max: center + half_size,
65
}
66
}
67
68
/// Computes the smallest [`Aabb2d`] containing the given set of points,
69
/// transformed by the rotation and translation of the given isometry.
70
///
71
/// # Panics
72
///
73
/// Panics if the given set of points is empty.
74
#[inline(always)]
75
pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> Aabb2d {
76
let isometry = isometry.into();
77
78
// Transform all points by rotation
79
let mut iter = points.iter().map(|point| isometry.rotation * *point);
80
81
let first = iter
82
.next()
83
.expect("point cloud must contain at least one point for Aabb2d construction");
84
85
let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
86
(point.min(prev_min), point.max(prev_max))
87
});
88
89
Aabb2d {
90
min: min + isometry.translation,
91
max: max + isometry.translation,
92
}
93
}
94
95
/// Computes the smallest [`BoundingCircle`] containing this [`Aabb2d`].
96
#[inline(always)]
97
pub fn bounding_circle(&self) -> BoundingCircle {
98
let radius = self.min.distance(self.max) / 2.0;
99
BoundingCircle::new(self.center(), radius)
100
}
101
102
/// Finds the point on the AABB that is closest to the given `point`.
103
///
104
/// If the point is outside the AABB, the returned point will be on the perimeter of the AABB.
105
/// Otherwise, it will be inside the AABB and returned as is.
106
#[inline(always)]
107
pub fn closest_point(&self, point: Vec2) -> Vec2 {
108
// Clamp point coordinates to the AABB
109
point.clamp(self.min, self.max)
110
}
111
}
112
113
impl BoundingVolume for Aabb2d {
114
type Translation = Vec2;
115
type Rotation = Rot2;
116
type HalfSize = Vec2;
117
118
#[inline(always)]
119
fn center(&self) -> Self::Translation {
120
(self.min + self.max) / 2.
121
}
122
123
#[inline(always)]
124
fn half_size(&self) -> Self::HalfSize {
125
(self.max - self.min) / 2.
126
}
127
128
#[inline(always)]
129
fn visible_area(&self) -> f32 {
130
let b = (self.max - self.min).max(Vec2::ZERO);
131
b.x * b.y
132
}
133
134
#[inline(always)]
135
fn contains(&self, other: &Self) -> bool {
136
other.min.x >= self.min.x
137
&& other.min.y >= self.min.y
138
&& other.max.x <= self.max.x
139
&& other.max.y <= self.max.y
140
}
141
142
#[inline(always)]
143
fn merge(&self, other: &Self) -> Self {
144
Self {
145
min: self.min.min(other.min),
146
max: self.max.max(other.max),
147
}
148
}
149
150
#[inline(always)]
151
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
152
let amount = amount.into();
153
let b = Self {
154
min: self.min - amount,
155
max: self.max + amount,
156
};
157
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
158
b
159
}
160
161
#[inline(always)]
162
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
163
let amount = amount.into();
164
let b = Self {
165
min: self.min + amount,
166
max: self.max - amount,
167
};
168
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
169
b
170
}
171
172
#[inline(always)]
173
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
174
let scale = scale.into();
175
let b = Self {
176
min: self.center() - (self.half_size() * scale),
177
max: self.center() + (self.half_size() * scale),
178
};
179
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
180
b
181
}
182
183
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
184
///
185
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
186
///
187
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
188
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
189
/// and consider storing the original AABB and rotating that every time instead.
190
#[inline(always)]
191
fn transformed_by(
192
mut self,
193
translation: impl Into<Self::Translation>,
194
rotation: impl Into<Self::Rotation>,
195
) -> Self {
196
self.transform_by(translation, rotation);
197
self
198
}
199
200
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
201
///
202
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
203
///
204
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
205
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
206
/// and consider storing the original AABB and rotating that every time instead.
207
#[inline(always)]
208
fn transform_by(
209
&mut self,
210
translation: impl Into<Self::Translation>,
211
rotation: impl Into<Self::Rotation>,
212
) {
213
self.rotate_by(rotation);
214
self.translate_by(translation);
215
}
216
217
#[inline(always)]
218
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
219
let translation = translation.into();
220
self.min += translation;
221
self.max += translation;
222
}
223
224
/// Rotates the bounding volume around the origin by the given rotation.
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(always)]
232
fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
233
self.rotate_by(rotation);
234
self
235
}
236
237
/// Rotates the bounding volume around the origin by the given rotation.
238
///
239
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
240
///
241
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
242
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
243
/// and consider storing the original AABB and rotating that every time instead.
244
#[inline(always)]
245
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
246
let rot_mat = Mat2::from(rotation.into());
247
let half_size = rot_mat.abs() * self.half_size();
248
*self = Self::new(rot_mat * self.center(), half_size);
249
}
250
}
251
252
impl IntersectsVolume<Self> for Aabb2d {
253
#[inline(always)]
254
fn intersects(&self, other: &Self) -> bool {
255
let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
256
let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
257
x_overlaps && y_overlaps
258
}
259
}
260
261
impl IntersectsVolume<BoundingCircle> for Aabb2d {
262
#[inline(always)]
263
fn intersects(&self, circle: &BoundingCircle) -> bool {
264
let closest_point = self.closest_point(circle.center);
265
let distance_squared = circle.center.distance_squared(closest_point);
266
let radius_squared = circle.radius().squared();
267
distance_squared <= radius_squared
268
}
269
}
270
271
#[cfg(test)]
272
mod aabb2d_tests {
273
use approx::assert_relative_eq;
274
275
use super::Aabb2d;
276
use crate::{
277
bounding::{BoundingCircle, BoundingVolume, IntersectsVolume},
278
ops, Vec2,
279
};
280
281
#[test]
282
fn center() {
283
let aabb = Aabb2d {
284
min: Vec2::new(-0.5, -1.),
285
max: Vec2::new(1., 1.),
286
};
287
assert!((aabb.center() - Vec2::new(0.25, 0.)).length() < f32::EPSILON);
288
let aabb = Aabb2d {
289
min: Vec2::new(5., -10.),
290
max: Vec2::new(10., -5.),
291
};
292
assert!((aabb.center() - Vec2::new(7.5, -7.5)).length() < f32::EPSILON);
293
}
294
295
#[test]
296
fn half_size() {
297
let aabb = Aabb2d {
298
min: Vec2::new(-0.5, -1.),
299
max: Vec2::new(1., 1.),
300
};
301
let half_size = aabb.half_size();
302
assert!((half_size - Vec2::new(0.75, 1.)).length() < f32::EPSILON);
303
}
304
305
#[test]
306
fn area() {
307
let aabb = Aabb2d {
308
min: Vec2::new(-1., -1.),
309
max: Vec2::new(1., 1.),
310
};
311
assert!(ops::abs(aabb.visible_area() - 4.) < f32::EPSILON);
312
let aabb = Aabb2d {
313
min: Vec2::new(0., 0.),
314
max: Vec2::new(1., 0.5),
315
};
316
assert!(ops::abs(aabb.visible_area() - 0.5) < f32::EPSILON);
317
}
318
319
#[test]
320
fn contains() {
321
let a = Aabb2d {
322
min: Vec2::new(-1., -1.),
323
max: Vec2::new(1., 1.),
324
};
325
let b = Aabb2d {
326
min: Vec2::new(-2., -1.),
327
max: Vec2::new(1., 1.),
328
};
329
assert!(!a.contains(&b));
330
let b = Aabb2d {
331
min: Vec2::new(-0.25, -0.8),
332
max: Vec2::new(1., 1.),
333
};
334
assert!(a.contains(&b));
335
}
336
337
#[test]
338
fn merge() {
339
let a = Aabb2d {
340
min: Vec2::new(-1., -1.),
341
max: Vec2::new(1., 0.5),
342
};
343
let b = Aabb2d {
344
min: Vec2::new(-2., -0.5),
345
max: Vec2::new(0.75, 1.),
346
};
347
let merged = a.merge(&b);
348
assert!((merged.min - Vec2::new(-2., -1.)).length() < f32::EPSILON);
349
assert!((merged.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
350
assert!(merged.contains(&a));
351
assert!(merged.contains(&b));
352
assert!(!a.contains(&merged));
353
assert!(!b.contains(&merged));
354
}
355
356
#[test]
357
fn grow() {
358
let a = Aabb2d {
359
min: Vec2::new(-1., -1.),
360
max: Vec2::new(1., 1.),
361
};
362
let padded = a.grow(Vec2::ONE);
363
assert!((padded.min - Vec2::new(-2., -2.)).length() < f32::EPSILON);
364
assert!((padded.max - Vec2::new(2., 2.)).length() < f32::EPSILON);
365
assert!(padded.contains(&a));
366
assert!(!a.contains(&padded));
367
}
368
369
#[test]
370
fn shrink() {
371
let a = Aabb2d {
372
min: Vec2::new(-2., -2.),
373
max: Vec2::new(2., 2.),
374
};
375
let shrunk = a.shrink(Vec2::ONE);
376
assert!((shrunk.min - Vec2::new(-1., -1.)).length() < f32::EPSILON);
377
assert!((shrunk.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
378
assert!(a.contains(&shrunk));
379
assert!(!shrunk.contains(&a));
380
}
381
382
#[test]
383
fn scale_around_center() {
384
let a = Aabb2d {
385
min: Vec2::NEG_ONE,
386
max: Vec2::ONE,
387
};
388
let scaled = a.scale_around_center(Vec2::splat(2.));
389
assert!((scaled.min - Vec2::splat(-2.)).length() < f32::EPSILON);
390
assert!((scaled.max - Vec2::splat(2.)).length() < f32::EPSILON);
391
assert!(!a.contains(&scaled));
392
assert!(scaled.contains(&a));
393
}
394
395
#[test]
396
fn rotate() {
397
let a = Aabb2d {
398
min: Vec2::new(-2.0, -2.0),
399
max: Vec2::new(2.0, 2.0),
400
};
401
let rotated = a.rotated_by(core::f32::consts::PI);
402
assert_relative_eq!(rotated.min, a.min);
403
assert_relative_eq!(rotated.max, a.max);
404
}
405
406
#[test]
407
fn transform() {
408
let a = Aabb2d {
409
min: Vec2::new(-2.0, -2.0),
410
max: Vec2::new(2.0, 2.0),
411
};
412
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
413
let half_length = ops::hypot(2.0, 2.0);
414
assert_eq!(
415
transformed.min,
416
Vec2::new(2.0 - half_length, -half_length - 2.0)
417
);
418
assert_eq!(
419
transformed.max,
420
Vec2::new(2.0 + half_length, half_length - 2.0)
421
);
422
}
423
424
#[test]
425
fn closest_point() {
426
let aabb = Aabb2d {
427
min: Vec2::NEG_ONE,
428
max: Vec2::ONE,
429
};
430
assert_eq!(aabb.closest_point(Vec2::X * 10.0), Vec2::X);
431
assert_eq!(aabb.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
432
assert_eq!(
433
aabb.closest_point(Vec2::new(0.25, 0.1)),
434
Vec2::new(0.25, 0.1)
435
);
436
}
437
438
#[test]
439
fn intersect_aabb() {
440
let aabb = Aabb2d {
441
min: Vec2::NEG_ONE,
442
max: Vec2::ONE,
443
};
444
assert!(aabb.intersects(&aabb));
445
assert!(aabb.intersects(&Aabb2d {
446
min: Vec2::new(0.5, 0.5),
447
max: Vec2::new(2.0, 2.0),
448
}));
449
assert!(aabb.intersects(&Aabb2d {
450
min: Vec2::new(-2.0, -2.0),
451
max: Vec2::new(-0.5, -0.5),
452
}));
453
assert!(!aabb.intersects(&Aabb2d {
454
min: Vec2::new(1.1, 0.0),
455
max: Vec2::new(2.0, 0.5),
456
}));
457
}
458
459
#[test]
460
fn intersect_bounding_circle() {
461
let aabb = Aabb2d {
462
min: Vec2::NEG_ONE,
463
max: Vec2::ONE,
464
};
465
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
466
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
467
assert!(aabb.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.5, 1.0)));
468
assert!(!aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.75, 1.0)));
469
}
470
}
471
472
use crate::primitives::Circle;
473
474
/// A bounding circle
475
#[derive(Clone, Copy, Debug, PartialEq)]
476
#[cfg_attr(
477
feature = "bevy_reflect",
478
derive(Reflect),
479
reflect(Debug, PartialEq, Clone)
480
)]
481
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
482
#[cfg_attr(
483
all(feature = "serialize", feature = "bevy_reflect"),
484
reflect(Serialize, Deserialize)
485
)]
486
pub struct BoundingCircle {
487
/// The center of the bounding circle
488
pub center: Vec2,
489
/// The circle
490
pub circle: Circle,
491
}
492
493
impl BoundingCircle {
494
/// Constructs a bounding circle from its center and radius.
495
#[inline(always)]
496
pub fn new(center: Vec2, radius: f32) -> Self {
497
debug_assert!(radius >= 0.);
498
Self {
499
center,
500
circle: Circle { radius },
501
}
502
}
503
504
/// Computes a [`BoundingCircle`] containing the given set of points,
505
/// transformed by the rotation and translation of the given isometry.
506
///
507
/// The bounding circle is not guaranteed to be the smallest possible.
508
#[inline(always)]
509
pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> BoundingCircle {
510
let isometry = isometry.into();
511
512
let center = point_cloud_2d_center(points);
513
let mut radius_squared = 0.0;
514
515
for point in points {
516
// Get squared version to avoid unnecessary sqrt calls
517
let distance_squared = point.distance_squared(center);
518
if distance_squared > radius_squared {
519
radius_squared = distance_squared;
520
}
521
}
522
523
BoundingCircle::new(isometry * center, ops::sqrt(radius_squared))
524
}
525
526
/// Get the radius of the bounding circle
527
#[inline(always)]
528
pub fn radius(&self) -> f32 {
529
self.circle.radius
530
}
531
532
/// Computes the smallest [`Aabb2d`] containing this [`BoundingCircle`].
533
#[inline(always)]
534
pub fn aabb_2d(&self) -> Aabb2d {
535
Aabb2d {
536
min: self.center - Vec2::splat(self.radius()),
537
max: self.center + Vec2::splat(self.radius()),
538
}
539
}
540
541
/// Finds the point on the bounding circle that is closest to the given `point`.
542
///
543
/// If the point is outside the circle, the returned point will be on the perimeter of the circle.
544
/// Otherwise, it will be inside the circle and returned as is.
545
#[inline(always)]
546
pub fn closest_point(&self, point: Vec2) -> Vec2 {
547
self.circle.closest_point(point - self.center) + self.center
548
}
549
}
550
551
impl BoundingVolume for BoundingCircle {
552
type Translation = Vec2;
553
type Rotation = Rot2;
554
type HalfSize = f32;
555
556
#[inline(always)]
557
fn center(&self) -> Self::Translation {
558
self.center
559
}
560
561
#[inline(always)]
562
fn half_size(&self) -> Self::HalfSize {
563
self.radius()
564
}
565
566
#[inline(always)]
567
fn visible_area(&self) -> f32 {
568
core::f32::consts::PI * self.radius() * self.radius()
569
}
570
571
#[inline(always)]
572
fn contains(&self, other: &Self) -> bool {
573
let diff = self.radius() - other.radius();
574
self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)
575
}
576
577
#[inline(always)]
578
fn merge(&self, other: &Self) -> Self {
579
let diff = other.center - self.center;
580
let length = diff.length();
581
if self.radius() >= length + other.radius() {
582
return *self;
583
}
584
if other.radius() >= length + self.radius() {
585
return *other;
586
}
587
let dir = diff / length;
588
Self::new(
589
(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
590
(length + self.radius() + other.radius()) / 2.,
591
)
592
}
593
594
#[inline(always)]
595
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
596
let amount = amount.into();
597
debug_assert!(amount >= 0.);
598
Self::new(self.center, self.radius() + amount)
599
}
600
601
#[inline(always)]
602
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
603
let amount = amount.into();
604
debug_assert!(amount >= 0.);
605
debug_assert!(self.radius() >= amount);
606
Self::new(self.center, self.radius() - amount)
607
}
608
609
#[inline(always)]
610
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
611
let scale = scale.into();
612
debug_assert!(scale >= 0.);
613
Self::new(self.center, self.radius() * scale)
614
}
615
616
#[inline(always)]
617
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
618
self.center += translation.into();
619
}
620
621
#[inline(always)]
622
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
623
let rotation: Rot2 = rotation.into();
624
self.center = rotation * self.center;
625
}
626
}
627
628
impl IntersectsVolume<Self> for BoundingCircle {
629
#[inline(always)]
630
fn intersects(&self, other: &Self) -> bool {
631
let center_distance_squared = self.center.distance_squared(other.center);
632
let radius_sum_squared = (self.radius() + other.radius()).squared();
633
center_distance_squared <= radius_sum_squared
634
}
635
}
636
637
impl IntersectsVolume<Aabb2d> for BoundingCircle {
638
#[inline(always)]
639
fn intersects(&self, aabb: &Aabb2d) -> bool {
640
aabb.intersects(self)
641
}
642
}
643
644
#[cfg(test)]
645
mod bounding_circle_tests {
646
use super::BoundingCircle;
647
use crate::{
648
bounding::{BoundingVolume, IntersectsVolume},
649
ops, Vec2,
650
};
651
652
#[test]
653
fn area() {
654
let circle = BoundingCircle::new(Vec2::ONE, 5.);
655
// Since this number is messy we check it with a higher threshold
656
assert!(ops::abs(circle.visible_area() - 78.5398) < 0.001);
657
}
658
659
#[test]
660
fn contains() {
661
let a = BoundingCircle::new(Vec2::ONE, 5.);
662
let b = BoundingCircle::new(Vec2::new(5.5, 1.), 1.);
663
assert!(!a.contains(&b));
664
let b = BoundingCircle::new(Vec2::new(1., -3.5), 0.5);
665
assert!(a.contains(&b));
666
}
667
668
#[test]
669
fn contains_identical() {
670
let a = BoundingCircle::new(Vec2::ONE, 5.);
671
assert!(a.contains(&a));
672
}
673
674
#[test]
675
fn merge() {
676
// When merging two circles that don't contain each other, we find a center position that
677
// contains both
678
let a = BoundingCircle::new(Vec2::ONE, 5.);
679
let b = BoundingCircle::new(Vec2::new(1., -4.), 1.);
680
let merged = a.merge(&b);
681
assert!((merged.center - Vec2::new(1., 0.5)).length() < f32::EPSILON);
682
assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);
683
assert!(merged.contains(&a));
684
assert!(merged.contains(&b));
685
assert!(!a.contains(&merged));
686
assert!(!b.contains(&merged));
687
688
// When one circle contains the other circle, we use the bigger circle
689
let b = BoundingCircle::new(Vec2::ZERO, 3.);
690
assert!(a.contains(&b));
691
let merged = a.merge(&b);
692
assert_eq!(merged.center, a.center);
693
assert_eq!(merged.radius(), a.radius());
694
695
// When two circles are at the same point, we use the bigger radius
696
let b = BoundingCircle::new(Vec2::ONE, 6.);
697
let merged = a.merge(&b);
698
assert_eq!(merged.center, a.center);
699
assert_eq!(merged.radius(), b.radius());
700
}
701
702
#[test]
703
fn merge_identical() {
704
let a = BoundingCircle::new(Vec2::ONE, 5.);
705
let merged = a.merge(&a);
706
assert_eq!(merged.center, a.center);
707
assert_eq!(merged.radius(), a.radius());
708
}
709
710
#[test]
711
fn grow() {
712
let a = BoundingCircle::new(Vec2::ONE, 5.);
713
let padded = a.grow(1.25);
714
assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);
715
assert!(padded.contains(&a));
716
assert!(!a.contains(&padded));
717
}
718
719
#[test]
720
fn shrink() {
721
let a = BoundingCircle::new(Vec2::ONE, 5.);
722
let shrunk = a.shrink(0.5);
723
assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);
724
assert!(a.contains(&shrunk));
725
assert!(!shrunk.contains(&a));
726
}
727
728
#[test]
729
fn scale_around_center() {
730
let a = BoundingCircle::new(Vec2::ONE, 5.);
731
let scaled = a.scale_around_center(2.);
732
assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);
733
assert!(!a.contains(&scaled));
734
assert!(scaled.contains(&a));
735
}
736
737
#[test]
738
fn transform() {
739
let a = BoundingCircle::new(Vec2::ONE, 5.0);
740
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
741
assert_eq!(
742
transformed.center,
743
Vec2::new(2.0, core::f32::consts::SQRT_2 - 2.0)
744
);
745
assert_eq!(transformed.radius(), 5.0);
746
}
747
748
#[test]
749
fn closest_point() {
750
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
751
assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
752
assert_eq!(
753
circle.closest_point(Vec2::NEG_ONE * 10.0),
754
Vec2::NEG_ONE.normalize()
755
);
756
assert_eq!(
757
circle.closest_point(Vec2::new(0.25, 0.1)),
758
Vec2::new(0.25, 0.1)
759
);
760
}
761
762
#[test]
763
fn intersect_bounding_circle() {
764
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
765
assert!(circle.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
766
assert!(circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.25, 1.0)));
767
assert!(circle.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.25, 1.0)));
768
assert!(!circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
769
}
770
}
771
772