Skip to content

Commit 0b0bf52

Browse files
committed
Clipper.Core - fixed residual issues with CrossProduct calcs (#1009)
1 parent 5de1066 commit 0b0bf52

File tree

8 files changed

+325
-185
lines changed

8 files changed

+325
-185
lines changed

CPP/Clipper2Lib/include/clipper2/clipper.core.h

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************************
22
* Author : Angus Johnson *
3-
* Date : 24 March 2025 *
3+
* Date : 7 October 2025 *
44
* Website : https://www.angusj.com *
55
* Copyright : Angus Johnson 2010-2025 *
66
* Purpose : Core Clipper Library structures and functions *
@@ -708,9 +708,10 @@ namespace Clipper2Lib
708708
{
709709
return lo == other.lo && hi == other.hi;
710710
};
711+
711712
};
712713

713-
inline UInt128Struct Multiply(uint64_t a, uint64_t b) // #834, #835
714+
inline UInt128Struct MultiplyUInt64(uint64_t a, uint64_t b) // #834, #835
714715
{
715716
// note to self - lamba expressions follow
716717
const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
@@ -719,10 +720,7 @@ namespace Clipper2Lib
719720
const uint64_t x1 = lo(a) * lo(b);
720721
const uint64_t x2 = hi(a) * lo(b) + hi(x1);
721722
const uint64_t x3 = lo(a) * hi(b) + lo(x2);
722-
const uint64_t lobits = lo(x3) << 32 | lo(x1);
723-
const uint64_t hibits = hi(a) * hi(b) + hi(x2) + hi(x3);
724-
725-
return { lobits, hibits };
723+
return { uint64_t(lo(x3) << 32 | lo(x1)), uint64_t(hi(a) * hi(b) + hi(x2) + hi(x3)) };
726724
}
727725

728726
// returns true if (and only if) a * b == c * d
@@ -739,8 +737,8 @@ namespace Clipper2Lib
739737
const auto abs_c = static_cast<uint64_t>(std::abs(c));
740738
const auto abs_d = static_cast<uint64_t>(std::abs(d));
741739

742-
const auto ab = Multiply(abs_a, abs_b);
743-
const auto cd = Multiply(abs_c, abs_d);
740+
const auto ab = MultiplyUInt64(abs_a, abs_b);
741+
const auto cd = MultiplyUInt64(abs_c, abs_d);
744742

745743
// nb: it's important to differentiate 0 values here from other values
746744
const auto sign_ab = TriSign(a) * TriSign(b);
@@ -765,14 +763,8 @@ namespace Clipper2Lib
765763
else if (ab < cd) return -1;
766764
else return 0;
767765
#else
768-
// nb: unsigned values needed for calculating carry into 'hi'
769-
const auto abs_a = static_cast<uint64_t>(std::abs(a));
770-
const auto abs_b = static_cast<uint64_t>(std::abs(b));
771-
const auto abs_c = static_cast<uint64_t>(std::abs(c));
772-
const auto abs_d = static_cast<uint64_t>(std::abs(d));
773-
774-
const auto ab = Multiply(abs_a, abs_b);
775-
const auto cd = Multiply(abs_c, abs_d);
766+
const auto ab = MultiplyUInt64(std::abs(a), std::abs(b));
767+
const auto cd = MultiplyUInt64(std::abs(c), std::abs(d));
776768

777769
const auto sign_ab = TriSign(a) * TriSign(b);
778770
const auto sign_cd = TriSign(c) * TriSign(d);
@@ -1005,19 +997,19 @@ namespace Clipper2Lib
1005997
{
1006998
if (inclusive)
1007999
{
1008-
double res1 = CrossProduct(seg1a, seg2a, seg2b);
1009-
double res2 = CrossProduct(seg1b, seg2a, seg2b);
1000+
int res1 = CrossProductSign(seg1a, seg2a, seg2b);
1001+
int res2 = CrossProductSign(seg1b, seg2a, seg2b);
10101002
if (res1 * res2 > 0) return false;
1011-
double res3 = CrossProduct(seg2a, seg1a, seg1b);
1012-
double res4 = CrossProduct(seg2b, seg1a, seg1b);
1003+
int res3 = CrossProductSign(seg2a, seg1a, seg1b);
1004+
int res4 = CrossProductSign(seg2b, seg1a, seg1b);
10131005
if (res3 * res4 > 0) return false;
10141006
return (res1 || res2 || res3 || res4); // ensures not collinear
10151007
}
10161008
else {
1017-
return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) *
1018-
GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) &&
1019-
(GetSign(CrossProduct(seg2a, seg1a, seg1b)) *
1020-
GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0);
1009+
return (CrossProductSign(seg1a, seg2a, seg2b) *
1010+
CrossProductSign(seg1b, seg2a, seg2b) < 0) &&
1011+
(CrossProductSign(seg2a, seg1a, seg1b) *
1012+
CrossProductSign(seg2b, seg1a, seg1b) < 0);
10211013
}
10221014
}
10231015

