1use super::types::{Contact, ContactID, ContactManifold, ManifoldPoint, RigidBody, Shape};
2
3pub fn test_collision(a: &RigidBody, b: &RigidBody) -> Option<Contact> {
6 match (&a.shape, &b.shape) {
7 (Shape::Circle { .. }, Shape::Circle { .. }) => circle_vs_circle(a, b),
8 (Shape::Circle { .. }, Shape::AABB { .. }) => circle_vs_aabb(a, b, false),
9 (Shape::AABB { .. }, Shape::Circle { .. }) => circle_vs_aabb(b, a, true),
10 (Shape::AABB { .. }, Shape::AABB { .. }) => aabb_vs_aabb(a, b),
11 (Shape::Polygon { .. }, Shape::Polygon { .. }) => polygon_vs_polygon(a, b),
12 (Shape::Circle { .. }, Shape::Polygon { .. }) => circle_vs_polygon(a, b, false),
13 (Shape::Polygon { .. }, Shape::Circle { .. }) => circle_vs_polygon(b, a, true),
14 (Shape::AABB { .. }, Shape::Polygon { .. }) => aabb_vs_polygon(a, b, false),
15 (Shape::Polygon { .. }, Shape::AABB { .. }) => aabb_vs_polygon(b, a, true),
16 }
17}
18
19fn circle_vs_circle(a: &RigidBody, b: &RigidBody) -> Option<Contact> {
20 let ra = match a.shape {
21 Shape::Circle { radius } => radius,
22 _ => return None,
23 };
24 let rb = match b.shape {
25 Shape::Circle { radius } => radius,
26 _ => return None,
27 };
28
29 let dx = b.x - a.x;
30 let dy = b.y - a.y;
31 let dist_sq = dx * dx + dy * dy;
32 let sum_r = ra + rb;
33
34 if dist_sq >= sum_r * sum_r {
35 return None;
36 }
37
38 let dist = dist_sq.sqrt();
39 let (nx, ny) = if dist > 1e-8 {
40 (dx / dist, dy / dist)
41 } else {
42 (1.0, 0.0)
43 };
44
45 Some(Contact {
46 body_a: a.id,
47 body_b: b.id,
48 normal: (nx, ny),
49 penetration: sum_r - dist,
50 contact_point: (
51 a.x + nx * (ra - (sum_r - dist) * 0.5),
52 a.y + ny * (ra - (sum_r - dist) * 0.5),
53 ),
54 accumulated_jn: 0.0,
55 accumulated_jt: 0.0,
56 velocity_bias: 0.0,
57 tangent: (0.0, 0.0),
58 })
59}
60
61fn circle_vs_aabb(circle: &RigidBody, aabb: &RigidBody, swapped: bool) -> Option<Contact> {
63 let radius = match circle.shape {
64 Shape::Circle { radius } => radius,
65 _ => return None,
66 };
67 let (hw, hh) = match aabb.shape {
68 Shape::AABB { half_w, half_h } => (half_w, half_h),
69 _ => return None,
70 };
71
72 let local_x = circle.x - aabb.x;
74 let local_y = circle.y - aabb.y;
75
76 let closest_x = local_x.clamp(-hw, hw);
78 let closest_y = local_y.clamp(-hh, hh);
79
80 let dx = local_x - closest_x;
81 let dy = local_y - closest_y;
82 let dist_sq = dx * dx + dy * dy;
83
84 if dist_sq >= radius * radius {
85 return None;
86 }
87
88 let inside = local_x.abs() < hw && local_y.abs() < hh;
90
91 let (nx, ny, penetration) = if inside {
92 let overlap_x = hw - local_x.abs();
94 let overlap_y = hh - local_y.abs();
95 if overlap_x < overlap_y {
96 let nx = if local_x >= 0.0 { 1.0 } else { -1.0 };
97 (nx, 0.0, overlap_x + radius)
98 } else {
99 let ny = if local_y >= 0.0 { 1.0 } else { -1.0 };
100 (0.0, ny, overlap_y + radius)
101 }
102 } else {
103 let dist = dist_sq.sqrt();
104 let nx = if dist > 1e-8 { dx / dist } else { 1.0 };
105 let ny = if dist > 1e-8 { dy / dist } else { 0.0 };
106 (nx, ny, radius - dist)
107 };
108
109 let contact_x = aabb.x + closest_x;
110 let contact_y = aabb.y + closest_y;
111
112 if swapped {
113 Some(Contact {
115 body_a: aabb.id,
116 body_b: circle.id,
117 normal: (nx, ny),
118 penetration,
119 contact_point: (contact_x, contact_y),
120 accumulated_jn: 0.0,
121 accumulated_jt: 0.0,
122 velocity_bias: 0.0,
123 tangent: (0.0, 0.0),
124 })
125 } else {
126 Some(Contact {
128 body_a: circle.id,
129 body_b: aabb.id,
130 normal: (-nx, -ny),
131 penetration,
132 contact_point: (contact_x, contact_y),
133 accumulated_jn: 0.0,
134 accumulated_jt: 0.0,
135 velocity_bias: 0.0,
136 tangent: (0.0, 0.0),
137 })
138 }
139}
140
141fn aabb_vs_aabb(a: &RigidBody, b: &RigidBody) -> Option<Contact> {
142 let (ahw, ahh) = match a.shape {
143 Shape::AABB { half_w, half_h } => (half_w, half_h),
144 _ => return None,
145 };
146 let (bhw, bhh) = match b.shape {
147 Shape::AABB { half_w, half_h } => (half_w, half_h),
148 _ => return None,
149 };
150
151 let dx = b.x - a.x;
152 let dy = b.y - a.y;
153 let overlap_x = (ahw + bhw) - dx.abs();
154 let overlap_y = (ahh + bhh) - dy.abs();
155
156 if overlap_x <= 0.0 || overlap_y <= 0.0 {
157 return None;
158 }
159
160 let (nx, ny, penetration) = if overlap_x < overlap_y {
161 let nx = if dx >= 0.0 { 1.0 } else { -1.0 };
162 (nx, 0.0, overlap_x)
163 } else {
164 let ny = if dy >= 0.0 { 1.0 } else { -1.0 };
165 (0.0, ny, overlap_y)
166 };
167
168 let (cpx, cpy) = if overlap_x < overlap_y {
170 let cx = if dx >= 0.0 { a.x + ahw } else { a.x - ahw };
172 let y_min = (a.y - ahh).max(b.y - bhh);
173 let y_max = (a.y + ahh).min(b.y + bhh);
174 (cx, (y_min + y_max) * 0.5)
175 } else {
176 let cy = if dy >= 0.0 { a.y + ahh } else { a.y - ahh };
178 let x_min = (a.x - ahw).max(b.x - bhw);
179 let x_max = (a.x + ahw).min(b.x + bhw);
180 ((x_min + x_max) * 0.5, cy)
181 };
182
183 Some(Contact {
184 body_a: a.id,
185 body_b: b.id,
186 normal: (nx, ny),
187 penetration,
188 contact_point: (cpx, cpy),
189 accumulated_jn: 0.0,
190 accumulated_jt: 0.0,
191 velocity_bias: 0.0,
192 tangent: (0.0, 0.0),
193 })
194}
195
196fn get_world_vertices(body: &RigidBody) -> Vec<(f32, f32)> {
198 let verts = match &body.shape {
199 Shape::Polygon { vertices } => vertices,
200 _ => return Vec::new(),
201 };
202 let cos = body.angle.cos();
203 let sin = body.angle.sin();
204 verts
205 .iter()
206 .map(|&(vx, vy)| {
207 (
208 vx * cos - vy * sin + body.x,
209 vx * sin + vy * cos + body.y,
210 )
211 })
212 .collect()
213}
214
215fn get_edge_normals(vertices: &[(f32, f32)]) -> Vec<(f32, f32)> {
217 let n = vertices.len();
218 let mut normals = Vec::with_capacity(n);
219 for i in 0..n {
220 let (x0, y0) = vertices[i];
221 let (x1, y1) = vertices[(i + 1) % n];
222 let ex = x1 - x0;
223 let ey = y1 - y0;
224 let len = (ex * ex + ey * ey).sqrt();
225 if len > 1e-8 {
226 normals.push((ey / len, -ex / len));
227 }
228 }
229 normals
230}
231
232fn project_vertices(vertices: &[(f32, f32)], axis: (f32, f32)) -> (f32, f32) {
234 let mut min = f32::MAX;
235 let mut max = f32::MIN;
236 for &(vx, vy) in vertices {
237 let p = vx * axis.0 + vy * axis.1;
238 min = min.min(p);
239 max = max.max(p);
240 }
241 (min, max)
242}
243
244fn polygon_vs_polygon(a: &RigidBody, b: &RigidBody) -> Option<Contact> {
245 let verts_a = get_world_vertices(a);
246 let verts_b = get_world_vertices(b);
247 if verts_a.len() < 3 || verts_b.len() < 3 {
248 return None;
249 }
250
251 let normals_a = get_edge_normals(&verts_a);
252 let normals_b = get_edge_normals(&verts_b);
253
254 let mut min_overlap = f32::MAX;
255 let mut min_axis = (0.0f32, 0.0f32);
256
257 for &axis in normals_a.iter().chain(normals_b.iter()) {
258 let (min_a, max_a) = project_vertices(&verts_a, axis);
259 let (min_b, max_b) = project_vertices(&verts_b, axis);
260
261 let overlap = (max_a.min(max_b)) - (min_a.max(min_b));
262 if overlap <= 0.0 {
263 return None;
264 }
265 if overlap < min_overlap {
266 min_overlap = overlap;
267 min_axis = axis;
268 }
269 }
270
271 let dx = b.x - a.x;
273 let dy = b.y - a.y;
274 if dx * min_axis.0 + dy * min_axis.1 < 0.0 {
275 min_axis = (-min_axis.0, -min_axis.1);
276 }
277
278 let mut best_dot = f32::MAX;
280 let mut best_point = ((a.x + b.x) * 0.5, (a.y + b.y) * 0.5);
281 for &(vx, vy) in &verts_b {
282 let d = vx * min_axis.0 + vy * min_axis.1;
283 if d < best_dot {
284 best_dot = d;
285 best_point = (vx, vy);
286 }
287 }
288
289 Some(Contact {
290 body_a: a.id,
291 body_b: b.id,
292 normal: min_axis,
293 penetration: min_overlap,
294 contact_point: best_point,
295 accumulated_jn: 0.0,
296 accumulated_jt: 0.0,
297 velocity_bias: 0.0,
298 tangent: (0.0, 0.0),
299 })
300}
301
302fn circle_vs_polygon(circle: &RigidBody, poly: &RigidBody, swapped: bool) -> Option<Contact> {
303 let radius = match circle.shape {
304 Shape::Circle { radius } => radius,
305 _ => return None,
306 };
307 let verts = get_world_vertices(poly);
308 if verts.len() < 3 {
309 return None;
310 }
311
312 let mut closest_dist_sq = f32::MAX;
314 let mut closest_point = (0.0f32, 0.0f32);
315
316 let n = verts.len();
317 for i in 0..n {
318 let (ax, ay) = verts[i];
319 let (bx, by) = verts[(i + 1) % n];
320 let (cx, cy) = closest_point_on_segment(circle.x, circle.y, ax, ay, bx, by);
321 let dx = circle.x - cx;
322 let dy = circle.y - cy;
323 let d2 = dx * dx + dy * dy;
324 if d2 < closest_dist_sq {
325 closest_dist_sq = d2;
326 closest_point = (cx, cy);
327 }
328 }
329
330 let inside = point_in_polygon(circle.x, circle.y, &verts);
332
333 let dist = closest_dist_sq.sqrt();
334
335 if !inside && dist >= radius {
336 return None;
337 }
338
339 let (nx, ny, penetration) = if inside {
340 let dx = circle.x - closest_point.0;
342 let dy = circle.y - closest_point.1;
343 let len = (dx * dx + dy * dy).sqrt();
344 if len > 1e-8 {
345 (-dx / len, -dy / len, radius + dist)
346 } else {
347 (1.0, 0.0, radius)
348 }
349 } else {
350 let dx = circle.x - closest_point.0;
351 let dy = circle.y - closest_point.1;
352 (dx / dist, dy / dist, radius - dist)
353 };
354
355 let (ba, bb, fnx, fny) = if swapped {
356 (poly.id, circle.id, -nx, -ny)
357 } else {
358 (circle.id, poly.id, nx, ny)
359 };
360
361 let dir_x = if swapped { circle.x - poly.x } else { poly.x - circle.x };
363 let dir_y = if swapped { circle.y - poly.y } else { poly.y - circle.y };
364 let dot = fnx * dir_x + fny * dir_y;
365 let (fnx, fny) = if dot < 0.0 { (-fnx, -fny) } else { (fnx, fny) };
366
367 Some(Contact {
368 body_a: ba,
369 body_b: bb,
370 normal: (fnx, fny),
371 penetration,
372 contact_point: closest_point,
373 accumulated_jn: 0.0,
374 accumulated_jt: 0.0,
375 velocity_bias: 0.0,
376 tangent: (0.0, 0.0),
377 })
378}
379
380fn aabb_vs_polygon(aabb: &RigidBody, poly: &RigidBody, swapped: bool) -> Option<Contact> {
381 let (hw, hh) = match aabb.shape {
382 Shape::AABB { half_w, half_h } => (half_w, half_h),
383 _ => return None,
384 };
385
386 let aabb_as_poly = RigidBody {
388 shape: Shape::Polygon {
389 vertices: vec![(-hw, -hh), (hw, -hh), (hw, hh), (-hw, hh)],
390 },
391 ..aabb.clone()
392 };
393
394 let result = polygon_vs_polygon(&aabb_as_poly, poly)?;
395
396 if swapped {
397 Some(Contact {
398 body_a: poly.id,
399 body_b: aabb.id,
400 normal: (-result.normal.0, -result.normal.1),
401 penetration: result.penetration,
402 contact_point: result.contact_point,
403 accumulated_jn: 0.0,
404 accumulated_jt: 0.0,
405 velocity_bias: 0.0,
406 tangent: (0.0, 0.0),
407 })
408 } else {
409 Some(Contact {
410 body_a: aabb.id,
411 body_b: poly.id,
412 ..result
413 })
414 }
415}
416
417fn closest_point_on_segment(
418 px: f32, py: f32,
419 ax: f32, ay: f32,
420 bx: f32, by: f32,
421) -> (f32, f32) {
422 let abx = bx - ax;
423 let aby = by - ay;
424 let apx = px - ax;
425 let apy = py - ay;
426 let ab_sq = abx * abx + aby * aby;
427 if ab_sq < 1e-12 {
428 return (ax, ay);
429 }
430 let t = ((apx * abx + apy * aby) / ab_sq).clamp(0.0, 1.0);
431 (ax + abx * t, ay + aby * t)
432}
433
434fn point_in_polygon(px: f32, py: f32, verts: &[(f32, f32)]) -> bool {
435 let n = verts.len();
436 let mut inside = false;
437 let mut j = n - 1;
438 for i in 0..n {
439 let (xi, yi) = verts[i];
440 let (xj, yj) = verts[j];
441 if ((yi > py) != (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
442 inside = !inside;
443 }
444 j = i;
445 }
446 inside
447}
448
449pub fn test_collision_manifold(a: &RigidBody, b: &RigidBody) -> Option<ContactManifold> {
457 match (&a.shape, &b.shape) {
458 (Shape::Circle { .. }, Shape::Circle { .. }) => circle_vs_circle_manifold(a, b),
459 (Shape::Circle { .. }, Shape::AABB { .. }) => circle_vs_aabb_manifold(a, b, false),
460 (Shape::AABB { .. }, Shape::Circle { .. }) => circle_vs_aabb_manifold(b, a, true),
461 (Shape::AABB { .. }, Shape::AABB { .. }) => aabb_vs_aabb_manifold(a, b),
462 (Shape::Polygon { .. }, Shape::Polygon { .. }) => polygon_vs_polygon_manifold(a, b),
463 (Shape::Circle { .. }, Shape::Polygon { .. }) => circle_vs_polygon_manifold(a, b, false),
464 (Shape::Polygon { .. }, Shape::Circle { .. }) => circle_vs_polygon_manifold(b, a, true),
465 (Shape::AABB { .. }, Shape::Polygon { .. }) => aabb_vs_polygon_manifold(a, b, false),
466 (Shape::Polygon { .. }, Shape::AABB { .. }) => aabb_vs_polygon_manifold(b, a, true),
467 }
468}
469
470fn world_to_local(body: &RigidBody, wx: f32, wy: f32) -> (f32, f32) {
472 let dx = wx - body.x;
473 let dy = wy - body.y;
474 let cos = body.angle.cos();
475 let sin = body.angle.sin();
476 (dx * cos + dy * sin, -dx * sin + dy * cos)
478}
479
480fn circle_vs_circle_manifold(a: &RigidBody, b: &RigidBody) -> Option<ContactManifold> {
481 let ra = match a.shape {
482 Shape::Circle { radius } => radius,
483 _ => return None,
484 };
485 let rb = match b.shape {
486 Shape::Circle { radius } => radius,
487 _ => return None,
488 };
489
490 let dx = b.x - a.x;
491 let dy = b.y - a.y;
492 let dist_sq = dx * dx + dy * dy;
493 let sum_r = ra + rb;
494
495 if dist_sq >= sum_r * sum_r {
496 return None;
497 }
498
499 let dist = dist_sq.sqrt();
500 let (nx, ny) = if dist > 1e-8 {
501 (dx / dist, dy / dist)
502 } else {
503 (1.0, 0.0)
504 };
505
506 let penetration = sum_r - dist;
507
508 let cpx = a.x + nx * (ra - penetration * 0.5);
510 let cpy = a.y + ny * (ra - penetration * 0.5);
511
512 let local_a = world_to_local(a, cpx, cpy);
514 let local_b = world_to_local(b, cpx, cpy);
515
516 Some(ContactManifold {
517 body_a: a.id,
518 body_b: b.id,
519 normal: (nx, ny),
520 points: vec![ManifoldPoint::new(local_a, local_b, penetration, ContactID::circle())],
521 tangent: (-ny, nx),
522 velocity_bias: 0.0,
523 })
524}
525
526fn circle_vs_aabb_manifold(circle: &RigidBody, aabb: &RigidBody, swapped: bool) -> Option<ContactManifold> {
527 let radius = match circle.shape {
528 Shape::Circle { radius } => radius,
529 _ => return None,
530 };
531 let (hw, hh) = match aabb.shape {
532 Shape::AABB { half_w, half_h } => (half_w, half_h),
533 _ => return None,
534 };
535
536 let local_x = circle.x - aabb.x;
537 let local_y = circle.y - aabb.y;
538
539 let closest_x = local_x.clamp(-hw, hw);
540 let closest_y = local_y.clamp(-hh, hh);
541
542 let dx = local_x - closest_x;
543 let dy = local_y - closest_y;
544 let dist_sq = dx * dx + dy * dy;
545
546 if dist_sq >= radius * radius {
547 return None;
548 }
549
550 let inside = local_x.abs() < hw && local_y.abs() < hh;
551
552 let (nx, ny, penetration) = if inside {
553 let overlap_x = hw - local_x.abs();
554 let overlap_y = hh - local_y.abs();
555 if overlap_x < overlap_y {
556 let nx = if local_x >= 0.0 { 1.0 } else { -1.0 };
557 (nx, 0.0, overlap_x + radius)
558 } else {
559 let ny = if local_y >= 0.0 { 1.0 } else { -1.0 };
560 (0.0, ny, overlap_y + radius)
561 }
562 } else {
563 let dist = dist_sq.sqrt();
564 let nx = if dist > 1e-8 { dx / dist } else { 1.0 };
565 let ny = if dist > 1e-8 { dy / dist } else { 0.0 };
566 (nx, ny, radius - dist)
567 };
568
569 let cpx = aabb.x + closest_x;
570 let cpy = aabb.y + closest_y;
571
572 let (body_a, body_b, fnx, fny) = if swapped {
573 (aabb, circle, nx, ny)
574 } else {
575 (circle, aabb, -nx, -ny)
576 };
577
578 let local_a = world_to_local(body_a, cpx, cpy);
579 let local_b = world_to_local(body_b, cpx, cpy);
580
581 Some(ContactManifold {
582 body_a: body_a.id,
583 body_b: body_b.id,
584 normal: (fnx, fny),
585 points: vec![ManifoldPoint::new(local_a, local_b, penetration, ContactID::circle())],
586 tangent: (-fny, fnx),
587 velocity_bias: 0.0,
588 })
589}
590
591fn aabb_vs_aabb_manifold(a: &RigidBody, b: &RigidBody) -> Option<ContactManifold> {
592 let (ahw, ahh) = match a.shape {
593 Shape::AABB { half_w, half_h } => (half_w, half_h),
594 _ => return None,
595 };
596 let (bhw, bhh) = match b.shape {
597 Shape::AABB { half_w, half_h } => (half_w, half_h),
598 _ => return None,
599 };
600
601 let dx = b.x - a.x;
602 let dy = b.y - a.y;
603 let overlap_x = (ahw + bhw) - dx.abs();
604 let overlap_y = (ahh + bhh) - dy.abs();
605
606 if overlap_x <= 0.0 || overlap_y <= 0.0 {
607 return None;
608 }
609
610 if overlap_x < overlap_y {
612 let nx = if dx >= 0.0 { 1.0 } else { -1.0 };
614
615 let cx = if dx >= 0.0 { a.x + ahw } else { a.x - ahw };
617
618 let y_min = (a.y - ahh).max(b.y - bhh);
620 let y_max = (a.y + ahh).min(b.y + bhh);
621
622 let mut points = Vec::with_capacity(2);
624
625 let cp1 = (cx, y_min);
626 let local_a1 = world_to_local(a, cp1.0, cp1.1);
627 let local_b1 = world_to_local(b, cp1.0, cp1.1);
628 points.push(ManifoldPoint::new(local_a1, local_b1, overlap_x, ContactID::new(0, 0, 0)));
629
630 let cp2 = (cx, y_max);
631 let local_a2 = world_to_local(a, cp2.0, cp2.1);
632 let local_b2 = world_to_local(b, cp2.0, cp2.1);
633 points.push(ManifoldPoint::new(local_a2, local_b2, overlap_x, ContactID::new(0, 0, 1)));
634
635 Some(ContactManifold {
636 body_a: a.id,
637 body_b: b.id,
638 normal: (nx, 0.0),
639 points,
640 tangent: (0.0, 1.0),
641 velocity_bias: 0.0,
642 })
643 } else {
644 let ny = if dy >= 0.0 { 1.0 } else { -1.0 };
646
647 let cy = if dy >= 0.0 { a.y + ahh } else { a.y - ahh };
649
650 let x_min = (a.x - ahw).max(b.x - bhw);
652 let x_max = (a.x + ahw).min(b.x + bhw);
653
654 let mut points = Vec::with_capacity(2);
656
657 let cp1 = (x_min, cy);
658 let local_a1 = world_to_local(a, cp1.0, cp1.1);
659 let local_b1 = world_to_local(b, cp1.0, cp1.1);
660 points.push(ManifoldPoint::new(local_a1, local_b1, overlap_y, ContactID::new(1, 1, 0)));
661
662 let cp2 = (x_max, cy);
663 let local_a2 = world_to_local(a, cp2.0, cp2.1);
664 let local_b2 = world_to_local(b, cp2.0, cp2.1);
665 points.push(ManifoldPoint::new(local_a2, local_b2, overlap_y, ContactID::new(1, 1, 1)));
666
667 Some(ContactManifold {
668 body_a: a.id,
669 body_b: b.id,
670 normal: (0.0, ny),
671 points,
672 tangent: (1.0, 0.0),
673 velocity_bias: 0.0,
674 })
675 }
676}
677
678fn find_max_separation(
681 a_verts: &[(f32, f32)],
682 b_verts: &[(f32, f32)],
683) -> (f32, usize) {
684 let mut max_sep = f32::MIN;
685 let mut best_edge = 0;
686
687 let n = a_verts.len();
688 for i in 0..n {
689 let v0 = a_verts[i];
690 let v1 = a_verts[(i + 1) % n];
691
692 let ex = v1.0 - v0.0;
694 let ey = v1.1 - v0.1;
695 let len = (ex * ex + ey * ey).sqrt();
696 if len < 1e-8 {
697 continue;
698 }
699 let nx = ey / len;
700 let ny = -ex / len;
701
702 let mut min_dot = f32::MAX;
704 for &bv in b_verts {
705 let d = (bv.0 - v0.0) * nx + (bv.1 - v0.1) * ny;
706 min_dot = min_dot.min(d);
707 }
708
709 if min_dot > max_sep {
711 max_sep = min_dot;
712 best_edge = i;
713 }
714 }
715
716 (max_sep, best_edge)
717}
718
719fn find_incident_edge(
721 inc_verts: &[(f32, f32)],
722 ref_normal: (f32, f32),
723) -> usize {
724 let n = inc_verts.len();
725 let mut min_dot = f32::MAX;
726 let mut best_edge = 0;
727
728 for i in 0..n {
729 let v0 = inc_verts[i];
730 let v1 = inc_verts[(i + 1) % n];
731
732 let ex = v1.0 - v0.0;
734 let ey = v1.1 - v0.1;
735 let len = (ex * ex + ey * ey).sqrt();
736 if len < 1e-8 {
737 continue;
738 }
739 let nx = ey / len;
740 let ny = -ex / len;
741
742 let dot = nx * ref_normal.0 + ny * ref_normal.1;
744 if dot < min_dot {
745 min_dot = dot;
746 best_edge = i;
747 }
748 }
749
750 best_edge
751}
752
753fn clip_segment_to_line(
757 v0: (f32, f32),
758 v1: (f32, f32),
759 line_point: (f32, f32),
760 normal: (f32, f32),
761) -> Vec<(f32, f32)> {
762 let mut result = Vec::with_capacity(2);
763
764 let d0 = (v0.0 - line_point.0) * normal.0 + (v0.1 - line_point.1) * normal.1;
766 let d1 = (v1.0 - line_point.0) * normal.0 + (v1.1 - line_point.1) * normal.1;
767
768 if d0 >= 0.0 {
770 result.push(v0);
771 }
772 if d1 >= 0.0 {
773 result.push(v1);
774 }
775
776 if d0 * d1 < 0.0 {
778 let t = d0 / (d0 - d1);
779 let cx = v0.0 + t * (v1.0 - v0.0);
780 let cy = v0.1 + t * (v1.1 - v0.1);
781 result.push((cx, cy));
782 }
783
784 result
785}
786
787fn polygon_vs_polygon_manifold(a: &RigidBody, b: &RigidBody) -> Option<ContactManifold> {
790 let verts_a = get_world_vertices(a);
791 let verts_b = get_world_vertices(b);
792
793 if verts_a.len() < 3 || verts_b.len() < 3 {
794 return None;
795 }
796
797 let (sep_a, edge_a) = find_max_separation(&verts_a, &verts_b);
799 let (sep_b, edge_b) = find_max_separation(&verts_b, &verts_a);
800
801 if sep_a > 0.0 || sep_b > 0.0 {
803 return None;
804 }
805
806 let (ref_verts, inc_verts, ref_edge, ref_body, inc_body, flip) = if sep_a > sep_b - 0.001 {
809 (&verts_a, &verts_b, edge_a, a, b, false)
810 } else {
811 (&verts_b, &verts_a, edge_b, b, a, true)
812 };
813
814 let n = ref_verts.len();
815 let ref_v0 = ref_verts[ref_edge];
816 let ref_v1 = ref_verts[(ref_edge + 1) % n];
817
818 let ref_ex = ref_v1.0 - ref_v0.0;
820 let ref_ey = ref_v1.1 - ref_v0.1;
821 let ref_len = (ref_ex * ref_ex + ref_ey * ref_ey).sqrt();
822 if ref_len < 1e-8 {
823 return None;
824 }
825 let ref_nx = ref_ey / ref_len;
826 let ref_ny = -ref_ex / ref_len;
827
828 let ref_tx = ref_ex / ref_len;
830 let ref_ty = ref_ey / ref_len;
831
832 let inc_edge = find_incident_edge(inc_verts, (ref_nx, ref_ny));
834 let inc_n = inc_verts.len();
835 let inc_v0 = inc_verts[inc_edge];
836 let inc_v1 = inc_verts[(inc_edge + 1) % inc_n];
837
838 let mut clipped = clip_segment_to_line(inc_v0, inc_v1, ref_v0, (ref_tx, ref_ty));
846
847 if clipped.len() < 2 {
848 if clipped.is_empty() {
850 return None;
851 }
852 }
853
854 if clipped.len() >= 2 {
856 clipped = clip_segment_to_line(clipped[0], clipped[1], ref_v1, (-ref_tx, -ref_ty));
857 }
858
859 const CLIP_TOLERANCE: f32 = 0.02;
862 let mut points = Vec::with_capacity(2);
863 for (i, &cp) in clipped.iter().enumerate() {
864 let sep = (cp.0 - ref_v0.0) * ref_nx + (cp.1 - ref_v0.1) * ref_ny;
866
867 if sep <= CLIP_TOLERANCE {
868 let penetration = -sep;
870
871 let (local_a, local_b) = if flip {
872 (world_to_local(inc_body, cp.0, cp.1), world_to_local(ref_body, cp.0, cp.1))
873 } else {
874 (world_to_local(ref_body, cp.0, cp.1), world_to_local(inc_body, cp.0, cp.1))
875 };
876
877 let id = ContactID::new(ref_edge as u8, inc_edge as u8, i as u8);
878 points.push(ManifoldPoint::new(local_a, local_b, penetration, id));
879 }
880 }
881
882 if points.is_empty() {
883 return None;
884 }
885
886 let (final_nx, final_ny) = if flip {
888 (-ref_nx, -ref_ny)
889 } else {
890 (ref_nx, ref_ny)
891 };
892
893 let dir_x = b.x - a.x;
895 let dir_y = b.y - a.y;
896 let (final_nx, final_ny) = if dir_x * final_nx + dir_y * final_ny < 0.0 {
897 (-final_nx, -final_ny)
898 } else {
899 (final_nx, final_ny)
900 };
901
902 Some(ContactManifold {
903 body_a: a.id,
904 body_b: b.id,
905 normal: (final_nx, final_ny),
906 points,
907 tangent: (-final_ny, final_nx),
908 velocity_bias: 0.0,
909 })
910}
911
912fn circle_vs_polygon_manifold(circle: &RigidBody, poly: &RigidBody, swapped: bool) -> Option<ContactManifold> {
913 let radius = match circle.shape {
914 Shape::Circle { radius } => radius,
915 _ => return None,
916 };
917 let verts = get_world_vertices(poly);
918 if verts.len() < 3 {
919 return None;
920 }
921
922 let mut closest_dist_sq = f32::MAX;
924 let mut closest_point = (0.0f32, 0.0f32);
925
926 let n = verts.len();
927 for i in 0..n {
928 let (ax, ay) = verts[i];
929 let (bx, by) = verts[(i + 1) % n];
930 let (cx, cy) = closest_point_on_segment(circle.x, circle.y, ax, ay, bx, by);
931 let dx = circle.x - cx;
932 let dy = circle.y - cy;
933 let d2 = dx * dx + dy * dy;
934 if d2 < closest_dist_sq {
935 closest_dist_sq = d2;
936 closest_point = (cx, cy);
937 }
938 }
939
940 let inside = point_in_polygon(circle.x, circle.y, &verts);
941 let dist = closest_dist_sq.sqrt();
942
943 if !inside && dist >= radius {
944 return None;
945 }
946
947 let (nx, ny, penetration) = if inside {
948 let dx = circle.x - closest_point.0;
949 let dy = circle.y - closest_point.1;
950 let len = (dx * dx + dy * dy).sqrt();
951 if len > 1e-8 {
952 (-dx / len, -dy / len, radius + dist)
953 } else {
954 (1.0, 0.0, radius)
955 }
956 } else {
957 let dx = circle.x - closest_point.0;
958 let dy = circle.y - closest_point.1;
959 (dx / dist, dy / dist, radius - dist)
960 };
961
962 let (body_a, body_b, fnx, fny) = if swapped {
963 (poly, circle, -nx, -ny)
964 } else {
965 (circle, poly, nx, ny)
966 };
967
968 let dir_x = body_b.x - body_a.x;
970 let dir_y = body_b.y - body_a.y;
971 let (fnx, fny) = if fnx * dir_x + fny * dir_y < 0.0 {
972 (-fnx, -fny)
973 } else {
974 (fnx, fny)
975 };
976
977 let local_a = world_to_local(body_a, closest_point.0, closest_point.1);
978 let local_b = world_to_local(body_b, closest_point.0, closest_point.1);
979
980 Some(ContactManifold {
981 body_a: body_a.id,
982 body_b: body_b.id,
983 normal: (fnx, fny),
984 points: vec![ManifoldPoint::new(local_a, local_b, penetration, ContactID::circle())],
985 tangent: (-fny, fnx),
986 velocity_bias: 0.0,
987 })
988}
989
990fn aabb_vs_polygon_manifold(aabb: &RigidBody, poly: &RigidBody, swapped: bool) -> Option<ContactManifold> {
991 let (hw, hh) = match aabb.shape {
992 Shape::AABB { half_w, half_h } => (half_w, half_h),
993 _ => return None,
994 };
995
996 let aabb_as_poly = RigidBody {
998 shape: Shape::Polygon {
999 vertices: vec![(-hw, -hh), (hw, -hh), (hw, hh), (-hw, hh)],
1000 },
1001 ..aabb.clone()
1002 };
1003
1004 let mut manifold = polygon_vs_polygon_manifold(&aabb_as_poly, poly)?;
1005
1006 if swapped {
1007 std::mem::swap(&mut manifold.body_a, &mut manifold.body_b);
1009 manifold.normal = (-manifold.normal.0, -manifold.normal.1);
1010 manifold.tangent = (-manifold.tangent.0, -manifold.tangent.1);
1011
1012 for point in &mut manifold.points {
1014 std::mem::swap(&mut point.local_a, &mut point.local_b);
1015 }
1016 } else {
1017 manifold.body_a = aabb.id;
1019 }
1020
1021 Some(manifold)
1022}
1023
1024pub fn test_collision_manifold_speculative(
1033 a: &RigidBody,
1034 b: &RigidBody,
1035 margin: f32,
1036) -> Option<ContactManifold> {
1037 if let Some(manifold) = test_collision_manifold(a, b) {
1039 return Some(manifold);
1040 }
1041
1042 match (&a.shape, &b.shape) {
1045 (Shape::Circle { .. }, Shape::Circle { .. }) => {
1046 circle_vs_circle_speculative(a, b, margin)
1047 }
1048 (Shape::Circle { .. }, Shape::AABB { .. }) => {
1049 circle_vs_aabb_speculative(a, b, margin, false)
1050 }
1051 (Shape::AABB { .. }, Shape::Circle { .. }) => {
1052 circle_vs_aabb_speculative(b, a, margin, true)
1053 }
1054 (Shape::AABB { .. }, Shape::AABB { .. }) => {
1055 aabb_vs_aabb_speculative(a, b, margin)
1056 }
1057 (Shape::Polygon { .. }, Shape::Polygon { .. }) => {
1058 polygon_vs_polygon_speculative(a, b, margin)
1059 }
1060 (Shape::Circle { .. }, Shape::Polygon { .. }) => {
1061 circle_vs_polygon_speculative(a, b, margin, false)
1062 }
1063 (Shape::Polygon { .. }, Shape::Circle { .. }) => {
1064 circle_vs_polygon_speculative(b, a, margin, true)
1065 }
1066 (Shape::AABB { half_w, half_h }, Shape::Polygon { .. }) => {
1068 let aabb_as_poly = RigidBody {
1069 shape: Shape::Polygon {
1070 vertices: vec![(-half_w, -half_h), (*half_w, -half_h), (*half_w, *half_h), (-half_w, *half_h)],
1071 },
1072 ..a.clone()
1073 };
1074 let mut result = polygon_vs_polygon_speculative(&aabb_as_poly, b, margin)?;
1075 result.body_a = a.id;
1076 Some(result)
1077 }
1078 (Shape::Polygon { .. }, Shape::AABB { half_w, half_h }) => {
1079 let aabb_as_poly = RigidBody {
1080 shape: Shape::Polygon {
1081 vertices: vec![(-half_w, -half_h), (*half_w, -half_h), (*half_w, *half_h), (-half_w, *half_h)],
1082 },
1083 ..b.clone()
1084 };
1085 let mut result = polygon_vs_polygon_speculative(a, &aabb_as_poly, margin)?;
1086 result.body_b = b.id;
1087 Some(result)
1088 }
1089 }
1090}
1091
1092fn circle_vs_circle_speculative(a: &RigidBody, b: &RigidBody, margin: f32) -> Option<ContactManifold> {
1094 let ra = match a.shape {
1095 Shape::Circle { radius } => radius,
1096 _ => return None,
1097 };
1098 let rb = match b.shape {
1099 Shape::Circle { radius } => radius,
1100 _ => return None,
1101 };
1102
1103 let dx = b.x - a.x;
1104 let dy = b.y - a.y;
1105 let dist = (dx * dx + dy * dy).sqrt();
1106 let sum_r = ra + rb;
1107 let separation = dist - sum_r;
1108
1109 if separation <= 0.0 || separation > margin {
1111 return None;
1112 }
1113
1114 let (nx, ny) = if dist > 1e-8 {
1116 (dx / dist, dy / dist)
1117 } else {
1118 (1.0, 0.0)
1119 };
1120
1121 let cpx = a.x + nx * ra;
1123 let cpy = a.y + ny * ra;
1124
1125 let local_a = world_to_local(a, cpx, cpy);
1126 let local_b = world_to_local(b, cpx, cpy);
1127
1128 Some(ContactManifold {
1130 body_a: a.id,
1131 body_b: b.id,
1132 normal: (nx, ny),
1133 points: vec![ManifoldPoint::new(local_a, local_b, -separation, ContactID::circle())],
1134 tangent: (-ny, nx),
1135 velocity_bias: 0.0,
1136 })
1137}
1138
1139fn circle_vs_aabb_speculative(
1141 circle: &RigidBody,
1142 aabb: &RigidBody,
1143 margin: f32,
1144 swapped: bool,
1145) -> Option<ContactManifold> {
1146 let radius = match circle.shape {
1147 Shape::Circle { radius } => radius,
1148 _ => return None,
1149 };
1150 let (hw, hh) = match aabb.shape {
1151 Shape::AABB { half_w, half_h } => (half_w, half_h),
1152 _ => return None,
1153 };
1154
1155 let local_x = circle.x - aabb.x;
1157 let local_y = circle.y - aabb.y;
1158
1159 let closest_x = local_x.clamp(-hw, hw);
1161 let closest_y = local_y.clamp(-hh, hh);
1162
1163 let dx = local_x - closest_x;
1164 let dy = local_y - closest_y;
1165 let dist_sq = dx * dx + dy * dy;
1166 let dist = dist_sq.sqrt();
1167
1168 let separation = dist - radius;
1170
1171 if separation <= 0.0 || separation > margin {
1173 return None;
1174 }
1175
1176 let (nx, ny) = if dist > 1e-8 {
1178 (dx / dist, dy / dist)
1179 } else {
1180 (1.0, 0.0)
1181 };
1182
1183 let cpx = aabb.x + closest_x;
1184 let cpy = aabb.y + closest_y;
1185
1186 let (body_a, body_b, fnx, fny) = if swapped {
1187 (aabb, circle, nx, ny)
1188 } else {
1189 (circle, aabb, -nx, -ny)
1190 };
1191
1192 let local_a = world_to_local(body_a, cpx, cpy);
1193 let local_b = world_to_local(body_b, cpx, cpy);
1194
1195 Some(ContactManifold {
1196 body_a: body_a.id,
1197 body_b: body_b.id,
1198 normal: (fnx, fny),
1199 points: vec![ManifoldPoint::new(local_a, local_b, -separation, ContactID::circle())],
1200 tangent: (-fny, fnx),
1201 velocity_bias: 0.0,
1202 })
1203}
1204
1205fn aabb_vs_aabb_speculative(a: &RigidBody, b: &RigidBody, margin: f32) -> Option<ContactManifold> {
1207 let (ahw, ahh) = match a.shape {
1208 Shape::AABB { half_w, half_h } => (half_w, half_h),
1209 _ => return None,
1210 };
1211 let (bhw, bhh) = match b.shape {
1212 Shape::AABB { half_w, half_h } => (half_w, half_h),
1213 _ => return None,
1214 };
1215
1216 let dx = b.x - a.x;
1217 let dy = b.y - a.y;
1218
1219 let sep_x = dx.abs() - (ahw + bhw);
1221 let sep_y = dy.abs() - (ahh + bhh);
1222
1223 let separation = sep_x.max(sep_y);
1225
1226 if separation <= 0.0 || separation > margin {
1227 return None;
1228 }
1229
1230 let (nx, ny, sep) = if sep_x > sep_y {
1232 let nx = if dx >= 0.0 { 1.0 } else { -1.0 };
1234 (nx, 0.0, sep_x)
1235 } else {
1236 let ny = if dy >= 0.0 { 1.0 } else { -1.0 };
1238 (0.0, ny, sep_y)
1239 };
1240
1241 let cpx = a.x + nx * ahw;
1243 let cpy = a.y + ny * ahh;
1244
1245 let local_a = world_to_local(a, cpx, cpy);
1246 let local_b = world_to_local(b, cpx, cpy);
1247
1248 Some(ContactManifold {
1249 body_a: a.id,
1250 body_b: b.id,
1251 normal: (nx, ny),
1252 points: vec![ManifoldPoint::new(local_a, local_b, -sep, ContactID::new(0, 0, 0))],
1253 tangent: (-ny, nx),
1254 velocity_bias: 0.0,
1255 })
1256}
1257
1258fn polygon_vs_polygon_speculative(a: &RigidBody, b: &RigidBody, margin: f32) -> Option<ContactManifold> {
1260 let verts_a = get_world_vertices(a);
1261 let verts_b = get_world_vertices(b);
1262
1263 if verts_a.len() < 3 || verts_b.len() < 3 {
1264 return None;
1265 }
1266
1267 let (sep_a, edge_a) = find_max_separation(&verts_a, &verts_b);
1269 let (sep_b, edge_b) = find_max_separation(&verts_b, &verts_a);
1270
1271 let separation = sep_a.max(sep_b);
1273
1274 if separation <= 0.0 || separation > margin {
1276 return None;
1277 }
1278
1279 let (ref_verts, inc_verts, ref_edge, _ref_body, _inc_body, flip) = if sep_a >= sep_b {
1281 (&verts_a, &verts_b, edge_a, a, b, false)
1282 } else {
1283 (&verts_b, &verts_a, edge_b, b, a, true)
1284 };
1285
1286 let n = ref_verts.len();
1288 let ref_v0 = ref_verts[ref_edge];
1289 let ref_v1 = ref_verts[(ref_edge + 1) % n];
1290
1291 let ref_ex = ref_v1.0 - ref_v0.0;
1292 let ref_ey = ref_v1.1 - ref_v0.1;
1293 let ref_len = (ref_ex * ref_ex + ref_ey * ref_ey).sqrt();
1294 if ref_len < 1e-8 {
1295 return None;
1296 }
1297 let ref_nx = ref_ey / ref_len;
1298 let ref_ny = -ref_ex / ref_len;
1299
1300 let mut min_proj = f32::MAX;
1302 let mut closest_point = inc_verts[0];
1303 for &v in inc_verts {
1304 let proj = (v.0 - ref_v0.0) * ref_nx + (v.1 - ref_v0.1) * ref_ny;
1305 if proj < min_proj {
1306 min_proj = proj;
1307 closest_point = v;
1308 }
1309 }
1310
1311 let (final_nx, final_ny) = if flip {
1313 (-ref_nx, -ref_ny)
1314 } else {
1315 (ref_nx, ref_ny)
1316 };
1317
1318 let dir_x = b.x - a.x;
1320 let dir_y = b.y - a.y;
1321 let (final_nx, final_ny) = if dir_x * final_nx + dir_y * final_ny < 0.0 {
1322 (-final_nx, -final_ny)
1323 } else {
1324 (final_nx, final_ny)
1325 };
1326
1327 let local_a = world_to_local(a, closest_point.0, closest_point.1);
1328 let local_b = world_to_local(b, closest_point.0, closest_point.1);
1329
1330 Some(ContactManifold {
1331 body_a: a.id,
1332 body_b: b.id,
1333 normal: (final_nx, final_ny),
1334 points: vec![ManifoldPoint::new(local_a, local_b, -separation, ContactID::new(ref_edge as u8, 0, 0))],
1335 tangent: (-final_ny, final_nx),
1336 velocity_bias: 0.0,
1337 })
1338}
1339
1340fn circle_vs_polygon_speculative(
1342 circle: &RigidBody,
1343 poly: &RigidBody,
1344 margin: f32,
1345 swapped: bool,
1346) -> Option<ContactManifold> {
1347 let radius = match circle.shape {
1348 Shape::Circle { radius } => radius,
1349 _ => return None,
1350 };
1351 let verts = get_world_vertices(poly);
1352 if verts.len() < 3 {
1353 return None;
1354 }
1355
1356 let mut closest_dist_sq = f32::MAX;
1358 let mut closest_point = (0.0f32, 0.0f32);
1359
1360 let n = verts.len();
1361 for i in 0..n {
1362 let (ax, ay) = verts[i];
1363 let (bx, by) = verts[(i + 1) % n];
1364 let (cx, cy) = closest_point_on_segment(circle.x, circle.y, ax, ay, bx, by);
1365 let dx = circle.x - cx;
1366 let dy = circle.y - cy;
1367 let d2 = dx * dx + dy * dy;
1368 if d2 < closest_dist_sq {
1369 closest_dist_sq = d2;
1370 closest_point = (cx, cy);
1371 }
1372 }
1373
1374 if point_in_polygon(circle.x, circle.y, &verts) {
1376 return None;
1377 }
1378
1379 let dist = closest_dist_sq.sqrt();
1380 let separation = dist - radius;
1381
1382 if separation <= 0.0 || separation > margin {
1384 return None;
1385 }
1386
1387 let dx = circle.x - closest_point.0;
1389 let dy = circle.y - closest_point.1;
1390 let (nx, ny) = if dist > 1e-8 {
1391 (dx / dist, dy / dist)
1392 } else {
1393 (1.0, 0.0)
1394 };
1395
1396 let (body_a, body_b, fnx, fny) = if swapped {
1397 (poly, circle, -nx, -ny)
1398 } else {
1399 (circle, poly, nx, ny)
1400 };
1401
1402 let dir_x = body_b.x - body_a.x;
1404 let dir_y = body_b.y - body_a.y;
1405 let (fnx, fny) = if fnx * dir_x + fny * dir_y < 0.0 {
1406 (-fnx, -fny)
1407 } else {
1408 (fnx, fny)
1409 };
1410
1411 let local_a = world_to_local(body_a, closest_point.0, closest_point.1);
1412 let local_b = world_to_local(body_b, closest_point.0, closest_point.1);
1413
1414 Some(ContactManifold {
1415 body_a: body_a.id,
1416 body_b: body_b.id,
1417 normal: (fnx, fny),
1418 points: vec![ManifoldPoint::new(local_a, local_b, -separation, ContactID::circle())],
1419 tangent: (-fny, fnx),
1420 velocity_bias: 0.0,
1421 })
1422}