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:
And here the results with the current buggy comparer:
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.
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):
Helper methods:
I also implemented the unit tests from Lior; here the results with the fixed comparer:
And here the results with the current buggy comparer:
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 needFindTransitionas List<T> comes with an equivalentBinarySearchmethod out of the box.