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