// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace SixLabors.PolygonClipper {
///
/// Manages scanline ordering and local minima scheduling for the sweep line.
///
///
/// This type keeps local minima in sorted order and seeds scanlines from their Y coordinates.
/// It also provides ordered scanline pop/insert operations used during the sweep.
///
internal sealed class ScanlineSchedule
{
private static readonly LocalMinimaComparer LocalMinimaComparerInstance = new();
private ArrayBuilder localMinima;
private readonly List scanlines;
private int localMinimaIndex;
private bool isLocalMinimaSorted;
///
/// Initializes a new instance of the class.
///
public ScanlineSchedule()
{
this.localMinima = new ArrayBuilder(16);
this.scanlines = [];
}
///
/// Gets the number of registered local minima.
///
public int LocalMinimaCount => this.localMinima.Length;
///
/// Gets a retained-capacity score used to decide pooling reuse.
///
public int RetainedCapacityScore => this.localMinima.Capacity + this.scanlines.Capacity;
///
/// Adds a local minima to the schedule.
///
/// The minima to append.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddLocalMinima(in LocalMinima localMinima)
{
this.localMinima.Add(localMinima);
this.isLocalMinimaSorted = false;
}
///
/// Marks the local minima list as unsorted.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MarkDirty() => this.isLocalMinimaSorted = false;
///
/// Clears all minima and scanline state.
///
public void Clear()
{
this.localMinima.Clear();
this.scanlines.Clear();
this.localMinimaIndex = 0;
this.isLocalMinimaSorted = false;
}
///
/// Clears scanlines while keeping local minima intact.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ClearScanlines() => this.scanlines.Clear();
///
/// Sorts minima (if needed) and seeds the scanline list.
///
public void Reset()
{
if (!this.isLocalMinimaSorted)
{
this.localMinima.Sort(LocalMinimaComparerInstance);
this.isLocalMinimaSorted = true;
}
this.scanlines.Clear();
int localMinimaCount = this.localMinima.Length;
this.scanlines.EnsureCapacity(localMinimaCount);
for (int i = localMinimaCount - 1; i >= 0; i--)
{
this.scanlines.Add(this.localMinima[i].Vertex.Point.Y);
}
this.localMinimaIndex = 0;
}
///
/// Determines whether the next local minima is on the given scanline.
///
/// The scanline Y coordinate.
/// if a minima exists at this Y.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasLocalMinimaAtY(double y)
=> this.localMinimaIndex < this.localMinima.Length &&
this.localMinima[this.localMinimaIndex].Vertex.Point.Y == y;
///
/// Pops the next local minima from the schedule.
///
/// The next local minima.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public LocalMinima PopLocalMinima() => this.localMinima[this.localMinimaIndex++];
///
/// Inserts a scanline value into the ordered list.
///
/// The scanline Y coordinate.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void InsertScanline(double y)
{
int index = this.scanlines.BinarySearch(y);
if (index >= 0)
{
return;
}
index = ~index;
this.scanlines.Insert(index, y);
}
///
/// Pops the next scanline from the schedule.
///
/// The popped scanline value.
/// when a scanline was available.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPopScanline(out double y)
{
int count = this.scanlines.Count - 1;
if (count < 0)
{
y = 0;
return false;
}
y = this.scanlines[count];
this.scanlines.RemoveAt(count--);
while (count >= 0 && y == this.scanlines[count])
{
this.scanlines.RemoveAt(count--);
}
return true;
}
}
}