1use bevy_ecs::prelude::*;
2use bevy_math::prelude::*;
3use bevy_transform::components::GlobalTransform;
4use core::fmt;
5use std::any::type_name;
6
7use crate::{
8 CollisionRect, CollisionRotatedRect,
9 collision::{Collision, CollisionQuery, DynCollision, Relation, UpdateCollision},
10};
11
12#[derive(Component, Clone)]
21pub struct CollisionCircle<const ID: usize = 0> {
22 pub(crate) center: Vec2,
23 scale: f32,
24 init_radius: f32,
25}
26
27impl<const ID: usize> fmt::Debug for CollisionCircle<ID> {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 write!(
30 f,
31 "{}: center = ({}, {}); r = {} x {} = {}",
32 type_name::<Self>(),
33 self.center.x,
34 self.center.y,
35 self.init_radius,
36 self.scale,
37 self.init_radius * self.scale
38 )
39 }
40}
41
42impl<const ID: usize> From<&CollisionCircle<ID>> for CollisionCircle<0> {
43 fn from(value: &CollisionCircle<ID>) -> Self {
46 Self {
47 center: value.center,
48 scale: value.scale,
49 init_radius: value.init_radius,
50 }
51 }
52}
53
54impl CollisionCircle {
55 pub fn new(center: Vec2, radius: f32) -> Self {
61 Self {
62 center,
63 scale: 1.,
64 init_radius: radius,
65 }
66 }
67}
68
69impl<const ID: usize> CollisionCircle<ID> {
70 pub fn new_id(center: Vec2, radius: f32) -> Self {
72 Self {
73 center,
74 scale: 1.,
75 init_radius: radius,
76 }
77 }
78
79 fn radius(&self) -> f32 {
80 self.init_radius * self.scale
81 }
82
83 pub fn set_init_radius(&mut self, radius: f32) {
85 self.init_radius = radius;
86 }
87}
88
89impl Collision<CollisionRect> for CollisionCircle {
90 fn detect(&self, rect: &CollisionRect) -> Relation {
91 let rect_max = rect.max();
92 let rect_min = rect.min();
93 let i = rect_max - rect.center; let center = (self.center - rect.center).abs(); let ds = [
96 (self.center - rect_max).length(),
97 (self.center - Vec2::new(rect_min.x, rect_max.y)).length(),
98 (self.center - rect_min).length(),
99 (self.center - Vec2::new(rect_max.x, rect_min.y)).length(),
100 ];
101 let radius = self.radius();
102 if ds.iter().all(|&d| d < radius) {
103 Relation::Contain
104 } else if center.x > i.x + radius || center.y > i.y + radius {
105 Relation::Disjoint
106 } else if center.x < i.x - radius && center.y < i.y - radius {
107 Relation::Contained
108 } else if center.x > i.x && center.y > i.y && (i - center).length() > radius {
109 Relation::Disjoint
110 } else {
111 Relation::Overlap
112 }
113 }
114}
115
116impl Collision<CollisionRotatedRect> for CollisionCircle {
117 fn detect(&self, r_rect: &CollisionRotatedRect) -> Relation {
118 let r_rect_size = r_rect.init_size * r_rect.scale;
119 let i = r_rect_size / 2.; let center = (r_rect.isometric.inverse() * self.center).abs(); let ds = [
122 (center - i).length(),
123 (center - Vec2::new(-i.x, i.y)).length(),
124 (center - Vec2::new(-i.x, -i.y)).length(),
125 (center - Vec2::new(i.x, -i.y)).length(),
126 ];
127 let radius = self.radius();
128 if ds.iter().all(|&d| d < radius) {
129 Relation::Contain
130 } else if center.x > i.x + radius || center.y > i.y + radius {
131 Relation::Disjoint
132 } else if center.x < i.x - radius && center.y < i.y - radius {
133 Relation::Contained
134 } else if center.x > i.x && center.y > i.y && ds[0] > radius {
135 Relation::Disjoint
136 } else {
137 Relation::Overlap
138 }
139 }
140}
141
142impl Collision<CollisionCircle> for CollisionCircle {
143 fn detect(&self, circle: &CollisionCircle) -> Relation {
144 let d = (self.center - circle.center).length();
145 let self_r = self.radius();
146 let circle_r = circle.radius();
147 if d + circle_r < self_r {
148 Relation::Contain
149 } else if d + self_r < circle_r {
150 Relation::Contained
151 } else if d > self_r + circle_r {
152 Relation::Disjoint
153 } else {
154 Relation::Overlap
155 }
156 }
157}
158
159impl<const ID: usize> UpdateCollision<GlobalTransform> for CollisionCircle<ID> {
160 fn update() -> impl FnOnce(Mut<Self>, &GlobalTransform) {
161 |mut circle, global_transform| {
162 circle.center = global_transform.translation().truncate();
163 debug_assert_eq!(
164 global_transform.scale().x, global_transform.scale().y,
165 "Do not perform scaling with different x and y values,
166 it will cause the circle to be an ellipse, and the collision detection will be incorrect."
167 );
168 circle.scale = global_transform.scale().x;
169 }
170 }
171}
172
173impl CollisionQuery for CollisionCircle {
174 fn query(&self, obj: &dyn DynCollision) -> Relation {
175 match obj.detect(self) {
176 Relation::Contain => Relation::Contained,
177 Relation::Contained => Relation::Contain,
178 r => r,
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use bevy_math::{Rect, Rot2};
186
187 use super::*;
188 use std::f32::consts::*;
189
190 #[test]
191 fn collision_circle_detect() {
192 let circle = CollisionCircle::new(Vec2::ZERO, 1.);
193 let contain = CollisionCircle::new(Vec2::ZERO, 0.5);
194 let contained = CollisionCircle::new(Vec2::ZERO, 2.);
195 let disjoint = CollisionCircle::new(Vec2::new(2., 2.), 1.);
196 let overlap = CollisionCircle::new(Vec2::new(0.5, 0.5), 1.);
197 assert_eq!(circle.detect(&contain), Relation::Contain);
198 assert_eq!(circle.detect(&contained), Relation::Contained);
199 assert_eq!(circle.detect(&disjoint), Relation::Disjoint);
200 assert_eq!(circle.detect(&overlap), Relation::Overlap);
201 }
202
203 #[test]
204 fn collision_circle_detect_rect() {
205 let circle = CollisionCircle::new(Vec2::ZERO, 1.);
206 let contain = CollisionRect::from(Rect::from_center_size(Vec2::ZERO, Vec2::ONE / 2.));
207 let contained = CollisionRect::from(Rect::from_center_size(Vec2::ZERO, Vec2::ONE * 3.));
208 let disjoint = CollisionRect::from(Rect::from_center_size(Vec2::new(2., 2.), Vec2::ONE));
209 let overlap = CollisionRect::from(Rect::from_center_size(Vec2::new(0.5, 0.5), Vec2::ONE));
210 assert_eq!(circle.detect(&contain), Relation::Contain);
211 assert_eq!(circle.detect(&contained), Relation::Contained);
212 assert_eq!(circle.detect(&disjoint), Relation::Disjoint);
213 assert_eq!(circle.detect(&overlap), Relation::Overlap);
214 }
215
216 #[test]
217 fn collision_circal_detect_rotated_rect() {
218 let circle = CollisionCircle::new(Vec2::ZERO, 1.);
219 let contain = CollisionRotatedRect::new(
220 Rect::from_center_size(Vec2::ZERO, Vec2::ONE / 2.),
221 Rot2::radians(FRAC_PI_4),
222 );
223 let contained = CollisionRotatedRect::new(
224 Rect::from_center_size(Vec2::ZERO, Vec2::ONE * 3.),
225 Rot2::radians(FRAC_PI_4),
226 );
227 let disjoint = CollisionRotatedRect::new(
228 Rect::from_center_size(Vec2::new(2., 2.), Vec2::ONE),
229 Rot2::radians(FRAC_PI_4),
230 );
231 let overlap = CollisionRotatedRect::new(
232 Rect::from_center_size(Vec2::new(0.5, 0.5), Vec2::ONE),
233 Rot2::radians(FRAC_PI_4),
234 );
235 assert_eq!(circle.detect(&contain), Relation::Contain);
236 assert_eq!(circle.detect(&contained), Relation::Contained);
237 assert_eq!(circle.detect(&disjoint), Relation::Disjoint);
238 assert_eq!(circle.detect(&overlap), Relation::Overlap);
239 let circle = CollisionCircle::new(Vec2::ZERO, 1.);
240 let contain = CollisionRotatedRect::new(
241 Rect::from_center_size(Vec2::new(0.9, 0.), Vec2::new(0.2, 0.001)),
242 Rot2::radians(FRAC_PI_2),
243 );
244 assert_eq!(circle.detect(&contain), Relation::Contain);
245 }
246}