parry3d/transformation/polygon_intersection.rs
1use crate::math::Vector2;
2use alloc::{vec, vec::Vec};
3use ordered_float::OrderedFloat;
4
5use crate::math::Real;
6use crate::shape::{SegmentPointLocation, Triangle, TriangleOrientation};
7use crate::utils::hashmap::HashMap;
8use crate::utils::{self, SegmentsIntersection};
9
10/// Tolerances for polygon intersection algorithms.
11///
12/// These tolerances control how the intersection algorithm handles numerical precision
13/// issues when determining geometric relationships between points, lines, and polygons.
14///
15/// # Examples
16///
17/// ```
18/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
19/// # use parry2d::transformation::PolygonIntersectionTolerances;
20/// // Use default tolerances (recommended for most cases)
21/// let default_tol = PolygonIntersectionTolerances::default();
22///
23/// // Or create custom tolerances for special cases
24/// let custom_tol = PolygonIntersectionTolerances {
25/// collinearity_epsilon: 1.0e-5,
26/// };
27/// # }
28/// ```
29#[derive(Copy, Clone, PartialEq, Debug)]
30pub struct PolygonIntersectionTolerances {
31 /// The epsilon given to [`Triangle::orientation2d`] for detecting if three points are collinear.
32 ///
33 /// Three points forming a triangle with an area smaller than this value are considered collinear.
34 pub collinearity_epsilon: Real,
35}
36
37impl Default for PolygonIntersectionTolerances {
38 fn default() -> Self {
39 Self {
40 collinearity_epsilon: Real::EPSILON * 100.0,
41 }
42 }
43}
44
45#[derive(Copy, Clone, Debug, PartialEq, Eq)]
46enum InFlag {
47 // The current neighborhood of the traversed point on poly1 is inside poly2.
48 Poly1IsInside,
49 // The current neighborhood of the traversed point on poly2 is inside poly1.
50 Poly2IsInside,
51 Unknown,
52}
53
54/// Location of a point on a polyline.
55///
56/// This enum represents where a point lies on a polygon's boundary. It's used by
57/// the intersection algorithms to precisely describe intersection points.
58///
59/// # Variants
60///
61/// * `OnVertex(i)` - The point is exactly on vertex `i` of the polygon
62/// * `OnEdge(i, j, bcoords)` - The point lies on the edge between vertices `i` and `j`,
63/// with barycentric coordinates `bcoords` where `bcoords[0] + bcoords[1] = 1.0`
64///
65/// # Examples
66///
67/// ```
68/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
69/// # use parry2d::transformation::PolylinePointLocation;
70/// # use parry2d::math::Vector;
71/// let polygon = vec![
72/// Vector::ZERO,
73/// Vector::new(2.0, 0.0),
74/// Vector::new(2.0, 2.0),
75/// Vector::new(0.0, 2.0),
76/// ];
77///
78/// // A point on vertex 0
79/// let loc1 = PolylinePointLocation::OnVertex(0);
80/// let pt1 = loc1.to_point(&polygon);
81/// assert_eq!(pt1, Vector::ZERO);
82///
83/// // A point halfway along the edge from vertex 0 to vertex 1
84/// let loc2 = PolylinePointLocation::OnEdge(0, 1, [0.5, 0.5]);
85/// let pt2 = loc2.to_point(&polygon);
86/// assert_eq!(pt2, Vector::new(1.0, 0.0));
87/// # }
88/// ```
89#[derive(Copy, Clone, Debug, PartialEq)]
90pub enum PolylinePointLocation {
91 /// Vector on a vertex.
92 OnVertex(usize),
93 /// Vector on an edge.
94 OnEdge(usize, usize, [Real; 2]),
95}
96
97impl PolylinePointLocation {
98 /// The barycentric coordinates such that the point in the intersected segment `[a, b]` is
99 /// equal to `a + (b - a) * centered_bcoords`.
100 fn centered_bcoords(&self, edge: [usize; 2]) -> Real {
101 match self {
102 Self::OnVertex(vid) => {
103 if *vid == edge[0] {
104 0.0
105 } else {
106 1.0
107 }
108 }
109 Self::OnEdge(ia, ib, bcoords) => {
110 assert_eq!([*ia, *ib], edge);
111 bcoords[1]
112 }
113 }
114 }
115
116 /// Computes the point corresponding to this location.
117 ///
118 /// Given a polygon (as a slice of points), this method converts the location
119 /// into an actual 2D point coordinate.
120 ///
121 /// # Arguments
122 ///
123 /// * `pts` - The vertices of the polygon
124 ///
125 /// # Examples
126 ///
127 /// ```
128 /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
129 /// # use parry2d::transformation::PolylinePointLocation;
130 /// # use parry2d::math::Vector;
131 /// let polygon = vec![
132 /// Vector::ZERO,
133 /// Vector::new(4.0, 0.0),
134 /// Vector::new(4.0, 4.0),
135 /// ];
136 ///
137 /// let loc = PolylinePointLocation::OnEdge(0, 1, [0.75, 0.25]);
138 /// let point = loc.to_point(&polygon);
139 /// assert_eq!(point, Vector::new(1.0, 0.0)); // 75% of vertex 0 + 25% of vertex 1
140 /// # }
141 /// ```
142 pub fn to_point(self, pts: &[Vector2]) -> Vector2 {
143 match self {
144 PolylinePointLocation::OnVertex(i) => pts[i],
145 PolylinePointLocation::OnEdge(i1, i2, bcoords) => {
146 pts[i1] * bcoords[0] + pts[i2] * bcoords[1]
147 }
148 }
149 }
150
151 fn from_segment_point_location(a: usize, b: usize, loc: SegmentPointLocation) -> Self {
152 match loc {
153 SegmentPointLocation::OnVertex(0) => PolylinePointLocation::OnVertex(a),
154 SegmentPointLocation::OnVertex(1) => PolylinePointLocation::OnVertex(b),
155 SegmentPointLocation::OnVertex(_) => unreachable!(),
156 SegmentPointLocation::OnEdge(bcoords) => PolylinePointLocation::OnEdge(a, b, bcoords),
157 }
158 }
159}
160
161/// Computes the intersection points of two convex polygons.
162///
163/// This function takes two convex polygons and computes their intersection, returning
164/// the vertices of the resulting intersection polygon. The result is added to the `out` vector.
165///
166/// # Important Notes
167///
168/// - **Convex polygons only**: Both input polygons must be convex. For non-convex polygons,
169/// use [`polygons_intersection_points`] instead.
170/// - **Counter-clockwise winding**: Input polygons should be oriented counter-clockwise.
171/// - **Default tolerances**: Uses default numerical tolerances. For custom tolerances,
172/// use [`convex_polygons_intersection_points_with_tolerances`].
173///
174/// # Arguments
175///
176/// * `poly1` - First convex polygon as a slice of vertices
177/// * `poly2` - Second convex polygon as a slice of vertices
178/// * `out` - Output vector where intersection vertices will be appended
179///
180/// # Examples
181///
182/// ```
183/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
184/// # use parry2d::transformation::convex_polygons_intersection_points;
185/// # use parry2d::math::Vector;
186/// // Define two overlapping squares
187/// let square1 = vec![
188/// Vector::ZERO,
189/// Vector::new(2.0, 0.0),
190/// Vector::new(2.0, 2.0),
191/// Vector::new(0.0, 2.0),
192/// ];
193///
194/// let square2 = vec![
195/// Vector::new(1.0, 1.0),
196/// Vector::new(3.0, 1.0),
197/// Vector::new(3.0, 3.0),
198/// Vector::new(1.0, 3.0),
199/// ];
200///
201/// let mut intersection = Vec::new();
202/// convex_polygons_intersection_points(&square1, &square2, &mut intersection);
203///
204/// // The intersection should be a square from (1,1) to (2,2)
205/// assert_eq!(intersection.len(), 4);
206/// # }
207/// ```
208///
209/// # See Also
210///
211/// * [`convex_polygons_intersection`] - For closure-based output
212/// * [`polygons_intersection_points`] - For non-convex polygons
213pub fn convex_polygons_intersection_points(
214 poly1: &[Vector2],
215 poly2: &[Vector2],
216 out: &mut Vec<Vector2>,
217) {
218 convex_polygons_intersection_points_with_tolerances(poly1, poly2, Default::default(), out);
219}
220
221/// Computes the intersection points of two convex polygons with custom tolerances.
222///
223/// This is the same as [`convex_polygons_intersection_points`] but allows you to specify
224/// custom numerical tolerances for the intersection computation.
225///
226/// # Arguments
227///
228/// * `poly1` - First convex polygon as a slice of vertices
229/// * `poly2` - Second convex polygon as a slice of vertices
230/// * `tolerances` - Custom tolerances for numerical precision
231/// * `out` - Output vector where intersection vertices will be appended
232///
233/// # Examples
234///
235/// ```
236/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
237/// # use parry2d::transformation::{convex_polygons_intersection_points_with_tolerances, PolygonIntersectionTolerances};
238/// # use parry2d::math::Vector;
239/// let triangle1 = vec![
240/// Vector::ZERO,
241/// Vector::new(4.0, 0.0),
242/// Vector::new(2.0, 3.0),
243/// ];
244///
245/// let triangle2 = vec![
246/// Vector::new(1.0, 0.5),
247/// Vector::new(3.0, 0.5),
248/// Vector::new(2.0, 2.5),
249/// ];
250///
251/// let mut intersection = Vec::new();
252/// let tolerances = PolygonIntersectionTolerances {
253/// collinearity_epsilon: 1.0e-6,
254/// };
255///
256/// convex_polygons_intersection_points_with_tolerances(
257/// &triangle1,
258/// &triangle2,
259/// tolerances,
260/// &mut intersection
261/// );
262///
263/// // The triangles overlap, so we should get intersection points
264/// assert!(intersection.len() >= 3);
265/// # }
266/// ```
267pub fn convex_polygons_intersection_points_with_tolerances(
268 poly1: &[Vector2],
269 poly2: &[Vector2],
270 tolerances: PolygonIntersectionTolerances,
271 out: &mut Vec<Vector2>,
272) {
273 convex_polygons_intersection_with_tolerances(poly1, poly2, tolerances, |loc1, loc2| {
274 if let Some(loc1) = loc1 {
275 out.push(loc1.to_point(poly1))
276 } else if let Some(loc2) = loc2 {
277 out.push(loc2.to_point(poly2))
278 }
279 })
280}
281
282/// Computes the intersection of two convex polygons with closure-based output.
283///
284/// This function is similar to [`convex_polygons_intersection_points`] but provides more
285/// flexibility by calling a closure for each intersection vertex. The closure receives
286/// the location of the vertex on each polygon (if applicable).
287///
288/// This is useful when you need to track which polygon each intersection vertex comes from,
289/// or when you want to process vertices as they're computed rather than collecting them all.
290///
291/// # Arguments
292///
293/// * `poly1` - First convex polygon as a slice of vertices
294/// * `poly2` - Second convex polygon as a slice of vertices
295/// * `out` - Closure called for each intersection vertex with its location on both polygons
296///
297/// # Closure Arguments
298///
299/// The closure receives `(Option<PolylinePointLocation>, Option<PolylinePointLocation>)`:
300/// - If the point comes from `poly1`, the first option contains its location on `poly1`
301/// - If the point comes from `poly2`, the second option contains its location on `poly2`
302/// - At least one of the options will always be `Some`
303///
304/// # Examples
305///
306/// ```
307/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
308/// # use parry2d::transformation::convex_polygons_intersection;
309/// # use parry2d::math::Vector;
310/// let square = vec![
311/// Vector::ZERO,
312/// Vector::new(2.0, 0.0),
313/// Vector::new(2.0, 2.0),
314/// Vector::new(0.0, 2.0),
315/// ];
316///
317/// let diamond = vec![
318/// Vector::new(1.0, -0.5),
319/// Vector::new(2.5, 1.0),
320/// Vector::new(1.0, 2.5),
321/// Vector::new(-0.5, 1.0),
322/// ];
323///
324/// let mut intersection_points = Vec::new();
325/// convex_polygons_intersection(&square, &diamond, |loc1, loc2| {
326/// if let Some(loc) = loc1 {
327/// intersection_points.push(loc.to_point(&square));
328/// } else if let Some(loc) = loc2 {
329/// intersection_points.push(loc.to_point(&diamond));
330/// }
331/// });
332///
333/// // The intersection should have multiple vertices
334/// assert!(intersection_points.len() >= 3);
335/// # }
336/// ```
337///
338/// # See Also
339///
340/// * [`convex_polygons_intersection_points`] - Simpler vector-based output
341/// * [`convex_polygons_intersection_with_tolerances`] - With custom tolerances
342pub fn convex_polygons_intersection(
343 poly1: &[Vector2],
344 poly2: &[Vector2],
345 out: impl FnMut(Option<PolylinePointLocation>, Option<PolylinePointLocation>),
346) {
347 convex_polygons_intersection_with_tolerances(poly1, poly2, Default::default(), out)
348}
349
350/// Computes the intersection of two convex polygons with custom tolerances and closure-based output.
351///
352/// This is the most flexible version of the convex polygon intersection function, combining
353/// custom tolerances with closure-based output for maximum control.
354///
355/// # Arguments
356///
357/// * `poly1` - First convex polygon as a slice of vertices
358/// * `poly2` - Second convex polygon as a slice of vertices
359/// * `tolerances` - Custom tolerances for numerical precision
360/// * `out` - Closure called for each intersection vertex
361///
362/// # Algorithm
363///
364/// This function implements the Sutherland-Hodgman-like algorithm for convex polygon
365/// intersection. It works by:
366/// 1. Traversing the edges of both polygons simultaneously
367/// 2. Detecting edge-edge intersections
368/// 3. Determining which vertices are inside the other polygon
369/// 4. Outputting the vertices of the intersection polygon in order
370///
371/// # Examples
372///
373/// ```
374/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
375/// # use parry2d::transformation::{convex_polygons_intersection_with_tolerances, PolygonIntersectionTolerances};
376/// # use parry2d::math::Vector;
377/// let hexagon = vec![
378/// Vector::new(2.0, 0.0),
379/// Vector::new(1.0, 1.732),
380/// Vector::new(-1.0, 1.732),
381/// Vector::new(-2.0, 0.0),
382/// Vector::new(-1.0, -1.732),
383/// Vector::new(1.0, -1.732),
384/// ];
385///
386/// let square = vec![
387/// Vector::new(-1.0, -1.0),
388/// Vector::new(1.0, -1.0),
389/// Vector::new(1.0, 1.0),
390/// Vector::new(-1.0, 1.0),
391/// ];
392///
393/// let tolerances = PolygonIntersectionTolerances::default();
394/// let mut intersection_points = Vec::new();
395///
396/// convex_polygons_intersection_with_tolerances(
397/// &hexagon,
398/// &square,
399/// tolerances,
400/// |loc1, loc2| {
401/// if let Some(loc) = loc1 {
402/// intersection_points.push(loc.to_point(&hexagon));
403/// } else if let Some(loc) = loc2 {
404/// intersection_points.push(loc.to_point(&square));
405/// }
406/// }
407/// );
408///
409/// // The intersection should form a polygon
410/// assert!(intersection_points.len() >= 3);
411/// # }
412/// ```
413pub fn convex_polygons_intersection_with_tolerances(
414 poly1: &[Vector2],
415 poly2: &[Vector2],
416 tolerances: PolygonIntersectionTolerances,
417 mut out: impl FnMut(Option<PolylinePointLocation>, Option<PolylinePointLocation>),
418) {
419 // TODO: this does not handle correctly the case where the
420 // first triangle of the polygon is degenerate.
421 let rev1 = poly1.len() > 2
422 && Triangle::orientation2d(
423 poly1[0],
424 poly1[1],
425 poly1[2],
426 tolerances.collinearity_epsilon,
427 ) == TriangleOrientation::Clockwise;
428 let rev2 = poly2.len() > 2
429 && Triangle::orientation2d(
430 poly2[0],
431 poly2[1],
432 poly2[2],
433 tolerances.collinearity_epsilon,
434 ) == TriangleOrientation::Clockwise;
435
436 let len1 = poly1.len();
437 let len2 = poly2.len();
438
439 let mut i1 = 0; // Current index on the first polyline.
440 let mut i2 = 0; // Current index on the second polyline.
441 let mut nsteps1 = 0; // Number of times we advanced on the first polyline.
442 let mut nsteps2 = 0; // Number of times we advanced on the second polyline.
443 let mut inflag = InFlag::Unknown;
444 let mut first_point_found = false;
445
446 // Quit when both adv. indices have cycled, or one has cycled twice.
447 while (nsteps1 < len1 || nsteps2 < len2) && nsteps1 < 2 * len1 && nsteps2 < 2 * len2 {
448 let (a1, b1) = if rev1 {
449 ((len1 - i1) % len1, len1 - i1 - 1)
450 } else {
451 // Vector before `i1`, and point at `i1`.
452 ((i1 + len1 - 1) % len1, i1)
453 };
454
455 let (a2, b2) = if rev2 {
456 ((len2 - i2) % len2, len2 - i2 - 1)
457 } else {
458 // Vector before `i2`, and point at `i2`.
459 ((i2 + len2 - 1) % len2, i2)
460 };
461
462 let dir_edge1 = poly1[b1] - poly1[a1];
463 let dir_edge2 = poly2[b2] - poly2[a2];
464
465 // If there is an intersection, this will determine if the edge from poly2 is transitioning
466 // Left -> Right (CounterClockwise) or Right -> Left (Clockwise) relative to the edge from
467 // poly1.
468 let cross = Triangle::orientation2d(
469 Vector2::ZERO,
470 Vector2::from(dir_edge1),
471 Vector2::from(dir_edge2),
472 tolerances.collinearity_epsilon,
473 );
474 // Determines if b1 is left (CounterClockwise) or right (Clockwise) of [a2, b2].
475 let a2_b2_b1 = Triangle::orientation2d(
476 poly2[a2],
477 poly2[b2],
478 poly1[b1],
479 tolerances.collinearity_epsilon,
480 );
481 // Determines if b2 is left (CounterClockwise) or right (Clockwise) of [a1, b1].
482 let a1_b1_b2 = Triangle::orientation2d(
483 poly1[a1],
484 poly1[b1],
485 poly2[b2],
486 tolerances.collinearity_epsilon,
487 );
488
489 // If edge1 & edge2 intersect, update inflag.
490 if let Some(inter) = utils::segments_intersection2d(
491 poly1[a1],
492 poly1[b1],
493 poly2[a2],
494 poly2[b2],
495 tolerances.collinearity_epsilon,
496 ) {
497 match inter {
498 SegmentsIntersection::Point { loc1, loc2 } => {
499 if a2_b2_b1 != TriangleOrientation::Degenerate
500 && a1_b1_b2 != TriangleOrientation::Degenerate
501 {
502 let loc1 = PolylinePointLocation::from_segment_point_location(a1, b1, loc1);
503 let loc2 = PolylinePointLocation::from_segment_point_location(a2, b2, loc2);
504 out(Some(loc1), Some(loc2));
505
506 if inflag == InFlag::Unknown && !first_point_found {
507 // This is the first point, reset the number of steps since we are
508 // effectively starting the actual traversal now.
509 nsteps1 = 0;
510 nsteps2 = 0;
511 first_point_found = true;
512 }
513
514 if a2_b2_b1 == TriangleOrientation::CounterClockwise {
515 // The point b1 is left of [a2, b2] so it is inside poly2 ???
516 inflag = InFlag::Poly1IsInside;
517 } else if a1_b1_b2 == TriangleOrientation::CounterClockwise {
518 // The point b2 is left of [a1, b1] so it is inside poly1 ???
519 inflag = InFlag::Poly2IsInside;
520 }
521 }
522 }
523 SegmentsIntersection::Segment {
524 first_loc1,
525 first_loc2,
526 second_loc1,
527 second_loc2,
528 } => {
529 if dir_edge1.dot(dir_edge2) < 0.0 {
530 // Special case: edge1 & edge2 overlap and oppositely oriented. The
531 // intersection is degenerate (equals to a segment). Output
532 // the segment and exit.
533 let loc1 =
534 PolylinePointLocation::from_segment_point_location(a1, b1, first_loc1);
535 let loc2 =
536 PolylinePointLocation::from_segment_point_location(a2, b2, first_loc2);
537 out(Some(loc1), Some(loc2));
538
539 let loc1 =
540 PolylinePointLocation::from_segment_point_location(a1, b1, second_loc1);
541 let loc2 =
542 PolylinePointLocation::from_segment_point_location(a2, b2, second_loc2);
543 out(Some(loc1), Some(loc2));
544 return;
545 }
546 }
547 }
548 }
549
550 // Special case: edge1 & edge2 parallel and separated.
551 if cross == TriangleOrientation::Degenerate
552 && a2_b2_b1 == TriangleOrientation::Clockwise
553 && a1_b1_b2 == TriangleOrientation::Clockwise
554 // TODO: should this also include any case where a2_b2_b1 and a1_b1_b2 are both different from Degenerate?
555 {
556 return;
557 }
558 // Special case: edge1 & edge2 collinear.
559 else if cross == TriangleOrientation::Degenerate
560 && a2_b2_b1 == TriangleOrientation::Degenerate
561 && a1_b1_b2 == TriangleOrientation::Degenerate
562 {
563 // Advance but do not output point.
564 if inflag == InFlag::Poly1IsInside {
565 i2 = advance(i2, &mut nsteps2, len2);
566 } else {
567 i1 = advance(i1, &mut nsteps1, len1);
568 }
569 }
570 // Generic cases.
571 else if cross == TriangleOrientation::CounterClockwise {
572 if a1_b1_b2 == TriangleOrientation::CounterClockwise {
573 if inflag == InFlag::Poly1IsInside {
574 out(Some(PolylinePointLocation::OnVertex(b1)), None)
575 }
576 i1 = advance(i1, &mut nsteps1, len1);
577 } else {
578 if inflag == InFlag::Poly2IsInside {
579 out(None, Some(PolylinePointLocation::OnVertex(b2)))
580 }
581 i2 = advance(i2, &mut nsteps2, len2);
582 }
583 } else {
584 // We have cross == TriangleOrientation::Clockwise.
585 if a2_b2_b1 == TriangleOrientation::CounterClockwise {
586 if inflag == InFlag::Poly2IsInside {
587 out(None, Some(PolylinePointLocation::OnVertex(b2)))
588 }
589 i2 = advance(i2, &mut nsteps2, len2);
590 } else {
591 if inflag == InFlag::Poly1IsInside {
592 out(Some(PolylinePointLocation::OnVertex(b1)), None)
593 }
594 i1 = advance(i1, &mut nsteps1, len1);
595 }
596 }
597 }
598
599 if !first_point_found {
600 // No intersection: test if one polygon completely encloses the other.
601 let mut orient = TriangleOrientation::Degenerate;
602 let mut ok = true;
603
604 // TODO: avoid the O(n²) complexity.
605 for a in 0..len1 {
606 for p2 in poly2 {
607 let a_minus_1 = (a + len1 - 1) % len1; // a - 1
608 let new_orient = Triangle::orientation2d(
609 poly1[a_minus_1],
610 poly1[a],
611 *p2,
612 tolerances.collinearity_epsilon,
613 );
614
615 if orient == TriangleOrientation::Degenerate {
616 orient = new_orient
617 } else if new_orient != orient && new_orient != TriangleOrientation::Degenerate {
618 ok = false;
619 break;
620 }
621 }
622 }
623
624 if ok {
625 for mut b in 0..len2 {
626 if rev2 {
627 b = len2 - b - 1;
628 }
629 out(None, Some(PolylinePointLocation::OnVertex(b)))
630 }
631 }
632
633 let mut orient = TriangleOrientation::Degenerate;
634 let mut ok = true;
635
636 // TODO: avoid the O(n²) complexity.
637 for b in 0..len2 {
638 for p1 in poly1 {
639 let b_minus_1 = (b + len2 - 1) % len2; // = b - 1
640 let new_orient = Triangle::orientation2d(
641 poly2[b_minus_1],
642 poly2[b],
643 *p1,
644 tolerances.collinearity_epsilon,
645 );
646
647 if orient == TriangleOrientation::Degenerate {
648 orient = new_orient
649 } else if new_orient != orient && new_orient != TriangleOrientation::Degenerate {
650 ok = false;
651 break;
652 }
653 }
654 }
655
656 if ok {
657 for mut a in 0..len1 {
658 if rev1 {
659 a = len1 - a - 1;
660 }
661 out(Some(PolylinePointLocation::OnVertex(a)), None)
662 }
663 }
664 }
665}
666
667#[inline]
668fn advance(a: usize, aa: &mut usize, n: usize) -> usize {
669 *aa += 1;
670 (a + 1) % n
671}
672
673/// Error type for polygon intersection operations.
674///
675/// This error can occur when computing intersections of non-convex polygons.
676#[derive(thiserror::Error, Debug)]
677pub enum PolygonsIntersectionError {
678 /// An infinite loop was detected during intersection computation.
679 ///
680 /// This typically indicates that the input polygons are ill-formed, such as:
681 /// - Self-intersecting polygons
682 /// - Polygons with duplicate or degenerate edges
683 /// - Polygons with inconsistent winding order
684 #[error("Infinite loop detected; input polygons are ill-formed.")]
685 InfiniteLoop,
686}
687
688/// Computes the intersection points of two possibly non-convex polygons.
689///
690/// This function handles both convex and **non-convex (concave)** polygons, making it more
691/// general than [`convex_polygons_intersection_points`]. However, it requires that:
692/// - Neither polygon self-intersects
693/// - Both polygons are oriented counter-clockwise
694///
695/// The result is a vector of polygons, where each polygon represents one connected component
696/// of the intersection. In most cases there will be only one component, but complex intersections
697/// can produce multiple separate regions.
698///
699/// # Important Notes
700///
701/// - **Non-convex support**: This function works with concave polygons
702/// - **Multiple components**: Returns a `Vec<Vec<Vector2>>` because the intersection
703/// of two concave polygons can produce multiple separate regions
704/// - **No self-intersection**: Input polygons must not self-intersect
705/// - **Counter-clockwise winding**: Both polygons must be oriented counter-clockwise
706/// - **Performance**: Slower than [`convex_polygons_intersection_points`]. If both polygons
707/// are convex, use that function instead.
708///
709/// # Arguments
710///
711/// * `poly1` - First polygon as a slice of vertices
712/// * `poly2` - Second polygon as a slice of vertices
713///
714/// # Returns
715///
716/// * `Ok(Vec<Vec<Vector2>>)` - A vector of intersection polygons (usually just one)
717/// * `Err(PolygonsIntersectionError::InfiniteLoop)` - If the polygons are ill-formed
718///
719/// # Examples
720///
721/// ## Example 1: Two non-convex polygons
722///
723/// ```
724/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
725/// # use parry2d::transformation::polygons_intersection_points;
726/// # use parry2d::math::Vector;
727/// // L-shaped polygon
728/// let l_shape = vec![
729/// Vector::ZERO,
730/// Vector::new(3.0, 0.0),
731/// Vector::new(3.0, 1.0),
732/// Vector::new(1.0, 1.0),
733/// Vector::new(1.0, 3.0),
734/// Vector::new(0.0, 3.0),
735/// ];
736///
737/// // Square overlapping the L-shape
738/// let square = vec![
739/// Vector::new(0.5, 0.5),
740/// Vector::new(2.5, 0.5),
741/// Vector::new(2.5, 2.5),
742/// Vector::new(0.5, 2.5),
743/// ];
744///
745/// let result = polygons_intersection_points(&l_shape, &square).unwrap();
746///
747/// // Should have intersection regions
748/// assert!(!result.is_empty());
749/// # }
750/// ```
751///
752/// ## Example 2: Convex polygons (also works)
753///
754/// ```
755/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
756/// # use parry2d::transformation::polygons_intersection_points;
757/// # use parry2d::math::Vector;
758/// let triangle = vec![
759/// Vector::ZERO,
760/// Vector::new(4.0, 0.0),
761/// Vector::new(2.0, 3.0),
762/// ];
763///
764/// let square = vec![
765/// Vector::new(1.0, 0.5),
766/// Vector::new(3.0, 0.5),
767/// Vector::new(3.0, 2.0),
768/// Vector::new(1.0, 2.0),
769/// ];
770///
771/// let result = polygons_intersection_points(&triangle, &square).unwrap();
772/// assert_eq!(result.len(), 1); // One intersection region
773/// # }
774/// ```
775///
776/// # Errors
777///
778/// Returns `PolygonsIntersectionError::InfiniteLoop` if:
779/// - Either polygon self-intersects
780/// - The polygons have degenerate or duplicate edges
781/// - The winding order is inconsistent
782///
783/// # See Also
784///
785/// * [`convex_polygons_intersection_points`] - Faster version for convex polygons only
786/// * [`polygons_intersection`] - Closure-based version with more control
787pub fn polygons_intersection_points(
788 poly1: &[Vector2],
789 poly2: &[Vector2],
790) -> Result<Vec<Vec<Vector2>>, PolygonsIntersectionError> {
791 let mut result = vec![];
792 let mut curr_poly = vec![];
793 polygons_intersection(poly1, poly2, |loc1, loc2| {
794 if let Some(loc1) = loc1 {
795 curr_poly.push(loc1.to_point(poly1))
796 } else if let Some(loc2) = loc2 {
797 curr_poly.push(loc2.to_point(poly2))
798 } else if !curr_poly.is_empty() {
799 result.push(core::mem::take(&mut curr_poly));
800 }
801 })?;
802
803 Ok(result)
804}
805
806/// Computes the intersection of two possibly non-convex polygons with closure-based output.
807///
808/// This is the closure-based version of [`polygons_intersection_points`], providing more
809/// control over how intersection vertices are processed. It handles non-convex (concave)
810/// polygons but requires they do not self-intersect.
811///
812/// # Arguments
813///
814/// * `poly1` - First polygon as a slice of vertices
815/// * `poly2` - Second polygon as a slice of vertices
816/// * `out` - Closure called for each intersection vertex or component separator
817///
818/// # Closure Arguments
819///
820/// The closure receives `(Option<PolylinePointLocation>, Option<PolylinePointLocation>)`:
821/// - When **both are `Some`**: An edge-edge intersection point
822/// - When **one is `Some`**: A vertex from one polygon inside the other
823/// - When **both are `None`**: Marks the end of a connected component
824///
825/// # Algorithm
826///
827/// This function uses a graph-based traversal algorithm:
828/// 1. Finds all edge-edge intersection points between the two polygons
829/// 2. Builds a graph where vertices are intersection points
830/// 3. Traverses the graph, alternating between polygons at intersection points
831/// 4. Outputs vertices that form the intersection boundary
832///
833/// # Examples
834///
835/// ## Example 1: Collecting intersection points
836///
837/// ```
838/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
839/// # use parry2d::transformation::polygons_intersection;
840/// # use parry2d::math::Vector;
841/// let pentagon = vec![
842/// Vector::new(1.0, 0.0),
843/// Vector::new(0.309, 0.951),
844/// Vector::new(-0.809, 0.588),
845/// Vector::new(-0.809, -0.588),
846/// Vector::new(0.309, -0.951),
847/// ];
848///
849/// let square = vec![
850/// Vector::new(-0.5, -0.5),
851/// Vector::new(0.5, -0.5),
852/// Vector::new(0.5, 0.5),
853/// Vector::new(-0.5, 0.5),
854/// ];
855///
856/// let mut components = Vec::new();
857/// let mut current_component = Vec::new();
858///
859/// polygons_intersection(&pentagon, &square, |loc1, loc2| {
860/// if loc1.is_none() && loc2.is_none() {
861/// // End of a component
862/// if !current_component.is_empty() {
863/// components.push(std::mem::take(&mut current_component));
864/// }
865/// } else if let Some(loc) = loc1 {
866/// current_component.push(loc.to_point(&pentagon));
867/// } else if let Some(loc) = loc2 {
868/// current_component.push(loc.to_point(&square));
869/// }
870/// }).unwrap();
871///
872/// // Should have at least one intersection component
873/// assert!(!components.is_empty());
874/// # }
875/// ```
876///
877/// ## Example 2: Counting intersection vertices
878///
879/// ```
880/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
881/// # use parry2d::transformation::polygons_intersection;
882/// # use parry2d::math::Vector;
883/// let hexagon = vec![
884/// Vector::new(2.0, 0.0),
885/// Vector::new(1.0, 1.732),
886/// Vector::new(-1.0, 1.732),
887/// Vector::new(-2.0, 0.0),
888/// Vector::new(-1.0, -1.732),
889/// Vector::new(1.0, -1.732),
890/// ];
891///
892/// let circle_approx = vec![
893/// Vector::new(1.0, 0.0),
894/// Vector::new(0.707, 0.707),
895/// Vector::new(0.0, 1.0),
896/// Vector::new(-0.707, 0.707),
897/// Vector::new(-1.0, 0.0),
898/// Vector::new(-0.707, -0.707),
899/// Vector::new(0.0, -1.0),
900/// Vector::new(0.707, -0.707),
901/// ];
902///
903/// let mut vertex_count = 0;
904/// polygons_intersection(&hexagon, &circle_approx, |loc1, loc2| {
905/// if loc1.is_some() || loc2.is_some() {
906/// vertex_count += 1;
907/// }
908/// }).unwrap();
909///
910/// assert!(vertex_count > 0);
911/// # }
912/// ```
913///
914/// # Errors
915///
916/// Returns `PolygonsIntersectionError::InfiniteLoop` if the polygons are ill-formed.
917///
918/// # See Also
919///
920/// * [`polygons_intersection_points`] - Simpler vector-based output
921/// * [`convex_polygons_intersection`] - Faster version for convex polygons
922pub fn polygons_intersection(
923 poly1: &[Vector2],
924 poly2: &[Vector2],
925 mut out: impl FnMut(Option<PolylinePointLocation>, Option<PolylinePointLocation>),
926) -> Result<(), PolygonsIntersectionError> {
927 let tolerances = PolygonIntersectionTolerances::default();
928
929 #[derive(Debug)]
930 struct ToTraverse {
931 poly: usize,
932 edge: EdgeId,
933 }
934
935 let (intersections, num_intersections) =
936 compute_sorted_edge_intersections(poly1, poly2, tolerances.collinearity_epsilon);
937 let mut visited = vec![false; num_intersections];
938 let segment = |eid: EdgeId, poly: &[Vector2]| [poly[eid], poly[(eid + 1) % poly.len()]];
939
940 // Traverse all the intersections.
941 for inters in intersections[0].values() {
942 for inter in inters {
943 if visited[inter.id] {
944 continue;
945 }
946
947 // We found an intersection we haven’t visited yet, traverse the loop, alternating
948 // between poly1 and poly2 when reaching an intersection.
949 let [a1, b1] = segment(inter.edges[0], poly1);
950 let [a2, b2] = segment(inter.edges[1], poly2);
951 let poly_to_traverse =
952 match Triangle::orientation2d(a1, b1, a2, tolerances.collinearity_epsilon) {
953 TriangleOrientation::Clockwise => 1,
954 TriangleOrientation::CounterClockwise => 0,
955 TriangleOrientation::Degenerate => {
956 match Triangle::orientation2d(a1, b1, b2, tolerances.collinearity_epsilon) {
957 TriangleOrientation::Clockwise => 0,
958 TriangleOrientation::CounterClockwise => 1,
959 TriangleOrientation::Degenerate => {
960 log::debug!("Unhandled edge-edge overlap case.");
961 0
962 }
963 }
964 }
965 };
966
967 #[derive(Debug)]
968 enum TraversalStatus {
969 OnVertex,
970 OnIntersection(usize),
971 }
972
973 let polys = [poly1, poly2];
974 let mut to_traverse = ToTraverse {
975 poly: poly_to_traverse,
976 edge: inter.edges[poly_to_traverse],
977 };
978
979 let mut status = TraversalStatus::OnIntersection(inter.id);
980
981 for loop_id in 0.. {
982 if loop_id > poly1.len() * poly2.len() {
983 return Err(PolygonsIntersectionError::InfiniteLoop);
984 }
985
986 let empty = Vec::new();
987 let edge_inters = intersections[to_traverse.poly]
988 .get(&to_traverse.edge)
989 .unwrap_or(&empty);
990
991 match status {
992 TraversalStatus::OnIntersection(inter_id) => {
993 let (curr_inter_pos, curr_inter) = edge_inters
994 .iter()
995 .enumerate()
996 .find(|(_, inter)| inter.id == inter_id)
997 .unwrap_or_else(|| unreachable!());
998
999 if visited[curr_inter.id] {
1000 // We already saw this intersection: we looped back to the start of
1001 // the intersection polygon.
1002 out(None, None);
1003 break;
1004 }
1005
1006 out(Some(curr_inter.locs[0]), Some(curr_inter.locs[1]));
1007 visited[curr_inter.id] = true;
1008
1009 if curr_inter_pos + 1 < edge_inters.len() {
1010 // There are other intersections after this one.
1011 // Move forward to the next intersection point and move on to traversing
1012 // the other polygon.
1013 let next_inter = &edge_inters[curr_inter_pos + 1];
1014 to_traverse.poly = (to_traverse.poly + 1) % 2;
1015 to_traverse.edge = next_inter.edges[to_traverse.poly];
1016 status = TraversalStatus::OnIntersection(next_inter.id);
1017 } else {
1018 // This was the last intersection, move to the next vertex on the
1019 // same polygon.
1020 to_traverse.edge =
1021 (to_traverse.edge + 1) % polys[to_traverse.poly].len();
1022 status = TraversalStatus::OnVertex;
1023 }
1024 }
1025 TraversalStatus::OnVertex => {
1026 let location = PolylinePointLocation::OnVertex(to_traverse.edge);
1027
1028 if to_traverse.poly == 0 {
1029 out(Some(location), None);
1030 } else {
1031 out(None, Some(location))
1032 };
1033
1034 if let Some(first_intersection) = edge_inters.first() {
1035 // Jump on the first intersection and move on to the other polygon.
1036 to_traverse.poly = (to_traverse.poly + 1) % 2;
1037 to_traverse.edge = first_intersection.edges[to_traverse.poly];
1038 status = TraversalStatus::OnIntersection(first_intersection.id);
1039 } else {
1040 // Move forward to the next vertex/edge on the same polygon.
1041 to_traverse.edge =
1042 (to_traverse.edge + 1) % polys[to_traverse.poly].len();
1043 }
1044 }
1045 }
1046 }
1047 }
1048 }
1049
1050 // If there are no intersection, check if one polygon is inside the other.
1051 if intersections[0].is_empty() {
1052 if utils::point_in_poly2d(poly1[0], poly2) {
1053 for pt_id in 0..poly1.len() {
1054 out(Some(PolylinePointLocation::OnVertex(pt_id)), None)
1055 }
1056 out(None, None);
1057 } else if utils::point_in_poly2d(poly2[0], poly1) {
1058 for pt_id in 0..poly2.len() {
1059 out(None, Some(PolylinePointLocation::OnVertex(pt_id)))
1060 }
1061 out(None, None);
1062 }
1063 }
1064
1065 Ok(())
1066}
1067
1068type EdgeId = usize;
1069type IntersectionId = usize;
1070
1071#[derive(Copy, Clone, Debug)]
1072struct IntersectionVector {
1073 id: IntersectionId,
1074 edges: [EdgeId; 2],
1075 locs: [PolylinePointLocation; 2],
1076}
1077
1078fn compute_sorted_edge_intersections(
1079 poly1: &[Vector2],
1080 poly2: &[Vector2],
1081 eps: Real,
1082) -> ([HashMap<EdgeId, Vec<IntersectionVector>>; 2], usize) {
1083 let mut inter1: HashMap<EdgeId, Vec<IntersectionVector>> = HashMap::default();
1084 let mut inter2: HashMap<EdgeId, Vec<IntersectionVector>> = HashMap::default();
1085 let mut id = 0;
1086
1087 // Find the intersections.
1088 // TODO: this is a naive O(n²) check. Could use an acceleration structure for large polygons.
1089 for i1 in 0..poly1.len() {
1090 let j1 = (i1 + 1) % poly1.len();
1091
1092 for i2 in 0..poly2.len() {
1093 let j2 = (i2 + 1) % poly2.len();
1094
1095 let Some(inter) =
1096 utils::segments_intersection2d(poly1[i1], poly1[j1], poly2[i2], poly2[j2], eps)
1097 else {
1098 continue;
1099 };
1100
1101 match inter {
1102 SegmentsIntersection::Point { loc1, loc2 } => {
1103 let loc1 = PolylinePointLocation::from_segment_point_location(i1, j1, loc1);
1104 let loc2 = PolylinePointLocation::from_segment_point_location(i2, j2, loc2);
1105 let intersection = IntersectionVector {
1106 id,
1107 edges: [i1, i2],
1108 locs: [loc1, loc2],
1109 };
1110 inter1.entry(i1).or_default().push(intersection);
1111 inter2.entry(i2).or_default().push(intersection);
1112 id += 1;
1113 }
1114 SegmentsIntersection::Segment { .. } => {
1115 // TODO
1116 log::debug!(
1117 "Collinear segment-segment intersections not properly handled yet."
1118 );
1119 }
1120 }
1121 }
1122 }
1123
1124 // Sort the intersections.
1125 for inters in inter1.values_mut() {
1126 inters.sort_by_key(|a| {
1127 let edge = [a.edges[0], (a.edges[0] + 1) % poly1.len()];
1128 OrderedFloat(a.locs[0].centered_bcoords(edge))
1129 });
1130 }
1131
1132 for inters in inter2.values_mut() {
1133 inters.sort_by_key(|a| {
1134 let edge = [a.edges[1], (a.edges[1] + 1) % poly2.len()];
1135 OrderedFloat(a.locs[1].centered_bcoords(edge))
1136 });
1137 }
1138
1139 ([inter1, inter2], id)
1140}
1141
1142#[cfg(all(test, feature = "dim2"))]
1143mod test {
1144 use crate::math::Vector;
1145 use crate::query::PointQuery;
1146 use crate::shape::Triangle;
1147 use crate::transformation::convex_polygons_intersection_points_with_tolerances;
1148 use crate::transformation::polygon_intersection::PolygonIntersectionTolerances;
1149 use alloc::vec::Vec;
1150 use std::println;
1151
1152 #[test]
1153 fn intersect_triangle_common_vertex() {
1154 let tris = [
1155 (
1156 Triangle::new(
1157 Vector::new(-0.0008759537858568062, -2.0103871966663305),
1158 Vector::new(0.3903908709629763, -1.3421764825890266),
1159 Vector::new(1.3380817875388151, -2.0098007857739013),
1160 ),
1161 Triangle::new(
1162 Vector::new(0.0, -0.0),
1163 Vector::new(-0.0008759537858568062, -2.0103871966663305),
1164 Vector::new(1.9991979155226394, -2.009511242880474),
1165 ),
1166 ),
1167 (
1168 Triangle::new(
1169 Vector::new(0.7319315811016305, -0.00004046981523721891),
1170 Vector::new(2.0004914907008944, -0.00011061077714557787),
1171 Vector::new(1.1848406021956144, -0.8155712451545468),
1172 ),
1173 Triangle::new(
1174 Vector::ZERO,
1175 Vector::new(0.00011061077714557787, -2.000024893134292),
1176 Vector::new(2.0004914907008944, -0.00011061077714557787),
1177 ),
1178 ),
1179 (
1180 Triangle::new(
1181 Vector::new(-0.000049995168258705205, -0.9898801451981707),
1182 Vector::new(0.0, -0.0),
1183 Vector::new(0.583013294019752, -1.4170136900568633),
1184 ),
1185 Triangle::new(
1186 Vector::new(0.0, -0.0),
1187 Vector::new(-0.00010101395240669591, -2.000027389553894),
1188 Vector::new(2.000372544168497, 0.00010101395240669591),
1189 ),
1190 ),
1191 (
1192 Triangle::new(
1193 Vector::new(-0.940565646581769, -0.939804943675256),
1194 Vector::new(0.0, -0.0),
1195 Vector::new(-0.001533592366792066, -0.9283586484736431),
1196 ),
1197 Triangle::new(
1198 Vector::new(0.0, -0.0),
1199 Vector::new(-2.00752629829582, -2.0059026672784825),
1200 Vector::new(-0.0033081650580626698, -2.0025945022204197),
1201 ),
1202 ),
1203 ];
1204
1205 for (tri1, tri2) in tris {
1206 let mut inter = Vec::new();
1207 let tolerances = PolygonIntersectionTolerances {
1208 collinearity_epsilon: 1.0e-5,
1209 };
1210 convex_polygons_intersection_points_with_tolerances(
1211 tri1.vertices(),
1212 tri2.vertices(),
1213 tolerances,
1214 &mut inter,
1215 );
1216
1217 println!("----------");
1218 println!("inter: {:?}", inter);
1219 println!(
1220 "tri1 is in tri2: {}",
1221 tri1.vertices().iter().all(|pt| tri2
1222 .project_local_point(*pt, false)
1223 .is_inside_eps(*pt, 1.0e-5))
1224 );
1225 println!(
1226 "tri2 is in tri1: {}",
1227 tri2.vertices().iter().all(|pt| tri1
1228 .project_local_point(*pt, false)
1229 .is_inside_eps(*pt, 1.0e-5))
1230 );
1231 for pt in &inter {
1232 let proj1 = tri1.project_local_point(*pt, false);
1233 let proj2 = tri2.project_local_point(*pt, false);
1234 assert!(proj1.is_inside_eps(*pt, 1.0e-5));
1235 assert!(proj2.is_inside_eps(*pt, 1.0e-5));
1236 }
1237 }
1238 }
1239}