@@ -1105,7 +1097,7 @@ namespace Clipper2Lib
11051097
val = 1 - val; // toggle val
11061098
else
11071099
{
1108-
double d = CrossProduct(*prev, *curr, pt);
1100+
int d = CrossProductSign(*prev, *curr, pt);
11091101
if (d == 0) return PointInPolygonResult::IsOn;
11101102
if ((d < 0) == is_above) val = 1 - val;
11111103
}
@@ -1119,7 +1111,7 @@ namespace Clipper2Lib
11191111
if (curr == cend) curr = cbegin;
11201112
if (curr == cbegin) prev = cend - 1;
11211113
else prev = curr - 1;
1122-
double d = CrossProduct(*prev, *curr, pt);
1114+
int d = CrossProductSign(*prev, *curr, pt);
11231115
if (d == 0) return PointInPolygonResult::IsOn;
11241116
if ((d < 0) == is_above) val = 1 - val;
11251117
}

CSharp/Clipper2Lib/Clipper.Core.cs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************************
22
* Author : Angus Johnson *
3-
* Date : 22 January 2025 *
3+
* Date : 7 October 2025 *
44
* Website : https://www.angusj.com *
55
* Copyright : Angus Johnson 2010-2025 *
66
* Purpose : Core structures and functions for the Clipper Library *
@@ -536,6 +536,31 @@ public static double CrossProduct(Point64 pt1, Point64 pt2, Point64 pt3)
536536
(double) (pt2.Y - pt1.Y) * (pt3.X - pt2.X));
537537
}
538538

539+
public static int CrossProductSign(Point64 pt1, Point64 pt2, Point64 pt3)
540+
{
541+
long a = pt2.X - pt1.X;
542+
long b = pt3.Y - pt2.Y;
543+
long c = pt2.Y - pt1.Y;
544+
long d = pt3.X - pt2.X;
545+
UInt128Struct ab = MultiplyUInt64((ulong) Math.Abs(a), (ulong) Math.Abs(b));
546+
UInt128Struct cd = MultiplyUInt64((ulong) Math.Abs(c), (ulong) Math.Abs(d));
547+
var signAB = TriSign(a) * TriSign(b);
548+
var signCD = TriSign(c) * TriSign(d);
549+
550+
if (signAB == signCD)
551+
{
552+
int result;
553+
if (ab.hi64 == cd.hi64)
554+
{
555+
if (ab.lo64 == cd.lo64) return 0;
556+
result = (ab.lo64 > cd.lo64) ? 1 : -1;
557+
}
558+
else result = (ab.hi64 > cd.hi64) ? 1 : -1;
559+
return (signAB > 0) ? result : -result;
560+
}
561+
return (signAB > signCD) ? 1 : -1;
562+
}
563+
539564
#if USINGZ
540565
public static Path64 SetZ(Path64 path, long Z)
541566
{
@@ -561,8 +586,7 @@ internal static bool IsAlmostZero(double value)
561586
[MethodImpl(MethodImplOptions.AggressiveInlining)]
562587
internal static int TriSign(long x) // returns 0, 1 or -1
563588
{
564-
if (x < 0) return -1;
565-
return x > 1 ? 1 : 0;
589+
return (x < 0) ? -1 : (x > 0) ? 1 : 0;
566590
}
567591

568592
public struct UInt128Struct
@@ -675,15 +699,15 @@ internal static bool SegsIntersect(Point64 seg1a,
675699
Point64 seg1b, Point64 seg2a, Point64 seg2b, bool inclusive = false)
676700
{
677701
if (!inclusive)
678-
return (CrossProduct(seg1a, seg2a, seg2b) *
679-
CrossProduct(seg1b, seg2a, seg2b) < 0) &&
680-
(CrossProduct(seg2a, seg1a, seg1b) *
681-
CrossProduct(seg2b, seg1a, seg1b) < 0);
682-
double res1 = CrossProduct(seg1a, seg2a, seg2b);
683-
double res2 = CrossProduct(seg1b, seg2a, seg2b);
702+
return (CrossProductSign(seg1a, seg2a, seg2b) *
703+
CrossProductSign(seg1b, seg2a, seg2b) < 0) &&
704+
(CrossProductSign(seg2a, seg1a, seg1b) *
705+
CrossProductSign(seg2b, seg1a, seg1b) < 0);
706+
int res1 = CrossProductSign(seg1a, seg2a, seg2b);
707+
int res2 = CrossProductSign(seg1b, seg2a, seg2b);
684708
if (res1 * res2 > 0) return false;
685-
double res3 = CrossProduct(seg2a, seg1a, seg1b);
686-
double res4 = CrossProduct(seg2b, seg1a, seg1b);
709+
int res3 = CrossProductSign(seg2a, seg1a, seg1b);
710+
int res4 = CrossProductSign(seg2b, seg1a, seg1b);
687711
if (res3 * res4 > 0) return false;
688712
// ensure NOT collinear
689713
return (res1 != 0 || res2 != 0 || res3 != 0 || res4 != 0);
@@ -727,7 +751,6 @@ public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon)
727751
while (start < len && polygon[start].Y == pt.Y) start++;
728752
if (start == len) return PointInPolygonResult.IsOutside;
729753

730-
double d;
731754
bool isAbove = polygon[start].Y < pt.Y, startingAbove = isAbove;
732755
int val = 0, i = start + 1, end = len;
733756
while (true)
@@ -774,20 +797,22 @@ public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon)
774797
}
775798
else
776799
{
777-
d = CrossProduct(prev, curr, pt);
778-
if (d == 0) return PointInPolygonResult.IsOn;
779-
if ((d < 0) == isAbove) val = 1 - val;
800+
int cps2 = CrossProductSign(prev, curr, pt);
801+
if (cps2 == 0) return PointInPolygonResult.IsOn;
802+
if ((cps2 < 0) == isAbove) val = 1 - val;
780803
}
781804
isAbove = !isAbove;
782805
i++;
783806
}
784807

