1use crate::physics_components::Transform2D;
2use bevy::{math::Mat2, prelude::*};
3
4mod aabb;
5mod circle;
6mod square;
7mod capsule;
8mod triangle;
9
10pub use aabb::*;
11pub use circle::*;
12pub use square::*;
13pub use capsule::*;
14pub use triangle::*;
15
16pub trait SAT {
17 fn aabb(&self, trans: &Transform2D) -> Aabb {
19 let (xmin, xmax) = self.project(trans, Vec2::X);
20 let (ymin, ymax) = self.project(trans, Vec2::Y);
21
22 let min = Vec2::new(xmin, ymin);
23 let max = Vec2::new(xmax, ymax);
24
25 let extents = (max - min) * 0.5;
26 let position = min + extents;
27
28 Aabb { extents, position }
29 }
30
31 fn get_normals(&self, trans: &Transform2D) -> Box<dyn Iterator<Item = Vec2> + '_>;
35
36 fn project(&self, trans: &Transform2D, normal: Vec2) -> (f32,f32);
40
41 fn get_closest_vertex(&self, trans: &Transform2D, vertex: Vec2) -> Vec2;
43
44 fn ray(&self, trans: &Transform2D, ray_origin: Vec2, ray_cast: Vec2) -> Option<f32>;
50}
51
52pub fn collide(a: &CollisionShape, trans_a: &Transform2D, b: &CollisionShape, trans_b: &Transform2D) -> Option<Vec2> {
56 if let CollisionShape::Multiple(v) = a {
57 let mut sum = Vec2::ZERO;
59 for s in v {
60 if let Some(c) = collide(s, trans_a, b, trans_b) {
61 sum += c;
64 }
65 }
66 if sum.length_squared() < 0.01 {
67 return None;
68 }
69 else {
70 return Some(sum);
71 }
72
73 }
74 if let CollisionShape::Multiple(v) = b {
76 let mut sum = Vec2::ZERO;
78 for s in v {
79 if let Some(c) = collide(a, trans_a, s, trans_b) {
80 sum += c;
83 }
84 }
85 if sum.length_squared() < 0.01 {
86 return None;
87 }
88 else {
89 return Some(sum);
90 }
91 }
92
93 let sat_a = a.sat();
94 let sat_b = b.sat();
95
96 match (sat_a, sat_b) {
97 (Some(a), Some(b)) => sat_normal(a, trans_a, b, trans_b),
98 (Some(a), None) => sat_special(a, trans_a, b, trans_b), (None, Some(b)) => sat_special(b, trans_b, a, trans_a).map(|c| -c), (None, None) => collide_special(a, trans_a, b, trans_b), }
102}
103
104fn sat_normal(a: &dyn SAT, ta: &Transform2D, b: &dyn SAT, tb: &Transform2D) -> Option<Vec2> {
105 let na = a.get_normals(ta);
106 let nb = b.get_normals(tb);
107
108 let mut minimal_dis = f32::INFINITY;
109 let mut minimal_n = Vec2::ZERO;
110
111 for n in na.chain(nb) {
112 let (mina, maxa) = a.project(ta, n);
113 let (minb, maxb) = b.project(tb, n);
114
115 if mina < maxb && minb < maxa {
116 let p1 = maxb - mina;
118 let p2 = minb - maxa;
119
120 let p = if p1.abs() < p2.abs() { p1 } else { p2 };
121
122 if p.abs() < minimal_dis.abs() {
123 minimal_dis = p;
124 minimal_n = n;
125 }
126 }
127 else {
128 return None;
130 }
131 }
132 Some(minimal_dis * minimal_n)
133}
134
135fn sat_special(a: &dyn SAT, ta: &Transform2D, b: &CollisionShape, tb: &Transform2D) -> Option<Vec2> {
136 let na = a.get_normals(ta);
137 let b_rot = Mat2::from_angle(tb.rotation());
138 let nb = match b {
139 CollisionShape::Circle(c) => {
140 let offset = b_rot * c.offset;
141 let v = a.get_closest_vertex(ta, tb.translation() + offset);
142 (tb.translation() + offset - v).normalize()
143 },
144 CollisionShape::Capsule(c) => {
145 let offset = b_rot * c.offset;
146 let v = a.get_closest_vertex(ta, tb.translation() + offset);
147 c.sat_normal(tb, v)
148 }
149 _ => panic!("Shouldn't happen, if this occur to you please report it as a bug(and how you got here)")
150 };
151
152 let mut minimal_dis = f32::INFINITY;
153 let mut minimal_n = Vec2::ZERO;
154
155 for n in na.chain([nb]) {
156 let (mina, maxa) = a.project(ta, n);
157 let (minb, maxb) = match b {
158 CollisionShape::Circle(c) => {
159 let center = tb.translation() + b_rot * c.offset;
160 let center = center.dot(n);
161
162 (center - c.radius, center + c.radius)
163 },
164 CollisionShape::Capsule(c) => c.project(tb, n),
165 _ => panic!("If you paniced here, something is REALLY wrong")
166 };
167
168 if mina < maxb && minb < maxa {
169 let p1 = maxb - mina;
171 let p2 = minb - maxa;
172
173 let p = if p1.abs() < p2.abs() { p1 } else { p2 };
174
175 if p.abs() < minimal_dis.abs() {
176 minimal_dis = p;
177 minimal_n = n;
178 }
179 }
180 else {
181 return None;
183 }
184 }
185 Some(minimal_dis * minimal_n)
186}
187
188fn collide_special(a: &CollisionShape, ta: &Transform2D, b: &CollisionShape, tb: &Transform2D) -> Option<Vec2> {
189 #[allow(clippy::enum_glob_use)]
190 use CollisionShape::*;
191
192 match (a, b) {
193 (Circle(a), Circle(b)) => {
194 let ac = ta.translation() + Mat2::from_angle(ta.rotation()) * a.offset;
195 let bc = tb.translation() + Mat2::from_angle(tb.rotation()) * b.offset;
196 let d = ac - bc;
197 let d_len = d.length();
198
199 if d_len < a.radius + b.radius {
200 Some((a.radius + b.radius - d_len) * (d / d_len))
202 }
203 else {
204 None
205 }
206 },
207 (Circle(a), Capsule(b)) => collide_circle_capsule(a, ta, b, tb),
208 (Capsule(a), Circle(b)) => collide_circle_capsule(b, tb, a, ta).map(|v| -v),
209 (Capsule(a), Capsule(b)) => {
210 let a_rot = Mat2::from_angle(ta.rotation());
211 let b_rot = Mat2::from_angle(tb.rotation());
212
213 let n1 = a_rot * Vec2::X;
216 let n2 = b_rot * Vec2::X;
217
218 let n3 = {
220 let b1 = b_rot * Vec2::new(0.0, b.half_height) + tb.translation() + b_rot * b.offset;
221 let b2 = b_rot * Vec2::new(0.0, -b.half_height) + tb.translation() + b_rot * b.offset;
222
223 let v = ta.translation() + a_rot * a.offset;
224
225 let d1 = b1 - v;
226 let d2 = b2 - v;
227
228 if d1.length_squared() < d2.length_squared() {
229 d1.normalize_or_zero()
230 }
231 else {
232 d2.normalize_or_zero()
233 }
234 };
235
236 let mut minimal_dis = f32::INFINITY;
237 let mut minimal_n = Vec2::ZERO;
238
239 for n in [n1,n2,n3] {
240 let (mina, maxa) = a.project(ta, n);
241 let (minb, maxb) = b.project(tb, n);
242
243 if mina < maxb && minb < maxa {
244 let p1 = maxb - mina;
246 let p2 = minb - maxa;
247
248 let p = if p1.abs() < p2.abs() { p1 } else { p2 };
249
250 if p.abs() < minimal_dis.abs() {
251 minimal_dis = p;
252 minimal_n = n;
253 }
254 }
255 else {
256 return None;
258 }
259 }
260 Some(minimal_dis * minimal_n)
261 },
262 _ => panic!("Something is missing, please report it on github(with the shapes used)"),
263 }
264}
265
266fn collide_circle_capsule(a: &Circle, ta: &Transform2D, b: &Capsule, tb: &Transform2D) -> Option<Vec2> {
267 let brot = Mat2::from_angle(tb.rotation());
268
269 let (ba, bb) = b.center_line(tb);
271
272 let acenter = ta.translation() + Mat2::from_angle(ta.rotation()) * a.offset;
273
274 let n = brot * Vec2::X;
275 let p = brot * Vec2::Y;
276
277 let bn = n.dot(ba); let bap = p.dot(ba);
279 let bbp = p.dot(bb);
280
281 let an = n.dot(acenter);
282 let ap = p.dot(acenter);
283
284 let bpmin = bap.min(bbp);
285 let bpmax = bap.max(bbp);
286
287 let dp = if ap > bpmax { ap - bpmax } else if ap < bpmin { ap - bpmin } else { 0.0 };
288
289 let dis = n * (an - bn) + p * dp;
290
291 let dis_n = dis.normalize();
292 let dis_l = dis.dot(dis_n);
293
294 if dis_l < (a.radius + b.radius) {
295 Some(dis_n * (a.radius + b.radius - dis_l))
296 } else {
297 None
298 }
299}
300
301#[derive(Component)]
317pub enum CollisionShape {
318 Square(Square),
319 Triangle(Triangle),
320 Circle(Circle),
321 Capsule(Capsule),
322 Multiple(Vec<CollisionShape>),
323 Convex(Box<dyn SAT + Send + Sync>),
324}
325impl CollisionShape {
326 pub fn sat(&self) -> Option<&dyn SAT> {
327 match self {
328 CollisionShape::Square(s) => Some(s),
329 CollisionShape::Triangle(t) => Some(t),
330 CollisionShape::Circle(_) => None,
331 CollisionShape::Capsule(_) => None,
332 CollisionShape::Multiple(_) => None,
333 CollisionShape::Convex(s) => Some(s.as_ref())
334 }
335 }
336
337 pub fn aabb(&self, t: &Transform2D) -> Aabb {
338 if let Some(sat) = self.sat() {
339 sat.aabb(t)
340 }
341 else {
342 match self {
343 CollisionShape::Circle(c) => c.aabb(t),
344 CollisionShape::Capsule(c) => c.aabb(t),
345 CollisionShape::Multiple(v) => {
346 assert!(v.len() != 0, "CollisionShape::Multiple cannot be empty!");
348
349 let (mut min, mut max) = v[0].aabb(t).min_max();
350
351 for s in v.iter().skip(1) {
353 let (sn, sx) = s.aabb(t).min_max();
354 min = min.min(sn);
355 max = max.max(sx);
356 }
357 Aabb::from_min_max(min, max)
358 }
359 _ => panic!("Something is missing, please report on github(with the shape used)"),
360 }
361 }
362 }
363
364 pub fn ray(&self, trans: &Transform2D, ray_origin: Vec2, ray_cast: Vec2) -> Option<f32> {
365 if let Some(sat) = self.sat() {
366 sat.ray(trans, ray_origin, ray_cast)
367 }
368 else {
369 match self {
370 CollisionShape::Circle(c) => c.ray(trans, ray_origin, ray_cast),
371 CollisionShape::Capsule(c) => c.ray(trans, ray_origin, ray_cast),
372 CollisionShape::Multiple(v) => {
373 assert!(v.len() != 0, "CollisionShape::Multiple cannot be empty!");
375
376 let mut res = None;
377 for s in v {
378 if let Some(r) = s.ray(trans, ray_origin, ray_cast) {
379 if r < res.unwrap_or(f32::INFINITY) {
380 res = Some(r);
381 }
382 }
383 }
384 res
385 }
386 _ => panic!("Something is missing, please report on github(with the shape used)"),
387 }
388 }
389 }
390}
391impl Default for CollisionShape {
392 fn default() -> Self {
393 CollisionShape::Square(Square::default())
394 }
395}
396
397#[cfg(test)]
398mod sat_tests {
399 use super::*;
400
401 use std::f32::consts::PI;
402 const EPSILON: f32 = 0.001;
405
406 #[test]
407 fn squares() {
408 let s1 = Square {
409 offset: Vec2::ZERO,
410 extents: Vec2::splat(1.0),
411 };
412
413 let t1 = Transform2D::new(
414 Vec2::ZERO,
415 0.0,
416 Vec2::splat(1.0),
417 );
418
419 let s2 = Square {
420 offset: Vec2::ZERO,
421 extents: Vec2::splat(1.0),
422 };
423
424 let t2 = Transform2D::new(
425 Vec2::new(1.5, 0.0),
426 0.0,
427 Vec2::splat(1.0),
428 );
429
430 let cs1 = CollisionShape::Square(s1);
431 let cs2 = CollisionShape::Square(s2);
432
433 assert_eq!(
434 collide(&cs1, &t1, &cs2, &t2),
435 Some(Vec2::new(-0.5, 0.0))
436 );
437 assert_eq!(
438 collide(&cs2, &t2, &cs1, &t1),
439 Some(Vec2::new(0.5,0.0))
440 );
441 }
442 #[test]
443 fn squares_rotation() {
444 let a = Square {
445 offset: Vec2::ZERO,
446 extents: Vec2::splat(1.0),
447 };
448 let ta = Transform2D::new(
449 Vec2::ZERO,
450 0.0,
451 Vec2::splat(1.0)
452 );
453
454 let b = Square {
455 offset: Vec2::ZERO,
456 extents: Vec2::splat(1.0),
457 };
458 let tb = Transform2D::new(
459 Vec2::new(2.0, 0.5),
460 PI * 0.25,
461 Vec2::splat(1.0),
462 );
463
464 let c = collide(&CollisionShape::Square(a), &ta, &CollisionShape::Square(b), &tb);
465
466 assert!((c.unwrap() + Vec2::new(2.0_f32.sqrt() - 1.0, 0.0)).length() < EPSILON);
467 }
468}