// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Collections.Generic; using System.Runtime.CompilerServices; namespace SixLabors.PolygonClipper { /// /// Maintains the active edge list (AEL) plus a horizontal edge stack for the sweep. /// /// /// The AEL is ordered left-to-right at the current scanline. As edges are inserted /// and removed, this list preserves adjacency for intersection processing. /// The horizontal stack is a lightweight LIFO queue used to process horizontal /// bounds separately from the main sweep order. /// internal sealed class ActiveEdgeList { private readonly Stack pool; private ActiveEdge? horizontalHead; /// /// Initializes a new instance of the class. /// public ActiveEdgeList() => this.pool = new Stack(); /// /// Gets the head of the active edge list. /// public ActiveEdge? Head { get; private set; } /// /// Gets the number of retained pooled edge objects. /// public int RetainedPoolCount => this.pool.Count; /// /// Clears all active edges and returns them to the pool. /// public void ClearActiveEdges() { while (this.Head != null) { this.Remove(this.Head); } this.horizontalHead = null; } /// /// Resets sweep pointers without clearing the pool. /// public void Reset() { this.Head = null; this.horizontalHead = null; } /// /// Acquires a reusable active edge, allocating if the pool is empty. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ActiveEdge Acquire() => this.pool.Count == 0 ? new ActiveEdge() : this.pool.Pop(); /// /// Inserts an edge into the active list, maintaining left-to-right order. /// /// The edge to insert. public void InsertLeft(ActiveEdge edge) { if (this.Head == null) { edge.PrevInAel = null; edge.NextInAel = null; this.Head = edge; return; } if (!IsValidActiveEdgeOrder(this.Head, edge)) { edge.PrevInAel = null; edge.NextInAel = this.Head; this.Head.PrevInAel = edge; this.Head = edge; return; } ActiveEdge edge2 = this.Head; while (edge2.NextInAel != null && IsValidActiveEdgeOrder(edge2.NextInAel, edge)) { edge2 = edge2.NextInAel; } // Keep joined edges adjacent in the active list. if (edge2.JoinWith == JoinWith.Right) { edge2 = edge2.NextInAel!; } edge.NextInAel = edge2.NextInAel; if (edge2.NextInAel != null) { edge2.NextInAel.PrevInAel = edge; } edge.PrevInAel = edge2; edge2.NextInAel = edge; } /// /// Inserts a right bound edge immediately after another edge in the active list. /// /// The anchor edge. /// The edge to insert. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InsertRight(ActiveEdge edge, ActiveEdge edge2) { edge2.NextInAel = edge.NextInAel; if (edge.NextInAel != null) { edge.NextInAel.PrevInAel = edge2; } edge2.PrevInAel = edge; edge.NextInAel = edge2; } /// /// Removes an edge from the active list and returns it to the pool. /// /// The edge to remove. public void Remove(ActiveEdge edge) { ActiveEdge? prev = edge.PrevInAel; ActiveEdge? next = edge.NextInAel; if (prev == null && next == null && edge != this.Head) { return; } if (prev != null) { prev.NextInAel = next; } else { this.Head = next; } if (next != null) { next.PrevInAel = prev; } this.Recycle(edge); } /// /// Swaps the positions of two adjacent edges in the active list. /// /// The left edge. /// The right edge. public void SwapPositions(ActiveEdge left, ActiveEdge right) { // Precondition: left must be immediately to the left of right. ActiveEdge? next = right.NextInAel; if (next != null) { next.PrevInAel = left; } ActiveEdge? prev = left.PrevInAel; if (prev != null) { prev.NextInAel = right; } right.PrevInAel = prev; right.NextInAel = left; left.PrevInAel = right; left.NextInAel = next; if (right.PrevInAel == null) { this.Head = right; } } /// /// Clears the horizontal edge stack. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ClearHorizontalQueue() => this.horizontalHead = null; /// /// Pushes a horizontal edge onto the processing stack. /// /// The horizontal edge to push. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PushHorizontal(ActiveEdge edge) { edge.NextInSel = this.horizontalHead; this.horizontalHead = edge; } /// /// Pops the next horizontal edge to process. /// /// The next horizontal edge, or . /// when a horizontal edge was available. public bool TryPopHorizontal(out ActiveEdge? edge) { while (true) { edge = this.horizontalHead; if (edge == null) { return false; } ActiveEdge? next = edge.NextInSel; this.horizontalHead = ReferenceEquals(next, edge) ? null : next; if (edge.VertexTop != null) { return true; } } } /// /// Copies the active list into a sorted list and updates current X values. /// /// The scanline top Y coordinate. /// The head of the sorted list. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public ActiveEdge? CopyToSorted(double topY) { ActiveEdge? edge = this.Head; ActiveEdge? sortedHead = edge; while (edge != null) { edge.PrevInSel = edge.PrevInAel; edge.NextInSel = edge.NextInAel; edge.Jump = edge.NextInSel; // Joined edges can be split later during intersection processing. edge.CurrentX = ActiveEdge.TopX(edge, topY); // Defer any Y updates; intersection tests use original bounds. edge = edge.NextInAel; } return sortedHead; } /// /// Determines whether the newcomer should be inserted after the resident in the active list. /// /// The current resident edge. /// The incoming edge to compare. /// if the newcomer bedoubles after the resident. public static bool IsValidActiveEdgeOrder(ActiveEdge resident, ActiveEdge newcomer) { if (newcomer.CurrentX != resident.CurrentX) { return newcomer.CurrentX > resident.CurrentX; } // Compare turning direction: resident.Top -> newcomer.Bottom -> newcomer.Top. int d = PolygonUtilities.CrossSign(resident.Top, newcomer.Bottom, newcomer.Top); if (d != 0) { return d < 0; } // For collinear bounds, use the next turn to order them. if (!resident.IsMaxima && (resident.Top.Y > newcomer.Top.Y)) { return PolygonUtilities.CrossSign( newcomer.Bottom, resident.Top, resident.NextVertex.Point) <= 0; } if (!newcomer.IsMaxima && (newcomer.Top.Y > resident.Top.Y)) { return PolygonUtilities.CrossSign( newcomer.Bottom, newcomer.Top, newcomer.NextVertex.Point) >= 0; } double y = newcomer.Bottom.Y; bool newcomerIsLeft = newcomer.IsLeftBound; if (resident.Bottom.Y != y || resident.LocalMin.Vertex.Point.Y != y) { return newcomer.IsLeftBound; } // Only newly inserted edges reach this branch. if (resident.IsLeftBound != newcomerIsLeft) { return newcomerIsLeft; } if (PolygonUtilities.IsCollinear( resident.PrevPrevVertex.Point, resident.Bottom, resident.Top)) { return true; } // Use the alternate bound turn to break the tie. return (PolygonUtilities.CrossSign( resident.PrevPrevVertex.Point, newcomer.Bottom, newcomer.PrevPrevVertex.Point) > 0) == newcomerIsLeft; } /// /// Resets and returns an active edge to the reuse pool. /// /// The edge to recycle. [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Recycle(ActiveEdge edge) { // Clear references so pooled edges do not retain objects. edge.Bottom = default; edge.Top = default; edge.Dx = 0.0; edge.CurrentX = 0; edge.WindCount = 0; edge.OutputRecord = null; edge.PrevInAel = null; edge.NextInAel = null; edge.PrevInSel = null; edge.NextInSel = null; edge.Jump = null; edge.VertexTop = null; edge.LocalMin = default; edge.IsLeftBound = false; edge.JoinWith = JoinWith.None; this.pool.Push(edge); } } }