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