5
5
using System . Collections . Generic ;
6
6
using System . Diagnostics ;
7
7
using System . Diagnostics . CodeAnalysis ;
8
+ using System . Runtime . CompilerServices ;
9
+ using System . Runtime . InteropServices ;
8
10
9
11
namespace PolygonClipper ;
10
12
@@ -120,7 +122,9 @@ public Polygon Run()
120
122
// Process all segments in the subject polygon
121
123
Vertex min = new ( double . PositiveInfinity ) ;
122
124
Vertex max = new ( double . NegativeInfinity ) ;
123
- StablePriorityQueue < SweepEvent , SweepEventComparer > eventQueue = new ( new SweepEventComparer ( ) ) ;
125
+
126
+ int eventCount = ( subject . GetVertexCount ( ) + clipping . GetVertexCount ( ) ) * 2 ;
127
+ StablePriorityQueue < SweepEvent , SweepEventComparer > eventQueue = new ( new SweepEventComparer ( ) , eventCount ) ;
124
128
int contourId = 0 ;
125
129
for ( int i = 0 ; i < subject . ContourCount ; i ++ )
126
130
{
@@ -162,6 +166,7 @@ public Polygon Run()
162
166
163
167
SweepEvent ? prevEvent ;
164
168
SweepEvent ? nextEvent ;
169
+ Span < SweepEvent > workspace = new SweepEvent [ 4 ] ;
165
170
while ( eventQueue . Count > 0 )
166
171
{
167
172
SweepEvent sweepEvent = eventQueue . Dequeue ( ) ;
@@ -188,7 +193,7 @@ public Polygon Run()
188
193
if ( nextEvent != null )
189
194
{
190
195
// Check intersection with the next neighbor
191
- if ( PossibleIntersection ( sweepEvent , nextEvent , eventQueue ) == 2 )
196
+ if ( PossibleIntersection ( sweepEvent , nextEvent , eventQueue , workspace ) == 2 )
192
197
{
193
198
ComputeFields ( sweepEvent , prevEvent , operation ) ;
194
199
ComputeFields ( nextEvent , sweepEvent , operation ) ;
@@ -199,7 +204,7 @@ public Polygon Run()
199
204
if ( prevEvent != null )
200
205
{
201
206
// Check intersection with the previous neighbor
202
- if ( PossibleIntersection ( prevEvent , sweepEvent , eventQueue ) == 2 )
207
+ if ( PossibleIntersection ( prevEvent , sweepEvent , eventQueue , workspace ) == 2 )
203
208
{
204
209
SweepEvent ? prevPrevEvent = statusLine . Prev ( prevEvent . PosSL ) ;
205
210
ComputeFields ( prevEvent , prevPrevEvent , operation ) ;
@@ -218,7 +223,7 @@ public Polygon Run()
218
223
// Check intersection between neighbors
219
224
if ( prevEvent != null && nextEvent != null )
220
225
{
221
- _ = PossibleIntersection ( prevEvent , nextEvent , eventQueue ) ;
226
+ _ = PossibleIntersection ( prevEvent , nextEvent , eventQueue , workspace ) ;
222
227
}
223
228
224
229
statusLine . RemoveAt ( it ) ;
@@ -499,6 +504,10 @@ private static bool InResult(SweepEvent sweepEvent, BooleanOperation operation)
499
504
/// <param name="le1">The first sweep event representing a line segment.</param>
500
505
/// <param name="le2">The second sweep event representing a line segment.</param>
501
506
/// <param name="eventQueue">The event queue to add new events to.</param>
507
+ /// <param name="workspace">
508
+ /// A scratch space for temporary storage of sweep events.
509
+ /// Must be at least 4 elements long to hold the events for the two segments and their associated other events.
510
+ /// </param>
502
511
/// <returns>
503
512
/// An integer indicating the result of the intersection:
504
513
/// <list type="bullet">
@@ -514,7 +523,8 @@ private static bool InResult(SweepEvent sweepEvent, BooleanOperation operation)
514
523
private static int PossibleIntersection (
515
524
SweepEvent le1 ,
516
525
SweepEvent le2 ,
517
- StablePriorityQueue < SweepEvent , SweepEventComparer > eventQueue )
526
+ StablePriorityQueue < SweepEvent , SweepEventComparer > eventQueue ,
527
+ Span < SweepEvent > workspace )
518
528
{
519
529
if ( le1 . OtherEvent == null || le2 . OtherEvent == null )
520
530
{
@@ -569,41 +579,55 @@ private static int PossibleIntersection(
569
579
}
570
580
571
581
// The line segments associated with le1 and le2 overlap.
572
- // TODO: Rewrite this to avoid allocation.
573
- List < SweepEvent > events = new ( 4 ) ;
574
582
bool leftCoincide = le1 . Point == le2 . Point ;
575
583
bool rightCoincide = le1 . OtherEvent . Point == le2 . OtherEvent . Point ;
576
584
577
- // Populate the events
585
+ // Populate the events.
586
+ // The working buffer has a length of 4, which is sufficient to hold the events
587
+ // for the two segments and their associated other events.
588
+ // Events are assigned in a specific order to avoid overwriting shared references.
589
+ ref SweepEvent wRef = ref MemoryMarshal . GetReference ( workspace ) ;
578
590
if ( ! leftCoincide )
579
591
{
580
592
if ( comparer . Compare ( le1 , le2 ) > 0 )
581
593
{
582
- events . Add ( le2 ) ;
583
- events . Add ( le1 ) ;
594
+ Unsafe . Add ( ref wRef , 0u ) = le2 ;
595
+ Unsafe . Add ( ref wRef , 1u ) = le1 ;
584
596
}
585
597
else
586
598
{
587
- events . Add ( le1 ) ;
588
- events . Add ( le2 ) ;
599
+ Unsafe . Add ( ref wRef , 0u ) = le1 ;
600
+ Unsafe . Add ( ref wRef , 1u ) = le2 ;
589
601
}
590
- }
591
602
592
- if ( ! rightCoincide )
603
+ // Positions 0 and 1 contain the left events of the segments.
604
+ // Positions 2 and 3 will contain the right events of the segments.
605
+ if ( ! rightCoincide )
606
+ {
607
+ Unsafe . Add ( ref wRef , 2u ) = le1. OtherEvent ;
608
+ Unsafe . Add ( ref wRef , 3u ) = le2. OtherEvent ;
609
+ }
610
+ else
611
+ {
612
+ Unsafe . Add ( ref wRef , 2u ) = le2. OtherEvent ;
613
+ Unsafe . Add ( ref wRef , 3u ) = le1. OtherEvent ;
614
+ }
615
+ }
616
+ else if ( leftCoincide && ! rightCoincide )
593
617
{
618
+ // Only the right endpoints differ, so we use positions 0 and 1 for their sorted order.
594
619
if ( comparer . Compare ( le1 . OtherEvent , le2 . OtherEvent ) > 0 )
595
620
{
596
- events . Add ( le2 . OtherEvent ) ;
597
- events . Add ( le1 . OtherEvent ) ;
621
+ Unsafe . Add ( ref wRef , 0u ) = le2. OtherEvent ;
622
+ Unsafe . Add ( ref wRef , 1u ) = le1. OtherEvent ;
598
623
}
599
624
else
600
625
{
601
- events . Add ( le1 . OtherEvent ) ;
602
- events . Add ( le2 . OtherEvent ) ;
626
+ Unsafe . Add ( ref wRef , 0u ) = le1. OtherEvent ;
627
+ Unsafe . Add ( ref wRef , 1u ) = le2. OtherEvent ;
603
628
}
604
629
}
605
630
606
- // Handle leftCoincide case
607
631
if ( leftCoincide )
608
632
{
609
633
le2 . EdgeType = EdgeType . NonContributing ;
@@ -613,30 +637,31 @@ private static int PossibleIntersection(
613
637
614
638
if ( leftCoincide && ! rightCoincide )
615
639
{
616
- DivideSegment ( events [ 1 ] . OtherEvent , events [ 0 ] . Point , eventQueue , comparer ) ;
640
+ DivideSegment ( Unsafe . Add ( ref wRef , 1u ) . OtherEvent , Unsafe . Add ( ref wRef , 0u ) . Point , eventQueue , comparer ) ;
617
641
}
618
642
619
643
return 2 ;
620
644
}
621
645
622
- // Handle the rightCoincide case
623
646
if ( rightCoincide )
624
647
{
625
- DivideSegment ( events [ 0 ] , events [ 1 ] . Point , eventQueue , comparer ) ;
648
+ // Since leftCoincide is false, the first two workspace slots contain distinct left events.
649
+ DivideSegment ( Unsafe . Add ( ref wRef , 0u ) , Unsafe . Add ( ref wRef , 1u ) . Point , eventQueue , comparer ) ;
626
650
return 3 ;
627
651
}
628
652
629
653
// Handle general overlapping case
630
- if ( events [ 0 ] != events [ 3 ] . OtherEvent )
654
+ // At this point: workspace[0,1] = sorted left events, workspace[2,3] = sorted right events.
655
+ if ( Unsafe . Add ( ref wRef , 0u ) != Unsafe . Add ( ref wRef , 3u ) . OtherEvent )
631
656
{
632
- DivideSegment ( events [ 0 ] , events [ 1 ] . Point , eventQueue , comparer ) ;
633
- DivideSegment ( events [ 1 ] , events [ 2 ] . Point , eventQueue , comparer ) ;
657
+ DivideSegment ( Unsafe . Add ( ref wRef , 0u ) , Unsafe . Add ( ref wRef , 1u ) . Point , eventQueue , comparer ) ;
658
+ DivideSegment ( Unsafe . Add ( ref wRef , 1u ) , Unsafe . Add ( ref wRef , 2u ) . Point , eventQueue , comparer ) ;
634
659
return 3 ;
635
660
}
636
661
637
662
// One segment fully contains the other
638
- DivideSegment ( events [ 0 ] , events [ 1 ] . Point , eventQueue , comparer ) ;
639
- DivideSegment ( events [ 3 ] . OtherEvent , events [ 2 ] . Point , eventQueue , comparer ) ;
663
+ DivideSegment ( Unsafe . Add ( ref wRef , 0u ) , Unsafe . Add ( ref wRef , 1u ) . Point , eventQueue , comparer ) ;
664
+ DivideSegment ( Unsafe . Add ( ref wRef , 3u ) . OtherEvent , Unsafe . Add ( ref wRef , 2u ) . Point , eventQueue , comparer ) ;
640
665
return 3 ;
641
666
}
642
667
@@ -924,6 +949,7 @@ private static ReadOnlySpan<int> PrecomputeIterationOrder(List<SweepEvent> data)
924
949
return map ;
925
950
}
926
951
952
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
927
953
private static void MarkProcessed ( SweepEvent sweepEvent , Span < bool > processed , int pos , int contourId )
928
954
{
929
955
processed [ pos ] = true ;
@@ -994,11 +1020,9 @@ private static Contour InitializeContourFromContext(SweepEvent sweepEvent, Polyg
994
1020
/// starting from the given position.
995
1021
/// </summary>
996
1022
/// <param name="pos">The current position in the result events.</param>
997
- /// <param name="resultEvents">The list of sweep events representing result segments.</param>
998
1023
/// <param name="processed">A list indicating whether each event at the corresponding index has been processed.</param>
999
- /// <param name="originalPos">The original position to return if no unprocessed event is found.</param>
1000
- /// <param name="iterationMap"></param>
1001
- /// <param name="found"></param>
1024
+ /// <param name="iterationMap">A precomputed map that indicates the next position to check for unprocessed events.</param>
1025
+ /// <param name="found">A boolean indicating whether an unprocessed event was found.</param>
1002
1026
/// <returns>The index of the next unprocessed position.</returns>
1003
1027
/// <remarks>
1004
1028
/// This method searches forward from the current position until it finds an unprocessed event with
0 commit comments