From bd133d9434bdfbf44f991d6502e615b683905880 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 24 Dec 2013 12:40:46 +0100 Subject: [PATCH] Update Clipper to 6.1.2 --- lib/Slic3r/Print.pm | 2 +- t/clipper.t | 8 +- xs/src/ClipperUtils.cpp | 69 +- xs/src/clipper.cpp | 1975 +++++++++++++++++++-------------------- xs/src/clipper.hpp | 95 +- xs/t/11_clipper.t | 6 +- 6 files changed, 1064 insertions(+), 1091 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 83566b1f6..65b6628e8 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -932,7 +932,7 @@ sub write_gcode { # append full config print $fh "\n"; foreach my $opt_key (sort @{$self->config->get_keys}) { - next if @{$Slic3r::Config::Options->{$opt_key}{shortcut}}; + next if $Slic3r::Config::Options->{$opt_key}{shortcut}; printf $fh "; %s = %s\n", $opt_key, $self->config->serialize($opt_key); } } diff --git a/t/clipper.t b/t/clipper.t index 665c799fc..c25b8dc61 100644 --- a/t/clipper.t +++ b/t/clipper.t @@ -9,6 +9,7 @@ BEGIN { use lib "$FindBin::Bin/../lib"; } +use List::Util qw(sum); use Slic3r; use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex); @@ -33,7 +34,7 @@ use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex); ]; my $intersection = intersection_ex([ $square, $hole_in_square ], [ $square2 ]); - is_deeply [ map $_->pp, @$intersection ], [[ + is sum(map $_->area, @$intersection), Slic3r::ExPolygon->new( [ [20, 18], [10, 18], @@ -46,7 +47,7 @@ use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex); [16, 14], [14, 14], ], - ]], 'hole is preserved after intersection'; + )->area, 'hole is preserved after intersection'; } #========================================================== @@ -62,7 +63,8 @@ use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex); 'union of two ccw and one cw is a contour with no holes'; my $diff = diff_ex([ $contour1, $contour2 ], [ $hole ]); - is_deeply [ map $_->pp, @$diff ], [[ [ [40,40], [0,40], [0,0], [40,0] ], [ [15,25], [25,25], [25,15], [15,15] ] ]], + is sum(map $_->area, @$diff), + Slic3r::ExPolygon->new([ [40,40], [0,40], [0,0], [40,0] ], [ [15,25], [25,25], [25,15], [15,15] ])->area, 'difference of a cw from two ccw is a contour with one hole'; } diff --git a/xs/src/ClipperUtils.cpp b/xs/src/ClipperUtils.cpp index 3ba65924d..57216f0b2 100644 --- a/xs/src/ClipperUtils.cpp +++ b/xs/src/ClipperUtils.cpp @@ -106,15 +106,21 @@ offset(const Slic3r::Polygons &polygons, ClipperLib::Paths &retval, const float double scale, ClipperLib::JoinType joinType, double miterLimit) { // read input - ClipperLib::Paths* input = new ClipperLib::Paths(); - Slic3rMultiPoints_to_ClipperPaths(polygons, *input); + ClipperLib::Paths input; + Slic3rMultiPoints_to_ClipperPaths(polygons, input); // scale input - scaleClipperPolygons(*input, scale); + scaleClipperPolygons(input, scale); // perform offset - ClipperLib::OffsetPaths(*input, retval, (delta*scale), joinType, ClipperLib::etClosed, miterLimit); - delete input; + ClipperLib::ClipperOffset co; + if (joinType == jtRound) { + co.ArcTolerance = miterLimit; + } else { + co.MiterLimit = miterLimit; + } + co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); + co.Execute(retval, (delta*scale)); // unscale output scaleClipperPolygons(retval, 1/scale); @@ -125,12 +131,11 @@ offset(const Slic3r::Polygons &polygons, Slic3r::Polygons &retval, const float d double scale, ClipperLib::JoinType joinType, double miterLimit) { // perform offset - ClipperLib::Paths* output = new ClipperLib::Paths(); - offset(polygons, *output, delta, scale, joinType, miterLimit); + ClipperLib::Paths output; + offset(polygons, output, delta, scale, joinType, miterLimit); // convert into ExPolygons - ClipperPaths_to_Slic3rMultiPoints(*output, retval); - delete output; + ClipperPaths_to_Slic3rMultiPoints(output, retval); } void @@ -138,15 +143,21 @@ offset(const Slic3r::Polylines &polylines, ClipperLib::Paths &retval, const floa double scale, ClipperLib::JoinType joinType, double miterLimit) { // read input - ClipperLib::Paths* input = new ClipperLib::Paths(); - Slic3rMultiPoints_to_ClipperPaths(polylines, *input); + ClipperLib::Paths input; + Slic3rMultiPoints_to_ClipperPaths(polylines, input); // scale input - scaleClipperPolygons(*input, scale); + scaleClipperPolygons(input, scale); // perform offset - ClipperLib::OffsetPaths(*input, retval, (delta*scale), joinType, ClipperLib::etButt, miterLimit); - delete input; + ClipperLib::ClipperOffset co; + if (joinType == jtRound) { + co.ArcTolerance = miterLimit; + } else { + co.MiterLimit = miterLimit; + } + co.AddPaths(input, joinType, ClipperLib::etOpenButt); + co.Execute(retval, (delta*scale)); // unscale output scaleClipperPolygons(retval, 1/scale); @@ -201,20 +212,29 @@ offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths &retval, const float const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) { // read input - ClipperLib::Paths* input = new ClipperLib::Paths(); - Slic3rMultiPoints_to_ClipperPaths(polygons, *input); + ClipperLib::Paths input; + Slic3rMultiPoints_to_ClipperPaths(polygons, input); // scale input - scaleClipperPolygons(*input, scale); + scaleClipperPolygons(input, scale); + + // prepare ClipperOffset object + ClipperLib::ClipperOffset co; + if (joinType == jtRound) { + co.ArcTolerance = miterLimit; + } else { + co.MiterLimit = miterLimit; + } // perform first offset - ClipperLib::Paths* output1 = new ClipperLib::Paths(); - ClipperLib::OffsetPaths(*input, *output1, (delta1*scale), joinType, ClipperLib::etClosed, miterLimit); - delete input; + ClipperLib::Paths output1; + co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); + co.Execute(output1, (delta1*scale)); // perform second offset - ClipperLib::OffsetPaths(*output1, retval, (delta2*scale), joinType, ClipperLib::etClosed, miterLimit); - delete output1; + co.Clear(); + co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon); + co.Execute(retval, (delta2*scale)); // unscale output scaleClipperPolygons(retval, 1/scale); @@ -444,7 +464,10 @@ void safety_offset(ClipperLib::Paths* &subject) // perform offset (delta = scale 1e-05) ClipperLib::Paths* retval = new ClipperLib::Paths(); - ClipperLib::OffsetPaths(*subject, *retval, 10.0 * CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, ClipperLib::etClosed, 2); + ClipperLib::ClipperOffset co; + co.MiterLimit = 2; + co.AddPaths(*subject, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); + co.Execute(*retval, 10.0 * CLIPPER_OFFSET_SCALE); // unscale output scaleClipperPolygons(*retval, 1.0/CLIPPER_OFFSET_SCALE); diff --git a/xs/src/clipper.cpp b/xs/src/clipper.cpp index de0351ec0..340c14984 100755 --- a/xs/src/clipper.cpp +++ b/xs/src/clipper.cpp @@ -1,8 +1,8 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.0.0 * -* Date : 30 October 2013 * +* Version : 6.1.2 * +* Date : 15 December 2013 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2013 * * * @@ -60,6 +60,9 @@ namespace ClipperLib { #endif static double const pi = 3.141592653589793238; +static double const two_pi = pi *2; +static double const def_arc_tolerance = 0.25; + enum Direction { dRightToLeft, dLeftToRight }; static int const Unassigned = -1; //edge not currently 'owning' a solution @@ -94,7 +97,6 @@ struct IntersectNode { TEdge *Edge1; TEdge *Edge2; IntPoint Pt; - IntersectNode *Next; }; struct LocalMinima { @@ -168,7 +170,7 @@ PolyNode* PolyTree::GetFirst() const int PolyTree::Total() const { - return AllNodes.size(); + return (int)AllNodes.size(); } //------------------------------------------------------------------------------ @@ -182,13 +184,13 @@ PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) int PolyNode::ChildCount() const { - return Childs.size(); + return (int)Childs.size(); } //------------------------------------------------------------------------------ void PolyNode::AddChild(PolyNode& child) { - unsigned cnt = Childs.size(); + unsigned cnt = (unsigned)Childs.size(); Childs.push_back(&child); child.Parent = this; child.Index = cnt; @@ -390,7 +392,7 @@ class Int128 return result; } else if (rhs.hi == this->hi && rhs.lo == this->lo) - return Int128(1); + return Int128(negate ? -1: 1); else return Int128(0); } @@ -400,8 +402,9 @@ class Int128 const double shift64 = 18446744073709551616.0; //2^64 if (hi < 0) { - if (lo == 0) return (double)hi * shift64; - else return -(double)(~lo + ~hi * shift64); + cUInt lo_ = ~lo + 1; + if (lo_ == 0) return (double)hi * shift64; + else return -(double)(lo_ + ~hi * shift64); } else return (double)(lo + hi * shift64); @@ -449,14 +452,16 @@ bool Orientation(const Path &poly) double Area(const Path &poly) { - int highI = (int)poly.size() -1; - if (highI < 2) return 0; + int size = (int)poly.size(); + if (size < 3) return 0; - double a; - a = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y); - for (int i = 1; i <= highI; ++i) - a += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i - 1].Y); - return a / 2; + double a = 0; + for (int i = 0, j = size -1; i < size; ++i) + { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; } //------------------------------------------------------------------------------ @@ -466,10 +471,10 @@ double Area(const OutRec &outRec) if (!op) return 0; double a = 0; do { - a = a + (double)(op->Pt.X + op->Prev->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); + a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); op = op->Next; } while (op != outRec.Pts); - return a / 2; + return a * 0.5; } //------------------------------------------------------------------------------ @@ -486,73 +491,63 @@ bool PointIsVertex(const IntPoint &Pt, OutPt *pp) } //------------------------------------------------------------------------------ -bool PointOnLineSegment(const IntPoint Pt, - const IntPoint linePt1, const IntPoint linePt2, bool UseFullInt64Range) +int PointInPolygon (const IntPoint& pt, OutPt* op) { -#ifndef use_int32 - if (UseFullInt64Range) - return ((Pt.X == linePt1.X) && (Pt.Y == linePt1.Y)) || - ((Pt.X == linePt2.X) && (Pt.Y == linePt2.Y)) || - (((Pt.X > linePt1.X) == (Pt.X < linePt2.X)) && - ((Pt.Y > linePt1.Y) == (Pt.Y < linePt2.Y)) && - ((Int128Mul((Pt.X - linePt1.X), (linePt2.Y - linePt1.Y)) == - Int128Mul((linePt2.X - linePt1.X), (Pt.Y - linePt1.Y))))); - else -#endif - return ((Pt.X == linePt1.X) && (Pt.Y == linePt1.Y)) || - ((Pt.X == linePt2.X) && (Pt.Y == linePt2.Y)) || - (((Pt.X > linePt1.X) == (Pt.X < linePt2.X)) && - ((Pt.Y > linePt1.Y) == (Pt.Y < linePt2.Y)) && - ((Pt.X - linePt1.X) * (linePt2.Y - linePt1.Y) == - (linePt2.X - linePt1.X) * (Pt.Y - linePt1.Y))); -} -//------------------------------------------------------------------------------ - -bool PointOnPolygon(const IntPoint Pt, OutPt *pp, bool UseFullInt64Range) -{ - OutPt *pp2 = pp; - while (true) + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf + int result = 0; + OutPt* startOp = op; + for(;;) { - if (PointOnLineSegment(Pt, pp2->Pt, pp2->Next->Pt, UseFullInt64Range)) - return true; - pp2 = pp2->Next; - if (pp2 == pp) break; - } - return false; -} -//------------------------------------------------------------------------------ - -bool PointInPolygon(const IntPoint &Pt, OutPt *pp, bool UseFullInt64Range) -{ - OutPt *pp2 = pp; - bool result = false; -#ifndef use_int32 - if (UseFullInt64Range) { - do + if (op->Next->Pt.Y == pt.Y) { - if (((pp2->Pt.Y > Pt.Y) != (pp2->Prev->Pt.Y > Pt.Y)) && - (Int128(Pt.X - pp2->Pt.X) < - Int128Mul(pp2->Prev->Pt.X - pp2->Pt.X, Pt.Y - pp2->Pt.Y) / - Int128(pp2->Prev->Pt.Y - pp2->Pt.Y))) result = !result; - pp2 = pp2->Next; + if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && + ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; } - while (pp2 != pp); - return result; - } -#endif - do - { - //http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - if (((pp2->Pt.Y > Pt.Y) != (pp2->Prev->Pt.Y > Pt.Y)) && - ((Pt.X - pp2->Pt.X) < (pp2->Prev->Pt.X - pp2->Pt.X) * (Pt.Y - pp2->Pt.Y) / - (pp2->Prev->Pt.Y - pp2->Pt.Y))) result = !result; - pp2 = pp2->Next; - } - while (pp2 != pp); + if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) + { + if (op->Pt.X >= pt.X) + { + if (op->Next->Pt.X > pt.X) result = 1 - result; + else + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } else + { + if (op->Next->Pt.X > pt.X) + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } + } + op = op->Next; + if (startOp == op) break; + } return result; } //------------------------------------------------------------------------------ +bool Poly2ContainsPoly1(OutPt* OutPt1, OutPt* OutPt2) +{ + OutPt* op = OutPt1; + do + { + int res = PointInPolygon(op->Pt, OutPt2); + if (res >= 0) return res != 0; + op = op->Next; + } + while (op != OutPt1); + return true; +} +//---------------------------------------------------------------------- + bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { #ifndef use_int32 @@ -645,8 +640,8 @@ bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, //return false but for the edge.Dx value be equal due to double precision rounding. if (SlopesEqual(Edge1, Edge2, UseFullInt64Range) || Edge1.Dx == Edge2.Dx) { - if (Edge2.Bot.Y > Edge1.Bot.Y) ip.Y = Edge2.Bot.Y; - else ip.Y = Edge1.Bot.Y; + if (Edge2.Bot.Y > Edge1.Bot.Y) ip = Edge2.Bot; + else ip = Edge1.Bot; return false; } else if (Edge1.Delta.X == 0) @@ -686,20 +681,15 @@ bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) { if (Edge1.Top.Y > Edge2.Top.Y) - { ip.Y = Edge1.Top.Y; - ip.X = TopX(Edge2, Edge1.Top.Y); - return ip.X < Edge1.Top.X; - } else - { ip.Y = Edge2.Top.Y; - ip.X = TopX(Edge1, Edge2.Top.Y); - return ip.X > Edge2.Top.X; - } + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); } - else - return true; + return true; } //------------------------------------------------------------------------------ @@ -767,129 +757,6 @@ TEdge* RemoveEdge(TEdge* e) } //------------------------------------------------------------------------------ -TEdge* GetLastHorz(TEdge* Edge) -{ - TEdge* result = Edge; - while (result->OutIdx != Skip && result->Next != Edge && IsHorizontal(*result->Next)) - result = result->Next; - return result; -} -//------------------------------------------------------------------------------ - -bool SharedVertWithPrevAtTop(TEdge* Edge) -{ - TEdge* E = Edge; - bool result = true; - while (E->Prev != Edge) - { - if (E->Top == E->Prev->Top) - { - if (E->Bot == E->Prev->Bot) - {E = E->Prev; continue;} - else result = true; - } - else result = false; - break; - } - while (E != Edge) - { - result = !result; - E = E->Next; - } - return result; -} -//------------------------------------------------------------------------------ - -bool SharedVertWithNextIsBot(TEdge* Edge) -{ - bool result = true; - TEdge* E = Edge; - while (E->Prev != Edge) - { - bool A = (E->Next->Bot == E->Bot); - bool B = (E->Prev->Bot == E->Bot); - if (A != B) - { - result = A; - break; - } - A = (E->Next->Top == E->Top); - B = (E->Prev->Top == E->Top); - if (A != B) - { - result = B; - break; - } - E = E->Prev; - } - while (E != Edge) - { - result = !result; - E = E->Next; - } - return result; -} -//------------------------------------------------------------------------------ - -bool MoreBelow(TEdge* Edge) -{ - //Edge is Skip heading down. - TEdge* E = Edge; - if (IsHorizontal(*E)) - { - while (IsHorizontal(*E->Next)) E = E->Next; - return E->Next->Bot.Y > E->Bot.Y; - } else if (IsHorizontal(*E->Next)) - { - while (IsHorizontal(*E->Next)) E = E->Next; - return E->Next->Bot.Y > E->Bot.Y; - } - else return (E->Bot == E->Next->Top); -} -//------------------------------------------------------------------------------ - -bool JustBeforeLocMin(TEdge* Edge) -{ - //Edge is Skip and was heading down. - TEdge*E = Edge; - if (IsHorizontal(*E)) - { - while (IsHorizontal(*E->Next)) E = E->Next; - return E->Next->Top.Y < E->Bot.Y; - } - else return SharedVertWithNextIsBot(E); -} -//------------------------------------------------------------------------------ - -bool MoreAbove(TEdge* Edge) -{ - if (IsHorizontal(*Edge)) - { - Edge = GetLastHorz(Edge); - return (Edge->Next->Top.Y < Edge->Top.Y); - } else if (IsHorizontal(*Edge->Next)) - { - Edge = GetLastHorz(Edge->Next); - return (Edge->Next->Top.Y < Edge->Top.Y); - } - else - return (Edge->Next->Top.Y < Edge->Top.Y); -} -//------------------------------------------------------------------------------ - -bool AllHorizontal(TEdge* Edge) -{ - if (!IsHorizontal(*Edge)) return false; - TEdge* E = Edge->Next; - while (E != Edge) - { - if (!IsHorizontal(*E)) return false; - else E = E->Next; - } - return true; -} -//------------------------------------------------------------------------------ - inline void ReverseHorizontal(TEdge &e) { //swap horizontal edges' Top and Bottom x's so they follow the natural @@ -1094,6 +961,135 @@ void RangeTest(const IntPoint& Pt, bool& useFullRange) } //------------------------------------------------------------------------------ +TEdge* FindNextLocMin(TEdge* E) +{ + for (;;) + { + while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; + if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; + while (IsHorizontal(*E->Prev)) E = E->Prev; + TEdge* E2 = E; + while (IsHorizontal(*E)) E = E->Next; + if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. + if (E2->Prev->Bot.X < E->Bot.X) E = E2; + break; + } + return E; +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) +{ + TEdge *EStart = E, *Result = E; + TEdge *Horz = 0; + cInt StartX; + if (IsHorizontal(*E)) + { + //it's possible for adjacent overlapping horz edges to start heading left + //before finishing right, so ... + if (IsClockwise) StartX = E->Prev->Bot.X; + else StartX = E->Next->Bot.X; + if (E->Bot.X != StartX) ReverseHorizontal(*E); + } + + if (Result->OutIdx != Skip) + { + if (IsClockwise) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X == Result->Next->Top.X) + { + if (!IsClockwise) Result = Horz->Prev; + } + else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X) + { + if (!IsClockwise) Result = Horz->Next; + } + else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } + + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + } + + if (Result->OutIdx == Skip) + { + //if edges still remain in the current bound beyond the skip edge then + //create another LocMin and call ProcessBound once more + E = Result; + if (IsClockwise) + { + while (E->Top.Y == E->Next->Bot.Y) E = E->Next; + //don't include top horizontals when parsing a bound a second time, + //they will be contained in the opposite bound ... + while (E != Result && IsHorizontal(*E)) E = E->Prev; + } else + { + while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; + while (E != Result && IsHorizontal(*E)) E = E->Next; + } + if (E == Result) + { + if (IsClockwise) Result = E->Next; + else Result = E->Prev; + } else + { + //there are more edges in the bound beyond result starting with E + if (IsClockwise) + E = Result->Next; + else + E = Result->Prev; + LocalMinima* locMin = new LocalMinima; + locMin->Next = 0; + locMin->Y = E->Bot.Y; + locMin->LeftBound = 0; + locMin->RightBound = E; + locMin->RightBound->WindDelta = 0; + Result = ProcessBound(locMin->RightBound, IsClockwise); + InsertLocalMinima(locMin); + } + } + return Result; +} +//------------------------------------------------------------------------------ + bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { #ifdef use_lines @@ -1105,15 +1101,15 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) #endif int highI = (int)pg.size() -1; - bool ClosedOrSemiClosed = (highI > 0) && (Closed || (pg[0] == pg[highI])); - while (highI > 0 && (pg[highI] == pg[0])) --highI; + if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; //create a new edge array ... TEdge *edges = new TEdge [highI +1]; - //1. Basic initialization of Edges ... + bool IsFlat = true; + //1. Basic (first) edge initialization ... try { edges[1].Curr = pg[1]; @@ -1134,14 +1130,15 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) } TEdge *eStart = &edges[0]; - if (!ClosedOrSemiClosed) eStart->Prev->OutIdx = Skip; + if (!Closed) eStart->Prev->OutIdx = Skip; - //2. Remove duplicate vertices, and collinear edges (when closed) ... + //2. Remove duplicate vertices, and (when closed) collinear edges ... TEdge *E = eStart, *eLoopStop = eStart; for (;;) { if ((E->Curr == E->Next->Curr)) { + if (E == E->Next) break; if (E == eStart) eStart = E->Next; E = RemoveEdge(E); eLoopStop = E; @@ -1149,24 +1146,20 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) } if (E->Prev == E->Next) break; //only two vertices - else if ((ClosedOrSemiClosed || - (E->Prev->OutIdx != Skip && E->OutIdx != Skip && - E->Next->OutIdx != Skip)) && - SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange)) + else if (Closed && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) { - //All collinear edges are allowed for open paths but in closed paths - //inner vertices of adjacent collinear edges are removed. However if the - //PreserveCollinear property has been enabled, only overlapping collinear - //edges (ie spikes) are removed from closed paths. - if (Closed && (!m_PreserveCollinear || - !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) - { - if (E == eStart) eStart = E->Next; - E = RemoveEdge(E); - E = E->Prev; - eLoopStop = E; - continue; - } + //Collinear edges are allowed for open paths but in closed paths + //the default is to merge adjacent collinear edges into a single edge. + //However, if the PreserveCollinear property is enabled, only overlapping + //collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; } E = E->Next; if (E == eLoopStop) break; @@ -1177,67 +1170,94 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) delete [] edges; return false; } - m_edges.push_back(edges); - if (!Closed) - m_HasOpenPaths = true; + if (!Closed) m_HasOpenPaths = true; - //3. Do final Init and also find the 'highest' Edge. (nb: since I'm much - //more familiar with positive downwards Y axes, 'highest' here will be - //the Edge with the *smallest* Top.Y.) - TEdge *eHighest = eStart; + //3. Do second stage of edge initialization ... E = eStart; do { InitEdge2(*E, PolyTyp); - if (E->Top.Y < eHighest->Top.Y) eHighest = E; E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; } while (E != eStart); - //4. build the local minima list ... - if (AllHorizontal(E)) + //4. Finally, add edge bounds to LocalMinima list ... + + //Totally flat paths must be handled differently when adding them + //to LocalMinima list to avoid endless loops etc ... + if (IsFlat) { - if (ClosedOrSemiClosed) - E->Prev->OutIdx = Skip; - AscendToMax(E, false, false); - return true; + if (Closed) + { + delete [] edges; + return false; + } + E->Prev->OutIdx = Skip; + if (E->Prev->Bot.X < E->Prev->Top.X) ReverseHorizontal(*E->Prev); + LocalMinima* locMin = new LocalMinima(); + locMin->Next = 0; + locMin->Y = E->Bot.Y; + locMin->LeftBound = 0; + locMin->RightBound = E; + locMin->RightBound->Side = esRight; + locMin->RightBound->WindDelta = 0; + while (E->Next->OutIdx != Skip) + { + E->NextInLML = E->Next; + if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + InsertLocalMinima(locMin); + m_edges.push_back(edges); + return true; } - //if eHighest is also the Skip then it's a natural break, otherwise - //make sure eHighest is positioned so we're either at a top horizontal or - //just starting to head down one edge of the polygon - E = eStart->Prev; //EStart.Prev == Skip edge - if (E->Prev == E->Next) - eHighest = E->Next; - else if (!ClosedOrSemiClosed && E->Top.Y == eHighest->Top.Y) + m_edges.push_back(edges); + bool clockwise; + TEdge* EMin = 0; + for (;;) { - if ((IsHorizontal(*E) || IsHorizontal(*E->Next)) && - E->Next->Bot.Y == eHighest->Top.Y) - eHighest = E->Next; - else if (SharedVertWithPrevAtTop(E)) eHighest = E; - else if (E->Top == E->Prev->Top) eHighest = E->Prev; - else eHighest = E->Next; - } else - { - E = eHighest; - while (IsHorizontal(*eHighest) || - (eHighest->Top == eHighest->Next->Top) || - (eHighest->Top == eHighest->Next->Bot)) //next is high horizontal + E = FindNextLocMin(E); + if (E == EMin) break; + else if (!EMin) EMin = E; + + //E and E.Prev now share a local minima (left aligned if horizontal). + //Compare their slopes to find which starts which bound ... + LocalMinima* locMin = new LocalMinima; + locMin->Next = 0; + locMin->Y = E->Bot.Y; + if (E->Dx < E->Prev->Dx) { - eHighest = eHighest->Next; - if (eHighest == E) - { - while (IsHorizontal(*eHighest) || !SharedVertWithPrevAtTop(eHighest)) - eHighest = eHighest->Next; - break; //avoids potential endless loop - } + locMin->LeftBound = E->Prev; + locMin->RightBound = E; + clockwise = false; //Q.nextInLML = Q.prev + } else + { + locMin->LeftBound = E; + locMin->RightBound = E->Prev; + clockwise = true; //Q.nextInLML = Q.next } + locMin->LeftBound->Side = esLeft; + locMin->RightBound->Side = esRight; + + if (!Closed) locMin->LeftBound->WindDelta = 0; + else if (locMin->LeftBound->Next == locMin->RightBound) + locMin->LeftBound->WindDelta = -1; + else locMin->LeftBound->WindDelta = 1; + locMin->RightBound->WindDelta = -locMin->LeftBound->WindDelta; + + E = ProcessBound(locMin->LeftBound, clockwise); + TEdge* E2 = ProcessBound(locMin->RightBound, !clockwise); + + if (locMin->LeftBound->OutIdx == Skip) + locMin->LeftBound = 0; + else if (locMin->RightBound->OutIdx == Skip) + locMin->RightBound = 0; + InsertLocalMinima(locMin); + if (!clockwise) E = E2; } - E = eHighest; - do - E = AddBoundsToLML(E, Closed); - while (E != eHighest); return true; } //------------------------------------------------------------------------------ @@ -1272,191 +1292,6 @@ void ClipperBase::InsertLocalMinima(LocalMinima *newLm) } //------------------------------------------------------------------------------ -void ClipperBase::DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed) -{ - if (!E1) - { - if (!E2) return; - LocalMinima* NewLm = new LocalMinima; - NewLm->Next = 0; - NewLm->Y = E2->Bot.Y; - NewLm->LeftBound = 0; - E2->WindDelta = 0; - NewLm->RightBound = E2; - InsertLocalMinima(NewLm); - } else - { - //E and E.Prev are now at a local minima ... - LocalMinima* NewLm = new LocalMinima; - NewLm->Y = E1->Bot.Y; - NewLm->Next = 0; - if (IsHorizontal(*E2)) //Horz. edges never start a Left bound - { - if (E2->Bot.X != E1->Bot.X) ReverseHorizontal(*E2); - NewLm->LeftBound = E1; - NewLm->RightBound = E2; - } else if (E2->Dx < E1->Dx) - { - NewLm->LeftBound = E1; - NewLm->RightBound = E2; - } else - { - NewLm->LeftBound = E2; - NewLm->RightBound = E1; - } - NewLm->LeftBound->Side = esLeft; - NewLm->RightBound->Side = esRight; - //set the winding state of the first edge in each bound - //(it'll be copied to subsequent edges in the bound) ... - if (!IsClosed) NewLm->LeftBound->WindDelta = 0; - else if (NewLm->LeftBound->Next == NewLm->RightBound) NewLm->LeftBound->WindDelta = -1; - else NewLm->LeftBound->WindDelta = 1; - NewLm->RightBound->WindDelta = -NewLm->LeftBound->WindDelta; - InsertLocalMinima(NewLm); - } -} -//---------------------------------------------------------------------- - -TEdge* ClipperBase::DescendToMin(TEdge *&E) -{ - //PRECONDITION: STARTING EDGE IS A VALID DESCENDING EDGE. - //Starting at the top of one bound we progress to the bottom where there's - //A local minima. We go to the top of the Next bound. These two bounds - //form the left and right (or right and left) bounds of the local minima. - TEdge* EHorz; - E->NextInLML = 0; - if (IsHorizontal(*E)) - { - EHorz = E; - while (IsHorizontal(*EHorz->Next)) EHorz = EHorz->Next; - if (EHorz->Bot != EHorz->Next->Top) - ReverseHorizontal(*E); - } - for (;;) - { - E = E->Next; - if (E->OutIdx == Skip) break; - else if (IsHorizontal(*E)) - { - //nb: proceed through horizontals when approaching from their right, - // but break on horizontal minima if approaching from their left. - // This ensures 'local minima' are always on the left of horizontals. - - //look ahead is required in case of multiple consec. horizontals - EHorz = GetLastHorz(E); - if(EHorz == E->Prev || //horizontal line - (EHorz->Next->Top.Y < E->Top.Y && //bottom horizontal - EHorz->Next->Bot.X > E->Prev->Bot.X)) //approaching from the left - break; - if (E->Top.X != E->Prev->Bot.X) ReverseHorizontal(*E); - if (EHorz->OutIdx == Skip) EHorz = EHorz->Prev; - while (E != EHorz) - { - E->NextInLML = E->Prev; - E = E->Next; - if (E->Top.X != E->Prev->Bot.X) ReverseHorizontal(*E); - } - } - else if (E->Bot.Y == E->Prev->Bot.Y) break; - E->NextInLML = E->Prev; - } - return E->Prev; -} -//---------------------------------------------------------------------- - -void ClipperBase::AscendToMax(TEdge *&E, bool Appending, bool IsClosed) -{ - if (E->OutIdx == Skip) - { - E = E->Next; - if (!MoreAbove(E->Prev)) return; - } - - if (IsHorizontal(*E) && Appending && (E->Bot != E->Prev->Bot)) - ReverseHorizontal(*E); - //now process the ascending bound .... - TEdge *EStart = E; - for (;;) - { - if (E->Next->OutIdx == Skip || - ((E->Next->Top.Y == E->Top.Y) && !IsHorizontal(*E->Next))) break; - E->NextInLML = E->Next; - E = E->Next; - if (IsHorizontal(*E) && (E->Bot.X != E->Prev->Top.X)) - ReverseHorizontal(*E); - } - - if (!Appending) - { - if (EStart->OutIdx == Skip) EStart = EStart->Next; - if (EStart != E->Next) - DoMinimaLML(0, EStart, IsClosed); - } - E = E->Next; -} -//---------------------------------------------------------------------- - -TEdge* ClipperBase::AddBoundsToLML(TEdge* E, bool IsClosed) -{ - //Starting at the top of one bound we progress to the bottom where there's - //A local minima. We then go to the top of the Next bound. These two bounds - //form the left and right (or right and left) bounds of the local minima. - - TEdge* B; - bool AppendMaxima; - //do minima ... - if (E->OutIdx == Skip) - { - if (MoreBelow(E)) - { - E = E->Next; - B = DescendToMin(E); - } else - B = 0; - } else - B = DescendToMin(E); - - if (E->OutIdx == Skip) //nb: may be BEFORE, AT or just THRU LM - { - //do minima before Skip... - DoMinimaLML(0, B, IsClosed); //store what we've got so far (if anything) - AppendMaxima = false; - //finish off any minima ... - if ((E->Bot != E->Prev->Bot) && MoreBelow(E)) - { - E = E->Next; - B = DescendToMin(E); - DoMinimaLML(B, E, IsClosed); - AppendMaxima = true; - } - else if (JustBeforeLocMin(E)) - E = E->Next; - } else - { - DoMinimaLML(B, E, IsClosed); - AppendMaxima = true; - } - - //now do maxima ... - AscendToMax(E, AppendMaxima, IsClosed); - - if (E->OutIdx == Skip && (E->Top != E->Prev->Top)) - { - //may be BEFORE, AT or just AFTER maxima - //finish off any maxima ... - if (MoreAbove(E)) - { - E = E->Next; - AscendToMax(E, false, IsClosed); - } - else if ((E->Top == E->Next->Top) || - (IsHorizontal(*E->Next) && (E->Top == E->Next->Bot))) - E = E->Next; //ie just before Maxima - } - return E; -} -//---------------------------------------------------------------------- - void ClipperBase::Clear() { DisposeLocalMinimaList(); @@ -1487,15 +1322,16 @@ void ClipperBase::Reset() { e->Curr = e->Bot; e->Side = esLeft; - if (e->OutIdx != Skip) - e->OutIdx = Unassigned; - } - e = lm->RightBound; - e->Curr = e->Bot; - e->Side = esRight; - if (e->OutIdx != Skip) e->OutIdx = Unassigned; + } + e = lm->RightBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; + } lm = lm->Next; } } @@ -1568,7 +1404,6 @@ Clipper::Clipper(int initOptions) : ClipperBase() //constructor { m_ActiveEdges = 0; m_SortedEdges = 0; - m_IntersectNodes = 0; m_ExecuteLocked = false; m_UseFullRange = false; m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); @@ -2008,6 +1843,7 @@ OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { AddOutPt( e1, Pt ); + if (e2->WindDelta == 0) AddOutPt(e2, Pt); if( e1->OutIdx == e2->OutIdx ) { e1->OutIdx = Unassigned; @@ -2105,6 +1941,14 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) if (IsContributing(*rb)) Op1 = AddOutPt(rb, rb->Bot); } + else if (!rb) + { + InsertEdgeIntoAEL(lb, 0); + SetWindingCount(*lb); + if (IsContributing(*lb)) + Op1 = AddOutPt(lb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } else { InsertEdgeIntoAEL(lb, 0); @@ -2117,12 +1961,13 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) InsertScanbeam(lb->Top.Y); } - if(IsHorizontal(*rb)) - AddEdgeToSEL(rb); - else - InsertScanbeam( rb->Top.Y ); + if (rb) + { + if(IsHorizontal(*rb)) AddEdgeToSEL(rb); + else InsertScanbeam( rb->Top.Y ); + } - if (!lb) continue; + if (!lb || !rb) continue; //if any output polygons share an edge, they'll need joining later ... if (Op1 && IsHorizontal(*rb) && @@ -2840,10 +2685,12 @@ void Clipper::PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam) //we need to create 'ghost' Join records of 'contrubuting' horizontals that //we can compare with horizontals at the bottom of the next SB. if (isTopOfScanbeam) + { if (outPt->Pt == horzEdge->Top) AddGhostJoin(outPt, horzEdge->Bot); else AddGhostJoin(outPt, horzEdge->Top); + } } //------------------------------------------------------------------------------ @@ -2886,12 +2733,12 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) if ((dir == dLeftToRight && e->Curr.X <= horzRight) || (dir == dRightToLeft && e->Curr.X >= horzLeft)) { + if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); //so far we're still in range of the horizontal Edge but make sure //we're at the last of consec. horizontals when matching with eMaxPair if(e == eMaxPair && IsLastHorz) { - if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) - PrepareHorzJoins(horzEdge, isTopOfScanbeam); if (dir == dLeftToRight) IntersectEdges(horzEdge, e, e->Top); else @@ -3009,8 +2856,9 @@ bool Clipper::ProcessIntersections(const cInt botY, const cInt topY) if( !m_ActiveEdges ) return true; try { BuildIntersectList(botY, topY); - if (!m_IntersectNodes) return true; - if (!m_IntersectNodes->Next || FixupIntersectionOrder()) ProcessIntersectList(); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) return true; + if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); else return false; } catch(...) @@ -3026,12 +2874,9 @@ bool Clipper::ProcessIntersections(const cInt botY, const cInt topY) void Clipper::DisposeIntersectNodes() { - while ( m_IntersectNodes ) - { - IntersectNode* iNode = m_IntersectNodes->Next; - delete m_IntersectNodes; - m_IntersectNodes = iNode; - } + for (size_t i = 0; i < m_IntersectList.size(); ++i ) + delete m_IntersectList[i]; + m_IntersectList.clear(); } //------------------------------------------------------------------------------ @@ -3071,7 +2916,13 @@ void Clipper::BuildIntersectList(const cInt botY, const cInt topY) Pt.X = TopX(*eNext, botY); else Pt.X = TopX(*e, botY); } - InsertIntersectNode( e, eNext, Pt ); + + IntersectNode * newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.push_back(newNode); + SwapPositionsInSEL(e, eNext); isModified = true; } @@ -3086,43 +2937,55 @@ void Clipper::BuildIntersectList(const cInt botY, const cInt topY) } //------------------------------------------------------------------------------ -void Clipper::InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &Pt) -{ - IntersectNode* newNode = new IntersectNode; - newNode->Edge1 = e1; - newNode->Edge2 = e2; - newNode->Pt = Pt; - newNode->Next = 0; - if( !m_IntersectNodes ) m_IntersectNodes = newNode; - else if(newNode->Pt.Y > m_IntersectNodes->Pt.Y ) - { - newNode->Next = m_IntersectNodes; - m_IntersectNodes = newNode; - } - else - { - IntersectNode* iNode = m_IntersectNodes; - while(iNode->Next && newNode->Pt.Y <= iNode->Next->Pt.Y) - iNode = iNode->Next; - newNode->Next = iNode->Next; - iNode->Next = newNode; - } -} -//------------------------------------------------------------------------------ void Clipper::ProcessIntersectList() { - while( m_IntersectNodes ) + for (size_t i = 0; i < m_IntersectList.size(); ++i) { - IntersectNode* iNode = m_IntersectNodes->Next; + IntersectNode* iNode = m_IntersectList[i]; { - IntersectEdges( m_IntersectNodes->Edge1 , - m_IntersectNodes->Edge2 , m_IntersectNodes->Pt, true); - SwapPositionsInAEL( m_IntersectNodes->Edge1 , m_IntersectNodes->Edge2 ); + IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt, true); + SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); } - delete m_IntersectNodes; - m_IntersectNodes = iNode; + delete iNode; } + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) +{ + return node2->Pt.Y < node1->Pt.Y; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) +{ + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() +{ + //pre-condition: intersections are sorted Bottom-most first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + CopyAELToSEL(); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); + size_t cnt = m_IntersectList.size(); + for (size_t i = 0; i < cnt; ++i) + { + if (!EdgesAdjacent(*m_IntersectList[i])) + { + size_t j = i + 1; + while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; + if (j == cnt) return false; + std::swap(m_IntersectList[i], m_IntersectList[j]); + } + SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); + } + return true; } //------------------------------------------------------------------------------ @@ -3385,7 +3248,7 @@ void Clipper::BuildResult2(PolyTree& polytree) outRec->PolyNd->m_IsOpen = true; polytree.AddChild(*outRec->PolyNd); } - else if (outRec->FirstLeft) + else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); else polytree.AddChild(*outRec->PolyNd); @@ -3406,38 +3269,6 @@ void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) } //------------------------------------------------------------------------------ -inline bool EdgesAdjacent(const IntersectNode &inode) -{ - return (inode.Edge1->NextInSEL == inode.Edge2) || - (inode.Edge1->PrevInSEL == inode.Edge2); -} -//------------------------------------------------------------------------------ - -bool Clipper::FixupIntersectionOrder() -{ - //pre-condition: intersections are sorted Bottom-most (then Left-most) first. - //Now it's crucial that intersections are made only between adjacent edges, - //so to ensure this the order of intersections may need adjusting ... - IntersectNode *inode = m_IntersectNodes; - CopyAELToSEL(); - while (inode) - { - if (!EdgesAdjacent(*inode)) - { - IntersectNode *nextNode = inode->Next; - while (nextNode && !EdgesAdjacent(*nextNode)) - nextNode = nextNode->Next; - if (!nextNode) - return false; - SwapIntersectNodes(*inode, *nextNode); - } - SwapPositionsInSEL(inode->Edge1, inode->Edge2); - inode = inode->Next; - } - return true; -} -//------------------------------------------------------------------------------ - inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) { if (e2.Curr.X == e1.Curr.X) @@ -3618,10 +3449,8 @@ bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, } //------------------------------------------------------------------------------ -bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) +bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) { - OutRec* outRec1 = GetOutRec(j->OutPt1->Idx); - OutRec* outRec2 = GetOutRec(j->OutPt2->Idx); OutPt *op1 = j->OutPt1, *op1b; OutPt *op2 = j->OutPt2, *op2b; @@ -3655,8 +3484,8 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; - p1 = op1; - p2 = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; return true; } else { @@ -3666,8 +3495,8 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; - p1 = op1; - p2 = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; return true; } } @@ -3716,7 +3545,7 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) { Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); } - p1 = op1; p2 = op2; + j->OutPt1 = op1; j->OutPt2 = op2; return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); } else { @@ -3759,8 +3588,8 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; - p1 = op1; - p2 = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; return true; } else { @@ -3770,30 +3599,14 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; - p1 = op1; - p2 = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; return true; } } } //---------------------------------------------------------------------- -bool Poly2ContainsPoly1(OutPt* OutPt1, OutPt* OutPt2, bool UseFullInt64Range) -{ - OutPt* Pt = OutPt1; - //Because the polygons may be touching, we need to find a vertex that - //isn't touching the other polygon ... - if (PointOnPolygon(Pt->Pt, OutPt2, UseFullInt64Range)) - { - Pt = Pt->Next; - while (Pt != OutPt1 && PointOnPolygon(Pt->Pt, OutPt2, UseFullInt64Range)) - Pt = Pt->Next; - if (Pt == OutPt1) return true; - } - return PointInPolygon(Pt->Pt, OutPt2, UseFullInt64Range); -} -//---------------------------------------------------------------------- - void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) { @@ -3802,7 +3615,7 @@ void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) OutRec* outRec = m_PolyOuts[i]; if (outRec->Pts && outRec->FirstLeft == OldOutRec) { - if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts, m_UseFullRange)) + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) outRec->FirstLeft = NewOutRec; } } @@ -3819,14 +3632,22 @@ void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) } //---------------------------------------------------------------------- +static OutRec* ParseFirstLeft(OutRec* FirstLeft) +{ + while (FirstLeft && !FirstLeft->Pts) + FirstLeft = FirstLeft->FirstLeft; + return FirstLeft; +} +//------------------------------------------------------------------------------ + void Clipper::JoinCommonEdges() { for (JoinList::size_type i = 0; i < m_Joins.size(); i++) { - Join* j = m_Joins[i]; + Join* join = m_Joins[i]; - OutRec *outRec1 = GetOutRec(j->OutPt1->Idx); - OutRec *outRec2 = GetOutRec(j->OutPt2->Idx); + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); if (!outRec1->Pts || !outRec2->Pts) continue; @@ -3838,22 +3659,33 @@ void Clipper::JoinCommonEdges() else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; else holeStateRec = GetLowermostRec(outRec1, outRec2); - OutPt *p1, *p2; - if (!JoinPoints(j, p1, p2)) continue; + if (!JoinPoints(join, outRec1, outRec2)) continue; if (outRec1 == outRec2) { //instead of joining two polygons, we've just created a new one by //splitting one polygon into two. - outRec1->Pts = p1; + outRec1->Pts = join->OutPt1; outRec1->BottomPt = 0; outRec2 = CreateOutRec(); - outRec2->Pts = p2; + outRec2->Pts = join->OutPt2; //update all OutRec2.Pts Idx's ... UpdateOutPtIdxs(*outRec2); - if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts, m_UseFullRange)) + //We now need to check every OutRec.FirstLeft pointer. If it points + //to OutRec1 it may need to point to OutRec2 instead ... + if (m_UsingPolyTree) + for (PolyOutList::size_type j = 0; j < m_PolyOuts.size() - 1; j++) + { + OutRec* oRec = m_PolyOuts[j]; + if (!oRec->Pts || ParseFirstLeft(oRec->FirstLeft) != outRec1 || + oRec->IsHole == outRec1->IsHole) continue; + if (Poly2ContainsPoly1(oRec->Pts, join->OutPt2)) + oRec->FirstLeft = outRec2; + } + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) { //outRec2 is contained by outRec1 ... outRec2->IsHole = !outRec1->IsHole; @@ -3865,7 +3697,7 @@ void Clipper::JoinCommonEdges() if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) ReversePolyPtLinks(outRec2->Pts); - } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts, m_UseFullRange)) + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) { //outRec1 is contained by outRec2 ... outRec2->IsHole = outRec1->IsHole; @@ -3907,6 +3739,449 @@ void Clipper::JoinCommonEdges() } } } + +//------------------------------------------------------------------------------ +// ClipperOffset support functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double Dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + Dx *= f; + dy *= f; + return DoublePoint(dy, -Dx); +} + +//------------------------------------------------------------------------------ +// ClipperOffset class +//------------------------------------------------------------------------------ + +ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) +{ + this->MiterLimit = miterLimit; + this->ArcTolerance = arcTolerance; + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +ClipperOffset::~ClipperOffset() +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Clear() +{ + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + delete m_polyNodes.Childs[i]; + m_polyNodes.Childs.clear(); + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) +{ + int highI = (int)path.size() - 1; + if (highI < 0) return; + PolyNode* newNode = new PolyNode(); + newNode->m_jointype = joinType; + newNode->m_endtype = endType; + + //strip duplicate points from path and also get index to the lowest point ... + if (endType == etClosedLine || endType == etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) highI--; + newNode->Contour.reserve(highI + 1); + newNode->Contour.push_back(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode->Contour[j] != path[i]) + { + j++; + newNode->Contour.push_back(path[i]); + if (path[i].Y > newNode->Contour[k].Y || + (path[i].Y == newNode->Contour[k].Y && + path[i].X < newNode->Contour[k].X)) k = j; + } + if ((endType == etClosedPolygon && j < 2) || + (endType != etClosedPolygon && j < 0)) + { + delete newNode; + return; + } + m_polyNodes.AddChild(*newNode); + + //if this path's lowest pt is lower than all the others then update m_lowest + if (endType != etClosedPolygon) return; + if (m_lowest.X < 0) + m_lowest = IntPoint(0, k); + else + { + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; + if (newNode->Contour[k].Y > ip.Y || + (newNode->Contour[k].Y == ip.Y && + newNode->Contour[k].X < ip.X)) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) +{ + for (Paths::size_type i = 0; i < paths.size(); ++i) + AddPath(paths[i], joinType, endType); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::FixOrientations() +{ + //fixup orientations of all closed paths if the orientation of the + //closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon || + (node.m_endtype == etClosedLine && Orientation(node.Contour))) + ReversePath(node.Contour); + } + } else + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) + ReversePath(node.Contour); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(Paths& solution, double delta) +{ + solution.clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + if (solution.size() > 0) solution.erase(solution.begin()); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(PolyTree& solution, double delta) +{ + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + //remove the outer PolyNode rectangle ... + if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) + { + PolyNode* outerNode = solution.Childs[0]; + solution.Childs.reserve(outerNode->ChildCount()); + solution.Childs[0] = outerNode->Childs[0]; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Childs[i]); + } + else + solution.Clear(); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoOffset(double delta) +{ + m_destPolys.clear(); + m_delta = delta; + + //if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (NEAR_ZERO(delta)) + { + m_destPolys.reserve(m_polyNodes.ChildCount()); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.push_back(node.Contour); + } + return; + } + + //see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); + else m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) y = def_arc_tolerance; + else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) + y = std::fabs(delta) * def_arc_tolerance; + else y = ArcTolerance; + //see offset_triginometry2.svg in the documentation folder ... + double steps = pi / std::acos(1 - y / std::fabs(delta)); + if (steps > std::fabs(delta) * pi) + steps = std::fabs(delta) * pi; //ie excessive precision check + m_sin = std::sin(two_pi / steps); + m_cos = std::cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) m_sin = -m_sin; + + m_destPolys.reserve(m_polyNodes.ChildCount() * 2); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + m_srcPoly = node.Contour; + + int len = (int)m_srcPoly.size(); + if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) + continue; + + m_destPoly.clear(); + if (len == 1) + { + if (node.m_jointype == jtRound) + { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= steps; j++) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } + else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + m_destPolys.push_back(m_destPoly); + continue; + } + //build m_normals ... + m_normals.clear(); + m_normals.reserve(len); + for (int j = 0; j < len - 1; ++j) + m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) + m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.push_back(DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == etClosedPolygon) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else if (node.m_endtype == etClosedLine) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + m_destPoly.clear(); + //re-build m_normals ... + DoublePoint n = m_normals[len -1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else + { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == etOpenButt) + { + int j = len - 1; + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + //re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); + + if (node.m_endtype == etOpenButt) + { + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + k = 1; + m_sinA = 0; + if (node.m_endtype == etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.push_back(m_destPoly); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) +{ + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (m_sinA < 0.00005 && m_sinA > -0.00005) return; + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(m_srcPoly[j]); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + else + switch (jointype) + { + case jtMiter: + { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); + break; + } + case jtSquare: DoSquare(j, k); break; + case jtRound: DoRound(j, k); break; + } + k = j; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoSquare(int j, int k) +{ + double dx = std::tan(std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoMiter(int j, int k, double r) +{ + double q = m_delta / r; + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoRound(int j, int k) +{ + double a = std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + int steps = (int)Round(m_StepsPerRad * std::fabs(a)); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); +} + +//------------------------------------------------------------------------------ +// Miscellaneous public functions //------------------------------------------------------------------------------ void Clipper::DoSimplePolygons() @@ -3936,14 +4211,14 @@ void Clipper::DoSimplePolygons() OutRec* outrec2 = CreateOutRec(); outrec2->Pts = op2; UpdateOutPtIdxs(*outrec2); - if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts, m_UseFullRange)) + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) { //OutRec2 is contained by OutRec1 ... outrec2->IsHole = !outrec->IsHole; outrec2->FirstLeft = outrec; } else - if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts, m_UseFullRange)) + if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) { //OutRec1 is contained by OutRec2 ... outrec2->IsHole = outrec->IsHole; @@ -3978,363 +4253,6 @@ void ReversePaths(Paths& p) for (Paths::size_type i = 0; i < p.size(); ++i) ReversePath(p[i]); } - -//------------------------------------------------------------------------------ -// OffsetPolygon functions ... -//------------------------------------------------------------------------------ - -DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) -{ - if(pt2.X == pt1.X && pt2.Y == pt1.Y) - return DoublePoint(0, 0); - - double Dx = (double)(pt2.X - pt1.X); - double dy = (double)(pt2.Y - pt1.Y); - double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); - Dx *= f; - dy *= f; - return DoublePoint(dy, -Dx); -} - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -class OffsetBuilder -{ -private: - const Paths& m_p; - Path* m_curr_poly; - std::vector normals; - double m_delta, m_sinA, m_sin, m_cos; - double m_miterLim, m_Steps360; - size_t m_i, m_j, m_k; - static const int buffLength = 128; - -public: - -OffsetBuilder(const Paths& in_polys, Paths& out_polys, - double Delta, JoinType jointype, EndType endtype, double limit): m_p(in_polys) -{ - //precondition: &out_polys != &in_polys - - if (NEAR_ZERO(Delta)) {out_polys = in_polys; return;} - //we can't shrink a polyline so ... - if (endtype != etClosed && Delta < 0) Delta = -Delta; - m_delta = Delta; - - if (jointype == jtMiter) - { - //m_miterLim: see offset_triginometry.svg in the documentation folder ... - if (limit > 2) m_miterLim = 2/(limit*limit); - else m_miterLim = 0.5; - if (endtype == etRound) limit = 0.25; - } - - if (jointype == jtRound || endtype == etRound) - { - if (limit <= 0) limit = 0.25; - else if (limit > std::fabs(Delta)*0.25) limit = std::fabs(Delta)*0.25; - //m_Steps360: see offset_triginometry2.svg in the documentation folder ... - m_Steps360 = pi / acos(1 - limit / std::fabs(Delta)); - m_sin = std::sin(2 * pi / m_Steps360); - m_cos = std::cos(2 * pi / m_Steps360); - m_Steps360 /= pi * 2; - if (Delta < 0) m_sin = -m_sin; - } - - out_polys.clear(); - out_polys.resize(m_p.size()); - for (m_i = 0; m_i < m_p.size(); m_i++) - { - size_t len = m_p[m_i].size(); - - if (len == 0 || (len < 3 && Delta <= 0)) continue; - - if (len == 1) - { - if (jointype == jtRound) - { - double X = 1.0, Y = 0.0; - for (cInt j = 1; j <= Round(m_Steps360 * 2 * pi); j++) - { - AddPoint(IntPoint( - Round(m_p[m_i][0].X + X * Delta), - Round(m_p[m_i][0].Y + Y * Delta))); - double X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - } else - { - double X = -1.0, Y = -1.0; - for (int j = 0; j < 4; ++j) - { - AddPoint(IntPoint( Round(m_p[m_i][0].X + X * Delta), - Round(m_p[m_i][0].Y + Y * Delta))); - if (X < 0) X = 1; - else if (Y < 0) Y = 1; - else X = -1; - } - } - continue; - } - - //build normals ... - normals.clear(); - normals.resize(len); - for (m_j = 0; m_j < len -1; ++m_j) - normals[m_j] = GetUnitNormal(m_p[m_i][m_j], m_p[m_i][m_j +1]); - if (endtype == etClosed) - normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]); - else //is open polyline - normals[len-1] = normals[len-2]; - - m_curr_poly = &out_polys[m_i]; - m_curr_poly->reserve(len); - - if (endtype == etClosed) - { - m_k = len -1; - for (m_j = 0; m_j < len; ++m_j) - OffsetPoint(jointype); - } - else //is open polyline - { - //offset the polyline going forward ... - m_k = 0; - for (m_j = 1; m_j < len -1; ++m_j) - OffsetPoint(jointype); - - //handle the end (butt, round or square) ... - IntPoint pt1; - if (endtype == etButt) - { - m_j = len - 1; - pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - AddPoint(pt1); - pt1 = IntPoint(Round(m_p[m_i][m_j].X - normals[m_j].X * m_delta), - Round(m_p[m_i][m_j].Y - normals[m_j].Y * m_delta)); - AddPoint(pt1); - } - else - { - m_j = len - 1; - m_k = len - 2; - m_sinA = 0; - normals[m_j].X = -normals[m_j].X; - normals[m_j].Y = -normals[m_j].Y; - if (endtype == etSquare) - DoSquare(); - else - DoRound(); - } - - //re-build Normals ... - for (int j = len - 1; j > 0; --j) - { - normals[j].X = -normals[j - 1].X; - normals[j].Y = -normals[j - 1].Y; - } - normals[0].X = -normals[1].X; - normals[0].Y = -normals[1].Y; - - //offset the polyline going backward ... - m_k = len -1; - for (m_j = m_k - 1; m_j > 0; --m_j) - OffsetPoint(jointype); - - //finally handle the start (butt, round or square) ... - if (endtype == etButt) - { - pt1 = IntPoint(Round(m_p[m_i][0].X - normals[0].X * m_delta), - Round(m_p[m_i][0].Y - normals[0].Y * m_delta)); - AddPoint(pt1); - pt1 = IntPoint(Round(m_p[m_i][0].X + normals[0].X * m_delta), - Round(m_p[m_i][0].Y + normals[0].Y * m_delta)); - AddPoint(pt1); - } else - { - m_sinA = 0; - m_k = 1; - if (endtype == etSquare) - DoSquare(); - else - DoRound(); - } - } - } - - //and clean up untidy corners using Clipper ... - Clipper clpr; - clpr.AddPaths(out_polys, ptSubject, true); - if (Delta > 0) - { - if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive)) - out_polys.clear(); - } - else - { - IntRect r = clpr.GetBounds(); - Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPath(outer, ptSubject, true); - clpr.ReverseSolution(true); - if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative)) - out_polys.erase(out_polys.begin()); - else - out_polys.clear(); - } -} -//------------------------------------------------------------------------------ - -private: - -void OffsetPoint(JoinType jointype) -{ - m_sinA = (normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y); - if (std::fabs(m_sinA) < 0.00005) return; //ie collinear - else if (m_sinA > 1.0) m_sinA = 1.0; - else if (m_sinA < -1.0) m_sinA = -1.0; - - if (m_sinA * m_delta < 0) - { - AddPoint(IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), - Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta))); - AddPoint(m_p[m_i][m_j]); - AddPoint(IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta))); - } - else - switch (jointype) - { - case jtMiter: - { - double r = 1 + (normals[m_j].X*normals[m_k].X + - normals[m_j].Y*normals[m_k].Y); - if (r >= m_miterLim) DoMiter(r); else DoSquare(); - break; - } - case jtSquare: DoSquare(); break; - case jtRound: DoRound(); break; - } - m_k = m_j; -} -//------------------------------------------------------------------------------ - -void AddPoint(const IntPoint& Pt) -{ - if (m_curr_poly->size() == m_curr_poly->capacity()) - m_curr_poly->reserve(m_curr_poly->capacity() + buffLength); - m_curr_poly->push_back(Pt); -} -//------------------------------------------------------------------------------ - -void DoSquare() -{ - double Dx = std::tan(std::atan2(m_sinA, - normals[m_k].X * normals[m_j].X + normals[m_k].Y * normals[m_j].Y)/4); - AddPoint(IntPoint( - Round(m_p[m_i][m_j].X + m_delta * (normals[m_k].X - normals[m_k].Y *Dx)), - Round(m_p[m_i][m_j].Y + m_delta * (normals[m_k].Y + normals[m_k].X *Dx)))); - AddPoint(IntPoint( - Round(m_p[m_i][m_j].X + m_delta * (normals[m_j].X + normals[m_j].Y *Dx)), - Round(m_p[m_i][m_j].Y + m_delta * (normals[m_j].Y - normals[m_j].X *Dx)))); -} -//------------------------------------------------------------------------------ - -void DoMiter(double r) -{ - double q = m_delta / r; - AddPoint(IntPoint(Round(m_p[m_i][m_j].X + (normals[m_k].X + normals[m_j].X) * q), - Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); -} -//------------------------------------------------------------------------------ - -void DoRound() -{ - double a = std::atan2(m_sinA, - normals[m_k].X * normals[m_j].X + normals[m_k].Y * normals[m_j].Y); - int steps = (int)Round(m_Steps360 * std::fabs(a)); - - double X = normals[m_k].X, Y = normals[m_k].Y, X2; - for (int i = 0; i < steps; ++i) - { - AddPoint(IntPoint( - Round(m_p[m_i][m_j].X + X * m_delta), - Round(m_p[m_i][m_j].Y + Y * m_delta))); - X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - AddPoint(IntPoint( - Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta))); -} -//-------------------------------------------------------------------------- - -}; //end PolyOffsetBuilder - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -void StripDupsAndGetBotPt(Path& in_path, Path& out_path, bool closed, IntPoint* botPt) -{ - botPt = 0; - size_t len = in_path.size(); - if (closed) - while (len > 0 && (in_path[0] == in_path[len -1])) len--; - if (len == 0) return; - out_path.resize(len); - int j = 0; - out_path[0] = in_path[0]; - botPt = &out_path[0]; - for (size_t i = 1; i < len; ++i) - if (in_path[i] != out_path[j]) - { - j++; - out_path[j] = in_path[i]; - if (out_path[j].Y > botPt->Y) - botPt = &out_path[j]; - else if ((out_path[j].Y == botPt->Y) && out_path[j].X < botPt->X) - botPt = &out_path[j]; - } - j++; - if (j < 2 || (closed && (j == 2))) j = 0; - out_path.resize(j); -} -//------------------------------------------------------------------------------ - -void OffsetPaths(const Paths &in_polys, Paths &out_polys, - double delta, JoinType jointype, EndType endtype, double limit) -{ - //just in case in_polys == &out_polys ... - Paths inPolys = Paths(in_polys); - out_polys.clear(); - out_polys.resize(inPolys.size()); - - IntPoint *botPt = 0, *pt = 0; - int botIdx = -1; - for (size_t i = 0; i < in_polys.size(); ++i) - { - StripDupsAndGetBotPt(inPolys[i], out_polys[i], endtype == etClosed, pt); - if (botPt) - if (!botPt || pt->Y > botPt->Y || (pt->Y == botPt->Y && pt->X < botPt->X)) - { - botPt = pt; - botIdx = i; - } - - } - if (endtype == etClosed && botIdx >= 0 && !Orientation(inPolys[botIdx])) - ReversePaths(inPolys); - - OffsetBuilder(inPolys, out_polys, delta, jointype, endtype, limit); -} //------------------------------------------------------------------------------ void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) @@ -4360,27 +4278,27 @@ inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) } //------------------------------------------------------------------------------ -DoublePoint ClosestPointOnLine(const IntPoint& Pt, const IntPoint& linePt1, const IntPoint& linePt2) +double DistanceFromLineSqrd( + const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) { - double Dx = ((double)linePt2.X - linePt1.X); - double dy = ((double)linePt2.Y - linePt1.Y); - if (Dx == 0 && dy == 0) - return DoublePoint((double)linePt1.X, (double)linePt1.Y); - double q = ((Pt.X-linePt1.X)*Dx + (Pt.Y-linePt1.Y)*dy) / (Dx*Dx + dy*dy); - return DoublePoint( - (1-q)*linePt1.X + q*linePt2.X, - (1-q)*linePt1.Y + q*linePt2.Y); + //The equation of a line in general form (Ax + By + C = 0) + //given 2 points (x¹,y¹) & (x²,y²) is ... + //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 + //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = double(ln1.Y - ln2.Y); + double B = double(ln2.X - ln1.X); + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); } -//------------------------------------------------------------------------------ +//--------------------------------------------------------------------------- bool SlopesNearCollinear(const IntPoint& pt1, const IntPoint& pt2, const IntPoint& pt3, double distSqrd) { - if (DistanceSqrd(pt1, pt2) > DistanceSqrd(pt1, pt3)) return false; - DoublePoint cpol = ClosestPointOnLine(pt2, pt1, pt3); - double Dx = pt2.X - cpol.X; - double dy = pt2.Y - cpol.Y; - return (Dx*Dx + dy*dy) < distSqrd; + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; } //------------------------------------------------------------------------------ @@ -4392,35 +4310,73 @@ bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) } //------------------------------------------------------------------------------ +OutPt* ExcludeOp(OutPt* op) +{ + OutPt* result = op->Prev; + result->Next = op->Next; + op->Next->Prev = result; + result->Idx = 0; + return result; +} +//------------------------------------------------------------------------------ + void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) { //distance = proximity in units/pixels below which vertices //will be stripped. Default ~= sqrt(2). - int highI = in_poly.size() -1; - double distSqrd = distance * distance; - while (highI > 0 && PointsAreClose(in_poly[highI], in_poly[0], distSqrd)) highI--; - if (highI < 2) { out_poly.clear(); return; } - if (&in_poly != &out_poly) - out_poly.resize(highI + 1); - - IntPoint Pt = in_poly[highI]; - int i = 0, k = 0; - for (;;) + size_t size = in_poly.size(); + + if (size == 0) { - while (i < highI && PointsAreClose(Pt, in_poly[i+1], distSqrd)) i+=2; - int i2 = i; - while (i < highI && (PointsAreClose(in_poly[i], in_poly[i+1], distSqrd) || - SlopesNearCollinear(Pt, in_poly[i], in_poly[i+1], distSqrd))) i++; - if (i >= highI) break; - else if (i != i2) continue; - Pt = in_poly[i++]; - out_poly[k++] = Pt; + out_poly.clear(); + return; } - if (i <= highI) out_poly[k++] = in_poly[i]; - if (k > 2 && SlopesNearCollinear(out_poly[k -2], out_poly[k -1], out_poly[0], distSqrd)) k--; - if (k < 3) out_poly.clear(); - else if (k <= highI) out_poly.resize(k); + + OutPt* outPts = new OutPt[size]; + for (size_t i = 0; i < size; ++i) + { + outPts[i].Pt = in_poly[i]; + outPts[i].Next = &outPts[(i + 1) % size]; + outPts[i].Next->Prev = &outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt* op = &outPts[0]; + while (op->Idx == 0 && op->Next != op->Prev) + { + if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) + { + ExcludeOp(op->Next); + op = ExcludeOp(op); + size -= 2; + } + else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else + { + op->Idx = 1; + op = op->Next; + } + } + + if (size < 3) size = 0; + out_poly.resize(size); + for (size_t i = 0; i < size; ++i) + { + out_poly[i] = op->Pt; + op = op->Next; + } + delete [] outPts; } //------------------------------------------------------------------------------ @@ -4443,7 +4399,7 @@ void CleanPolygons(Paths& polys, double distance) } //------------------------------------------------------------------------------ -void Minkowki(const Path& poly, const Path& path, +void Minkowski(const Path& poly, const Path& path, Paths& solution, bool isSum, bool isClosed) { int delta = (isClosed ? 1 : 0); @@ -4491,15 +4447,15 @@ void Minkowki(const Path& poly, const Path& path, } //------------------------------------------------------------------------------ -void MinkowkiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed) +void MinkowskiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed) { - Minkowki(poly, path, solution, true, isClosed); + Minkowski(poly, path, solution, true, isClosed); } //------------------------------------------------------------------------------ -void MinkowkiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed) +void MinkowskiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed) { - Minkowki(poly, path, solution, false, isClosed); + Minkowski(poly, path, solution, false, isClosed); } //------------------------------------------------------------------------------ @@ -4573,45 +4529,16 @@ std::ostream& operator <<(std::ostream &s, const Paths &p) //------------------------------------------------------------------------------ #ifdef use_deprecated -bool ClipperBase::AddPolygon(const Path &pg, PolyType PolyTyp) + +void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType_ endtype, double limit) { - return AddPath(pg, PolyTyp, true); + ClipperOffset co(limit, limit); + co.AddPaths(in_polys, jointype, (EndType)endtype); + co.Execute(out_polys, delta); } //------------------------------------------------------------------------------ -bool ClipperBase::AddPolygons(const Paths &ppg, PolyType PolyTyp) -{ - bool result = false; - for (Paths::size_type i = 0; i < ppg.size(); ++i) - if (AddPath(ppg[i], PolyTyp, true)) result = true; - return result; -} -//------------------------------------------------------------------------------ - -void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype, double limit, bool autoFix) -{ - OffsetPaths(in_polys, out_polys, delta, jointype, etClosed, limit); -} -//------------------------------------------------------------------------------ - -void PolyTreeToPolygons(const PolyTree& polytree, Paths& paths) -{ - PolyTreeToPaths(polytree, paths); -} -//------------------------------------------------------------------------------ - -void ReversePolygon(Path& p) -{ - std::reverse(p.begin(), p.end()); -} -//------------------------------------------------------------------------------ - -void ReversePolygons(Paths& p) -{ - for (Paths::size_type i = 0; i < p.size(); ++i) - ReversePolygon(p[i]); -} #endif diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp index eea704b9f..269954565 100755 --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -1,8 +1,8 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.0.0 * -* Date : 30 October 2013 * +* Version : 6.1.2 * +* Date : 15 December 2013 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2013 * * * @@ -34,7 +34,7 @@ #ifndef clipper_hpp #define clipper_hpp -#define CLIPPER_VERSION "6.0.0" +#define CLIPPER_VERSION "6.1.2" //use_int32: When enabled 32bit ints are used instead of 64bit ints. This //improve performance but coordinate values are limited to the range +/- 46340 @@ -46,9 +46,8 @@ //use_lines: Enables line clipping. Adds a very minor cost to performance. #define use_lines -//When enabled, code developed with earlier versions of Clipper -//(ie prior to ver 6) should compile without changes. -//In a future update, this compatability code will be removed. +//use_deprecated: Enables support for the obsolete OffsetPaths() function +//which has been replace with the ClipperOffset class. //#define use_deprecated #include @@ -108,12 +107,6 @@ std::ostream& operator <<(std::ostream &s, const IntPoint &p); std::ostream& operator <<(std::ostream &s, const Path &p); std::ostream& operator <<(std::ostream &s, const Paths &p); -#ifdef use_deprecated -typedef signed long long long64; //backward compatibility only -typedef Path Polygon; -typedef Paths Polygons; -#endif - struct DoublePoint { double X; @@ -127,6 +120,13 @@ struct DoublePoint typedef void (*TZFillCallback)(IntPoint& z1, IntPoint& z2, IntPoint& pt); #endif +enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; +enum JoinType {jtSquare, jtRound, jtMiter}; +enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; +#ifdef use_deprecated + enum EndType_ {etClosed, etButt = 2, etSquare, etRound}; +#endif + class PolyNode; typedef std::vector< PolyNode* > PolyNodes; @@ -142,11 +142,14 @@ public: bool IsOpen() const; int ChildCount() const; private: - bool m_IsOpen; - PolyNode* GetNextSiblingUp() const; unsigned Index; //node index in Parent.Childs + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + PolyNode* GetNextSiblingUp() const; void AddChild(PolyNode& child); friend class Clipper; //to access Index + friend class ClipperOffset; }; class PolyTree: public PolyNode @@ -161,24 +164,14 @@ private: friend class Clipper; //to access AllNodes }; -enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; -enum JoinType {jtSquare, jtRound, jtMiter}; -enum EndType {etClosed, etButt, etSquare, etRound}; - bool Orientation(const Path &poly); double Area(const Path &poly); #ifdef use_deprecated - void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype = jtSquare, double limit = 0, bool autoFix = true); - void PolyTreeToPolygons(const PolyTree& polytree, Paths& paths); - void ReversePolygon(Path& p); - void ReversePolygons(Paths& p); + void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType_ endtype, double limit = 0); #endif -void OffsetPaths(const Paths &in_polys, Paths &out_polys, - double delta, JoinType jointype, EndType endtype, double limit = 0); - void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); @@ -188,8 +181,8 @@ void CleanPolygon(Path& poly, double distance = 1.415); void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); void CleanPolygons(Paths& polys, double distance = 1.415); -void MinkowkiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed); -void MinkowkiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed); +void MinkowskiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed); +void MinkowskiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); @@ -215,6 +208,8 @@ struct Join; typedef std::vector < OutRec* > PolyOutList; typedef std::vector < TEdge* > EdgeList; typedef std::vector < Join* > JoinList; +typedef std::vector < IntersectNode* > IntersectList; + //------------------------------------------------------------------------------ @@ -228,12 +223,6 @@ public: virtual ~ClipperBase(); bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); - -#ifdef use_deprecated - bool AddPolygon(const Path &pg, PolyType PolyTyp); - bool AddPolygons(const Paths &ppg, PolyType PolyTyp); -#endif - virtual void Clear(); IntRect GetBounds(); bool PreserveCollinear() {return m_PreserveCollinear;}; @@ -243,6 +232,7 @@ protected: TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); void PopLocalMinima(); virtual void Reset(); + TEdge* ProcessBound(TEdge* E, bool IsClockwise); void InsertLocalMinima(LocalMinima *newLm); void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed); TEdge* DescendToMin(TEdge *&E); @@ -285,11 +275,11 @@ private: PolyOutList m_PolyOuts; JoinList m_Joins; JoinList m_GhostJoins; + IntersectList m_IntersectList; ClipType m_ClipType; std::set< cInt, std::greater > m_Scanbeam; TEdge *m_ActiveEdges; TEdge *m_SortedEdges; - IntersectNode *m_IntersectNodes; bool m_ExecuteLocked; PolyFillType m_ClipFillType; PolyFillType m_SubjFillType; @@ -330,7 +320,6 @@ private: void DisposeAllOutRecs(); void DisposeOutRec(PolyOutList::size_type index); bool ProcessIntersections(const cInt botY, const cInt topY); - void InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt); void BuildIntersectList(const cInt botY, const cInt topY); void ProcessIntersectList(); void ProcessEdgesAtTopOfScanbeam(const cInt topY); @@ -341,12 +330,13 @@ private: bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outrec); bool IsHole(TEdge *e); + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); void FixHoleLinkage(OutRec &outrec); void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); void ClearJoins(); void ClearGhostJoins(); void AddGhostJoin(OutPt *op, const IntPoint offPt); - bool JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2); + bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); void JoinCommonEdges(); void DoSimplePolygons(); void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); @@ -357,6 +347,37 @@ private: }; //------------------------------------------------------------------------------ +class ClipperOffset +{ +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + ~ClipperOffset(); + void AddPath(const Path& path, JoinType joinType, EndType endType); + void AddPaths(const Paths& paths, JoinType joinType, EndType endType); + void Execute(Paths& solution, double delta); + void Execute(PolyTree& solution, double delta); + void Clear(); + double MiterLimit; + double ArcTolerance; +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + void DoOffset(double delta); + void OffsetPoint(int j, int& k, JoinType jointype); + void DoSquare(int j, int k); + void DoMiter(int j, int k, double r); + void DoRound(int j, int k); +}; +//------------------------------------------------------------------------------ + class clipperException : public std::exception { public: diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index cf2c7844d..84ea29755 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -43,10 +43,10 @@ my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); [95, 95], [205, 95], ], [ + [145, 145], [145, 155], [155, 155], [155, 145], - [145, 145], ] ], 'offset_ex'; } @@ -58,10 +58,10 @@ my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); [97, 97], [203, 97], ], [ + [143, 143], [143, 157], [157, 157], [157, 143], - [143, 143], ] ], 'offset2_ex'; } @@ -78,7 +78,7 @@ my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); [5000000, 5000000], ]); my $result = Slic3r::Geometry::Clipper::offset2_ex([ @$expolygon2 ], -1, +1); - is_deeply $result->[0]->pp, $expolygon2->pp, 'offset2_ex'; + is $result->[0]->area, $expolygon2->area, 'offset2_ex'; } {