Skip to content

Commit 0de9052

Browse files
Use 4ry heap with delayed sorting
1 parent c164036 commit 0de9052

File tree

2 files changed

+73
-16
lines changed

2 files changed

+73
-16
lines changed

src/PolygonClipper/PolygonClipper.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ public Polygon Run()
131131
int clippingVertexCount = clipping.GetVertexCount();
132132
int eventCount = (subjectVertexCount + clippingVertexCount) * 2;
133133

134-
StablePriorityQueue<SweepEvent, SweepEventComparer> eventQueue = new(new SweepEventComparer(), eventCount);
134+
SweepEventComparer comparer = new();
135+
List<SweepEvent> unorderedEventQueue = new(eventCount);
135136
int contourId = 0;
136137

137138
for (int i = 0; i < subject.ContourCount; i++)
@@ -140,7 +141,14 @@ public Polygon Run()
140141
contourId++;
141142
for (int j = 0; j < contour.VertexCount - 1; j++)
142143
{
143-
ProcessSegment(contourId, contour.Segment(j), PolygonType.Subject, eventQueue, ref min, ref max);
144+
ProcessSegment(
145+
contourId,
146+
contour.Segment(j),
147+
PolygonType.Subject,
148+
unorderedEventQueue,
149+
comparer,
150+
ref min,
151+
ref max);
144152
}
145153
}
146154

@@ -155,7 +163,14 @@ public Polygon Run()
155163

156164
for (int j = 0; j < contour.VertexCount - 1; j++)
157165
{
158-
ProcessSegment(contourId, contour.Segment(j), PolygonType.Clipping, eventQueue, ref min, ref max);
166+
ProcessSegment(
167+
contourId,
168+
contour.Segment(j),
169+
PolygonType.Clipping,
170+
unorderedEventQueue,
171+
comparer,
172+
ref min,
173+
ref max);
159174
}
160175
}
161176

@@ -166,14 +181,14 @@ public Polygon Run()
166181
}
167182

168183
// Sweep line algorithm: process events in the priority queue
184+
StablePriorityQueue<SweepEvent, SweepEventComparer> eventQueue = new(comparer, unorderedEventQueue);
169185
List<SweepEvent> sortedEvents = new(eventCount);
170186

171187
// Heuristic capacity for the sweep line status structure.
172188
// At any given point during the sweep, only a subset of segments
173189
// are active, so we preallocate half the subject's vertex count
174190
// to reduce resizing without overcommitting memory.
175191
StatusLine statusLine = new(subjectVertexCount >> 1);
176-
SweepEventComparer comparer = eventQueue.Comparer;
177192
double subjectMaxX = subjectBB.Max.X;
178193
double minMaxX = Vertex.Min(subjectBB.Max, clippingBB.Max).X;
179194

