1mod primitive_impls;
2
3use super::{BoundingVolume, IntersectsVolume};
4use crate::{
5 prelude::{Mat2, Rot2, Vec2},
6 FloatPow, Isometry2d,
7};
8
9#[cfg(feature = "bevy_reflect")]
10use bevy_reflect::Reflect;
11
12#[inline(always)]
14fn point_cloud_2d_center(points: &[Vec2]) -> Vec2 {
15 assert!(
16 !points.is_empty(),
17 "cannot compute the center of an empty set of points"
18 );
19
20 let denom = 1.0 / points.len() as f32;
21 points.iter().fold(Vec2::ZERO, |acc, point| acc + *point) * denom
22}
23
24pub trait Bounded2d {
26 fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d;
28 fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle;
30}
31
32#[doc(alias = "BoundingRectangle")]
34#[derive(Clone, Copy, Debug)]
35#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
36pub struct Aabb2d {
37 pub min: Vec2,
39 pub max: Vec2,
41}
42
43impl Aabb2d {
44 #[inline(always)]
46 pub fn new(center: Vec2, half_size: Vec2) -> Self {
47 debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0);
48 Self {
49 min: center - half_size,
50 max: center + half_size,
51 }
52 }
53
54 #[inline(always)]
61 pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> Aabb2d {
62 let isometry = isometry.into();
63
64 let mut iter = points.iter().map(|point| isometry.rotation * *point);
66
67 let first = iter
68 .next()
69 .expect("point cloud must contain at least one point for Aabb2d construction");
70
71 let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
72 (point.min(prev_min), point.max(prev_max))
73 });
74
75 Aabb2d {
76 min: min + isometry.translation,
77 max: max + isometry.translation,
78 }
79 }
80
81 #[inline(always)]
83 pub fn bounding_circle(&self) -> BoundingCircle {
84 let radius = self.min.distance(self.max) / 2.0;
85 BoundingCircle::new(self.center(), radius)
86 }
87
88 #[inline(always)]
93 pub fn closest_point(&self, point: Vec2) -> Vec2 {
94 point.clamp(self.min, self.max)
96 }
97}
98
99impl BoundingVolume for Aabb2d {
100 type Translation = Vec2;
101 type Rotation = Rot2;
102 type HalfSize = Vec2;
103
104 #[inline(always)]
105 fn center(&self) -> Self::Translation {
106 (self.min + self.max) / 2.
107 }
108
109 #[inline(always)]
110 fn half_size(&self) -> Self::HalfSize {
111 (self.max - self.min) / 2.
112 }
113
114 #[inline(always)]
115 fn visible_area(&self) -> f32 {
116 let b = self.max - self.min;
117 b.x * b.y
118 }
119
120 #[inline(always)]
121 fn contains(&self, other: &Self) -> bool {
122 other.min.x >= self.min.x
123 && other.min.y >= self.min.y
124 && other.max.x <= self.max.x
125 && other.max.y <= self.max.y
126 }
127
128 #[inline(always)]
129 fn merge(&self, other: &Self) -> Self {
130 Self {
131 min: self.min.min(other.min),
132 max: self.max.max(other.max),
133 }
134 }
135
136 #[inline(always)]
137 fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
138 let amount = amount.into();
139 let b = Self {
140 min: self.min - amount,
141 max: self.max + amount,
142 };
143 debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
144 b
145 }
146
147 #[inline(always)]
148 fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
149 let amount = amount.into();
150 let b = Self {
151 min: self.min + amount,
152 max: self.max - amount,
153 };
154 debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
155 b
156 }
157
158 #[inline(always)]
159 fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
160 let scale = scale.into();
161 let b = Self {
162 min: self.center() - (self.half_size() * scale),
163 max: self.center() + (self.half_size() * scale),
164 };
165 debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
166 b
167 }
168
169 #[inline(always)]
177 fn transformed_by(
178 mut self,
179 translation: impl Into<Self::Translation>,
180 rotation: impl Into<Self::Rotation>,
181 ) -> Self {
182 self.transform_by(translation, rotation);
183 self
184 }
185
186 #[inline(always)]
194 fn transform_by(
195 &mut self,
196 translation: impl Into<Self::Translation>,
197 rotation: impl Into<Self::Rotation>,
198 ) {
199 self.rotate_by(rotation);
200 self.translate_by(translation);
201 }
202
203 #[inline(always)]
204 fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
205 let translation = translation.into();
206 self.min += translation;
207 self.max += translation;
208 }
209
210 #[inline(always)]
218 fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
219 self.rotate_by(rotation);
220 self
221 }
222
223 #[inline(always)]
231 fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
232 let rotation: Rot2 = rotation.into();
233 let abs_rot_mat = Mat2::from_cols(
234 Vec2::new(rotation.cos, rotation.sin),
235 Vec2::new(rotation.sin, rotation.cos),
236 );
237 let half_size = abs_rot_mat * self.half_size();
238 *self = Self::new(rotation * self.center(), half_size);
239 }
240}
241
242impl IntersectsVolume<Self> for Aabb2d {
243 #[inline(always)]
244 fn intersects(&self, other: &Self) -> bool {
245 let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
246 let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
247 x_overlaps && y_overlaps
248 }
249}
250
251impl IntersectsVolume<BoundingCircle> for Aabb2d {
252 #[inline(always)]
253 fn intersects(&self, circle: &BoundingCircle) -> bool {
254 let closest_point = self.closest_point(circle.center);
255 let distance_squared = circle.center.distance_squared(closest_point);
256 let radius_squared = circle.radius().squared();
257 distance_squared <= radius_squared
258 }
259}
260
261#[cfg(test)]
262mod aabb2d_tests {
263 use super::Aabb2d;
264 use crate::{
265 bounding::{BoundingCircle, BoundingVolume, IntersectsVolume},
266 ops, Vec2,
267 };
268
269 #[test]
270 fn center() {
271 let aabb = Aabb2d {
272 min: Vec2::new(-0.5, -1.),
273 max: Vec2::new(1., 1.),
274 };
275 assert!((aabb.center() - Vec2::new(0.25, 0.)).length() < f32::EPSILON);
276 let aabb = Aabb2d {
277 min: Vec2::new(5., -10.),
278 max: Vec2::new(10., -5.),
279 };
280 assert!((aabb.center() - Vec2::new(7.5, -7.5)).length() < f32::EPSILON);
281 }
282
283 #[test]
284 fn half_size() {
285 let aabb = Aabb2d {
286 min: Vec2::new(-0.5, -1.),
287 max: Vec2::new(1., 1.),
288 };
289 let half_size = aabb.half_size();
290 assert!((half_size - Vec2::new(0.75, 1.)).length() < f32::EPSILON);
291 }
292
293 #[test]
294 fn area() {
295 let aabb = Aabb2d {
296 min: Vec2::new(-1., -1.),
297 max: Vec2::new(1., 1.),
298 };
299 assert!((aabb.visible_area() - 4.).abs() < f32::EPSILON);
300 let aabb = Aabb2d {
301 min: Vec2::new(0., 0.),
302 max: Vec2::new(1., 0.5),
303 };
304 assert!((aabb.visible_area() - 0.5).abs() < f32::EPSILON);
305 }
306
307 #[test]
308 fn contains() {
309 let a = Aabb2d {
310 min: Vec2::new(-1., -1.),
311 max: Vec2::new(1., 1.),
312 };
313 let b = Aabb2d {
314 min: Vec2::new(-2., -1.),
315 max: Vec2::new(1., 1.),
316 };
317 assert!(!a.contains(&b));
318 let b = Aabb2d {
319 min: Vec2::new(-0.25, -0.8),
320 max: Vec2::new(1., 1.),
321 };
322 assert!(a.contains(&b));
323 }
324
325 #[test]
326 fn merge() {
327 let a = Aabb2d {
328 min: Vec2::new(-1., -1.),
329 max: Vec2::new(1., 0.5),
330 };
331 let b = Aabb2d {
332 min: Vec2::new(-2., -0.5),
333 max: Vec2::new(0.75, 1.),
334 };
335 let merged = a.merge(&b);
336 assert!((merged.min - Vec2::new(-2., -1.)).length() < f32::EPSILON);
337 assert!((merged.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
338 assert!(merged.contains(&a));
339 assert!(merged.contains(&b));
340 assert!(!a.contains(&merged));
341 assert!(!b.contains(&merged));
342 }
343
344 #[test]
345 fn grow() {
346 let a = Aabb2d {
347 min: Vec2::new(-1., -1.),
348 max: Vec2::new(1., 1.),
349 };
350 let padded = a.grow(Vec2::ONE);
351 assert!((padded.min - Vec2::new(-2., -2.)).length() < f32::EPSILON);
352 assert!((padded.max - Vec2::new(2., 2.)).length() < f32::EPSILON);
353 assert!(padded.contains(&a));
354 assert!(!a.contains(&padded));
355 }
356
357 #[test]
358 fn shrink() {
359 let a = Aabb2d {
360 min: Vec2::new(-2., -2.),
361 max: Vec2::new(2., 2.),
362 };
363 let shrunk = a.shrink(Vec2::ONE);
364 assert!((shrunk.min - Vec2::new(-1., -1.)).length() < f32::EPSILON);
365 assert!((shrunk.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
366 assert!(a.contains(&shrunk));
367 assert!(!shrunk.contains(&a));
368 }
369
370 #[test]
371 fn scale_around_center() {
372 let a = Aabb2d {
373 min: Vec2::NEG_ONE,
374 max: Vec2::ONE,
375 };
376 let scaled = a.scale_around_center(Vec2::splat(2.));
377 assert!((scaled.min - Vec2::splat(-2.)).length() < f32::EPSILON);
378 assert!((scaled.max - Vec2::splat(2.)).length() < f32::EPSILON);
379 assert!(!a.contains(&scaled));
380 assert!(scaled.contains(&a));
381 }
382
383 #[test]
384 fn transform() {
385 let a = Aabb2d {
386 min: Vec2::new(-2.0, -2.0),
387 max: Vec2::new(2.0, 2.0),
388 };
389 let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
390 let half_length = ops::hypot(2.0, 2.0);
391 assert_eq!(
392 transformed.min,
393 Vec2::new(2.0 - half_length, -half_length - 2.0)
394 );
395 assert_eq!(
396 transformed.max,
397 Vec2::new(2.0 + half_length, half_length - 2.0)
398 );
399 }
400
401 #[test]
402 fn closest_point() {
403 let aabb = Aabb2d {
404 min: Vec2::NEG_ONE,
405 max: Vec2::ONE,
406 };
407 assert_eq!(aabb.closest_point(Vec2::X * 10.0), Vec2::X);
408 assert_eq!(aabb.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
409 assert_eq!(
410 aabb.closest_point(Vec2::new(0.25, 0.1)),
411 Vec2::new(0.25, 0.1)
412 );
413 }
414
415 #[test]
416 fn intersect_aabb() {
417 let aabb = Aabb2d {
418 min: Vec2::NEG_ONE,
419 max: Vec2::ONE,
420 };
421 assert!(aabb.intersects(&aabb));
422 assert!(aabb.intersects(&Aabb2d {
423 min: Vec2::new(0.5, 0.5),
424 max: Vec2::new(2.0, 2.0),
425 }));
426 assert!(aabb.intersects(&Aabb2d {
427 min: Vec2::new(-2.0, -2.0),
428 max: Vec2::new(-0.5, -0.5),
429 }));
430 assert!(!aabb.intersects(&Aabb2d {
431 min: Vec2::new(1.1, 0.0),
432 max: Vec2::new(2.0, 0.5),
433 }));
434 }
435
436 #[test]
437 fn intersect_bounding_circle() {
438 let aabb = Aabb2d {
439 min: Vec2::NEG_ONE,
440 max: Vec2::ONE,
441 };
442 assert!(aabb.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
443 assert!(aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
444 assert!(aabb.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.5, 1.0)));
445 assert!(!aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.75, 1.0)));
446 }
447}
448
449use crate::primitives::Circle;
450
451#[derive(Clone, Copy, Debug)]
453#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
454pub struct BoundingCircle {
455 pub center: Vec2,
457 pub circle: Circle,
459}
460
461impl BoundingCircle {
462 #[inline(always)]
464 pub fn new(center: Vec2, radius: f32) -> Self {
465 debug_assert!(radius >= 0.);
466 Self {
467 center,
468 circle: Circle { radius },
469 }
470 }
471
472 #[inline(always)]
477 pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> BoundingCircle {
478 let isometry = isometry.into();
479
480 let center = point_cloud_2d_center(points);
481 let mut radius_squared = 0.0;
482
483 for point in points {
484 let distance_squared = point.distance_squared(center);
486 if distance_squared > radius_squared {
487 radius_squared = distance_squared;
488 }
489 }
490
491 BoundingCircle::new(isometry * center, radius_squared.sqrt())
492 }
493
494 #[inline(always)]
496 pub fn radius(&self) -> f32 {
497 self.circle.radius
498 }
499
500 #[inline(always)]
502 pub fn aabb_2d(&self) -> Aabb2d {
503 Aabb2d {
504 min: self.center - Vec2::splat(self.radius()),
505 max: self.center + Vec2::splat(self.radius()),
506 }
507 }
508
509 #[inline(always)]
514 pub fn closest_point(&self, point: Vec2) -> Vec2 {
515 self.circle.closest_point(point - self.center) + self.center
516 }
517}
518
519impl BoundingVolume for BoundingCircle {
520 type Translation = Vec2;
521 type Rotation = Rot2;
522 type HalfSize = f32;
523
524 #[inline(always)]
525 fn center(&self) -> Self::Translation {
526 self.center
527 }
528
529 #[inline(always)]
530 fn half_size(&self) -> Self::HalfSize {
531 self.radius()
532 }
533
534 #[inline(always)]
535 fn visible_area(&self) -> f32 {
536 core::f32::consts::PI * self.radius() * self.radius()
537 }
538
539 #[inline(always)]
540 fn contains(&self, other: &Self) -> bool {
541 let diff = self.radius() - other.radius();
542 self.center.distance_squared(other.center) <= diff.squared().copysign(diff)
543 }
544
545 #[inline(always)]
546 fn merge(&self, other: &Self) -> Self {
547 let diff = other.center - self.center;
548 let length = diff.length();
549 if self.radius() >= length + other.radius() {
550 return *self;
551 }
552 if other.radius() >= length + self.radius() {
553 return *other;
554 }
555 let dir = diff / length;
556 Self::new(
557 (self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
558 (length + self.radius() + other.radius()) / 2.,
559 )
560 }
561
562 #[inline(always)]
563 fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
564 let amount = amount.into();
565 debug_assert!(amount >= 0.);
566 Self::new(self.center, self.radius() + amount)
567 }
568
569 #[inline(always)]
570 fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
571 let amount = amount.into();
572 debug_assert!(amount >= 0.);
573 debug_assert!(self.radius() >= amount);
574 Self::new(self.center, self.radius() - amount)
575 }
576
577 #[inline(always)]
578 fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
579 let scale = scale.into();
580 debug_assert!(scale >= 0.);
581 Self::new(self.center, self.radius() * scale)
582 }
583
584 #[inline(always)]
585 fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
586 self.center += translation.into();
587 }
588
589 #[inline(always)]
590 fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
591 let rotation: Rot2 = rotation.into();
592 self.center = rotation * self.center;
593 }
594}
595
596impl IntersectsVolume<Self> for BoundingCircle {
597 #[inline(always)]
598 fn intersects(&self, other: &Self) -> bool {
599 let center_distance_squared = self.center.distance_squared(other.center);
600 let radius_sum_squared = (self.radius() + other.radius()).squared();
601 center_distance_squared <= radius_sum_squared
602 }
603}
604
605impl IntersectsVolume<Aabb2d> for BoundingCircle {
606 #[inline(always)]
607 fn intersects(&self, aabb: &Aabb2d) -> bool {
608 aabb.intersects(self)
609 }
610}
611
612#[cfg(test)]
613mod bounding_circle_tests {
614 use super::BoundingCircle;
615 use crate::{
616 bounding::{BoundingVolume, IntersectsVolume},
617 Vec2,
618 };
619
620 #[test]
621 fn area() {
622 let circle = BoundingCircle::new(Vec2::ONE, 5.);
623 assert!((circle.visible_area() - 78.5398).abs() < 0.001);
625 }
626
627 #[test]
628 fn contains() {
629 let a = BoundingCircle::new(Vec2::ONE, 5.);
630 let b = BoundingCircle::new(Vec2::new(5.5, 1.), 1.);
631 assert!(!a.contains(&b));
632 let b = BoundingCircle::new(Vec2::new(1., -3.5), 0.5);
633 assert!(a.contains(&b));
634 }
635
636 #[test]
637 fn contains_identical() {
638 let a = BoundingCircle::new(Vec2::ONE, 5.);
639 assert!(a.contains(&a));
640 }
641
642 #[test]
643 fn merge() {
644 let a = BoundingCircle::new(Vec2::ONE, 5.);
647 let b = BoundingCircle::new(Vec2::new(1., -4.), 1.);
648 let merged = a.merge(&b);
649 assert!((merged.center - Vec2::new(1., 0.5)).length() < f32::EPSILON);
650 assert!((merged.radius() - 5.5).abs() < f32::EPSILON);
651 assert!(merged.contains(&a));
652 assert!(merged.contains(&b));
653 assert!(!a.contains(&merged));
654 assert!(!b.contains(&merged));
655
656 let b = BoundingCircle::new(Vec2::ZERO, 3.);
658 assert!(a.contains(&b));
659 let merged = a.merge(&b);
660 assert_eq!(merged.center, a.center);
661 assert_eq!(merged.radius(), a.radius());
662
663 let b = BoundingCircle::new(Vec2::ONE, 6.);
665 let merged = a.merge(&b);
666 assert_eq!(merged.center, a.center);
667 assert_eq!(merged.radius(), b.radius());
668 }
669
670 #[test]
671 fn merge_identical() {
672 let a = BoundingCircle::new(Vec2::ONE, 5.);
673 let merged = a.merge(&a);
674 assert_eq!(merged.center, a.center);
675 assert_eq!(merged.radius(), a.radius());
676 }
677
678 #[test]
679 fn grow() {
680 let a = BoundingCircle::new(Vec2::ONE, 5.);
681 let padded = a.grow(1.25);
682 assert!((padded.radius() - 6.25).abs() < f32::EPSILON);
683 assert!(padded.contains(&a));
684 assert!(!a.contains(&padded));
685 }
686
687 #[test]
688 fn shrink() {
689 let a = BoundingCircle::new(Vec2::ONE, 5.);
690 let shrunk = a.shrink(0.5);
691 assert!((shrunk.radius() - 4.5).abs() < f32::EPSILON);
692 assert!(a.contains(&shrunk));
693 assert!(!shrunk.contains(&a));
694 }
695
696 #[test]
697 fn scale_around_center() {
698 let a = BoundingCircle::new(Vec2::ONE, 5.);
699 let scaled = a.scale_around_center(2.);
700 assert!((scaled.radius() - 10.).abs() < f32::EPSILON);
701 assert!(!a.contains(&scaled));
702 assert!(scaled.contains(&a));
703 }
704
705 #[test]
706 fn transform() {
707 let a = BoundingCircle::new(Vec2::ONE, 5.0);
708 let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
709 assert_eq!(
710 transformed.center,
711 Vec2::new(2.0, core::f32::consts::SQRT_2 - 2.0)
712 );
713 assert_eq!(transformed.radius(), 5.0);
714 }
715
716 #[test]
717 fn closest_point() {
718 let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
719 assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
720 assert_eq!(
721 circle.closest_point(Vec2::NEG_ONE * 10.0),
722 Vec2::NEG_ONE.normalize()
723 );
724 assert_eq!(
725 circle.closest_point(Vec2::new(0.25, 0.1)),
726 Vec2::new(0.25, 0.1)
727 );
728 }
729
730 #[test]
731 fn intersect_bounding_circle() {
732 let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
733 assert!(circle.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
734 assert!(circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.25, 1.0)));
735 assert!(circle.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.25, 1.0)));
736 assert!(!circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
737 }
738}