// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace SixLabors.PolygonClipper { /// /// Allows the comparison of segments for sorting. /// internal sealed class SegmentComparer : IComparer, IComparer { /// public int Compare(SweepEvent? x, SweepEvent? y) { // If the events are the same, return 0 (no order difference) if (ReferenceEquals(x, y)) { return 0; } if (x == null) { return -1; } if (y == null) { return 1; } SweepEvent perhapsInversedX, perhapsInversedY; bool inversed; if (x.IsBefore(y)) { perhapsInversedX = x; perhapsInversedY = y; inversed = false; } else { perhapsInversedX = y; perhapsInversedY = x; inversed = true; } // Check if the segments are collinear by comparing their signed areas double area1 = PolygonUtilities.SignedArea(perhapsInversedX.Point, perhapsInversedX.OtherEvent.Point, perhapsInversedY.Point); double area2 = PolygonUtilities.SignedArea(perhapsInversedX.Point, perhapsInversedX.OtherEvent.Point, perhapsInversedY.OtherEvent.Point); if (area1 != 0 || area2 != 0) { // Segments are not collinear // If they share their left endpoint, use the right endpoint to sort if (perhapsInversedX.Point == perhapsInversedY.Point) { return LessIf(perhapsInversedX.IsBelow(perhapsInversedY.OtherEvent.Point), inversed); } // Different left endpoints: use the y-coordinate to sort if x-coordinates are the same if (perhapsInversedX.Point.X == perhapsInversedY.Point.X) { return LessIf(perhapsInversedX.Point.Y < perhapsInversedY.Point.Y, inversed); } // If `x` and `y` lie on the same side of the reference segment, // no intersection check is necessary. if ((area1 > 0) == (area2 > 0)) { return LessIf(area1 > 0, inversed); } // If `x` lies on the reference segment, compare based on `y`. if (area1 == 0) { return LessIf(area2 > 0, inversed); } // Form segments from the events. Segment seg0 = new(perhapsInversedX.Point, perhapsInversedX.OtherEvent.Point); Segment seg1 = new(perhapsInversedY.Point, perhapsInversedY.OtherEvent.Point); // Call the provided intersection method. int interResult = PolygonUtilities.FindIntersection(seg0, seg1, out Vertex pi0, out Vertex _); if (interResult == 0) { // No unique intersection found: decide based on area1. return LessIf(area1 > 0, inversed); } else if (interResult == 1) { // Unique intersection found. if (pi0 == y.Point) { return LessIf(area2 > 0, inversed); } return LessIf(area1 > 0, inversed); } // If interResult is neither 0 nor 1, fall through to collinear logic. } // Collinear branch – mimicking the Rust logic: if (perhapsInversedX.PolygonType == perhapsInversedY.PolygonType) { // Both segments belong to the same polygon. if (perhapsInversedX.Point == perhapsInversedY.Point) { // When left endpoints are identical, order by contour id. return LessIf(perhapsInversedX.ContourId < perhapsInversedY.ContourId, inversed); } // If left endpoints differ, the Rust version simply returns "less" (i.e. the one inserted earlier). // Here we mimic that by always returning -1. return LessIf(true, inversed); } // Segments are collinear but belong to different polygons. return LessIf(perhapsInversedX.PolygonType == PolygonType.Subject, inversed); } /// public int Compare(object? x, object? y) { if (x == null) { return -1; } if (y == null) { return 1; } if (x is SweepEvent a && y is SweepEvent b) { return this.Compare(a, b); } throw new ArgumentException("Both arguments must be of type SweepEvent.", nameof(x)); } /// /// Converts a boolean comparison result to an ordering value. /// Returns -1 if the condition is true, 1 if false. /// /// The boolean condition to evaluate. /// Should the result be inversed. /// -1 if condition is true, 1 if false. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int LessIf(bool condition, bool inversed = false) => condition ^ inversed ? -1 : 1; } }