@@ -348,14 +363,16 @@ private static bool TryTrivialOperationForNonOverlappingBoundingBoxes(
348363
/// <param name="contourId">The identifier of the contour to which the segment belongs.</param>
349364
/// <param name="s">The segment to process.</param>
350365
/// <param name="pt">The polygon type to which the segment belongs.</param>
351-
/// <param name="eventQueue">The event queue to add the generated events to.</param>
366+
/// <param name="eventQueue">The unordered event queue to add the generated events to.</param>
367+
/// <param name="comparer">The comparer used to determine the order of sweep events in the queue.</param>
352368
/// <param name="min">The minimum vertex of the bounding box.</param>
353369
/// <param name="max">The maximum vertex of the bounding box.</param>
354370
private static void ProcessSegment(
355371
int contourId,
356372
Segment s,
357373
PolygonType pt,
358-
StablePriorityQueue<SweepEvent, SweepEventComparer> eventQueue,
374+
List<SweepEvent> eventQueue,
375+
SweepEventComparer comparer,
359376
ref Vertex min,
360377
ref Vertex max)
361378
{
@@ -372,7 +389,7 @@ private static void ProcessSegment(
372389
e1.ContourId = e2.ContourId = contourId;
373390

374391
// Determine which endpoint is the left endpoint
375-
if (eventQueue.Comparer.Compare(e1, e2) < 0)
392+
if (comparer.Compare(e1, e2) < 0)
376393
{
377394
e2.Left = false;
378395
}
@@ -385,8 +402,8 @@ private static void ProcessSegment(
385402
max = Vertex.Max(max, s.Max);
386403

387404
// Add the events to the event queue
388-
eventQueue.Enqueue(e1);
389-
eventQueue.Enqueue(e2);
405+
eventQueue.Add(e1);
406+
eventQueue.Add(e2);
390407
}
391408

392409
/// <summary>

src/PolygonClipper/StablePriorityQueue{T,TComparer}.cs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace PolygonClipper;
1818
internal sealed class StablePriorityQueue<T, TComparer>
1919
where TComparer : IComparer<T>
2020
{
21+
private const int Log2Arity = 2;
2122
private readonly List<T> heap;
2223

2324
/// <summary>
@@ -31,6 +32,23 @@ public StablePriorityQueue(TComparer comparer, int capacity)
3132
this.heap = new List<T>(capacity > 0 ? capacity : 16);
3233
}
3334

35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="StablePriorityQueue{T, TComparer}"/> class
37+
/// with a specified comparer and an initial collection of unordered elements.
38+
/// The heap property is established in linear time.
39+
/// </summary>
40+
/// <param name="comparer">The comparer to determine the priority of the elements.</param>
41+
/// <param name="items">
42+
/// The initial collection of elements to heapify.
43+
/// Note: The collection is modified to establish the heap property.
44+
/// </param>
45+
public StablePriorityQueue(TComparer comparer, List<T> items)
46+
{
47+
this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
48+
this.heap = items ?? throw new ArgumentNullException(nameof(items));
49+
this.Heapify(this.heap);
50+
}
51+
3452
/// <summary>
3553
/// Gets the number of elements in the priority queue.
3654
/// </summary>
@@ -107,7 +125,7 @@ private void Up(uint index, List<T> heap)
107125

108126
while (index > 0)
109127
{
110-
uint parent = (index - 1u) >> 1;
128+
uint parent = (index - 1u) >> Log2Arity;
111129
T current = Unsafe.Add(ref dRef, parent);
112130
if (comparer.Compare(item, current) >= 0)
113131
{
@@ -133,18 +151,21 @@ private void Down(uint index, List<T> heap)
133151
ref T dRef = ref MemoryMarshal.GetReference(data);
134152

135153
uint length = (uint)data.Length;
136-
uint halfLength = length >> 1;
137154
T item = Unsafe.Add(ref dRef, index);
138155
TComparer comparer = this.Comparer;
139156

140-
while (index < halfLength)
157+
while ((index << Log2Arity) + 1u < length)
141158
{
142-
uint bestChild = (index << 1) + 1; // Initially left child
143-
uint right = bestChild + 1u;
159+
uint firstChild = (index << Log2Arity) + 1u;
160+
uint bestChild = firstChild;
161+
uint maxChild = Math.Min(firstChild + (1u << Log2Arity), length);
144162

145-
if (right < length && comparer.Compare(Unsafe.Add(ref dRef, right), Unsafe.Add(ref dRef, bestChild)) < 0)
163+
for (uint i = firstChild + 1u; i < maxChild; i++)
146164
{
147-
bestChild = right;
165+
if (comparer.Compare(Unsafe.Add(ref dRef, i), Unsafe.Add(ref dRef, bestChild)) < 0)
166+
{
167+
bestChild = i;
168+
}
148169
}
149170

150171
if (comparer.Compare(Unsafe.Add(ref dRef, bestChild), item) >= 0)
@@ -159,6 +180,25 @@ private void Down(uint index, List<T> heap)
159180
Unsafe.Add(ref dRef, index) = item;
160181
}
161182

183+
/// <summary>
184+
/// Heapifies the given list to establish the min-heap property.
185+
/// </summary>
186+
/// <param name="heap">The list to heapify.</param>
187+
private void Heapify(List<T> heap)
188+
{
189+
int count = heap.Count;
190+
if (count <= 1)
191+
{
192+
return;
193+
}
194+
195+
int lastParent = (count - 2) >> Log2Arity;
196+
for (int i = lastParent; i >= 0; i--)
197+
{
198+
this.Down((uint)i, heap);
199+
}
200+
}
201+
162202
[MethodImpl(MethodImplOptions.NoInlining)]
163203
private static void ThrowIfEmpty(int count)
164204
{

0 commit comments

Comments
 (0)