1mod primitive_impls;
2
3use super::{BoundingVolume, IntersectsVolume};
4use crate::{
5 ops,
6 prelude::{Mat2, Rot2, Vec2},
7 FloatPow, Isometry2d,
8};
9
10#[cfg(feature = "bevy_reflect")]
11use bevy_reflect::Reflect;
12#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
13use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
14#[cfg(feature = "serialize")]
15use serde::{Deserialize, Serialize};
16
17#[inline(always)]
19fn 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
29pub trait Bounded2d {
31 fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d;
33 fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle;
35}
36
37#[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)]
50pub struct Aabb2d {
51 pub min: Vec2,
53 pub max: Vec2,
55}
56
57impl Aabb2d {
58 #[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 #[inline(always)]
75 pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> Aabb2d {
76 let isometry = isometry.into();
77
78 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 #[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 #[inline(always)]
107 pub fn closest_point(&self, point: Vec2) -> Vec2 {
108 point.clamp(self.min, self.max)
110 }
111}
112
113impl 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;
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 #[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 #[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 #[inline(always)]
232 fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
233 self.rotate_by(rotation);
234 self
235 }
236
237 #[inline(always)]
245 fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
246 let rotation: Rot2 = rotation.into();
247 let abs_rot_mat = Mat2::from_cols(
248 Vec2::new(rotation.cos, rotation.sin),
249 Vec2::new(rotation.sin, rotation.cos),
250 );
251 let half_size = abs_rot_mat * self.half_size();
252 *self = Self::new(rotation * self.center(), half_size);
253 }
254}
255
256impl IntersectsVolume<Self> for Aabb2d {
257 #[inline(always)]
258 fn intersects(&self, other: &Self) -> bool {
259 let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
260 let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
261 x_overlaps && y_overlaps
262 }
263}
264
265impl IntersectsVolume<BoundingCircle> for Aabb2d {
266 #[inline(always)]
267 fn intersects(&self, circle: &BoundingCircle) -> bool {
268 let closest_point = self.closest_point(circle.center);
269 let distance_squared = circle.center.distance_squared(closest_point);
270 let radius_squared = circle.radius().squared();
271 distance_squared <= radius_squared
272 }
273}
274
275#[cfg(test)]
276mod aabb2d_tests {
277 use super::Aabb2d;
278 use crate::{
279 bounding::{BoundingCircle, BoundingVolume, IntersectsVolume},
280 ops, Vec2,
281 };
282
283 #[test]
284 fn center() {
285 let aabb = Aabb2d {
286 min: Vec2::new(-0.5, -1.),
287 max: Vec2::new(1., 1.),
288 };
289 assert!((aabb.center() - Vec2::new(0.25, 0.)).length() < f32::EPSILON);
290 let aabb = Aabb2d {
291 min: Vec2::new(5., -10.),
292 max: Vec2::new(10., -5.),
293 };
294 assert!((aabb.center() - Vec2::new(7.5, -7.5)).length() < f32::EPSILON);
295 }
296
297 #[test]
298 fn half_size() {
299 let aabb = Aabb2d {
300 min: Vec2::new(-0.5, -1.),
301 max: Vec2::new(1., 1.),
302 };
303 let half_size = aabb.half_size();
304 assert!((half_size - Vec2::new(0.75, 1.)).length() < f32::EPSILON);
305 }
306
307 #[test]
308 fn area() {
309 let aabb = Aabb2d {
310 min: Vec2::new(-1., -1.),
311 max: Vec2::new(1., 1.),
312 };
313 assert!(ops::abs(aabb.visible_area() - 4.) < f32::EPSILON);
314 let aabb = Aabb2d {
315 min: Vec2::new(0., 0.),
316 max: Vec2::new(1., 0.5),
317 };
318 assert!(ops::abs(aabb.visible_area() - 0.5) < f32::EPSILON);
319 }
320
321 #[test]
322 fn contains() {
323 let a = Aabb2d {
324 min: Vec2::new(-1., -1.),
325 max: Vec2::new(1., 1.),
326 };
327 let b = Aabb2d {
328 min: Vec2::new(-2., -1.),
329 max: Vec2::new(1., 1.),
330 };
331 assert!(!a.contains(&b));
332 let b = Aabb2d {
333 min: Vec2::new(-0.25, -0.8),
334 max: Vec2::new(1., 1.),
335 };
336 assert!(a.contains(&b));
337 }
338
339 #[test]
340 fn merge() {
341 let a = Aabb2d {
342 min: Vec2::new(-1., -1.),
343 max: Vec2::new(1., 0.5),
344 };
345 let b = Aabb2d {
346 min: Vec2::new(-2., -0.5),
347 max: Vec2::new(0.75, 1.),
348 };
349 let merged = a.merge(&b);
350 assert!((merged.min - Vec2::new(-2., -1.)).length() < f32::EPSILON);
351 assert!((merged.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
352 assert!(merged.contains(&a));
353 assert!(merged.contains(&b));
354 assert!(!a.contains(&merged));
355 assert!(!b.contains(&merged));
356 }
357
358 #[test]
359 fn grow() {
360 let a = Aabb2d {
361 min: Vec2::new(-1., -1.),
362 max: Vec2::new(1., 1.),
363 };
364 let padded = a.grow(Vec2::ONE);
365 assert!((padded.min - Vec2::new(-2., -2.)).length() < f32::EPSILON);
366 assert!((padded.max - Vec2::new(2., 2.)).length() < f32::EPSILON);
367 assert!(padded.contains(&a));
368 assert!(!a.contains(&padded));
369 }
370
371 #[test]
372 fn shrink() {
373 let a = Aabb2d {
374 min: Vec2::new(-2., -2.),
375 max: Vec2::new(2., 2.),
376 };
377 let shrunk = a.shrink(Vec2::ONE);
378 assert!((shrunk.min - Vec2::new(-1., -1.)).length() < f32::EPSILON);
379 assert!((shrunk.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
380 assert!(a.contains(&shrunk));
381 assert!(!shrunk.contains(&a));
382 }
383
384 #[test]
385 fn scale_around_center() {
386 let a = Aabb2d {
387 min: Vec2::NEG_ONE,
388 max: Vec2::ONE,
389 };
390 let scaled = a.scale_around_center(Vec2::splat(2.));
391 assert!((scaled.min - Vec2::splat(-2.)).length() < f32::EPSILON);
392 assert!((scaled.max - Vec2::splat(2.)).length() < f32::EPSILON);
393 assert!(!a.contains(&scaled));
394 assert!(scaled.contains(&a));
395 }
396
397 #[test]
398 fn transform() {
399 let a = Aabb2d {
400 min: Vec2::new(-2.0, -2.0),
401 max: Vec2::new(2.0, 2.0),
402 };
403 let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
404 let half_length = ops::hypot(2.0, 2.0);
405 assert_eq!(
406 transformed.min,
407 Vec2::new(2.0 - half_length, -half_length - 2.0)
408 );
409 assert_eq!(
410 transformed.max,
411 Vec2::new(2.0 + half_length, half_length - 2.0)
412 );
413 }
414
415 #[test]
416 fn closest_point() {
417 let aabb = Aabb2d {
418 min: Vec2::NEG_ONE,
419 max: Vec2::ONE,
420 };
421 assert_eq!(aabb.closest_point(Vec2::X * 10.0), Vec2::X);
422 assert_eq!(aabb.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
423 assert_eq!(
424 aabb.closest_point(Vec2::new(0.25, 0.1)),
425 Vec2::new(0.25, 0.1)
426 );
427 }
428
429 #[test]
430 fn intersect_aabb() {
431 let aabb = Aabb2d {
432 min: Vec2::NEG_ONE,
433 max: Vec2::ONE,
434 };
435 assert!(aabb.intersects(&aabb));
436 assert!(aabb.intersects(&Aabb2d {
437 min: Vec2::new(0.5, 0.5),
438 max: Vec2::new(2.0, 2.0),
439 }));
440 assert!(aabb.intersects(&Aabb2d {
441 min: Vec2::new(-2.0, -2.0),
442 max: Vec2::new(-0.5, -0.5),
443 }));
444 assert!(!aabb.intersects(&Aabb2d {
445 min: Vec2::new(1.1, 0.0),
446 max: Vec2::new(2.0, 0.5),
447 }));
448 }
449
450 #[test]
451 fn intersect_bounding_circle() {
452 let aabb = Aabb2d {
453 min: Vec2::NEG_ONE,
454 max: Vec2::ONE,
455 };
456 assert!(aabb.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
457 assert!(aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
458 assert!(aabb.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.5, 1.0)));
459 assert!(!aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.75, 1.0)));
460 }
461}
462
463use crate::primitives::Circle;
464
465#[derive(Clone, Copy, Debug, PartialEq)]
467#[cfg_attr(
468 feature = "bevy_reflect",
469 derive(Reflect),
470 reflect(Debug, PartialEq, Clone)
471)]
472#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
473#[cfg_attr(
474 all(feature = "serialize", feature = "bevy_reflect"),
475 reflect(Serialize, Deserialize)
476)]
477pub struct BoundingCircle {
478 pub center: Vec2,
480 pub circle: Circle,
482}
483
484impl BoundingCircle {
485 #[inline(always)]
487 pub fn new(center: Vec2, radius: f32) -> Self {
488 debug_assert!(radius >= 0.);
489 Self {
490 center,
491 circle: Circle { radius },
492 }
493 }
494
495 #[inline(always)]
500 pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> BoundingCircle {
501 let isometry = isometry.into();
502
503 let center = point_cloud_2d_center(points);
504 let mut radius_squared = 0.0;
505
506 for point in points {
507 let distance_squared = point.distance_squared(center);
509 if distance_squared > radius_squared {
510 radius_squared = distance_squared;
511 }
512 }
513
514 BoundingCircle::new(isometry * center, ops::sqrt(radius_squared))
515 }
516
517 #[inline(always)]
519 pub fn radius(&self) -> f32 {
520 self.circle.radius
521 }
522
523 #[inline(always)]
525 pub fn aabb_2d(&self) -> Aabb2d {
526 Aabb2d {
527 min: self.center - Vec2::splat(self.radius()),
528 max: self.center + Vec2::splat(self.radius()),
529 }
530 }
531
532 #[inline(always)]
537 pub fn closest_point(&self, point: Vec2) -> Vec2 {
538 self.circle.closest_point(point - self.center) + self.center
539 }
540}
541
542impl BoundingVolume for BoundingCircle {
543 type Translation = Vec2;
544 type Rotation = Rot2;
545 type HalfSize = f32;
546
547 #[inline(always)]
548 fn center(&self) -> Self::Translation {
549 self.center
550 }
551
552 #[inline(always)]
553 fn half_size(&self) -> Self::HalfSize {
554 self.radius()
555 }
556
557 #[inline(always)]
558 fn visible_area(&self) -> f32 {
559 core::f32::consts::PI * self.radius() * self.radius()
560 }
561
562 #[inline(always)]
563 fn contains(&self, other: &Self) -> bool {
564 let diff = self.radius() - other.radius();
565 self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)
566 }
567
568 #[inline(always)]
569 fn merge(&self, other: &Self) -> Self {
570 let diff = other.center - self.center;
571 let length = diff.length();
572 if self.radius() >= length + other.radius() {
573 return *self;
574 }
575 if other.radius() >= length + self.radius() {
576 return *other;
577 }
578 let dir = diff / length;
579 Self::new(
580 (self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
581 (length + self.radius() + other.radius()) / 2.,
582 )
583 }
584
585 #[inline(always)]
586 fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
587 let amount = amount.into();
588 debug_assert!(amount >= 0.);
589 Self::new(self.center, self.radius() + amount)
590 }
591
592 #[inline(always)]
593 fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
594 let amount = amount.into();
595 debug_assert!(amount >= 0.);
596 debug_assert!(self.radius() >= amount);
597 Self::new(self.center, self.radius() - amount)
598 }
599
600 #[inline(always)]
601 fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
602 let scale = scale.into();
603 debug_assert!(scale >= 0.);
604 Self::new(self.center, self.radius() * scale)
605 }
606
607 #[inline(always)]
608 fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
609 self.center += translation.into();
610 }
611
612 #[inline(always)]
613 fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
614 let rotation: Rot2 = rotation.into();
615 self.center = rotation * self.center;
616 }
617}
618
619impl IntersectsVolume<Self> for BoundingCircle {
620 #[inline(always)]
621 fn intersects(&self, other: &Self) -> bool {
622 let center_distance_squared = self.center.distance_squared(other.center);
623 let radius_sum_squared = (self.radius() + other.radius()).squared();
624 center_distance_squared <= radius_sum_squared
625 }
626}
627
628impl IntersectsVolume<Aabb2d> for BoundingCircle {
629 #[inline(always)]
630 fn intersects(&self, aabb: &Aabb2d) -> bool {
631 aabb.intersects(self)
632 }
633}
634
635#[cfg(test)]
636mod bounding_circle_tests {
637 use super::BoundingCircle;
638 use crate::{
639 bounding::{BoundingVolume, IntersectsVolume},
640 ops, Vec2,
641 };
642
643 #[test]
644 fn area() {
645 let circle = BoundingCircle::new(Vec2::ONE, 5.);
646 assert!(ops::abs(circle.visible_area() - 78.5398) < 0.001);
648 }
649
650 #[test]
651 fn contains() {
652 let a = BoundingCircle::new(Vec2::ONE, 5.);
653 let b = BoundingCircle::new(Vec2::new(5.5, 1.), 1.);
654 assert!(!a.contains(&b));
655 let b = BoundingCircle::new(Vec2::new(1., -3.5), 0.5);
656 assert!(a.contains(&b));
657 }
658
659 #[test]
660 fn contains_identical() {
661 let a = BoundingCircle::new(Vec2::ONE, 5.);
662 assert!(a.contains(&a));
663 }
664
665 #[test]
666 fn merge() {
667 let a = BoundingCircle::new(Vec2::ONE, 5.);
670 let b = BoundingCircle::new(Vec2::new(1., -4.), 1.);
671 let merged = a.merge(&b);
672 assert!((merged.center - Vec2::new(1., 0.5)).length() < f32::EPSILON);
673 assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);
674 assert!(merged.contains(&a));
675 assert!(merged.contains(&b));
676 assert!(!a.contains(&merged));
677 assert!(!b.contains(&merged));
678
679 let b = BoundingCircle::new(Vec2::ZERO, 3.);
681 assert!(a.contains(&b));
682 let merged = a.merge(&b);
683 assert_eq!(merged.center, a.center);
684 assert_eq!(merged.radius(), a.radius());
685
686 let b = BoundingCircle::new(Vec2::ONE, 6.);
688 let merged = a.merge(&b);
689 assert_eq!(merged.center, a.center);
690 assert_eq!(merged.radius(), b.radius());
691 }
692
693 #[test]
694 fn merge_identical() {
695 let a = BoundingCircle::new(Vec2::ONE, 5.);
696 let merged = a.merge(&a);
697 assert_eq!(merged.center, a.center);
698 assert_eq!(merged.radius(), a.radius());
699 }
700
701 #[test]
702 fn grow() {
703 let a = BoundingCircle::new(Vec2::ONE, 5.);
704 let padded = a.grow(1.25);
705 assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);
706 assert!(padded.contains(&a));
707 assert!(!a.contains(&padded));
708 }
709
710 #[test]
711 fn shrink() {
712 let a = BoundingCircle::new(Vec2::ONE, 5.);
713 let shrunk = a.shrink(0.5);
714 assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);
715 assert!(a.contains(&shrunk));
716 assert!(!shrunk.contains(&a));
717 }
718
719 #[test]
720 fn scale_around_center() {
721 let a = BoundingCircle::new(Vec2::ONE, 5.);
722 let scaled = a.scale_around_center(2.);
723 assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);
724 assert!(!a.contains(&scaled));
725 assert!(scaled.contains(&a));
726 }
727
728 #[test]
729 fn transform() {
730 let a = BoundingCircle::new(Vec2::ONE, 5.0);
731 let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
732 assert_eq!(
733 transformed.center,
734 Vec2::new(2.0, core::f32::consts::SQRT_2 - 2.0)
735 );
736 assert_eq!(transformed.radius(), 5.0);
737 }
738
739 #[test]
740 fn closest_point() {
741 let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
742 assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
743 assert_eq!(
744 circle.closest_point(Vec2::NEG_ONE * 10.0),
745 Vec2::NEG_ONE.normalize()
746 );
747 assert_eq!(
748 circle.closest_point(Vec2::new(0.25, 0.1)),
749 Vec2::new(0.25, 0.1)
750 );
751 }
752
753 #[test]
754 fn intersect_bounding_circle() {
755 let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
756 assert!(circle.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
757 assert!(circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.25, 1.0)));
758 assert!(circle.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.25, 1.0)));
759 assert!(!circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
760 }
761}