diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 927e7a75..036b4020 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 September 2024 * +* Date : 9 February 2025 * * Website : https://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Copyright : Angus Johnson 2010-2025 * * Purpose : This is the main polygon clipping module * * License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -1648,10 +1648,10 @@ namespace Clipper2Lib { void ClipperBase::FixSelfIntersects(OutRec* outrec) { OutPt* op2 = outrec->pts; + // triangles can't self-intersect + if (op2->prev == op2->next->next) return; for (; ; ) { - // triangles can't self-intersect - if (op2->prev == op2->next->next) break; if (SegmentsIntersect(op2->prev->pt, op2->pt, op2->next->pt, op2->next->next->pt)) { @@ -1660,6 +1660,8 @@ namespace Clipper2Lib { DoSplitOp(outrec, op2); if (!outrec->pts) break; op2 = outrec->pts; + // triangles can't self-intersect + if (op2->prev == op2->next->next) break; continue; } else @@ -2932,12 +2934,14 @@ namespace Clipper2Lib { { for (auto split : *splits) { + if (!split->pts && split->splits && + CheckSplitOwner(outrec, split->splits)) return true; //#942 split = GetRealOutRec(split); - if(!split || split == outrec || split->recursive_split == outrec) continue; + if (!split || split == outrec || split->recursive_split == outrec) continue; split->recursive_split = outrec; // prevent infinite loops if (split->splits && CheckSplitOwner(outrec, split->splits)) - return true; + return true; else if (CheckBounds(split) && IsValidOwner(outrec, split) && split->bounds.Contains(outrec->bounds) && diff --git a/CPP/Tests/TestPolytreeHoles.cpp b/CPP/Tests/TestPolytreeHoles.cpp index 901521a0..381f2edb 100644 --- a/CPP/Tests/TestPolytreeHoles.cpp +++ b/CPP/Tests/TestPolytreeHoles.cpp @@ -21,6 +21,7 @@ TEST(Clipper2Tests, TestPolytreeHoles1) c.Execute(ct, fr, solution, solution_open); EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution)); } + void PolyPathContainsPoint(const PolyPath64& pp, const Point64 pt, int& counter) { if (pp.Polygon().size() > 0) @@ -34,6 +35,7 @@ void PolyPathContainsPoint(const PolyPath64& pp, const Point64 pt, int& counter) for (const auto& child : pp) PolyPathContainsPoint(*child, pt, counter); } + bool PolytreeContainsPoint(const PolyPath64& pp, const Point64 pt) { int counter = 0; @@ -42,6 +44,7 @@ bool PolytreeContainsPoint(const PolyPath64& pp, const Point64 pt) EXPECT_GE(counter, 0); //ie 'pt' can't be inside more holes than outers return counter != 0; } + void GetPolyPathArea(const PolyPath64& pp, double& area) { area += Area(pp.Polygon()); @@ -55,6 +58,7 @@ double GetPolytreeArea(const PolyPath64& pp) GetPolyPathArea(*child, result); return result; } + TEST(Clipper2Tests, TestPolytreeHoles2) { std::ifstream ifs("PolytreeHoleOwner2.txt"); @@ -125,6 +129,7 @@ TEST(Clipper2Tests, TestPolytreeHoles2) for (const auto& poi_inside : points_of_interest_inside) EXPECT_TRUE(PolytreeContainsPoint(solution_tree, poi_inside)); } + TEST(Clipper2Tests, TestPolytreeHoles3) { Paths64 subject, clip, sol; @@ -142,6 +147,7 @@ TEST(Clipper2Tests, TestPolytreeHoles3) c.Execute(ClipType::Intersection, FillRule::NonZero, solution); EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 2); } + TEST(Clipper2Tests, TestPolytreeHoles4) //#618 { Paths64 subject; @@ -162,6 +168,7 @@ TEST(Clipper2Tests, TestPolytreeHoles4) //#618 // +- Hole EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 3); } + TEST(Clipper2Tests, TestPolytreeHoles5) { Paths64 subject, clip; @@ -179,6 +186,7 @@ TEST(Clipper2Tests, TestPolytreeHoles5) // + -Polygon (2) contains 2 holes. EXPECT_TRUE(tree.Count() == 3 && tree[2]->Count() == 2); } + TEST(Clipper2Tests, TestPolytreeHoles6) //#618 { Paths64 subject, clip; @@ -201,6 +209,7 @@ TEST(Clipper2Tests, TestPolytreeHoles6) //#618 // + -Polygon (2) contains 1 holes. EXPECT_TRUE(tree.Count() == 3 && tree[2]->Count() == 1); } + TEST(Clipper2Tests, TestPolytreeHoles7) //#618 { Paths64 subject; @@ -213,4 +222,40 @@ TEST(Clipper2Tests, TestPolytreeHoles7) //#618 c.Execute(ClipType::Union, FillRule::NonZero, polytree); //std::cout << polytree << std::endl; EXPECT_TRUE(polytree.Count() == 1 && polytree[0]->Count() == 1); -} \ No newline at end of file +} + +TEST(Clipper2Tests, TestPolytreeHoles8) // #942 +{ + Paths64 subject; + PolyTree64 solution; + Clipper64 c; + subject.push_back(MakePath({ 1588700,-8717600, 1616200,-8474800, + 1588700,-8474800 })); + subject.push_back(MakePath({ 13583800,-15601600, 13582800,-15508500, + 13555300,-15508500, 13555500,-15182200, 13010900,-15185400 })); + subject.push_back(MakePath({ 956700,-3092300, 1152600,3147400, 25600,3151700 })); + subject.push_back(MakePath({ 22575900,-16604000, 31286800,-12171900, + 31110200,4882800, 30996200,4826300, 30414400,5447400, 30260000,5391500, + 29662200,5805400, 28844500,5337900, 28435000,5789300, 27721400,5026400, + 22876300,5034300, 21977700,4414900, 21148000,4654700, 20917600,4653400, + 19334300,12411000, -2591700,12177200, 53200,3151100, -2564300,12149800, + 7819400,4692400, 10116000,5228600, 6975500,3120100, 7379700,3124700, + 11037900,596200, 12257000,2587800, 12257000,596200, 15227300,2352700, + 18444400,1112100, 19961100,5549400, 20173200,5078600, 20330000,5079300, + 20970200,4544300, 20989600,4563700, 19465500,1112100, 21611600,4182100, + 22925100,1112200, 22952700,1637200, 23059000,1112200, 24908100,4181200, + 27070100,3800600, 27238000,3800700, 28582200,520300, 29367800,1050100, + 29291400,179400, 29133700,360700, 29056700,312600, 29121900,332500, + 29269900,162300, 28941400,213100, 27491300,-3041500, 27588700,-2997800, + 22104900,-16142800, 13010900,-15603000, 13555500,-15182200, + 13555300,-15508500, 13582800,-15508500, 13583100,-15154700, + 1588700,-8822800, 1588700,-8379900, 1588700,-8474800, 1616200,-8474800, + 1003900,-630100, 1253300,-12284500, 12983400,-16239900 })); + subject.push_back(MakePath({ 198200,12149800, 1010600,12149800, 1011500,11859600 })); + subject.push_back(MakePath({ 21996700,-7432000, 22096700,-7432000, 22096700,-7332000 })); + c.AddSubject(subject); + c.Execute(ClipType::Union, FillRule::NonZero, solution); + + EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 2 && (*solution[0])[1]->Count() == 1); +} + diff --git a/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolytree.cs b/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolytree.cs index 7c30087c..560ea95e 100644 --- a/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolytree.cs +++ b/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolytree.cs @@ -132,5 +132,50 @@ public void TestPolytree2() "The polytree indicates it does not contain a point that it should contain"); } - } + + [TestMethod] + public void TestPolytree3() + { + Paths64 subject = new(); + subject.Add(Clipper.MakePath(new int[] {1588700, -8717600, + 1616200, -8474800, 1588700, -8474800 })); + subject.Add(Clipper.MakePath(new int[] { 13583800,-15601600, + 13582800,-15508500, 13555300,-15508500, 13555500,-15182200, + 13010900,-15185400 })); + subject.Add(Clipper.MakePath(new int[] { 956700, -3092300, 1152600, + 3147400, 25600, 3151700 })); + subject.Add(Clipper.MakePath(new int[] { + 22575900,-16604000, 31286800,-12171900, + 31110200,4882800, 30996200,4826300, 30414400,5447400, 30260000,5391500, + 29662200,5805400, 28844500,5337900, 28435000,5789300, 27721400,5026400, + 22876300,5034300, 21977700,4414900, 21148000,4654700, 20917600,4653400, + 19334300,12411000, -2591700,12177200, 53200,3151100, -2564300,12149800, + 7819400,4692400, 10116000,5228600, 6975500,3120100, 7379700,3124700, + 11037900,596200, 12257000,2587800, 12257000,596200, 15227300,2352700, + 18444400,1112100, 19961100,5549400, 20173200,5078600, 20330000,5079300, + 20970200,4544300, 20989600,4563700, 19465500,1112100, 21611600,4182100, + 22925100,1112200, 22952700,1637200, 23059000,1112200, 24908100,4181200, + 27070100,3800600, 27238000,3800700, 28582200,520300, 29367800,1050100, + 29291400,179400, 29133700,360700, 29056700,312600, 29121900,332500, + 29269900,162300, 28941400,213100, 27491300,-3041500, 27588700,-2997800, + 22104900,-16142800, 13010900,-15603000, 13555500,-15182200, + 13555300,-15508500, 13582800,-15508500, 13583100,-15154700, + 1588700,-8822800, 1588700,-8379900, 1588700,-8474800, 1616200,-8474800, + 1003900,-630100, 1253300,-12284500, 12983400,-16239900})); + subject.Add(Clipper.MakePath(new int[] { 198200, 12149800, 1010600, 12149800, 1011500, 11859600 })); + subject.Add(Clipper.MakePath(new int[] { 21996700, -7432000, 22096700, -7432000, 22096700, -7332000 })); + PolyTree64 solutionTree = new(); + + Clipper64 clipper = new(); + clipper.AddSubject(subject); + clipper.Execute(ClipType.Union, FillRule.NonZero, solutionTree); + + Assert.IsTrue(solutionTree.Count == 1 && solutionTree[0].Count == 2 + && solutionTree[0][1].Count == 1, "Incorrect PolyTree nesting."); + + + } // end TESTMETHOD TestPolytree3 + + } // end TestClass + } diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index f9015376..e4e60ee7 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 10 October 2024 * +* Date : 9 February 2025 * * Website : https://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Copyright : Angus Johnson 2010-2025 * * Purpose : This is the main polygon clipping module * * Thanks : Special thanks to Thong Nguyen, Guus Kuiper, Phil Stopford, * * : and Daniel Gosnell for their invaluable assistance with C#. * @@ -2917,16 +2917,18 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp) private void FixSelfIntersects(OutRec outrec) { OutPt op2 = outrec.pts!; + // triangles can't self-intersect + if (op2.prev == op2.next!.next) return; for (; ; ) { - // triangles can't self-intersect - if (op2.prev == op2.next!.next) break; if (InternalClipper.SegsIntersect(op2.prev.pt, - op2.pt, op2.next.pt, op2.next.next!.pt)) + op2.pt, op2.next!.pt, op2.next.next!.pt)) { DoSplitOp(outrec, op2); if (outrec.pts == null) return; op2 = outrec.pts; + // triangles can't self-intersect + if (op2.prev == op2.next!.next) break; continue; } @@ -3036,7 +3038,10 @@ private bool CheckSplitOwner(OutRec outrec, List? splits) { foreach (int i in splits!) { - OutRec? split = GetRealOutRec(_outrecList[i]); + OutRec? split = _outrecList[i]; + if (split.pts == null && split.splits != null && + CheckSplitOwner(outrec, split.splits)) return true; //#942 + split = GetRealOutRec(split); if (split == null || split == outrec || split.recursiveSplit == outrec) continue; split.recursiveSplit = outrec; //#599 if (split.splits != null && CheckSplitOwner(outrec, split.splits)) return true; diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index e66db765..cafc6a1d 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,9 +2,9 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 22 November 2024 * +* Date : 9 February 2025 * * Website : https://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Copyright : Angus Johnson 2010-2025 * * Purpose : This is the main polygon clipping module * * License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) @@ -2229,17 +2229,18 @@ procedure TClipperBase.FixSelfIntersects(outrec: POutRec); op2: POutPt; begin op2 := outrec.pts; + // triangles can't self-intersect + if (op2.prev = op2.next.next) then Exit; while true do begin - // triangles can't self-intersect - if (op2.prev = op2.next.next) then - Break - else if SegmentsIntersect(op2.prev.pt, op2.pt, + if SegmentsIntersect(op2.prev.pt, op2.pt, op2.next.pt, op2.next.next.pt) then begin DoSplitOp(outrec, op2); if not assigned(outrec.pts) then Break; op2 := outrec.pts; + // triangles can't self-intersect + if (op2.prev = op2.next.next) then Break; Continue; end else op2 := op2.next; @@ -3088,7 +3089,7 @@ procedure TClipperBase.ProcessHorzJoins; or1.pts.outrec := or1; end; - if FUsingPolytree then //#498, #520, #584, D#576, #618 + if FUsingPolytree then // #498, #520, #584, D#576, #618 begin if Path1InsidePath2(or1.pts, or2.pts) then begin @@ -3104,7 +3105,7 @@ procedure TClipperBase.ProcessHorzJoins; else if Path1InsidePath2(or2.pts, or1.pts) then begin or2.owner := or1; - end + end else or2.owner := or1.owner; @@ -3115,10 +3116,10 @@ procedure TClipperBase.ProcessHorzJoins; end else begin or2.pts := nil; - if FUsingPolytree then + if FUsingPolytree then begin SetOwner(or2, or1); - MoveSplits(or2, or1); //#618 + MoveSplits(or2, or1); // #618 end else or2.owner := or1; end; @@ -3737,12 +3738,15 @@ function TClipperBase.CheckSplitOwner(outrec: POutRec; const splits: TOutRecArra Result := true; for i := 0 to High(splits) do begin - split := GetRealOutRec(splits[i]); - if (split = nil) or - (split = outrec) or - (split.recursiveCheck = outrec) then Continue; - + split := splits[i]; + if not Assigned(split.pts) and Assigned(split.splits) and + CheckSplitOwner(outrec, split.splits) then Exit; // #942 + + split := GetRealOutRec(split); + if (split = nil) or (split = outrec) or + (split.recursiveCheck = outrec) then Continue; split.recursiveCheck := outrec; // prevent infinite loops + if Assigned(split.splits) and CheckSplitOwner(outrec, split.splits) then Exit else if IsValidOwner(outrec, split) and