@@ -532,6 +532,12 @@ private static int PossibleIntersection(
532
532
SweepEvent le2 ,
533
533
StablePriorityQueue < SweepEvent , SweepEventComparer > eventQueue )
534
534
{
535
+ if ( le1 . OtherEvent == null || le2 . OtherEvent == null )
536
+ {
537
+ // No intersection possible.
538
+ return 0 ;
539
+ }
540
+
535
541
// Point intersections
536
542
int nIntersections = PolygonUtilities . FindIntersection (
537
543
le1 . Segment ( ) ,
@@ -545,22 +551,22 @@ private static int PossibleIntersection(
545
551
return 0 ;
546
552
}
547
553
554
+ // Ignore intersection if it occurs at the exact left or right endpoint of both segments
548
555
if ( nIntersections == 1 &&
549
556
( le1 . Point == le2 . Point || le1 . OtherEvent . Point == le2 . OtherEvent . Point ) )
550
557
{
551
558
// Line segments intersect at an endpoint of both line segments
552
559
return 0 ;
553
560
}
554
561
562
+ // If segments overlap and belong to the same polygon, ignore them
555
563
if ( nIntersections == 2 && le1 . PolygonType == le2 . PolygonType )
556
564
{
557
- // Line segments overlap but belong to the same polygon
558
565
return 0 ;
559
566
}
560
567
568
+ // Handle a single intersection point
561
569
SweepEventComparer comparer = eventQueue . Comparer ;
562
-
563
- // The line segments associated with le1 and le2 intersect
564
570
if ( nIntersections == 1 )
565
571
{
566
572
// If the intersection point is not an endpoint of le1 segment.
@@ -613,8 +619,8 @@ private static int PossibleIntersection(
613
619
}
614
620
}
615
621
616
- // Handle leftCoincide and rightCoincide cases
617
- if ( ( leftCoincide && rightCoincide ) || leftCoincide )
622
+ // Handle leftCoincide case
623
+ if ( leftCoincide )
618
624
{
619
625
le2 . EdgeType = EdgeType . NonContributing ;
620
626
le1 . EdgeType = ( le2 . InOut == le1 . InOut )
@@ -636,15 +642,15 @@ private static int PossibleIntersection(
636
642
return 3 ;
637
643
}
638
644
639
- // Handle overlapping segments
645
+ // Handle general overlapping case
640
646
if ( events [ 0 ] != events [ 3 ] . OtherEvent )
641
647
{
642
648
DivideSegment ( events [ 0 ] , events [ 1 ] . Point , eventQueue , comparer ) ;
643
649
DivideSegment ( events [ 1 ] , events [ 2 ] . Point , eventQueue , comparer ) ;
644
650
return 3 ;
645
651
}
646
652
647
- // Handle one segment fully containing the other
653
+ // One segment fully contains the other
648
654
DivideSegment ( events [ 0 ] , events [ 1 ] . Point , eventQueue , comparer ) ;
649
655
DivideSegment ( events [ 3 ] . OtherEvent , events [ 2 ] . Point , eventQueue , comparer ) ;
650
656
return 3 ;
@@ -663,37 +669,100 @@ private static void DivideSegment(
663
669
StablePriorityQueue < SweepEvent , SweepEventComparer > eventQueue ,
664
670
SweepEventComparer comparer )
665
671
{
666
- // Create the right event for the left segment (result of division)
672
+ if ( le . OtherEvent == null )
673
+ {
674
+ return ;
675
+ }
676
+
677
+ SweepEvent re = le . OtherEvent ;
678
+
679
+ // The idea is to divide the segment based on the given `inter` coordinate as follows:
680
+ //
681
+ // (se_l)--------(r)(l)--------(re)
682
+ //
683
+ // Under normal circumstances the resulting events satisfy the conditions:
684
+ //
685
+ // se_l is before r, and l is before re.
686
+ //
687
+ // Since the intersection point computation is bounded to the interval [se_l.x, re.x]
688
+ // it is impossible for r/l to fall outside the interval. This leaves the corner cases:
689
+ //
690
+ // 1. r.x == se_l.x and r.y < se_l.y: This corresponds to the case where the first
691
+ // sub-segment becomes a perfectly vertical line. The problem is that vertical
692
+ // segments always have to be processed from bottom to top consistency. The
693
+ // theoretically correct event order would be r first (bottom), se_l later (top).
694
+ // However, se_l is the event just being processed, so there is no (easy) way of
695
+ // processing r before se_l. The easiest solution to the problem is to avoid it,
696
+ // by incrementing inter.x by one ULP.
697
+ // 2. l.x == re.x and l.y > re.y: This corresponds to the case where the second
698
+ // sub-segment becomes a perfectly vertical line, and because of the bottom-to-top
699
+ // convention for vertical segment, the order of l and re must be swapped.
700
+ // In this case swapping is not a problem, because both events are in the future.
701
+ //
702
+ // See also: https://github.com/21re/rust-geo-booleanop/pull/11
703
+
704
+ // Prevent from corner case 1
705
+ if ( p . X == le . Point . X && p . Y < le . Point . Y )
706
+ {
707
+ // TODO: enabling this line makes a single test issue76.geojson fail.
708
+ // The files are different in the two reference repositories but both fail.
709
+ // p = new Vertex(NextAfter(p.X, true), p.Y);
710
+ }
711
+
712
+ // Create the right event for the left segment (new right endpoint)
667
713
SweepEvent r = new ( p , false , le , le . PolygonType ) ;
668
714
669
- // Create the left event for the right segment (result of division )
670
- SweepEvent l = new ( p , true , le . OtherEvent , le . PolygonType ) ;
715
+ // Create the left event for the right segment (new left endpoint )
716
+ SweepEvent l = new ( p , true , re , le . PolygonType ) ;
671
717
672
- // Assign the same contour id to the new events for sorting.
718
+ // Assign the same contour ID to maintain connectivity
673
719
r . ContourId = l . ContourId = le . ContourId ;
674
720
675
- // Avoid rounding error: ensure the left event is processed before the right event
676
- if ( comparer . Compare ( l , le . OtherEvent ) > 0 )
721
+ // Corner case 2 can be accounted for by swapping l / se_r
722
+ if ( comparer . Compare ( l , re ) > 0 )
677
723
{
678
724
Debug . WriteLine ( "Rounding error detected: Adjusting left/right flags for event ordering." ) ;
679
- le . OtherEvent . Left = true ;
725
+ re . Left = true ;
680
726
l . Left = false ;
681
727
}
682
728
683
- if ( comparer . Compare ( le , r ) > 0 )
684
- {
685
- Debug . WriteLine ( "Rounding error detected: Event ordering issue for right event." ) ;
686
- }
687
-
688
729
// Update references to maintain correct linkage
689
- le . OtherEvent . OtherEvent = l ;
730
+ re . OtherEvent = l ;
690
731
le . OtherEvent = r ;
691
732
692
733
// Add the new events to the event queue
693
734
eventQueue . Enqueue ( l ) ;
694
735
eventQueue . Enqueue ( r ) ;
695
736
}
696
737
738
+ /// <summary>
739
+ /// Returns the next representable double-precision floating-point value in the given direction.
740
+ /// <see href="https://docs.rs/float_next_after/latest/float_next_after/trait.NextAfter.html"/>
741
+ /// </summary>
742
+ /// <param name="x">The starting double value.</param>
743
+ /// <param name="up">If true, moves towards positive infinity; otherwise, towards negative infinity.</param>
744
+ /// <returns>The next representable double in the given direction.</returns>
745
+ private static double NextAfter ( double x , bool up )
746
+ {
747
+ if ( double . IsNaN ( x ) || x == double . PositiveInfinity || x == double . NegativeInfinity )
748
+ {
749
+ return x ; // NaN and infinity stay the same
750
+ }
751
+
752
+ // Convert double to its IEEE 754 bit representation
753
+ long bits = BitConverter . DoubleToInt64Bits ( x ) ;
754
+ if ( up )
755
+ {
756
+ bits += ( bits >= 0 ) ? 1 : - 1 ; // Increase magnitude
757
+ }
758
+ else
759
+ {
760
+ bits += ( bits > 0 ) ? - 1 : 1 ; // Decrease magnitude
761
+ }
762
+
763
+ return BitConverter . Int64BitsToDouble ( bits ) ;
764
+ }
765
+
697
766
/// <summary>
698
767
/// Connects edges in the result polygon by processing the sweep events
699
768
/// and constructing contours for the final result.
0 commit comments