// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
namespace SixLabors.PolygonClipper {
///
/// Represents an edge that is currently active in the sweep-line.
///
///
/// The sweep assumes a Y-axis-positive-down coordinate system. "Bottom" and "Top"
/// refer to the lower and upper scanline endpoints (larger and smaller Y respectively).
///
internal sealed class ActiveEdge
{
#pragma warning disable SA1401 // Hot sweep state uses fields to avoid accessor overhead.
///
/// The lower endpoint of the edge in scanline order.
///
public Vertex Bottom;
///
/// The upper endpoint of the edge in scanline order.
///
public Vertex Top;
///
/// The X coordinate where the edge intersects the current scanline.
///
public double CurrentX;
///
/// The delta-X per delta-Y for the edge (its scanline slope).
///
public double Dx;
///
/// The winding delta contributed by this edge (+1 or -1).
///
public int WindDelta;
///
/// The accumulated winding count for this edge.
///
public int WindCount;
///
/// The output record this edge is contributing to, if any.
///
public OutputRecord? OutputRecord;
///
/// The previous edge in the Active Edge List (AEL).
///
public ActiveEdge? PrevInAel;
///
/// The next edge in the Active Edge List (AEL).
///
public ActiveEdge? NextInAel;
///
/// The previous edge in the Sorted Edge List (SEL).
///
public ActiveEdge? PrevInSel;
///
/// The next edge in the Sorted Edge List (SEL).
///
public ActiveEdge? NextInSel;
///
/// The temporary link used when sorting intersections.
///
public ActiveEdge? Jump;
///
/// The current top vertex for this edge's bound.
///
public SweepVertex? VertexTop;
///
/// The local minima that spawned this edge.
///
public LocalMinima LocalMin;
///
/// Indicates whether this edge is the left bound of its pair.
///
public bool IsLeftBound;
///
/// The pending join state for this edge.
///
public JoinWith JoinWith;
#pragma warning restore SA1401
///
/// Gets a value indicating whether this edge currently contributes to output.
///
public bool IsHot => this.OutputRecord != null;
///
/// Gets a value indicating whether the edge is horizontal within tolerance.
///
public bool IsHorizontal => this.Top.Y == this.Bottom.Y;
///
/// Gets a value indicating whether a horizontal edge is heading right.
///
public bool IsHeadingRightHorizontal => double.IsNegativeInfinity(this.Dx);
///
/// Gets a value indicating whether a horizontal edge is heading left.
///
public bool IsHeadingLeftHorizontal => double.IsPositiveInfinity(this.Dx);
///
/// Gets a value indicating whether the current top vertex is a local maxima.
///
public bool IsMaxima => this.VertexTop != null && this.VertexTop.IsMaxima;
///
/// Gets a value indicating whether this edge is the front edge of its output record.
///
public bool IsFront => this.OutputRecord != null && this == this.OutputRecord.FrontEdge;
///
/// Gets the next input vertex along the bound in the winding direction.
///
public SweepVertex NextVertex => this.WindDelta > 0 ? this.VertexTop!.Next! : this.VertexTop!.Prev!;
///
/// Gets the vertex two steps behind the current top, used for turn tests.
///
public SweepVertex PrevPrevVertex => this.WindDelta > 0 ? this.VertexTop!.Prev!.Prev! : this.VertexTop!.Next!.Next!;
///
/// Finds the previous hot edge in the AEL, if any.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ActiveEdge? GetPrevHotEdge()
{
ActiveEdge? prev = this.PrevInAel;
while (prev != null && !prev.IsHot)
{
prev = prev.PrevInAel;
}
return prev;
}
///
/// Calculates the X coordinate where this edge intersects the scanline at .
///
// This method sits on the hottest path in large self-intersection workloads.
// AggressiveOptimization consistently improves codegen here versus tiered defaults.
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static double TopX(ActiveEdge edge, double currentY)
{
if (currentY == edge.Top.Y || edge.Top.X == edge.Bottom.X)
{
return edge.Top.X;
}
if (currentY == edge.Bottom.Y)
{
return edge.Bottom.X;
}
return edge.Bottom.X + (edge.Dx * (currentY - edge.Bottom.Y));
}
///
/// Recomputes from the current endpoints.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateDx() => this.Dx = GetDx(this.Bottom, this.Top);
///
/// Computes delta-X per delta-Y, returning infinities for horizontal edges.
///
private static double GetDx(Vertex pt1, Vertex pt2)
{
double dy = pt2.Y - pt1.Y;
if (dy != 0)
{
return (pt2.X - pt1.X) / dy;
}
return pt2.X > pt1.X ? double.NegativeInfinity : double.PositiveInfinity;
}
}
}