diff --git a/src/main/java/clipper2/Clipper.java b/src/main/java/clipper2/Clipper.java index 9fe2339..0bfeedf 100644 --- a/src/main/java/clipper2/Clipper.java +++ b/src/main/java/clipper2/Clipper.java @@ -808,7 +808,7 @@ public static RectD GetBounds(PathD path) { result.bottom = pt.y; } } - return result.left == Double.MAX_VALUE ? new RectD() : result; + return Math.abs(result.left - Double.MAX_VALUE) < InternalClipper.FLOATING_POINT_TOLERANCE ? new RectD() : result; } public static RectD GetBounds(PathsD paths) { @@ -829,7 +829,7 @@ public static RectD GetBounds(PathsD paths) { } } } - return result.left == Double.MAX_VALUE ? new RectD() : result; + return Math.abs(result.left - Double.MAX_VALUE) < InternalClipper.FLOATING_POINT_TOLERANCE ? new RectD() : result; } public static Path64 MakePath(int[] arr) { @@ -1366,10 +1366,10 @@ public static Path64 TrimCollinear(Path64 path, boolean isOpen) { int len = path.size(); int i = 0; if (!isOpen) { - while (i < len - 1 && InternalClipper.CrossProduct(path.get(len - 1), path.get(i), path.get(i + 1)) == 0) { + while (i < len - 1 && InternalClipper.IsCollinear(path.get(len - 1), path.get(i), path.get(i + 1))) { i++; } - while (i < len - 1 && InternalClipper.CrossProduct(path.get(len - 2), path.get(len - 1), path.get(i)) == 0) { + while (i < len - 1 && InternalClipper.IsCollinear(path.get(len - 2), path.get(len - 1), path.get(i))) { len--; } } @@ -1385,7 +1385,7 @@ public static Path64 TrimCollinear(Path64 path, boolean isOpen) { Point64 last = path.get(i); result.add(last); for (i++; i < len - 1; i++) { - if (InternalClipper.CrossProduct(last, path.get(i), path.get(i + 1)) == 0) { + if (InternalClipper.IsCollinear(last, path.get(i), path.get(i + 1))) { continue; } last = path.get(i); @@ -1394,10 +1394,10 @@ public static Path64 TrimCollinear(Path64 path, boolean isOpen) { if (isOpen) { result.add(path.get(len - 1)); - } else if (InternalClipper.CrossProduct(last, path.get(len - 1), result.get(0)) != 0) { + } else if (!InternalClipper.IsCollinear(last, path.get(len - 1), result.get(0))) { result.add(path.get(len - 1)); } else { - while (result.size() > 2 && InternalClipper.CrossProduct(result.get(result.size() - 1), result.get(result.size() - 2), result.get(0)) == 0) { + while (result.size() > 2 && InternalClipper.IsCollinear(result.get(result.size() - 1), result.get(result.size() - 2), result.get(0))) { result.remove(result.size() - 1); } if (result.size() < 3) { diff --git a/src/main/java/clipper2/core/InternalClipper.java b/src/main/java/clipper2/core/InternalClipper.java index 634f738..bb3c660 100644 --- a/src/main/java/clipper2/core/InternalClipper.java +++ b/src/main/java/clipper2/core/InternalClipper.java @@ -9,7 +9,7 @@ public final class InternalClipper { private static final long Invalid64 = Long.MAX_VALUE; public static final double DEFAULT_ARC_TOLERANCE = 0.25; - private static final double FLOATING_POINT_TOLERANCE = 1E-12; + public static final double FLOATING_POINT_TOLERANCE = 1E-12; // private static final double DEFAULT_MIN_EDGE_LENGTH = 0.1; private static final String PRECISION_RANGE_ERROR = "Error: Precision is out of range."; @@ -52,7 +52,7 @@ public static long CheckCastInt64(double val) { return (long) Math.rint(val); } - public static boolean GetIntersectPoint(Point64 ln1a, Point64 ln1b, Point64 ln2a, Point64 ln2b, /* out */ Point64 ip) { + public static boolean GetSegmentIntersectPt(Point64 ln1a, Point64 ln1b, Point64 ln2a, Point64 ln2b, /* out */ Point64 ip) { double dy1 = (ln1b.y - ln1a.y); double dx1 = (ln1b.x - ln1a.x); double dy2 = (ln2b.y - ln2a.y); @@ -86,6 +86,10 @@ public static boolean GetIntersectPoint(Point64 ln1a, Point64 ln1b, Point64 ln2a return true; } + public static boolean GetIntersectPoint(Point64 ln1a, Point64 ln1b, Point64 ln2a, Point64 ln2b, /* out */ Point64 ip) { + return GetSegmentIntersectPt(ln1a, ln1b, ln2a, ln2b, ip); + } + public static boolean SegsIntersect(Point64 seg1a, Point64 seg1b, Point64 seg2a, Point64 seg2b) { return SegsIntersect(seg1a, seg1b, seg2a, seg2b, false); } @@ -295,4 +299,4 @@ private static int triSign(long x) { return x > 0 ? 1 : (x < 0 ? -1 : 0); } -} \ No newline at end of file +} diff --git a/src/main/java/clipper2/core/Path64.java b/src/main/java/clipper2/core/Path64.java index 590c4d9..cee6901 100644 --- a/src/main/java/clipper2/core/Path64.java +++ b/src/main/java/clipper2/core/Path64.java @@ -36,7 +36,10 @@ public Path64(Point64... path) { public String toString() { StringBuilder s = new StringBuilder(); for (Point64 p : this) { - s.append(p.toString()).append(" "); + s.append(p.toString()).append(", "); + } + if (!s.isEmpty()) { + s.setLength(s.length() - 2); } return s.toString(); } diff --git a/src/main/java/clipper2/core/PathD.java b/src/main/java/clipper2/core/PathD.java index 8e10f9c..dd56ff0 100644 --- a/src/main/java/clipper2/core/PathD.java +++ b/src/main/java/clipper2/core/PathD.java @@ -31,7 +31,10 @@ public PathD(List path) { public String toString() { StringBuilder s = new StringBuilder(); for (PointD p : this) { - s.append(p.toString()).append(" "); + s.append(p.toString()).append(", "); + } + if (!s.isEmpty()) { + s.setLength(s.length() - 2); } return s.toString(); } diff --git a/src/main/java/clipper2/engine/ClipperBase.java b/src/main/java/clipper2/engine/ClipperBase.java index 22c0d93..9aff17c 100644 --- a/src/main/java/clipper2/engine/ClipperBase.java +++ b/src/main/java/clipper2/engine/ClipperBase.java @@ -1003,7 +1003,7 @@ private static boolean IsValidAelOrder(Active resident, Active newcomer) { if (resident.isLeftBound != newcomerIsLeft) { return newcomerIsLeft; } - if (InternalClipper.CrossProduct(PrevPrevVertex(resident).pt, resident.bot, resident.top) == 0) { + if (InternalClipper.IsCollinear(PrevPrevVertex(resident).pt, resident.bot, resident.top)) { return true; } // compare turning direction of the alternate bound @@ -1416,13 +1416,13 @@ private static Active FindEdgeWithMatchingLocMin(Active e) { return result; } - private OutPt IntersectEdges(Active ae1, Active ae2, Point64 pt) { + private void IntersectEdges(Active ae1, Active ae2, Point64 pt) { OutPt resultOp = null; // MANAGE OPEN PATH INTERSECTIONS SEPARATELY ... if (hasOpenPaths && (IsOpen(ae1) || IsOpen(ae2))) { if (IsOpen(ae1) && IsOpen(ae2)) { - return null; + return; } // the following line avoids duplicating quite a bit of code if (IsOpen(ae2)) { @@ -1436,26 +1436,26 @@ private OutPt IntersectEdges(Active ae1, Active ae2, Point64 pt) { if (cliptype == ClipType.Union) { if (!IsHotEdge(ae2)) { - return null; + return; } } else if (ae2.localMin.polytype == PathType.Subject) { - return null; + return; } switch (fillrule) { case Positive : if (ae2.windCount != 1) { - return null; + return; } break; case Negative : if (ae2.windCount != -1) { - return null; + return; } break; default : if (Math.abs(ae2.windCount) != 1) { - return null; + return; } break; } @@ -1483,7 +1483,7 @@ else if (pt.opEquals(ae1.localMin.vertex.pt) && !IsOpenEnd(ae1.localMin.vertex)) } else { SetSides(ae3.outrec, ae3, ae1); } - return ae3.outrec.pts; + return; } resultOp = StartOpenPath(ae1, pt); @@ -1491,7 +1491,7 @@ else if (pt.opEquals(ae1.localMin.vertex.pt) && !IsOpenEnd(ae1.localMin.vertex)) resultOp = StartOpenPath(ae1, pt); } - return resultOp; + return; } // MANAGING CLOSED PATHS FROM HERE ON @@ -1554,7 +1554,7 @@ else if (pt.opEquals(ae1.localMin.vertex.pt) && !IsOpenEnd(ae1.localMin.vertex)) boolean e2WindCountIs0or1 = oldE2WindCount == 0 || oldE2WindCount == 1; if ((!IsHotEdge(ae1) && !e1WindCountIs0or1) || (!IsHotEdge(ae2) && !e2WindCountIs0or1)) { - return null; + return; } // NOW PROCESS THE INTERSECTION ... @@ -1612,7 +1612,7 @@ else if (IsHotEdge(ae1)) { switch (cliptype) { case Union : if (e1Wc2 > 0 && e2Wc2 > 0) { - return null; + return; } resultOp = AddLocalMinPoly(ae1, ae2, pt); break; @@ -1631,15 +1631,13 @@ else if (IsHotEdge(ae1)) { default : // ClipType.Intersection: if (e1Wc2 <= 0 || e2Wc2 <= 0) { - return null; + return; } resultOp = AddLocalMinPoly(ae1, ae2, pt); break; } } } - - return resultOp; } private void DeleteFromAEL(Active ae) { @@ -1727,7 +1725,7 @@ private void DisposeIntersectNodes() { private void AddNewIntersectNode(Active ae1, Active ae2, long topY) { Point64 ip = new Point64(); - if (!InternalClipper.GetIntersectPoint(ae1.bot, ae1.top, ae2.bot, ae2.top, ip)) { + if (!InternalClipper.GetSegmentIntersectPt(ae1.bot, ae1.top, ae2.bot, ae2.top, ip)) { ip = new Point64(ae1.curX, topY); } @@ -2232,7 +2230,7 @@ private void CheckJoinLeft(Active e, Point64 pt) { private void CheckJoinLeft(Active e, Point64 pt, boolean checkCurrX) { @Nullable Active prev = e.prevInAEL; - if (prev == null || IsOpen(e) || IsOpen(prev) || !IsHotEdge(e) || !IsHotEdge(prev)) { + if (prev == null || !IsHotEdge(e) || !IsHotEdge(prev) || IsHorizontal(e) || IsHorizontal(prev) || IsOpen(e) || IsOpen(prev)) { return; } @@ -2249,7 +2247,7 @@ private void CheckJoinLeft(Active e, Point64 pt, boolean checkCurrX) { } else if (e.curX != prev.curX) { return; } - if (InternalClipper.CrossProduct(e.top, pt, prev.top) != 0) { + if (!InternalClipper.IsCollinear(e.top, pt, prev.top)) { return; } @@ -2272,7 +2270,7 @@ private void CheckJoinRight(Active e, Point64 pt) { private void CheckJoinRight(Active e, Point64 pt, boolean checkCurrX) { @Nullable Active next = e.nextInAEL; - if (next == null || IsOpen(e) || IsOpen(next) || !IsHotEdge(e) || !IsHotEdge(next) || IsJoined(e)) { + if (next == null || !IsHotEdge(e) || !IsHotEdge(next) || IsHorizontal(e) || IsHorizontal(next) || IsOpen(e) || IsOpen(next)) { return; } @@ -2289,7 +2287,7 @@ private void CheckJoinRight(Active e, Point64 pt, boolean checkCurrX) { } else if (e.curX != next.curX) { return; } - if (InternalClipper.CrossProduct(e.top, pt, next.top) != 0) { + if (!InternalClipper.IsCollinear(e.top, pt, next.top)) { return; } @@ -2656,7 +2654,7 @@ private void CleanCollinear(OutRec outrec) { OutPt op2 = startOp; for (;;) { // NB if preserveCollinear == true, then only remove 180 deg. spikes - if ((InternalClipper.CrossProduct(op2.prev.pt, op2.pt, op2.next.pt) == 0) && ((op2.pt.opEquals(op2.prev.pt)) || (op2.pt.opEquals(op2.next.pt)) + if (InternalClipper.IsCollinear(op2.prev.pt, op2.pt, op2.next.pt) && ((op2.pt.opEquals(op2.prev.pt)) || (op2.pt.opEquals(op2.next.pt)) || !getPreserveCollinear() || (InternalClipper.DotProduct(op2.prev.pt, op2.pt, op2.next.pt) < 0))) { if (op2.equals(outrec.pts)) { outrec.pts = op2.prev; @@ -2686,7 +2684,7 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp) { // OutPt result = prevOp; Point64 ip = new Point64(); // ip mutated by GetIntersectPoint() - InternalClipper.GetIntersectPoint(prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, ip); + InternalClipper.GetSegmentIntersectPt(prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, ip); double area1 = Area(prevOp); double absArea1 = Math.abs(area1); @@ -2793,7 +2791,7 @@ public static boolean BuildPath(@Nullable OutPt op, boolean reverse, boolean isO } } - if (path.size() == 3 && IsVerySmallTriangle(op2)) { + if (path.size() == 3 && !isOpen && IsVerySmallTriangle(op2)) { return false; } else { return true; diff --git a/src/main/java/clipper2/offset/ClipperOffset.java b/src/main/java/clipper2/offset/ClipperOffset.java index 858e929..68a43af 100644 --- a/src/main/java/clipper2/offset/ClipperOffset.java +++ b/src/main/java/clipper2/offset/ClipperOffset.java @@ -1,8 +1,5 @@ package clipper2.offset; -import static clipper2.core.InternalClipper.MAX_COORD; -import static clipper2.core.InternalClipper.MIN_COORD; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -69,7 +66,6 @@ public class ClipperOffset { private static double TOLERANCE = 1.0E-12; - private static final String COORD_RANGE_ERROR = "Error: Coordinate range."; private final List groupList = new ArrayList<>(); private Path64 pathOut = new Path64(); @@ -459,7 +455,7 @@ private void DoSquare(Path64 path, int j, int k) { } } - private void DoMiter(Group group, Path64 path, int j, int k, double cosA) { + private void DoMiter(Path64 path, int j, int k, double cosA) { final double q = groupDelta / (cosA + 1); pathOut.add(new Point64(path.get(j).x + (normals.get(k).x + normals.get(j).x) * q, path.get(j).y + (normals.get(k).y + normals.get(j).y) * q)); } @@ -497,6 +493,10 @@ private void DoRound(Path64 path, int j, int k, double angle) { private void BuildNormals(Path64 path) { int cnt = path.size(); normals.clear(); + if (cnt == 0) { + return; + } + normals.ensureCapacity(cnt); for (int i = 0; i < cnt - 1; i++) { normals.add(GetUnitNormal(path.get(i), path.get(i + 1))); @@ -505,6 +505,9 @@ private void BuildNormals(Path64 path) { } private int OffsetPoint(Group group, Path64 path, int j, int k) { + if (path.get(j).equals(path.get(k))) { + return j; + } // Let A = change in angle where edges join // A == 0: ie no change in angle (flat join) // A == PI: edges 'spike' @@ -529,20 +532,22 @@ private int OffsetPoint(Group group, Path64 path, int j, int k) { return j; } - if (cosA > -0.99 && (sinA * groupDelta < 0)) { // test for concavity first (#593) + if (cosA > -0.999 && (sinA * groupDelta < 0)) { // test for concavity first (#593) // is concave pathOut.add(GetPerpendic(path.get(j), normals.get(k))); - // this extra point is the only (simple) way to ensure that - // path reversals are fully cleaned with the trailing clipper - pathOut.add(path.get(j)); // (#405) + // this extra point is the only simple way to ensure that path reversals + // are fully cleaned out with the trailing union op. + if (cosA < 0.99) { + pathOut.add(path.get(j)); // (#405) + } pathOut.add(GetPerpendic(path.get(j), normals.get(j))); } else if (cosA > 0.999 && joinType != JoinType.Round) { // almost straight - less than 2.5 degree (#424, #482, #526 & #724) - DoMiter(group, path, j, k, cosA); + DoMiter(path, j, k, cosA); } else if (joinType == JoinType.Miter) { // miter unless the angle is sufficiently acute to exceed ML if (cosA > mitLimSqr - 1) { - DoMiter(group, path, j, k, cosA); + DoMiter(path, j, k, cosA); } else { DoSquare(path, j, k); } @@ -584,16 +589,6 @@ private void OffsetOpenPath(Group group, Path64 path) { return; } - // Overwrite the polygon-based normals with normals for an open path - normals.clear(); - for (int i = 0; i < highI; ++i) { - normals.add(GetUnitNormal(path.get(i), path.get(i + 1))); - } - // The C# version uses a clever trick by calculating n normals and then - // overwriting them. A clearer approach in Java is to build the correct - // n-1 normals first, and then add a temporary one for the logic to work. - normals.add(new PointD(normals.get(highI - 1))); - if (deltaCallback != null) { groupDelta = deltaCallback.calculate(path, normals, 0, 0); } @@ -659,10 +654,6 @@ private void OffsetOpenPath(Group group, Path64 path) { solution.add(pathOut); } - private static boolean ToggleBoolIf(boolean val, boolean condition) { - return condition ? !val : val; - } - private void DoGroupOffset(Group group) { if (group.endType == EndType.Polygon) { // a straight path (2 points) can now also be 'polygon' offset @@ -676,19 +667,15 @@ private void DoGroupOffset(Group group) { } double absDelta = Math.abs(groupDelta); - if (!ValidateBounds(group.boundsList, absDelta)) { - throw new RuntimeException(COORD_RANGE_ERROR); - } joinType = group.joinType; endType = group.endType; if (group.joinType == JoinType.Round || group.endType == EndType.Round) { - // calculate a sensible number of steps (for 360 deg for the given offset - // arcTol - when fArcTolerance is undefined (0), the amount of - // curve imprecision that's allowed is based on the size of the - // offset (delta). Obviously very large offsets will almost always - // require much less precision. + // calculate the number of steps required to approximate a circle + // (see http://www.angusj.com/clipper2/Docs/Trigonometry.htm) + // arcTol - when arcTolerance is undefined (0), curve imprecision + // will be relative to the size of the offset (delta). double arcTol = arcTolerance > 0.01 ? arcTolerance : Math.log10(2 + absDelta) * InternalClipper.DEFAULT_ARC_TOLERANCE; double stepsPer360 = Math.PI / Math.acos(1 - arcTol / absDelta); stepSin = Math.sin((2 * Math.PI) / stepsPer360); @@ -699,22 +686,22 @@ private void DoGroupOffset(Group group) { stepsPerRad = stepsPer360 / (2 * Math.PI); } - int i = 0; for (Path64 p : group.inPaths) { - // NOTE use int i rather than 3 iterators - Rect64 pathBounds = group.boundsList.get(i); - boolean isHole = group.isHoleList.get(i++); - if (!pathBounds.IsValid()) { - continue; - } + pathOut = new Path64(); int cnt = p.size(); if ((cnt == 0) || ((cnt < 3) && (endType == EndType.Polygon))) { continue; } - pathOut = new Path64(); if (cnt == 1) { Point64 pt = p.get(0); + if (deltaCallback != null) { + groupDelta = deltaCallback.calculate(p, normals, 0, 0); + if (group.pathsReversed) { + groupDelta = -groupDelta; + } + absDelta = Math.abs(groupDelta); + } // single vertex so build a circle or square ... if (group.endType == EndType.Round) { @@ -730,11 +717,6 @@ private void DoGroupOffset(Group group) { continue; } // end of offsetting a single point - // when shrinking outer paths, make sure they can shrink this far (#593) - // also when shrinking holes, make sure they too can shrink this far (#715) - if (((groupDelta > 0) == ToggleBoolIf(isHole, group.pathsReversed)) && (Math.min(pathBounds.getWidth(), pathBounds.getHeight()) <= -groupDelta * 2)) - continue; - if (cnt == 2 && group.endType == EndType.Joined) { endType = (group.joinType == JoinType.Round) ? EndType.Round : EndType.Square; } @@ -750,16 +732,4 @@ private void DoGroupOffset(Group group) { } } - private static boolean ValidateBounds(List boundsList, double delta) { - int intDelta = (int) delta; - for (Rect64 r : boundsList) { - if (!r.IsValid()) { - continue; // ignore invalid paths - } else if (r.left < MIN_COORD + intDelta || r.right > MAX_COORD + intDelta || r.top < MIN_COORD + intDelta || r.bottom > MAX_COORD + intDelta) { - return false; - } - } - return true; - } - } diff --git a/src/main/java/clipper2/offset/Group.java b/src/main/java/clipper2/offset/Group.java index 19b4884..8f0469d 100644 --- a/src/main/java/clipper2/offset/Group.java +++ b/src/main/java/clipper2/offset/Group.java @@ -1,20 +1,15 @@ package clipper2.offset; import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import clipper2.Clipper; import clipper2.core.Path64; import clipper2.core.Paths64; import clipper2.core.Point64; -import clipper2.core.Rect64; class Group { Paths64 inPaths; - List boundsList; - List isHoleList; JoinType joinType; EndType endType; boolean pathsReversed; @@ -35,74 +30,28 @@ class Group { inPaths.add(Clipper.StripDuplicates(path, isJoined)); } - // get bounds of each path --> boundsList - boundsList = new ArrayList<>(inPaths.size()); - GetMultiBounds(inPaths, boundsList); - if (endType == EndType.Polygon) { - lowestPathIdx = GetLowestPathIdx(boundsList); - isHoleList = new ArrayList<>(inPaths.size()); - - for (Path64 path : inPaths) { - isHoleList.add(Clipper.Area(path) < 0); - } - - // the lowermost path must be an outer path, so if its orientation is negative, - // then flag that the whole group is 'reversed' (will negate delta etc.) - // as this is much more efficient than reversing every path. - pathsReversed = (lowestPathIdx >= 0) && isHoleList.get(lowestPathIdx); - if (pathsReversed) { - for (int i = 0; i < isHoleList.size(); i++) { - isHoleList.set(i, !isHoleList.get(i)); - } - } + lowestPathIdx = GetLowestPathIdx(inPaths); + pathsReversed = (lowestPathIdx >= 0) && (Clipper.Area(inPaths.get(lowestPathIdx)) < 0); } else { lowestPathIdx = -1; - isHoleList = new ArrayList<>(Collections.nCopies(inPaths.size(), false)); pathsReversed = false; } } - private static void GetMultiBounds(Paths64 paths, List boundsList) { - for (Path64 path : paths) { - if (path.size() < 1) { - boundsList.add(Clipper.InvalidRect64.clone()); - continue; - } - - Point64 pt1 = path.get(0); - Rect64 r = new Rect64(pt1.x, pt1.y, pt1.x, pt1.y); - - for (Point64 pt : path) { - if (pt.y > r.bottom) { - r.bottom = pt.y; - } else if (pt.y < r.top) { - r.top = pt.y; - } - if (pt.x > r.right) { - r.right = pt.x; - } else if (pt.x < r.left) { - r.left = pt.x; - } - } - - boundsList.add(r); - } - } - - private static int GetLowestPathIdx(List boundsList) { + private static int GetLowestPathIdx(Paths64 paths) { int result = -1; Point64 botPt = new Point64(Long.MAX_VALUE, Long.MIN_VALUE); - for (int i = 0; i < boundsList.size(); i++) { - Rect64 r = boundsList.get(i); - if (!r.IsValid()) { - continue; // ignore invalid paths - } else if (r.bottom > botPt.y || (r.bottom == botPt.y && r.left < botPt.x)) { - botPt = new Point64(r.left, r.bottom); + for (int i = 0; i < paths.size(); i++) { + for (Point64 pt : paths.get(i)) { + if (pt.y < botPt.y || (pt.y == botPt.y && pt.x >= botPt.x)) { + continue; + } result = i; + botPt = new Point64(pt); } } return result; } -} \ No newline at end of file +} diff --git a/src/main/java/clipper2/rectclip/RectClip64.java b/src/main/java/clipper2/rectclip/RectClip64.java index 927dfbf..b538492 100644 --- a/src/main/java/clipper2/rectclip/RectClip64.java +++ b/src/main/java/clipper2/rectclip/RectClip64.java @@ -336,7 +336,7 @@ private static boolean getSegmentIntersection(Point64 p1, Point64 p2, Point64 p3 ipRefObject.set(new Point64(0, 0)); return false; } - return InternalClipper.GetIntersectPoint(p1, p2, p3, p4, ipRefObject); + return InternalClipper.GetSegmentIntersectPt(p1, p2, p3, p4, ipRefObject); } protected static IntersectionResult getIntersection(Path64 rectPath, Point64 p, Point64 p2, Location loc, Point64 ipRefObject) { diff --git a/src/test/java/clipper2/TestCollinear.java b/src/test/java/clipper2/TestCollinear.java new file mode 100644 index 0000000..7271b69 --- /dev/null +++ b/src/test/java/clipper2/TestCollinear.java @@ -0,0 +1,24 @@ +package clipper2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import clipper2.core.Path64; + +class TestCollinear { + + @Test + void TrimCollinearHandlesLargeIntegerCoordinates() { + long n = 1_000_000_000_000_000L; + Path64 path = Clipper.MakePath(new long[] { + 0, 0, + 3 * n, n, + 9 * n, 3 * n, + 9 * n, 0 + }); + + Path64 trimmed = Clipper.TrimCollinear(path); + assertEquals(3, trimmed.size()); + } +} diff --git a/src/test/java/clipper2/TestToStringOutput.java b/src/test/java/clipper2/TestToStringOutput.java index db6fcfd..45059bf 100644 --- a/src/test/java/clipper2/TestToStringOutput.java +++ b/src/test/java/clipper2/TestToStringOutput.java @@ -18,7 +18,7 @@ class TestToStringOutput { @Test void path64ToStringMatchesExistingFormat() { Path64 path = new Path64(new Point64(1, 2), new Point64(3, 4)); - assertEquals("(1,2) (3,4) ", path.toString()); + assertEquals("(1,2) , (3,4) ", path.toString()); } @Test @@ -26,13 +26,13 @@ void pathDToStringMatchesExistingFormat() { PathD path = new PathD(2); path.add(new PointD(1.5, 2.5)); path.add(new PointD(3.5, 4.5)); - assertEquals("(1.500000,2.500000) (3.500000,4.500000) ", path.toString()); + assertEquals("(1.500000,2.500000) , (3.500000,4.500000) ", path.toString()); } @Test void paths64ToStringMatchesExistingFormat() { Paths64 paths = new Paths64(new Path64(new Point64(1, 2))); - assertEquals("(1,2) \n", paths.toString()); + assertEquals("(1,2) \n", paths.toString()); } @Test @@ -41,7 +41,7 @@ void pathsDToStringMatchesExistingFormat() { PathD path = new PathD(1); path.add(new PointD(1.5, 2.5)); paths.add(path); - assertEquals("(1.500000,2.500000) \n", paths.toString()); + assertEquals("(1.500000,2.500000) \n", paths.toString()); } @Test