// 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; } } }