785808
if (isAbove == startingAbove) return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside;
786-
if (i == len) i = 0;
787-
d = i == 0 ? CrossProduct(polygon[len - 1], polygon[0], pt) : CrossProduct(polygon[i - 1], polygon[i], pt);
788-
if (d == 0) return PointInPolygonResult.IsOn;
789-
if ((d < 0) == isAbove) val = 1 - val;
809+
if (i == len) i = 0;
810+
int cps = (i == 0) ?
811+
CrossProductSign(polygon[len - 1], polygon[0], pt) :
812+
CrossProductSign(polygon[i - 1], polygon[i], pt);
790813

814+
if (cps == 0) return PointInPolygonResult.IsOn;
815+
if ((cps < 0) == isAbove) val = 1 - val;
791816
return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside;
792817
}
793818

CSharp/Clipper2Lib/Clipper.Engine.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************************
22
* Author : Angus Johnson *
3-
* Date : 15 June 2025 *
3+
* Date : 7 October 2025 *
44
* Website : https://www.angusj.com *
55
* Copyright : Angus Johnson 2010-2025 *
66
* Purpose : This is the main polygon clipping module *
@@ -1073,7 +1073,7 @@ private static bool IsValidAelOrder(Active resident, Active newcomer)
10731073
return newcomer.curX > resident.curX;
10741074

10751075
// get the turning direction a1.top, a2.bot, a2.top
1076-
double d = InternalClipper.CrossProduct(resident.top, newcomer.bot, newcomer.top);
1076+
int d = InternalClipper.CrossProductSign(resident.top, newcomer.bot, newcomer.top);
10771077
if (d != 0) return (d < 0);
10781078

