Skip to content

Commit

Permalink
Clipper.Engine - fixed incorrect Polytree (AngusJohnson#942)
Browse files Browse the repository at this point in the history
  • Loading branch information
AngusJohnson committed Feb 9, 2025
1 parent 6901921 commit fcf5607
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 29 deletions.
16 changes: 10 additions & 6 deletions CPP/Clipper2Lib/src/clipper.engine.cpp
Original file line number Diff line number Diff line change
@@ -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 *
*******************************************************************************/
Expand Down Expand Up @@ -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))
{
Expand All @@ -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
Expand Down Expand Up @@ -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) &&
Expand Down
47 changes: 46 additions & 1 deletion CPP/Tests/TestPolytreeHoles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
Expand All @@ -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());
Expand All @@ -55,6 +58,7 @@ double GetPolytreeArea(const PolyPath64& pp)
GetPolyPathArea(*child, result);
return result;
}

TEST(Clipper2Tests, TestPolytreeHoles2)
{
std::ifstream ifs("PolytreeHoleOwner2.txt");
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -162,6 +168,7 @@ TEST(Clipper2Tests, TestPolytreeHoles4) //#618
// +- Hole
EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 3);
}

TEST(Clipper2Tests, TestPolytreeHoles5)
{
Paths64 subject, clip;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
}
}

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);
}

47 changes: 46 additions & 1 deletion CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolytree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

}
17 changes: 11 additions & 6 deletions CSharp/Clipper2Lib/Clipper.Engine.cs
Original file line number Diff line number Diff line change
@@ -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#. *
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -3036,7 +3038,10 @@ private bool CheckSplitOwner(OutRec outrec, List<int>? 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;
Expand Down
34 changes: 19 additions & 15 deletions Delphi/Clipper2Lib/Clipper.Engine.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
*******************************************************************************)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit fcf5607

Please sign in to comment.