Skip to content

compareSegments does not sort correctly #9

Description

@Fribur

While working on issue #8 (I think I got a fairly simple solution which I will post under issue 8 once final), I noticed compareSegments is not sorting correctly, and think for clarity this warrants this dedicated issue. In fact the credit for discovering this bug and finding a solution goes all to Lior Sinai.

The correct comparer (copy and paste from my C# implementation):

public struct EventSegmentComparer : IComparer<EventBool>
{
    //assumes segments are already sorted by x coordinate (seg1 is on x-axis before seg2)
    /// <summary> Sorts events in DESCENDING order on y-axis </summary>
    /// <returns>Returns 1 if eventA is smaller, -1 if bEvent is smaller, 0 if equal </returns>
    public int Compare(EventBool eventA, EventBool eventB)
    {
        Segment seg1 = eventA.seg;
        Segment seg2 = eventB.seg;
        var a = seg1.p0_start;
        var b = seg1.p1_end;
        var c = seg2.p0_start;
        var d = seg2.p1_end;
        if (PointUtils.IsCollinear(c, b, a, out double orient2d))
            return PointUtils.IsAboveOrOn(a, b, d) < 0 ? -1 : 1;
        if (c.x < a.x)
            return PointUtils.IsAboveOrOn(c, d, a) >= 0 ? -1 : 1;
        else
            return PointUtils.IsAboveOrOn(a, b, c) < 0 ? -1 : 1;
    }
}

Helper methods:

/// <summary>
/// Returns a positive value if the points a, b, and p occur in counterclockwise order (CCW, p lies to the left of the directed line defined by points a and b).
/// Returns a negative value if they occur in clockwise order(CW, p lies to the right of the directed line ab).
/// Returns zero if they are collinear.
/// Result also happens to be twice the signed area of the triangle
/// </summary>  
public static double Orient2DFast(double2 a, double2 b, double2 p)
{
    return (a.x - p.x) * (b.y - p.y) - (a.y - p.y) * (b.x - p.x);
}

public static bool IsCollinear(double2 a, double2 b, double2 p, out double orient2d)
{
    orient2d = Orient2DFast(a, b, p);
    return math.abs(orient2d) < epsilon1;
}

public static double IsAboveOrOn(double2 a, double2 b, double2 p)
{
    if(GenericEquals(b.x, a.x))
        return p.y >= math.max(a.y, b.y) ? 1 : -1;

    var orient2d = Orient2DFast(a, b, p);
    return orient2d;
}

/// <summary>Tolerance comparison for large and small values. https://realtimecollisiondetection.net/blog/?p=89</summary>
public static bool GenericEquals(double x, double y)
{
    return math.abs(x - y) <= math.max(epsilon1, relativeTolerance * math.max(math.abs(x), math.abs(y)));
}

public const float epsilon1   = 0.000002f; //next representable float at 1 is 1 +- 1.19*10^-7, so set epsilon a bit larger than that
public const float epsilon100 = 0.0001f;//next representable float at 100 is 100 +- 7.6*10^-6, so set epsilon a bit larger than that

I also implemented the unit tests from Lior; here the results with the fixed comparer:

Image

And here the results with the current buggy comparer:

Image

Lastly, while I am not familiar with Typescript, I think this compare function is unnecessary, because check(b), which is check(node) will always be 0. So the while loop test can be directly if (check(this.nodes[mid]) < 0). In fact in C# I do not need FindTransition as List<T> comes with an equivalent BinarySearch method out of the box.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions