1use crate::{
2 CollisionCircle, CollisionRect,
3 collision::{Collision, CollisionQuery, DynCollision, Relation, UpdateCollision},
4};
5use bevy_ecs::prelude::*;
6#[cfg(feature = "sprite")]
7use bevy_log::warn;
8use bevy_math::prelude::*;
9#[cfg(feature = "sprite")]
10use bevy_sprite::Sprite;
11use bevy_transform::components::GlobalTransform;
12use core::fmt;
13use std::any::type_name;
14
15#[derive(Component, Clone)]
20pub struct CollisionRotatedRect<const ID: usize = 0> {
21 pub(crate) init_size: Vec2,
22 pub(crate) scale: Vec2,
23 pub(crate) isometric: Isometry2d,
24}
25
26impl<const ID: usize> fmt::Debug for CollisionRotatedRect<ID> {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(
29 f,
30 "{}: center = ({}, {}); size = ({} x {}) x ({} x {}) = {} x {}; totation = {}",
31 type_name::<Self>(),
32 self.isometric.translation.x,
33 self.isometric.translation.y,
34 self.init_size.x,
35 self.scale.x,
36 self.init_size.y,
37 self.scale.y,
38 self.init_size.x * self.scale.x,
39 self.init_size.y * self.scale.y,
40 self.isometric.rotation.as_degrees()
41 )
42 }
43}
44
45impl<const ID: usize> From<&CollisionRotatedRect<ID>> for CollisionRotatedRect<0> {
46 fn from(value: &CollisionRotatedRect<ID>) -> Self {
49 Self {
50 init_size: value.init_size,
51 scale: value.scale,
52 isometric: value.isometric,
53 }
54 }
55}
56
57impl From<Rect> for CollisionRotatedRect {
58 fn from(rect: Rect) -> Self {
62 Self {
63 init_size: rect.size(),
64 scale: Vec2::ONE,
65 isometric: Isometry2d::new(rect.center(), Rot2::IDENTITY),
66 }
67 }
68}
69
70impl CollisionRotatedRect {
71 pub fn new(rect: Rect, rotation: Rot2) -> Self {
77 Self {
78 init_size: rect.size(),
79 scale: Vec2::ONE,
80 isometric: Isometry2d::new(rect.center(), rotation),
81 }
82 }
83}
84
85impl<const ID: usize> CollisionRotatedRect<ID> {
86 pub fn new_id(rect: Rect, rotation: Rot2) -> Self {
88 Self {
89 init_size: rect.size(),
90 scale: Vec2::ONE,
91 isometric: Isometry2d::new(rect.center(), rotation),
92 }
93 }
94
95 pub fn set_init_size(&mut self, size: Vec2) {
97 self.init_size = size;
98 }
99}
100
101impl Collision<CollisionRect> for CollisionRotatedRect {
102 fn detect(&self, rect: &CollisionRect) -> Relation {
103 match Collision::detect(rect, self) {
104 Relation::Contain => Relation::Contained,
105 Relation::Contained => Relation::Contain,
106 r => r,
107 }
108 }
109}
110
111impl Collision<CollisionRotatedRect> for CollisionRotatedRect {
112 fn detect(&self, r_rect: &CollisionRotatedRect) -> Relation {
113 let self_half_size = self.init_size * self.scale / 2.;
114 let r_half_size = r_rect.init_size * r_rect.scale / 2.;
115 let mut vetex = [
116 Vec2::new(r_half_size.x, r_half_size.y),
117 Vec2::new(-r_half_size.x, r_half_size.y),
118 Vec2::new(-r_half_size.x, -r_half_size.y),
119 Vec2::new(r_half_size.x, -r_half_size.y),
120 ];
121 let (mut min_x, mut max_x, mut min_y, mut max_y) = (
122 f32::INFINITY,
123 f32::NEG_INFINITY,
124 f32::INFINITY,
125 f32::NEG_INFINITY,
126 );
127 let inv = self.isometric.inverse();
128 for v in vetex.iter_mut() {
129 *v = inv * r_rect.isometric * *v;
130 min_x = min_x.min(v.x);
131 max_x = max_x.max(v.x);
132 min_y = min_y.min(v.y);
133 max_y = max_y.max(v.y);
134 }
135 if self_half_size.x < min_x
136 || -self_half_size.x > max_x
137 || self_half_size.y < min_y
138 || -self_half_size.y > max_y
139 {
140 return Relation::Disjoint;
141 } else if -self_half_size.x < min_x
142 && max_x < self_half_size.x
143 && -self_half_size.y < min_y
144 && max_y < self_half_size.y
145 {
146 return Relation::Contain;
147 }
148 let mut vetex = [
149 Vec2::new(self_half_size.x, self_half_size.y),
150 Vec2::new(-self_half_size.x, self_half_size.y),
151 Vec2::new(-self_half_size.x, -self_half_size.y),
152 Vec2::new(self_half_size.x, -self_half_size.y),
153 ];
154 let (mut min_x, mut max_x, mut min_y, mut max_y) = (
155 f32::INFINITY,
156 f32::NEG_INFINITY,
157 f32::INFINITY,
158 f32::NEG_INFINITY,
159 );
160 let inv = r_rect.isometric.inverse();
161 for v in vetex.iter_mut() {
162 *v = inv * self.isometric * *v;
163 min_x = min_x.min(v.x);
164 max_x = max_x.max(v.x);
165 min_y = min_y.min(v.y);
166 max_y = max_y.max(v.y);
167 }
168 if r_half_size.x < min_x
169 || -r_half_size.x > max_x
170 || r_half_size.y < min_y
171 || -r_half_size.y > max_y
172 {
173 return Relation::Disjoint;
174 } else if -r_half_size.x < min_x
175 && max_x < r_half_size.x
176 && -r_half_size.y < min_y
177 && max_y < r_half_size.y
178 {
179 return Relation::Contained;
180 }
181 Relation::Overlap
182 }
183}
184
185impl Collision<CollisionCircle> for CollisionRotatedRect {
186 fn detect(&self, circle: &CollisionCircle) -> Relation {
187 match Collision::detect(circle, self) {
188 Relation::Contain => Relation::Contained,
189 Relation::Contained => Relation::Contain,
190 r => r,
191 }
192 }
193}
194
195impl<const ID: usize> UpdateCollision<GlobalTransform> for CollisionRotatedRect<ID> {
196 fn update() -> impl FnOnce(Mut<Self>, &GlobalTransform) {
197 |mut rect, global_transform| {
198 debug_assert_eq!(
199 global_transform.rotation().to_axis_angle().0.z,
200 1.,
201 "Rotation for CollisionRotatedRect should around z-axis only"
202 );
203 rect.scale = global_transform.scale().truncate();
204 rect.isometric = Isometry2d::new(
205 global_transform.translation().truncate(),
206 Rot2::radians(global_transform.rotation().to_axis_angle().1),
207 );
208 }
209 }
210}
211
212#[cfg(feature = "sprite")]
213impl<const ID: usize> UpdateCollision<Sprite> for CollisionRotatedRect<ID> {
214 fn update() -> impl FnOnce(Mut<Self>, &Sprite) {
215 |mut rect, sprite| {
216 if let Some(size) = sprite.custom_size {
217 if size != rect.init_size {
218 rect.init_size = size;
219 }
220 } else {
221 warn!("Tracking sprite with no custom size");
222 }
223 }
224 }
225}
226
227impl CollisionQuery for CollisionRotatedRect {
228 fn query(&self, obj: &dyn DynCollision) -> Relation {
229 match obj.detect(self) {
230 Relation::Contain => Relation::Contained,
231 Relation::Contained => Relation::Contain,
232 r => r,
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use std::f32::consts::*;
241
242 #[test]
243 fn collision_rotated_rect_detect() {
244 let rect = CollisionRotatedRect::new(
245 Rect::from_center_size(Vec2::ZERO, Vec2::ONE),
246 Rot2::radians(FRAC_PI_4),
247 );
248 let contain =
249 CollisionRotatedRect::from(Rect::from_center_size(Vec2::ZERO, Vec2::ONE / 2.));
250 let contained = CollisionRotatedRect::new(
251 Rect::from_center_size(Vec2::ZERO, Vec2::ONE * 1.4),
252 Rot2::degrees(15.),
253 );
254 let disjoint = CollisionRotatedRect::from(Rect::from_center_size(Vec2::ONE, Vec2::ONE));
255 let overlap =
256 CollisionRotatedRect::from(Rect::from_center_size(Vec2::new(0.5, 0.5), Vec2::ONE));
257 assert_eq!(rect.detect(&contain), Relation::Contain);
258 assert_eq!(rect.detect(&contained), Relation::Contained);
259 assert_eq!(rect.detect(&disjoint), Relation::Disjoint);
260 assert_eq!(rect.detect(&overlap), Relation::Overlap);
261 }
262
263 #[test]
264 fn collision_rotated_rect_detect_rect() {
265 let rect = CollisionRect::from(Rect::from_center_size(Vec2::ZERO, Vec2::ONE));
266 let contain = CollisionCircle::new(Vec2::ZERO, 0.4);
267 let contained = CollisionCircle::new(Vec2::ZERO, 2.);
268 let disjoint = CollisionCircle::new(Vec2::new(2., 2.), 1.);
269 let overlap = CollisionCircle::new(Vec2::new(0.5, 0.5), 1.);
270 assert_eq!(rect.detect(&contain), Relation::Contain);
271 assert_eq!(rect.detect(&contained), Relation::Contained);
272 assert_eq!(rect.detect(&disjoint), Relation::Disjoint);
273 assert_eq!(rect.detect(&overlap), Relation::Overlap);
274 }
275
276 #[test]
277 fn collision_rotated_rect_detect_circle() {
278 let rect = CollisionRect::from(Rect::from_center_size(Vec2::ZERO, Vec2::ONE));
279 let contain = CollisionRotatedRect::new(
280 Rect::from_center_size(Vec2::ZERO, Vec2::ONE / 2.),
281 Rot2::radians(FRAC_PI_4),
282 );
283 let contained = CollisionRotatedRect::new(
284 Rect::from_center_size(Vec2::ZERO, Vec2::ONE * 3.),
285 Rot2::radians(FRAC_PI_4),
286 );
287 let disjoint = CollisionRotatedRect::new(
288 Rect::from_center_size(Vec2::new(2., 2.), Vec2::ONE),
289 Rot2::radians(FRAC_PI_4),
290 );
291 let overlap = CollisionRotatedRect::new(
292 Rect::from_center_size(Vec2::new(0.5, 0.5), Vec2::ONE),
293 Rot2::radians(FRAC_PI_4),
294 );
295 assert_eq!(rect.detect(&contain), Relation::Contain);
296 assert_eq!(rect.detect(&contained), Relation::Contained);
297 assert_eq!(rect.detect(&disjoint), Relation::Disjoint);
298 assert_eq!(rect.detect(&overlap), Relation::Overlap);
299 }
300}