From b37abdbb9aaf8d3e278aef1a82f206fbc13b3137 Mon Sep 17 00:00:00 2001 From: Michael Carleton Date: Sun, 21 Jan 2024 14:30:08 +0000 Subject: [PATCH 1/8] cda99ed --- .../java/clipper2/engine/ClipperBase.java | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/main/java/clipper2/engine/ClipperBase.java b/src/main/java/clipper2/engine/ClipperBase.java index 626d28e..a85467a 100644 --- a/src/main/java/clipper2/engine/ClipperBase.java +++ b/src/main/java/clipper2/engine/ClipperBase.java @@ -2212,10 +2212,15 @@ 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) || pt.y < e.top.y + 2 || pt.y < prev.top.y + 2) { + if (prev == null || IsOpen(e) || IsOpen(prev) || !IsHotEdge(e) || !IsHotEdge(prev)) { return; } + if ((pt.y < e.top.y + 2 || pt.y < prev.top.y + 2) && // avoid trivial joins + ((e.bot.y > pt.y) || (prev.bot.y > pt.y))) { + return; // (#490) + } + if (checkCurrX) { if (Clipper.PerpendicDistFromLineSqrd(pt, prev.bot, prev.top) > 0.25) { return; @@ -2245,10 +2250,13 @@ private void CheckJoinRight(Active e, Point64 pt) { private void CheckJoinRight(Active e, Point64 pt, boolean checkCurrX) { @Nullable Active next = e.nextInAEL; - if (IsOpen(e) || !IsHotEdge(e) || IsJoined(e) || next == null || IsOpen(next) || !IsHotEdge(next) || pt.y < e.top.y + 2 - || pt.y < next.top.y + 2) { + if (IsOpen(e) || !IsHotEdge(e) || IsJoined(e) || next == null || IsOpen(next) || !IsHotEdge(next)) { return; } + if ((pt.y < e.top.y + 2 || pt.y < next.top.y + 2) && // avoid trivial joins + ((e.bot.y > pt.y) || (next.bot.y > pt.y))) { + return; // (#490) + } if (checkCurrX) { if (Clipper.PerpendicDistFromLineSqrd(pt, next.bot, next.top) > 0.25) { @@ -2542,6 +2550,10 @@ private void ProcessHorzJoins() { } else if (Path1InsidePath2(or1.pts, or2.pts)) { SetOwner(or1, or2); } else { + if (or1.splits == null) { + or1.splits = new ArrayList(); + } + or1.splits.add(or2.idx); // (#498) or2.owner = or1; } } else { @@ -2811,31 +2823,39 @@ private boolean CheckBounds(OutRec outrec) { return true; } + private boolean checkSplitOwner(OutRec outrec) { + if (outrec.owner == null || outrec.owner.splits == null) { + return false; + } + for (int i : outrec.owner.splits) { + OutRec split = GetRealOutRec(outrecList.get(i)); + if (split != null && split != outrec && split != outrec.owner && CheckBounds(split) && split.bounds.Contains(outrec.bounds) + && Path1InsidePath2(outrec.pts, split.pts)) { + outrec.owner = split; // found in split + return true; + } + } + return false; + } + private void RecursiveCheckOwners(OutRec outrec, PolyPathBase polypath) { // pre-condition: outrec will have valid bounds // post-condition: if a valid path, outrec will have a polypath - if (outrec.polypath != null || outrec.bounds.IsEmpty()) { + if (outrec.polypath != null || outrec.bounds.IsEmpty()) return; - } - - while (outrec.owner != null && (outrec.owner.pts == null || !CheckBounds(outrec.owner))) { - outrec.owner = outrec.owner.owner; - } - - if (outrec.owner != null && outrec.owner.polypath == null) { - RecursiveCheckOwners(outrec.owner, polypath); - } while (outrec.owner != null) { - if (outrec.owner.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec.pts, outrec.owner.pts)) { - break; // found - owner contain outrec! - } else { - outrec.owner = outrec.owner.owner; - } + if (outrec.owner.splits != null && checkSplitOwner(outrec)) + break; + if (outrec.owner.pts != null && CheckBounds(outrec.owner) && Path1InsidePath2(outrec.pts, outrec.owner.pts)) + break; + outrec.owner = outrec.owner.owner; } if (outrec.owner != null) { + if (outrec.owner.polypath == null) + RecursiveCheckOwners(outrec.owner, polypath); outrec.polypath = outrec.owner.polypath.AddChild(outrec.path); } else { outrec.polypath = polypath.AddChild(outrec.path); @@ -2887,7 +2907,7 @@ protected void BuildTree(PolyPathBase polytree, Paths64 solutionOpen) { continue; } if (CheckBounds(outrec)) { - DeepCheckOwners(outrec, polytree); + RecursiveCheckOwners(outrec, polytree); } } } From 76818991850f2beddbb6bd182601dac565b032b1 Mon Sep 17 00:00:00 2001 From: Michael Carleton Date: Sun, 21 Jan 2024 14:31:18 +0000 Subject: [PATCH 2/8] d18faa9 fixed signifcant clipping bug (#500) --- src/main/java/clipper2/engine/ClipperBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/clipper2/engine/ClipperBase.java b/src/main/java/clipper2/engine/ClipperBase.java index a85467a..5b15953 100644 --- a/src/main/java/clipper2/engine/ClipperBase.java +++ b/src/main/java/clipper2/engine/ClipperBase.java @@ -1352,7 +1352,7 @@ private void UpdateEdgeIntoAEL(Active ae) { scanlineSet.add(ae.top.y); CheckJoinLeft(ae, ae.bot); - CheckJoinRight(ae, ae.bot); + CheckJoinRight(ae, ae.bot, true); // (#500) } private static Active FindEdgeWithMatchingLocMin(Active e) { From 88b83d1683542fda2ecbd034c2863e7eb0e2c895 Mon Sep 17 00:00:00 2001 From: Michael Carleton Date: Sun, 21 Jan 2024 14:44:50 +0000 Subject: [PATCH 3/8] f3fd560 one more tweak to Polytree path ownership --- src/main/java/clipper2/engine/ClipperBase.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/clipper2/engine/ClipperBase.java b/src/main/java/clipper2/engine/ClipperBase.java index 5b15953..cb5b843 100644 --- a/src/main/java/clipper2/engine/ClipperBase.java +++ b/src/main/java/clipper2/engine/ClipperBase.java @@ -2823,14 +2823,18 @@ private boolean CheckBounds(OutRec outrec) { return true; } - private boolean checkSplitOwner(OutRec outrec) { + private boolean CheckSplitOwner(OutRec outrec, List splits) { if (outrec.owner == null || outrec.owner.splits == null) { return false; } for (int i : outrec.owner.splits) { - OutRec split = GetRealOutRec(outrecList.get(i)); - if (split != null && split != outrec && split != outrec.owner && CheckBounds(split) && split.bounds.Contains(outrec.bounds) - && Path1InsidePath2(outrec.pts, split.pts)) { + OutRec split = outrecList.get(i); + if (split == outrec || split == outrec.owner) { + continue; + } else if (split.splits != null && CheckSplitOwner(outrec, split.splits)) { + return true; + } + if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec.pts, split.pts)) { outrec.owner = split; // found in split return true; } @@ -2846,7 +2850,7 @@ private void RecursiveCheckOwners(OutRec outrec, PolyPathBase polypath) { return; while (outrec.owner != null) { - if (outrec.owner.splits != null && checkSplitOwner(outrec)) + if (outrec.owner.splits != null && CheckSplitOwner(outrec, outrec.owner.splits)) break; if (outrec.owner.pts != null && CheckBounds(outrec.owner) && Path1InsidePath2(outrec.pts, outrec.owner.pts)) break; From c9676bf78e146f8799c8e151f2368fb0ca7a5aa7 Mon Sep 17 00:00:00 2001 From: Michael Carleton Date: Sun, 21 Jan 2024 15:07:15 +0000 Subject: [PATCH 4/8] 95edbf6 Added ReuseableDataContainer64 class --- src/main/java/clipper2/engine/Clipper64.java | 5 + .../java/clipper2/engine/ClipperBase.java | 240 +++++++++++------- 2 files changed, 153 insertions(+), 92 deletions(-) diff --git a/src/main/java/clipper2/engine/Clipper64.java b/src/main/java/clipper2/engine/Clipper64.java index 05b5996..324b1fc 100644 --- a/src/main/java/clipper2/engine/Clipper64.java +++ b/src/main/java/clipper2/engine/Clipper64.java @@ -96,4 +96,9 @@ public final boolean Execute(ClipType clipType, FillRule fillRule, PolyTree64 po return Execute(clipType, fillRule, polytree, new Paths64()); } + @Override + public void AddReuseableData(ReuseableDataContainer64 reuseableData) { + this.AddReuseableData(reuseableData); + } + } \ No newline at end of file diff --git a/src/main/java/clipper2/engine/ClipperBase.java b/src/main/java/clipper2/engine/ClipperBase.java index cb5b843..5740791 100644 --- a/src/main/java/clipper2/engine/ClipperBase.java +++ b/src/main/java/clipper2/engine/ClipperBase.java @@ -148,6 +148,126 @@ static class Active { } + private static class ClipperEngine { + + private static void AddLocMin(Vertex vert, PathType polytype, boolean isOpen, List minimaList) { + // make sure the vertex is added only once ... + if ((vert.flags & VertexFlags.LocalMin) != VertexFlags.None) { + return; + } + vert.flags |= VertexFlags.LocalMin; + + LocalMinima lm = new LocalMinima(vert, polytype, isOpen); + minimaList.add(lm); + } + + private static void AddPathsToVertexList(Paths64 paths, PathType polytype, boolean isOpen, List minimaList, + List vertexList) { + for (Path64 path : paths) { + Vertex v0 = null, prevV = null, currV; + for (Point64 pt : path) { + if (v0 == null) { + v0 = new Vertex(pt, VertexFlags.None, null); + vertexList.add(v0); + prevV = v0; + } else if (prevV.pt.opNotEquals(pt)) { // ie skips duplicates + currV = new Vertex(pt, VertexFlags.None, prevV); + vertexList.add(currV); + prevV.next = currV; + prevV = currV; + } + } + if (prevV == null || prevV.prev == null) { + continue; + } + if (!isOpen && v0.pt.opEquals(prevV.pt)) { + prevV = prevV.prev; + } + prevV.next = v0; + v0.prev = prevV; + if (!isOpen && prevV == prevV.next) { + continue; + } + + // OK, we have a valid path + boolean goingup, goingup0; + if (isOpen) { + currV = v0.next; + while (v0 != currV && currV.pt.y == v0.pt.y) { + currV = currV.next; + } + goingup = currV.pt.y <= v0.pt.y; + if (goingup) { + v0.flags = VertexFlags.OpenStart; + AddLocMin(v0, polytype, true, minimaList); + } else { + v0.flags = VertexFlags.OpenStart | VertexFlags.LocalMax; + } + } else { // closed path + prevV = v0.prev; + while (!v0.equals(prevV) && prevV.pt.y == v0.pt.y) { + prevV = prevV.prev; + } + if (v0.equals(prevV)) { + continue; // only open paths can be completely flat + } + goingup = prevV.pt.y > v0.pt.y; + } + + goingup0 = goingup; + prevV = v0; + currV = v0.next; + while (!v0.equals(currV)) { + if (currV.pt.y > prevV.pt.y && goingup) { + prevV.flags |= VertexFlags.LocalMax; + goingup = false; + } else if (currV.pt.y < prevV.pt.y && !goingup) { + goingup = true; + AddLocMin(prevV, polytype, isOpen, minimaList); + } + prevV = currV; + currV = currV.next; + } + + if (isOpen) { + prevV.flags |= VertexFlags.OpenEnd; + if (goingup) { + prevV.flags |= VertexFlags.LocalMax; + } else { + AddLocMin(prevV, polytype, isOpen, minimaList); + } + } else if (goingup != goingup0) { + if (goingup0) { + AddLocMin(prevV, polytype, false, minimaList); + } else { + prevV.flags = prevV.flags | VertexFlags.LocalMax; + } + } + } + } + + } + + public class ReuseableDataContainer64 { + + private final List minimaList; + private final List vertexList; + + public ReuseableDataContainer64() { + minimaList = new ArrayList<>(); + vertexList = new ArrayList<>(); + } + + public void Clear() { + minimaList.clear(); + vertexList.clear(); + } + + public void AddPaths(Paths64 paths, PathType pt, boolean isOpen) { + ClipperEngine.AddPathsToVertexList(paths, pt, isOpen, minimaList, vertexList); + } + } + /** * Vertex data structure for clipping solutions */ @@ -202,7 +322,7 @@ static final class IntersectNode { * ascending and descending 'bounds' (or sides) that start at local minima and * ascend to a local maxima, before descending again. */ - class Vertex { + static class Vertex { Point64 pt = new Point64(); @Nullable @@ -219,7 +339,7 @@ class Vertex { } } - class VertexFlags { + static class VertexFlags { static final int None = 0; static final int OpenStart = 1; @@ -602,90 +722,6 @@ private void AddLocMin(Vertex vert, PathType polytype, boolean isOpen) { minimaList.add(lm); } - protected final void AddPathsToVertexList(Paths64 paths, PathType polytype, boolean isOpen) { - for (Path64 path : paths) { - Vertex v0 = null, prevV = null, currV; - for (Point64 pt : path) { - if (v0 == null) { - v0 = new Vertex(pt, VertexFlags.None, null); - vertexList.add(v0); - prevV = v0; - } else if (prevV.pt.opNotEquals(pt)) { // ie skips duplicates - currV = new Vertex(pt, VertexFlags.None, prevV); - vertexList.add(currV); - prevV.next = currV; - prevV = currV; - } - } - if (prevV == null || prevV.prev == null) { - continue; - } - if (!isOpen && v0.pt.opEquals(prevV.pt)) { - prevV = prevV.prev; - } - prevV.next = v0; - v0.prev = prevV; - if (!isOpen && prevV == prevV.next) { - continue; - } - - // OK, we have a valid path - boolean goingup, goingup0; - if (isOpen) { - currV = v0.next; - while (v0 != currV && currV.pt.y == v0.pt.y) { - currV = currV.next; - } - goingup = currV.pt.y <= v0.pt.y; - if (goingup) { - v0.flags = VertexFlags.OpenStart; - AddLocMin(v0, polytype, true); - } else { - v0.flags = VertexFlags.OpenStart | VertexFlags.LocalMax; - } - } else { // closed path - prevV = v0.prev; - while (!v0.equals(prevV) && prevV.pt.y == v0.pt.y) { - prevV = prevV.prev; - } - if (v0.equals(prevV)) { - continue; // only open paths can be completely flat - } - goingup = prevV.pt.y > v0.pt.y; - } - - goingup0 = goingup; - prevV = v0; - currV = v0.next; - while (!v0.equals(currV)) { - if (currV.pt.y > prevV.pt.y && goingup) { - prevV.flags |= VertexFlags.LocalMax; - goingup = false; - } else if (currV.pt.y < prevV.pt.y && !goingup) { - goingup = true; - AddLocMin(prevV, polytype, isOpen); - } - prevV = currV; - currV = currV.next; - } - - if (isOpen) { - prevV.flags |= VertexFlags.OpenEnd; - if (goingup) { - prevV.flags |= VertexFlags.LocalMax; - } else { - AddLocMin(prevV, polytype, isOpen); - } - } else if (goingup != goingup0) { - if (goingup0) { - AddLocMin(prevV, polytype, false); - } else { - prevV.flags = prevV.flags | VertexFlags.LocalMax; - } - } - } - } - public final void AddSubject(Path64 path) { AddPath(path, PathType.Subject); } @@ -738,7 +774,23 @@ public final void AddPaths(Paths64 paths, PathType polytype, boolean isOpen) { hasOpenPaths = true; } isSortedMinimaList = false; - AddPathsToVertexList(paths, polytype, isOpen); + ClipperEngine.AddPathsToVertexList(paths, polytype, isOpen, minimaList, vertexList); + } + + protected void AddReuseableData(ReuseableDataContainer64 reuseableData) { + if (reuseableData.minimaList.isEmpty()) { + return; + } + // nb: reuseableData will continue to own the vertices, so it's important + // that the reuseableData object isn't destroyed before the Clipper object + // that's using the data. + isSortedMinimaList = false; + for (LocalMinima lm : reuseableData.minimaList) { + minimaList.add(new LocalMinima(lm.vertex, lm.polytype, lm.isOpen)); + if (lm.isOpen) { + hasOpenPaths = true; + } + } } private boolean IsContributingClosed(Active ae) { @@ -2551,7 +2603,7 @@ private void ProcessHorzJoins() { SetOwner(or1, or2); } else { if (or1.splits == null) { - or1.splits = new ArrayList(); + or1.splits = new ArrayList<>(); } or1.splits.add(or2.idx); // (#498) or2.owner = or1; @@ -2846,20 +2898,24 @@ private void RecursiveCheckOwners(OutRec outrec, PolyPathBase polypath) { // pre-condition: outrec will have valid bounds // post-condition: if a valid path, outrec will have a polypath - if (outrec.polypath != null || outrec.bounds.IsEmpty()) + if (outrec.polypath != null || outrec.bounds.IsEmpty()) { return; + } while (outrec.owner != null) { - if (outrec.owner.splits != null && CheckSplitOwner(outrec, outrec.owner.splits)) + if (outrec.owner.splits != null && CheckSplitOwner(outrec, outrec.owner.splits)) { break; - if (outrec.owner.pts != null && CheckBounds(outrec.owner) && Path1InsidePath2(outrec.pts, outrec.owner.pts)) + } + if (outrec.owner.pts != null && CheckBounds(outrec.owner) && Path1InsidePath2(outrec.pts, outrec.owner.pts)) { break; + } outrec.owner = outrec.owner.owner; } if (outrec.owner != null) { - if (outrec.owner.polypath == null) + if (outrec.owner.polypath == null) { RecursiveCheckOwners(outrec.owner, polypath); + } outrec.polypath = outrec.owner.polypath.AddChild(outrec.path); } else { outrec.polypath = polypath.AddChild(outrec.path); From ee017db0a99e4d60e49d2407b0d4ef364ba5bd1f Mon Sep 17 00:00:00 2001 From: Michael Carleton Date: Mon, 22 Jan 2024 22:25:12 +0000 Subject: [PATCH 5/8] 6e15ba0 added support for variable offsets --- pom.xml | 2 +- .../java/clipper2/engine/ClipperBase.java | 10 +- .../java/clipper2/offset/ClipperOffset.java | 172 +++++++++++------- .../java/clipper2/offset/DeltaCallback64.java | 37 ++++ 4 files changed, 156 insertions(+), 65 deletions(-) create mode 100644 src/main/java/clipper2/offset/DeltaCallback64.java diff --git a/pom.xml b/pom.xml index 3be27b8..c68a05a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 micycle clipper2 - 1.2.2 + 1.2.4 Clipper2 1.36 diff --git a/src/main/java/clipper2/engine/ClipperBase.java b/src/main/java/clipper2/engine/ClipperBase.java index 5740791..f74e7ec 100644 --- a/src/main/java/clipper2/engine/ClipperBase.java +++ b/src/main/java/clipper2/engine/ClipperBase.java @@ -2601,6 +2601,10 @@ private void ProcessHorzJoins() { SetOwner(or2, or1); } else if (Path1InsidePath2(or1.pts, or2.pts)) { SetOwner(or1, or2); + if (or1.splits == null) { + or1.splits = new ArrayList<>(); + } + or1.splits.add(or2.idx); // (#520) } else { if (or1.splits == null) { or1.splits = new ArrayList<>(); @@ -2608,11 +2612,11 @@ private void ProcessHorzJoins() { or1.splits.add(or2.idx); // (#498) or2.owner = or1; } - } else { + } + else { or2.owner = or1; } - - outrecList.add(or2); + outrecList.add(or2); // NOTE removed in 6e15ba0, but then fails tests } else { or2.pts = null; if (usingPolytree) { diff --git a/src/main/java/clipper2/offset/ClipperOffset.java b/src/main/java/clipper2/offset/ClipperOffset.java index da4384a..ebf8b2f 100644 --- a/src/main/java/clipper2/offset/ClipperOffset.java +++ b/src/main/java/clipper2/offset/ClipperOffset.java @@ -2,6 +2,10 @@ import static clipper2.core.InternalClipper.DEFAULT_ARC_TOLERANCE; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import clipper2.Clipper; import clipper2.core.ClipType; import clipper2.core.FillRule; @@ -17,10 +21,6 @@ import tangible.OutObject; import tangible.RefObject; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - /** * Geometric offsetting refers to the process of creating parallel curves that * are offset a specified distance from their primary curves. @@ -41,12 +41,13 @@ */ public class ClipperOffset { + private static double TOLERANCE = 1.0E-12; + private final List groupList = new ArrayList<>(); private final PathD normals = new PathD(); private final Paths64 solution = new Paths64(); private double groupDelta; // *0.5 for open paths; *-1.0 for negative areas private double delta; - private double absGroupDelta; private double mitLimSqr; private double stepsPerRad; private double stepSin; @@ -58,6 +59,7 @@ public class ClipperOffset { private double miterLimit; private boolean preserveCollinear; private boolean reverseSolution; + private DeltaCallback64 deltaCallback; /** * @see #ClipperOffset(double, double, boolean, boolean) @@ -93,8 +95,8 @@ public ClipperOffset() { * Creates a ClipperOffset object, using the supplied parameters. * * @param miterLimit This property sets the maximum distance in multiples - * of groupDelta that vertices can be offset from - * their original positions before squaring is applied. + * of groupDelta that vertices can be offset from their + * original positions before squaring is applied. * (Squaring truncates a miter by 'cutting it off' at 1 * × groupDelta distance from the original vertex.) *

@@ -203,6 +205,11 @@ public final void Execute(double delta, Paths64 solution) { } } + public void Execute(DeltaCallback64 deltaCallback64, Paths64 solution) { + deltaCallback = deltaCallback64; + Execute(1.0, solution); + } + public void Execute(double delta, PolyTree64 polytree) { polytree.Clear(); ExecuteInternal(delta); @@ -260,6 +267,14 @@ public final void setReverseSolution(boolean value) { reverseSolution = value; } + public final void setDeltaCallBack64(DeltaCallback64 callback) { + deltaCallback = callback; + } + + public final DeltaCallback64 getDeltaCallBack64() { + return deltaCallback; + } + private static PointD GetUnitNormal(Point64 pt1, Point64 pt2) { double dx = (pt2.x - pt1.x); double dy = (pt2.y - pt1.y); @@ -374,10 +389,10 @@ private void DoSquare(Group group, Path64 path, int j, int k) { } else { vec = GetAvgUnitVector(new PointD(-normals.get(k).y, normals.get(k).x), new PointD(normals.get(j).y, -normals.get(j).x)); } - + double absDelta = Math.abs(groupDelta); // now offset the original vertex delta units along unit vector PointD ptQ = new PointD(path.get(j)); - ptQ = TranslatePoint(ptQ, absGroupDelta * vec.x, absGroupDelta * vec.y); + ptQ = TranslatePoint(ptQ, absDelta * vec.x, absDelta * vec.y); // get perpendicular vertices PointD pt1 = TranslatePoint(ptQ, groupDelta * vec.y, groupDelta * -vec.x); @@ -401,12 +416,26 @@ private void DoSquare(Group group, Path64 path, int j, int k) { } private void DoMiter(Group group, Path64 path, int j, int k, double cosA) { - double q = groupDelta / (cosA + 1); + final double q = groupDelta / (cosA + 1); group.outPath.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)); } private void DoRound(Group group, Path64 path, int j, int k, double angle) { + if (deltaCallback != null) { + // when deltaCallback is assigned, groupDelta won't be constant, + // so we'll need to do the following calculations for *every* vertex. + double absDelta = Math.abs(groupDelta); + double arcTol = arcTolerance > 0.01 ? arcTolerance : Math.log10(2 + absDelta) * DEFAULT_ARC_TOLERANCE; + double stepsPer360 = Math.PI / Math.acos(1 - arcTol / absDelta); + stepSin = Math.sin((2 * Math.PI) / stepsPer360); + stepCos = Math.cos((2 * Math.PI) / stepsPer360); + if (groupDelta < 0.0) { + stepSin = -stepSin; + } + stepsPerRad = stepsPer360 / (2 * Math.PI); + } + Point64 pt = path.get(j); PointD offsetVec = new PointD(normals.get(k).x * groupDelta, normals.get(k).y * groupDelta); if (j == k) { @@ -449,21 +478,23 @@ private void OffsetPoint(Group group, Path64 path, int j, RefObject k) sinA = -1.0; } - if (cosA > 0.99) // almost straight - less than 8 degrees - { - group.outPath.add(GetPerpendic(path.get(j), normals.get(k.argValue))); - if (cosA < 0.9998) { // greater than 1 degree (#424) - group.outPath.add(GetPerpendic(path.get(j), normals.get(j))); // (#418) - } - } else if (cosA > -0.99 && (sinA * groupDelta < 0)) // is concave - { + if (deltaCallback != null) { + groupDelta = deltaCallback.calculate(path, normals, j, k.argValue); + } + if (Math.abs(groupDelta) < TOLERANCE) { + group.outPath.add(path.get(j)); + return; + } + + if (cosA > 0.99) { + DoMiter(group, path, j, k.argValue, cosA); + } else if (cosA > -0.99 && (sinA * groupDelta < 0)) { + // is concave group.outPath.add(GetPerpendic(path.get(j), normals.get(k.argValue))); // this extra point is the only (simple) way to ensure that // path reversals are fully cleaned with the trailing clipper group.outPath.add(path.get(j)); // (#405) group.outPath.add(GetPerpendic(path.get(j), normals.get(j))); - } else if (joinType == JoinType.Round) { - DoRound(group, path, j, k.argValue, Math.atan2(sinA, cosA)); } else if (joinType == JoinType.Miter) { // miter unless the angle is so acute the miter would exceeds ML if (cosA > mitLimSqr - 1) { @@ -471,14 +502,13 @@ private void OffsetPoint(Group group, Path64 path, int j, RefObject k) } else { DoSquare(group, path, j, k.argValue); } - } - // don't bother squaring angles that deviate < ~20 degrees because - // squaring will be indistinguishable from mitering and just be a lot slower - else if (cosA > 0.9) { - DoMiter(group, path, j, k.argValue, cosA); - } else { + } else if (joinType == JoinType.Square) { + // angle less than 8 degrees or a squared join DoSquare(group, path, j, k.argValue); + } else { + DoRound(group, path, j, k.argValue, Math.atan2(sinA, cosA)); } + k.argValue = j; } @@ -503,19 +533,28 @@ private void OffsetOpenPath(Group group, Path64 path) { group.outPath = new Path64(); int highI = path.size() - 1; + if (deltaCallback != null) { + groupDelta = deltaCallback.calculate(path, normals, 0, 0); + } + // do the line start cap - switch (this.endType) { - case Butt : - group.outPath - .add(new Point64(path.get(0).x - normals.get(0).x * groupDelta, path.get(0).y - normals.get(0).y * groupDelta)); - group.outPath.add(GetPerpendic(path.get(0), normals.get(0))); - break; - case Round : - DoRound(group, path, 0, 0, Math.PI); - break; - default : - DoSquare(group, path, 0, 0); - break; + if (Math.abs(groupDelta) < TOLERANCE) { + group.outPath.add(path.get(0)); + } else { + // do the line start cap + switch (this.endType) { + case Butt : + group.outPath + .add(new Point64(path.get(0).x - normals.get(0).x * groupDelta, path.get(0).y - normals.get(0).y * groupDelta)); + group.outPath.add(GetPerpendic(path.get(0), normals.get(0))); + break; + case Round : + DoRound(group, path, 0, 0, Math.PI); + break; + default : + DoSquare(group, path, 0, 0); + break; + } } // offset the left side going forward @@ -530,19 +569,26 @@ private void OffsetOpenPath(Group group, Path64 path) { } normals.set(0, normals.get(highI)); + if (deltaCallback != null) { + groupDelta = deltaCallback.calculate(path, normals, highI, highI); + } // do the line end cap - switch (this.endType) { - case Butt : - group.outPath.add(new Point64(path.get(highI).x - normals.get(highI).x * groupDelta, - path.get(highI).y - normals.get(highI).y * groupDelta)); - group.outPath.add(GetPerpendic(path.get(highI), normals.get(highI))); - break; - case Round : - DoRound(group, path, highI, highI, Math.PI); - break; - default : - DoSquare(group, path, highI, highI); - break; + if (Math.abs(groupDelta) < TOLERANCE) { + group.outPath.add(path.get(highI)); + } else { + switch (this.endType) { + case Butt : + group.outPath.add(new Point64(path.get(highI).x - normals.get(highI).x * groupDelta, + path.get(highI).y - normals.get(highI).y * groupDelta)); + group.outPath.add(GetPerpendic(path.get(highI), normals.get(highI))); + break; + case Round : + DoRound(group, path, highI, highI, Math.PI); + break; + default : + DoSquare(group, path, highI, highI); + break; + } } // offset the left side going back @@ -556,8 +602,10 @@ private void OffsetOpenPath(Group group, Path64 path) { private void DoGroupOffset(Group group) { if (group.endType == EndType.Polygon) { - // the lowermost polygon must be an outer polygon. So we can use that as the - // designated orientation for outer polygons (needed for tidy-up clipping) + /* + * The lowermost polygon must be an outer polygon. So we can use that as the + * designated orientation for outer polygons (needed for tidy-up clipping) + */ OutObject lowestIdx = new OutObject<>(); OutObject grpBounds = new OutObject<>(); GetBoundsAndLowestPolyIdx(group.inPaths, lowestIdx, grpBounds); @@ -576,18 +624,20 @@ private void DoGroupOffset(Group group) { group.pathsReversed = false; this.groupDelta = Math.abs(this.delta) * 0.5; } - this.absGroupDelta = Math.abs(this.groupDelta); + double absDelta = Math.abs(this.groupDelta); this.joinType = group.joinType; this.endType = group.endType; // calculate a sensible number of steps (for 360 deg for the given offset - if (group.joinType == JoinType.Round || group.endType == EndType.Round) { - // 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. See also offset_triginometry2.svg - double arcTol = arcTolerance > 0.01 ? arcTolerance : Math.log10(2 + this.absGroupDelta) * DEFAULT_ARC_TOLERANCE; - double stepsPer360 = Math.PI / Math.acos(1 - arcTol / absGroupDelta); + if (deltaCallback == null && (group.joinType == JoinType.Round || group.endType == EndType.Round)) { + /* + * 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. See also + * offset_triginometry2.svg + */ + double arcTol = arcTolerance > 0.01 ? arcTolerance : Math.log10(2 + absDelta) * DEFAULT_ARC_TOLERANCE; + double stepsPer360 = Math.PI / Math.acos(1 - arcTol / absDelta); stepSin = Math.sin((2 * Math.PI) / stepsPer360); stepCos = Math.cos((2 * Math.PI) / stepsPer360); if (groupDelta < 0.0) { @@ -609,7 +659,7 @@ private void DoGroupOffset(Group group) { group.outPath = new Path64(); // single vertex so build a circle or square ... if (group.endType == EndType.Round) { - double r = this.absGroupDelta; + double r = absDelta; group.outPath = Clipper.Ellipse(path.get(0), r, r); } else { int d = (int) Math.ceil(this.groupDelta); diff --git a/src/main/java/clipper2/offset/DeltaCallback64.java b/src/main/java/clipper2/offset/DeltaCallback64.java new file mode 100644 index 0000000..f95cf7a --- /dev/null +++ b/src/main/java/clipper2/offset/DeltaCallback64.java @@ -0,0 +1,37 @@ +package clipper2.offset; + +import clipper2.core.Path64; +import clipper2.core.PathD; + +/** + * Functional interface for calculating a variable delta during polygon + * offsetting. + *

+ * Implementations of this interface define how to calculate the delta (the + * amount of offset) to apply at each point in a polygon during an offset + * operation. The offset can vary from point to point, allowing for variable + * offsetting. + */ +@FunctionalInterface +public interface DeltaCallback64 { + /** + * Calculates the delta (offset) for a given point in the polygon path. + *

+ * This method is used during polygon offsetting operations to determine the + * amount by which each point of the polygon should be offset. + * + * @param path The {@link Path64} object representing the original polygon + * path. + * @param path_norms The {@link PathD} object containing the normals of the + * path, which may be used to influence the delta calculation. + * @param currPt The index of the current point in the path for which the + * delta is being calculated. + * @param prevPt The index of the previous point in the path, which can be + * referenced to determine the delta based on adjacent + * segments. + * @return A {@code double} value representing the calculated delta for the + * current point. This value will be used to offset the point in the + * resulting polygon. + */ + double calculate(Path64 path, PathD path_norms, int currPt, int prevPt); +} From 1b92678877af802eb36ad44253c395b10954e0c4 Mon Sep 17 00:00:00 2001 From: Michael Carleton Date: Mon, 22 Jan 2024 22:30:40 +0000 Subject: [PATCH 6/8] 6d69b97 rename rectclip... to rectclip...64. fix minor offsetting bugs --- src/main/java/clipper2/Clipper.java | 12 ++++++------ src/main/java/clipper2/offset/ClipperOffset.java | 6 +++--- .../rectclip/{RectClip.java => RectClip64.java} | 10 +++++----- .../{RectClipLines.java => RectClipLines64.java} | 13 ++++++------- src/main/java/clipper2/rectclip/package-info.java | 2 +- 5 files changed, 21 insertions(+), 22 deletions(-) rename src/main/java/clipper2/rectclip/{RectClip.java => RectClip64.java} (98%) rename src/main/java/clipper2/rectclip/{RectClipLines.java => RectClipLines64.java} (91%) diff --git a/src/main/java/clipper2/Clipper.java b/src/main/java/clipper2/Clipper.java index 6e9982b..3324297 100644 --- a/src/main/java/clipper2/Clipper.java +++ b/src/main/java/clipper2/Clipper.java @@ -28,8 +28,8 @@ import clipper2.offset.ClipperOffset; import clipper2.offset.EndType; import clipper2.offset.JoinType; -import clipper2.rectclip.RectClip; -import clipper2.rectclip.RectClipLines; +import clipper2.rectclip.RectClip64; +import clipper2.rectclip.RectClipLines64; public final class Clipper { @@ -234,7 +234,7 @@ public static Paths64 ExecuteRectClip(Rect64 rect, Paths64 paths, boolean convex if (rect.IsEmpty() || paths.size() == 0) { return new Paths64(); } - RectClip rc = new RectClip(rect); + RectClip64 rc = new RectClip64(rect); return rc.Execute(paths, convexOnly); } @@ -263,7 +263,7 @@ public static PathsD ExecuteRectClip(RectD rect, PathsD paths, int precision, bo double scale = Math.pow(10, precision); Rect64 r = ScaleRect(rect, scale); Paths64 tmpPath = ScalePaths64(paths, scale); - RectClip rc = new RectClip(r); + RectClip64 rc = new RectClip64(r); tmpPath = rc.Execute(tmpPath, convexOnly); return ScalePathsD(tmpPath, 1 / scale); } @@ -285,7 +285,7 @@ public static Paths64 ExecuteRectClipLines(Rect64 rect, Paths64 paths) { if (rect.IsEmpty() || paths.size() == 0) { return new Paths64(); } - RectClipLines rc = new RectClipLines(rect); + RectClipLines64 rc = new RectClipLines64(rect); return rc.Execute(paths); } @@ -310,7 +310,7 @@ public static PathsD ExecuteRectClipLines(RectD rect, PathsD paths, int precisio double scale = Math.pow(10, precision); Rect64 r = ScaleRect(rect, scale); Paths64 tmpPath = ScalePaths64(paths, scale); - RectClipLines rc = new RectClipLines(r); + RectClipLines64 rc = new RectClipLines64(r); tmpPath = rc.Execute(tmpPath); return ScalePathsD(tmpPath, 1 / scale); } diff --git a/src/main/java/clipper2/offset/ClipperOffset.java b/src/main/java/clipper2/offset/ClipperOffset.java index ebf8b2f..72c9ba4 100644 --- a/src/main/java/clipper2/offset/ClipperOffset.java +++ b/src/main/java/clipper2/offset/ClipperOffset.java @@ -385,7 +385,7 @@ private PointD GetPerpendicD(Point64 pt, PointD norm) { private void DoSquare(Group group, Path64 path, int j, int k) { PointD vec; if (j == k) { - vec = new PointD(normals.get(0).y, -normals.get(0).x); + vec = new PointD(normals.get(j).y, -normals.get(j).x); } else { vec = GetAvgUnitVector(new PointD(-normals.get(k).y, normals.get(k).x), new PointD(normals.get(j).y, -normals.get(j).x)); } @@ -486,7 +486,7 @@ private void OffsetPoint(Group group, Path64 path, int j, RefObject k) return; } - if (cosA > 0.99) { + if (cosA > 0.999) { DoMiter(group, path, j, k.argValue, cosA); } else if (cosA > -0.99 && (sinA * groupDelta < 0)) { // is concave @@ -502,7 +502,7 @@ private void OffsetPoint(Group group, Path64 path, int j, RefObject k) } else { DoSquare(group, path, j, k.argValue); } - } else if (joinType == JoinType.Square) { + } else if (cosA > 0.99 || joinType == JoinType.Square) { // angle less than 8 degrees or a squared join DoSquare(group, path, j, k.argValue); } else { diff --git a/src/main/java/clipper2/rectclip/RectClip.java b/src/main/java/clipper2/rectclip/RectClip64.java similarity index 98% rename from src/main/java/clipper2/rectclip/RectClip.java rename to src/main/java/clipper2/rectclip/RectClip64.java index 1b3d7b1..6af0f10 100644 --- a/src/main/java/clipper2/rectclip/RectClip.java +++ b/src/main/java/clipper2/rectclip/RectClip64.java @@ -15,16 +15,16 @@ import tangible.RefObject; /** - * RectClip intersects subject polygons with the specified rectangular clipping - * region. Polygons may be simple or complex (self-intersecting). + * RectClip64 intersects subject polygons with the specified rectangular + * clipping region. Polygons may be simple or complex (self-intersecting). *

* This function is extremely fast when compared to the Library's general * purpose Intersect clipper. Where Intersect has roughly O(n³) performance, - * RectClip has O(n) performance. + * RectClip64 has O(n) performance. * * @since 1.0.6 */ -public class RectClip { +public class RectClip64 { protected static class OutPt2 { @Nullable @@ -55,7 +55,7 @@ protected enum Location { protected int currIdx = -1; @SuppressWarnings("unchecked") - public RectClip(Rect64 rect) { + public RectClip64(Rect64 rect) { currIdx = -1; this.rect = rect; mp = rect.MidPoint(); diff --git a/src/main/java/clipper2/rectclip/RectClipLines.java b/src/main/java/clipper2/rectclip/RectClipLines64.java similarity index 91% rename from src/main/java/clipper2/rectclip/RectClipLines.java rename to src/main/java/clipper2/rectclip/RectClipLines64.java index a8040bd..0f9b915 100644 --- a/src/main/java/clipper2/rectclip/RectClipLines.java +++ b/src/main/java/clipper2/rectclip/RectClipLines64.java @@ -9,18 +9,18 @@ import tangible.RefObject; /** - * RectClipLines intersects subject open paths (polylines) with the specified + * RectClipLines64 intersects subject open paths (polylines) with the specified * rectangular clipping region. *

* This function is extremely fast when compared to the Library's general * purpose Intersect clipper. Where Intersect has roughly O(n³) performance, - * RectClipLines has O(n) performance. + * RectClipLines64 has O(n) performance. * * @since 1.0.6 */ -public class RectClipLines extends RectClip { +public class RectClipLines64 extends RectClip64 { - public RectClipLines(Rect64 rect) { + public RectClipLines64(Rect64 rect) { super(rect); } @@ -123,9 +123,8 @@ private void ExecuteInternal(Path64 path) { // we must be crossing the rect boundary to get here //////////////////////////////////////////////////// - if (loc.argValue == Location.INSIDE) // path must be entering rect - { - Add(ip); + if (loc.argValue == Location.INSIDE) { // path must be entering rect + Add(ip, true); } else if (prev.argValue != Location.INSIDE) { // passing right through rect. 'ip' here will be the second // intersect pt but we'll also need the first intersect pt (ip2) diff --git a/src/main/java/clipper2/rectclip/package-info.java b/src/main/java/clipper2/rectclip/package-info.java index 021dae3..ec7f2f2 100644 --- a/src/main/java/clipper2/rectclip/package-info.java +++ b/src/main/java/clipper2/rectclip/package-info.java @@ -1,5 +1,5 @@ /** - * This unit contains the code that implements the RectClip functions found in + * This unit contains the code that implements the RectClip64 functions found in * the Clipper Unit. * * @since 1.0.6 From 518f286b2961a21840d1467b05afd1194eff78fb Mon Sep 17 00:00:00 2001 From: Michael Carleton Date: Mon, 22 Jan 2024 22:50:23 +0000 Subject: [PATCH 7/8] 9d946d7 Removed ConvexOnly parameter from ClipRect function --- src/main/java/clipper2/Clipper.java | 68 ++++++++----------- .../java/clipper2/rectclip/RectClip64.java | 12 ++-- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/main/java/clipper2/Clipper.java b/src/main/java/clipper2/Clipper.java index 3324297..ea8131d 100644 --- a/src/main/java/clipper2/Clipper.java +++ b/src/main/java/clipper2/Clipper.java @@ -226,85 +226,77 @@ public static PathsD InflatePaths(PathsD paths, double delta, JoinType joinType, return ScalePathsD(tmp, 1 / scale); } - public static Paths64 ExecuteRectClip(Rect64 rect, Paths64 paths) { - return ExecuteRectClip(rect, paths, false); - } - - public static Paths64 ExecuteRectClip(Rect64 rect, Paths64 paths, boolean convexOnly) { - if (rect.IsEmpty() || paths.size() == 0) { + public static Paths64 RectClip(Rect64 rect, Paths64 paths) { + if (rect.IsEmpty() || paths.isEmpty()) { return new Paths64(); } RectClip64 rc = new RectClip64(rect); - return rc.Execute(paths, convexOnly); - } - - public static Paths64 ExecuteRectClip(Rect64 rect, Path64 path) { - return ExecuteRectClip(rect, path, false); + return rc.Execute(paths); } - public static Paths64 ExecuteRectClip(Rect64 rect, Path64 path, boolean convexOnly) { - if (rect.IsEmpty() || path.size() == 0) { + public static Paths64 RectClip(Rect64 rect, Path64 path) { + if (rect.IsEmpty() || path.isEmpty()) { return new Paths64(); } Paths64 tmp = new Paths64(); tmp.add(path); - return ExecuteRectClip(rect, tmp, convexOnly); + return RectClip(rect, tmp); } - public static PathsD ExecuteRectClip(RectD rect, PathsD paths) { - return ExecuteRectClip(rect, paths, 2, false); + public static PathsD RectClip(RectD rect, PathsD paths) { + return RectClip(rect, paths, 2); } - public static PathsD ExecuteRectClip(RectD rect, PathsD paths, int precision, boolean convexOnly) { + public static PathsD RectClip(RectD rect, PathsD paths, int precision) { InternalClipper.CheckPrecision(precision); - if (rect.IsEmpty() || paths.size() == 0) { + if (rect.IsEmpty() || paths.isEmpty()) { return new PathsD(); } double scale = Math.pow(10, precision); Rect64 r = ScaleRect(rect, scale); Paths64 tmpPath = ScalePaths64(paths, scale); RectClip64 rc = new RectClip64(r); - tmpPath = rc.Execute(tmpPath, convexOnly); + tmpPath = rc.Execute(tmpPath); return ScalePathsD(tmpPath, 1 / scale); } - public static PathsD ExecuteRectClip(RectD rect, PathD path) { - return ExecuteRectClip(rect, path, 2, false); + public static PathsD RectClip(RectD rect, PathD path) { + return RectClip(rect, path, 2); } - public static PathsD ExecuteRectClip(RectD rect, PathD path, int precision, boolean convexOnly) { - if (rect.IsEmpty() || path.size() == 0) { + public static PathsD RectClip(RectD rect, PathD path, int precision) { + if (rect.IsEmpty() || path.isEmpty()) { return new PathsD(); } PathsD tmp = new PathsD(); tmp.add(path); - return ExecuteRectClip(rect, tmp, precision, convexOnly); + return RectClip(rect, tmp, precision); } - public static Paths64 ExecuteRectClipLines(Rect64 rect, Paths64 paths) { - if (rect.IsEmpty() || paths.size() == 0) { + public static Paths64 RectClipLines(Rect64 rect, Paths64 paths) { + if (rect.IsEmpty() || paths.isEmpty()) { return new Paths64(); } RectClipLines64 rc = new RectClipLines64(rect); return rc.Execute(paths); } - public static Paths64 ExecuteRectClipLines(Rect64 rect, Path64 path) { - if (rect.IsEmpty() || path.size() == 0) { + public static Paths64 RectClipLines(Rect64 rect, Path64 path) { + if (rect.IsEmpty() || path.isEmpty()) { return new Paths64(); } Paths64 tmp = new Paths64(); tmp.add(path); - return ExecuteRectClipLines(rect, tmp); + return RectClipLines(rect, tmp); } - public static PathsD ExecuteRectClipLines(RectD rect, PathsD paths) { - return ExecuteRectClipLines(rect, paths, 2); + public static PathsD RectClipLines(RectD rect, PathsD paths) { + return RectClipLines(rect, paths, 2); } - public static PathsD ExecuteRectClipLines(RectD rect, PathsD paths, int precision) { + public static PathsD RectClipLines(RectD rect, PathsD paths, int precision) { InternalClipper.CheckPrecision(precision); - if (rect.IsEmpty() || paths.size() == 0) { + if (rect.IsEmpty() || paths.isEmpty()) { return new PathsD(); } double scale = Math.pow(10, precision); @@ -315,17 +307,17 @@ public static PathsD ExecuteRectClipLines(RectD rect, PathsD paths, int precisio return ScalePathsD(tmpPath, 1 / scale); } - public static PathsD ExecuteRectClipLines(RectD rect, PathD path) { - return ExecuteRectClipLines(rect, path, 2); + public static PathsD RectClipLines(RectD rect, PathD path) { + return RectClipLines(rect, path, 2); } - public static PathsD ExecuteRectClipLines(RectD rect, PathD path, int precision) { - if (rect.IsEmpty() || path.size() == 0) { + public static PathsD RectClipLines(RectD rect, PathD path, int precision) { + if (rect.IsEmpty() || path.isEmpty()) { return new PathsD(); } PathsD tmp = new PathsD(); tmp.add(path); - return ExecuteRectClipLines(rect, tmp, precision); + return RectClipLines(rect, tmp, precision); } public static Paths64 MinkowskiSum(Path64 pattern, Path64 path, boolean isClosed) { diff --git a/src/main/java/clipper2/rectclip/RectClip64.java b/src/main/java/clipper2/rectclip/RectClip64.java index 6af0f10..8bb7cd3 100644 --- a/src/main/java/clipper2/rectclip/RectClip64.java +++ b/src/main/java/clipper2/rectclip/RectClip64.java @@ -586,7 +586,7 @@ private void ExecuteInternal(Path64 path) { } } } else if (loc.argValue != Location.INSIDE && (loc.argValue != firstCross || startLocs.size() > 2)) { - if (startLocs.size() > 0) { + if (!startLocs.isEmpty()) { prev.argValue = loc.argValue; for (Location loc2 : startLocs) { if (prev.argValue == loc2) { @@ -603,7 +603,7 @@ private void ExecuteInternal(Path64 path) { } } - public Paths64 Execute(Paths64 paths, boolean convexOnly) { + public Paths64 Execute(Paths64 paths) { Paths64 result = new Paths64(); if (rect.IsEmpty()) { return result; @@ -621,11 +621,9 @@ public Paths64 Execute(Paths64 paths, boolean convexOnly) { continue; } ExecuteInternal(path); - if (!convexOnly) { - CheckEdges(); - for (int i = 0; i < 4; ++i) { - TidyEdgePair(i, edges[i * 2], edges[i * 2 + 1]); - } + CheckEdges(); + for (int i = 0; i < 4; ++i) { + TidyEdgePair(i, edges[i * 2], edges[i * 2 + 1]); } for (@Nullable From a5327e0e754123e093b6c3a4034483619e6931c2 Mon Sep 17 00:00:00 2001 From: Michael Carleton Date: Mon, 22 Jan 2024 23:01:03 +0000 Subject: [PATCH 8/8] e6d2ac1 Fixed infinite loop bug --- src/main/java/clipper2/engine/ClipperBase.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/clipper2/engine/ClipperBase.java b/src/main/java/clipper2/engine/ClipperBase.java index f74e7ec..2da2846 100644 --- a/src/main/java/clipper2/engine/ClipperBase.java +++ b/src/main/java/clipper2/engine/ClipperBase.java @@ -2130,6 +2130,7 @@ else if ((isLeftToRight && (TopX(ae, pt.y) >= pt.x)) || (!isLeftToRight && (TopX if (IsHotEdge(horz)) { AddOutPt(horz, horz.top); } + UpdateEdgeIntoAEL(horz); if (getPreserveCollinear() && !horzIsOpen && HorzIsSpike(horz)) { @@ -2145,7 +2146,8 @@ else if ((isLeftToRight && (TopX(ae, pt.y) >= pt.x)) || (!isLeftToRight && (TopX } // end for loop and end of (possible consecutive) horizontals if (IsHotEdge(horz)) { - AddOutPt(horz, horz.top); + OutPt op = AddOutPt(horz, horz.top); + AddToHorzSegList(op); } UpdateEdgeIntoAEL(horz); // this is the end of an intermediate horiz. } @@ -2421,10 +2423,8 @@ private void ConvertHorzSegsToJoins() { // for each HorzSegment, find others that overlap for (int j = i + 1; j < k; j++) { HorzSegment hs2 = horzSegList.get(j); - if (hs2.leftOp.pt.x >= hs1.rightOp.pt.x) { - break; - } - if (hs2.leftToRight == hs1.leftToRight || (hs2.rightOp.pt.x <= hs1.leftOp.pt.x)) { + if ((hs2.leftOp.pt.x >= hs1.rightOp.pt.x) || (hs2.leftToRight == hs1.leftToRight) + || (hs2.rightOp.pt.x <= hs1.leftOp.pt.x)) { continue; } long currY = hs1.leftOp.pt.y; @@ -2612,8 +2612,7 @@ private void ProcessHorzJoins() { or1.splits.add(or2.idx); // (#498) or2.owner = or1; } - } - else { + } else { or2.owner = or1; } outrecList.add(or2); // NOTE removed in 6e15ba0, but then fails tests @@ -2883,11 +2882,12 @@ private boolean CheckSplitOwner(OutRec outrec, List splits) { if (outrec.owner == null || outrec.owner.splits == null) { return false; } - for (int i : outrec.owner.splits) { + for (int i : splits) { OutRec split = outrecList.get(i); if (split == outrec || split == outrec.owner) { continue; - } else if (split.splits != null && CheckSplitOwner(outrec, split.splits)) { + } + if (split.splits != null && CheckSplitOwner(outrec, split.splits)) { return true; } if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec.pts, split.pts)) {