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