10791079
// edges must be collinear to get here
@@ -1082,13 +1082,13 @@ private static bool IsValidAelOrder(Active resident, Active newcomer)
10821082
// the direction they're about to turn
10831083
if (!IsMaxima(resident) && (resident.top.Y > newcomer.top.Y))
10841084
{
1085-
return InternalClipper.CrossProduct(newcomer.bot,
1085+
return InternalClipper.CrossProductSign(newcomer.bot,
10861086
resident.top, NextVertex(resident).pt) <= 0;
10871087
}
10881088

10891089
if (!IsMaxima(newcomer) && (newcomer.top.Y > resident.top.Y))
10901090
{
1091-
return InternalClipper.CrossProduct(newcomer.bot,
1091+
return InternalClipper.CrossProductSign(newcomer.bot,
10921092
newcomer.top, NextVertex(newcomer).pt) >= 0;
10931093
}
10941094

@@ -1103,7 +1103,7 @@ private static bool IsValidAelOrder(Active resident, Active newcomer)
11031103
if (InternalClipper.IsCollinear(PrevPrevVertex(resident).pt,
11041104
resident.bot, resident.top)) return true;
11051105
// compare turning direction of the alternate bound
1106-
return (InternalClipper.CrossProduct(PrevPrevVertex(resident).pt,
1106+
return (InternalClipper.CrossProductSign(PrevPrevVertex(resident).pt,
11071107
newcomer.bot, PrevPrevVertex(newcomer).pt) > 0) == newcomerIsLeft;
11081108
}
11091109

@@ -2646,7 +2646,7 @@ private static PointInPolygonResult PointInOpPolygon(Point64 pt, OutPt op)
26462646
val = 1 - val; // toggle val
26472647
else
26482648
{
2649-
double d = InternalClipper.CrossProduct(op2.prev.pt, op2.pt, pt);
2649+
int d = InternalClipper.CrossProductSign(op2.prev.pt, op2.pt, pt);
26502650
if (d == 0) return PointInPolygonResult.IsOn;
26512651
if ((d < 0) == isAbove) val = 1 - val;
26522652
}
@@ -2657,7 +2657,7 @@ private static PointInPolygonResult PointInOpPolygon(Point64 pt, OutPt op)
26572657

26582658
if (isAbove == startingAbove) return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside;
26592659
{
2660-
double d = InternalClipper.CrossProduct(op2.prev.pt, op2.pt, pt);
2660+
int d = InternalClipper.CrossProductSign(op2.prev.pt, op2.pt, pt);
26612661
if (d == 0) return PointInPolygonResult.IsOn;
26622662
if ((d < 0) == isAbove) val = 1 - val;
26632663
}

CSharp/Clipper2Lib/Clipper.Offset.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************************
22
* Author : Angus Johnson *
3-
* Date : 4 May 2025 *
3+
* Date : 7 October 2025 *
44
* Website : https://www.angusj.com *
55
* Copyright : Angus Johnson 2010-2025 *
66
* Purpose : Path Offset (Inflate/Shrink) *

CSharp/Clipper2Lib/Clipper.RectClip.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/*******************************************************************************
22
* Author : Angus Johnson *
3-
* Date : 10 October 2024 *
3+
* Date : 7 October 2025 *
44
* Website : https://www.angusj.com *
5-
* Copyright : Angus Johnson 2010-2024 *
5+
* Copyright : Angus Johnson 2010-2025 *
66
* Purpose : FAST rectangular clipping *
77
* License : https://www.boost.org/LICENSE_1_0.txt *
88
*******************************************************************************/
@@ -112,7 +112,7 @@ private static bool IsClockwise(Location prev, Location curr,
112112
Point64 prevPt, Point64 currPt, Point64 rectMidPoint)
113113
{
114114
if (AreOpposites(prev, curr))
115-
return InternalClipper.CrossProduct(prevPt, rectMidPoint, currPt) < 0;
115+
return InternalClipper.CrossProductSign(prevPt, rectMidPoint, currPt) < 0;
116116
return HeadingClockwise(prev, curr);
117117
}
118118

@@ -278,8 +278,8 @@ private static bool IsHorizontal(Point64 pt1, Point64 pt2)
278278
private static bool GetSegmentIntersection(Point64 p1,
279279
Point64 p2, Point64 p3, Point64 p4, out Point64 ip)
280280
{
281-
double res1 = InternalClipper.CrossProduct(p1, p3, p4);
282-
double res2 = InternalClipper.CrossProduct(p2, p3, p4);
281+
int res1 = InternalClipper.CrossProductSign(p1, p3, p4);
282+
int res2 = InternalClipper.CrossProductSign(p2, p3, p4);
283283
if (res1 == 0)
284284
{
285285
ip = p1;
@@ -303,8 +303,8 @@ private static bool GetSegmentIntersection(Point64 p1,
303303
return false;
304304
}
305305

306-
double res3 = InternalClipper.CrossProduct(p3, p1, p2);
307-
double res4 = InternalClipper.CrossProduct(p4, p1, p2);
306+
int res3 = InternalClipper.CrossProductSign(p3, p1, p2);
307+
int res4 = InternalClipper.CrossProductSign(p4, p1, p2);
308308
if (res3 == 0)
309309
{
310310
ip = p3;

0 commit comments

Comments
 (0)