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