1use crate::primitives::{AABB2, AABB3};
16
17pub fn polygons_overlap(poly_a: &[(f64, f64)], poly_b: &[(f64, f64)]) -> bool {
31 polygons_overlap_with_tolerance(poly_a, poly_b, 1e-6)
32}
33
34pub fn polygons_overlap_with_tolerance(
38 poly_a: &[(f64, f64)],
39 poly_b: &[(f64, f64)],
40 tolerance: f64,
41) -> bool {
42 if poly_a.len() < 3 || poly_b.len() < 3 {
43 return false;
44 }
45
46 if let (Some(aabb_a), Some(aabb_b)) = (aabb_from_tuples(poly_a), aabb_from_tuples(poly_b)) {
48 let expanded_a = aabb_a.expand(tolerance);
49 if !expanded_a.intersects(&aabb_b) {
50 return false;
51 }
52 }
53
54 for polygon in [poly_a, poly_b] {
56 let n = polygon.len();
57 for i in 0..n {
58 let j = (i + 1) % n;
59 let edge_x = polygon[j].0 - polygon[i].0;
60 let edge_y = polygon[j].1 - polygon[i].1;
61
62 let len = (edge_x * edge_x + edge_y * edge_y).sqrt();
64 if len < 1e-15 {
65 continue;
66 }
67 let axis = (-edge_y / len, edge_x / len);
68
69 let (min_a, max_a) = project_on_axis(poly_a, axis);
71 let (min_b, max_b) = project_on_axis(poly_b, axis);
72
73 let overlap = max_a.min(max_b) - min_a.max(min_b);
77 if overlap < tolerance {
78 return false; }
80 }
81 }
82
83 true }
85
86pub fn overlap_depth(poly_a: &[(f64, f64)], poly_b: &[(f64, f64)]) -> Option<f64> {
94 if poly_a.len() < 3 || poly_b.len() < 3 {
95 return None;
96 }
97
98 let mut min_depth = f64::INFINITY;
99
100 for polygon in [poly_a, poly_b] {
101 let n = polygon.len();
102 for i in 0..n {
103 let j = (i + 1) % n;
104 let edge_x = polygon[j].0 - polygon[i].0;
105 let edge_y = polygon[j].1 - polygon[i].1;
106
107 let len = (edge_x * edge_x + edge_y * edge_y).sqrt();
108 if len < 1e-15 {
109 continue;
110 }
111 let axis = (-edge_y / len, edge_x / len);
112
113 let (min_a, max_a) = project_on_axis(poly_a, axis);
114 let (min_b, max_b) = project_on_axis(poly_b, axis);
115
116 let overlap = (max_a.min(max_b) - min_a.max(min_b)).max(0.0);
117 if overlap <= 0.0 {
118 return None; }
120 min_depth = min_depth.min(overlap);
121 }
122 }
123
124 if min_depth < f64::INFINITY {
125 Some(min_depth)
126 } else {
127 None
128 }
129}
130
131#[inline]
136pub fn aabb_overlap(a: &AABB2, b: &AABB2) -> bool {
137 a.intersects(b)
138}
139
140#[inline]
142fn project_on_axis(polygon: &[(f64, f64)], axis: (f64, f64)) -> (f64, f64) {
143 let mut min_proj = f64::INFINITY;
144 let mut max_proj = f64::NEG_INFINITY;
145
146 for &(x, y) in polygon {
147 let proj = x * axis.0 + y * axis.1;
148 min_proj = min_proj.min(proj);
149 max_proj = max_proj.max(proj);
150 }
151
152 (min_proj, max_proj)
153}
154
155#[inline]
162pub fn aabb3_overlap(a: &AABB3, b: &AABB3) -> bool {
163 a.intersects(b)
164}
165
166pub fn aabb3_overlap_with_tolerance(a: &AABB3, b: &AABB3, tolerance: f64) -> bool {
174 let overlap_x = a.max.x.min(b.max.x) - a.min.x.max(b.min.x);
175 let overlap_y = a.max.y.min(b.max.y) - a.min.y.max(b.min.y);
176 let overlap_z = a.max.z.min(b.max.z) - a.min.z.max(b.min.z);
177 overlap_x > tolerance && overlap_y > tolerance && overlap_z > tolerance
178}
179
180#[inline]
187pub fn aabb3_within(inner: &AABB3, boundary: &AABB3) -> bool {
188 boundary.contains(inner)
189}
190
191pub fn aabb3_within_with_margin(inner: &AABB3, boundary: &AABB3, margin: f64) -> bool {
198 inner.min.x >= boundary.min.x + margin
199 && inner.min.y >= boundary.min.y + margin
200 && inner.min.z >= boundary.min.z + margin
201 && inner.max.x <= boundary.max.x - margin
202 && inner.max.y <= boundary.max.y - margin
203 && inner.max.z <= boundary.max.z - margin
204}
205
206fn aabb_from_tuples(points: &[(f64, f64)]) -> Option<AABB2> {
208 let first = points.first()?;
209 let mut min_x = first.0;
210 let mut min_y = first.1;
211 let mut max_x = first.0;
212 let mut max_y = first.1;
213
214 for &(x, y) in points.iter().skip(1) {
215 min_x = min_x.min(x);
216 min_y = min_y.min(y);
217 max_x = max_x.max(x);
218 max_y = max_y.max(y);
219 }
220
221 Some(AABB2::new(min_x, min_y, max_x, max_y))
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 fn square(x: f64, y: f64, size: f64) -> Vec<(f64, f64)> {
229 vec![(x, y), (x + size, y), (x + size, y + size), (x, y + size)]
230 }
231
232 fn triangle(x: f64, y: f64, size: f64) -> Vec<(f64, f64)> {
233 vec![(x, y), (x + size, y), (x + size / 2.0, y + size)]
234 }
235
236 #[test]
237 fn test_overlapping_squares() {
238 let a = square(0.0, 0.0, 10.0);
239 let b = square(5.0, 5.0, 10.0);
240 assert!(polygons_overlap(&a, &b));
241 }
242
243 #[test]
244 fn test_separated_squares() {
245 let a = square(0.0, 0.0, 10.0);
246 let b = square(20.0, 0.0, 10.0);
247 assert!(!polygons_overlap(&a, &b));
248 }
249
250 #[test]
251 fn test_touching_squares() {
252 let a = square(0.0, 0.0, 10.0);
254 let b = square(10.0, 0.0, 10.0);
255 assert!(!polygons_overlap(&a, &b));
256 }
257
258 #[test]
259 fn test_contained_square() {
260 let a = square(0.0, 0.0, 10.0);
261 let b = square(2.0, 2.0, 3.0);
262 assert!(polygons_overlap(&a, &b));
263 }
264
265 #[test]
266 fn test_triangle_overlap() {
267 let a = triangle(0.0, 0.0, 10.0);
268 let b = triangle(5.0, 0.0, 10.0);
269 assert!(polygons_overlap(&a, &b));
270 }
271
272 #[test]
273 fn test_triangle_no_overlap() {
274 let a = triangle(0.0, 0.0, 10.0);
275 let b = triangle(20.0, 0.0, 10.0);
276 assert!(!polygons_overlap(&a, &b));
277 }
278
279 #[test]
280 fn test_degenerate_polygons() {
281 let a = vec![(0.0, 0.0), (1.0, 0.0)]; let b = square(0.0, 0.0, 10.0);
283 assert!(!polygons_overlap(&a, &b));
284 }
285
286 #[test]
287 fn test_tolerance_effect() {
288 let a = square(0.0, 0.0, 10.0);
289 let b = square(9.5, 0.0, 10.0);
291 assert!(polygons_overlap(&a, &b));
293 assert!(!polygons_overlap_with_tolerance(&a, &b, 1.0));
295 }
296
297 #[test]
298 fn test_overlap_depth_overlapping() {
299 let a = square(0.0, 0.0, 10.0);
300 let b = square(7.0, 0.0, 10.0);
301 let depth = overlap_depth(&a, &b);
302 assert!(depth.is_some());
303 assert!((depth.unwrap() - 3.0).abs() < 1e-10);
304 }
305
306 #[test]
307 fn test_overlap_depth_separated() {
308 let a = square(0.0, 0.0, 10.0);
309 let b = square(20.0, 0.0, 10.0);
310 assert!(overlap_depth(&a, &b).is_none());
311 }
312
313 #[test]
314 fn test_aabb_overlap() {
315 let a = AABB2::new(0.0, 0.0, 10.0, 10.0);
316 let b = AABB2::new(5.0, 5.0, 15.0, 15.0);
317 assert!(aabb_overlap(&a, &b));
318
319 let c = AABB2::new(20.0, 20.0, 30.0, 30.0);
320 assert!(!aabb_overlap(&a, &c));
321 }
322
323 fn box3d(x: f64, y: f64, z: f64, w: f64, d: f64, h: f64) -> AABB3 {
326 AABB3::new(x, y, z, x + w, y + d, z + h)
327 }
328
329 #[test]
330 fn test_aabb3_overlap_intersecting() {
331 let a = box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
332 let b = box3d(5.0, 5.0, 5.0, 10.0, 10.0, 10.0);
333 assert!(aabb3_overlap(&a, &b));
334 }
335
336 #[test]
337 fn test_aabb3_overlap_separated() {
338 let a = box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
339 let b = box3d(20.0, 0.0, 0.0, 10.0, 10.0, 10.0);
340 assert!(!aabb3_overlap(&a, &b));
341 }
342
343 #[test]
344 fn test_aabb3_overlap_touching() {
345 let a = box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
347 let b = box3d(10.0, 0.0, 0.0, 10.0, 10.0, 10.0);
348 assert!(aabb3_overlap(&a, &b));
349 }
350
351 #[test]
352 fn test_aabb3_overlap_with_tolerance() {
353 let a = box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
354 let b = box3d(10.0, 0.0, 0.0, 10.0, 10.0, 10.0);
355 assert!(!aabb3_overlap_with_tolerance(&a, &b, 1e-6));
357
358 let c = box3d(9.5, 0.0, 0.0, 10.0, 10.0, 10.0);
360 assert!(aabb3_overlap_with_tolerance(&a, &c, 1e-6));
361 assert!(!aabb3_overlap_with_tolerance(&a, &c, 1.0));
363 }
364
365 #[test]
366 fn test_aabb3_within() {
367 let outer = box3d(0.0, 0.0, 0.0, 20.0, 20.0, 20.0);
368 let inner = box3d(5.0, 5.0, 5.0, 5.0, 5.0, 5.0);
369 assert!(aabb3_within(&inner, &outer));
370 assert!(!aabb3_within(&outer, &inner));
371 }
372
373 #[test]
374 fn test_aabb3_within_with_margin() {
375 let boundary = box3d(0.0, 0.0, 0.0, 20.0, 20.0, 20.0);
376 let inner = box3d(2.0, 2.0, 2.0, 5.0, 5.0, 5.0);
377 assert!(aabb3_within_with_margin(&inner, &boundary, 1.0));
378 assert!(!aabb3_within_with_margin(&inner, &boundary, 3.0));
380 }
381
382 #[test]
383 fn test_aabb3_overlap_z_separated() {
384 let a = box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
386 let b = box3d(5.0, 5.0, 20.0, 10.0, 10.0, 10.0);
387 assert!(!aabb3_overlap(&a, &b));
388 }
389
390 #[test]
401 fn test_sat_separated_no_collision() {
402 let a = square(0.0, 0.0, 5.0);
404 let b = square(10.0, 0.0, 5.0); assert!(
406 !polygons_overlap(&a, &b),
407 "clearly separated polygons must not overlap"
408 );
409 }
410
411 #[test]
412 fn test_sat_overlapping_collision() {
413 let a = square(0.0, 0.0, 6.0);
415 let b = square(4.0, 0.0, 6.0); assert!(
417 polygons_overlap(&a, &b),
418 "overlapping polygons must report collision"
419 );
420 }
421
422 #[test]
423 fn test_sat_fully_contained_collision() {
424 let outer = square(0.0, 0.0, 10.0);
426 let inner = square(3.0, 3.0, 3.0);
427 assert!(
428 polygons_overlap(&outer, &inner),
429 "fully contained polygon must report collision"
430 );
431 assert!(
433 polygons_overlap(&inner, &outer),
434 "collision must be symmetric"
435 );
436 }
437
438 #[test]
439 fn test_sat_touching_boundary_no_collision() {
440 let a = square(0.0, 0.0, 5.0);
442 let b = square(5.0, 0.0, 5.0); assert!(
444 !polygons_overlap(&a, &b),
445 "boundary-touching polygons must not report collision (tolerance=1e-6)"
446 );
447 }
448
449 #[test]
450 fn test_sat_symmetry() {
451 let a = square(0.0, 0.0, 7.0);
453 let b = square(5.0, 3.0, 7.0);
454 assert_eq!(
455 polygons_overlap(&a, &b),
456 polygons_overlap(&b, &a),
457 "collision detection must be symmetric"
458 );
459 }
460
461 #[test]
462 fn test_sat_self_overlap() {
463 let a = square(0.0, 0.0, 5.0);
465 assert!(polygons_overlap(&a, &a), "polygon must overlap with itself");
466 }
467}