From ab873279fc6888f83e6cc9658b5d66c2a8b6adc2 Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Thu, 5 Dec 2013 15:30:26 +0100 Subject: [PATCH 01/20] Updated to clipper library 5.1.4 Added functions: "setDebug": set debug level, debug sends messages to console "orientation": returns orientation of polygon "minimum": shrink polygon, normally a triangle to put placemark in the middle "clip": clip two polygons, all clipper cliptypes are supported "clean": interface to clipper "CleanPolygons" function "fixOrientation": fix orientation for inner and outer polygons "simplify": interface to clipper "SimplifyPolygons" function --- .gitignore | 2 +- binding.gyp | 13 +- lib/bindings.js | 2 +- lib/clipper.js | 2 +- src/clipper.cpp | 1493 +++++++++++++++++++++++++---------------------- src/clipper.hpp | 105 ++-- src/init.cc | 631 +++++++++++++++++--- 7 files changed, 1433 insertions(+), 815 deletions(-) mode change 100755 => 100644 src/clipper.cpp mode change 100755 => 100644 src/clipper.hpp diff --git a/.gitignore b/.gitignore index 0be51af..0a453a4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ node_modules # Vim cruft *.swp -*.un~ \ No newline at end of file +*.un~ diff --git a/binding.gyp b/binding.gyp index d9c8feb..c2bd265 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,16 +2,23 @@ "targets": [ { "target_name": "clipper", - "sources": [ + "sources": [ "src/init.cc" ], - "cflags!": [ "-fno-exceptions" ], - "cflags_cc!": [ "-fno-exceptions" ], + 'cflags': [ '-fexceptions' ], + 'cflags_cc': [ '-fexceptions' ], + "conditions": [ ['OS=="mac"', { 'xcode_settings': { 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' #-fno-exceptions } + }], + [ 'OS=="linux"', { + 'target_defaults': { + 'cflags': [ '-fexceptions' ], + 'cflags_cc': [ '-fexceptions' ], + } }] ] } diff --git a/lib/bindings.js b/lib/bindings.js index bc8b4d2..0136194 100644 --- a/lib/bindings.js +++ b/lib/bindings.js @@ -2,4 +2,4 @@ try { module.exports = require('../build/Release/clipper'); } catch (e) { throw e; -} \ No newline at end of file +} diff --git a/lib/clipper.js b/lib/clipper.js index a9ae994..b14d924 100644 --- a/lib/clipper.js +++ b/lib/clipper.js @@ -1,2 +1,2 @@ var clipper = require('./bindings'); -module.exports = clipper; \ No newline at end of file +module.exports = clipper; diff --git a/src/clipper.cpp b/src/clipper.cpp old mode 100755 new mode 100644 index e97b8fc..060ab6c --- a/src/clipper.cpp +++ b/src/clipper.cpp @@ -1,10 +1,10 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 4.8.6 * -* Date : 11 August 2012 * +* Version : 5.1.4 * +* Date : 24 March 2013 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2012 * +* Copyright : Angus Johnson 2010-2013 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * @@ -26,7 +26,7 @@ * Paper no. DETC2005-85513 pp. 565-575 * * ASME 2005 International Design Engineering Technical Conferences * * and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24–28, 2005 , Long Beach, California, USA * +* September 24-28, 2005 , Long Beach, California, USA * * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * * * *******************************************************************************/ @@ -63,8 +63,90 @@ inline long64 Abs(long64 val) { return val < 0 ? -val : val; } + +//------------------------------------------------------------------------------ +// PolyTree methods ... //------------------------------------------------------------------------------ +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() const +{ + if (Childs.size() > 0) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const +{ + return AllNodes.size(); +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode(): Childs(), Parent(0), Index(0) +{ +} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const +{ + return Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() const +{ + if (Childs.size() > 0) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() const +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} + //------------------------------------------------------------------------------ // Int128 class (enables safe math on signed 64bit integers) // eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 @@ -77,18 +159,24 @@ class Int128 { public: + ulong64 lo; + long64 hi; + Int128(long64 _lo = 0) { - lo = _lo; - if (lo < 0) hi = -1; else hi = 0; + lo = (ulong64)_lo; + if (_lo < 0) hi = -1; else hi = 0; } - Int128(const Int128 &val): hi(val.hi), lo(val.lo){} + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} + + Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + long64 operator = (const long64 &val) { - lo = val; - if (lo < 0) hi = -1; else hi = 0; + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; return val; } @@ -124,7 +212,7 @@ class Int128 { hi += rhs.hi; lo += rhs.lo; - if (ulong64(lo) < ulong64(rhs.lo)) hi++; + if (lo < rhs.lo) hi++; return *this; } @@ -137,25 +225,10 @@ class Int128 Int128& operator -= (const Int128 &rhs) { - Int128 tmp(rhs); - Negate(tmp); - *this += tmp; + *this += -rhs; return *this; } - //Int128 operator -() const - //{ - // Int128 result(*this); - // if (result.lo == 0) { - // if (result.hi != 0) result.hi = -1; - // } - // else { - // result.lo = -result.lo; - // result.hi = ~result.hi; - // } - // return result; - //} - Int128 operator - (const Int128 &rhs) const { Int128 result(*this); @@ -163,149 +236,114 @@ class Int128 return result; } - Int128 operator * (const Int128 &rhs) const + Int128 operator-() const //unary negation { - if ( !(hi == 0 || hi == -1) || !(rhs.hi == 0 || rhs.hi == -1)) - throw "Int128 operator*: overflow error"; - bool negate = (hi < 0) != (rhs.hi < 0); - - Int128 tmp(*this); - if (tmp.hi < 0) Negate(tmp); - ulong64 int1Hi = ulong64(tmp.lo) >> 32; - ulong64 int1Lo = ulong64(tmp.lo & 0xFFFFFFFF); - - tmp = rhs; - if (tmp.hi < 0) Negate(tmp); - ulong64 int2Hi = ulong64(tmp.lo) >> 32; - ulong64 int2Lo = ulong64(tmp.lo & 0xFFFFFFFF); - - //nb: see comments in clipper.pas - ulong64 a = int1Hi * int2Hi; - ulong64 b = int1Lo * int2Lo; - ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; - - tmp.hi = long64(a + (c >> 32)); - tmp.lo = long64(c << 32); - tmp.lo += long64(b); - if (ulong64(tmp.lo) < b) tmp.hi++; - if (negate) Negate(tmp); - return tmp; + if (lo == 0) + return Int128(-hi,0); + else + return Int128(~hi,~lo +1); } Int128 operator/ (const Int128 &rhs) const { if (rhs.lo == 0 && rhs.hi == 0) throw "Int128 operator/: divide by zero"; + bool negate = (rhs.hi < 0) != (hi < 0); - Int128 result(*this), denom(rhs); - if (result.hi < 0) Negate(result); - if (denom.hi < 0) Negate(denom); - if (denom > result) return Int128(0); //result is only a fraction of 1 - Negate(denom); - - Int128 p(0); - for (int i = 0; i < 128; ++i) + Int128 dividend = *this; + Int128 divisor = rhs; + if (dividend.hi < 0) dividend = -dividend; + if (divisor.hi < 0) divisor = -divisor; + + if (divisor < dividend) { - p.hi = p.hi << 1; - if (p.lo < 0) p.hi++; - p.lo = long64(p.lo) << 1; - if (result.hi < 0) p.lo++; - result.hi = result.hi << 1; - if (result.lo < 0) result.hi++; - result.lo = long64(result.lo) << 1; - Int128 p2(p); - p += denom; - if (p.hi < 0) p = p2; - else result.lo++; + Int128 result = Int128(0); + Int128 cntr = Int128(1); + while (divisor.hi >= 0 && !(divisor > dividend)) + { + divisor.hi <<= 1; + if ((long64)divisor.lo < 0) divisor.hi++; + divisor.lo <<= 1; + + cntr.hi <<= 1; + if ((long64)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi = (ulong64)divisor.hi >> 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(dividend < divisor)) + { + dividend -= divisor; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + } + if (negate) result = -result; + return result; } - if (negate) Negate(result); - return result; + else if (rhs.hi == this->hi && rhs.lo == this->lo) + return Int128(1); + else + return Int128(0); } double AsDouble() const { const double shift64 = 18446744073709551616.0; //2^64 - const double bit64 = 9223372036854775808.0; if (hi < 0) { - Int128 tmp(*this); - Negate(tmp); - if (tmp.lo < 0) - return (double)tmp.lo - bit64 - tmp.hi * shift64; - else - return -(double)tmp.lo - tmp.hi * shift64; + if (lo == 0) return (double)hi * shift64; + else return -(double)(~lo + ~hi * shift64); } - else if (lo < 0) - return -(double)lo + bit64 + hi * shift64; else - return (double)lo + (double)hi * shift64; - } - - //for bug testing ... - //std::string AsString() const - //{ - // std::string result; - // unsigned char r = 0; - // Int128 tmp(0), val(*this); - // if (hi < 0) Negate(val); - // result.resize(50); - // std::string::size_type i = result.size() -1; - // while (val.hi != 0 || val.lo != 0) - // { - // Div10(val, tmp, r); - // result[i--] = char('0' + r); - // val = tmp; - // } - // if (hi < 0) result[i--] = '-'; - // result.erase(0,i+1); - // if (result.size() == 0) result = "0"; - // return result; - //} + return (double)(lo + hi * shift64); + } +}; -private: - long64 hi; - long64 lo; +Int128 Int128Mul (long64 lhs, long64 rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); - static void Negate(Int128 &val) - { - if (val.lo == 0) { - if (val.hi != 0) val.hi = -val.hi;; - } - else { - val.lo = -val.lo; - val.hi = ~val.hi; - } - } + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); - //debugging only ... - //void Div10(const Int128 val, Int128& result, unsigned char & remainder) const - //{ - // remainder = 0; - // result = 0; - // for (int i = 63; i >= 0; --i) - // { - // if ((val.hi & ((long64)1 << i)) != 0) - // remainder = char((remainder * 2) + 1); else - // remainder *= char(2); - // if (remainder >= 10) - // { - // result.hi += ((long64)1 << i); - // remainder -= char(10); - // } - // } - // for (int i = 63; i >= 0; --i) - // { - // if ((val.lo & ((long64)1 << i)) != 0) - // remainder = char((remainder * 2) + 1); else - // remainder *= char(2); - // if (remainder >= 10) - // { - // result.lo += ((long64)1 << i); - // remainder -= char(10); - // } - // } - //} -}; + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +} //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ @@ -326,39 +364,7 @@ bool FullRangeNeeded(const Polygon &pts) bool Orientation(const Polygon &poly) { - int highI = (int)poly.size() -1; - if (highI < 2) return false; - - int j = 0, jplus, jminus; - for (int i = 0; i <= highI; ++i) - { - if (poly[i].Y < poly[j].Y) continue; - if ((poly[i].Y > poly[j].Y || poly[i].X < poly[j].X)) j = i; - }; - if (j == highI) jplus = 0; - else jplus = j +1; - if (j == 0) jminus = highI; - else jminus = j -1; - - IntPoint vec1, vec2; - //get cross product of vectors of the edges adjacent to highest point ... - vec1.X = poly[j].X - poly[jminus].X; - vec1.Y = poly[j].Y - poly[jminus].Y; - vec2.X = poly[jplus].X - poly[j].X; - vec2.Y = poly[jplus].Y - poly[j].Y; - - if (Abs(vec1.X) > loRange || Abs(vec1.Y) > loRange || - Abs(vec2.X) > loRange || Abs(vec2.Y) > loRange) - { - if (Abs(vec1.X) > hiRange || Abs(vec1.Y) > hiRange || - Abs(vec2.X) > hiRange || Abs(vec2.Y) > hiRange) - throw "Coordinate exceeds range bounds."; - Int128 cross = Int128(vec1.X) * Int128(vec2.Y) - - Int128(vec2.X) * Int128(vec1.Y); - return cross >= 0; - } - else - return (vec1.X * vec2.Y - vec2.X * vec1.Y) >= 0; + return Area(poly) >= 0; } //------------------------------------------------------------------------------ @@ -368,44 +374,6 @@ inline bool PointsEqual( const IntPoint &pt1, const IntPoint &pt2) } //------------------------------------------------------------------------------ -bool Orientation(OutRec *outRec, bool UseFullInt64Range) -{ - //first make sure bottomPt is correctly assigned ... - OutPt *opBottom = outRec->pts, *op = outRec->pts->next; - while (op != outRec->pts) - { - if (op->pt.Y >= opBottom->pt.Y) - { - if (op->pt.Y > opBottom->pt.Y || op->pt.X < opBottom->pt.X) - opBottom = op; - } - op = op->next; - } - outRec->bottomPt = opBottom; - opBottom->idx = outRec->idx; - - op = opBottom; - //find vertices either side of bottomPt (skipping duplicate points) .... - OutPt *opPrev = op->prev; - OutPt *opNext = op->next; - while (op != opPrev && PointsEqual(op->pt, opPrev->pt)) - opPrev = opPrev->prev; - while (op != opNext && PointsEqual(op->pt, opNext->pt)) - opNext = opNext->next; - - IntPoint ip1, ip2; - ip1.X = op->pt.X - opPrev->pt.X; - ip1.Y = op->pt.Y - opPrev->pt.Y; - ip2.X = opNext->pt.X - op->pt.X; - ip2.Y = opNext->pt.Y - op->pt.Y; - - if (UseFullInt64Range) - return Int128(ip1.X) * Int128(ip2.Y) - Int128(ip2.X) * Int128(ip1.Y) >= 0; - else - return (ip1.X * ip2.Y - ip2.X * ip1.Y) >= 0; -} -//------------------------------------------------------------------------------ - double Area(const Polygon &poly) { int highI = (int)poly.size() -1; @@ -413,20 +381,18 @@ double Area(const Polygon &poly) if (FullRangeNeeded(poly)) { Int128 a; - a = (Int128(poly[highI].X) * Int128(poly[0].Y)) - - Int128(poly[0].X) * Int128(poly[highI].Y); - for (int i = 0; i < highI; ++i) - a += Int128(poly[i].X) * Int128(poly[i+1].Y) - - Int128(poly[i+1].X) * Int128(poly[i].Y); + a = Int128Mul(poly[highI].X + poly[0].X, poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += Int128Mul(poly[i - 1].X + poly[i].X, poly[i].Y - poly[i -1].Y); return a.AsDouble() / 2; } else { double a; - a = (double)poly[highI].X * poly[0].Y - (double)poly[0].X * poly[highI].Y; - for (int i = 0; i < highI; ++i) - a += (double)poly[i].X * poly[i+1].Y - (double)poly[i+1].X * poly[i].Y; - return a/2; + 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; } } //------------------------------------------------------------------------------ @@ -434,11 +400,11 @@ double Area(const Polygon &poly) double Area(const OutRec &outRec, bool UseFullInt64Range) { OutPt *op = outRec.pts; + if (!op) return 0; if (UseFullInt64Range) { Int128 a(0); do { - a += (Int128(op->prev->pt.X) * Int128(op->pt.Y)) - - Int128(op->pt.X) * Int128(op->prev->pt.Y); + a += Int128Mul(op->pt.X + op->prev->pt.X, op->prev->pt.Y - op->pt.Y); op = op->next; } while (op != outRec.pts); return a.AsDouble() / 2; @@ -447,10 +413,10 @@ double Area(const OutRec &outRec, bool UseFullInt64Range) { double a = 0; do { - a += (op->prev->pt.X * op->pt.Y) - (op->pt.X * op->prev->pt.Y); + a = a + (op->pt.X + op->prev->pt.X) * (op->prev->pt.Y - op->pt.Y); op = op->next; } while (op != outRec.pts); - return a/2; + return a / 2; } } //------------------------------------------------------------------------------ @@ -477,8 +443,9 @@ bool PointInPolygon(const IntPoint &pt, OutPt *pp, bool UseFullInt64Range) { if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && - Int128(pt.X - pp2->pt.X) < (Int128(pp2->prev->pt.X - pp2->pt.X) * - Int128(pt.Y - pp2->pt.Y)) / Int128(pp2->prev->pt.Y - pp2->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; } @@ -503,10 +470,8 @@ bool PointInPolygon(const IntPoint &pt, OutPt *pp, bool UseFullInt64Range) bool SlopesEqual(TEdge &e1, TEdge &e2, bool UseFullInt64Range) { if (UseFullInt64Range) - return Int128(e1.ytop - e1.ybot) * Int128(e2.xtop - e2.xbot) == - Int128(e1.xtop - e1.xbot) * Int128(e2.ytop - e2.ybot); - else return (e1.ytop - e1.ybot)*(e2.xtop - e2.xbot) == - (e1.xtop - e1.xbot)*(e2.ytop - e2.ybot); + return Int128Mul(e1.deltaY, e2.deltaX) == Int128Mul(e1.deltaX, e2.deltaY); + else return e1.deltaY * e2.deltaX == e1.deltaX * e2.deltaY; } //------------------------------------------------------------------------------ @@ -514,8 +479,7 @@ bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, bool UseFullInt64Range) { if (UseFullInt64Range) - return Int128(pt1.Y-pt2.Y) * Int128(pt2.X-pt3.X) == - Int128(pt1.X-pt2.X) * Int128(pt2.Y-pt3.Y); + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); else return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); } //------------------------------------------------------------------------------ @@ -524,8 +488,7 @@ bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) { if (UseFullInt64Range) - return Int128(pt1.Y-pt2.Y) * Int128(pt3.X-pt4.X) == - Int128(pt1.X-pt2.X) * Int128(pt3.Y-pt4.Y); + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); else return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); } //------------------------------------------------------------------------------ @@ -533,14 +496,17 @@ bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, double GetDx(const IntPoint pt1, const IntPoint pt2) { return (pt1.Y == pt2.Y) ? - HORIZONTAL : (double)(pt2.X - pt1.X) / (double)(pt2.Y - pt1.Y); + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); } //--------------------------------------------------------------------------- void SetDx(TEdge &e) { - if (e.ybot == e.ytop) e.dx = HORIZONTAL; - else e.dx = (double)(e.xtop - e.xbot) / (double)(e.ytop - e.ybot); + e.deltaX = (e.xtop - e.xbot); + e.deltaY = (e.ytop - e.ybot); + + if (e.deltaY == 0) e.dx = HORIZONTAL; + else e.dx = (double)(e.deltaX) / e.deltaY; } //--------------------------------------------------------------------------- @@ -562,8 +528,7 @@ void SwapPolyIndexes(TEdge &edge1, TEdge &edge2) inline long64 Round(double val) { - return (val < 0) ? - static_cast(val - 0.5) : static_cast(val + 0.5); + return (val < 0) ? static_cast(val - 0.5) : static_cast(val + 0.5); } //------------------------------------------------------------------------------ @@ -574,75 +539,81 @@ long64 TopX(TEdge &edge, const long64 currentY) } //------------------------------------------------------------------------------ -long64 TopX(const IntPoint pt1, const IntPoint pt2, const long64 currentY) -{ - //preconditions: pt1.Y <> pt2.Y and pt1.Y > pt2.Y - if (currentY >= pt1.Y) return pt1.X; - else if (currentY == pt2.Y) return pt2.X; - else if (pt1.X == pt2.X) return pt1.X; - else - { - double q = (double)(pt1.X-pt2.X)/(double)(pt1.Y-pt2.Y); - return Round(pt1.X + (currentY - pt1.Y) *q); - } -} -//------------------------------------------------------------------------------ - bool IntersectPoint(TEdge &edge1, TEdge &edge2, IntPoint &ip, bool UseFullInt64Range) { double b1, b2; - if (SlopesEqual(edge1, edge2, UseFullInt64Range)) return false; + if (SlopesEqual(edge1, edge2, UseFullInt64Range)) + { + if (edge2.ybot > edge1.ybot) ip.Y = edge2.ybot; + else ip.Y = edge1.ybot; + return false; + } else if (NEAR_ZERO(edge1.dx)) { ip.X = edge1.xbot; if (NEAR_EQUAL(edge2.dx, HORIZONTAL)) - { ip.Y = edge2.ybot; - } else + else { - b2 = edge2.ybot - (edge2.xbot/edge2.dx); - ip.Y = Round(ip.X/edge2.dx + b2); + b2 = edge2.ybot - (edge2.xbot / edge2.dx); + ip.Y = Round(ip.X / edge2.dx + b2); } } else if (NEAR_ZERO(edge2.dx)) { ip.X = edge2.xbot; if (NEAR_EQUAL(edge1.dx, HORIZONTAL)) - { ip.Y = edge1.ybot; - } else + else { - b1 = edge1.ybot - (edge1.xbot/edge1.dx); - ip.Y = Round(ip.X/edge1.dx + b1); + b1 = edge1.ybot - (edge1.xbot / edge1.dx); + ip.Y = Round(ip.X / edge1.dx + b1); } - } else + } + else { b1 = edge1.xbot - edge1.ybot * edge1.dx; b2 = edge2.xbot - edge2.ybot * edge2.dx; - b2 = (b2-b1)/(edge1.dx - edge2.dx); - ip.Y = Round(b2); - ip.X = Round(edge1.dx * b2 + b1); + double q = (b2-b1) / (edge1.dx - edge2.dx); + ip.Y = Round(q); + if (std::fabs(edge1.dx) < std::fabs(edge2.dx)) + ip.X = Round(edge1.dx * q + b1); + else + ip.X = Round(edge2.dx * q + b2); } - return - //can be *so close* to the top of one edge that the rounded Y equals one ytop ... - (ip.Y == edge1.ytop && ip.Y >= edge2.ytop && edge1.tmpX > edge2.tmpX) || - (ip.Y == edge2.ytop && ip.Y >= edge1.ytop && edge1.tmpX > edge2.tmpX) || - (ip.Y > edge1.ytop && ip.Y > edge2.ytop); + if (ip.Y < edge1.ytop || ip.Y < edge2.ytop) + { + if (edge1.ytop > edge2.ytop) + { + ip.X = edge1.xtop; + ip.Y = edge1.ytop; + return TopX(edge2, edge1.ytop) < edge1.xtop; + } + else + { + ip.X = edge2.xtop; + ip.Y = edge2.ytop; + return TopX(edge1, edge2.ytop) > edge2.xtop; + } + } + else + return true; } //------------------------------------------------------------------------------ -void ReversePolyPtLinks(OutPt &pp) +void ReversePolyPtLinks(OutPt *pp) { + if (!pp) return; OutPt *pp1, *pp2; - pp1 = &pp; + pp1 = pp; do { pp2 = pp1->next; pp1->next = pp1->prev; pp1->prev = pp2; pp1 = pp2; - } while( pp1 != &pp ); + } while( pp1 != pp ); } //------------------------------------------------------------------------------ @@ -712,7 +683,7 @@ bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) { //precondition: segments are colinear. - if ( pt1a.Y == pt1b.Y || Abs((pt1a.X - pt1b.X)/(pt1a.Y - pt1b.Y)) > 1 ) + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) { if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); @@ -787,7 +758,8 @@ OutPt* GetBottomPt(OutPt *pp) } //------------------------------------------------------------------------------ -bool FindSegment(OutPt* &pp, IntPoint &pt1, IntPoint &pt2) +bool FindSegment(OutPt* &pp, bool UseFullInt64Range, + IntPoint &pt1, IntPoint &pt2) { //outPt1 & outPt2 => the overlap segment (if the function returns true) if (!pp) return false; @@ -795,8 +767,8 @@ bool FindSegment(OutPt* &pp, IntPoint &pt1, IntPoint &pt2) IntPoint pt1a = pt1, pt2a = pt2; do { - if (SlopesEqual(pt1a, pt2a, pp->pt, pp->prev->pt, true) && - SlopesEqual(pt1a, pt2a, pp->pt, true) && + if (SlopesEqual(pt1a, pt2a, pp->pt, pp->prev->pt, UseFullInt64Range) && + SlopesEqual(pt1a, pt2a, pp->pt, UseFullInt64Range) && GetOverlapSegment(pt1a, pt2a, pp->pt, pp->prev->pt, pt1, pt2)) return true; pp = pp->next; @@ -858,6 +830,7 @@ bool ClipperBase::AddPolygon( const Polygon &pg, PolyType polyType) { int len = (int)pg.size(); if (len < 3) return false; + Polygon p(len); p[0] = pg[0]; int j = 0; @@ -1197,89 +1170,46 @@ bool Clipper::Execute(ClipType clipType, Polygons &solution, m_SubjFillType = subjFillType; m_ClipFillType = clipFillType; m_ClipType = clipType; - bool succeeded = ExecuteInternal(false); + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); if (succeeded) BuildResult(solution); m_ExecuteLocked = false; return succeeded; } //------------------------------------------------------------------------------ -bool Clipper::Execute(ClipType clipType, ExPolygons &solution, +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, PolyFillType subjFillType, PolyFillType clipFillType) { if( m_ExecuteLocked ) return false; m_ExecuteLocked = true; - solution.resize(0); m_SubjFillType = subjFillType; m_ClipFillType = clipFillType; m_ClipType = clipType; - bool succeeded = ExecuteInternal(true); - if (succeeded) BuildResultEx(solution); + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); m_ExecuteLocked = false; return succeeded; } //------------------------------------------------------------------------------ -bool PolySort(OutRec *or1, OutRec *or2) -{ - if (or1 == or2) return false; - if (!or1->pts || !or2->pts) - { - if (or1->pts != or2->pts) - { - return or1->pts ? true : false; - } - else return false; - } - int i1, i2; - if (or1->isHole) - i1 = or1->FirstLeft->idx; else - i1 = or1->idx; - if (or2->isHole) - i2 = or2->FirstLeft->idx; else - i2 = or2->idx; - int result = i1 - i2; - if (result == 0 && (or1->isHole != or2->isHole)) - { - return or1->isHole ? false : true; - } - else return result < 0; -} -//------------------------------------------------------------------------------ - -OutRec* FindAppendLinkEnd(OutRec *outRec) -{ - while (outRec->AppendLink) outRec = outRec->AppendLink; - return outRec; -} -//------------------------------------------------------------------------------ - -void Clipper::FixHoleLinkage(OutRec *outRec) +void Clipper::FixHoleLinkage(OutRec &outRec) { - OutRec *tmp; - if (outRec->bottomPt) - tmp = m_PolyOuts[outRec->bottomPt->idx]->FirstLeft; - else - tmp = outRec->FirstLeft; - if (outRec == tmp) throw clipperException("HoleLinkage error"); + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outRec.FirstLeft || + (outRec.isHole != outRec.FirstLeft->isHole && + outRec.FirstLeft->pts)) return; - if (tmp) - { - if (tmp->AppendLink) tmp = FindAppendLinkEnd(tmp); - if (tmp == outRec) tmp = 0; - else if (tmp->isHole) - { - FixHoleLinkage(tmp); - tmp = tmp->FirstLeft; - } - } - outRec->FirstLeft = tmp; - if (!tmp) outRec->isHole = false; - outRec->AppendLink = 0; + OutRec* orfl = outRec.FirstLeft; + while (orfl && ((orfl->isHole == outRec.isHole) || !orfl->pts)) + orfl = orfl->FirstLeft; + outRec.FirstLeft = orfl; } //------------------------------------------------------------------------------ -bool Clipper::ExecuteInternal(bool fixHoleLinkages) +bool Clipper::ExecuteInternal() { bool succeeded; try { @@ -1310,23 +1240,12 @@ bool Clipper::ExecuteInternal(bool fixHoleLinkages) if (!outRec->pts) continue; FixupOutPolygon(*outRec); if (!outRec->pts) continue; - if (outRec->isHole && fixHoleLinkages) FixHoleLinkage(outRec); - - if (outRec->bottomPt == outRec->bottomFlag && - (Orientation(outRec, m_UseFullRange) != (Area(*outRec, m_UseFullRange) > 0))) - { - DisposeBottomPt(*outRec); - FixupOutPolygon(*outRec); - }; - if (outRec->isHole == - (m_ReverseOutput ^ Orientation(outRec, m_UseFullRange))) - ReversePolyPtLinks(*outRec->pts); + if ((outRec->isHole ^ m_ReverseOutput) == (Area(*outRec, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec->pts); } - JoinCommonEdges(fixHoleLinkages); - if (fixHoleLinkages) - std::sort(m_PolyOuts.begin(), m_PolyOuts.end(), PolySort); + if (m_Joins.size() > 0) JoinCommonEdges(); } ClearJoins(); @@ -1615,14 +1534,10 @@ void Clipper::CopyAELToSEL() { TEdge* e = m_ActiveEdges; m_SortedEdges = e; - if (!m_ActiveEdges) return; - m_SortedEdges->prevInSEL = 0; - e = e->nextInAEL; while ( e ) { e->prevInSEL = e->prevInAEL; - e->prevInSEL->nextInSEL = e; - e->nextInSEL = 0; + e->nextInSEL = e->nextInAEL; e = e->nextInAEL; } } @@ -1670,7 +1585,7 @@ void Clipper::ClearHorzJoins() } //------------------------------------------------------------------------------ -void Clipper::InsertLocalMinimaIntoAEL( const long64 botY) +void Clipper::InsertLocalMinimaIntoAEL(const long64 botY) { while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) { @@ -1707,21 +1622,18 @@ void Clipper::InsertLocalMinimaIntoAEL( const long64 botY) AddLocalMinPoly( lb, rb, IntPoint(lb->xcurr, m_CurrentLM->Y) ); //if any output polygons share an edge, they'll need joining later ... - if (rb->outIdx >= 0) + if (rb->outIdx >= 0 && NEAR_EQUAL(rb->dx, HORIZONTAL)) { - if (NEAR_EQUAL(rb->dx, HORIZONTAL)) + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) { - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) - { - IntPoint pt, pt2; //returned by GetOverlapSegment() but unused here. - HorzJoinRec* hj = m_HorizJoins[i]; - //if horizontals rb and hj.edge overlap, flag for joining later ... - if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), - IntPoint(hj->edge->xtop, hj->edge->ytop), - IntPoint(rb->xbot, rb->ybot), - IntPoint(rb->xtop, rb->ytop), pt, pt2)) - AddJoin(hj->edge, rb, hj->savedIdx); - } + IntPoint pt, pt2; //returned by GetOverlapSegment() but unused here. + HorzJoinRec* hj = m_HorizJoins[i]; + //if horizontals rb and hj.edge overlap, flag for joining later ... + if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), + IntPoint(hj->edge->xtop, hj->edge->ytop), + IntPoint(rb->xbot, rb->ybot), + IntPoint(rb->xtop, rb->ytop), pt, pt2)) + AddJoin(hj->edge, rb, hj->savedIdx); } } @@ -1774,7 +1686,7 @@ void Clipper::DeleteFromSEL(TEdge *e) //------------------------------------------------------------------------------ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, - const IntPoint &pt, IntersectProtects protects) + const IntPoint &pt, const IntersectProtects protects) { //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before //e2 in AEL except when e1 is being inserted at the intersection point ... @@ -1854,17 +1766,11 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, } else if ( e1Contributing ) { - if ((e2Wc == 0 || e2Wc == 1) && - (m_ClipType != ctIntersection || - e2->polyType == ptSubject || (e2->windCnt2 != 0))) - DoEdge1(e1, e2, pt); + if (e2Wc == 0 || e2Wc == 1) DoEdge1(e1, e2, pt); } else if ( e2contributing ) { - if ((e1Wc == 0 || e1Wc == 1) && - (m_ClipType != ctIntersection || - e1->polyType == ptSubject || (e1->windCnt2 != 0))) - DoEdge2(e1, e2, pt); + if (e1Wc == 0 || e1Wc == 1) DoEdge2(e1, e2, pt); } else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) @@ -1974,9 +1880,12 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) OutRec *outRec2 = m_PolyOuts[e2->outIdx]; OutRec *holeStateRec; - if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; - else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; - else holeStateRec = GetLowermostRec(outRec1, outRec2); + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); OutPt* p1_lft = outRec1->pts; OutPt* p1_rt = p1_lft->prev; @@ -1990,7 +1899,7 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) if( e2->side == esLeft ) { //z y x a b c - ReversePolyPtLinks(*p2_lft); + ReversePolyPtLinks(p2_lft); p2_lft->next = p1_lft; p1_lft->prev = p2_lft; p1_rt->next = p2_rt; @@ -2011,7 +1920,7 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) if( e2->side == esRight ) { //a b c z y x - ReversePolyPtLinks( *p2_lft ); + ReversePolyPtLinks(p2_lft); p1_rt->next = p2_rt; p2_rt->prev = p1_rt; p2_lft->next = p1_lft; @@ -2037,7 +1946,9 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) } outRec2->pts = 0; outRec2->bottomPt = 0; - outRec2->AppendLink = outRec1; + + outRec2->FirstLeft = outRec1; + int OKIdx = e1->outIdx; int ObsoleteIdx = e2->outIdx; @@ -2076,28 +1987,13 @@ OutRec* Clipper::CreateOutRec() OutRec* result = new OutRec; result->isHole = false; result->FirstLeft = 0; - result->AppendLink = 0; result->pts = 0; result->bottomPt = 0; - result->sides = esNeither; - result->bottomFlag = 0; - + result->polyNode = 0; return result; } //------------------------------------------------------------------------------ -void Clipper::DisposeBottomPt(OutRec &outRec) -{ - OutPt* next = outRec.bottomPt->next; - OutPt* prev = outRec.bottomPt->prev; - if (outRec.pts == outRec.bottomPt) outRec.pts = next; - delete outRec.bottomPt; - next->prev = prev; - prev->next = next; - outRec.bottomPt = next; -} -//------------------------------------------------------------------------------ - void Clipper::AddOutPt(TEdge *e, const IntPoint &pt) { bool ToFront = (e->side == esLeft); @@ -2122,52 +2018,6 @@ void Clipper::AddOutPt(TEdge *e, const IntPoint &pt) if ((ToFront && PointsEqual(pt, op->pt)) || (!ToFront && PointsEqual(pt, op->prev->pt))) return; - if ((e->side | outRec->sides) != outRec->sides) - { - //check for 'rounding' artefacts ... - if (outRec->sides == esNeither && pt.Y == op->pt.Y) - if (ToFront) - { - if (pt.X == op->pt.X +1) return; //ie wrong side of bottomPt - } - else if (pt.X == op->pt.X -1) return; //ie wrong side of bottomPt - - outRec->sides = (EdgeSide)(outRec->sides | e->side); - if (outRec->sides == esBoth) - { - //A vertex from each side has now been added. - //Vertices of one side of an output polygon are quite commonly close to - //or even 'touching' edges of the other side of the output polygon. - //Very occasionally vertices from one side can 'cross' an edge on the - //the other side. The distance 'crossed' is always less that a unit - //and is purely an artefact of coordinate rounding. Nevertheless, this - //results in very tiny self-intersections. Because of the way - //orientation is calculated, even tiny self-intersections can cause - //the Orientation function to return the wrong result. Therefore, it's - //important to ensure that any self-intersections close to BottomPt are - //detected and removed before orientation is assigned. - - OutPt *opBot, *op2; - if (ToFront) - { - opBot = outRec->pts; - op2 = opBot->next; //op2 == right side - if (opBot->pt.Y != op2->pt.Y && opBot->pt.Y != pt.Y && - ((opBot->pt.X - pt.X)/(opBot->pt.Y - pt.Y) < - (opBot->pt.X - op2->pt.X)/(opBot->pt.Y - op2->pt.Y))) - outRec->bottomFlag = opBot; - } else - { - opBot = outRec->pts->prev; - op2 = opBot->prev; //op2 == left side - if (opBot->pt.Y != op2->pt.Y && opBot->pt.Y != pt.Y && - ((opBot->pt.X - pt.X)/(opBot->pt.Y - pt.Y) > - (opBot->pt.X - op2->pt.X)/(opBot->pt.Y - op2->pt.Y))) - outRec->bottomFlag = opBot; - } - } - } - OutPt* op2 = new OutPt; op2->pt = pt; op2->idx = outRec->idx; @@ -2236,9 +2086,6 @@ TEdge *GetMaximaPair(TEdge *e) void Clipper::SwapPositionsInAEL(TEdge *edge1, TEdge *edge2) { - if( !edge1->nextInAEL && !edge1->prevInAEL ) return; - if( !edge2->nextInAEL && !edge2->prevInAEL ) return; - if( edge1->nextInAEL == edge2 ) { TEdge* next = edge2->nextInAEL; @@ -2356,28 +2203,28 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) TEdge* e = GetNextInAEL( horzEdge , dir ); while( e ) { - TEdge* eNext = GetNextInAEL( e, dir ); - - if (eMaxPair || - ((dir == dLeftToRight) && (e->xcurr <= horzRight)) || - ((dir == dRightToLeft) && (e->xcurr >= horzLeft))) + if ( e->xcurr == horzEdge->xtop && !eMaxPair ) { - //ok, so far it looks like we're still in range of the horizontal edge - if ( e->xcurr == horzEdge->xtop && !eMaxPair ) + if (SlopesEqual(*e, *horzEdge->nextInLML, m_UseFullRange)) { - if (SlopesEqual(*e, *horzEdge->nextInLML, m_UseFullRange)) - { - //if output polygons share an edge, they'll need joining later ... - if (horzEdge->outIdx >= 0 && e->outIdx >= 0) - AddJoin(horzEdge->nextInLML, e, horzEdge->outIdx); - break; //we've reached the end of the horizontal line - } - else if (e->dx < horzEdge->nextInLML->dx) - //we really have got to the end of the intermediate horz edge so quit. - //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. - break; + //if output polygons share an edge, they'll need joining later ... + if (horzEdge->outIdx >= 0 && e->outIdx >= 0) + AddJoin(horzEdge->nextInLML, e, horzEdge->outIdx); + break; //we've reached the end of the horizontal line } + else if (e->dx < horzEdge->nextInLML->dx) + //we really have got to the end of the intermediate horz edge so quit. + //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. + break; + } + + TEdge* eNext = GetNextInAEL( e, dir ); + if (eMaxPair || + ((dir == dLeftToRight) && (e->xcurr < horzRight)) || + ((dir == dRightToLeft) && (e->xcurr > horzLeft))) + { + //so far we're still in range of the horizontal edge if( e == eMaxPair ) { //horzEdge is evidently a maxima horizontal and we've arrived at its end. @@ -2413,8 +2260,8 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) } SwapPositionsInAEL( horzEdge, e ); } - else if( (dir == dLeftToRight && e->xcurr > horzRight && m_SortedEdges) || - (dir == dRightToLeft && e->xcurr < horzLeft && m_SortedEdges) ) break; + else if( (dir == dLeftToRight && e->xcurr >= horzRight) || + (dir == dRightToLeft && e->xcurr <= horzLeft) ) break; e = eNext; } //end while @@ -2463,7 +2310,7 @@ bool Clipper::ProcessIntersections(const long64 botY, const long64 topY) try { BuildIntersectList(botY, topY); if ( !m_IntersectNodes) return true; - if ( FixupIntersections() ) ProcessIntersectList(); + if ( FixupIntersectionOrder() ) ProcessIntersectList(); else return false; } catch(...) { @@ -2492,16 +2339,12 @@ void Clipper::BuildIntersectList(const long64 botY, const long64 topY) //prepare for sorting ... TEdge* e = m_ActiveEdges; - e->tmpX = TopX( *e, topY ); m_SortedEdges = e; - m_SortedEdges->prevInSEL = 0; - e = e->nextInAEL; while( e ) { e->prevInSEL = e->prevInAEL; - e->prevInSEL->nextInSEL = e; - e->nextInSEL = 0; - e->tmpX = TopX( *e, topY ); + e->nextInSEL = e->nextInAEL; + e->xcurr = TopX( *e, topY ); e = e->nextInAEL; } @@ -2515,9 +2358,10 @@ void Clipper::BuildIntersectList(const long64 botY, const long64 topY) { TEdge *eNext = e->nextInSEL; IntPoint pt; - if(e->tmpX > eNext->tmpX && - IntersectPoint(*e, *eNext, pt, m_UseFullRange)) + if(e->xcurr > eNext->xcurr) { + if (!IntersectPoint(*e, *eNext, pt, m_UseFullRange) && e->xcurr > eNext->xcurr +1) + throw clipperException("Intersection error"); if (pt.Y > botY) { pt.Y = botY; @@ -2537,7 +2381,7 @@ void Clipper::BuildIntersectList(const long64 botY, const long64 topY) } //------------------------------------------------------------------------------ -bool ProcessParam1BeforeParam2(IntersectNode &node1, IntersectNode &node2) +bool ProcessParam1BeforeParam2(const IntersectNode &node1, const IntersectNode &node2) { bool result; if (node1.pt.Y == node2.pt.Y) @@ -2607,7 +2451,8 @@ void Clipper::DoMaxima(TEdge *e, long64 topY) { if (!eNext) throw clipperException("DoMaxima error"); IntersectEdges( e, eNext, IntPoint(X, topY), ipBoth ); - eNext = eNext->nextInAEL; + SwapPositionsInAEL(e, eNext); + eNext = e->nextInAEL; } if( e->outIdx < 0 && eMaxPair->outIdx < 0 ) { @@ -2632,10 +2477,10 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const long64 topY) if( IsMaxima(e, topY) && !NEAR_EQUAL(GetMaximaPair(e)->dx, HORIZONTAL) ) { //'e' might be removed from AEL, as may any following edges so ... - TEdge* ePrior = e->prevInAEL; + TEdge* ePrev = e->prevInAEL; DoMaxima(e, topY); - if( !ePrior ) e = m_ActiveEdges; - else e = ePrior->nextInAEL; + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->nextInAEL; } else { @@ -2684,25 +2529,23 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const long64 topY) UpdateEdgeIntoAEL(e); //if output polygons share an edge, they'll need joining later ... - if (e->outIdx >= 0 && e->prevInAEL && e->prevInAEL->outIdx >= 0 && - e->prevInAEL->xcurr == e->xbot && e->prevInAEL->ycurr == e->ybot && - SlopesEqual(IntPoint(e->xbot,e->ybot), IntPoint(e->xtop, e->ytop), - IntPoint(e->xbot,e->ybot), - IntPoint(e->prevInAEL->xtop, e->prevInAEL->ytop), m_UseFullRange)) + TEdge* ePrev = e->prevInAEL; + TEdge* eNext = e->nextInAEL; + if (ePrev && ePrev->xcurr == e->xbot && + ePrev->ycurr == e->ybot && e->outIdx >= 0 && + ePrev->outIdx >= 0 && ePrev->ycurr > ePrev->ytop && + SlopesEqual(*e, *ePrev, m_UseFullRange)) { - AddOutPt(e->prevInAEL, IntPoint(e->xbot, e->ybot)); - AddJoin(e, e->prevInAEL); + AddOutPt(ePrev, IntPoint(e->xbot, e->ybot)); + AddJoin(e, ePrev); } - else if (e->outIdx >= 0 && e->nextInAEL && e->nextInAEL->outIdx >= 0 && - e->nextInAEL->ycurr > e->nextInAEL->ytop && - e->nextInAEL->ycurr <= e->nextInAEL->ybot && - e->nextInAEL->xcurr == e->xbot && e->nextInAEL->ycurr == e->ybot && - SlopesEqual(IntPoint(e->xbot,e->ybot), IntPoint(e->xtop, e->ytop), - IntPoint(e->xbot,e->ybot), - IntPoint(e->nextInAEL->xtop, e->nextInAEL->ytop), m_UseFullRange)) + else if (eNext && eNext->xcurr == e->xbot && + eNext->ycurr == e->ybot && e->outIdx >= 0 && + eNext->outIdx >= 0 && eNext->ycurr > eNext->ytop && + SlopesEqual(*e, *eNext, m_UseFullRange)) { - AddOutPt(e->nextInAEL, IntPoint(e->xbot, e->ybot)); - AddJoin(e, e->nextInAEL); + AddOutPt(eNext, IntPoint(e->xbot, e->ybot)); + AddJoin(e, eNext); } } e = e->nextInAEL; @@ -2757,61 +2600,77 @@ void Clipper::FixupOutPolygon(OutRec &outRec) void Clipper::BuildResult(Polygons &polys) { - int k = 0; - polys.resize(m_PolyOuts.size()); + polys.reserve(m_PolyOuts.size()); for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { if (m_PolyOuts[i]->pts) { - Polygon* pg = &polys[k]; - pg->clear(); + Polygon pg; OutPt* p = m_PolyOuts[i]->pts; do { - pg->push_back(p->pt); - p = p->next; + pg.push_back(p->pt); + p = p->prev; } while (p != m_PolyOuts[i]->pts); - //make sure each polygon has at least 3 vertices ... - if (pg->size() < 3) pg->clear(); else k++; + if (pg.size() > 2) + polys.push_back(pg); } } - polys.resize(k); } //------------------------------------------------------------------------------ -void Clipper::BuildResultEx(ExPolygons &polys) +int PointCount(OutPt *pts) { - PolyOutList::size_type i = 0; - int k = 0; - polys.resize(0); - polys.reserve(m_PolyOuts.size()); - while (i < m_PolyOuts.size() && m_PolyOuts[i]->pts) - { - ExPolygon epg; - OutPt* p = m_PolyOuts[i]->pts; - do { - epg.outer.push_back(p->pt); - p = p->next; - } while (p != m_PolyOuts[i]->pts); - i++; - //make sure polygons have at least 3 vertices ... - if (epg.outer.size() < 3) continue; - while (i < m_PolyOuts.size() - && m_PolyOuts[i]->pts && m_PolyOuts[i]->isHole) + if (!pts) return 0; + int result = 0; + OutPt* p = pts; + do { - Polygon pg; - p = m_PolyOuts[i]->pts; - do { - pg.push_back(p->pt); + result++; p = p->next; - } while (p != m_PolyOuts[i]->pts); - epg.holes.push_back(pg); - i++; } - polys.push_back(epg); - k++; - } - polys.resize(k); + while (p != pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->pts); + if (cnt < 3) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->polyNode = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->pts; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->pt); + op = op->prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->polyNode) continue; + if (outRec->FirstLeft) + outRec->FirstLeft->polyNode->AddChild(*outRec->polyNode); + else + polytree.AddChild(*outRec->polyNode); + } } //------------------------------------------------------------------------------ @@ -2831,7 +2690,7 @@ void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) } //------------------------------------------------------------------------------ -bool Clipper::FixupIntersections() +bool Clipper::FixupIntersectionOrder() { if ( !m_IntersectNodes->next ) return true; @@ -2846,8 +2705,8 @@ bool Clipper::FixupIntersections() else if (e1->nextInSEL == int1->edge2) e2 = e1->nextInSEL; else { - //The current intersection is out of order, so try and swap it with - //a subsequent intersection ... + //The current intersection (Int1) is out of order (since it doesn't + //contain adjacent edges), so swap it with a subsequent intersection ... while (int2) { if (int2->edge1->nextInSEL == int2->edge2 || @@ -2856,7 +2715,8 @@ bool Clipper::FixupIntersections() } if ( !int2 ) return false; //oops!!! - //found an intersect node that can be swapped ... + //found an intersect node (Int2) that does contain adjacent edges, + //so prepare to process it before Int1 ... SwapIntersectNodes(*int1, *int2); e1 = int1->edge1; e2 = int1->edge2; @@ -2931,101 +2791,162 @@ void Clipper::DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt) } //---------------------------------------------------------------------- -void Clipper::CheckHoleLinkages1(OutRec *outRec1, OutRec *outRec2) +bool Clipper::JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2) +{ + OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; + OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; + if (!outRec1 || !outRec2) return false; + OutPt *pp1a = outRec1->pts; + OutPt *pp2a = outRec2->pts; + IntPoint pt1 = j->pt2a, pt2 = j->pt2b; + IntPoint pt3 = j->pt1a, pt4 = j->pt1b; + if (!FindSegment(pp1a, m_UseFullRange, pt1, pt2)) return false; + if (outRec1 == outRec2) + { + //we're searching the same polygon for overlapping segments so + //segment 2 mustn't be the same as segment 1 ... + pp2a = pp1a->next; + if (!FindSegment(pp2a, m_UseFullRange, pt3, pt4) || (pp2a == pp1a)) + return false; + } + else if (!FindSegment(pp2a, m_UseFullRange, pt3, pt4)) return false; + + if (!GetOverlapSegment(pt1, pt2, pt3, pt4, pt1, pt2)) return false; + + OutPt *p3, *p4, *prev = pp1a->prev; + //get p1 & p2 polypts - the overlap start & endpoints on poly1 + if (PointsEqual(pp1a->pt, pt1)) p1 = pp1a; + else if (PointsEqual(prev->pt, pt1)) p1 = prev; + else p1 = InsertPolyPtBetween(pp1a, prev, pt1); + + if (PointsEqual(pp1a->pt, pt2)) p2 = pp1a; + else if (PointsEqual(prev->pt, pt2)) p2 = prev; + else if ((p1 == pp1a) || (p1 == prev)) + p2 = InsertPolyPtBetween(pp1a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp1a->pt, p1->pt, pt2)) + p2 = InsertPolyPtBetween(pp1a, p1, pt2); else + p2 = InsertPolyPtBetween(p1, prev, pt2); + + //get p3 & p4 polypts - the overlap start & endpoints on poly2 + prev = pp2a->prev; + if (PointsEqual(pp2a->pt, pt1)) p3 = pp2a; + else if (PointsEqual(prev->pt, pt1)) p3 = prev; + else p3 = InsertPolyPtBetween(pp2a, prev, pt1); + + if (PointsEqual(pp2a->pt, pt2)) p4 = pp2a; + else if (PointsEqual(prev->pt, pt2)) p4 = prev; + else if ((p3 == pp2a) || (p3 == prev)) + p4 = InsertPolyPtBetween(pp2a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp2a->pt, p3->pt, pt2)) + p4 = InsertPolyPtBetween(pp2a, p3, pt2); else + p4 = InsertPolyPtBetween(p3, prev, pt2); + + //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ... + if (p1->next == p2 && p3->prev == p4) + { + p1->next = p3; + p3->prev = p1; + p2->prev = p4; + p4->next = p2; + return true; + } + else if (p1->prev == p2 && p3->next == p4) + { + p1->prev = p3; + p3->next = p1; + p2->next = p4; + p4->prev = p2; + return true; + } + else + return false; //an orientation is probably wrong +} +//---------------------------------------------------------------------- + +void Clipper::FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx) +{ + for (JoinList::size_type k = startIdx; k < m_Joins.size(); k++) + { + JoinRec* j2 = m_Joins[k]; + if (j2->poly1Idx == j->poly1Idx && PointIsVertex(j2->pt1a, pt)) + j2->poly1Idx = j->poly2Idx; + if (j2->poly2Idx == j->poly1Idx && PointIsVertex(j2->pt2a, pt)) + j2->poly2Idx = j->poly2Idx; + } +} +//---------------------------------------------------------------------- + +bool Poly2ContainsPoly1(OutPt* outPt1, OutPt* outPt2, bool UseFullInt64Range) { - //when a polygon is split into 2 polygons, make sure any holes the original - //polygon contained link to the correct polygon ... + //find the first pt in outPt1 that isn't also a vertex of outPt2 ... + OutPt* outPt = outPt1; + do + { + if (!PointIsVertex(outPt->pt, outPt2)) break; + outPt = outPt->next; + } + while (outPt != outPt1); + bool result; + //sometimes a point on one polygon can be touching the other polygon + //so to be totally confident outPt1 is inside outPt2 repeat ... + do + { + result = PointInPolygon(outPt->pt, outPt2, UseFullInt64Range); + outPt = outPt->next; + } + while (result && outPt != outPt1); + return result; +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { - OutRec *orec = m_PolyOuts[i]; - if (orec->isHole && orec->bottomPt && orec->FirstLeft == outRec1 && - !PointInPolygon(orec->bottomPt->pt, outRec1->pts, m_UseFullRange)) - orec->FirstLeft = outRec2; + OutRec* outRec = m_PolyOuts[i]; + if (outRec->pts && outRec->FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->pts, NewOutRec->pts, m_UseFullRange)) + outRec->FirstLeft = NewOutRec; + } } } //---------------------------------------------------------------------- -void Clipper::CheckHoleLinkages2(OutRec *outRec1, OutRec *outRec2) -{ - //if a hole is owned by outRec2 then make it owned by outRec1 ... +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) +{ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - if (m_PolyOuts[i]->isHole && m_PolyOuts[i]->bottomPt && - m_PolyOuts[i]->FirstLeft == outRec2) - m_PolyOuts[i]->FirstLeft = outRec1; + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + } } //---------------------------------------------------------------------- -void Clipper::JoinCommonEdges(bool fixHoleLinkages) +void Clipper::JoinCommonEdges() { for (JoinList::size_type i = 0; i < m_Joins.size(); i++) { JoinRec* j = m_Joins[i]; + OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; - OutPt *pp1a = outRec1->pts; OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; - OutPt *pp2a = outRec2->pts; - IntPoint pt1 = j->pt2a, pt2 = j->pt2b; - IntPoint pt3 = j->pt1a, pt4 = j->pt1b; - if (!FindSegment(pp1a, pt1, pt2)) continue; - if (j->poly1Idx == j->poly2Idx) - { - //we're searching the same polygon for overlapping segments so - //segment 2 mustn't be the same as segment 1 ... - pp2a = pp1a->next; - if (!FindSegment(pp2a, pt3, pt4) || (pp2a == pp1a)) continue; - } - else if (!FindSegment(pp2a, pt3, pt4)) continue; - - if (!GetOverlapSegment(pt1, pt2, pt3, pt4, pt1, pt2)) continue; - - OutPt *p1, *p2, *p3, *p4; - OutPt *prev = pp1a->prev; - //get p1 & p2 polypts - the overlap start & endpoints on poly1 - if (PointsEqual(pp1a->pt, pt1)) p1 = pp1a; - else if (PointsEqual(prev->pt, pt1)) p1 = prev; - else p1 = InsertPolyPtBetween(pp1a, prev, pt1); - - if (PointsEqual(pp1a->pt, pt2)) p2 = pp1a; - else if (PointsEqual(prev->pt, pt2)) p2 = prev; - else if ((p1 == pp1a) || (p1 == prev)) - p2 = InsertPolyPtBetween(pp1a, prev, pt2); - else if (Pt3IsBetweenPt1AndPt2(pp1a->pt, p1->pt, pt2)) - p2 = InsertPolyPtBetween(pp1a, p1, pt2); else - p2 = InsertPolyPtBetween(p1, prev, pt2); - - //get p3 & p4 polypts - the overlap start & endpoints on poly2 - prev = pp2a->prev; - if (PointsEqual(pp2a->pt, pt1)) p3 = pp2a; - else if (PointsEqual(prev->pt, pt1)) p3 = prev; - else p3 = InsertPolyPtBetween(pp2a, prev, pt1); - - if (PointsEqual(pp2a->pt, pt2)) p4 = pp2a; - else if (PointsEqual(prev->pt, pt2)) p4 = prev; - else if ((p3 == pp2a) || (p3 == prev)) - p4 = InsertPolyPtBetween(pp2a, prev, pt2); - else if (Pt3IsBetweenPt1AndPt2(pp2a->pt, p3->pt, pt2)) - p4 = InsertPolyPtBetween(pp2a, p3, pt2); else - p4 = InsertPolyPtBetween(p3, prev, pt2); - - //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ... - if (p1->next == p2 && p3->prev == p4) - { - p1->next = p3; - p3->prev = p1; - p2->prev = p4; - p4->next = p2; - } - else if (p1->prev == p2 && p3->next == p4) - { - p1->prev = p3; - p3->next = p1; - p2->next = p4; - p4->prev = p2; - } - else - continue; //an orientation is probably wrong - if (j->poly2Idx == j->poly1Idx) + if (!outRec1->pts || !outRec2->pts) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt *p1, *p2; + if (!JoinPoints(j, p1, p2)) continue; + + if (outRec1 == outRec2) { //instead of joining two polygons, we've just created a new one by //splitting one polygon into two. @@ -3040,68 +2961,75 @@ void Clipper::JoinCommonEdges(bool fixHoleLinkages) outRec2->bottomPt = outRec2->pts; outRec2->bottomPt->idx = outRec2->idx; - if (PointInPolygon(outRec2->pts->pt, outRec1->pts, m_UseFullRange)) + if (Poly2ContainsPoly1(outRec2->pts, outRec1->pts, m_UseFullRange)) { //outRec2 is contained by outRec1 ... outRec2->isHole = !outRec1->isHole; outRec2->FirstLeft = outRec1; - if (outRec2->isHole == Orientation(outRec2, m_UseFullRange)) - ReversePolyPtLinks(*outRec2->pts); - } else if (PointInPolygon(outRec1->pts->pt, outRec2->pts, m_UseFullRange)) + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + + + if ((outRec2->isHole ^ m_ReverseOutput) == (Area(*outRec2, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec2->pts); + + } else if (Poly2ContainsPoly1(outRec1->pts, outRec2->pts, m_UseFullRange)) { //outRec1 is contained by outRec2 ... outRec2->isHole = outRec1->isHole; outRec1->isHole = !outRec2->isHole; outRec2->FirstLeft = outRec1->FirstLeft; outRec1->FirstLeft = outRec2; - if (outRec1->isHole == Orientation(outRec1, m_UseFullRange)) - ReversePolyPtLinks(*outRec1->pts); - //make sure any contained holes now link to the correct polygon ... - if (fixHoleLinkages) CheckHoleLinkages1(outRec1, outRec2); - } else + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + + if ((outRec1->isHole ^ m_ReverseOutput) == (Area(*outRec1, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec1->pts); + } + else { + //the 2 polygons are completely separate ... outRec2->isHole = outRec1->isHole; outRec2->FirstLeft = outRec1->FirstLeft; - //make sure any contained holes now link to the correct polygon ... - if (fixHoleLinkages) CheckHoleLinkages1(outRec1, outRec2); - } - //now fixup any subsequent joins that match this polygon - for (JoinList::size_type k = i+1; k < m_Joins.size(); k++) - { - JoinRec* j2 = m_Joins[k]; - if (j2->poly1Idx == j->poly1Idx && PointIsVertex(j2->pt1a, p2)) - j2->poly1Idx = j->poly2Idx; - if (j2->poly2Idx == j->poly1Idx && PointIsVertex(j2->pt2a, p2)) - j2->poly2Idx = j->poly2Idx; - } + FixupJoinRecs(j, p2, i+1); - //now cleanup redundant edges too ... - FixupOutPolygon(*outRec1); - FixupOutPolygon(*outRec2); + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + } + } else { //joined 2 polygons together ... - //make sure any holes contained by outRec2 now link to outRec1 ... - if (fixHoleLinkages) CheckHoleLinkages2(outRec1, outRec2); - - //now cleanup redundant edges too ... + //cleanup redundant edges ... FixupOutPolygon(*outRec1); - if (outRec1->pts) - { - outRec1->isHole = !Orientation(outRec1, m_UseFullRange); - if (outRec1->isHole && !outRec1->FirstLeft) - outRec1->FirstLeft = outRec2->FirstLeft; - } - //delete the obsolete pointer ... int OKIdx = outRec1->idx; int ObsoleteIdx = outRec2->idx; outRec2->pts = 0; outRec2->bottomPt = 0; - outRec2->AppendLink = outRec1; + + outRec1->isHole = holeStateRec->isHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; //now fixup any subsequent Joins that match this polygon for (JoinList::size_type k = i+1; k < m_Joins.size(); k++) @@ -3110,12 +3038,15 @@ void Clipper::JoinCommonEdges(bool fixHoleLinkages) if (j2->poly1Idx == ObsoleteIdx) j2->poly1Idx = OKIdx; if (j2->poly2Idx == ObsoleteIdx) j2->poly2Idx = OKIdx; } + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); } } } //------------------------------------------------------------------------------ -void ReversePolygons(Polygon& p) +void ReversePolygon(Polygon& p) { std::reverse(p.begin(), p.end()); } @@ -3124,7 +3055,7 @@ void ReversePolygons(Polygon& p) void ReversePolygons(Polygons& p) { for (Polygons::size_type i = 0; i < p.size(); ++i) - ReversePolygons(p[i]); + ReversePolygon(p[i]); } //------------------------------------------------------------------------------ @@ -3140,25 +3071,32 @@ struct DoublePoint //------------------------------------------------------------------------------ Polygon BuildArc(const IntPoint &pt, - const double a1, const double a2, const double r) -{ - long64 steps = std::max(6, int(std::sqrt(std::fabs(r)) * std::fabs(a2 - a1))); - if (steps > 0x100000) steps = 0x100000; - int n = (unsigned)steps; - Polygon result(n); - double da = (a2 - a1) / (n -1); - double a = a1; - for (int i = 0; i < n; ++i) - { - result[i].X = pt.X + Round(std::cos(a)*r); - result[i].Y = pt.Y + Round(std::sin(a)*r); - a += da; + const double a1, const double a2, const double r, double limit) +{ + //see notes in clipper.pas regarding steps + double arcFrac = std::fabs(a2 - a1) / (2 * pi); + int steps = (int)(arcFrac * pi / std::acos(1 - limit / std::fabs(r))); + if (steps < 2) steps = 2; + else if (steps > (int)(222.0 * arcFrac)) steps = (int)(222.0 * arcFrac); + + double x = std::cos(a1); + double y = std::sin(a1); + double c = std::cos((a2 - a1) / steps); + double s = std::sin((a2 - a1) / steps); + Polygon result(steps +1); + for (int i = 0; i <= steps; ++i) + { + result[i].X = pt.X + Round(x * r); + result[i].Y = pt.Y + Round(y * r); + double x2 = x; + x = x * c - s * y; //cross product + y = x2 * s + y * c; //dot product } return result; } //------------------------------------------------------------------------------ -DoublePoint GetUnitNormal( const IntPoint &pt1, const IntPoint &pt2) +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) { if(pt2.X == pt1.X && pt2.Y == pt1.Y) return DoublePoint(0, 0); @@ -3188,7 +3126,7 @@ class PolyOffsetBuilder public: PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, - double delta, JoinType jointype, double MiterLimit) + double delta, JoinType jointype, double limit, bool autoFix) { //nb precondition - out_polys != ptsin_polys if (NEAR_ZERO(delta)) @@ -3200,22 +3138,68 @@ PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, this->m_p = in_polys; this->m_delta = delta; this->m_jointype = jointype; - if (MiterLimit <= 1) MiterLimit = 1; - m_RMin = 2/(MiterLimit*MiterLimit); + + //ChecksInput - fixes polygon orientation if necessary and removes + //duplicate vertices. Can be set false when you're sure that polygon + //orientation is correct and that there are no duplicate vertices. + if (autoFix) + { + size_t Len = m_p.size(), botI = 0; + while (botI < Len && m_p[botI].size() == 0) botI++; + if (botI == Len) return; + + //botPt: used to find the lowermost (in inverted Y-axis) & leftmost point + //This point (on m_p[botI]) must be on an outer polygon ring and if + //its orientation is false (counterclockwise) then assume all polygons + //need reversing ... + IntPoint botPt = m_p[botI][0]; + for (size_t i = botI; i < Len; ++i) + { + if (m_p[i].size() < 3) continue; + if (UpdateBotPt(m_p[i][0], botPt)) botI = i; + Polygon::iterator it = m_p[i].begin() +1; + while (it != m_p[i].end()) + { + if (PointsEqual(*it, *(it -1))) + it = m_p[i].erase(it); + else + { + if (UpdateBotPt(*it, botPt)) botI = i; + ++it; + } + } + } + if (!Orientation(m_p[botI])) + ReversePolygons(m_p); + } + + switch (jointype) + { + case jtRound: + if (limit <= 0) limit = 0.25; + else if (limit > std::fabs(delta)) limit = std::fabs(delta); + break; + case jtMiter: + if (limit < 2) limit = 2; + break; + default: //unused + limit = 1; + } + m_RMin = 2.0/(limit*limit); double deltaSq = delta*delta; out_polys.clear(); - out_polys.resize(in_polys.size()); - for (m_i = 0; m_i < in_polys.size(); m_i++) + out_polys.resize(m_p.size()); + for (m_i = 0; m_i < m_p.size(); m_i++) { m_curr_poly = &out_polys[m_i]; - size_t len = in_polys[m_i].size(); + size_t len = m_p[m_i].size(); if (len > 1 && m_p[m_i][0].X == m_p[m_i][len - 1].X && m_p[m_i][0].Y == m_p[m_i][len-1].Y) len--; //when 'shrinking' polygons - to minimize artefacts //strip those polygons that have an area < pi * delta^2 ... - double a1 = Area(in_polys[m_i]); + double a1 = Area(m_p[m_i]); if (delta < 0) { if (a1 > 0 && a1 < deltaSq *pi) len = 0; } else if (a1 < 0 && -a1 < deltaSq *pi) len = 0; //holes have neg. area @@ -3224,7 +3208,7 @@ PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, else if (len == 1) { Polygon arc; - arc = BuildArc(in_polys[m_i][len-1], 0, 2 * pi, delta); + arc = BuildArc(m_p[m_i][len-1], 0, 2 * pi, delta, limit); out_polys[m_i] = arc; continue; } @@ -3232,9 +3216,9 @@ PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, //build normals ... normals.clear(); normals.resize(len); - normals[len-1] = GetUnitNormal(in_polys[m_i][len-1], in_polys[m_i][0]); + normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]); for (m_j = 0; m_j < len -1; ++m_j) - normals[m_j] = GetUnitNormal(in_polys[m_i][m_j], in_polys[m_i][m_j+1]); + normals[m_j] = GetUnitNormal(m_p[m_i][m_j], m_p[m_i][m_j+1]); m_k = len -1; for (m_j = 0; m_j < len; ++m_j) @@ -3245,11 +3229,11 @@ PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, { m_R = 1 + (normals[m_j].X*normals[m_k].X + normals[m_j].Y*normals[m_k].Y); - if (m_R >= m_RMin) DoMiter(); else DoSquare(MiterLimit); + if (m_R >= m_RMin) DoMiter(); else DoSquare(limit); break; } - case jtSquare: DoSquare(); break; - case jtRound: DoRound(); break; + case jtSquare: DoSquare(1.0); break; + case jtRound: DoRound(limit); break; } m_k = m_j; } @@ -3288,25 +3272,25 @@ PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, void AddPoint(const IntPoint& pt) { - Polygon::size_type len = m_curr_poly->size(); - if (len == m_curr_poly->capacity()) - m_curr_poly->reserve(len + buffLength); + 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 mul = 1.0) +void DoSquare(double mul) { IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - double sinAngle = normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y; - if (sinAngle * m_delta >= 0) + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) { - //occasionally (due to floating point math) sinAngle can be > 1 so ... - if (sinAngle > 1) sinAngle = 1; else if (sinAngle < -1) sinAngle = -1; - double dx = std::tan((pi - std::asin(sinAngle))/4) * std::abs(m_delta*mul); + double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); + double a2 = std::atan2(-normals[m_j].Y, -normals[m_j].X); + a1 = std::fabs(a2 - a1); + if (a1 > pi) a1 = pi * 2 - a1; + double dx = std::tan((pi - a1) / 4) * std::fabs(m_delta * mul); pt1 = IntPoint((long64)(pt1.X -normals[m_k].Y * dx), (long64)(pt1.Y + normals[m_k].X * dx)); AddPoint(pt1); @@ -3316,9 +3300,9 @@ void DoSquare(double mul = 1.0) } else { - AddPoint(pt1); - AddPoint(m_p[m_i][m_j]); - AddPoint(pt2); + AddPoint(pt1); + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); } } //------------------------------------------------------------------------------ @@ -3345,7 +3329,7 @@ void DoMiter() } //------------------------------------------------------------------------------ -void DoRound() +void DoRound(double limit) { IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); @@ -3361,7 +3345,7 @@ void DoRound() double a2 = std::atan2(normals[m_j].Y, normals[m_j].X); if (m_delta > 0 && a2 < a1) a2 += pi *2; else if (m_delta < 0 && a2 > a1) a2 -= pi *2; - Polygon arc = BuildArc(m_p[m_i][m_j], a1, a2, m_delta); + Polygon arc = BuildArc(m_p[m_i][m_j], a1, a2, m_delta, limit); for (Polygon::size_type m = 0; m < arc.size(); m++) AddPoint(arc[m]); } @@ -3372,20 +3356,31 @@ void DoRound() } //-------------------------------------------------------------------------- +bool UpdateBotPt(const IntPoint &pt, IntPoint &botPt) +{ + if (pt.Y > botPt.Y || (pt.Y == botPt.Y && pt.X < botPt.X)) + { + botPt = pt; + return true; + } + else return false; +} +//-------------------------------------------------------------------------- + }; //end PolyOffsetBuilder //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype, double MiterLimit) + double delta, JoinType jointype, double limit, bool autoFix) { if (&out_polys == &in_polys) { Polygons poly2(in_polys); - PolyOffsetBuilder(poly2, out_polys, delta, jointype, MiterLimit); + PolyOffsetBuilder(poly2, out_polys, delta, jointype, limit, autoFix); } - else PolyOffsetBuilder(in_polys, out_polys, delta, jointype, MiterLimit); + else PolyOffsetBuilder(in_polys, out_polys, delta, jointype, limit, autoFix); } //------------------------------------------------------------------------------ @@ -3411,6 +3406,86 @@ void SimplifyPolygons(Polygons &polys, PolyFillType fillType) } //------------------------------------------------------------------------------ +bool PointsAreClose(IntPoint pt1, IntPoint pt2, long64 distSqrd) +{ + long64 dx = pt1.X - pt2.X; + long64 dy = pt1.Y - pt2.Y; + return ((dx * dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Polygon& in_poly, Polygon& out_poly, double distance) +{ + //distance = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2). + int highI = in_poly.size() -1; + long64 d = (int)(distance * distance); + while (highI > 0 && PointsAreClose(in_poly[highI], in_poly[0], d)) highI--; + if (highI < 2) + { + out_poly.clear(); + return; + } + out_poly.resize(highI + 1); + bool UseFullRange = FullRangeNeeded(in_poly); + IntPoint pt = in_poly[highI]; + int i = 0; + int k = 0; + for (;;) + { + if (i >= highI) break; + int j = i + 1; + + if (PointsAreClose(pt, in_poly[j], d)) + { + i = j + 1; + while (i <= highI && PointsAreClose(pt, in_poly[i], d)) i++; + continue; + } + + if (PointsAreClose(in_poly[i], in_poly[j], d) || + SlopesEqual(pt, in_poly[i], in_poly[j], UseFullRange)) + { + i = j; + continue; + } + + pt = in_poly[i++]; + out_poly[k++] = pt; + } + + if (i <= highI) out_poly[k++] = in_poly[i]; + if (k > 2 && SlopesEqual(out_poly[k -2], out_poly[k -1], out_poly[0], UseFullRange)) + k--; + if (k < 3) out_poly.clear(); + else if (k <= highI) out_poly.resize(k); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Polygons& in_polys, Polygons& out_polys, double distance) +{ + for (Polygons::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void AddPolyNodeToPolygons(PolyNode& polynode, Polygons& polygons) +{ + if (polynode.Contour.size() > 0) + polygons.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPolygons(*polynode.Childs[i], polygons); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPolygons(PolyTree& polytree, Polygons& polygons) +{ + polygons.resize(0); + polygons.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, polygons); +} +//------------------------------------------------------------------------------ + std::ostream& operator <<(std::ostream &s, IntPoint& p) { s << p.X << ' ' << p.Y << "\n"; diff --git a/src/clipper.hpp b/src/clipper.hpp old mode 100755 new mode 100644 index e9d69d2..16540f3 --- a/src/clipper.hpp +++ b/src/clipper.hpp @@ -1,10 +1,10 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 4.8.6 * -* Date : 11 August 2012 * +* Version : 5.1.4 * +* Date : 24 March 2013 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2012 * +* Copyright : Angus Johnson 2010-2013 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * @@ -26,7 +26,7 @@ * Paper no. DETC2005-85513 pp. 565-575 * * ASME 2005 International Design Engineering Technical Conferences * * and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24–28, 2005 , Long Beach, California, USA * +* September 24-28, 2005 , Long Beach, California, USA * * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * * * *******************************************************************************/ @@ -64,30 +64,64 @@ struct IntPoint { typedef std::vector< IntPoint > Polygon; typedef std::vector< Polygon > Polygons; + std::ostream& operator <<(std::ostream &s, Polygon &p); std::ostream& operator <<(std::ostream &s, Polygons &p); -struct ExPolygon { - Polygon outer; - Polygons holes; +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ +public: + PolyNode(); + Polygon Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext() const; + bool IsHole() const; + int ChildCount() const; +private: + PolyNode* GetNextSiblingUp() const; + unsigned Index; //node index in Parent.Childs + void AddChild(PolyNode& child); + friend class Clipper; //to access Index }; -typedef std::vector< ExPolygon > ExPolygons; +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst() const; + void Clear(); + int Total() const; +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + enum JoinType { jtSquare, jtRound, jtMiter }; bool Orientation(const Polygon &poly); double Area(const Polygon &poly); + void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype = jtSquare, double MiterLimit = 2); + double delta, JoinType jointype = jtSquare, double limit = 0, bool autoFix = true); + void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(Polygons &polys, PolyFillType fillType = pftEvenOdd); +void CleanPolygon(Polygon& in_poly, Polygon& out_poly, double distance = 1.415); +void CleanPolygons(Polygons& in_polys, Polygons& out_polys, double distance = 1.415); + +void PolyTreeToPolygons(PolyTree& polytree, Polygons& polygons); + void ReversePolygon(Polygon& p); void ReversePolygons(Polygons& p); //used internally ... -enum EdgeSide { esNeither = 0, esLeft = 1, esRight = 2, esBoth = 3 }; +enum EdgeSide { esLeft = 1, esRight = 2}; enum IntersectProtects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 }; struct TEdge { @@ -98,7 +132,8 @@ struct TEdge { long64 xtop; long64 ytop; double dx; - long64 tmpX; + long64 deltaX; + long64 deltaY; PolyType polyType; EdgeSide side; int windDelta; //1 or -1 depending on winding direction @@ -138,12 +173,10 @@ struct OutPt; //forward declaration struct OutRec { int idx; bool isHole; - OutRec *FirstLeft; - OutRec *AppendLink; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *polyNode; OutPt *pts; OutPt *bottomPt; - OutPt *bottomFlag; - EdgeSide sides; }; struct OutPt { @@ -204,19 +237,19 @@ class Clipper : public virtual ClipperBase Clipper(); ~Clipper(); bool Execute(ClipType clipType, - Polygons &solution, - PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); + Polygons &solution, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); bool Execute(ClipType clipType, - ExPolygons &solution, - PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); + PolyTree &polytree, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); void Clear(); bool ReverseSolution() {return m_ReverseOutput;}; void ReverseSolution(bool value) {m_ReverseOutput = value;}; protected: void Reset(); - virtual bool ExecuteInternal(bool fixHoleLinkages); + virtual bool ExecuteInternal(); private: PolyOutList m_PolyOuts; JoinList m_Joins; @@ -225,11 +258,12 @@ class Clipper : public virtual ClipperBase Scanbeam *m_Scanbeam; TEdge *m_ActiveEdges; TEdge *m_SortedEdges; - IntersectNode *m_IntersectNodes; - bool m_ExecuteLocked; - PolyFillType m_ClipFillType; - PolyFillType m_SubjFillType; - bool m_ReverseOutput; + IntersectNode *m_IntersectNodes; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; void DisposeScanbeamList(); void SetWindingCount(TEdge& edge); bool IsEvenOddFillType(const TEdge& edge) const; @@ -257,10 +291,9 @@ class Clipper : public virtual ClipperBase void DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt); void DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt); void IntersectEdges(TEdge *e1, TEdge *e2, - const IntPoint &pt, IntersectProtects protects); + const IntPoint &pt, const IntersectProtects protects); OutRec* CreateOutRec(); void AddOutPt(TEdge *e, const IntPoint &pt); - void DisposeBottomPt(OutRec &outRec); void DisposeAllPolyPts(); void DisposeOutRec(PolyOutList::size_type index); bool ProcessIntersections(const long64 botY, const long64 topY); @@ -269,20 +302,22 @@ class Clipper : public virtual ClipperBase void ProcessIntersectList(); void ProcessEdgesAtTopOfScanbeam(const long64 topY); void BuildResult(Polygons& polys); - void BuildResultEx(ExPolygons& polys); + void BuildResult2(PolyTree& polytree); void SetHoleState(TEdge *e, OutRec *OutRec); void DisposeIntersectNodes(); - bool FixupIntersections(); + bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outRec); bool IsHole(TEdge *e); - void FixHoleLinkage(OutRec *outRec); - void CheckHoleLinkages1(OutRec *outRec1, OutRec *outRec2); - void CheckHoleLinkages2(OutRec *outRec1, OutRec *outRec2); + void FixHoleLinkage(OutRec &outRec); void AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx = -1, int e2OutIdx = -1); void ClearJoins(); void AddHorzJoin(TEdge *e, int idx); void ClearHorzJoins(); - void JoinCommonEdges(bool fixHoleLinkages); + bool JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2); + void FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx); + void JoinCommonEdges(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); }; //------------------------------------------------------------------------------ diff --git a/src/init.cc b/src/init.cc index 05373e1..4ad6cb2 100644 --- a/src/init.cc +++ b/src/init.cc @@ -1,4 +1,6 @@ -#define BUILDING_NODE_EXTENSION +#define DEBUG + +#include #include #include @@ -8,70 +10,569 @@ using namespace node; using namespace v8; using namespace ClipperLib; +const unsigned long doubleFactor = 0x4000000000000; +int debug = 0; + + +v8::Handle setDebug(const Arguments& args) { + v8::HandleScope scope; + + if (args.Length() > 0 && args[0]->IsNumber()) { + debug = args[0]->NumberValue(); + } + + return scope.Close(v8::Number::New(debug)); +} + + + +Polygons v8ArrayToPolygons(v8::Handle inOutPolygons, bool doubleType) { + int len = inOutPolygons->Length(); + Polygons polyshape(len); + #ifdef DEBUG + if (debug > 1) std::cout << "polygonArray: length: " << len << std::endl; + #endif + + for (int i = 0; i < len; i++) { + v8::Local polyLine = v8::Local::Cast(inOutPolygons->Get(i)); + #ifdef DEBUG + if (debug > 2) std::cout << "polyLine: length: " << polyLine->Length() << std::endl; + #endif + for (unsigned int j = 0; j < polyLine->Length(); j++) { + v8::Local point = v8::Local::Cast(polyLine->Get(j)); + v8::Local x = point->Get(0); + v8::Local y = point->Get(1); + IntPoint p; + if (doubleType) { + p = IntPoint( + x->NumberValue() * doubleFactor, + y->NumberValue() * doubleFactor + ); + } else { + p = IntPoint( + x->NumberValue(), + y->NumberValue() + ); + } + polyshape[i].push_back(p); + #ifdef DEBUG + if (debug > 3) std::cout << "polyLine: point: " << p.X << " " << p.Y << std::endl; + #endif + } + } + return polyshape; +} + + +long signedSum(long a, long b) { + if ((a < 0 && b < 0) || (a > 0 && b > 0) || a == 0 || b == 0) { + return a + b; + } + return a - b; +} + + +v8::Handle polygonsToV8Array(Polygons polygons, bool doubleType) { + v8::Handle result = v8::Array::New(); + for (unsigned int i = 0; i < polygons.size(); i++) { + v8::Handle points = v8::Array::New(); + for (unsigned int k = 0; k < polygons[i].size(); k++) { + IntPoint ip = polygons[i][k]; + v8::Local x; + v8::Local y; + if (doubleType) { + x = v8::Number::New((double)ip.X / (double)doubleFactor); + y = v8::Number::New((double)ip.Y / (double)doubleFactor); + } else { + x = v8::Number::New(ip.X); + y = v8::Number::New(ip.Y); + } + v8::Handle point = v8::Array::New(); + point->Set(point->Length(), x); + point->Set(point->Length(), y); + points->Set(points->Length(), point); + } + result->Set(result->Length(), points); + } + return result; +} + + +void doFixOrientation(Polygons &polyshape) { + if (!ClipperLib::Orientation(polyshape[0])) { + ClipperLib::ReversePolygon(polyshape[0]); + #ifdef DEBUG + if (debug > 0) std::cout << "doFixOrientation: outerPoints reversed" << std::endl; + #endif + } + + for (unsigned int i = 1; i < polyshape.size(); i++) { + if (ClipperLib::Orientation(polyshape[i])) { + ClipperLib::ReversePolygon(polyshape[i]); + #ifdef DEBUG + if (debug > 0) std::cout << "doFixOrientation: innerPoints reversed: " << i - 1 << std::endl; + #endif + } + } +} + + +v8::Local checkOffsetArguments(const Arguments& args, int checkLength) { + v8::Local result = String::New(""); + + /* check args for wrong function call + * args[0]: array of outerPoints and innerPoints arrays + * args[1]: pointType integer || double -> IntPoint = doubleFactor * double + * args[2]: Polygon shrink value (negative -> shrink; positive -> expand) + * args[3]: optional: Jointype (jtMiter, jtSquare or jtRound) + * args[4]: optional: double MiterLimit + */ + if (args.Length() < 2) { + result = String::New("Too few arguments! At least 'polyshape[][][]' and 'pointType' are required!"); + return result; + } + if (args.Length() < checkLength) { + result = String::New("Too few arguments!"); + return result; + } + + if (!args[0]->IsArray()) { + result = String::Concat(String::New("Wrong argument 'polyshape': array[shapes][points][point] required: "), args[0]->ToString()); + return result; + } + + if (checkLength < 2) { + return result; + } + + if (!args[1]->IsString() || !(args[1]->Equals(String::New("double")) || args[1]->Equals(String::New("integer")))) { + result = String::Concat(String::New("Wrong argument 'pointType': 'double' || 'integer' required: "), args[1]->ToString()); + return result; + } + + if ((args.Length() > 2) && (checkLength > 2)) { + if (!args[2]->IsNumber()) { + result = String::Concat(String::New("Wrong argument 'delta' || 'distance': number required: "), args[2]->ToString()); + return result; + } + } + + if ((args.Length() > 3) && (checkLength > 3)) { + if (!args[3]->IsString() || !(args[3]->Equals(String::New("jtMiter")) || args[3]->Equals(String::New("jtSquare")) || args[3]->Equals(String::New("jtRound")))) { + result = String::Concat(String::New("Wrong argument 'joinType': 'jtMiter' || 'jtSquare' || 'jtRound' required: "), args[3]->ToString()); + return result; + } + } + + if ((args.Length() > 4) && (checkLength > 4)) { + if (!args[4]->IsNumber()) { + result = String::Concat(String::New("Wrong argument 'miterLimit': number required: "), args[4]->ToString()); + return result; + } + } + return result; +} + + +v8::Handle orientation(const Arguments& args) { + v8::HandleScope scope; + + bool doubleType = false; + + v8::Local errMsg = checkOffsetArguments(args, 2); + if (errMsg->Length() > 0) { + ThrowException(Exception::TypeError(errMsg)); + return scope.Close(v8::Undefined()); + } + + if (args[1]->Equals(String::New("double"))) { + doubleType = true; + } + + Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + if (polyshape.size() <= 0) { + return scope.Close(v8::Undefined()); + } + + v8::Handle orientations = v8::Array::New(); + for (unsigned int i = 0; i < polyshape.size(); i++) { + bool polyOrientation = ClipperLib::Orientation(polyshape[i]); + orientations->Set(orientations->Length(), v8::Boolean::New(polyOrientation)); + } + + return scope.Close(orientations); +} + +v8::Handle offset(const Arguments& args) { + v8::HandleScope scope; + + JoinType joinType = jtMiter; + double miterLimit = 30.0; + long delta; + bool doubleType = false; + + v8::Local errMsg = checkOffsetArguments(args, 3); + if (errMsg->Length() > 0) { + ThrowException(Exception::TypeError(errMsg)); + return scope.Close(v8::Undefined()); + } + + if (args[1]->Equals(String::New("double"))) { + doubleType = true; + } + + if (doubleType) { + delta = args[2]->NumberValue() * doubleFactor; + } else { + delta = args[2]->NumberValue(); + } + #ifdef DEBUG + if (debug > 0) std::cout << "args[2]: delta: " << delta << std::endl; + #endif + + if (args.Length() > 3) { + if (args[3]->Equals(String::New("jtMiter"))) { + joinType = jtMiter; + } + if (args[3]->Equals(String::New("jtSquare"))) { + joinType = jtSquare; + } + if (args[3]->Equals(String::New("jtRound"))) { + joinType = jtRound; + } + #ifdef DEBUG + if (debug > 0) std::cout << "args[3]: joinType: " << *v8::String::AsciiValue(args[3]) << std::endl; + #endif + } + + if (args.Length() > 4) { + miterLimit = args[4]->NumberValue(); + #ifdef DEBUG + if (debug > 0) std::cout << "args[4]: miterLimit: " << miterLimit << std::endl; + #endif + } + + Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + doFixOrientation(polyshape); + Polygons polyshapeOut; + + #ifdef DEBUG + if (debug > 1) std::cout << "before Offset: polyshape.size(): " << polyshape.size() << std::endl; + #endif + //void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, double delta, JoinType jointype = jtSquare, double MiterLimit = 2.0); + ClipperLib::OffsetPolygons(polyshape, polyshapeOut, delta, joinType, miterLimit); + #ifdef DEBUG + if (debug > 1) std::cout << "after Offset: polyshapeOut.size(): " << polyshapeOut.size() << std::endl; + #endif + + if (polyshapeOut.size() > 0) { + return scope.Close(polygonsToV8Array(polyshapeOut, doubleType)); + } + + return scope.Close(v8::Undefined()); +} + + +v8::Handle minimum(const Arguments& args) { + v8::HandleScope scope; + const unsigned long ScaleMax = doubleFactor - 1; + + JoinType joinType = jtMiter; + double miterLimit = 30.0; + bool doubleType = false; + + v8::Local errMsg = checkOffsetArguments(args, 2); + if (errMsg->Length() > 0) { + ThrowException(Exception::TypeError(errMsg)); + return scope.Close(v8::Undefined()); + } + + if (args[1]->Equals(String::New("double"))) { + doubleType = true; + } + + if (args.Length() > 3) { + if (args[3]->Equals(String::New("jtMiter"))) { + joinType = jtMiter; + } + if (args[3]->Equals(String::New("jtSquare"))) { + joinType = jtSquare; + } + if (args[3]->Equals(String::New("jtRound"))) { + joinType = jtRound; + } + #ifdef DEBUG + if (debug > 0) std::cout << "args[3]: joinType: " << *v8::String::AsciiValue(args[3]) << std::endl; + #endif + } + + if (args.Length() > 4) { + miterLimit = args[4]->NumberValue(); + #ifdef DEBUG + if (debug > 0) std::cout << "args[4]: miterLimit: " << miterLimit << std::endl; + #endif + } + + Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + if (polyshape.size() <= 0) { + return scope.Close(v8::Undefined()); + } + doFixOrientation(polyshape); + + Polygons polyshapeOut(polyshape.size()); + long xMin = 0, xMax = 0, yMin = 0, yMax = 0, xScale = 0, yScale = 0, xyScale = 0; + + for (unsigned int i = 0; i < polyshape[0].size(); i++) { + IntPoint ip = polyshape[0][i]; + if (i == 0) { + xMin = ip.X; + xMax = ip.X; + yMin = ip.Y; + yMax = ip.Y; + continue; + } + if (ip.X < xMin) xMin = ip.X; + if (ip.X > xMax) xMax = ip.X; + if (ip.Y < yMin) yMin = ip.Y; + if (ip.Y > yMax) yMax = ip.Y; + } + #ifdef DEBUG + if (debug > 1) std::cout << "polyshape[0]: boundingBox: " << xMin << " " << yMin << " " << xMax << " " << yMax << std::endl; + #endif + xScale = signedSum(xMin, xMax) / 2; + yScale = signedSum(yMin, yMax) / 2; + xyScale = std::min(std::abs(xScale), std::abs(yScale)); + + int s = -1; + int loops = 0; + long scale = xyScale; + #ifdef DEBUG + if (debug > 1) std::cout << "OffsetPolygons: xyScale: " << xyScale << std::endl; + #endif + long lastScale = 2; + do { + loops++; + //void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, double delta, JoinType jointype = jtSquare, double MiterLimit = 2.0); + try { + ClipperLib::OffsetPolygons(polyshape, polyshapeOut, s * scale, joinType, miterLimit); + } + catch (...) { + miterLimit /= 2; + if (miterLimit < 5) { + miterLimit = 5; + } + std::cout << "node-clipper: exception from ClipperLib catched! miterLimit reduced: " << miterLimit<< std::endl; + continue; + } + #ifdef DEBUG + if (debug > 1) std::cout << "polyshapeOut.size(): " << polyshapeOut.size() << std::endl; + if (debug > 1) std::cout << "polyshapeOut[0].size(): " << polyshapeOut[0].size() << std::endl; + #endif + if (polyshapeOut.size() <= 0) { + scale -= xyScale / lastScale; + } else { + // only one line with four or less vertices -> break + if (polyshapeOut.size() == 1 && polyshapeOut[0].size() <= 3) break; + if (polyshapeOut.size() == 1 && polyshapeOut[0].size() <= 5 && loops > 32) break; + scale += xyScale / lastScale; + } + lastScale = (lastScale << 1) & ScaleMax; + #ifdef DEBUG + if (debug > 1) std::cout << "OffsetPolygons: scale: " << scale << std::endl; + if (debug > 1) std::cout << "OffsetPolygons: lastScale: " << lastScale << std::endl; + #endif + } while (lastScale != 0 || loops > 64); + #ifdef DEBUG + if (debug > 0) std::cout << "MinimumPolygon: finished: loops: " << loops << std::endl; + #endif + + if (polyshapeOut.size() > 0) { + return scope.Close(polygonsToV8Array(polyshapeOut, doubleType)); + } + + return scope.Close(v8::Undefined()); +} + + +v8::Handle clip(const Arguments& args) { + v8::HandleScope scope; + bool doubleType = false; + ClipType clipType = ctIntersection; + + v8::Local errMsg = checkOffsetArguments(args, 1); + if (errMsg->Length() > 0) { + ThrowException(Exception::TypeError(errMsg)); + return scope.Close(v8::Undefined()); + } + + if (args[2]->Equals(String::New("double"))) { + doubleType = true; + } + + if (args.Length() > 3) { + if (args[3]->Equals(String::New("ctUnion"))) { + clipType = ctUnion; + } + if (args[3]->Equals(String::New("ctDifference"))) { + clipType = ctDifference; + } + if (args[3]->Equals(String::New("ctXor"))) { + clipType = ctXor; + } + #ifdef DEBUG + if (debug > 0) std::cout << "args[3]: clipType: " << *v8::String::AsciiValue(args[3]) << std::endl; + #endif + } + + Clipper clipper; + + Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + if (polyshape.size() <= 0) { + return scope.Close(v8::Undefined()); + } + clipper.AddPolygons(polyshape, ptSubject); + + polyshape = v8ArrayToPolygons(v8::Local::Cast(args[1]), doubleType); + if (polyshape.size() <= 0) { + return scope.Close(v8::Undefined()); + } + clipper.AddPolygons(polyshape, ptClip); + + Polygons clipperSolution; + + clipper.Execute(clipType, clipperSolution, pftNonZero, pftNonZero); + + #ifdef DEBUG + if (debug > 1) std::cout << "clipperSolution.size(): " << clipperSolution.size() << std::endl; + #endif + + v8::Handle solutions = v8::Array::New(); + Polygons singleSolution; + unsigned int i = 0; + while (i < clipperSolution.size()) { + do { + singleSolution.push_back(clipperSolution[i]); + i++; + } while (i < clipperSolution.size() && !ClipperLib::Orientation(clipperSolution[i])); + solutions->Set(solutions->Length(), polygonsToV8Array(singleSolution, doubleType)); + singleSolution.clear(); + } + #ifdef DEBUG + if (debug > 1) std::cout << "solutions->Length(): " << solutions->Length() << std::endl; + #endif + + if (solutions->Length() > 0) { + return scope.Close(solutions); + } else { + return scope.Close(v8::Undefined()); + } +} + + +v8::Handle clean(const Arguments& args) { + v8::HandleScope scope; + bool doubleType = false; + double distance = 1.415; + + v8::Local errMsg = checkOffsetArguments(args, 2); + if (errMsg->Length() > 0) { + ThrowException(Exception::TypeError(errMsg)); + return scope.Close(v8::Undefined()); + } + + if (args[1]->Equals(String::New("double"))) { + doubleType = true; + } + + if (args.Length() > 2) { + distance = args[2]->NumberValue(); + #ifdef DEBUG + if (debug > 0) std::cout << "args[2]: distance: " << *v8::String::AsciiValue(args[2]) << std::endl; + #endif + } + + Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + Polygons polyshapeOut(polyshape.size()); + + #ifdef DEBUG + if (debug > 1) std::cout << "before CleanPolygons: polyshape.size(): " << polyshape.size() << std::endl; + #endif + //void CleanPolygons(Polygons &in_polys, Polygon &out_polys, double distance = 1.415); + ClipperLib::CleanPolygons(polyshape, polyshapeOut, distance); + #ifdef DEBUG + if (debug > 1) std::cout << "after CleanPolygons: polyshapeOut.size(): " << polyshapeOut.size() << std::endl; + #endif + + if (polyshapeOut.size() > 0) { + return scope.Close(polygonsToV8Array(polyshapeOut, doubleType)); + } + + return scope.Close(v8::Undefined()); +} + + +v8::Handle fixOrientation(const Arguments& args) { + v8::HandleScope scope; + bool doubleType = false; + + v8::Local errMsg = checkOffsetArguments(args, 2); + if (errMsg->Length() > 0) { + ThrowException(Exception::TypeError(errMsg)); + return scope.Close(v8::Undefined()); + } + + if (args[1]->Equals(String::New("double"))) { + doubleType = true; + } + + + Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + doFixOrientation(polyshape); + + if (polyshape.size() > 0) { + return scope.Close(polygonsToV8Array(polyshape, doubleType)); + } + + return scope.Close(v8::Undefined()); +} + + +v8::Handle simplify(const Arguments& args) { + v8::HandleScope scope; + bool doubleType = false; + + v8::Local errMsg = checkOffsetArguments(args, 2); + if (errMsg->Length() > 0) { + ThrowException(Exception::TypeError(errMsg)); + return scope.Close(v8::Undefined()); + } + + if (args[1]->Equals(String::New("double"))) { + doubleType = true; + } + + + Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + ClipperLib::SimplifyPolygons(polyshape, polyshape, pftNonZero); + + if (polyshape.size() > 0) { + return scope.Close(polygonsToV8Array(polyshape, doubleType)); + } + + return scope.Close(v8::Undefined()); +} + + -v8::Handle Method(const Arguments& args) { - v8::HandleScope scope; - - if (args[0]->IsArray()) { - try { - v8::Local points = v8::Array::Cast(*args[0]); - - ClipperLib::Polygons polyshape(1), polydone; - - // Populate the subject polygon - for (int i = 0, limiti = points->Length(); i < limiti; i += 2) { - // pair = v8::Array::New(2); - // pair->set(0, points[0]); - v8::Local pairA = points->Get(i); - v8::Local pairB = points->Get(i+1); - polyshape[0].push_back( - IntPoint( - pairA->NumberValue(), - pairB->NumberValue() - ) - ); - } - - bool wasReversed = false; - if (!ClipperLib::Orientation(polyshape[0])) { - ClipperLib::ReversePolygons(polyshape[0]); - wasReversed = true; - } - - double miterLimit = 3.0; - - ClipperLib::OffsetPolygons(polyshape, polydone, args[1]->NumberValue(), jtMiter, miterLimit); - - if (!wasReversed && ClipperLib::Orientation(polydone[0])) { - ClipperLib::ReversePolygons(polydone); - } - - if (polydone.size() > 0) { - Handle outPolygons = Array::New(); - for (Polygon::size_type g = 0; g < polydone.size(); ++g) { - Handle outPoints = Array::New(); - for (Polygon::size_type k = 0; k < polydone[g].size(); ++k) { - IntPoint ip = polydone[g][k]; - v8::Local x = v8::Number::New(ip.X); - v8::Local y = v8::Number::New(ip.Y); - outPoints->Set(outPoints->Length(), x); - outPoints->Set(outPoints->Length(), y); - } - outPolygons->Set(g, outPoints); - } - return scope.Close(outPolygons); - } - } catch (...) { - return scope.Close(v8::Boolean::New(false)); - } - } - return scope.Close(v8::Undefined()); +extern "C" void init(Handle target) { + target->Set(String::NewSymbol("setDebug"), FunctionTemplate::New(setDebug)->GetFunction()); + target->Set(String::NewSymbol("orientation"), FunctionTemplate::New(orientation)->GetFunction()); + target->Set(String::NewSymbol("offset"), FunctionTemplate::New(offset)->GetFunction()); + target->Set(String::NewSymbol("minimum"), FunctionTemplate::New(minimum)->GetFunction()); + target->Set(String::NewSymbol("clip"), FunctionTemplate::New(clip)->GetFunction()); + target->Set(String::NewSymbol("clean"), FunctionTemplate::New(clean)->GetFunction()); + target->Set(String::NewSymbol("fixOrientation"), FunctionTemplate::New(fixOrientation)->GetFunction()); + target->Set(String::NewSymbol("simplify"), FunctionTemplate::New(simplify)->GetFunction()); } -extern "C" { - static void init(Handle target) { - target->Set(String::NewSymbol("offset"), - FunctionTemplate::New(Method)->GetFunction()); - } - NODE_MODULE(clipper, init) -} \ No newline at end of file +NODE_MODULE(clipper, init) From dba9bef5250e9cca4236ca6ff367d3ec1b3c39d8 Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Thu, 5 Dec 2013 15:58:44 +0100 Subject: [PATCH 02/20] updated example for new datastructure --- examples/offset.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/offset.js b/examples/offset.js index 4572cea..321c4ef 100644 --- a/examples/offset.js +++ b/examples/offset.js @@ -5,14 +5,14 @@ clipper = require('../build/Release/clipper') * arguments: flattened_coordinate_path, offset * where: offset can be positive or negative (negative will go inside the polygon, positive will expand outside) */ -flattened_coordinate_path = [0, 0, 0, 10, 10, 10, 10, 0] +flattened_coordinate_path = [[[0, 0], [0, 10], [10, 10], [10, 0]]] // A negative offset gets an offset inside our Polygon: -inner_highlight_path = clipper.offset(flattened_coordinate_path, -2) +inner_highlight_path = clipper.offset(flattened_coordinate_path, 'integer', -2) console.log(inner_highlight_path) // returns [ [ 2, 2, 8, 2, 8, 8, 2, 8 ] ] // A positive offset gets an offset outside our Polygon: -outter_highlight_path = clipper.offset(flattened_coordinate_path, 2) +outter_highlight_path = clipper.offset(flattened_coordinate_path, 'integer', 2) console.log(outter_highlight_path) // returns [ [ -2, 12, -2, -2, 12, -2, 12, 12 ] ] \ No newline at end of file From 2b464129663f510a4a4db12705d616184d79aff8 Mon Sep 17 00:00:00 2001 From: uhucrew Date: Thu, 5 Dec 2013 18:06:44 +0100 Subject: [PATCH 03/20] Update README.md --- README.md | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d1a2c81..e2c57dd 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,37 @@ A node.js wrapper for the [Clipper polygon clipper](http://www.angusj.com/delphi ## Project State -Please note that this very light wrapper was built for an internal project, so almost -no effort has gone on to documentation of 1-to-1 binding of functions we haven't needed. +The wrapper exposes a lot of useful functions of clipper library to node.js. The function "minimum" is an outstanding feature to compute the most inner point of complex polygons with inner and outer borders. It is very useful to get the perfect point for a placemark insede the polygon. + + +## Data structures + +### Value + +A value is a javascript number. In clipper library all numbers are integer. So if floating point numbers are used it will be converted in the function call. Mandantory the functions have to be called with number type "integer" | "double". Double values should not be greater than ~1000 because of the upscaling to an integer. + +### Point + +A point is an array of two values: +```javascript +var p= [ v1, v2 ]; +´´´ + +### Polygon + +A polygon is an array of Points: +```javascript +var poly= [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ]; +´´´ + +### Polyshape + +The polyshape structure is an array of polygons. The first element in the array is the outer border polygon (points of this polygon clockwise oriented) followed by any number of inner border polygons: +```javascript +var polyShape= [ [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ], [ [ v9, v10 ], [ v11, v12 ], [ v13, v14 ], [ v15, v16 ] ] ]; +´´´ + -But we would be interested in expanding it over time and will happily accept pull requests -to expand the bindings. ## Bindings From 519e6ef1f4b5e39e452e85aa640abdb82d5976d3 Mon Sep 17 00:00:00 2001 From: uhucrew Date: Thu, 5 Dec 2013 18:08:05 +0100 Subject: [PATCH 04/20] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e2c57dd..81381c5 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,21 @@ A value is a javascript number. In clipper library all numbers are integer. So i A point is an array of two values: ```javascript var p= [ v1, v2 ]; -´´´ +``` ### Polygon A polygon is an array of Points: ```javascript var poly= [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ]; -´´´ +``` ### Polyshape The polyshape structure is an array of polygons. The first element in the array is the outer border polygon (points of this polygon clockwise oriented) followed by any number of inner border polygons: ```javascript var polyShape= [ [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ], [ [ v9, v10 ], [ v11, v12 ], [ v13, v14 ], [ v15, v16 ] ] ]; -´´´ +``` From 071f1e939d251702f0bd9bb79ea6c5c493244f88 Mon Sep 17 00:00:00 2001 From: uhucrew Date: Thu, 5 Dec 2013 18:09:26 +0100 Subject: [PATCH 05/20] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 81381c5..2e02806 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A node.js wrapper for the [Clipper polygon clipper](http://www.angusj.com/delphi ## Project State -The wrapper exposes a lot of useful functions of clipper library to node.js. The function "minimum" is an outstanding feature to compute the most inner point of complex polygons with inner and outer borders. It is very useful to get the perfect point for a placemark insede the polygon. +The wrapper exposes a lot of useful functions of clipper library to node.js. The function "minimum" is an outstanding feature to compute the most inner point of complex polygons with inner and outer borders. It is very useful to get the perfect point for a placemark inside the polygon. ## Data structures @@ -31,7 +31,10 @@ var poly= [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ]; The polyshape structure is an array of polygons. The first element in the array is the outer border polygon (points of this polygon clockwise oriented) followed by any number of inner border polygons: ```javascript -var polyShape= [ [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ], [ [ v9, v10 ], [ v11, v12 ], [ v13, v14 ], [ v15, v16 ] ] ]; +var polyShape= [ + [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ], + [ [ v9, v10 ], [ v11, v12 ], [ v13, v14 ], [ v15, v16 ] ] +]; ``` From de387c2984275c80f4db783eab98a293565768c8 Mon Sep 17 00:00:00 2001 From: uhucrew Date: Thu, 5 Dec 2013 18:28:30 +0100 Subject: [PATCH 06/20] Update README.md --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e02806..123241a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ var poly= [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ]; ### Polyshape -The polyshape structure is an array of polygons. The first element in the array is the outer border polygon (points of this polygon clockwise oriented) followed by any number of inner border polygons: +The polyshape structure is an array of polygons. The first element in the array is the outer border polygon (points of this polygon clockwise oriented) followed by any number of inner border polygons with orientation counterclockwise: ```javascript var polyShape= [ [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ], @@ -41,6 +41,42 @@ var polyShape= [ ## Bindings +### setDebug + +Sets the debug level in the module. Default is debug level 0. + +```javascript +'use strict'; + +var clipper= require('../build/Release/clipper'); + + +var square= [ [ [0, 0], [0, 10], [10, 10], [10, 0] ] ]; + + +/* + * set debug level + * messages goes directly to the console + * clipper.setDebug(debugLevel); + * + * debugLevel: integer 0..4 + * 0: no debug output + * 1..3: more debug output + * >=4: all debug messages + */ +console.log('function "fixOrientation" with debugLevel 0:\n'); +clipper.fixOrientation(square, 'integer'); + +console.log('\n\nfunction "fixOrientation" with debugLevel 3:\n'); +clipper.setDebug(3); +clipper.fixOrientation(square, 'integer'); + +console.log('\n\nfunction "fixOrientation" with debugLevel 4:\n'); +clipper.setDebug(4); +clipper.fixOrientation(square, 'integer'); +``` + + ### Offset See the [clipper documentation for Offset](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/OffsetPaths.htm)) From ab217ba955559d8200a786dcb1d17ed9c848ebc1 Mon Sep 17 00:00:00 2001 From: uhucrew Date: Thu, 5 Dec 2013 18:44:51 +0100 Subject: [PATCH 07/20] Update README.md --- README.md | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 123241a..72b3090 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A node.js wrapper for the [Clipper polygon clipper](http://www.angusj.com/delphi ## Project State -The wrapper exposes a lot of useful functions of clipper library to node.js. The function "minimum" is an outstanding feature to compute the most inner point of complex polygons with inner and outer borders. It is very useful to get the perfect point for a placemark inside the polygon. +The wrapper exposes and extends functions of clipper library to node.js. The function "minimum" is an outstanding feature to compute the most inner point of complex polygons with inner and outer borders. It is very useful to get the perfect point for a placemark inside the polygon. ## Data structures @@ -29,7 +29,7 @@ var poly= [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ]; ### Polyshape -The polyshape structure is an array of polygons. The first element in the array is the outer border polygon (points of this polygon clockwise oriented) followed by any number of inner border polygons with orientation counterclockwise: +The polyShape structure is an array of polygons. The first element in the array is the outer border polygon (points of this polygon counterclockwise oriented) followed by any number of inner border polygons with orientation clockwise: ```javascript var polyShape= [ [ [ v1, v2 ], [ v3, v4 ], [ v5, v6 ], [ v7, v8 ] ], @@ -44,7 +44,7 @@ var polyShape= [ ### setDebug Sets the debug level in the module. Default is debug level 0. - +Example: ```javascript 'use strict'; @@ -77,6 +77,36 @@ clipper.fixOrientation(square, 'integer'); ``` + +### orientation + +Get orientation of all polygons in a polyShape array as array of boolean. +Example: +```javascript +'use strict'; + +var clipper= require('../build/Release/clipper'); + + +var squareOuter= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; +var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; +var polyShape= [ squareOuter, squareInner ]; + + +/* + * get orientation of all polygons in a polyShape array + * + * clipper.orientation(polyShape, numberType); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * + * result: array of boolean, true: counterclockwise, false: clockwise + */ +console.log(clipper.orientation(polyShape, 'integer')); +``` + + ### Offset See the [clipper documentation for Offset](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/OffsetPaths.htm)) From eaae5e1997109a08ceea2d303bbc261e6829f3f7 Mon Sep 17 00:00:00 2001 From: uhucrew Date: Thu, 5 Dec 2013 19:53:18 +0100 Subject: [PATCH 08/20] Update README.md --- README.md | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 72b3090..70f1d90 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ A node.js wrapper for the [Clipper polygon clipper](http://www.angusj.com/delphi The wrapper exposes and extends functions of clipper library to node.js. The function "minimum" is an outstanding feature to compute the most inner point of complex polygons with inner and outer borders. It is very useful to get the perfect point for a placemark inside the polygon. +## Installation + +```bash +node-gyp configure +node-gyp build +``` ## Data structures @@ -38,7 +44,6 @@ var polyShape= [ ``` - ## Bindings ### setDebug @@ -50,10 +55,8 @@ Example: var clipper= require('../build/Release/clipper'); - var square= [ [ [0, 0], [0, 10], [10, 10], [10, 0] ] ]; - /* * set debug level * messages goes directly to the console @@ -77,7 +80,6 @@ clipper.fixOrientation(square, 'integer'); ``` - ### orientation Get orientation of all polygons in a polyShape array as array of boolean. @@ -87,12 +89,10 @@ Example: var clipper= require('../build/Release/clipper'); - var squareOuter= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; var polyShape= [ squareOuter, squareInner ]; - /* * get orientation of all polygons in a polyShape array * @@ -107,28 +107,35 @@ console.log(clipper.orientation(polyShape, 'integer')); ``` -### Offset +### offset -See the [clipper documentation for Offset](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/OffsetPaths.htm)) -in order to get an idea of how this is used. +See the [clipper documentation for Offset](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/OffsetPaths.htm) in order to get an idea of how this is used. ```javascript -clipper = require('../build/Release/clipper') - -/*a - ## Offset - * arguments: flattened_coordinate_path, offset - * where: offset can be positive or negative (negative will go inside the polygon, positive will expand outside) -*/ -flattened_coordinate_path = [0, 0, 0, 10, 10, 10, 10, 0] - -// A negative offset gets an offset inside our Polygon: -inner_highlight_path = clipper.offset(flattened_coordinate_path, -2) -console.log(inner_highlight_path) -// returns [ [ 2, 2, 8, 2, 8, 8, 2, 8 ] ] - -// A positive offset gets an offset outside our Polygon: -outter_highlight_path = clipper.offset(flattened_coordinate_path, 2) -console.log(outter_highlight_path) -// returns [ [ -2, 12, -2, -2, 12, -2, 12, 12 ] ] +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var squareOuter= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; +var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; +var polyShape= [ squareOuter, squareInner ]; + +/* + * offset polygons in a polyShape array. + * positive offset: outer borders are expanded, inner borders are shrinked + * negative offset: outer borders are shrinked, inner borders are expanded + * + * clipper.offset(polyShape, numberType, offset); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * offset: number + * + * result: polyShape with offset + */ +console.log('original polygon:\n', polyShape); +console.log('\n\nshrinked, offset= -1:\n', clipper.offset(polyShape, 'integer', -1)); +console.log('\n\nexpanded, offset= 2, inner border disappears:\n', clipper.offset(polyShape, 'integer', 2)); ``` + + From d9ca6066c58a3be839867502027bda18a7e1717a Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Thu, 5 Dec 2013 19:54:39 +0100 Subject: [PATCH 09/20] fixed bug with empty arrays in internal function v8ArrayToPolygons --- src/init.cc | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/init.cc b/src/init.cc index 4ad6cb2..d7ac1ae 100644 --- a/src/init.cc +++ b/src/init.cc @@ -33,13 +33,24 @@ Polygons v8ArrayToPolygons(v8::Handle inOutPolygons, bool doubleType) { if (debug > 1) std::cout << "polygonArray: length: " << len << std::endl; #endif + //no polyshape with less than 1 polygon possible + if (len < 1) return polyshape; + for (int i = 0; i < len; i++) { v8::Local polyLine = v8::Local::Cast(inOutPolygons->Get(i)); #ifdef DEBUG if (debug > 2) std::cout << "polyLine: length: " << polyLine->Length() << std::endl; #endif + + //no polygon with less than 3 points + if (polyLine->Length() < 3) continue; + for (unsigned int j = 0; j < polyLine->Length(); j++) { v8::Local point = v8::Local::Cast(polyLine->Get(j)); + + //no point with less than 2 numbers + if (point->Length() < 2) continue; + v8::Local x = point->Get(0); v8::Local y = point->Get(1); IntPoint p; @@ -117,7 +128,7 @@ void doFixOrientation(Polygons &polyshape) { } -v8::Local checkOffsetArguments(const Arguments& args, int checkLength) { +v8::Local checkArguments(const Arguments& args, int checkLength) { v8::Local result = String::New(""); /* check args for wrong function call @@ -179,7 +190,7 @@ v8::Handle orientation(const Arguments& args) { bool doubleType = false; - v8::Local errMsg = checkOffsetArguments(args, 2); + v8::Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { ThrowException(Exception::TypeError(errMsg)); return scope.Close(v8::Undefined()); @@ -211,7 +222,7 @@ v8::Handle offset(const Arguments& args) { long delta; bool doubleType = false; - v8::Local errMsg = checkOffsetArguments(args, 3); + v8::Local errMsg = checkArguments(args, 3); if (errMsg->Length() > 0) { ThrowException(Exception::TypeError(errMsg)); return scope.Close(v8::Undefined()); @@ -281,7 +292,7 @@ v8::Handle minimum(const Arguments& args) { double miterLimit = 30.0; bool doubleType = false; - v8::Local errMsg = checkOffsetArguments(args, 2); + v8::Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { ThrowException(Exception::TypeError(errMsg)); return scope.Close(v8::Undefined()); @@ -399,7 +410,7 @@ v8::Handle clip(const Arguments& args) { bool doubleType = false; ClipType clipType = ctIntersection; - v8::Local errMsg = checkOffsetArguments(args, 1); + v8::Local errMsg = checkArguments(args, 1); if (errMsg->Length() > 0) { ThrowException(Exception::TypeError(errMsg)); return scope.Close(v8::Undefined()); @@ -474,7 +485,7 @@ v8::Handle clean(const Arguments& args) { bool doubleType = false; double distance = 1.415; - v8::Local errMsg = checkOffsetArguments(args, 2); + v8::Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { ThrowException(Exception::TypeError(errMsg)); return scope.Close(v8::Undefined()); @@ -515,7 +526,7 @@ v8::Handle fixOrientation(const Arguments& args) { v8::HandleScope scope; bool doubleType = false; - v8::Local errMsg = checkOffsetArguments(args, 2); + v8::Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { ThrowException(Exception::TypeError(errMsg)); return scope.Close(v8::Undefined()); @@ -541,7 +552,7 @@ v8::Handle simplify(const Arguments& args) { v8::HandleScope scope; bool doubleType = false; - v8::Local errMsg = checkOffsetArguments(args, 2); + v8::Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { ThrowException(Exception::TypeError(errMsg)); return scope.Close(v8::Undefined()); From f2e551392961ac0ff7d41764c7a56f2cfc5dc998 Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Thu, 5 Dec 2013 19:55:50 +0100 Subject: [PATCH 10/20] added examples for offset, orientation and setDebug functions --- examples/offset.js | 36 +++++++++++++++++++++--------------- examples/orientation.js | 19 +++++++++++++++++++ examples/setDebug.js | 26 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 examples/orientation.js create mode 100644 examples/setDebug.js diff --git a/examples/offset.js b/examples/offset.js index 321c4ef..eb425ab 100644 --- a/examples/offset.js +++ b/examples/offset.js @@ -1,18 +1,24 @@ -clipper = require('../build/Release/clipper') +'use strict'; -/*a - ## Offset - * arguments: flattened_coordinate_path, offset - * where: offset can be positive or negative (negative will go inside the polygon, positive will expand outside) -*/ -flattened_coordinate_path = [[[0, 0], [0, 10], [10, 10], [10, 0]]] +var clipper= require('../build/Release/clipper'); -// A negative offset gets an offset inside our Polygon: -inner_highlight_path = clipper.offset(flattened_coordinate_path, 'integer', -2) -console.log(inner_highlight_path) -// returns [ [ 2, 2, 8, 2, 8, 8, 2, 8 ] ] +var squareOuter= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; +var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; +var polyShape= [ squareOuter, squareInner ]; -// A positive offset gets an offset outside our Polygon: -outter_highlight_path = clipper.offset(flattened_coordinate_path, 'integer', 2) -console.log(outter_highlight_path) -// returns [ [ -2, 12, -2, -2, 12, -2, 12, 12 ] ] \ No newline at end of file +/* + * offset polygons in a polyShape array. + * positive offset: outer borders are expanded, inner borders are shrinked + * negative offset: outer borders are shrinked, inner borders are expanded + * + * clipper.offset(polyShape, numberType, offset); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * offset: number + * + * result: polyShape with offset + */ +console.log('original polygon:\n', polyShape); +console.log('\n\nshrinked, offset= -1:\n', clipper.offset(polyShape, 'integer', -1)); +console.log('\n\nexpanded, offset= 2, inner border disappears:\n', clipper.offset(polyShape, 'integer', 2)); diff --git a/examples/orientation.js b/examples/orientation.js new file mode 100644 index 0000000..0eb5040 --- /dev/null +++ b/examples/orientation.js @@ -0,0 +1,19 @@ +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var squareOuter= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; +var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; +var polyShape= [ squareOuter, squareInner ]; + +/* + * get orientation of all polygons in a polyShape array + * + * clipper.orientation(polyShape, numberType); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * + * result: array of boolean, true: counterclockwise, false: clockwise + */ +console.log(clipper.orientation(polyShape, 'integer')); diff --git a/examples/setDebug.js b/examples/setDebug.js new file mode 100644 index 0000000..8072ec4 --- /dev/null +++ b/examples/setDebug.js @@ -0,0 +1,26 @@ +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var square= [ [ [0, 0], [0, 10], [10, 10], [10, 0] ] ]; + +/* + * set debug level + * messages goes directly to the console + * clipper.setDebug(debugLevel); + * + * debugLevel: integer 0..4 + * 0: no debug output + * 1..3: more debug output + * >=4: all debug messages + */ +console.log('function "fixOrientation" with debugLevel 0:\n'); +clipper.fixOrientation(square, 'integer'); + +console.log('\n\nfunction "fixOrientation" with debugLevel 3:\n'); +clipper.setDebug(3); +clipper.fixOrientation(square, 'integer'); + +console.log('\n\nfunction "fixOrientation" with debugLevel 4:\n'); +clipper.setDebug(4); +clipper.fixOrientation(square, 'integer'); From 454e59df0e8e36f2ffbbd9eec8ee1a5723c3955a Mon Sep 17 00:00:00 2001 From: uhucrew Date: Thu, 5 Dec 2013 21:38:08 +0100 Subject: [PATCH 11/20] Update README.md --- README.md | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 70f1d90..0609084 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,8 @@ var square= [ [ [0, 0], [0, 10], [10, 10], [10, 0] ] ]; /* * set debug level * messages goes directly to the console - * clipper.setDebug(debugLevel); + * + * result= clipper.setDebug(debugLevel); * * debugLevel: integer 0..4 * 0: no debug output @@ -96,7 +97,7 @@ var polyShape= [ squareOuter, squareInner ]; /* * get orientation of all polygons in a polyShape array * - * clipper.orientation(polyShape, numberType); + * result= clipper.orientation(polyShape, numberType); * * polyShape: polygons as polyShape array * numberType: 'integer' || 'double' @@ -109,7 +110,7 @@ console.log(clipper.orientation(polyShape, 'integer')); ### offset -See the [clipper documentation for Offset](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/OffsetPaths.htm) in order to get an idea of how this is used. +See the [clipper documentation for offset](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/OffsetPaths.htm) in order to get an idea of how this is used. ```javascript 'use strict'; @@ -125,7 +126,7 @@ var polyShape= [ squareOuter, squareInner ]; * positive offset: outer borders are expanded, inner borders are shrinked * negative offset: outer borders are shrinked, inner borders are expanded * - * clipper.offset(polyShape, numberType, offset); + * result= clipper.offset(polyShape, numberType, offset); * * polyShape: polygons as polyShape array * numberType: 'integer' || 'double' @@ -139,3 +140,131 @@ console.log('\n\nexpanded, offset= 2, inner border disappears:\n', clipper.offse ``` +### clip + +Clip two polygons. The result may produce multiple polygons. See also the [clipper documentation for clip](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/ClipType.htm) + +```javascript +'use strict'; + +var clipper= require('../build/Release/clipper'); +var util= require('util'); + +var outerSubject= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; +var innerSubject= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; +var polySubject= [ outerSubject, innerSubject ]; + +var outerClip= [ [20, 0], [150, 100], [0, 50], [0, 0] ]; +var innerClip= [ [13, 13], [13, 17], [17, 17], [17, 13] ]; +var polyClip= [ outerClip, innerClip ]; + + +/* + * compute minimum polygon from a polyShape array + * the resulting polygon is normally a very very small triangle + * this algorithm cannot work for two exact centered rectangles + * it wont work for small integer numbers too, double type is recommended + * with successive approximation a polygon offset factor is generated + * and the shrinked result is returned + * this process is CPU intensive for complex polygons + * + * result= clipper.minimum(polySubject, polyClip, numberType, clipType); + * + * polySubject: polygons as polyShape array + * polyClip: polygons as polyShape array + * numberType: 'integer' || 'double' + * clipType: 'ctIntersection' || 'ctUnion' || 'ctDifference' || 'ctXor' + * + * result: polyShape with minimum polygon + */ +console.log('polygon subject:\n', polySubject); +console.log('\n\npolygon clip:\n', polyClip); +console.log('\n\nsolution:\n', util.inspect(clipper.clip(polySubject, polyClip, 'integer', 'ctIntersection'), false, 5)); +``` + + +### clean + +Clean polygon. See also the [clipper documentation for clean](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/CleanPolygons.htm) + +```javascript +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var outer= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; +var inner= [ [3, 3], [3, 7], [7, 6], [7, 3] ]; +var polyShape= [ outer, inner ]; + +/* + * clean polygon + * + * result= clipper.clean(polyShape, numberType, distance); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * distance: distance of neighbour vertices + * + * result: polyShape with minimum polygon + */ +console.log('original polygon:\n', polyShape); +console.log('\n\ncleaned polygon:\n', clipper.clean(polyShape, 'integer', 3)); +``` + + +### simplify + +Simplify polygon. See also the [clipper documentation for simplify](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygons.htm) + +```javascript +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var outer= [ [10, 0], [100, 100], [101, 100], [100, 100], [0, 10], [0, 0] ]; +var inner= [ [3, 3], [3, 7], [7, 6], [7, 3] ]; +var polyShape= [ outer, inner ]; + +/* + * simplify polygon + * + * result= clipper.simplify(polyShape, numberType); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * + * result: polyShape with simplified polygon + */ +console.log('original polygon:\n', polyShape); +console.log('\n\nsimplified polygon:\n', clipper.simplify(polyShape, 'integer')); +``` + + +### fixOrientation + +Fix orientation of all polygons an a polyShape array. + +```javascript +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var counterclockwise= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; +var clockwise= [ [3, 3], [7, 7], [3, 7], [7, 3] ]; +var polyShape= [ counterclockwise, clockwise ]; + +/* + * fix orientation of polygons in a polyShape array + * first element (outer border): orientation must be true + * all other elements (inner borders): orientation false + * + * result= clipper.fixOrientation(polyShape, numberType); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * + * result: polyShape with fixed orientations + */ +console.log('original polygon:\n', polyShape); +console.log('\n\norientation of inner and outer borders fixed:\n', clipper.fixOrientation(polyShape, 'integer')); +``` From fcbb09067506d4cca91ae86b3eb126bdf9a0f07b Mon Sep 17 00:00:00 2001 From: uhucrew Date: Thu, 5 Dec 2013 21:44:02 +0100 Subject: [PATCH 12/20] Update README.md --- README.md | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0609084..b935afe 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ var square= [ [ [0, 0], [0, 10], [10, 10], [10, 0] ] ]; * set debug level * messages goes directly to the console * - * result= clipper.setDebug(debugLevel); + * clipper.setDebug(debugLevel); * * debugLevel: integer 0..4 * 0: no debug output @@ -140,9 +140,46 @@ console.log('\n\nexpanded, offset= 2, inner border disappears:\n', clipper.offse ``` +### minimum + +Generate minimum polygon from polyShape. Read comments in the example to see, how it works. + +```javascript +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var squareOuter= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; +var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; +var polyShape= [ squareOuter, squareInner ]; + +/* + * compute minimum polygon from a polyShape array + * the resulting polygon is normally a very very small triangle + * this algorithm cannot work for two exact centered rectangles + * it wont work for small integer numbers too, double type is recommended + * with successive approximation a polygon offset factor is generated + * and the shrinked result is returned + * this process is CPU intensive for complex polygons + * + * result= clipper.minimum(polyShape, numberType, unused, joinType, miterLimit); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * unused: unused + * joinType: 'jtMiter' || 'jtSquare' || 'jtRound' + * miterLimit: limit of iterations for offset calculation in clipper lib + * + * result: polyShape with minimum polygon + */ +console.log('original polygon:\n', polyShape); +console.log('\n\nminimum polygon:\n', clipper.minimum(polyShape, 'integer', 0, 'jtMiter', 10)); +``` + + ### clip -Clip two polygons. The result may produce multiple polygons. See also the [clipper documentation for clip](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/ClipType.htm) +Clip two polygons. The result may produce multiple polygons. See also the [clipper documentation for clip](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/ClipType.htm). ```javascript 'use strict'; @@ -175,7 +212,7 @@ var polyClip= [ outerClip, innerClip ]; * numberType: 'integer' || 'double' * clipType: 'ctIntersection' || 'ctUnion' || 'ctDifference' || 'ctXor' * - * result: polyShape with minimum polygon + * result: polyShape with clipped polygons */ console.log('polygon subject:\n', polySubject); console.log('\n\npolygon clip:\n', polyClip); @@ -185,7 +222,7 @@ console.log('\n\nsolution:\n', util.inspect(clipper.clip(polySubject, polyClip, ### clean -Clean polygon. See also the [clipper documentation for clean](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/CleanPolygons.htm) +Clean polygon. See also the [clipper documentation for clean](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/CleanPolygons.htm). ```javascript 'use strict'; @@ -214,7 +251,7 @@ console.log('\n\ncleaned polygon:\n', clipper.clean(polyShape, 'integer', 3)); ### simplify -Simplify polygon. See also the [clipper documentation for simplify](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygons.htm) +Simplify polygon. See also the [clipper documentation for simplify](http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygons.htm). ```javascript 'use strict'; From 9e40bd247c5e3d794c98820c12603791aaab7148 Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Thu, 5 Dec 2013 21:45:01 +0100 Subject: [PATCH 13/20] Examples for all functions with descriptions added --- examples/clean.js | 21 +++++++++++++++++++++ examples/clip.js | 35 +++++++++++++++++++++++++++++++++++ examples/fixOrientation.js | 22 ++++++++++++++++++++++ examples/minimum.js | 29 +++++++++++++++++++++++++++++ examples/offset.js | 2 +- examples/orientation.js | 2 +- examples/simplify.js | 20 ++++++++++++++++++++ 7 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 examples/clean.js create mode 100644 examples/clip.js create mode 100644 examples/fixOrientation.js create mode 100644 examples/minimum.js create mode 100644 examples/simplify.js diff --git a/examples/clean.js b/examples/clean.js new file mode 100644 index 0000000..e31a9a4 --- /dev/null +++ b/examples/clean.js @@ -0,0 +1,21 @@ +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var outer= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; +var inner= [ [3, 3], [3, 7], [7, 6], [7, 3] ]; +var polyShape= [ outer, inner ]; + +/* + * clean polygon + * + * result= clipper.clean(polyShape, numberType, distance); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * distance: distance of neighbour vertices + * + * result: polyShape with minimum polygon + */ +console.log('original polygon:\n', polyShape); +console.log('\n\ncleaned polygon:\n', clipper.clean(polyShape, 'integer', 3)); diff --git a/examples/clip.js b/examples/clip.js new file mode 100644 index 0000000..bdbae4a --- /dev/null +++ b/examples/clip.js @@ -0,0 +1,35 @@ +'use strict'; + +var clipper= require('../build/Release/clipper'); +var util= require('util'); + +var outerSubject= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; +var innerSubject= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; +var polySubject= [ outerSubject, innerSubject ]; + +var outerClip= [ [20, 0], [150, 100], [0, 50], [0, 0] ]; +var innerClip= [ [13, 13], [13, 17], [17, 17], [17, 13] ]; +var polyClip= [ outerClip, innerClip ]; + + +/* + * compute minimum polygon from a polyShape array + * the resulting polygon is normally a very very small triangle + * this algorithm cannot work for two exact centered rectangles + * it wont work for small integer numbers too, double type is recommended + * with successive approximation a polygon offset factor is generated + * and the shrinked result is returned + * this process is CPU intensive for complex polygons + * + * result= clipper.clip(polySubject, polyClip, numberType, clipType); + * + * polySubject: polygons as polyShape array + * polyClip: polygons as polyShape array + * numberType: 'integer' || 'double' + * clipType: 'ctIntersection' || 'ctUnion' || 'ctDifference' || 'ctXor' + * + * result: polyShape with minimum polygon + */ +console.log('polygon subject:\n', polySubject); +console.log('\n\npolygon clip:\n', polyClip); +console.log('\n\nsolution:\n', util.inspect(clipper.clip(polySubject, polyClip, 'integer', 'ctIntersection'), false, 5)); diff --git a/examples/fixOrientation.js b/examples/fixOrientation.js new file mode 100644 index 0000000..82004c6 --- /dev/null +++ b/examples/fixOrientation.js @@ -0,0 +1,22 @@ +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var counterclockwise= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; +var clockwise= [ [3, 3], [7, 7], [3, 7], [7, 3] ]; +var polyShape= [ counterclockwise, clockwise ]; + +/* + * fix orientation of polygons in a polyShape array + * first element (outer border): orientation must be true + * all other elements (inner borders): orientation false + * + * result= clipper.fixOrientation(polyShape, numberType); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * + * result: polyShape with fixed orientations + */ +console.log('original polygon:\n', polyShape); +console.log('\n\norientation of inner and outer borders fixed:\n', clipper.fixOrientation(polyShape, 'integer')); diff --git a/examples/minimum.js b/examples/minimum.js new file mode 100644 index 0000000..4e51aba --- /dev/null +++ b/examples/minimum.js @@ -0,0 +1,29 @@ +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var squareOuter= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; +var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; +var polyShape= [ squareOuter, squareInner ]; + +/* + * compute minimum polygon from a polyShape array + * the resulting polygon is normally a very very small triangle + * this algorithm cannot work for two exact centered rectangles + * it wont work for small integer numbers too, double type is recommended + * with successive approximation a polygon offset factor is generated + * and the shrinked result is returned + * this process is CPU intensive for complex polygons + * + * result= clipper.minimum(polyShape, numberType, unused, joinType, miterLimit); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * unused: unused + * joinType: 'jtMiter' || 'jtSquare' || 'jtRound' + * miterLimit: limit of iterations for offset calculation in clipper lib + * + * result: polyShape with minimum polygon + */ +console.log('original polygon:\n', polyShape); +console.log('\n\nminimum polygon:\n', clipper.minimum(polyShape, 'integer', 0, 'jtMiter', 10)); diff --git a/examples/offset.js b/examples/offset.js index eb425ab..03f6590 100644 --- a/examples/offset.js +++ b/examples/offset.js @@ -11,7 +11,7 @@ var polyShape= [ squareOuter, squareInner ]; * positive offset: outer borders are expanded, inner borders are shrinked * negative offset: outer borders are shrinked, inner borders are expanded * - * clipper.offset(polyShape, numberType, offset); + * result= clipper.offset(polyShape, numberType, offset); * * polyShape: polygons as polyShape array * numberType: 'integer' || 'double' diff --git a/examples/orientation.js b/examples/orientation.js index 0eb5040..4b650f5 100644 --- a/examples/orientation.js +++ b/examples/orientation.js @@ -9,7 +9,7 @@ var polyShape= [ squareOuter, squareInner ]; /* * get orientation of all polygons in a polyShape array * - * clipper.orientation(polyShape, numberType); + * result= clipper.orientation(polyShape, numberType); * * polyShape: polygons as polyShape array * numberType: 'integer' || 'double' diff --git a/examples/simplify.js b/examples/simplify.js new file mode 100644 index 0000000..fecb618 --- /dev/null +++ b/examples/simplify.js @@ -0,0 +1,20 @@ +'use strict'; + +var clipper= require('../build/Release/clipper'); + +var outer= [ [10, 0], [100, 100], [101, 100], [100, 100], [0, 10], [0, 0] ]; +var inner= [ [3, 3], [3, 7], [7, 6], [7, 3] ]; +var polyShape= [ outer, inner ]; + +/* + * simplify polygon + * + * result= clipper.simplify(polyShape, numberType); + * + * polyShape: polygons as polyShape array + * numberType: 'integer' || 'double' + * + * result: polyShape with simplified polygon + */ +console.log('original polygon:\n', polyShape); +console.log('\n\nsimplified polygon:\n', clipper.simplify(polyShape, 'integer')); From 4820b16129136e82c08e962a5d13f5db97adfecf Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Thu, 5 Dec 2013 23:20:14 +0100 Subject: [PATCH 14/20] fixed wrong description in example --- README.md | 12 ++++-------- examples/clip.js | 8 ++------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b935afe..4fd3166 100644 --- a/README.md +++ b/README.md @@ -197,22 +197,18 @@ var polyClip= [ outerClip, innerClip ]; /* - * compute minimum polygon from a polyShape array - * the resulting polygon is normally a very very small triangle - * this algorithm cannot work for two exact centered rectangles - * it wont work for small integer numbers too, double type is recommended - * with successive approximation a polygon offset factor is generated - * and the shrinked result is returned + * clip two polygons + * multiple types of clipping are available * this process is CPU intensive for complex polygons * - * result= clipper.minimum(polySubject, polyClip, numberType, clipType); + * result= clipper.clip(polySubject, polyClip, numberType, clipType); * * polySubject: polygons as polyShape array * polyClip: polygons as polyShape array * numberType: 'integer' || 'double' * clipType: 'ctIntersection' || 'ctUnion' || 'ctDifference' || 'ctXor' * - * result: polyShape with clipped polygons + * result: polyShape with minimum polygon */ console.log('polygon subject:\n', polySubject); console.log('\n\npolygon clip:\n', polyClip); diff --git a/examples/clip.js b/examples/clip.js index bdbae4a..eda53f2 100644 --- a/examples/clip.js +++ b/examples/clip.js @@ -13,12 +13,8 @@ var polyClip= [ outerClip, innerClip ]; /* - * compute minimum polygon from a polyShape array - * the resulting polygon is normally a very very small triangle - * this algorithm cannot work for two exact centered rectangles - * it wont work for small integer numbers too, double type is recommended - * with successive approximation a polygon offset factor is generated - * and the shrinked result is returned + * clip two polygons + * multiple types of clipping are available * this process is CPU intensive for complex polygons * * result= clipper.clip(polySubject, polyClip, numberType, clipType); From e7538c68a5ea485b0764301c71ff49c5f3b2a467 Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Tue, 26 Jul 2016 16:53:34 +0200 Subject: [PATCH 15/20] Changed to v8 API --- src/init.cc | 329 +++++++++++++++++++++++++++------------------------- 1 file changed, 170 insertions(+), 159 deletions(-) diff --git a/src/init.cc b/src/init.cc index d7ac1ae..ba9146e 100644 --- a/src/init.cc +++ b/src/init.cc @@ -7,28 +7,38 @@ #include "clipper.hpp" #include "clipper.cpp" -using namespace node; -using namespace v8; +namespace demo { + +using v8::FunctionCallbackInfo; +using v8::Exception; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Array; +using v8::Value; +using v8::Handle; +using v8::Number; +using v8::Boolean; + using namespace ClipperLib; + const unsigned long doubleFactor = 0x4000000000000; int debug = 0; -v8::Handle setDebug(const Arguments& args) { - v8::HandleScope scope; - +void setDebug(const FunctionCallbackInfo& args) { if (args.Length() > 0 && args[0]->IsNumber()) { debug = args[0]->NumberValue(); } - return scope.Close(v8::Number::New(debug)); + args.GetReturnValue().Set(debug); } - -Polygons v8ArrayToPolygons(v8::Handle inOutPolygons, bool doubleType) { +Paths v8ArrayToPolygons(Handle inOutPolygons, bool doubleType) { int len = inOutPolygons->Length(); - Polygons polyshape(len); + Paths polyshape(len); #ifdef DEBUG if (debug > 1) std::cout << "polygonArray: length: " << len << std::endl; #endif @@ -37,7 +47,7 @@ Polygons v8ArrayToPolygons(v8::Handle inOutPolygons, bool doubleType) { if (len < 1) return polyshape; for (int i = 0; i < len; i++) { - v8::Local polyLine = v8::Local::Cast(inOutPolygons->Get(i)); + Local polyLine = Local::Cast(inOutPolygons->Get(i)); #ifdef DEBUG if (debug > 2) std::cout << "polyLine: length: " << polyLine->Length() << std::endl; #endif @@ -46,13 +56,13 @@ Polygons v8ArrayToPolygons(v8::Handle inOutPolygons, bool doubleType) { if (polyLine->Length() < 3) continue; for (unsigned int j = 0; j < polyLine->Length(); j++) { - v8::Local point = v8::Local::Cast(polyLine->Get(j)); + Local point = Local::Cast(polyLine->Get(j)); //no point with less than 2 numbers if (point->Length() < 2) continue; - v8::Local x = point->Get(0); - v8::Local y = point->Get(1); + Local x = point->Get(0); + Local y = point->Get(1); IntPoint p; if (doubleType) { p = IntPoint( @@ -83,22 +93,23 @@ long signedSum(long a, long b) { } -v8::Handle polygonsToV8Array(Polygons polygons, bool doubleType) { - v8::Handle result = v8::Array::New(); +Handle polygonsToV8Array(Isolate* isolate, Paths polygons, bool doubleType) { + Handle result = Array::New(isolate); for (unsigned int i = 0; i < polygons.size(); i++) { - v8::Handle points = v8::Array::New(); + Handle points = Array::New(isolate); for (unsigned int k = 0; k < polygons[i].size(); k++) { - IntPoint ip = polygons[i][k]; - v8::Local x; - v8::Local y; + IntPoint ip = polygons[i][k]; + Local x; + Local y; if (doubleType) { - x = v8::Number::New((double)ip.X / (double)doubleFactor); - y = v8::Number::New((double)ip.Y / (double)doubleFactor); + + x = Number::New(isolate, (double)ip.X / (double)doubleFactor); + y = Number::New(isolate, (double)ip.Y / (double)doubleFactor); } else { - x = v8::Number::New(ip.X); - y = v8::Number::New(ip.Y); + x = Number::New(isolate, ip.X); + y = Number::New(isolate, ip.Y); } - v8::Handle point = v8::Array::New(); + Handle point = Array::New(isolate); point->Set(point->Length(), x); point->Set(point->Length(), y); points->Set(points->Length(), point); @@ -109,17 +120,17 @@ v8::Handle polygonsToV8Array(Polygons polygons, bool doubleType) { } -void doFixOrientation(Polygons &polyshape) { - if (!ClipperLib::Orientation(polyshape[0])) { - ClipperLib::ReversePolygon(polyshape[0]); +void doFixOrientation(Paths &polyshape) { + if (!Orientation(polyshape[0])) { + ReversePath(polyshape[0]); #ifdef DEBUG if (debug > 0) std::cout << "doFixOrientation: outerPoints reversed" << std::endl; #endif } for (unsigned int i = 1; i < polyshape.size(); i++) { - if (ClipperLib::Orientation(polyshape[i])) { - ClipperLib::ReversePolygon(polyshape[i]); + if (Orientation(polyshape[i])) { + ReversePath(polyshape[i]); #ifdef DEBUG if (debug > 0) std::cout << "doFixOrientation: innerPoints reversed: " << i - 1 << std::endl; #endif @@ -128,8 +139,10 @@ void doFixOrientation(Polygons &polyshape) { } -v8::Local checkArguments(const Arguments& args, int checkLength) { - v8::Local result = String::New(""); +Local checkArguments(const FunctionCallbackInfo& args, int checkLength) { + Isolate* isolate = args.GetIsolate(); + + Local result = String::Empty(isolate); /* check args for wrong function call * args[0]: array of outerPoints and innerPoints arrays @@ -138,17 +151,19 @@ v8::Local checkArguments(const Arguments& args, int checkLength) { * args[3]: optional: Jointype (jtMiter, jtSquare or jtRound) * args[4]: optional: double MiterLimit */ + if (args.Length() < 2) { - result = String::New("Too few arguments! At least 'polyshape[][][]' and 'pointType' are required!"); + result = String::NewFromUtf8(isolate, "Too few arguments! At least 'polyshape[][][]' and 'pointType' are required!"); return result; } + if (args.Length() < checkLength) { - result = String::New("Too few arguments!"); + result = String::NewFromUtf8(isolate, "Too few arguments!"); return result; } if (!args[0]->IsArray()) { - result = String::Concat(String::New("Wrong argument 'polyshape': array[shapes][points][point] required: "), args[0]->ToString()); + result = String::Concat(String::NewFromUtf8(isolate, "Wrong argument 'polyshape': array[shapes][points][point] required: "), args[0]->ToString()); return result; } @@ -156,79 +171,82 @@ v8::Local checkArguments(const Arguments& args, int checkLength) { return result; } - if (!args[1]->IsString() || !(args[1]->Equals(String::New("double")) || args[1]->Equals(String::New("integer")))) { - result = String::Concat(String::New("Wrong argument 'pointType': 'double' || 'integer' required: "), args[1]->ToString()); + if (!args[1]->IsString() || !(args[1]->Equals(String::NewFromUtf8(isolate, "double")) || args[1]->Equals(String::NewFromUtf8(isolate, "integer")))) { + result = String::Concat(String::NewFromUtf8(isolate, "Wrong argument 'pointType': 'double' || 'integer' required: "), args[1]->ToString()); return result; } if ((args.Length() > 2) && (checkLength > 2)) { if (!args[2]->IsNumber()) { - result = String::Concat(String::New("Wrong argument 'delta' || 'distance': number required: "), args[2]->ToString()); + result = String::Concat(String::NewFromUtf8(isolate, "Wrong argument 'delta' || 'distance': number required: "), args[2]->ToString()); return result; } } if ((args.Length() > 3) && (checkLength > 3)) { - if (!args[3]->IsString() || !(args[3]->Equals(String::New("jtMiter")) || args[3]->Equals(String::New("jtSquare")) || args[3]->Equals(String::New("jtRound")))) { - result = String::Concat(String::New("Wrong argument 'joinType': 'jtMiter' || 'jtSquare' || 'jtRound' required: "), args[3]->ToString()); + if (!args[3]->IsString() || !(args[3]->Equals(String::NewFromUtf8(isolate, "jtMiter")) || args[3]->Equals(String::NewFromUtf8(isolate, "jtSquare")) || args[3]->Equals(String::NewFromUtf8(isolate, "jtRound")))) { + result = String::Concat(String::NewFromUtf8(isolate, "Wrong argument 'joinType': 'jtMiter' || 'jtSquare' || 'jtRound' required: "), args[3]->ToString()); return result; } } if ((args.Length() > 4) && (checkLength > 4)) { if (!args[4]->IsNumber()) { - result = String::Concat(String::New("Wrong argument 'miterLimit': number required: "), args[4]->ToString()); + result = String::Concat(String::NewFromUtf8(isolate, "Wrong argument 'miterLimit': number required: "), args[4]->ToString()); return result; } } + return result; } -v8::Handle orientation(const Arguments& args) { - v8::HandleScope scope; +void orientation(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); bool doubleType = false; - v8::Local errMsg = checkArguments(args, 2); + Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { - ThrowException(Exception::TypeError(errMsg)); - return scope.Close(v8::Undefined()); + isolate->ThrowException(Exception::TypeError(errMsg)); + return; } - if (args[1]->Equals(String::New("double"))) { + if (args[1]->Equals(String::NewFromUtf8(isolate, "double"))) { doubleType = true; } - Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + Paths polyshape = v8ArrayToPolygons(Local::Cast(args[0]), doubleType); if (polyshape.size() <= 0) { - return scope.Close(v8::Undefined()); + return; } - v8::Handle orientations = v8::Array::New(); + Handle orientations = Array::New(isolate); for (unsigned int i = 0; i < polyshape.size(); i++) { - bool polyOrientation = ClipperLib::Orientation(polyshape[i]); - orientations->Set(orientations->Length(), v8::Boolean::New(polyOrientation)); + bool polyOrientation = Orientation(polyshape[i]); + orientations->Set(orientations->Length(), Boolean::New(isolate, polyOrientation)); } - return scope.Close(orientations); + args.GetReturnValue().Set(orientations); } -v8::Handle offset(const Arguments& args) { - v8::HandleScope scope; + +void offset(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); JoinType joinType = jtMiter; + EndType_ endType = etClosed; double miterLimit = 30.0; long delta; bool doubleType = false; - v8::Local errMsg = checkArguments(args, 3); + Local errMsg = checkArguments(args, 3); if (errMsg->Length() > 0) { - ThrowException(Exception::TypeError(errMsg)); - return scope.Close(v8::Undefined()); + isolate->ThrowException(Exception::TypeError(errMsg)); + return; } - if (args[1]->Equals(String::New("double"))) { + if (args[1]->Equals(String::NewFromUtf8(isolate, "double"))) { doubleType = true; } @@ -242,17 +260,17 @@ v8::Handle offset(const Arguments& args) { #endif if (args.Length() > 3) { - if (args[3]->Equals(String::New("jtMiter"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "jtMiter"))) { joinType = jtMiter; } - if (args[3]->Equals(String::New("jtSquare"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "jtSquare"))) { joinType = jtSquare; } - if (args[3]->Equals(String::New("jtRound"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "jtRound"))) { joinType = jtRound; } #ifdef DEBUG - if (debug > 0) std::cout << "args[3]: joinType: " << *v8::String::AsciiValue(args[3]) << std::endl; + if (debug > 0) std::cout << "args[3]: joinType: " << *String::Utf8Value(args[3]) << std::endl; #endif } @@ -263,57 +281,56 @@ v8::Handle offset(const Arguments& args) { #endif } - Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + Paths polyshape = v8ArrayToPolygons(Local::Cast(args[0]), doubleType); doFixOrientation(polyshape); - Polygons polyshapeOut; + Paths polyshapeOut; #ifdef DEBUG if (debug > 1) std::cout << "before Offset: polyshape.size(): " << polyshape.size() << std::endl; #endif - //void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, double delta, JoinType jointype = jtSquare, double MiterLimit = 2.0); - ClipperLib::OffsetPolygons(polyshape, polyshapeOut, delta, joinType, miterLimit); + //void OffsetPaths(const Paths &in_polys, Paths &out_polys, double delta, JoinType jointype = jtSquare, EndType endtype = etClosed, double limit = 0.0); + OffsetPaths(polyshape, polyshapeOut, delta, joinType, endType, miterLimit); #ifdef DEBUG if (debug > 1) std::cout << "after Offset: polyshapeOut.size(): " << polyshapeOut.size() << std::endl; #endif if (polyshapeOut.size() > 0) { - return scope.Close(polygonsToV8Array(polyshapeOut, doubleType)); + args.GetReturnValue().Set(polygonsToV8Array(isolate, polyshapeOut, doubleType)); } - - return scope.Close(v8::Undefined()); } -v8::Handle minimum(const Arguments& args) { - v8::HandleScope scope; +void minimum(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); const unsigned long ScaleMax = doubleFactor - 1; JoinType joinType = jtMiter; + EndType_ endType = etClosed; double miterLimit = 30.0; bool doubleType = false; - v8::Local errMsg = checkArguments(args, 2); + Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { - ThrowException(Exception::TypeError(errMsg)); - return scope.Close(v8::Undefined()); + isolate->ThrowException(Exception::TypeError(errMsg)); + return; } - if (args[1]->Equals(String::New("double"))) { + if (args[1]->Equals(String::NewFromUtf8(isolate, "double"))) { doubleType = true; } if (args.Length() > 3) { - if (args[3]->Equals(String::New("jtMiter"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "jtMiter"))) { joinType = jtMiter; } - if (args[3]->Equals(String::New("jtSquare"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "jtSquare"))) { joinType = jtSquare; } - if (args[3]->Equals(String::New("jtRound"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "jtRound"))) { joinType = jtRound; } #ifdef DEBUG - if (debug > 0) std::cout << "args[3]: joinType: " << *v8::String::AsciiValue(args[3]) << std::endl; + if (debug > 0) std::cout << "args[3]: joinType: " << *String::Utf8Value(args[3]) << std::endl; #endif } @@ -324,13 +341,13 @@ v8::Handle minimum(const Arguments& args) { #endif } - Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + Paths polyshape = v8ArrayToPolygons(Local::Cast(args[0]), doubleType); if (polyshape.size() <= 0) { - return scope.Close(v8::Undefined()); + return; } doFixOrientation(polyshape); - Polygons polyshapeOut(polyshape.size()); + Paths polyshapeOut(polyshape.size()); long xMin = 0, xMax = 0, yMin = 0, yMax = 0, xScale = 0, yScale = 0, xyScale = 0; for (unsigned int i = 0; i < polyshape[0].size(); i++) { @@ -358,14 +375,14 @@ v8::Handle minimum(const Arguments& args) { int loops = 0; long scale = xyScale; #ifdef DEBUG - if (debug > 1) std::cout << "OffsetPolygons: xyScale: " << xyScale << std::endl; + if (debug > 1) std::cout << "OffsetPaths: xyScale: " << xyScale << std::endl; #endif long lastScale = 2; do { loops++; - //void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, double delta, JoinType jointype = jtSquare, double MiterLimit = 2.0); + //void OffsetPaths(const Paths &in_polys, Paths &out_polys, double delta, JoinType jointype = jtSquare, EndType endtype = etClosed, double limit = 0.0); try { - ClipperLib::OffsetPolygons(polyshape, polyshapeOut, s * scale, joinType, miterLimit); + OffsetPaths(polyshape, polyshapeOut, s * scale, joinType, endType, miterLimit); } catch (...) { miterLimit /= 2; @@ -389,8 +406,8 @@ v8::Handle minimum(const Arguments& args) { } lastScale = (lastScale << 1) & ScaleMax; #ifdef DEBUG - if (debug > 1) std::cout << "OffsetPolygons: scale: " << scale << std::endl; - if (debug > 1) std::cout << "OffsetPolygons: lastScale: " << lastScale << std::endl; + if (debug > 1) std::cout << "OffsetPaths: scale: " << scale << std::endl; + if (debug > 1) std::cout << "OffsetPaths: lastScale: " << lastScale << std::endl; #endif } while (lastScale != 0 || loops > 64); #ifdef DEBUG @@ -398,58 +415,56 @@ v8::Handle minimum(const Arguments& args) { #endif if (polyshapeOut.size() > 0) { - return scope.Close(polygonsToV8Array(polyshapeOut, doubleType)); + args.GetReturnValue().Set(polygonsToV8Array(isolate, polyshapeOut, doubleType)); } - - return scope.Close(v8::Undefined()); } -v8::Handle clip(const Arguments& args) { - v8::HandleScope scope; +void clip(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); bool doubleType = false; ClipType clipType = ctIntersection; - v8::Local errMsg = checkArguments(args, 1); + Local errMsg = checkArguments(args, 1); if (errMsg->Length() > 0) { - ThrowException(Exception::TypeError(errMsg)); - return scope.Close(v8::Undefined()); + isolate->ThrowException(Exception::TypeError(errMsg)); + return; } - if (args[2]->Equals(String::New("double"))) { + if (args[2]->Equals(String::NewFromUtf8(isolate, "double"))) { doubleType = true; } if (args.Length() > 3) { - if (args[3]->Equals(String::New("ctUnion"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "ctUnion"))) { clipType = ctUnion; } - if (args[3]->Equals(String::New("ctDifference"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "ctDifference"))) { clipType = ctDifference; } - if (args[3]->Equals(String::New("ctXor"))) { + if (args[3]->Equals(String::NewFromUtf8(isolate, "ctXor"))) { clipType = ctXor; } #ifdef DEBUG - if (debug > 0) std::cout << "args[3]: clipType: " << *v8::String::AsciiValue(args[3]) << std::endl; + if (debug > 0) std::cout << "args[3]: clipType: " << *String::Utf8Value(args[3]) << std::endl; #endif } Clipper clipper; - Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + Paths polyshape = v8ArrayToPolygons(Local::Cast(args[0]), doubleType); if (polyshape.size() <= 0) { - return scope.Close(v8::Undefined()); + return; } - clipper.AddPolygons(polyshape, ptSubject); + clipper.AddPaths(polyshape, ptSubject, true); - polyshape = v8ArrayToPolygons(v8::Local::Cast(args[1]), doubleType); + polyshape = v8ArrayToPolygons(Local::Cast(args[1]), doubleType); if (polyshape.size() <= 0) { - return scope.Close(v8::Undefined()); + return; } - clipper.AddPolygons(polyshape, ptClip); + clipper.AddPaths(polyshape, ptClip, true); - Polygons clipperSolution; + Paths clipperSolution; clipper.Execute(clipType, clipperSolution, pftNonZero, pftNonZero); @@ -457,15 +472,15 @@ v8::Handle clip(const Arguments& args) { if (debug > 1) std::cout << "clipperSolution.size(): " << clipperSolution.size() << std::endl; #endif - v8::Handle solutions = v8::Array::New(); - Polygons singleSolution; + Handle solutions = Array::New(isolate); + Paths singleSolution; unsigned int i = 0; while (i < clipperSolution.size()) { do { singleSolution.push_back(clipperSolution[i]); i++; - } while (i < clipperSolution.size() && !ClipperLib::Orientation(clipperSolution[i])); - solutions->Set(solutions->Length(), polygonsToV8Array(singleSolution, doubleType)); + } while (i < clipperSolution.size() && !Orientation(clipperSolution[i])); + solutions->Set(solutions->Length(), polygonsToV8Array(isolate, singleSolution, doubleType)); singleSolution.clear(); } #ifdef DEBUG @@ -473,117 +488,113 @@ v8::Handle clip(const Arguments& args) { #endif if (solutions->Length() > 0) { - return scope.Close(solutions); - } else { - return scope.Close(v8::Undefined()); + args.GetReturnValue().Set(solutions); } } -v8::Handle clean(const Arguments& args) { - v8::HandleScope scope; +void clean(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); bool doubleType = false; double distance = 1.415; - v8::Local errMsg = checkArguments(args, 2); + Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { - ThrowException(Exception::TypeError(errMsg)); - return scope.Close(v8::Undefined()); + isolate->ThrowException(Exception::TypeError(errMsg)); + return; } - if (args[1]->Equals(String::New("double"))) { + if (args[1]->Equals(String::NewFromUtf8(isolate, "double"))) { doubleType = true; } if (args.Length() > 2) { distance = args[2]->NumberValue(); #ifdef DEBUG - if (debug > 0) std::cout << "args[2]: distance: " << *v8::String::AsciiValue(args[2]) << std::endl; + if (debug > 0) std::cout << "args[2]: distance: " << *String::Utf8Value(args[2]) << std::endl; #endif } - Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); - Polygons polyshapeOut(polyshape.size()); + Paths polyshape = v8ArrayToPolygons(Local::Cast(args[0]), doubleType); + Paths polyshapeOut(polyshape.size()); #ifdef DEBUG if (debug > 1) std::cout << "before CleanPolygons: polyshape.size(): " << polyshape.size() << std::endl; #endif //void CleanPolygons(Polygons &in_polys, Polygon &out_polys, double distance = 1.415); - ClipperLib::CleanPolygons(polyshape, polyshapeOut, distance); + CleanPolygons(polyshape, polyshapeOut, distance); #ifdef DEBUG if (debug > 1) std::cout << "after CleanPolygons: polyshapeOut.size(): " << polyshapeOut.size() << std::endl; #endif if (polyshapeOut.size() > 0) { - return scope.Close(polygonsToV8Array(polyshapeOut, doubleType)); + args.GetReturnValue().Set(polygonsToV8Array(isolate, polyshape, doubleType)); } - - return scope.Close(v8::Undefined()); } -v8::Handle fixOrientation(const Arguments& args) { - v8::HandleScope scope; +void fixOrientation(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); bool doubleType = false; - v8::Local errMsg = checkArguments(args, 2); + Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { - ThrowException(Exception::TypeError(errMsg)); - return scope.Close(v8::Undefined()); + isolate->ThrowException(Exception::TypeError(errMsg)); + return; } - if (args[1]->Equals(String::New("double"))) { + if (args[1]->Equals(String::NewFromUtf8(isolate, "double"))) { doubleType = true; } - Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); + Paths polyshape = v8ArrayToPolygons(Local::Cast(args[0]), doubleType); doFixOrientation(polyshape); if (polyshape.size() > 0) { - return scope.Close(polygonsToV8Array(polyshape, doubleType)); + args.GetReturnValue().Set(polygonsToV8Array(isolate, polyshape, doubleType)); } - - return scope.Close(v8::Undefined()); } -v8::Handle simplify(const Arguments& args) { - v8::HandleScope scope; +void simplify(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); bool doubleType = false; - v8::Local errMsg = checkArguments(args, 2); + Local errMsg = checkArguments(args, 2); if (errMsg->Length() > 0) { - ThrowException(Exception::TypeError(errMsg)); - return scope.Close(v8::Undefined()); + isolate->ThrowException(Exception::TypeError(errMsg)); + return; } - if (args[1]->Equals(String::New("double"))) { + if (args[1]->Equals(String::NewFromUtf8(isolate, "double"))) { doubleType = true; } - Polygons polyshape = v8ArrayToPolygons(v8::Local::Cast(args[0]), doubleType); - ClipperLib::SimplifyPolygons(polyshape, polyshape, pftNonZero); + Paths polyshape = v8ArrayToPolygons(Local::Cast(args[0]), doubleType); + SimplifyPolygons(polyshape, polyshape, pftNonZero); if (polyshape.size() > 0) { - return scope.Close(polygonsToV8Array(polyshape, doubleType)); + args.GetReturnValue().Set(polygonsToV8Array(isolate, polyshape, doubleType)); } - - return scope.Close(v8::Undefined()); } - -extern "C" void init(Handle target) { - target->Set(String::NewSymbol("setDebug"), FunctionTemplate::New(setDebug)->GetFunction()); - target->Set(String::NewSymbol("orientation"), FunctionTemplate::New(orientation)->GetFunction()); - target->Set(String::NewSymbol("offset"), FunctionTemplate::New(offset)->GetFunction()); - target->Set(String::NewSymbol("minimum"), FunctionTemplate::New(minimum)->GetFunction()); - target->Set(String::NewSymbol("clip"), FunctionTemplate::New(clip)->GetFunction()); - target->Set(String::NewSymbol("clean"), FunctionTemplate::New(clean)->GetFunction()); - target->Set(String::NewSymbol("fixOrientation"), FunctionTemplate::New(fixOrientation)->GetFunction()); - target->Set(String::NewSymbol("simplify"), FunctionTemplate::New(simplify)->GetFunction()); +void Init(Local exports) { + NODE_SET_METHOD(exports, "setDebug", setDebug); + NODE_SET_METHOD(exports, "orientation", orientation); + NODE_SET_METHOD(exports, "offset", offset); + NODE_SET_METHOD(exports, "minimum", minimum); + NODE_SET_METHOD(exports, "clip", clip); + NODE_SET_METHOD(exports, "clean", clean); + NODE_SET_METHOD(exports, "fixOrientation", fixOrientation); + NODE_SET_METHOD(exports, "simplify", simplify); } -NODE_MODULE(clipper, init) +// Register the module with node. Note that "modulename" must be the same as +// the basename of the resulting .node file. You can specify that name in +// binding.gyp ("target_name"). When you change it there, change it here too. +NODE_MODULE(clipper, Init); + +} From 4102378ba3f6a4d73126b5f5a38809904587c271 Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Tue, 26 Jul 2016 16:54:22 +0200 Subject: [PATCH 16/20] update ClipperLib 5.1.4 -> 6.1.3 --- package.json | 12 +- src/clipper.cpp | 8124 +++++++++++++++++++++++++++-------------------- src/clipper.hpp | 739 +++-- 3 files changed, 5014 insertions(+), 3861 deletions(-) diff --git a/package.json b/package.json index 795a218..6886995 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,19 @@ { "name": "clipper", - "version": "v0.1.0", + "version": "v0.2.0", "main": "./lib/clipper.js", "keywords": ["polygon", "polygons", "offset"], "description": "Clipper Library Bindings", "author": { - "name" : "Sean Ouimet (Redman Technologies)", - "email" : "seano@redmantech.com", - "url" : "http://www.redmantech.com/" + "name" : "runlevel3 GmbH, Berlin", + "email" : "info@runlevel3.de", + "url" : "https://www.runlevel3.de/" }, "engines": { - "node": ">=0.8.0" + "node": ">=2.0.0" }, "repository" : { "type" : "git", - "url" : "https://github.com/snostorm/node-clipper.git" + "url" : "https://github.com/rl3/node-clipper.git" } } \ No newline at end of file diff --git a/src/clipper.cpp b/src/clipper.cpp index 060ab6c..9d6d029 100644 --- a/src/clipper.cpp +++ b/src/clipper.cpp @@ -1,3514 +1,4610 @@ -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 5.1.4 * -* Date : 24 March 2013 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2013 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -/******************************************************************************* -* * -* This is a translation of the Delphi Clipper library and the naming style * -* used has retained a Delphi flavour. * -* * -*******************************************************************************/ - -#include "clipper.hpp" -#include -#include -#include -#include -#include -#include -#include - -namespace ClipperLib { - -static long64 const loRange = 0x3FFFFFFF; -static long64 const hiRange = 0x3FFFFFFFFFFFFFFFLL; -static double const pi = 3.141592653589793238; -enum Direction { dRightToLeft, dLeftToRight }; - -#define HORIZONTAL (-1.0E+40) -#define TOLERANCE (1.0e-20) -#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) -#define NEAR_EQUAL(a, b) NEAR_ZERO((a) - (b)) - -inline long64 Abs(long64 val) -{ - return val < 0 ? -val : val; -} - -//------------------------------------------------------------------------------ -// PolyTree methods ... -//------------------------------------------------------------------------------ - -void PolyTree::Clear() -{ - for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) - delete AllNodes[i]; - AllNodes.resize(0); - Childs.resize(0); -} -//------------------------------------------------------------------------------ - -PolyNode* PolyTree::GetFirst() const -{ - if (Childs.size() > 0) - return Childs[0]; - else - return 0; -} -//------------------------------------------------------------------------------ - -int PolyTree::Total() const -{ - return AllNodes.size(); -} - -//------------------------------------------------------------------------------ -// PolyNode methods ... -//------------------------------------------------------------------------------ - -PolyNode::PolyNode(): Childs(), Parent(0), Index(0) -{ -} -//------------------------------------------------------------------------------ - -int PolyNode::ChildCount() const -{ - return Childs.size(); -} -//------------------------------------------------------------------------------ - -void PolyNode::AddChild(PolyNode& child) -{ - unsigned cnt = Childs.size(); - Childs.push_back(&child); - child.Parent = this; - child.Index = cnt; -} -//------------------------------------------------------------------------------ - -PolyNode* PolyNode::GetNext() const -{ - if (Childs.size() > 0) - return Childs[0]; - else - return GetNextSiblingUp(); -} -//------------------------------------------------------------------------------ - -PolyNode* PolyNode::GetNextSiblingUp() const -{ - if (!Parent) //protects against PolyTree.GetNextSiblingUp() - return 0; - else if (Index == Parent->Childs.size() - 1) - return Parent->GetNextSiblingUp(); - else - return Parent->Childs[Index + 1]; -} -//------------------------------------------------------------------------------ - -bool PolyNode::IsHole() const -{ - bool result = true; - PolyNode* node = Parent; - while (node) - { - result = !result; - node = node->Parent; - } - return result; -} - -//------------------------------------------------------------------------------ -// Int128 class (enables safe math on signed 64bit integers) -// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 -// Int128 val2((long64)9223372036854775807); -// Int128 val3 = val1 * val2; -// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) -//------------------------------------------------------------------------------ - -class Int128 -{ - public: - - ulong64 lo; - long64 hi; - - Int128(long64 _lo = 0) - { - lo = (ulong64)_lo; - if (_lo < 0) hi = -1; else hi = 0; - } - - - Int128(const Int128 &val): lo(val.lo), hi(val.hi){} - - Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} - - long64 operator = (const long64 &val) - { - lo = (ulong64)val; - if (val < 0) hi = -1; else hi = 0; - return val; - } - - bool operator == (const Int128 &val) const - {return (hi == val.hi && lo == val.lo);} - - bool operator != (const Int128 &val) const - { return !(*this == val);} - - bool operator > (const Int128 &val) const - { - if (hi != val.hi) - return hi > val.hi; - else - return lo > val.lo; - } - - bool operator < (const Int128 &val) const - { - if (hi != val.hi) - return hi < val.hi; - else - return lo < val.lo; - } - - bool operator >= (const Int128 &val) const - { return !(*this < val);} - - bool operator <= (const Int128 &val) const - { return !(*this > val);} - - Int128& operator += (const Int128 &rhs) - { - hi += rhs.hi; - lo += rhs.lo; - if (lo < rhs.lo) hi++; - return *this; - } - - Int128 operator + (const Int128 &rhs) const - { - Int128 result(*this); - result+= rhs; - return result; - } - - Int128& operator -= (const Int128 &rhs) - { - *this += -rhs; - return *this; - } - - Int128 operator - (const Int128 &rhs) const - { - Int128 result(*this); - result -= rhs; - return result; - } - - Int128 operator-() const //unary negation - { - if (lo == 0) - return Int128(-hi,0); - else - return Int128(~hi,~lo +1); - } - - Int128 operator/ (const Int128 &rhs) const - { - if (rhs.lo == 0 && rhs.hi == 0) - throw "Int128 operator/: divide by zero"; - - bool negate = (rhs.hi < 0) != (hi < 0); - Int128 dividend = *this; - Int128 divisor = rhs; - if (dividend.hi < 0) dividend = -dividend; - if (divisor.hi < 0) divisor = -divisor; - - if (divisor < dividend) - { - Int128 result = Int128(0); - Int128 cntr = Int128(1); - while (divisor.hi >= 0 && !(divisor > dividend)) - { - divisor.hi <<= 1; - if ((long64)divisor.lo < 0) divisor.hi++; - divisor.lo <<= 1; - - cntr.hi <<= 1; - if ((long64)cntr.lo < 0) cntr.hi++; - cntr.lo <<= 1; - } - divisor.lo >>= 1; - if ((divisor.hi & 1) == 1) - divisor.lo |= 0x8000000000000000LL; - divisor.hi = (ulong64)divisor.hi >> 1; - - cntr.lo >>= 1; - if ((cntr.hi & 1) == 1) - cntr.lo |= 0x8000000000000000LL; - cntr.hi >>= 1; - - while (cntr.hi != 0 || cntr.lo != 0) - { - if (!(dividend < divisor)) - { - dividend -= divisor; - result.hi |= cntr.hi; - result.lo |= cntr.lo; - } - divisor.lo >>= 1; - if ((divisor.hi & 1) == 1) - divisor.lo |= 0x8000000000000000LL; - divisor.hi >>= 1; - - cntr.lo >>= 1; - if ((cntr.hi & 1) == 1) - cntr.lo |= 0x8000000000000000LL; - cntr.hi >>= 1; - } - if (negate) result = -result; - return result; - } - else if (rhs.hi == this->hi && rhs.lo == this->lo) - return Int128(1); - else - return Int128(0); - } - - double AsDouble() const - { - const double shift64 = 18446744073709551616.0; //2^64 - if (hi < 0) - { - if (lo == 0) return (double)hi * shift64; - else return -(double)(~lo + ~hi * shift64); - } - else - return (double)(lo + hi * shift64); - } -}; - -Int128 Int128Mul (long64 lhs, long64 rhs) -{ - bool negate = (lhs < 0) != (rhs < 0); - - if (lhs < 0) lhs = -lhs; - ulong64 int1Hi = ulong64(lhs) >> 32; - ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); - - if (rhs < 0) rhs = -rhs; - ulong64 int2Hi = ulong64(rhs) >> 32; - ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); - - //nb: see comments in clipper.pas - ulong64 a = int1Hi * int2Hi; - ulong64 b = int1Lo * int2Lo; - ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; - - Int128 tmp; - tmp.hi = long64(a + (c >> 32)); - tmp.lo = long64(c << 32); - tmp.lo += long64(b); - if (tmp.lo < b) tmp.hi++; - if (negate) tmp = -tmp; - return tmp; -} - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -bool FullRangeNeeded(const Polygon &pts) -{ - bool result = false; - for (Polygon::size_type i = 0; i < pts.size(); ++i) - { - if (Abs(pts[i].X) > hiRange || Abs(pts[i].Y) > hiRange) - throw "Coordinate exceeds range bounds."; - else if (Abs(pts[i].X) > loRange || Abs(pts[i].Y) > loRange) - result = true; - } - return result; -} -//------------------------------------------------------------------------------ - -bool Orientation(const Polygon &poly) -{ - return Area(poly) >= 0; -} -//------------------------------------------------------------------------------ - -inline bool PointsEqual( const IntPoint &pt1, const IntPoint &pt2) -{ - return ( pt1.X == pt2.X && pt1.Y == pt2.Y ); -} -//------------------------------------------------------------------------------ - -double Area(const Polygon &poly) -{ - int highI = (int)poly.size() -1; - if (highI < 2) return 0; - - if (FullRangeNeeded(poly)) { - Int128 a; - a = Int128Mul(poly[highI].X + poly[0].X, poly[0].Y - poly[highI].Y); - for (int i = 1; i <= highI; ++i) - a += Int128Mul(poly[i - 1].X + poly[i].X, poly[i].Y - poly[i -1].Y); - return a.AsDouble() / 2; - } - else - { - 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 Area(const OutRec &outRec, bool UseFullInt64Range) -{ - OutPt *op = outRec.pts; - if (!op) return 0; - if (UseFullInt64Range) { - Int128 a(0); - do { - a += Int128Mul(op->pt.X + op->prev->pt.X, op->prev->pt.Y - op->pt.Y); - op = op->next; - } while (op != outRec.pts); - return a.AsDouble() / 2; - } - else - { - double a = 0; - do { - a = a + (op->pt.X + op->prev->pt.X) * (op->prev->pt.Y - op->pt.Y); - op = op->next; - } while (op != outRec.pts); - return a / 2; - } -} -//------------------------------------------------------------------------------ - -bool PointIsVertex(const IntPoint &pt, OutPt *pp) -{ - OutPt *pp2 = pp; - do - { - if (PointsEqual(pp2->pt, pt)) return true; - pp2 = pp2->next; - } - while (pp2 != pp); - return false; -} -//------------------------------------------------------------------------------ - -bool PointInPolygon(const IntPoint &pt, OutPt *pp, bool UseFullInt64Range) -{ - OutPt *pp2 = pp; - bool result = false; - if (UseFullInt64Range) { - do - { - if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || - ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->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; - } - while (pp2 != pp); - } - else - { - do - { - if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || - ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && - (pt.X < (pp2->prev->pt.X - pp2->pt.X) * (pt.Y - pp2->pt.Y) / - (pp2->prev->pt.Y - pp2->pt.Y) + pp2->pt.X )) result = !result; - pp2 = pp2->next; - } - while (pp2 != pp); - } - return result; -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(TEdge &e1, TEdge &e2, bool UseFullInt64Range) -{ - if (UseFullInt64Range) - return Int128Mul(e1.deltaY, e2.deltaX) == Int128Mul(e1.deltaX, e2.deltaY); - else return e1.deltaY * e2.deltaX == e1.deltaX * e2.deltaY; -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, - const IntPoint pt3, bool UseFullInt64Range) -{ - if (UseFullInt64Range) - return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); - else return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, - const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) -{ - if (UseFullInt64Range) - return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); - else return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); -} -//------------------------------------------------------------------------------ - -double GetDx(const IntPoint pt1, const IntPoint pt2) -{ - return (pt1.Y == pt2.Y) ? - HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); -} -//--------------------------------------------------------------------------- - -void SetDx(TEdge &e) -{ - e.deltaX = (e.xtop - e.xbot); - e.deltaY = (e.ytop - e.ybot); - - if (e.deltaY == 0) e.dx = HORIZONTAL; - else e.dx = (double)(e.deltaX) / e.deltaY; -} -//--------------------------------------------------------------------------- - -void SwapSides(TEdge &edge1, TEdge &edge2) -{ - EdgeSide side = edge1.side; - edge1.side = edge2.side; - edge2.side = side; -} -//------------------------------------------------------------------------------ - -void SwapPolyIndexes(TEdge &edge1, TEdge &edge2) -{ - int outIdx = edge1.outIdx; - edge1.outIdx = edge2.outIdx; - edge2.outIdx = outIdx; -} -//------------------------------------------------------------------------------ - -inline long64 Round(double val) -{ - return (val < 0) ? static_cast(val - 0.5) : static_cast(val + 0.5); -} -//------------------------------------------------------------------------------ - -long64 TopX(TEdge &edge, const long64 currentY) -{ - return ( currentY == edge.ytop ) ? - edge.xtop : edge.xbot + Round(edge.dx *(currentY - edge.ybot)); -} -//------------------------------------------------------------------------------ - -bool IntersectPoint(TEdge &edge1, TEdge &edge2, - IntPoint &ip, bool UseFullInt64Range) -{ - double b1, b2; - if (SlopesEqual(edge1, edge2, UseFullInt64Range)) - { - if (edge2.ybot > edge1.ybot) ip.Y = edge2.ybot; - else ip.Y = edge1.ybot; - return false; - } - else if (NEAR_ZERO(edge1.dx)) - { - ip.X = edge1.xbot; - if (NEAR_EQUAL(edge2.dx, HORIZONTAL)) - ip.Y = edge2.ybot; - else - { - b2 = edge2.ybot - (edge2.xbot / edge2.dx); - ip.Y = Round(ip.X / edge2.dx + b2); - } - } - else if (NEAR_ZERO(edge2.dx)) - { - ip.X = edge2.xbot; - if (NEAR_EQUAL(edge1.dx, HORIZONTAL)) - ip.Y = edge1.ybot; - else - { - b1 = edge1.ybot - (edge1.xbot / edge1.dx); - ip.Y = Round(ip.X / edge1.dx + b1); - } - } - else - { - b1 = edge1.xbot - edge1.ybot * edge1.dx; - b2 = edge2.xbot - edge2.ybot * edge2.dx; - double q = (b2-b1) / (edge1.dx - edge2.dx); - ip.Y = Round(q); - if (std::fabs(edge1.dx) < std::fabs(edge2.dx)) - ip.X = Round(edge1.dx * q + b1); - else - ip.X = Round(edge2.dx * q + b2); - } - - if (ip.Y < edge1.ytop || ip.Y < edge2.ytop) - { - if (edge1.ytop > edge2.ytop) - { - ip.X = edge1.xtop; - ip.Y = edge1.ytop; - return TopX(edge2, edge1.ytop) < edge1.xtop; - } - else - { - ip.X = edge2.xtop; - ip.Y = edge2.ytop; - return TopX(edge1, edge2.ytop) > edge2.xtop; - } - } - else - return true; -} -//------------------------------------------------------------------------------ - -void ReversePolyPtLinks(OutPt *pp) -{ - if (!pp) return; - OutPt *pp1, *pp2; - pp1 = pp; - do { - pp2 = pp1->next; - pp1->next = pp1->prev; - pp1->prev = pp2; - pp1 = pp2; - } while( pp1 != pp ); -} -//------------------------------------------------------------------------------ - -void DisposeOutPts(OutPt*& pp) -{ - if (pp == 0) return; - pp->prev->next = 0; - while( pp ) - { - OutPt *tmpPp = pp; - pp = pp->next; - delete tmpPp ; - } -} -//------------------------------------------------------------------------------ - -void InitEdge(TEdge *e, TEdge *eNext, - TEdge *ePrev, const IntPoint &pt, PolyType polyType) -{ - std::memset( e, 0, sizeof( TEdge )); - - e->next = eNext; - e->prev = ePrev; - e->xcurr = pt.X; - e->ycurr = pt.Y; - if (e->ycurr >= e->next->ycurr) - { - e->xbot = e->xcurr; - e->ybot = e->ycurr; - e->xtop = e->next->xcurr; - e->ytop = e->next->ycurr; - e->windDelta = 1; - } else - { - e->xtop = e->xcurr; - e->ytop = e->ycurr; - e->xbot = e->next->xcurr; - e->ybot = e->next->ycurr; - e->windDelta = -1; - } - SetDx(*e); - e->polyType = polyType; - e->outIdx = -1; -} -//------------------------------------------------------------------------------ - -inline void SwapX(TEdge &e) -{ - //swap horizontal edges' top and bottom x's so they follow the natural - //progression of the bounds - ie so their xbots will align with the - //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - e.xcurr = e.xtop; - e.xtop = e.xbot; - e.xbot = e.xcurr; -} -//------------------------------------------------------------------------------ - -void SwapPoints(IntPoint &pt1, IntPoint &pt2) -{ - IntPoint tmp = pt1; - pt1 = pt2; - pt2 = tmp; -} -//------------------------------------------------------------------------------ - -bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, - IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) -{ - //precondition: segments are colinear. - if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) - { - if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); - if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); - if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; - return pt1.X < pt2.X; - } else - { - if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); - if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); - if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; - return pt1.Y > pt2.Y; - } -} -//------------------------------------------------------------------------------ - -bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) -{ - OutPt *p = btmPt1->prev; - while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->prev; - double dx1p = std::fabs(GetDx(btmPt1->pt, p->pt)); - p = btmPt1->next; - while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->next; - double dx1n = std::fabs(GetDx(btmPt1->pt, p->pt)); - - p = btmPt2->prev; - while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->prev; - double dx2p = std::fabs(GetDx(btmPt2->pt, p->pt)); - p = btmPt2->next; - while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->next; - double dx2n = std::fabs(GetDx(btmPt2->pt, p->pt)); - return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); -} -//------------------------------------------------------------------------------ - -OutPt* GetBottomPt(OutPt *pp) -{ - OutPt* dups = 0; - OutPt* p = pp->next; - while (p != pp) - { - if (p->pt.Y > pp->pt.Y) - { - pp = p; - dups = 0; - } - else if (p->pt.Y == pp->pt.Y && p->pt.X <= pp->pt.X) - { - if (p->pt.X < pp->pt.X) - { - dups = 0; - pp = p; - } else - { - if (p->next != pp && p->prev != pp) dups = p; - } - } - p = p->next; - } - if (dups) - { - //there appears to be at least 2 vertices at bottomPt so ... - while (dups != p) - { - if (!FirstIsBottomPt(p, dups)) pp = dups; - dups = dups->next; - while (!PointsEqual(dups->pt, pp->pt)) dups = dups->next; - } - } - return pp; -} -//------------------------------------------------------------------------------ - -bool FindSegment(OutPt* &pp, bool UseFullInt64Range, - IntPoint &pt1, IntPoint &pt2) -{ - //outPt1 & outPt2 => the overlap segment (if the function returns true) - if (!pp) return false; - OutPt* pp2 = pp; - IntPoint pt1a = pt1, pt2a = pt2; - do - { - if (SlopesEqual(pt1a, pt2a, pp->pt, pp->prev->pt, UseFullInt64Range) && - SlopesEqual(pt1a, pt2a, pp->pt, UseFullInt64Range) && - GetOverlapSegment(pt1a, pt2a, pp->pt, pp->prev->pt, pt1, pt2)) - return true; - pp = pp->next; - } - while (pp != pp2); - return false; -} -//------------------------------------------------------------------------------ - -bool Pt3IsBetweenPt1AndPt2(const IntPoint pt1, - const IntPoint pt2, const IntPoint pt3) -{ - if (PointsEqual(pt1, pt3) || PointsEqual(pt2, pt3)) return true; - else if (pt1.X != pt2.X) return (pt1.X < pt3.X) == (pt3.X < pt2.X); - else return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y); -} -//------------------------------------------------------------------------------ - -OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint pt) -{ - if (p1 == p2) throw "JoinError"; - OutPt* result = new OutPt; - result->pt = pt; - if (p2 == p1->next) - { - p1->next = result; - p2->prev = result; - result->next = p2; - result->prev = p1; - } else - { - p2->next = result; - p1->prev = result; - result->next = p1; - result->prev = p2; - } - return result; -} - -//------------------------------------------------------------------------------ -// ClipperBase class methods ... -//------------------------------------------------------------------------------ - -ClipperBase::ClipperBase() //constructor -{ - m_MinimaList = 0; - m_CurrentLM = 0; - m_UseFullRange = true; -} -//------------------------------------------------------------------------------ - -ClipperBase::~ClipperBase() //destructor -{ - Clear(); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPolygon( const Polygon &pg, PolyType polyType) -{ - int len = (int)pg.size(); - if (len < 3) return false; - - Polygon p(len); - p[0] = pg[0]; - int j = 0; - - long64 maxVal; - if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange; - - for (int i = 0; i < len; ++i) - { - if (Abs(pg[i].X) > maxVal || Abs(pg[i].Y) > maxVal) - { - if (Abs(pg[i].X) > hiRange || Abs(pg[i].Y) > hiRange) - throw "Coordinate exceeds range bounds"; - maxVal = hiRange; - m_UseFullRange = true; - } - - if (i == 0 || PointsEqual(p[j], pg[i])) continue; - else if (j > 0 && SlopesEqual(p[j-1], p[j], pg[i], m_UseFullRange)) - { - if (PointsEqual(p[j-1], pg[i])) j--; - } else j++; - p[j] = pg[i]; - } - if (j < 2) return false; - - len = j+1; - while (len > 2) - { - //nb: test for point equality before testing slopes ... - if (PointsEqual(p[j], p[0])) j--; - else if (PointsEqual(p[0], p[1]) || - SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) - p[0] = p[j--]; - else if (SlopesEqual(p[j-1], p[j], p[0], m_UseFullRange)) j--; - else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) - { - for (int i = 2; i <= j; ++i) p[i-1] = p[i]; - j--; - } - else break; - len--; - } - if (len < 3) return false; - - //create a new edge array ... - TEdge *edges = new TEdge [len]; - m_edges.push_back(edges); - - //convert vertices to a double-linked-list of edges and initialize ... - edges[0].xcurr = p[0].X; - edges[0].ycurr = p[0].Y; - InitEdge(&edges[len-1], &edges[0], &edges[len-2], p[len-1], polyType); - for (int i = len-2; i > 0; --i) - InitEdge(&edges[i], &edges[i+1], &edges[i-1], p[i], polyType); - InitEdge(&edges[0], &edges[1], &edges[len-1], p[0], polyType); - - //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates - //increase downward so the 'highest' edge will have the smallest ytop) ... - TEdge *e = &edges[0]; - TEdge *eHighest = e; - do - { - e->xcurr = e->xbot; - e->ycurr = e->ybot; - if (e->ytop < eHighest->ytop) eHighest = e; - e = e->next; - } - while ( e != &edges[0]); - - //make sure eHighest is positioned so the following loop works safely ... - if (eHighest->windDelta > 0) eHighest = eHighest->next; - if (NEAR_EQUAL(eHighest->dx, HORIZONTAL)) eHighest = eHighest->next; - - //finally insert each local minima ... - e = eHighest; - do { - e = AddBoundsToLML(e); - } - while( e != eHighest ); - return true; -} -//------------------------------------------------------------------------------ - -void ClipperBase::InsertLocalMinima(LocalMinima *newLm) -{ - if( ! m_MinimaList ) - { - m_MinimaList = newLm; - } - else if( newLm->Y >= m_MinimaList->Y ) - { - newLm->next = m_MinimaList; - m_MinimaList = newLm; - } else - { - LocalMinima* tmpLm = m_MinimaList; - while( tmpLm->next && ( newLm->Y < tmpLm->next->Y ) ) - tmpLm = tmpLm->next; - newLm->next = tmpLm->next; - tmpLm->next = newLm; - } -} -//------------------------------------------------------------------------------ - -TEdge* ClipperBase::AddBoundsToLML(TEdge *e) -{ - //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. - e->nextInLML = 0; - e = e->next; - for (;;) - { - if (NEAR_EQUAL(e->dx, HORIZONTAL)) - { - //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. - if (e->next->ytop < e->ytop && e->next->xbot > e->prev->xbot) break; - if (e->xtop != e->prev->xbot) SwapX(*e); - e->nextInLML = e->prev; - } - else if (e->ycurr == e->prev->ycurr) break; - else e->nextInLML = e->prev; - e = e->next; - } - - //e and e.prev are now at a local minima ... - LocalMinima* newLm = new LocalMinima; - newLm->next = 0; - newLm->Y = e->prev->ybot; - - if ( NEAR_EQUAL(e->dx, HORIZONTAL) ) //horizontal edges never start a left bound - { - if (e->xbot != e->prev->xbot) SwapX(*e); - newLm->leftBound = e->prev; - newLm->rightBound = e; - } else if (e->dx < e->prev->dx) - { - newLm->leftBound = e->prev; - newLm->rightBound = e; - } else - { - newLm->leftBound = e; - newLm->rightBound = e->prev; - } - newLm->leftBound->side = esLeft; - newLm->rightBound->side = esRight; - InsertLocalMinima( newLm ); - - for (;;) - { - if ( e->next->ytop == e->ytop && !NEAR_EQUAL(e->next->dx, HORIZONTAL) ) break; - e->nextInLML = e->next; - e = e->next; - if ( NEAR_EQUAL(e->dx, HORIZONTAL) && e->xbot != e->prev->xtop) SwapX(*e); - } - return e->next; -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPolygons(const Polygons &ppg, PolyType polyType) -{ - bool result = false; - for (Polygons::size_type i = 0; i < ppg.size(); ++i) - if (AddPolygon(ppg[i], polyType)) result = true; - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::Clear() -{ - DisposeLocalMinimaList(); - for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) delete [] m_edges[i]; - m_edges.clear(); - m_UseFullRange = false; -} -//------------------------------------------------------------------------------ - -void ClipperBase::Reset() -{ - m_CurrentLM = m_MinimaList; - if( !m_CurrentLM ) return; //ie nothing to process - - //reset all edges ... - LocalMinima* lm = m_MinimaList; - while( lm ) - { - TEdge* e = lm->leftBound; - while( e ) - { - e->xcurr = e->xbot; - e->ycurr = e->ybot; - e->side = esLeft; - e->outIdx = -1; - e = e->nextInLML; - } - e = lm->rightBound; - while( e ) - { - e->xcurr = e->xbot; - e->ycurr = e->ybot; - e->side = esRight; - e->outIdx = -1; - e = e->nextInLML; - } - lm = lm->next; - } -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeLocalMinimaList() -{ - while( m_MinimaList ) - { - LocalMinima* tmpLm = m_MinimaList->next; - delete m_MinimaList; - m_MinimaList = tmpLm; - } - m_CurrentLM = 0; -} -//------------------------------------------------------------------------------ - -void ClipperBase::PopLocalMinima() -{ - if( ! m_CurrentLM ) return; - m_CurrentLM = m_CurrentLM->next; -} -//------------------------------------------------------------------------------ - -IntRect ClipperBase::GetBounds() -{ - IntRect result; - LocalMinima* lm = m_MinimaList; - if (!lm) - { - result.left = result.top = result.right = result.bottom = 0; - return result; - } - result.left = lm->leftBound->xbot; - result.top = lm->leftBound->ybot; - result.right = lm->leftBound->xbot; - result.bottom = lm->leftBound->ybot; - while (lm) - { - if (lm->leftBound->ybot > result.bottom) - result.bottom = lm->leftBound->ybot; - TEdge* e = lm->leftBound; - for (;;) { - TEdge* bottomE = e; - while (e->nextInLML) - { - if (e->xbot < result.left) result.left = e->xbot; - if (e->xbot > result.right) result.right = e->xbot; - e = e->nextInLML; - } - if (e->xbot < result.left) result.left = e->xbot; - if (e->xbot > result.right) result.right = e->xbot; - if (e->xtop < result.left) result.left = e->xtop; - if (e->xtop > result.right) result.right = e->xtop; - if (e->ytop < result.top) result.top = e->ytop; - - if (bottomE == lm->leftBound) e = lm->rightBound; - else break; - } - lm = lm->next; - } - return result; -} - - -//------------------------------------------------------------------------------ -// TClipper methods ... -//------------------------------------------------------------------------------ - -Clipper::Clipper() : ClipperBase() //constructor -{ - m_Scanbeam = 0; - m_ActiveEdges = 0; - m_SortedEdges = 0; - m_IntersectNodes = 0; - m_ExecuteLocked = false; - m_UseFullRange = false; - m_ReverseOutput = false; -} -//------------------------------------------------------------------------------ - -Clipper::~Clipper() //destructor -{ - Clear(); - DisposeScanbeamList(); -} -//------------------------------------------------------------------------------ - -void Clipper::Clear() -{ - if (m_edges.size() == 0) return; //avoids problems with ClipperBase destructor - DisposeAllPolyPts(); - ClipperBase::Clear(); -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeScanbeamList() -{ - while ( m_Scanbeam ) { - Scanbeam* sb2 = m_Scanbeam->next; - delete m_Scanbeam; - m_Scanbeam = sb2; - } -} -//------------------------------------------------------------------------------ - -void Clipper::Reset() -{ - ClipperBase::Reset(); - m_Scanbeam = 0; - m_ActiveEdges = 0; - m_SortedEdges = 0; - DisposeAllPolyPts(); - LocalMinima* lm = m_MinimaList; - while (lm) - { - InsertScanbeam(lm->Y); - InsertScanbeam(lm->leftBound->ytop); - lm = lm->next; - } -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, Polygons &solution, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - m_ExecuteLocked = true; - solution.resize(0); - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - m_UsingPolyTree = false; - bool succeeded = ExecuteInternal(); - if (succeeded) BuildResult(solution); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, PolyTree& polytree, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - m_ExecuteLocked = true; - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - m_UsingPolyTree = true; - bool succeeded = ExecuteInternal(); - if (succeeded) BuildResult2(polytree); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::FixHoleLinkage(OutRec &outRec) -{ - //skip OutRecs that (a) contain outermost polygons or - //(b) already have the correct owner/child linkage ... - if (!outRec.FirstLeft || - (outRec.isHole != outRec.FirstLeft->isHole && - outRec.FirstLeft->pts)) return; - - OutRec* orfl = outRec.FirstLeft; - while (orfl && ((orfl->isHole == outRec.isHole) || !orfl->pts)) - orfl = orfl->FirstLeft; - outRec.FirstLeft = orfl; -} -//------------------------------------------------------------------------------ - -bool Clipper::ExecuteInternal() -{ - bool succeeded; - try { - Reset(); - if (!m_CurrentLM ) return true; - long64 botY = PopScanbeam(); - do { - InsertLocalMinimaIntoAEL(botY); - ClearHorzJoins(); - ProcessHorizontals(); - long64 topY = PopScanbeam(); - succeeded = ProcessIntersections(botY, topY); - if (!succeeded) break; - ProcessEdgesAtTopOfScanbeam(topY); - botY = topY; - } while( m_Scanbeam ); - } - catch(...) { - succeeded = false; - } - - if (succeeded) - { - //tidy up output polygons and fix orientations where necessary ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec *outRec = m_PolyOuts[i]; - if (!outRec->pts) continue; - FixupOutPolygon(*outRec); - if (!outRec->pts) continue; - - if ((outRec->isHole ^ m_ReverseOutput) == (Area(*outRec, m_UseFullRange) > 0)) - ReversePolyPtLinks(outRec->pts); - } - - if (m_Joins.size() > 0) JoinCommonEdges(); - } - - ClearJoins(); - ClearHorzJoins(); - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::InsertScanbeam(const long64 Y) -{ - if( !m_Scanbeam ) - { - m_Scanbeam = new Scanbeam; - m_Scanbeam->next = 0; - m_Scanbeam->Y = Y; - } - else if( Y > m_Scanbeam->Y ) - { - Scanbeam* newSb = new Scanbeam; - newSb->Y = Y; - newSb->next = m_Scanbeam; - m_Scanbeam = newSb; - } else - { - Scanbeam* sb2 = m_Scanbeam; - while( sb2->next && ( Y <= sb2->next->Y ) ) sb2 = sb2->next; - if( Y == sb2->Y ) return; //ie ignores duplicates - Scanbeam* newSb = new Scanbeam; - newSb->Y = Y; - newSb->next = sb2->next; - sb2->next = newSb; - } -} -//------------------------------------------------------------------------------ - -long64 Clipper::PopScanbeam() -{ - long64 Y = m_Scanbeam->Y; - Scanbeam* sb2 = m_Scanbeam; - m_Scanbeam = m_Scanbeam->next; - delete sb2; - return Y; -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeAllPolyPts(){ - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - DisposeOutRec(i); - m_PolyOuts.clear(); -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeOutRec(PolyOutList::size_type index) -{ - OutRec *outRec = m_PolyOuts[index]; - if (outRec->pts) DisposeOutPts(outRec->pts); - delete outRec; - m_PolyOuts[index] = 0; -} -//------------------------------------------------------------------------------ - -void Clipper::SetWindingCount(TEdge &edge) -{ - TEdge *e = edge.prevInAEL; - //find the edge of the same polytype that immediately preceeds 'edge' in AEL - while ( e && e->polyType != edge.polyType ) e = e->prevInAEL; - if ( !e ) - { - edge.windCnt = edge.windDelta; - edge.windCnt2 = 0; - e = m_ActiveEdges; //ie get ready to calc windCnt2 - } else if ( IsEvenOddFillType(edge) ) - { - //EvenOdd filling ... - edge.windCnt = 1; - edge.windCnt2 = e->windCnt2; - e = e->nextInAEL; //ie get ready to calc windCnt2 - } else - { - //nonZero, Positive or Negative filling ... - if ( e->windCnt * e->windDelta < 0 ) - { - if (Abs(e->windCnt) > 1) - { - if (e->windDelta * edge.windDelta < 0) edge.windCnt = e->windCnt; - else edge.windCnt = e->windCnt + edge.windDelta; - } else - edge.windCnt = e->windCnt + e->windDelta + edge.windDelta; - } else - { - if ( Abs(e->windCnt) > 1 && e->windDelta * edge.windDelta < 0) - edge.windCnt = e->windCnt; - else if ( e->windCnt + edge.windDelta == 0 ) - edge.windCnt = e->windCnt; - else edge.windCnt = e->windCnt + edge.windDelta; - } - edge.windCnt2 = e->windCnt2; - e = e->nextInAEL; //ie get ready to calc windCnt2 - } - - //update windCnt2 ... - if ( IsEvenOddAltFillType(edge) ) - { - //EvenOdd filling ... - while ( e != &edge ) - { - edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0; - e = e->nextInAEL; - } - } else - { - //nonZero, Positive or Negative filling ... - while ( e != &edge ) - { - edge.windCnt2 += e->windDelta; - e = e->nextInAEL; - } - } -} -//------------------------------------------------------------------------------ - -bool Clipper::IsEvenOddFillType(const TEdge& edge) const -{ - if (edge.polyType == ptSubject) - return m_SubjFillType == pftEvenOdd; else - return m_ClipFillType == pftEvenOdd; -} -//------------------------------------------------------------------------------ - -bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const -{ - if (edge.polyType == ptSubject) - return m_ClipFillType == pftEvenOdd; else - return m_SubjFillType == pftEvenOdd; -} -//------------------------------------------------------------------------------ - -bool Clipper::IsContributing(const TEdge& edge) const -{ - PolyFillType pft, pft2; - if (edge.polyType == ptSubject) - { - pft = m_SubjFillType; - pft2 = m_ClipFillType; - } else - { - pft = m_ClipFillType; - pft2 = m_SubjFillType; - } - - switch(pft) - { - case pftEvenOdd: - case pftNonZero: - if (Abs(edge.windCnt) != 1) return false; - break; - case pftPositive: - if (edge.windCnt != 1) return false; - break; - default: //pftNegative - if (edge.windCnt != -1) return false; - } - - switch(m_ClipType) - { - case ctIntersection: - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.windCnt2 != 0); - case pftPositive: - return (edge.windCnt2 > 0); - default: - return (edge.windCnt2 < 0); - } - case ctUnion: - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.windCnt2 == 0); - case pftPositive: - return (edge.windCnt2 <= 0); - default: - return (edge.windCnt2 >= 0); - } - case ctDifference: - if (edge.polyType == ptSubject) - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.windCnt2 == 0); - case pftPositive: - return (edge.windCnt2 <= 0); - default: - return (edge.windCnt2 >= 0); - } - else - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.windCnt2 != 0); - case pftPositive: - return (edge.windCnt2 > 0); - default: - return (edge.windCnt2 < 0); - } - default: - return true; - } -} -//------------------------------------------------------------------------------ - -void Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) -{ - TEdge *e, *prevE; - if( NEAR_EQUAL(e2->dx, HORIZONTAL) || ( e1->dx > e2->dx ) ) - { - AddOutPt( e1, pt ); - e2->outIdx = e1->outIdx; - e1->side = esLeft; - e2->side = esRight; - e = e1; - if (e->prevInAEL == e2) - prevE = e2->prevInAEL; - else - prevE = e->prevInAEL; - } else - { - AddOutPt( e2, pt ); - e1->outIdx = e2->outIdx; - e1->side = esRight; - e2->side = esLeft; - e = e2; - if (e->prevInAEL == e1) - prevE = e1->prevInAEL; - else - prevE = e->prevInAEL; - } - if (prevE && prevE->outIdx >= 0 && - (TopX(*prevE, pt.Y) == TopX(*e, pt.Y)) && - SlopesEqual(*e, *prevE, m_UseFullRange)) - AddJoin(e, prevE, -1, -1); -} -//------------------------------------------------------------------------------ - -void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) -{ - AddOutPt( e1, pt ); - if( e1->outIdx == e2->outIdx ) - { - e1->outIdx = -1; - e2->outIdx = -1; - } - else if (e1->outIdx < e2->outIdx) - AppendPolygon(e1, e2); - else - AppendPolygon(e2, e1); -} -//------------------------------------------------------------------------------ - -void Clipper::AddEdgeToSEL(TEdge *edge) -{ - //SEL pointers in PEdge are reused to build a list of horizontal edges. - //However, we don't need to worry about order with horizontal edge processing. - if( !m_SortedEdges ) - { - m_SortedEdges = edge; - edge->prevInSEL = 0; - edge->nextInSEL = 0; - } - else - { - edge->nextInSEL = m_SortedEdges; - edge->prevInSEL = 0; - m_SortedEdges->prevInSEL = edge; - m_SortedEdges = edge; - } -} -//------------------------------------------------------------------------------ - -void Clipper::CopyAELToSEL() -{ - TEdge* e = m_ActiveEdges; - m_SortedEdges = e; - while ( e ) - { - e->prevInSEL = e->prevInAEL; - e->nextInSEL = e->nextInAEL; - e = e->nextInAEL; - } -} -//------------------------------------------------------------------------------ - -void Clipper::AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx, int e2OutIdx) -{ - JoinRec* jr = new JoinRec; - if (e1OutIdx >= 0) - jr->poly1Idx = e1OutIdx; else - jr->poly1Idx = e1->outIdx; - jr->pt1a = IntPoint(e1->xcurr, e1->ycurr); - jr->pt1b = IntPoint(e1->xtop, e1->ytop); - if (e2OutIdx >= 0) - jr->poly2Idx = e2OutIdx; else - jr->poly2Idx = e2->outIdx; - jr->pt2a = IntPoint(e2->xcurr, e2->ycurr); - jr->pt2b = IntPoint(e2->xtop, e2->ytop); - m_Joins.push_back(jr); -} -//------------------------------------------------------------------------------ - -void Clipper::ClearJoins() -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) - delete m_Joins[i]; - m_Joins.resize(0); -} -//------------------------------------------------------------------------------ - -void Clipper::AddHorzJoin(TEdge *e, int idx) -{ - HorzJoinRec* hj = new HorzJoinRec; - hj->edge = e; - hj->savedIdx = idx; - m_HorizJoins.push_back(hj); -} -//------------------------------------------------------------------------------ - -void Clipper::ClearHorzJoins() -{ - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); i++) - delete m_HorizJoins[i]; - m_HorizJoins.resize(0); -} -//------------------------------------------------------------------------------ - -void Clipper::InsertLocalMinimaIntoAEL(const long64 botY) -{ - while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) - { - TEdge* lb = m_CurrentLM->leftBound; - TEdge* rb = m_CurrentLM->rightBound; - - InsertEdgeIntoAEL( lb ); - InsertScanbeam( lb->ytop ); - InsertEdgeIntoAEL( rb ); - - if (IsEvenOddFillType(*lb)) - { - lb->windDelta = 1; - rb->windDelta = 1; - } - else - { - rb->windDelta = -lb->windDelta; - } - SetWindingCount( *lb ); - rb->windCnt = lb->windCnt; - rb->windCnt2 = lb->windCnt2; - - if( NEAR_EQUAL(rb->dx, HORIZONTAL) ) - { - //nb: only rightbounds can have a horizontal bottom edge - AddEdgeToSEL( rb ); - InsertScanbeam( rb->nextInLML->ytop ); - } - else - InsertScanbeam( rb->ytop ); - - if( IsContributing(*lb) ) - AddLocalMinPoly( lb, rb, IntPoint(lb->xcurr, m_CurrentLM->Y) ); - - //if any output polygons share an edge, they'll need joining later ... - if (rb->outIdx >= 0 && NEAR_EQUAL(rb->dx, HORIZONTAL)) - { - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) - { - IntPoint pt, pt2; //returned by GetOverlapSegment() but unused here. - HorzJoinRec* hj = m_HorizJoins[i]; - //if horizontals rb and hj.edge overlap, flag for joining later ... - if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), - IntPoint(hj->edge->xtop, hj->edge->ytop), - IntPoint(rb->xbot, rb->ybot), - IntPoint(rb->xtop, rb->ytop), pt, pt2)) - AddJoin(hj->edge, rb, hj->savedIdx); - } - } - - if( lb->nextInAEL != rb ) - { - if (rb->outIdx >= 0 && rb->prevInAEL->outIdx >= 0 && - SlopesEqual(*rb->prevInAEL, *rb, m_UseFullRange)) - AddJoin(rb, rb->prevInAEL); - - TEdge* e = lb->nextInAEL; - IntPoint pt = IntPoint(lb->xcurr, lb->ycurr); - while( e != rb ) - { - if(!e) throw clipperException("InsertLocalMinimaIntoAEL: missing rightbound!"); - //nb: For calculating winding counts etc, IntersectEdges() assumes - //that param1 will be to the right of param2 ABOVE the intersection ... - IntersectEdges( rb , e , pt , ipNone); //order important here - e = e->nextInAEL; - } - } - PopLocalMinima(); - } -} -//------------------------------------------------------------------------------ - -void Clipper::DeleteFromAEL(TEdge *e) -{ - TEdge* AelPrev = e->prevInAEL; - TEdge* AelNext = e->nextInAEL; - if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted - if( AelPrev ) AelPrev->nextInAEL = AelNext; - else m_ActiveEdges = AelNext; - if( AelNext ) AelNext->prevInAEL = AelPrev; - e->nextInAEL = 0; - e->prevInAEL = 0; -} -//------------------------------------------------------------------------------ - -void Clipper::DeleteFromSEL(TEdge *e) -{ - TEdge* SelPrev = e->prevInSEL; - TEdge* SelNext = e->nextInSEL; - if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted - if( SelPrev ) SelPrev->nextInSEL = SelNext; - else m_SortedEdges = SelNext; - if( SelNext ) SelNext->prevInSEL = SelPrev; - e->nextInSEL = 0; - e->prevInSEL = 0; -} -//------------------------------------------------------------------------------ - -void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, - const IntPoint &pt, const IntersectProtects protects) -{ - //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before - //e2 in AEL except when e1 is being inserted at the intersection point ... - bool e1stops = !(ipLeft & protects) && !e1->nextInLML && - e1->xtop == pt.X && e1->ytop == pt.Y; - bool e2stops = !(ipRight & protects) && !e2->nextInLML && - e2->xtop == pt.X && e2->ytop == pt.Y; - bool e1Contributing = ( e1->outIdx >= 0 ); - bool e2contributing = ( e2->outIdx >= 0 ); - - //update winding counts... - //assumes that e1 will be to the right of e2 ABOVE the intersection - if ( e1->polyType == e2->polyType ) - { - if ( IsEvenOddFillType( *e1) ) - { - int oldE1WindCnt = e1->windCnt; - e1->windCnt = e2->windCnt; - e2->windCnt = oldE1WindCnt; - } else - { - if (e1->windCnt + e2->windDelta == 0 ) e1->windCnt = -e1->windCnt; - else e1->windCnt += e2->windDelta; - if ( e2->windCnt - e1->windDelta == 0 ) e2->windCnt = -e2->windCnt; - else e2->windCnt -= e1->windDelta; - } - } else - { - if (!IsEvenOddFillType(*e2)) e1->windCnt2 += e2->windDelta; - else e1->windCnt2 = ( e1->windCnt2 == 0 ) ? 1 : 0; - if (!IsEvenOddFillType(*e1)) e2->windCnt2 -= e1->windDelta; - else e2->windCnt2 = ( e2->windCnt2 == 0 ) ? 1 : 0; - } - - PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; - if (e1->polyType == ptSubject) - { - e1FillType = m_SubjFillType; - e1FillType2 = m_ClipFillType; - } else - { - e1FillType = m_ClipFillType; - e1FillType2 = m_SubjFillType; - } - if (e2->polyType == ptSubject) - { - e2FillType = m_SubjFillType; - e2FillType2 = m_ClipFillType; - } else - { - e2FillType = m_ClipFillType; - e2FillType2 = m_SubjFillType; - } - - long64 e1Wc, e2Wc; - switch (e1FillType) - { - case pftPositive: e1Wc = e1->windCnt; break; - case pftNegative: e1Wc = -e1->windCnt; break; - default: e1Wc = Abs(e1->windCnt); - } - switch(e2FillType) - { - case pftPositive: e2Wc = e2->windCnt; break; - case pftNegative: e2Wc = -e2->windCnt; break; - default: e2Wc = Abs(e2->windCnt); - } - - if ( e1Contributing && e2contributing ) - { - if ( e1stops || e2stops || - (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || - (e1->polyType != e2->polyType && m_ClipType != ctXor) ) - AddLocalMaxPoly(e1, e2, pt); - else - DoBothEdges( e1, e2, pt ); - } - else if ( e1Contributing ) - { - if (e2Wc == 0 || e2Wc == 1) DoEdge1(e1, e2, pt); - } - else if ( e2contributing ) - { - if (e1Wc == 0 || e1Wc == 1) DoEdge2(e1, e2, pt); - } - else if ( (e1Wc == 0 || e1Wc == 1) && - (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) - { - //neither edge is currently contributing ... - - long64 e1Wc2, e2Wc2; - switch (e1FillType2) - { - case pftPositive: e1Wc2 = e1->windCnt2; break; - case pftNegative : e1Wc2 = -e1->windCnt2; break; - default: e1Wc2 = Abs(e1->windCnt2); - } - switch (e2FillType2) - { - case pftPositive: e2Wc2 = e2->windCnt2; break; - case pftNegative: e2Wc2 = -e2->windCnt2; break; - default: e2Wc2 = Abs(e2->windCnt2); - } - - if (e1->polyType != e2->polyType) - AddLocalMinPoly(e1, e2, pt); - else if (e1Wc == 1 && e2Wc == 1) - switch( m_ClipType ) { - case ctIntersection: - if (e1Wc2 > 0 && e2Wc2 > 0) - AddLocalMinPoly(e1, e2, pt); - break; - case ctUnion: - if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) - AddLocalMinPoly(e1, e2, pt); - break; - case ctDifference: - if (((e1->polyType == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((e1->polyType == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - AddLocalMinPoly(e1, e2, pt); - break; - case ctXor: - AddLocalMinPoly(e1, e2, pt); - } - else - SwapSides( *e1, *e2 ); - } - - if( (e1stops != e2stops) && - ( (e1stops && (e1->outIdx >= 0)) || (e2stops && (e2->outIdx >= 0)) ) ) - { - SwapSides( *e1, *e2 ); - SwapPolyIndexes( *e1, *e2 ); - } - - //finally, delete any non-contributing maxima edges ... - if( e1stops ) DeleteFromAEL( e1 ); - if( e2stops ) DeleteFromAEL( e2 ); -} -//------------------------------------------------------------------------------ - -void Clipper::SetHoleState(TEdge *e, OutRec *outRec) -{ - bool isHole = false; - TEdge *e2 = e->prevInAEL; - while (e2) - { - if (e2->outIdx >= 0) - { - isHole = !isHole; - if (! outRec->FirstLeft) - outRec->FirstLeft = m_PolyOuts[e2->outIdx]; - } - e2 = e2->prevInAEL; - } - if (isHole) outRec->isHole = true; -} -//------------------------------------------------------------------------------ - -OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) -{ - //work out which polygon fragment has the correct hole state ... - OutPt *outPt1 = outRec1->bottomPt; - OutPt *outPt2 = outRec2->bottomPt; - if (outPt1->pt.Y > outPt2->pt.Y) return outRec1; - else if (outPt1->pt.Y < outPt2->pt.Y) return outRec2; - else if (outPt1->pt.X < outPt2->pt.X) return outRec1; - else if (outPt1->pt.X > outPt2->pt.X) return outRec2; - else if (outPt1->next == outPt1) return outRec2; - else if (outPt2->next == outPt2) return outRec1; - else if (FirstIsBottomPt(outPt1, outPt2)) return outRec1; - else return outRec2; -} -//------------------------------------------------------------------------------ - -bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) -{ - do - { - outRec1 = outRec1->FirstLeft; - if (outRec1 == outRec2) return true; - } while (outRec1); - return false; -} -//------------------------------------------------------------------------------ - -void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) -{ - //get the start and ends of both output polygons ... - OutRec *outRec1 = m_PolyOuts[e1->outIdx]; - OutRec *outRec2 = m_PolyOuts[e2->outIdx]; - - OutRec *holeStateRec; - if (Param1RightOfParam2(outRec1, outRec2)) - holeStateRec = outRec2; - else if (Param1RightOfParam2(outRec2, outRec1)) - holeStateRec = outRec1; - else - holeStateRec = GetLowermostRec(outRec1, outRec2); - - OutPt* p1_lft = outRec1->pts; - OutPt* p1_rt = p1_lft->prev; - OutPt* p2_lft = outRec2->pts; - OutPt* p2_rt = p2_lft->prev; - - EdgeSide side; - //join e2 poly onto e1 poly and delete pointers to e2 ... - if( e1->side == esLeft ) - { - if( e2->side == esLeft ) - { - //z y x a b c - ReversePolyPtLinks(p2_lft); - p2_lft->next = p1_lft; - p1_lft->prev = p2_lft; - p1_rt->next = p2_rt; - p2_rt->prev = p1_rt; - outRec1->pts = p2_rt; - } else - { - //x y z a b c - p2_rt->next = p1_lft; - p1_lft->prev = p2_rt; - p2_lft->prev = p1_rt; - p1_rt->next = p2_lft; - outRec1->pts = p2_lft; - } - side = esLeft; - } else - { - if( e2->side == esRight ) - { - //a b c z y x - ReversePolyPtLinks(p2_lft); - p1_rt->next = p2_rt; - p2_rt->prev = p1_rt; - p2_lft->next = p1_lft; - p1_lft->prev = p2_lft; - } else - { - //a b c x y z - p1_rt->next = p2_lft; - p2_lft->prev = p1_rt; - p1_lft->prev = p2_rt; - p2_rt->next = p1_lft; - } - side = esRight; - } - - if (holeStateRec == outRec2) - { - outRec1->bottomPt = outRec2->bottomPt; - outRec1->bottomPt->idx = outRec1->idx; - if (outRec2->FirstLeft != outRec1) - outRec1->FirstLeft = outRec2->FirstLeft; - outRec1->isHole = outRec2->isHole; - } - outRec2->pts = 0; - outRec2->bottomPt = 0; - - outRec2->FirstLeft = outRec1; - - int OKIdx = e1->outIdx; - int ObsoleteIdx = e2->outIdx; - - e1->outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly - e2->outIdx = -1; - - TEdge* e = m_ActiveEdges; - while( e ) - { - if( e->outIdx == ObsoleteIdx ) - { - e->outIdx = OKIdx; - e->side = side; - break; - } - e = e->nextInAEL; - } - - for (JoinList::size_type i = 0; i < m_Joins.size(); ++i) - { - if (m_Joins[i]->poly1Idx == ObsoleteIdx) m_Joins[i]->poly1Idx = OKIdx; - if (m_Joins[i]->poly2Idx == ObsoleteIdx) m_Joins[i]->poly2Idx = OKIdx; - } - - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) - { - if (m_HorizJoins[i]->savedIdx == ObsoleteIdx) - m_HorizJoins[i]->savedIdx = OKIdx; - } - -} -//------------------------------------------------------------------------------ - -OutRec* Clipper::CreateOutRec() -{ - OutRec* result = new OutRec; - result->isHole = false; - result->FirstLeft = 0; - result->pts = 0; - result->bottomPt = 0; - result->polyNode = 0; - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::AddOutPt(TEdge *e, const IntPoint &pt) -{ - bool ToFront = (e->side == esLeft); - if( e->outIdx < 0 ) - { - OutRec *outRec = CreateOutRec(); - m_PolyOuts.push_back(outRec); - outRec->idx = (int)m_PolyOuts.size()-1; - e->outIdx = outRec->idx; - OutPt* op = new OutPt; - outRec->pts = op; - outRec->bottomPt = op; - op->pt = pt; - op->idx = outRec->idx; - op->next = op; - op->prev = op; - SetHoleState(e, outRec); - } else - { - OutRec *outRec = m_PolyOuts[e->outIdx]; - OutPt* op = outRec->pts; - if ((ToFront && PointsEqual(pt, op->pt)) || - (!ToFront && PointsEqual(pt, op->prev->pt))) return; - - OutPt* op2 = new OutPt; - op2->pt = pt; - op2->idx = outRec->idx; - if (op2->pt.Y == outRec->bottomPt->pt.Y && - op2->pt.X < outRec->bottomPt->pt.X) - outRec->bottomPt = op2; - op2->next = op; - op2->prev = op->prev; - op2->prev->next = op2; - op->prev = op2; - if (ToFront) outRec->pts = op2; - } -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessHorizontals() -{ - TEdge* horzEdge = m_SortedEdges; - while( horzEdge ) - { - DeleteFromSEL( horzEdge ); - ProcessHorizontal( horzEdge ); - horzEdge = m_SortedEdges; - } -} -//------------------------------------------------------------------------------ - -bool Clipper::IsTopHorz(const long64 XPos) -{ - TEdge* e = m_SortedEdges; - while( e ) - { - if( ( XPos >= std::min(e->xcurr, e->xtop) ) && - ( XPos <= std::max(e->xcurr, e->xtop) ) ) return false; - e = e->nextInSEL; - } - return true; -} -//------------------------------------------------------------------------------ - -bool IsMinima(TEdge *e) -{ - return e && (e->prev->nextInLML != e) && (e->next->nextInLML != e); -} -//------------------------------------------------------------------------------ - -bool IsMaxima(TEdge *e, const long64 Y) -{ - return e && e->ytop == Y && !e->nextInLML; -} -//------------------------------------------------------------------------------ - -bool IsIntermediate(TEdge *e, const long64 Y) -{ - return e->ytop == Y && e->nextInLML; -} -//------------------------------------------------------------------------------ - -TEdge *GetMaximaPair(TEdge *e) -{ - if( !IsMaxima(e->next, e->ytop) || e->next->xtop != e->xtop ) - return e->prev; else - return e->next; -} -//------------------------------------------------------------------------------ - -void Clipper::SwapPositionsInAEL(TEdge *edge1, TEdge *edge2) -{ - if( edge1->nextInAEL == edge2 ) - { - TEdge* next = edge2->nextInAEL; - if( next ) next->prevInAEL = edge1; - TEdge* prev = edge1->prevInAEL; - if( prev ) prev->nextInAEL = edge2; - edge2->prevInAEL = prev; - edge2->nextInAEL = edge1; - edge1->prevInAEL = edge2; - edge1->nextInAEL = next; - } - else if( edge2->nextInAEL == edge1 ) - { - TEdge* next = edge1->nextInAEL; - if( next ) next->prevInAEL = edge2; - TEdge* prev = edge2->prevInAEL; - if( prev ) prev->nextInAEL = edge1; - edge1->prevInAEL = prev; - edge1->nextInAEL = edge2; - edge2->prevInAEL = edge1; - edge2->nextInAEL = next; - } - else - { - TEdge* next = edge1->nextInAEL; - TEdge* prev = edge1->prevInAEL; - edge1->nextInAEL = edge2->nextInAEL; - if( edge1->nextInAEL ) edge1->nextInAEL->prevInAEL = edge1; - edge1->prevInAEL = edge2->prevInAEL; - if( edge1->prevInAEL ) edge1->prevInAEL->nextInAEL = edge1; - edge2->nextInAEL = next; - if( edge2->nextInAEL ) edge2->nextInAEL->prevInAEL = edge2; - edge2->prevInAEL = prev; - if( edge2->prevInAEL ) edge2->prevInAEL->nextInAEL = edge2; - } - - if( !edge1->prevInAEL ) m_ActiveEdges = edge1; - else if( !edge2->prevInAEL ) m_ActiveEdges = edge2; -} -//------------------------------------------------------------------------------ - -void Clipper::SwapPositionsInSEL(TEdge *edge1, TEdge *edge2) -{ - if( !( edge1->nextInSEL ) && !( edge1->prevInSEL ) ) return; - if( !( edge2->nextInSEL ) && !( edge2->prevInSEL ) ) return; - - if( edge1->nextInSEL == edge2 ) - { - TEdge* next = edge2->nextInSEL; - if( next ) next->prevInSEL = edge1; - TEdge* prev = edge1->prevInSEL; - if( prev ) prev->nextInSEL = edge2; - edge2->prevInSEL = prev; - edge2->nextInSEL = edge1; - edge1->prevInSEL = edge2; - edge1->nextInSEL = next; - } - else if( edge2->nextInSEL == edge1 ) - { - TEdge* next = edge1->nextInSEL; - if( next ) next->prevInSEL = edge2; - TEdge* prev = edge2->prevInSEL; - if( prev ) prev->nextInSEL = edge1; - edge1->prevInSEL = prev; - edge1->nextInSEL = edge2; - edge2->prevInSEL = edge1; - edge2->nextInSEL = next; - } - else - { - TEdge* next = edge1->nextInSEL; - TEdge* prev = edge1->prevInSEL; - edge1->nextInSEL = edge2->nextInSEL; - if( edge1->nextInSEL ) edge1->nextInSEL->prevInSEL = edge1; - edge1->prevInSEL = edge2->prevInSEL; - if( edge1->prevInSEL ) edge1->prevInSEL->nextInSEL = edge1; - edge2->nextInSEL = next; - if( edge2->nextInSEL ) edge2->nextInSEL->prevInSEL = edge2; - edge2->prevInSEL = prev; - if( edge2->prevInSEL ) edge2->prevInSEL->nextInSEL = edge2; - } - - if( !edge1->prevInSEL ) m_SortedEdges = edge1; - else if( !edge2->prevInSEL ) m_SortedEdges = edge2; -} -//------------------------------------------------------------------------------ - -TEdge* GetNextInAEL(TEdge *e, Direction dir) -{ - return dir == dLeftToRight ? e->nextInAEL : e->prevInAEL; -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessHorizontal(TEdge *horzEdge) -{ - Direction dir; - long64 horzLeft, horzRight; - - if( horzEdge->xcurr < horzEdge->xtop ) - { - horzLeft = horzEdge->xcurr; - horzRight = horzEdge->xtop; - dir = dLeftToRight; - } else - { - horzLeft = horzEdge->xtop; - horzRight = horzEdge->xcurr; - dir = dRightToLeft; - } - - TEdge* eMaxPair; - if( horzEdge->nextInLML ) eMaxPair = 0; - else eMaxPair = GetMaximaPair(horzEdge); - - TEdge* e = GetNextInAEL( horzEdge , dir ); - while( e ) - { - if ( e->xcurr == horzEdge->xtop && !eMaxPair ) - { - if (SlopesEqual(*e, *horzEdge->nextInLML, m_UseFullRange)) - { - //if output polygons share an edge, they'll need joining later ... - if (horzEdge->outIdx >= 0 && e->outIdx >= 0) - AddJoin(horzEdge->nextInLML, e, horzEdge->outIdx); - break; //we've reached the end of the horizontal line - } - else if (e->dx < horzEdge->nextInLML->dx) - //we really have got to the end of the intermediate horz edge so quit. - //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. - break; - } - - TEdge* eNext = GetNextInAEL( e, dir ); - - if (eMaxPair || - ((dir == dLeftToRight) && (e->xcurr < horzRight)) || - ((dir == dRightToLeft) && (e->xcurr > horzLeft))) - { - //so far we're still in range of the horizontal edge - if( e == eMaxPair ) - { - //horzEdge is evidently a maxima horizontal and we've arrived at its end. - if (dir == dLeftToRight) - IntersectEdges(horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); - else - IntersectEdges(e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); - if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); - return; - } - else if( NEAR_EQUAL(e->dx, HORIZONTAL) && !IsMinima(e) && !(e->xcurr > e->xtop) ) - { - //An overlapping horizontal edge. Overlapping horizontal edges are - //processed as if layered with the current horizontal edge (horizEdge) - //being infinitesimally lower that the next (e). Therfore, we - //intersect with e only if e.xcurr is within the bounds of horzEdge ... - if( dir == dLeftToRight ) - IntersectEdges( horzEdge , e, IntPoint(e->xcurr, horzEdge->ycurr), - (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); - else - IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), - (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); - } - else if( dir == dLeftToRight ) - { - IntersectEdges( horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), - (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); - } - else - { - IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), - (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); - } - SwapPositionsInAEL( horzEdge, e ); - } - else if( (dir == dLeftToRight && e->xcurr >= horzRight) || - (dir == dRightToLeft && e->xcurr <= horzLeft) ) break; - e = eNext; - } //end while - - if( horzEdge->nextInLML ) - { - if( horzEdge->outIdx >= 0 ) - AddOutPt( horzEdge, IntPoint(horzEdge->xtop, horzEdge->ytop)); - UpdateEdgeIntoAEL( horzEdge ); - } - else - { - if ( horzEdge->outIdx >= 0 ) - IntersectEdges( horzEdge, eMaxPair, - IntPoint(horzEdge->xtop, horzEdge->ycurr), ipBoth); - if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); - DeleteFromAEL(eMaxPair); - DeleteFromAEL(horzEdge); - } -} -//------------------------------------------------------------------------------ - -void Clipper::UpdateEdgeIntoAEL(TEdge *&e) -{ - if( !e->nextInLML ) throw - clipperException("UpdateEdgeIntoAEL: invalid call"); - TEdge* AelPrev = e->prevInAEL; - TEdge* AelNext = e->nextInAEL; - e->nextInLML->outIdx = e->outIdx; - if( AelPrev ) AelPrev->nextInAEL = e->nextInLML; - else m_ActiveEdges = e->nextInLML; - if( AelNext ) AelNext->prevInAEL = e->nextInLML; - e->nextInLML->side = e->side; - e->nextInLML->windDelta = e->windDelta; - e->nextInLML->windCnt = e->windCnt; - e->nextInLML->windCnt2 = e->windCnt2; - e = e->nextInLML; - e->prevInAEL = AelPrev; - e->nextInAEL = AelNext; - if( !NEAR_EQUAL(e->dx, HORIZONTAL) ) InsertScanbeam( e->ytop ); -} -//------------------------------------------------------------------------------ - -bool Clipper::ProcessIntersections(const long64 botY, const long64 topY) -{ - if( !m_ActiveEdges ) return true; - try { - BuildIntersectList(botY, topY); - if ( !m_IntersectNodes) return true; - if ( FixupIntersectionOrder() ) ProcessIntersectList(); - else return false; - } - catch(...) { - m_SortedEdges = 0; - DisposeIntersectNodes(); - throw clipperException("ProcessIntersections error"); - } - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeIntersectNodes() -{ - while ( m_IntersectNodes ) - { - IntersectNode* iNode = m_IntersectNodes->next; - delete m_IntersectNodes; - m_IntersectNodes = iNode; - } -} -//------------------------------------------------------------------------------ - -void Clipper::BuildIntersectList(const long64 botY, const long64 topY) -{ - if ( !m_ActiveEdges ) return; - - //prepare for sorting ... - TEdge* e = m_ActiveEdges; - m_SortedEdges = e; - while( e ) - { - e->prevInSEL = e->prevInAEL; - e->nextInSEL = e->nextInAEL; - e->xcurr = TopX( *e, topY ); - e = e->nextInAEL; - } - - //bubblesort ... - bool isModified = true; - while( isModified && m_SortedEdges ) - { - isModified = false; - e = m_SortedEdges; - while( e->nextInSEL ) - { - TEdge *eNext = e->nextInSEL; - IntPoint pt; - if(e->xcurr > eNext->xcurr) - { - if (!IntersectPoint(*e, *eNext, pt, m_UseFullRange) && e->xcurr > eNext->xcurr +1) - throw clipperException("Intersection error"); - if (pt.Y > botY) - { - pt.Y = botY; - pt.X = TopX(*e, pt.Y); - } - AddIntersectNode( e, eNext, pt ); - SwapPositionsInSEL(e, eNext); - isModified = true; - } - else - e = eNext; - } - if( e->prevInSEL ) e->prevInSEL->nextInSEL = 0; - else break; - } - m_SortedEdges = 0; -} -//------------------------------------------------------------------------------ - -bool ProcessParam1BeforeParam2(const IntersectNode &node1, const IntersectNode &node2) -{ - bool result; - if (node1.pt.Y == node2.pt.Y) - { - if (node1.edge1 == node2.edge1 || node1.edge2 == node2.edge1) - { - result = node2.pt.X > node1.pt.X; - return node2.edge1->dx > 0 ? !result : result; - } - else if (node1.edge1 == node2.edge2 || node1.edge2 == node2.edge2) - { - result = node2.pt.X > node1.pt.X; - return node2.edge2->dx > 0 ? !result : result; - } - else return node2.pt.X > node1.pt.X; - } - else return node1.pt.Y > node2.pt.Y; -} -//------------------------------------------------------------------------------ - -void Clipper::AddIntersectNode(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( ProcessParam1BeforeParam2(*newNode, *m_IntersectNodes) ) - { - newNode->next = m_IntersectNodes; - m_IntersectNodes = newNode; - } - else - { - IntersectNode* iNode = m_IntersectNodes; - while( iNode->next && ProcessParam1BeforeParam2(*iNode->next, *newNode) ) - iNode = iNode->next; - newNode->next = iNode->next; - iNode->next = newNode; - } -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessIntersectList() -{ - while( m_IntersectNodes ) - { - IntersectNode* iNode = m_IntersectNodes->next; - { - IntersectEdges( m_IntersectNodes->edge1 , - m_IntersectNodes->edge2 , m_IntersectNodes->pt, ipBoth ); - SwapPositionsInAEL( m_IntersectNodes->edge1 , m_IntersectNodes->edge2 ); - } - delete m_IntersectNodes; - m_IntersectNodes = iNode; - } -} -//------------------------------------------------------------------------------ - -void Clipper::DoMaxima(TEdge *e, long64 topY) -{ - TEdge* eMaxPair = GetMaximaPair(e); - long64 X = e->xtop; - TEdge* eNext = e->nextInAEL; - while( eNext != eMaxPair ) - { - if (!eNext) throw clipperException("DoMaxima error"); - IntersectEdges( e, eNext, IntPoint(X, topY), ipBoth ); - SwapPositionsInAEL(e, eNext); - eNext = e->nextInAEL; - } - if( e->outIdx < 0 && eMaxPair->outIdx < 0 ) - { - DeleteFromAEL( e ); - DeleteFromAEL( eMaxPair ); - } - else if( e->outIdx >= 0 && eMaxPair->outIdx >= 0 ) - { - IntersectEdges( e, eMaxPair, IntPoint(X, topY), ipNone ); - } - else throw clipperException("DoMaxima error"); -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessEdgesAtTopOfScanbeam(const long64 topY) -{ - TEdge* e = m_ActiveEdges; - while( e ) - { - //1. process maxima, treating them as if they're 'bent' horizontal edges, - // but exclude maxima with horizontal edges. nb: e can't be a horizontal. - if( IsMaxima(e, topY) && !NEAR_EQUAL(GetMaximaPair(e)->dx, HORIZONTAL) ) - { - //'e' might be removed from AEL, as may any following edges so ... - TEdge* ePrev = e->prevInAEL; - DoMaxima(e, topY); - if( !ePrev ) e = m_ActiveEdges; - else e = ePrev->nextInAEL; - } - else - { - //2. promote horizontal edges, otherwise update xcurr and ycurr ... - if( IsIntermediate(e, topY) && NEAR_EQUAL(e->nextInLML->dx, HORIZONTAL) ) - { - if (e->outIdx >= 0) - { - AddOutPt(e, IntPoint(e->xtop, e->ytop)); - - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) - { - IntPoint pt, pt2; - HorzJoinRec* hj = m_HorizJoins[i]; - if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), - IntPoint(hj->edge->xtop, hj->edge->ytop), - IntPoint(e->nextInLML->xbot, e->nextInLML->ybot), - IntPoint(e->nextInLML->xtop, e->nextInLML->ytop), pt, pt2)) - AddJoin(hj->edge, e->nextInLML, hj->savedIdx, e->outIdx); - } - - AddHorzJoin(e->nextInLML, e->outIdx); - } - UpdateEdgeIntoAEL(e); - AddEdgeToSEL(e); - } else - { - //this just simplifies horizontal processing ... - e->xcurr = TopX( *e, topY ); - e->ycurr = topY; - } - e = e->nextInAEL; - } - } - - //3. Process horizontals at the top of the scanbeam ... - ProcessHorizontals(); - - //4. Promote intermediate vertices ... - e = m_ActiveEdges; - while( e ) - { - if( IsIntermediate( e, topY ) ) - { - if( e->outIdx >= 0 ) AddOutPt(e, IntPoint(e->xtop,e->ytop)); - UpdateEdgeIntoAEL(e); - - //if output polygons share an edge, they'll need joining later ... - TEdge* ePrev = e->prevInAEL; - TEdge* eNext = e->nextInAEL; - if (ePrev && ePrev->xcurr == e->xbot && - ePrev->ycurr == e->ybot && e->outIdx >= 0 && - ePrev->outIdx >= 0 && ePrev->ycurr > ePrev->ytop && - SlopesEqual(*e, *ePrev, m_UseFullRange)) - { - AddOutPt(ePrev, IntPoint(e->xbot, e->ybot)); - AddJoin(e, ePrev); - } - else if (eNext && eNext->xcurr == e->xbot && - eNext->ycurr == e->ybot && e->outIdx >= 0 && - eNext->outIdx >= 0 && eNext->ycurr > eNext->ytop && - SlopesEqual(*e, *eNext, m_UseFullRange)) - { - AddOutPt(eNext, IntPoint(e->xbot, e->ybot)); - AddJoin(e, eNext); - } - } - e = e->nextInAEL; - } -} -//------------------------------------------------------------------------------ - -void Clipper::FixupOutPolygon(OutRec &outRec) -{ - //FixupOutPolygon() - removes duplicate points and simplifies consecutive - //parallel edges by removing the middle vertex. - OutPt *lastOK = 0; - outRec.pts = outRec.bottomPt; - OutPt *pp = outRec.bottomPt; - - for (;;) - { - if (pp->prev == pp || pp->prev == pp->next ) - { - DisposeOutPts(pp); - outRec.pts = 0; - outRec.bottomPt = 0; - return; - } - //test for duplicate points and for same slope (cross-product) ... - if ( PointsEqual(pp->pt, pp->next->pt) || - SlopesEqual(pp->prev->pt, pp->pt, pp->next->pt, m_UseFullRange) ) - { - lastOK = 0; - OutPt *tmp = pp; - if (pp == outRec.bottomPt) - outRec.bottomPt = 0; //flags need for updating - pp->prev->next = pp->next; - pp->next->prev = pp->prev; - pp = pp->prev; - delete tmp; - } - else if (pp == lastOK) break; - else - { - if (!lastOK) lastOK = pp; - pp = pp->next; - } - } - if (!outRec.bottomPt) { - outRec.bottomPt = GetBottomPt(pp); - outRec.bottomPt->idx = outRec.idx; - outRec.pts = outRec.bottomPt; - } -} -//------------------------------------------------------------------------------ - -void Clipper::BuildResult(Polygons &polys) -{ - polys.reserve(m_PolyOuts.size()); - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - if (m_PolyOuts[i]->pts) - { - Polygon pg; - OutPt* p = m_PolyOuts[i]->pts; - do - { - pg.push_back(p->pt); - p = p->prev; - } while (p != m_PolyOuts[i]->pts); - if (pg.size() > 2) - polys.push_back(pg); - } - } -} -//------------------------------------------------------------------------------ - -int PointCount(OutPt *pts) -{ - if (!pts) return 0; - int result = 0; - OutPt* p = pts; - do - { - result++; - p = p->next; - } - while (p != pts); - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::BuildResult2(PolyTree& polytree) -{ - polytree.Clear(); - polytree.AllNodes.reserve(m_PolyOuts.size()); - //add each output polygon/contour to polytree ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) - { - OutRec* outRec = m_PolyOuts[i]; - int cnt = PointCount(outRec->pts); - if (cnt < 3) continue; - FixHoleLinkage(*outRec); - PolyNode* pn = new PolyNode(); - //nb: polytree takes ownership of all the PolyNodes - polytree.AllNodes.push_back(pn); - outRec->polyNode = pn; - pn->Parent = 0; - pn->Index = 0; - pn->Contour.reserve(cnt); - OutPt *op = outRec->pts; - for (int j = 0; j < cnt; j++) - { - pn->Contour.push_back(op->pt); - op = op->prev; - } - } - - //fixup PolyNode links etc ... - polytree.Childs.reserve(m_PolyOuts.size()); - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) - { - OutRec* outRec = m_PolyOuts[i]; - if (!outRec->polyNode) continue; - if (outRec->FirstLeft) - outRec->FirstLeft->polyNode->AddChild(*outRec->polyNode); - else - polytree.AddChild(*outRec->polyNode); - } -} -//------------------------------------------------------------------------------ - -void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) -{ - TEdge *e1 = int1.edge1; - TEdge *e2 = int1.edge2; - IntPoint p = int1.pt; - - int1.edge1 = int2.edge1; - int1.edge2 = int2.edge2; - int1.pt = int2.pt; - - int2.edge1 = e1; - int2.edge2 = e2; - int2.pt = p; -} -//------------------------------------------------------------------------------ - -bool Clipper::FixupIntersectionOrder() -{ - if ( !m_IntersectNodes->next ) return true; - - CopyAELToSEL(); - IntersectNode *int1 = m_IntersectNodes; - IntersectNode *int2 = m_IntersectNodes->next; - while (int2) - { - TEdge *e1 = int1->edge1; - TEdge *e2; - if (e1->prevInSEL == int1->edge2) e2 = e1->prevInSEL; - else if (e1->nextInSEL == int1->edge2) e2 = e1->nextInSEL; - else - { - //The current intersection (Int1) is out of order (since it doesn't - //contain adjacent edges), so swap it with a subsequent intersection ... - while (int2) - { - if (int2->edge1->nextInSEL == int2->edge2 || - int2->edge1->prevInSEL == int2->edge2) break; - else int2 = int2->next; - } - if ( !int2 ) return false; //oops!!! - - //found an intersect node (Int2) that does contain adjacent edges, - //so prepare to process it before Int1 ... - SwapIntersectNodes(*int1, *int2); - e1 = int1->edge1; - e2 = int1->edge2; - } - SwapPositionsInSEL(e1, e2); - int1 = int1->next; - int2 = int1->next; - } - - m_SortedEdges = 0; - - //finally, check the last intersection too ... - return (int1->edge1->prevInSEL == int1->edge2 || - int1->edge1->nextInSEL == int1->edge2); -} -//------------------------------------------------------------------------------ - -bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) -{ - return e2.xcurr == e1.xcurr ? e2.dx > e1.dx : e2.xcurr < e1.xcurr; -} -//------------------------------------------------------------------------------ - -void Clipper::InsertEdgeIntoAEL(TEdge *edge) -{ - edge->prevInAEL = 0; - edge->nextInAEL = 0; - if( !m_ActiveEdges ) - { - m_ActiveEdges = edge; - } - else if( E2InsertsBeforeE1(*m_ActiveEdges, *edge) ) - { - edge->nextInAEL = m_ActiveEdges; - m_ActiveEdges->prevInAEL = edge; - m_ActiveEdges = edge; - } else - { - TEdge* e = m_ActiveEdges; - while( e->nextInAEL && !E2InsertsBeforeE1(*e->nextInAEL , *edge) ) - e = e->nextInAEL; - edge->nextInAEL = e->nextInAEL; - if( e->nextInAEL ) e->nextInAEL->prevInAEL = edge; - edge->prevInAEL = e; - e->nextInAEL = edge; - } -} -//---------------------------------------------------------------------- - -void Clipper::DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt) -{ - AddOutPt(edge1, pt); - SwapSides(*edge1, *edge2); - SwapPolyIndexes(*edge1, *edge2); -} -//---------------------------------------------------------------------- - -void Clipper::DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt) -{ - AddOutPt(edge2, pt); - SwapSides(*edge1, *edge2); - SwapPolyIndexes(*edge1, *edge2); -} -//---------------------------------------------------------------------- - -void Clipper::DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt) -{ - AddOutPt(edge1, pt); - AddOutPt(edge2, pt); - SwapSides( *edge1 , *edge2 ); - SwapPolyIndexes( *edge1 , *edge2 ); -} -//---------------------------------------------------------------------- - -bool Clipper::JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2) -{ - OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; - OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; - if (!outRec1 || !outRec2) return false; - OutPt *pp1a = outRec1->pts; - OutPt *pp2a = outRec2->pts; - IntPoint pt1 = j->pt2a, pt2 = j->pt2b; - IntPoint pt3 = j->pt1a, pt4 = j->pt1b; - if (!FindSegment(pp1a, m_UseFullRange, pt1, pt2)) return false; - if (outRec1 == outRec2) - { - //we're searching the same polygon for overlapping segments so - //segment 2 mustn't be the same as segment 1 ... - pp2a = pp1a->next; - if (!FindSegment(pp2a, m_UseFullRange, pt3, pt4) || (pp2a == pp1a)) - return false; - } - else if (!FindSegment(pp2a, m_UseFullRange, pt3, pt4)) return false; - - if (!GetOverlapSegment(pt1, pt2, pt3, pt4, pt1, pt2)) return false; - - OutPt *p3, *p4, *prev = pp1a->prev; - //get p1 & p2 polypts - the overlap start & endpoints on poly1 - if (PointsEqual(pp1a->pt, pt1)) p1 = pp1a; - else if (PointsEqual(prev->pt, pt1)) p1 = prev; - else p1 = InsertPolyPtBetween(pp1a, prev, pt1); - - if (PointsEqual(pp1a->pt, pt2)) p2 = pp1a; - else if (PointsEqual(prev->pt, pt2)) p2 = prev; - else if ((p1 == pp1a) || (p1 == prev)) - p2 = InsertPolyPtBetween(pp1a, prev, pt2); - else if (Pt3IsBetweenPt1AndPt2(pp1a->pt, p1->pt, pt2)) - p2 = InsertPolyPtBetween(pp1a, p1, pt2); else - p2 = InsertPolyPtBetween(p1, prev, pt2); - - //get p3 & p4 polypts - the overlap start & endpoints on poly2 - prev = pp2a->prev; - if (PointsEqual(pp2a->pt, pt1)) p3 = pp2a; - else if (PointsEqual(prev->pt, pt1)) p3 = prev; - else p3 = InsertPolyPtBetween(pp2a, prev, pt1); - - if (PointsEqual(pp2a->pt, pt2)) p4 = pp2a; - else if (PointsEqual(prev->pt, pt2)) p4 = prev; - else if ((p3 == pp2a) || (p3 == prev)) - p4 = InsertPolyPtBetween(pp2a, prev, pt2); - else if (Pt3IsBetweenPt1AndPt2(pp2a->pt, p3->pt, pt2)) - p4 = InsertPolyPtBetween(pp2a, p3, pt2); else - p4 = InsertPolyPtBetween(p3, prev, pt2); - - //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ... - if (p1->next == p2 && p3->prev == p4) - { - p1->next = p3; - p3->prev = p1; - p2->prev = p4; - p4->next = p2; - return true; - } - else if (p1->prev == p2 && p3->next == p4) - { - p1->prev = p3; - p3->next = p1; - p2->next = p4; - p4->prev = p2; - return true; - } - else - return false; //an orientation is probably wrong -} -//---------------------------------------------------------------------- - -void Clipper::FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx) -{ - for (JoinList::size_type k = startIdx; k < m_Joins.size(); k++) - { - JoinRec* j2 = m_Joins[k]; - if (j2->poly1Idx == j->poly1Idx && PointIsVertex(j2->pt1a, pt)) - j2->poly1Idx = j->poly2Idx; - if (j2->poly2Idx == j->poly1Idx && PointIsVertex(j2->pt2a, pt)) - j2->poly2Idx = j->poly2Idx; - } -} -//---------------------------------------------------------------------- - -bool Poly2ContainsPoly1(OutPt* outPt1, OutPt* outPt2, bool UseFullInt64Range) -{ - //find the first pt in outPt1 that isn't also a vertex of outPt2 ... - OutPt* outPt = outPt1; - do - { - if (!PointIsVertex(outPt->pt, outPt2)) break; - outPt = outPt->next; - } - while (outPt != outPt1); - bool result; - //sometimes a point on one polygon can be touching the other polygon - //so to be totally confident outPt1 is inside outPt2 repeat ... - do - { - result = PointInPolygon(outPt->pt, outPt2, UseFullInt64Range); - outPt = outPt->next; - } - while (result && outPt != outPt1); - return result; -} -//---------------------------------------------------------------------- - -void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) -{ - - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - if (outRec->pts && outRec->FirstLeft == OldOutRec) - { - if (Poly2ContainsPoly1(outRec->pts, NewOutRec->pts, m_UseFullRange)) - outRec->FirstLeft = NewOutRec; - } - } -} -//---------------------------------------------------------------------- - -void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) -{ - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; - } -} -//---------------------------------------------------------------------- - -void Clipper::JoinCommonEdges() -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) - { - JoinRec* j = m_Joins[i]; - - OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; - OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; - - if (!outRec1->pts || !outRec2->pts) continue; - - //get the polygon fragment with the correct hole state (FirstLeft) - //before calling JoinPoints() ... - OutRec *holeStateRec; - if (outRec1 == outRec2) holeStateRec = outRec1; - else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; - else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; - else holeStateRec = GetLowermostRec(outRec1, outRec2); - - OutPt *p1, *p2; - if (!JoinPoints(j, p1, p2)) continue; - - if (outRec1 == outRec2) - { - //instead of joining two polygons, we've just created a new one by - //splitting one polygon into two. - outRec1->pts = GetBottomPt(p1); - outRec1->bottomPt = outRec1->pts; - outRec1->bottomPt->idx = outRec1->idx; - outRec2 = CreateOutRec(); - m_PolyOuts.push_back(outRec2); - outRec2->idx = (int)m_PolyOuts.size()-1; - j->poly2Idx = outRec2->idx; - outRec2->pts = GetBottomPt(p2); - outRec2->bottomPt = outRec2->pts; - outRec2->bottomPt->idx = outRec2->idx; - - if (Poly2ContainsPoly1(outRec2->pts, outRec1->pts, m_UseFullRange)) - { - //outRec2 is contained by outRec1 ... - outRec2->isHole = !outRec1->isHole; - outRec2->FirstLeft = outRec1; - - FixupJoinRecs(j, p2, i+1); - - //fixup FirstLeft pointers that may need reassigning to OutRec1 - if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); - - FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation - FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() - - - if ((outRec2->isHole ^ m_ReverseOutput) == (Area(*outRec2, m_UseFullRange) > 0)) - ReversePolyPtLinks(outRec2->pts); - - } else if (Poly2ContainsPoly1(outRec1->pts, outRec2->pts, m_UseFullRange)) - { - //outRec1 is contained by outRec2 ... - outRec2->isHole = outRec1->isHole; - outRec1->isHole = !outRec2->isHole; - outRec2->FirstLeft = outRec1->FirstLeft; - outRec1->FirstLeft = outRec2; - - FixupJoinRecs(j, p2, i+1); - - //fixup FirstLeft pointers that may need reassigning to OutRec1 - if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); - - FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation - FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() - - if ((outRec1->isHole ^ m_ReverseOutput) == (Area(*outRec1, m_UseFullRange) > 0)) - ReversePolyPtLinks(outRec1->pts); - } - else - { - //the 2 polygons are completely separate ... - outRec2->isHole = outRec1->isHole; - outRec2->FirstLeft = outRec1->FirstLeft; - - FixupJoinRecs(j, p2, i+1); - - //fixup FirstLeft pointers that may need reassigning to OutRec2 - if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); - - FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation - FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() - } - - } else - { - //joined 2 polygons together ... - - //cleanup redundant edges ... - FixupOutPolygon(*outRec1); - - //delete the obsolete pointer ... - int OKIdx = outRec1->idx; - int ObsoleteIdx = outRec2->idx; - outRec2->pts = 0; - outRec2->bottomPt = 0; - - outRec1->isHole = holeStateRec->isHole; - if (holeStateRec == outRec2) - outRec1->FirstLeft = outRec2->FirstLeft; - outRec2->FirstLeft = outRec1; - - //now fixup any subsequent Joins that match this polygon - for (JoinList::size_type k = i+1; k < m_Joins.size(); k++) - { - JoinRec* j2 = m_Joins[k]; - if (j2->poly1Idx == ObsoleteIdx) j2->poly1Idx = OKIdx; - if (j2->poly2Idx == ObsoleteIdx) j2->poly2Idx = OKIdx; - } - - //fixup FirstLeft pointers that may need reassigning to OutRec1 - if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); - } - } -} -//------------------------------------------------------------------------------ - -void ReversePolygon(Polygon& p) -{ - std::reverse(p.begin(), p.end()); -} -//------------------------------------------------------------------------------ - -void ReversePolygons(Polygons& p) -{ - for (Polygons::size_type i = 0; i < p.size(); ++i) - ReversePolygon(p[i]); -} - -//------------------------------------------------------------------------------ -// OffsetPolygon functions ... -//------------------------------------------------------------------------------ - -struct DoublePoint -{ - double X; - double Y; - DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} -}; -//------------------------------------------------------------------------------ - -Polygon BuildArc(const IntPoint &pt, - const double a1, const double a2, const double r, double limit) -{ - //see notes in clipper.pas regarding steps - double arcFrac = std::fabs(a2 - a1) / (2 * pi); - int steps = (int)(arcFrac * pi / std::acos(1 - limit / std::fabs(r))); - if (steps < 2) steps = 2; - else if (steps > (int)(222.0 * arcFrac)) steps = (int)(222.0 * arcFrac); - - double x = std::cos(a1); - double y = std::sin(a1); - double c = std::cos((a2 - a1) / steps); - double s = std::sin((a2 - a1) / steps); - Polygon result(steps +1); - for (int i = 0; i <= steps; ++i) - { - result[i].X = pt.X + Round(x * r); - result[i].Y = pt.Y + Round(y * r); - double x2 = x; - x = x * c - s * y; //cross product - y = x2 * s + y * c; //dot product - } - return result; -} -//------------------------------------------------------------------------------ - -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 PolyOffsetBuilder -{ -private: - Polygons m_p; - Polygon* m_curr_poly; - std::vector normals; - double m_delta, m_RMin, m_R; - size_t m_i, m_j, m_k; - static const int buffLength = 128; - JoinType m_jointype; - -public: - -PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, - double delta, JoinType jointype, double limit, bool autoFix) -{ - //nb precondition - out_polys != ptsin_polys - if (NEAR_ZERO(delta)) - { - out_polys = in_polys; - return; - } - - this->m_p = in_polys; - this->m_delta = delta; - this->m_jointype = jointype; - - //ChecksInput - fixes polygon orientation if necessary and removes - //duplicate vertices. Can be set false when you're sure that polygon - //orientation is correct and that there are no duplicate vertices. - if (autoFix) - { - size_t Len = m_p.size(), botI = 0; - while (botI < Len && m_p[botI].size() == 0) botI++; - if (botI == Len) return; - - //botPt: used to find the lowermost (in inverted Y-axis) & leftmost point - //This point (on m_p[botI]) must be on an outer polygon ring and if - //its orientation is false (counterclockwise) then assume all polygons - //need reversing ... - IntPoint botPt = m_p[botI][0]; - for (size_t i = botI; i < Len; ++i) - { - if (m_p[i].size() < 3) continue; - if (UpdateBotPt(m_p[i][0], botPt)) botI = i; - Polygon::iterator it = m_p[i].begin() +1; - while (it != m_p[i].end()) - { - if (PointsEqual(*it, *(it -1))) - it = m_p[i].erase(it); - else - { - if (UpdateBotPt(*it, botPt)) botI = i; - ++it; - } - } - } - if (!Orientation(m_p[botI])) - ReversePolygons(m_p); - } - - switch (jointype) - { - case jtRound: - if (limit <= 0) limit = 0.25; - else if (limit > std::fabs(delta)) limit = std::fabs(delta); - break; - case jtMiter: - if (limit < 2) limit = 2; - break; - default: //unused - limit = 1; - } - m_RMin = 2.0/(limit*limit); - - double deltaSq = delta*delta; - out_polys.clear(); - out_polys.resize(m_p.size()); - for (m_i = 0; m_i < m_p.size(); m_i++) - { - m_curr_poly = &out_polys[m_i]; - size_t len = m_p[m_i].size(); - if (len > 1 && m_p[m_i][0].X == m_p[m_i][len - 1].X && - m_p[m_i][0].Y == m_p[m_i][len-1].Y) len--; - - //when 'shrinking' polygons - to minimize artefacts - //strip those polygons that have an area < pi * delta^2 ... - double a1 = Area(m_p[m_i]); - if (delta < 0) { if (a1 > 0 && a1 < deltaSq *pi) len = 0; } - else if (a1 < 0 && -a1 < deltaSq *pi) len = 0; //holes have neg. area - - if (len == 0 || (len < 3 && delta <= 0)) - continue; - else if (len == 1) - { - Polygon arc; - arc = BuildArc(m_p[m_i][len-1], 0, 2 * pi, delta, limit); - out_polys[m_i] = arc; - continue; - } - - //build normals ... - normals.clear(); - normals.resize(len); - normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]); - 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]); - - m_k = len -1; - for (m_j = 0; m_j < len; ++m_j) - { - switch (jointype) - { - case jtMiter: - { - m_R = 1 + (normals[m_j].X*normals[m_k].X + - normals[m_j].Y*normals[m_k].Y); - if (m_R >= m_RMin) DoMiter(); else DoSquare(limit); - break; - } - case jtSquare: DoSquare(1.0); break; - case jtRound: DoRound(limit); break; - } - m_k = m_j; - } - } - - //finally, clean up untidy corners using Clipper ... - Clipper clpr; - clpr.AddPolygons(out_polys, ptSubject); - if (delta > 0) - { - if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive)) - out_polys.clear(); - } - else - { - IntRect r = clpr.GetBounds(); - Polygon 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.AddPolygon(outer, ptSubject); - if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative)) - { - out_polys.erase(out_polys.begin()); - ReversePolygons(out_polys); - - } else - out_polys.clear(); - } -} -//------------------------------------------------------------------------------ - -private: - -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 mul) -{ - IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), - (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); - IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) - { - double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); - double a2 = std::atan2(-normals[m_j].Y, -normals[m_j].X); - a1 = std::fabs(a2 - a1); - if (a1 > pi) a1 = pi * 2 - a1; - double dx = std::tan((pi - a1) / 4) * std::fabs(m_delta * mul); - pt1 = IntPoint((long64)(pt1.X -normals[m_k].Y * dx), - (long64)(pt1.Y + normals[m_k].X * dx)); - AddPoint(pt1); - pt2 = IntPoint((long64)(pt2.X + normals[m_j].Y * dx), - (long64)(pt2.Y -normals[m_j].X * dx)); - AddPoint(pt2); - } - else - { - AddPoint(pt1); - AddPoint(m_p[m_i][m_j]); - AddPoint(pt2); - } -} -//------------------------------------------------------------------------------ - -void DoMiter() -{ - if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) - { - double q = m_delta / m_R; - AddPoint(IntPoint((long64)Round(m_p[m_i][m_j].X + - (normals[m_k].X + normals[m_j].X) * q), - (long64)Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); - } - else - { - IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * - m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); - IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * - m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - AddPoint(pt1); - AddPoint(m_p[m_i][m_j]); - AddPoint(pt2); - } -} -//------------------------------------------------------------------------------ - -void DoRound(double limit) -{ - IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), - (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); - IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - AddPoint(pt1); - //round off reflex angles (ie > 180 deg) unless almost flat (ie < ~10deg). - if ((normals[m_k].X*normals[m_j].Y - normals[m_j].X*normals[m_k].Y) * m_delta >= 0) - { - if (normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y < 0.985) - { - double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); - double a2 = std::atan2(normals[m_j].Y, normals[m_j].X); - if (m_delta > 0 && a2 < a1) a2 += pi *2; - else if (m_delta < 0 && a2 > a1) a2 -= pi *2; - Polygon arc = BuildArc(m_p[m_i][m_j], a1, a2, m_delta, limit); - for (Polygon::size_type m = 0; m < arc.size(); m++) - AddPoint(arc[m]); - } - } - else - AddPoint(m_p[m_i][m_j]); - AddPoint(pt2); -} -//-------------------------------------------------------------------------- - -bool UpdateBotPt(const IntPoint &pt, IntPoint &botPt) -{ - if (pt.Y > botPt.Y || (pt.Y == botPt.Y && pt.X < botPt.X)) - { - botPt = pt; - return true; - } - else return false; -} -//-------------------------------------------------------------------------- - -}; //end PolyOffsetBuilder - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype, double limit, bool autoFix) -{ - if (&out_polys == &in_polys) - { - Polygons poly2(in_polys); - PolyOffsetBuilder(poly2, out_polys, delta, jointype, limit, autoFix); - } - else PolyOffsetBuilder(in_polys, out_polys, delta, jointype, limit, autoFix); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType) -{ - Clipper c; - c.AddPolygon(in_poly, ptSubject); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType) -{ - Clipper c; - c.AddPolygons(in_polys, ptSubject); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(Polygons &polys, PolyFillType fillType) -{ - SimplifyPolygons(polys, polys, fillType); -} -//------------------------------------------------------------------------------ - -bool PointsAreClose(IntPoint pt1, IntPoint pt2, long64 distSqrd) -{ - long64 dx = pt1.X - pt2.X; - long64 dy = pt1.Y - pt2.Y; - return ((dx * dx) + (dy * dy) <= distSqrd); -} -//------------------------------------------------------------------------------ - -void CleanPolygon(Polygon& in_poly, Polygon& out_poly, double distance) -{ - //distance = proximity in units/pixels below which vertices - //will be stripped. Default ~= sqrt(2). - int highI = in_poly.size() -1; - long64 d = (int)(distance * distance); - while (highI > 0 && PointsAreClose(in_poly[highI], in_poly[0], d)) highI--; - if (highI < 2) - { - out_poly.clear(); - return; - } - out_poly.resize(highI + 1); - bool UseFullRange = FullRangeNeeded(in_poly); - IntPoint pt = in_poly[highI]; - int i = 0; - int k = 0; - for (;;) - { - if (i >= highI) break; - int j = i + 1; - - if (PointsAreClose(pt, in_poly[j], d)) - { - i = j + 1; - while (i <= highI && PointsAreClose(pt, in_poly[i], d)) i++; - continue; - } - - if (PointsAreClose(in_poly[i], in_poly[j], d) || - SlopesEqual(pt, in_poly[i], in_poly[j], UseFullRange)) - { - i = j; - continue; - } - - pt = in_poly[i++]; - out_poly[k++] = pt; - } - - if (i <= highI) out_poly[k++] = in_poly[i]; - if (k > 2 && SlopesEqual(out_poly[k -2], out_poly[k -1], out_poly[0], UseFullRange)) - k--; - if (k < 3) out_poly.clear(); - else if (k <= highI) out_poly.resize(k); -} -//------------------------------------------------------------------------------ - -void CleanPolygons(Polygons& in_polys, Polygons& out_polys, double distance) -{ - for (Polygons::size_type i = 0; i < in_polys.size(); ++i) - CleanPolygon(in_polys[i], out_polys[i], distance); -} -//------------------------------------------------------------------------------ - -void AddPolyNodeToPolygons(PolyNode& polynode, Polygons& polygons) -{ - if (polynode.Contour.size() > 0) - polygons.push_back(polynode.Contour); - for (int i = 0; i < polynode.ChildCount(); ++i) - AddPolyNodeToPolygons(*polynode.Childs[i], polygons); -} -//------------------------------------------------------------------------------ - -void PolyTreeToPolygons(PolyTree& polytree, Polygons& polygons) -{ - polygons.resize(0); - polygons.reserve(polytree.Total()); - AddPolyNodeToPolygons(polytree, polygons); -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, IntPoint& p) -{ - s << p.X << ' ' << p.Y << "\n"; - return s; -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, Polygon &p) -{ - for (Polygon::size_type i = 0; i < p.size(); i++) - s << p[i]; - s << "\n"; - return s; -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, Polygons &p) -{ - for (Polygons::size_type i = 0; i < p.size(); i++) - s << p[i]; - s << "\n"; - return s; -} -//------------------------------------------------------------------------------ - -} //ClipperLib namespace +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.1.3a * +* Date : 22 January 2014 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2014 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +#include "clipper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +#ifdef use_int32 + static cInt const loRange = 46340; + static cInt const hiRange = 46340; +#else + static cInt const loRange = 0x3FFFFFFF; + static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; + typedef unsigned long long ulong64; +#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 +static int const Skip = -2; //edge that would otherwise close a path + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) + +struct TEdge { + IntPoint Bot; + IntPoint Curr; + IntPoint Top; + IntPoint Delta; + double Dx; + PolyType PolyTyp; + EdgeSide Side; + int WindDelta; //1 or -1 depending on winding direction + int WindCnt; + int WindCnt2; //winding count of the opposite polytype + int OutIdx; + TEdge *Next; + TEdge *Prev; + TEdge *NextInLML; + TEdge *NextInAEL; + TEdge *PrevInAEL; + TEdge *NextInSEL; + TEdge *PrevInSEL; +}; + +struct IntersectNode { + TEdge *Edge1; + TEdge *Edge2; + IntPoint Pt; +}; + +struct LocalMinima { + cInt Y; + TEdge *LeftBound; + TEdge *RightBound; + LocalMinima *Next; +}; + +struct OutPt; + +struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *PolyNd; + OutPt *Pts; + OutPt *BottomPt; +}; + +struct OutPt { + int Idx; + IntPoint Pt; + OutPt *Next; + OutPt *Prev; +}; + +struct Join { + OutPt *OutPt1; + OutPt *OutPt2; + IntPoint OffPt; +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline cInt Round(double val) +{ + if ((val < 0)) return static_cast(val - 0.5); + else return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) +{ + return val < 0 ? -val : val; +} + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const +{ + return (int)AllNodes.size(); +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) +{ +} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const +{ + return (int)Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = (unsigned)Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() const +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsOpen() const +{ + return m_IsOpen; +} +//------------------------------------------------------------------------------ + +#ifndef use_int32 + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((cInt)9223372036854775807); //ie 2^63 -1 +// Int128 val2((cInt)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 +{ + public: + + cUInt lo; + cInt hi; + + Int128(cInt _lo = 0) + { + lo = (cUInt)_lo; + if (_lo < 0) hi = -1; else hi = 0; + } + + + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} + + Int128(const cInt& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + + Int128& operator = (const cInt &val) + { + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return *this; + } + + bool operator == (const Int128 &val) const + {return (hi == val.hi && lo == val.lo);} + + bool operator != (const Int128 &val) const + { return !(*this == val);} + + bool operator > (const Int128 &val) const + { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator < (const Int128 &val) const + { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator >= (const Int128 &val) const + { return !(*this < val);} + + bool operator <= (const Int128 &val) const + { return !(*this > val);} + + Int128& operator += (const Int128 &rhs) + { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) hi++; + return *this; + } + + Int128 operator + (const Int128 &rhs) const + { + Int128 result(*this); + result+= rhs; + return result; + } + + Int128& operator -= (const Int128 &rhs) + { + *this += -rhs; + return *this; + } + + Int128 operator - (const Int128 &rhs) const + { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const //unary negation + { + if (lo == 0) + return Int128(-hi,0); + else + return Int128(~hi,~lo +1); + } + + Int128 operator/ (const Int128 &rhs) const + { + if (rhs.lo == 0 && rhs.hi == 0) + throw "Int128 operator/: divide by zero"; + + bool negate = (rhs.hi < 0) != (hi < 0); + Int128 dividend = *this; + Int128 divisor = rhs; + if (dividend.hi < 0) dividend = -dividend; + if (divisor.hi < 0) divisor = -divisor; + + if (divisor < dividend) + { + Int128 result = Int128(0); + Int128 cntr = Int128(1); + while (divisor.hi >= 0 && !(divisor > dividend)) + { + divisor.hi <<= 1; + if ((cInt)divisor.lo < 0) divisor.hi++; + divisor.lo <<= 1; + + cntr.hi <<= 1; + if ((cInt)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi = (ulong64)divisor.hi >> 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(dividend < divisor)) + { + dividend -= divisor; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + } + if (negate) result = -result; + return result; + } + else if (rhs.hi == this->hi && rhs.lo == this->lo) + return Int128(negate ? -1: 1); + else + return Int128(0); + } + + double AsDouble() const + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + cUInt lo_ = ~lo + 1; + if (lo_ == 0) return (double)hi * shift64; + else return -(double)(lo_ + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } + +}; +//------------------------------------------------------------------------------ + +Int128 Int128Mul (cInt lhs, cInt rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = cInt(a + (c >> 32)); + tmp.lo = cInt(c << 32); + tmp.lo += cInt(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +}; +#endif + +//------------------------------------------------------------------------------ +// Miscellaneous global functions +//------------------------------------------------------------------------------ + +bool Orientation(const Path &poly) +{ + return Area(poly) >= 0; +} +//------------------------------------------------------------------------------ + +double Area(const Path &poly) +{ + int size = (int)poly.size(); + if (size < 3) return 0; + + 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; +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec) +{ + OutPt *op = outRec.Pts; + if (!op) return 0; + double a = 0; + do { + 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 * 0.5; +} +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &Pt, OutPt *pp) +{ + OutPt *pp2 = pp; + do + { + if (pp2->Pt == Pt) return true; + pp2 = pp2->Next; + } + while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +int PointInPolygon (const IntPoint &pt, const Path &path) +{ + //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; + size_t cnt = path.size(); + if (cnt < 3) return 0; + IntPoint ip = path[0]; + for(size_t i = 1; i <= cnt; ++i) + { + IntPoint ipNext = (i == cnt ? path[0] : path[i]); + if (ipNext.Y == pt.Y) + { + if ((ipNext.X == pt.X) || (ip.Y == pt.Y && + ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; + } + if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) + { + if (ip.X >= pt.X) + { + if (ipNext.X > pt.X) result = 1 - result; + else + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } else + { + if (ipNext.X > pt.X) + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } + } + ip = ipNext; + } + return result; +} +//------------------------------------------------------------------------------ + +int PointInPolygon (const IntPoint &pt, OutPt *op) +{ + //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 (op->Next->Pt.Y == pt.Y) + { + 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; + } + 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 + if (UseFullInt64Range) + return Int128Mul(e1.Delta.Y, e2.Delta.X) == Int128Mul(e1.Delta.X, e2.Delta.Y); + else +#endif + return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); +} +//------------------------------------------------------------------------------ + +inline bool IsHorizontal(TEdge &e) +{ + return e.Delta.Y == 0; +} +//------------------------------------------------------------------------------ + +inline double GetDx(const IntPoint pt1, const IntPoint pt2) +{ + return (pt1.Y == pt2.Y) ? + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +inline void SetDx(TEdge &e) +{ + e.Delta.X = (e.Top.X - e.Bot.X); + e.Delta.Y = (e.Top.Y - e.Bot.Y); + + if (e.Delta.Y == 0) e.Dx = HORIZONTAL; + else e.Dx = (double)(e.Delta.X) / e.Delta.Y; +} +//--------------------------------------------------------------------------- + +inline void SwapSides(TEdge &Edge1, TEdge &Edge2) +{ + EdgeSide Side = Edge1.Side; + Edge1.Side = Edge2.Side; + Edge2.Side = Side; +} +//------------------------------------------------------------------------------ + +inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) +{ + int OutIdx = Edge1.OutIdx; + Edge1.OutIdx = Edge2.OutIdx; + Edge2.OutIdx = OutIdx; +} +//------------------------------------------------------------------------------ + +inline cInt TopX(TEdge &edge, const cInt currentY) +{ + return ( currentY == edge.Top.Y ) ? + edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); +} +//------------------------------------------------------------------------------ + +bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, + IntPoint &ip, bool UseFullInt64Range) +{ +#ifdef use_xyz + ip.Z = 0; +#endif + double b1, b2; + //nb: with very large coordinate values, it's possible for SlopesEqual() to + //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 = Edge2.Bot; + else ip = Edge1.Bot; + return false; + } + else if (Edge1.Delta.X == 0) + { + ip.X = Edge1.Bot.X; + if (IsHorizontal(Edge2)) + ip.Y = Edge2.Bot.Y; + else + { + b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); + ip.Y = Round(ip.X / Edge2.Dx + b2); + } + } + else if (Edge2.Delta.X == 0) + { + ip.X = Edge2.Bot.X; + if (IsHorizontal(Edge1)) + ip.Y = Edge1.Bot.Y; + else + { + b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); + ip.Y = Round(ip.X / Edge1.Dx + b1); + } + } + else + { + b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; + b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); + ip.Y = Round(q); + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = Round(Edge1.Dx * q + b1); + else + ip.X = Round(Edge2.Dx * q + b2); + } + + if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) + { + if (Edge1.Top.Y > Edge2.Top.Y) + ip.Y = Edge1.Top.Y; + else + ip.Y = Edge2.Top.Y; + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); + } + return true; +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) +{ + if (!pp) return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->Next; + pp1->Next = pp1->Prev; + pp1->Prev = pp2; + pp1 = pp2; + } while( pp1 != pp ); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt*& pp) +{ + if (pp == 0) return; + pp->Prev->Next = 0; + while( pp ) + { + OutPt *tmpPp = pp; + pp = pp->Next; + delete tmpPp; + } +} +//------------------------------------------------------------------------------ + +inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) +{ + std::memset(e, 0, sizeof(TEdge)); + e->Next = eNext; + e->Prev = ePrev; + e->Curr = Pt; + e->OutIdx = Unassigned; +} +//------------------------------------------------------------------------------ + +void InitEdge2(TEdge& e, PolyType Pt) +{ + if (e.Curr.Y >= e.Next->Curr.Y) + { + e.Bot = e.Curr; + e.Top = e.Next->Curr; + } else + { + e.Top = e.Curr; + e.Bot = e.Next->Curr; + } + SetDx(e); + e.PolyTyp = Pt; +} +//------------------------------------------------------------------------------ + +TEdge* RemoveEdge(TEdge* e) +{ + //removes e from double_linked_list (but without removing from memory) + e->Prev->Next = e->Next; + e->Next->Prev = e->Prev; + TEdge* result = e->Next; + e->Prev = 0; //flag as removed (see ClipperBase.Clear) + return result; +} +//------------------------------------------------------------------------------ + +inline void ReverseHorizontal(TEdge &e) +{ + //swap horizontal edges' Top and Bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + cInt tmp = e.Top.X; + e.Top.X = e.Bot.X; + e.Bot.X = tmp; +#ifdef use_xyz + tmp = e.Top.Z; + e.Top.Z = e.Bot.Z; + e.Bot.Z = tmp; +#endif +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) +{ + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) +{ + //precondition: segments are Collinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) +{ + OutPt *p = btmPt1->Prev; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; + double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + p = btmPt1->Next; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; + double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + + p = btmPt2->Prev; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; + double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + p = btmPt2->Next; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; + double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt* GetBottomPt(OutPt *pp) +{ + OutPt* dups = 0; + OutPt* p = pp->Next; + while (p != pp) + { + if (p->Pt.Y > pp->Pt.Y) + { + pp = p; + dups = 0; + } + else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) + { + if (p->Pt.X < pp->Pt.X) + { + dups = 0; + pp = p; + } else + { + if (p->Next != pp && p->Prev != pp) dups = p; + } + } + p = p->Next; + } + if (dups) + { + //there appears to be at least 2 vertices at BottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups->Next; + while (dups->Pt != pp->Pt) dups = dups->Next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool FindSegment(OutPt* &pp, bool UseFullInt64Range, + IntPoint &pt1, IntPoint &pt2) +{ + //OutPt1 & OutPt2 => the overlap segment (if the function returns true) + if (!pp) return false; + OutPt* pp2 = pp; + IntPoint pt1a = pt1, pt2a = pt2; + do + { + if (SlopesEqual(pt1a, pt2a, pp->Pt, pp->Prev->Pt, UseFullInt64Range) && + SlopesEqual(pt1a, pt2a, pp->Pt, UseFullInt64Range) && + GetOverlapSegment(pt1a, pt2a, pp->Pt, pp->Prev->Pt, pt1, pt2)) + return true; + pp = pp->Next; + } + while (pp != pp2); + return false; +} +//------------------------------------------------------------------------------ + +bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, + const IntPoint pt2, const IntPoint pt3) +{ + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) + return false; + else if (pt1.X != pt3.X) + return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else + return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); +} +//------------------------------------------------------------------------------ + +OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint Pt) +{ + if (p1 == p2) throw "JoinError"; + OutPt* result = new OutPt; + result->Pt = Pt; + if (p2 == p1->Next) + { + p1->Next = result; + p2->Prev = result; + result->Next = p2; + result->Prev = p1; + } else + { + p2->Next = result; + p1->Prev = result; + result->Next = p1; + result->Prev = p2; + } + return result; +} +//------------------------------------------------------------------------------ + +bool HorzSegmentsOverlap(const IntPoint& pt1a, const IntPoint& pt1b, + const IntPoint& pt2a, const IntPoint& pt2b) +{ + //precondition: both segments are horizontal + if ((pt1a.X > pt2a.X) == (pt1a.X < pt2b.X)) return true; + else if ((pt1b.X > pt2a.X) == (pt1b.X < pt2b.X)) return true; + else if ((pt2a.X > pt1a.X) == (pt2a.X < pt1b.X)) return true; + else if ((pt2b.X > pt1a.X) == (pt2b.X < pt1b.X)) return true; + else if ((pt1a.X == pt2a.X) && (pt1b.X == pt2b.X)) return true; + else if ((pt1a.X == pt2b.X) && (pt1b.X == pt2a.X)) return true; + else return false; +} + + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() //constructor +{ + m_MinimaList = 0; + m_CurrentLM = 0; + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void RangeTest(const IntPoint& Pt, bool& useFullRange) +{ + if (useFullRange) + { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw "Coordinate outside allowed range"; + } + else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + { + useFullRange = true; + RangeTest(Pt, 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 + if (!Closed && PolyTyp == ptClip) + throw clipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw clipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.size() -1; + 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]; + + bool IsFlat = true; + //1. Basic (first) edge initialization ... + try + { + edges[1].Curr = pg[1]; + RangeTest(pg[0], m_UseFullRange); + RangeTest(pg[highI], m_UseFullRange); + InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); + InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) + { + RangeTest(pg[i], m_UseFullRange); + InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); + } + } + catch(...) + { + delete [] edges; + throw; //range test fails + } + TEdge *eStart = &edges[0]; + + //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; + continue; + } + if (E->Prev == E->Next) + break; //only two vertices + 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))) + { + //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; + } + + if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) + { + delete [] edges; + return false; + } + + if (!Closed) + { + m_HasOpenPaths = true; + eStart->Prev->OutIdx = Skip; + } + + //3. Do second stage of edge initialization ... + E = eStart; + do + { + InitEdge2(*E, PolyTyp); + E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; + } + while (E != eStart); + + //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 (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; + } + + m_edges.push_back(edges); + bool clockwise; + TEdge* EMin = 0; + for (;;) + { + 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) + { + 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; + } + return true; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) +{ + bool result = false; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, Closed)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertLocalMinima(LocalMinima *newLm) +{ + if( ! m_MinimaList ) + { + m_MinimaList = newLm; + } + else if( newLm->Y >= m_MinimaList->Y ) + { + newLm->Next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima* tmpLm = m_MinimaList; + while( tmpLm->Next && ( newLm->Y < tmpLm->Next->Y ) ) + tmpLm = tmpLm->Next; + newLm->Next = tmpLm->Next; + tmpLm->Next = newLm; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() +{ + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) + { + //for each edge array in turn, find the first used edge and + //check for and remove any hiddenPts in each edge in the array. + TEdge* edges = m_edges[i]; + delete [] edges; + } + m_edges.clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() +{ + m_CurrentLM = m_MinimaList; + if( !m_CurrentLM ) return; //ie nothing to process + + //reset all edges ... + LocalMinima* lm = m_MinimaList; + while( lm ) + { + TEdge* e = lm->LeftBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esLeft; + e->OutIdx = Unassigned; + } + + e = lm->RightBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; + } + lm = lm->Next; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() +{ + while( m_MinimaList ) + { + LocalMinima* tmpLm = m_MinimaList->Next; + delete m_MinimaList; + m_MinimaList = tmpLm; + } + m_CurrentLM = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::PopLocalMinima() +{ + if( ! m_CurrentLM ) return; + m_CurrentLM = m_CurrentLM->Next; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() +{ + IntRect result; + LocalMinima* lm = m_MinimaList; + if (!lm) + { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->LeftBound->Bot.X; + result.top = lm->LeftBound->Bot.Y; + result.right = lm->LeftBound->Bot.X; + result.bottom = lm->LeftBound->Bot.Y; + while (lm) + { + if (lm->LeftBound->Bot.Y > result.bottom) + result.bottom = lm->LeftBound->Bot.Y; + TEdge* e = lm->LeftBound; + for (;;) { + TEdge* bottomE = e; + while (e->NextInLML) + { + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + e = e->NextInLML; + } + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + if (e->Top.X < result.left) result.left = e->Top.X; + if (e->Top.X > result.right) result.right = e->Top.X; + if (e->Top.Y < result.top) result.top = e->Top.Y; + + if (bottomE == lm->LeftBound) e = lm->RightBound; + else break; + } + lm = lm->Next; + } + return result; +} + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper(int initOptions) : ClipperBase() //constructor +{ + m_ActiveEdges = 0; + m_SortedEdges = 0; + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); + m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); + m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); + m_HasOpenPaths = false; +#ifdef use_xyz + m_ZFill = 0; +#endif +} +//------------------------------------------------------------------------------ + +Clipper::~Clipper() //destructor +{ + Clear(); + m_Scanbeam.clear(); +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::ZFillFunction(TZFillCallback zFillFunc) +{ + m_ZFill = zFillFunc; +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::Reset() +{ + ClipperBase::Reset(); + m_Scanbeam.clear(); + m_ActiveEdges = 0; + m_SortedEdges = 0; + LocalMinima* lm = m_MinimaList; + while (lm) + { + InsertScanbeam(lm->Y); + lm = lm->Next; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + if (m_HasOpenPaths) + throw clipperException("Error: PolyTree struct is need for open path clipping."); + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.IsHole != outrec.FirstLeft->IsHole && + outrec.FirstLeft->Pts)) return; + + OutRec* orfl = outrec.FirstLeft; + while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded = true; + try { + Reset(); + if (!m_CurrentLM) return false; + cInt botY = PopScanbeam(); + do { + InsertLocalMinimaIntoAEL(botY); + ClearGhostJoins(); + ProcessHorizontals(false); + if (m_Scanbeam.empty()) break; + cInt topY = PopScanbeam(); + succeeded = ProcessIntersections(botY, topY); + if (!succeeded) break; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while (!m_Scanbeam.empty() || m_CurrentLM); + } + catch(...) + { + succeeded = false; + } + + if (succeeded) + { + //fix orientations ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts || outRec->IsOpen) continue; + if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) + ReversePolyPtLinks(outRec->Pts); + } + + if (!m_Joins.empty()) JoinCommonEdges(); + + //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (outRec->Pts && !outRec->IsOpen) + FixupOutPolygon(*outRec); + } + + if (m_StrictSimple) DoSimplePolygons(); + } + + ClearJoins(); + ClearGhostJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertScanbeam(const cInt Y) +{ + m_Scanbeam.insert(Y); +} +//------------------------------------------------------------------------------ + +cInt Clipper::PopScanbeam() +{ + cInt Y = *m_Scanbeam.begin(); + m_Scanbeam.erase(m_Scanbeam.begin()); + return Y; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeAllOutRecs(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->Pts) DisposeOutPts(outRec->Pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) +{ + TEdge *e = edge.PrevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; + if (!e) + { + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + edge.WindCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc WindCnt2 + } + else if (edge.WindDelta == 0 && m_ClipType != ctUnion) + { + edge.WindCnt = 1; + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else if (IsEvenOddFillType(edge)) + { + //EvenOdd filling ... + if (edge.WindDelta == 0) + { + //are we inside a subj polygon ... + bool Inside = true; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) + Inside = !Inside; + e2 = e2->PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } + else + { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else + { + //nonZero, Positive or Negative filling ... + if (e->WindCnt * e->WindDelta < 0) + { + //prev edge is 'decreasing' WindCount (WC) toward zero + //so we're outside the previous polygon ... + if (Abs(e->WindCnt) > 1) + { + //outside prev poly but still inside another. + //when reversing direction of prev poly use the same WC + if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise continue to 'decrease' WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + else + //now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } else + { + //prev edge is 'increasing' WindCount (WC) away from zero + //so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); + //if wind direction is reversing prev then use same WC + else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise add to WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + + //update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //EvenOdd filling ... + while (e != &edge) + { + if (e->WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e->NextInAEL; + } + } else + { + //nonZero, Positive or Negative filling ... + while ( e != &edge ) + { + edge.WindCnt2 += e->WindDelta; + e = e->NextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_SubjFillType == pftEvenOdd; else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_ClipFillType == pftEvenOdd; else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge& edge) const +{ + PolyFillType pft, pft2; + if (edge.PolyTyp == ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch(pft) + { + case pftEvenOdd: + //return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; + break; + case pftNonZero: + if (Abs(edge.WindCnt) != 1) return false; + break; + case pftPositive: + if (edge.WindCnt != 1) return false; + break; + default: //pftNegative + if (edge.WindCnt != -1) return false; + } + + switch(m_ClipType) + { + case ctIntersection: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctUnion: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + break; + case ctDifference: + if (edge.PolyTyp == ptSubject) + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctXor: + if (edge.WindDelta == 0) //XOr always contributing unless open + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + break; + default: + return true; + } +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + OutPt* result; + TEdge *e, *prevE; + if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) + { + result = AddOutPt(e1, Pt); + e2->OutIdx = e1->OutIdx; + e1->Side = esLeft; + e2->Side = esRight; + e = e1; + if (e->PrevInAEL == e2) + prevE = e2->PrevInAEL; + else + prevE = e->PrevInAEL; + } else + { + result = AddOutPt(e2, Pt); + e1->OutIdx = e2->OutIdx; + e1->Side = esRight; + e2->Side = esLeft; + e = e2; + if (e->PrevInAEL == e1) + prevE = e1->PrevInAEL; + else + prevE = e->PrevInAEL; + } + + if (prevE && prevE->OutIdx >= 0 && + (TopX(*prevE, Pt.Y) == TopX(*e, Pt.Y)) && + SlopesEqual(*e, *prevE, m_UseFullRange) && + (e->WindDelta != 0) && (prevE->WindDelta != 0)) + { + OutPt* outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } + return result; +} +//------------------------------------------------------------------------------ + +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; + e2->OutIdx = Unassigned; + } + else if (e1->OutIdx < e2->OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) +{ + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if( !m_SortedEdges ) + { + m_SortedEdges = edge; + edge->PrevInSEL = 0; + edge->NextInSEL = 0; + } + else + { + edge->NextInSEL = m_SortedEdges; + edge->PrevInSEL = 0; + m_SortedEdges->PrevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() +{ + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while ( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) +{ + Join* j = new Join; + j->OutPt1 = op1; + j->OutPt2 = op2; + j->OffPt = OffPt; + m_Joins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearGhostJoins() +{ + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) + delete m_GhostJoins[i]; + m_GhostJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) +{ + Join* j = new Join; + j->OutPt1 = op; + j->OutPt2 = 0; + j->OffPt = OffPt; + m_GhostJoins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) +{ + while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) + { + TEdge* lb = m_CurrentLM->LeftBound; + TEdge* rb = m_CurrentLM->RightBound; + PopLocalMinima(); + OutPt *Op1 = 0; + if (!lb) + { + //nb: don't insert LB into either AEL or SEL + InsertEdgeIntoAEL(rb, 0); + SetWindingCount(*rb); + 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); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount( *lb ); + rb->WindCnt = lb->WindCnt; + rb->WindCnt2 = lb->WindCnt2; + if (IsContributing(*lb)) + Op1 = AddLocalMinPoly(lb, rb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + + if (rb) + { + if(IsHorizontal(*rb)) AddEdgeToSEL(rb); + else InsertScanbeam( rb->Top.Y ); + } + + if (!lb || !rb) continue; + + //if any output polygons share an edge, they'll need joining later ... + if (Op1 && IsHorizontal(*rb) && + m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) + { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) + { + Join* jr = m_GhostJoins[i]; + //if the horizontal Rb and a 'ghost' horizontal overlap, then convert + //the 'ghost' join to a real join ready for later ... + if (HorzSegmentsOverlap(jr->OutPt1->Pt, jr->OffPt, rb->Bot, rb->Top)) + AddJoin(jr->OutPt1, Op1, jr->OffPt); + } + } + + if (lb->OutIdx >= 0 && lb->PrevInAEL && + lb->PrevInAEL->Curr.X == lb->Bot.X && + lb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) && + (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); + AddJoin(Op1, Op2, lb->Top); + } + + if(lb->NextInAEL != rb) + { + + if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(*rb->PrevInAEL, *rb, m_UseFullRange) && + (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); + AddJoin(Op1, Op2, rb->Top); + } + + TEdge* e = lb->NextInAEL; + if (e) + { + while( e != rb ) + { + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the Right of param2 ABOVE the intersection ... + IntersectEdges(rb , e , lb->Curr); //order important here + e = e->NextInAEL; + } + } + } + + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted + if( AelPrev ) AelPrev->NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if( AelNext ) AelNext->PrevInAEL = AelPrev; + e->NextInAEL = 0; + e->PrevInAEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) +{ + TEdge* SelPrev = e->PrevInSEL; + TEdge* SelNext = e->NextInSEL; + if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted + if( SelPrev ) SelPrev->NextInSEL = SelNext; + else m_SortedEdges = SelNext; + if( SelNext ) SelNext->PrevInSEL = SelPrev; + e->NextInSEL = 0; + e->PrevInSEL = 0; +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz + +void Clipper::SetZ(IntPoint& pt, TEdge& e) +{ + pt.Z = 0; + if (m_ZFill) + { + //put the 'preferred' point as first parameter ... + if (e.OutIdx < 0) + (*m_ZFill)(e.Bot, e.Top, pt); //outside a path so presume entering + else + (*m_ZFill)(e.Top, e.Bot, pt); //inside a path so presume exiting + } +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &Pt, bool protect) +{ + //e1 will be to the Left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + bool e1stops = !protect && !e1->NextInLML && + e1->Top.X == Pt.X && e1->Top.Y == Pt.Y; + bool e2stops = !protect && !e2->NextInLML && + e2->Top.X == Pt.X && e2->Top.Y == Pt.Y; + bool e1Contributing = ( e1->OutIdx >= 0 ); + bool e2Contributing = ( e2->OutIdx >= 0 ); + +#ifdef use_lines + //if either edge is on an OPEN path ... + if (e1->WindDelta == 0 || e2->WindDelta == 0) + { + //ignore subject-subject open path intersections UNLESS they + //are both open paths, AND they are both 'contributing maximas' ... + if (e1->WindDelta == 0 && e2->WindDelta == 0) + { + if ((e1stops || e2stops) && e1Contributing && e2Contributing) + AddLocalMaxPoly(e1, e2, Pt); + } + + //if intersecting a subj line with a subj poly ... + else if (e1->PolyTyp == e2->PolyTyp && + e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) + { + if (e1->WindDelta == 0) + { + if (e2Contributing) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + } + else + { + if (e1Contributing) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + } + else if (e1->PolyTyp != e2->PolyTyp) + { + //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... + if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && + (m_ClipType != ctUnion || e2->WindCnt2 == 0)) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && + (m_ClipType != ctUnion || e1->WindCnt2 == 0)) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + + if (e1stops) + if (e1->OutIdx < 0) DeleteFromAEL(e1); + else throw clipperException("Error intersecting polylines"); + if (e2stops) + if (e2->OutIdx < 0) DeleteFromAEL(e2); + else throw clipperException("Error intersecting polylines"); + return; + } +#endif + + //update winding counts... + //assumes that e1 will be to the Right of e2 ABOVE the intersection + if ( e1->PolyTyp == e2->PolyTyp ) + { + if ( IsEvenOddFillType( *e1) ) + { + int oldE1WindCnt = e1->WindCnt; + e1->WindCnt = e2->WindCnt; + e2->WindCnt = oldE1WindCnt; + } else + { + if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; + else e1->WindCnt += e2->WindDelta; + if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; + else e2->WindCnt -= e1->WindDelta; + } + } else + { + if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; + else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; + else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->PolyTyp == ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->PolyTyp == ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + cInt e1Wc, e2Wc; + switch (e1FillType) + { + case pftPositive: e1Wc = e1->WindCnt; break; + case pftNegative: e1Wc = -e1->WindCnt; break; + default: e1Wc = Abs(e1->WindCnt); + } + switch(e2FillType) + { + case pftPositive: e2Wc = e2->WindCnt; break; + case pftNegative: e2Wc = -e2->WindCnt; break; + default: e2Wc = Abs(e2->WindCnt); + } + + if ( e1Contributing && e2Contributing ) + { + if ( e1stops || e2stops || + (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) + AddLocalMaxPoly(e1, e2, Pt); + else + { + AddOutPt(e1, Pt); + AddOutPt(e2, Pt); + SwapSides( *e1 , *e2 ); + SwapPolyIndexes( *e1 , *e2 ); + } + } + else if ( e1Contributing ) + { + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( e2Contributing ) + { + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( (e1Wc == 0 || e1Wc == 1) && + (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + { + //neither edge is currently contributing ... + + cInt e1Wc2, e2Wc2; + switch (e1FillType2) + { + case pftPositive: e1Wc2 = e1->WindCnt2; break; + case pftNegative : e1Wc2 = -e1->WindCnt2; break; + default: e1Wc2 = Abs(e1->WindCnt2); + } + switch (e2FillType2) + { + case pftPositive: e2Wc2 = e2->WindCnt2; break; + case pftNegative: e2Wc2 = -e2->WindCnt2; break; + default: e2Wc2 = Abs(e2->WindCnt2); + } + + if (e1->PolyTyp != e2->PolyTyp) + AddLocalMinPoly(e1, e2, Pt); + else if (e1Wc == 1 && e2Wc == 1) + switch( m_ClipType ) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctUnion: + if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctDifference: + if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, Pt); + } + else + SwapSides( *e1, *e2 ); + } + + if( (e1stops != e2stops) && + ( (e1stops && (e1->OutIdx >= 0)) || (e2stops && (e2->OutIdx >= 0)) ) ) + { + SwapSides( *e1, *e2 ); + SwapPolyIndexes( *e1, *e2 ); + } + + //finally, delete any non-contributing maxima edges ... + if( e1stops ) DeleteFromAEL( e1 ); + if( e2stops ) DeleteFromAEL( e2 ); +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) +{ + bool IsHole = false; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->OutIdx >= 0 && e2->WindDelta != 0) + { + IsHole = !IsHole; + if (! outrec->FirstLeft) + outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; + } + e2 = e2->PrevInAEL; + } + if (IsHole) outrec->IsHole = true; +} +//------------------------------------------------------------------------------ + +OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) +{ + //work out which polygon fragment has the correct hole state ... + if (!outRec1->BottomPt) + outRec1->BottomPt = GetBottomPt(outRec1->Pts); + if (!outRec2->BottomPt) + outRec2->BottomPt = GetBottomPt(outRec2->Pts); + OutPt *OutPt1 = outRec1->BottomPt; + OutPt *OutPt2 = outRec2->BottomPt; + if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; + else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; + else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; + else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; + else if (OutPt1->Next == OutPt1) return outRec2; + else if (OutPt2->Next == OutPt2) return outRec1; + else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; + else return outRec2; +} +//------------------------------------------------------------------------------ + +bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +{ + do + { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::GetOutRec(int Idx) +{ + OutRec* outrec = m_PolyOuts[Idx]; + while (outrec != m_PolyOuts[outrec->Idx]) + outrec = m_PolyOuts[outrec->Idx]; + return outrec; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) +{ + //get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + + OutRec *holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + //get the start and ends of both output polygons and + //join e2 poly onto e1 poly and delete pointers to e2 ... + + OutPt* p1_lft = outRec1->Pts; + OutPt* p1_rt = p1_lft->Prev; + OutPt* p2_lft = outRec2->Pts; + OutPt* p2_rt = p2_lft->Prev; + + EdgeSide Side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->Side == esLeft ) + { + if( e2->Side == esLeft ) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + outRec1->Pts = p2_rt; + } else + { + //x y z a b c + p2_rt->Next = p1_lft; + p1_lft->Prev = p2_rt; + p2_lft->Prev = p1_rt; + p1_rt->Next = p2_lft; + outRec1->Pts = p2_lft; + } + Side = esLeft; + } else + { + if( e2->Side == esRight ) + { + //a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + } else + { + //a b c x y z + p1_rt->Next = p2_lft; + p2_lft->Prev = p1_rt; + p1_lft->Prev = p2_rt; + p2_rt->Next = p1_lft; + } + Side = esRight; + } + + outRec1->BottomPt = 0; + if (holeStateRec == outRec2) + { + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->IsHole = outRec2->IsHole; + } + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->OutIdx; + int ObsoleteIdx = e2->OutIdx; + + e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly + e2->OutIdx = Unassigned; + + TEdge* e = m_ActiveEdges; + while( e ) + { + if( e->OutIdx == ObsoleteIdx ) + { + e->OutIdx = OKIdx; + e->Side = Side; + break; + } + e = e->NextInAEL; + } + + outRec2->Idx = outRec1->Idx; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::CreateOutRec() +{ + OutRec* result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = 0; + result->Pts = 0; + result->BottomPt = 0; + result->PolyNd = 0; + m_PolyOuts.push_back(result); + result->Idx = (int)m_PolyOuts.size()-1; + return result; +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) +{ + bool ToFront = (e->Side == esLeft); + if( e->OutIdx < 0 ) + { + OutRec *outRec = CreateOutRec(); + outRec->IsOpen = (e->WindDelta == 0); + OutPt* newOp = new OutPt; + outRec->Pts = newOp; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = newOp; + newOp->Prev = newOp; + if (!outRec->IsOpen) + SetHoleState(e, outRec); +#ifdef use_xyz + if (pt == e->Bot) newOp->Pt = e->Bot; + else if (pt == e->Top) newOp->Pt = e->Top; + else SetZ(newOp->Pt, *e); +#endif + e->OutIdx = outRec->Idx; //nb: do this after SetZ ! + return newOp; + } else + { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt* op = outRec->Pts; + + if (ToFront && (pt == op->Pt)) return op; + else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; + + OutPt* newOp = new OutPt; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = op; + newOp->Prev = op->Prev; + newOp->Prev->Next = newOp; + op->Prev = newOp; + if (ToFront) outRec->Pts = newOp; +#ifdef use_xyz + if (pt == e->Bot) newOp->Pt = e->Bot; + else if (pt == e->Top) newOp->Pt = e->Top; + else SetZ(newOp->Pt, *e); +#endif + return newOp; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals(bool IsTopOfScanbeam) +{ + TEdge* horzEdge = m_SortedEdges; + while(horzEdge) + { + DeleteFromSEL(horzEdge); + ProcessHorizontal(horzEdge, IsTopOfScanbeam); + horzEdge = m_SortedEdges; + } +} +//------------------------------------------------------------------------------ + +inline bool IsMinima(TEdge *e) +{ + return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); +} +//------------------------------------------------------------------------------ + +inline bool IsMaxima(TEdge *e, const cInt Y) +{ + return e && e->Top.Y == Y && !e->NextInLML; +} +//------------------------------------------------------------------------------ + +inline bool IsIntermediate(TEdge *e, const cInt Y) +{ + return e->Top.Y == Y && e->NextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) +{ + TEdge* result = 0; + if ((e->Next->Top == e->Top) && !e->Next->NextInLML) + result = e->Next; + else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) + result = e->Prev; + + if (result && (result->OutIdx == Skip || + //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ... + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) + return 0; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) +{ + //check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) return; + + if( Edge1->NextInAEL == Edge2 ) + { + TEdge* Next = Edge2->NextInAEL; + if( Next ) Next->PrevInAEL = Edge1; + TEdge* Prev = Edge1->PrevInAEL; + if( Prev ) Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } + else if( Edge2->NextInAEL == Edge1 ) + { + TEdge* Next = Edge1->NextInAEL; + if( Next ) Next->PrevInAEL = Edge2; + TEdge* Prev = Edge2->PrevInAEL; + if( Prev ) Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } + else + { + TEdge* Next = Edge1->NextInAEL; + TEdge* Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if( Edge1->NextInAEL ) Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if( Edge1->PrevInAEL ) Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if( Edge2->NextInAEL ) Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if( Edge2->PrevInAEL ) Edge2->PrevInAEL->NextInAEL = Edge2; + } + + if( !Edge1->PrevInAEL ) m_ActiveEdges = Edge1; + else if( !Edge2->PrevInAEL ) m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) +{ + if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; + if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; + + if( Edge1->NextInSEL == Edge2 ) + { + TEdge* Next = Edge2->NextInSEL; + if( Next ) Next->PrevInSEL = Edge1; + TEdge* Prev = Edge1->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge2; + Edge2->PrevInSEL = Prev; + Edge2->NextInSEL = Edge1; + Edge1->PrevInSEL = Edge2; + Edge1->NextInSEL = Next; + } + else if( Edge2->NextInSEL == Edge1 ) + { + TEdge* Next = Edge1->NextInSEL; + if( Next ) Next->PrevInSEL = Edge2; + TEdge* Prev = Edge2->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge1; + Edge1->PrevInSEL = Prev; + Edge1->NextInSEL = Edge2; + Edge2->PrevInSEL = Edge1; + Edge2->NextInSEL = Next; + } + else + { + TEdge* Next = Edge1->NextInSEL; + TEdge* Prev = Edge1->PrevInSEL; + Edge1->NextInSEL = Edge2->NextInSEL; + if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; + Edge1->PrevInSEL = Edge2->PrevInSEL; + if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; + Edge2->NextInSEL = Next; + if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; + Edge2->PrevInSEL = Prev; + if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; + } + + if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; + else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; +} +//------------------------------------------------------------------------------ + +TEdge* GetNextInAEL(TEdge *e, Direction dir) +{ + return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; +} +//------------------------------------------------------------------------------ + +void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) +{ + if (HorzEdge.Bot.X < HorzEdge.Top.X) + { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = dLeftToRight; + } else + { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = dRightToLeft; + } +} +//------------------------------------------------------------------------ + +void Clipper::PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam) +{ + //get the last Op for this horizontal edge + //the point may be anywhere along the horizontal ... + OutPt* outPt = m_PolyOuts[horzEdge->OutIdx]->Pts; + if (horzEdge->Side != esLeft) outPt = outPt->Prev; + + //First, match up overlapping horizontal edges (eg when one polygon's + //intermediate horz edge overlaps an intermediate horz edge of another, or + //when one polygon sits on top of another) ... + //for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) + //{ + // Join* j = m_GhostJoins[i]; + // if (HorzSegmentsOverlap(j->OutPt1->Pt, j->OffPt, horzEdge->Bot, horzEdge->Top)) + // AddJoin(j->OutPt1, outPt, j->OffPt); + //} + + //Also, since horizontal edges at the top of one SB are often removed from + //the AEL before we process the horizontal edges at the bottom of the next, + //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); + } +} +//------------------------------------------------------------------------------ + +/******************************************************************************* +* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * +* Bottom of a scanbeam) are processed as if layered. The order in which HEs * +* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * +* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * +* and with other non-horizontal edges [*]. Once these intersections are * +* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * +* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * +*******************************************************************************/ + +void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) +{ + Direction dir; + cInt horzLeft, horzRight; + + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + TEdge* eLastHorz = horzEdge, *eMaxPair = 0; + while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) + eLastHorz = eLastHorz->NextInLML; + if (!eLastHorz->NextInLML) + eMaxPair = GetMaximaPair(eLastHorz); + + for (;;) + { + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge* e = GetNextInAEL(horzEdge, dir); + while(e) + { + //Break if we've got to the end of an intermediate horizontal edge ... + //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + e->Dx < horzEdge->NextInLML->Dx) break; + + TEdge* eNext = GetNextInAEL(e, dir); //saves eNext for later + + 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 (dir == dLeftToRight) + IntersectEdges(horzEdge, e, e->Top); + else + IntersectEdges(e, horzEdge, e->Top); + if (eMaxPair->OutIdx >= 0) throw clipperException("ProcessHorizontal error"); + return; + } + else if(dir == dLeftToRight) + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(horzEdge, e, Pt, true); + } + else + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges( e, horzEdge, Pt, true); + } + SwapPositionsInAEL( horzEdge, e ); + } + else if( (dir == dLeftToRight && e->Curr.X >= horzRight) || + (dir == dRightToLeft && e->Curr.X <= horzLeft) ) break; + e = eNext; + } //end while + + if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); + + if (horzEdge->NextInLML && IsHorizontal(*horzEdge->NextInLML)) + { + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + } else + break; + } //end for (;;) + + if(horzEdge->NextInLML) + { + if(horzEdge->OutIdx >= 0) + { + OutPt* op1 = AddOutPt( horzEdge, horzEdge->Top); + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->WindDelta == 0) return; + //nb: HorzEdge is no longer horizontal here + TEdge* ePrev = horzEdge->PrevInAEL; + TEdge* eNext = horzEdge->NextInAEL; + if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && + ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) + { + OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + else if (eNext && eNext->Curr.X == horzEdge->Bot.X && + eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) + { + OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + } + else + UpdateEdgeIntoAEL(horzEdge); + } + else if (eMaxPair) + { + if (eMaxPair->OutIdx >= 0) + { + if (dir == dLeftToRight) + IntersectEdges(horzEdge, eMaxPair, horzEdge->Top); + else + IntersectEdges(eMaxPair, horzEdge, horzEdge->Top); + if (eMaxPair->OutIdx >= 0) + throw clipperException("ProcessHorizontal error"); + } else + { + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + } + } else + { + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +void Clipper::UpdateEdgeIntoAEL(TEdge *&e) +{ + if( !e->NextInLML ) throw + clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (AelPrev) AelPrev->NextInAEL = e->NextInLML; + else m_ActiveEdges = e->NextInLML; + if (AelNext) AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const cInt botY, const cInt topY) +{ + if( !m_ActiveEdges ) return true; + try { + BuildIntersectList(botY, topY); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) return true; + if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); + else return false; + } + catch(...) + { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + m_SortedEdges = 0; + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() +{ + for (size_t i = 0; i < m_IntersectList.size(); ++i ) + delete m_IntersectList[i]; + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const cInt botY, const cInt topY) +{ + if ( !m_ActiveEdges ) return; + + //prepare for sorting ... + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e->Curr.X = TopX( *e, topY ); + e = e->NextInAEL; + } + + //bubblesort ... + bool isModified; + do + { + isModified = false; + e = m_SortedEdges; + while( e->NextInSEL ) + { + TEdge *eNext = e->NextInSEL; + IntPoint Pt; + if(e->Curr.X > eNext->Curr.X) + { + if (!IntersectPoint(*e, *eNext, Pt, m_UseFullRange) && e->Curr.X > eNext->Curr.X +1) + throw clipperException("Intersection error"); + if (Pt.Y > botY) + { + Pt.Y = botY; + if (std::fabs(e->Dx) > std::fabs(eNext->Dx)) + Pt.X = TopX(*eNext, botY); else + Pt.X = TopX(*e, botY); + } + + IntersectNode * newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.push_back(newNode); + + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; + else break; + } + while ( isModified ); + m_SortedEdges = 0; //important +} +//------------------------------------------------------------------------------ + + +void Clipper::ProcessIntersectList() +{ + for (size_t i = 0; i < m_IntersectList.size(); ++i) + { + IntersectNode* iNode = m_IntersectList[i]; + { + IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt, true); + SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); + } + 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; +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e) +{ + TEdge* eMaxPair = GetMaximaPair(e); + if (!eMaxPair) + { + if (e->OutIdx >= 0) + AddOutPt(e, e->Top); + DeleteFromAEL(e); + return; + } + + TEdge* eNext = e->NextInAEL; + while(eNext && eNext != eMaxPair) + { + IntersectEdges(e, eNext, e->Top, true); + SwapPositionsInAEL(e, eNext); + eNext = e->NextInAEL; + } + + if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) + { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } + else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) + { + IntersectEdges( e, eMaxPair, e->Top); + } +#ifdef use_lines + else if (e->WindDelta == 0) + { + if (e->OutIdx >= 0) + { + AddOutPt(e, e->Top); + e->OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair->OutIdx >= 0) + { + AddOutPt(eMaxPair, e->Top); + eMaxPair->OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) +{ + TEdge* e = m_ActiveEdges; + while( e ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if(IsMaximaEdge) + { + TEdge* eMaxPair = GetMaximaPair(e); + IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); + } + + if(IsMaximaEdge) + { + TEdge* ePrev = e->PrevInAEL; + DoMaxima(e); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->NextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) + { + UpdateEdgeIntoAEL(e); + if (e->OutIdx >= 0) + AddOutPt(e, e->Bot); + AddEdgeToSEL(e); + } + else + { + e->Curr.X = TopX( *e, topY ); + e->Curr.Y = topY; + } + + if (m_StrictSimple) + { + TEdge* ePrev = e->PrevInAEL; + if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && + (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) + { + OutPt* op = AddOutPt(ePrev, e->Curr); + OutPt* op2 = AddOutPt(e, e->Curr); + AddJoin(op, op2, e->Curr); //StrictlySimple (type-3) join + } + } + + e = e->NextInAEL; + } + } + + //3. Process horizontals at the Top of the scanbeam ... + ProcessHorizontals(true); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while(e) + { + if(IsIntermediate(e, topY)) + { + OutPt* op = 0; + if( e->OutIdx >= 0 ) + op = AddOutPt(e, e->Top); + UpdateEdgeIntoAEL(e); + + //if output polygons share an edge, they'll need joining later ... + TEdge* ePrev = e->PrevInAEL; + TEdge* eNext = e->NextInAEL; + if (ePrev && ePrev->Curr.X == e->Bot.X && + ePrev->Curr.Y == e->Bot.Y && op && + ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*e, *ePrev, m_UseFullRange) && + (e->WindDelta != 0) && (ePrev->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(ePrev, e->Bot); + AddJoin(op, op2, e->Top); + } + else if (eNext && eNext->Curr.X == e->Bot.X && + eNext->Curr.Y == e->Bot.Y && op && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*e, *eNext, m_UseFullRange) && + (e->WindDelta != 0) && (eNext->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(eNext, e->Bot); + AddJoin(op, op2, e->Top); + } + } + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outrec) +{ + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outrec.BottomPt = 0; + OutPt *pp = outrec.Pts; + + for (;;) + { + if (pp->Prev == pp || pp->Prev == pp->Next ) + { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } + + //test for duplicate points and collinear edges ... + if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || + (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) + { + lastOK = 0; + OutPt *tmp = pp; + pp->Prev->Next = pp->Next; + pp->Next->Prev = pp->Prev; + pp = pp->Prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->Next; + } + } + outrec.Pts = pp; +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *Pts) +{ + if (!Pts) return 0; + int result = 0; + OutPt* p = Pts; + do + { + result++; + p = p->Next; + } + while (p != Pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Paths &polys) +{ + polys.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + if (!m_PolyOuts[i]->Pts) continue; + Path pg; + OutPt* p = m_PolyOuts[i]->Pts->Prev; + int cnt = PointCount(p); + if (cnt < 2) continue; + pg.reserve(cnt); + for (int i = 0; i < cnt; ++i) + { + pg.push_back(p->Pt); + p = p->Prev; + } + polys.push_back(pg); + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->Pts); + if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->PolyNd = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->Pts->Prev; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->Pt); + op = op->Prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->PolyNd) continue; + if (outRec->IsOpen) + { + outRec->PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec->PolyNd); + } + else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) + outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else + polytree.AddChild(*outRec->PolyNd); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) +{ + //just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; //gets a copy of Int1 + int1.Edge1 = int2.Edge1; + int1.Edge2 = int2.Edge2; + int1.Pt = int2.Pt; + int2.Edge1 = inode.Edge1; + int2.Edge2 = inode.Edge2; + int2.Pt = inode.Pt; +} +//------------------------------------------------------------------------------ + +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) +{ + if (e2.Curr.X == e1.Curr.X) + { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else return e1.Top.X > TopX(e2, e1.Top.Y); + } + else return e2.Curr.X < e1.Curr.X; +} +//------------------------------------------------------------------------------ + +bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, + cInt& Left, cInt& Right) +{ + if (a1 < a2) + { + if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} + else {Left = std::max(a1,b2); Right = std::min(a2,b1);} + } + else + { + if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} + else {Left = std::max(a2,b2); Right = std::min(a1,b1);} + } + return Left < Right; +} +//------------------------------------------------------------------------------ + +inline void UpdateOutPtIdxs(OutRec& outrec) +{ + OutPt* op = outrec.Pts; + do + { + op->Idx = outrec.Idx; + op = op->Prev; + } + while(op != outrec.Pts); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) +{ + if(!m_ActiveEdges) + { + edge->PrevInAEL = 0; + edge->NextInAEL = 0; + m_ActiveEdges = edge; + } + else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) + { + edge->PrevInAEL = 0; + edge->NextInAEL = m_ActiveEdges; + m_ActiveEdges->PrevInAEL = edge; + m_ActiveEdges = edge; + } + else + { + if(!startEdge) startEdge = m_ActiveEdges; + while(startEdge->NextInAEL && + !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) + startEdge = startEdge->NextInAEL; + edge->NextInAEL = startEdge->NextInAEL; + if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; + edge->PrevInAEL = startEdge; + startEdge->NextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) +{ + OutPt* result = new OutPt; + result->Pt = outPt->Pt; + result->Idx = outPt->Idx; + if (InsertAfter) + { + result->Next = outPt->Next; + result->Prev = outPt; + outPt->Next->Prev = result; + outPt->Next = result; + } + else + { + result->Prev = outPt->Prev; + result->Next = outPt; + outPt->Prev->Next = result; + outPt->Prev = result; + } + return result; +} +//------------------------------------------------------------------------------ + +bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, + const IntPoint Pt, bool DiscardLeft) +{ + Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + if (Dir1 == Dir2) return false; + + //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + //So, to facilitate this while inserting Op1b and Op2b ... + //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == dLeftToRight) + { + while (op1->Next->Pt.X <= Pt.X && + op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } + else + { + while (op1->Next->Pt.X >= Pt.X && + op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == dLeftToRight) + { + while (op2->Next->Pt.X <= Pt.X && + op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else + { + while (op2->Next->Pt.X >= Pt.X && + op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == dLeftToRight) == DiscardLeft) + { + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + } + else + { + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + } + return true; +} +//------------------------------------------------------------------------------ + +bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) +{ + OutPt *op1 = j->OutPt1, *op1b; + OutPt *op2 = j->OutPt2, *op2b; + + //There are 3 kinds of joins for output polygons ... + //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere + //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). + //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + //location at the Bottom of the overlapping segment (& Join.OffPt is above). + //3. StrictSimple joins where edges touch but are not collinear and where + //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + + if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && + (j->OffPt == j->OutPt2->Pt)) + { + //Strictly Simple join ... + op1b = j->OutPt1->Next; + while (op1b != op1 && (op1b->Pt == j->OffPt)) + op1b = op1b->Next; + bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + op2b = j->OutPt2->Next; + while (op2b != op2 && (op2b->Pt == j->OffPt)) + op2b = op2b->Next; + bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + if (reverse1 == reverse2) return false; + if (reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } + else if (isHorizontal) + { + //treat horizontal joins differently to non-horizontal joins since with + //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + //may be anywhere along the horizontal edge. + op1b = op1; + while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) + op1 = op1->Prev; + while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) + op1b = op1b->Next; + if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' + + op2b = op2; + while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) + op2 = op2->Prev; + while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) + op2b = op2b->Next; + if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' + + cInt Left, Right; + //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + return false; + + //DiscardLeftSide: when overlapping edges are joined, a spike will created + //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + //on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1->Pt.X >= Left && op1->Pt.X <= Right) + { + Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + } + else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) + { + Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + } + else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) + { + Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + } + else + { + Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + } + j->OutPt1 = op1; j->OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else + { + //nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + //make sure the polygons are correctly oriented ... + op1b = op1->Next; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; + bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse1) + { + op1b = op1->Prev; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; + if ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; + }; + op2b = op2->Next; + while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; + bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse2) + { + op2b = op2->Prev; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; + if ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; + + if (Reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->Pts && outRec->FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) +{ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = 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* join = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); + + if (!outRec1->Pts || !outRec2->Pts) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + 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 = join->OutPt1; + outRec1->BottomPt = 0; + outRec2 = CreateOutRec(); + outRec2->Pts = join->OutPt2; + + //update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(*outRec2); + + //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; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) + ReversePolyPtLinks(outRec2->Pts); + + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) + { + //outRec1 is contained by outRec2 ... + outRec2->IsHole = outRec1->IsHole; + outRec1->IsHole = !outRec2->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) + ReversePolyPtLinks(outRec1->Pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->IsHole = outRec1->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + } + + } else + { + //joined 2 polygons together ... + + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->Idx = outRec1->Idx; + + outRec1->IsHole = holeStateRec->IsHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } +} + +//------------------------------------------------------------------------------ +// 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() +{ + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) + { + OutRec* outrec = m_PolyOuts[i++]; + OutPt* op = outrec->Pts; + if (!op) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt* op2 = op->Next; + while (op2 != outrec->Pts) + { + if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) + { + //split the polygon into two ... + OutPt* op3 = op->Prev; + OutPt* op4 = op2->Prev; + op->Prev = op4; + op4->Next = op; + op2->Prev = op3; + op3->Next = op2; + + outrec->Pts = op; + OutRec* outrec2 = CreateOutRec(); + outrec2->Pts = op2; + UpdateOutPtIdxs(*outrec2); + 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)) + { + //OutRec1 is contained by OutRec2 ... + outrec2->IsHole = outrec->IsHole; + outrec->IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + } else + { + //the 2 polygons are separate ... + outrec2->IsHole = outrec->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + } + op2 = op; //ie get ready for the Next iteration + } + op2 = op2->Next; + } + op = op->Next; + } + while (op != outrec->Pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePath(Path& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePaths(Paths& p) +{ + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePath(p[i]); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPath(in_poly, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPaths(in_polys, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Paths &polys, PolyFillType fillType) +{ + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) +{ + double Dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (Dx*Dx + dy*dy); +} +//------------------------------------------------------------------------------ + +double DistanceFromLineSqrd( + const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) +{ + //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) +{ + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) +{ + double Dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((Dx * Dx) + (dy * dy) <= 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). + + size_t size = in_poly.size(); + + if (size == 0) + { + out_poly.clear(); + return; + } + + 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; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Path& poly, double distance) +{ + CleanPolygon(poly, poly, distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) +{ + for (Paths::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Paths& polys, double distance) +{ + CleanPolygons(polys, polys, distance); +} +//------------------------------------------------------------------------------ + +void Minkowski(const Path& poly, const Path& path, + Paths& solution, bool isSum, bool isClosed) +{ + int delta = (isClosed ? 1 : 0); + size_t polyCnt = poly.size(); + size_t pathCnt = path.size(); + Paths pp; + pp.reserve(pathCnt); + if (isSum) + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + pp.push_back(p); + } + else + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + pp.push_back(p); + } + + Paths quads; + quads.reserve((pathCnt + delta) * (polyCnt + 1)); + for (size_t i = 0; i < pathCnt - 1 + delta; ++i) + for (size_t j = 0; j < polyCnt; ++j) + { + Path quad; + quad.reserve(4); + quad.push_back(pp[i % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) ReversePath(quad); + quads.push_back(quad); + } + + Clipper c; + c.AddPaths(quads, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) +{ + Minkowski(pattern, path, solution, true, pathIsClosed); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, + PolyFillType pathFillType, bool pathIsClosed) +{ + Clipper c; + for (size_t i = 0; i < paths.size(); ++i) + { + Paths tmp; + Minkowski(pattern, paths[i], tmp, true, pathIsClosed); + c.AddPaths(tmp, ptSubject, true); + } + if (pathIsClosed) c.AddPaths(paths, ptClip, true); + c.Execute(ctUnion, solution, pathFillType, pathFillType); +} +//------------------------------------------------------------------------------ + +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) +{ + Minkowski(poly1, poly2, solution, false, true); +} +//------------------------------------------------------------------------------ + +enum NodeType {ntAny, ntOpen, ntClosed}; + +void AddPolyNodeToPolygons(const PolyNode& polynode, NodeType nodetype, Paths& paths) +{ + bool match = true; + if (nodetype == ntClosed) match = !polynode.IsOpen(); + else if (nodetype == ntOpen) return; + + if (!polynode.Contour.empty() && match) + paths.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPolygons(*polynode.Childs[i], nodetype, paths); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, ntAny, paths); +} +//------------------------------------------------------------------------------ + +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, ntClosed, paths); +} +//------------------------------------------------------------------------------ + +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + //Open paths are top level only, so ... + for (int i = 0; i < polytree.ChildCount(); ++i) + if (polytree.Childs[i]->IsOpen()) + paths.push_back(polytree.Childs[i]->Contour); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const IntPoint &p) +{ + s << "(" << p.X << "," << p.Y << ")"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Path &p) +{ + if (p.empty()) return s; + Path::size_type last = p.size() -1; + for (Path::size_type i = 0; i < last; i++) + s << "(" << p[i].X << "," << p[i].Y << "), "; + s << "(" << p[last].X << "," << p[last].Y << ")\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Paths &p) +{ + for (Paths::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +#ifdef use_deprecated + +void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType_ endtype, double limit) +{ + ClipperOffset co(limit, limit); + co.AddPaths(in_polys, jointype, (EndType)endtype); + co.Execute(out_polys, delta); +} +//------------------------------------------------------------------------------ + +#endif + + +} //ClipperLib namespace diff --git a/src/clipper.hpp b/src/clipper.hpp index 16540f3..8487014 100644 --- a/src/clipper.hpp +++ b/src/clipper.hpp @@ -1,341 +1,398 @@ -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 5.1.4 * -* Date : 24 March 2013 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2013 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -#ifndef clipper_hpp -#define clipper_hpp - -#include -#include -#include -#include -#include - -namespace ClipperLib { - -enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; -enum PolyType { ptSubject, ptClip }; -//By far the most widely used winding rules for polygon filling are -//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) -//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) -//see http://glprogramming.com/red/chapter11.html -enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; - -typedef signed long long long64; -typedef unsigned long long ulong64; - -struct IntPoint { -public: - long64 X; - long64 Y; - IntPoint(long64 x = 0, long64 y = 0): X(x), Y(y) {}; - friend std::ostream& operator <<(std::ostream &s, IntPoint &p); -}; - -typedef std::vector< IntPoint > Polygon; -typedef std::vector< Polygon > Polygons; - - -std::ostream& operator <<(std::ostream &s, Polygon &p); -std::ostream& operator <<(std::ostream &s, Polygons &p); - -class PolyNode; -typedef std::vector< PolyNode* > PolyNodes; - -class PolyNode -{ -public: - PolyNode(); - Polygon Contour; - PolyNodes Childs; - PolyNode* Parent; - PolyNode* GetNext() const; - bool IsHole() const; - int ChildCount() const; -private: - PolyNode* GetNextSiblingUp() const; - unsigned Index; //node index in Parent.Childs - void AddChild(PolyNode& child); - friend class Clipper; //to access Index -}; - -class PolyTree: public PolyNode -{ -public: - ~PolyTree(){Clear();}; - PolyNode* GetFirst() const; - void Clear(); - int Total() const; -private: - PolyNodes AllNodes; - friend class Clipper; //to access AllNodes -}; - -enum JoinType { jtSquare, jtRound, jtMiter }; - -bool Orientation(const Polygon &poly); -double Area(const Polygon &poly); - -void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype = jtSquare, double limit = 0, bool autoFix = true); - -void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(Polygons &polys, PolyFillType fillType = pftEvenOdd); - -void CleanPolygon(Polygon& in_poly, Polygon& out_poly, double distance = 1.415); -void CleanPolygons(Polygons& in_polys, Polygons& out_polys, double distance = 1.415); - -void PolyTreeToPolygons(PolyTree& polytree, Polygons& polygons); - -void ReversePolygon(Polygon& p); -void ReversePolygons(Polygons& p); - -//used internally ... -enum EdgeSide { esLeft = 1, esRight = 2}; -enum IntersectProtects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 }; - -struct TEdge { - long64 xbot; - long64 ybot; - long64 xcurr; - long64 ycurr; - long64 xtop; - long64 ytop; - double dx; - long64 deltaX; - long64 deltaY; - PolyType polyType; - EdgeSide side; - int windDelta; //1 or -1 depending on winding direction - int windCnt; - int windCnt2; //winding count of the opposite polytype - int outIdx; - TEdge *next; - TEdge *prev; - TEdge *nextInLML; - TEdge *nextInAEL; - TEdge *prevInAEL; - TEdge *nextInSEL; - TEdge *prevInSEL; -}; - -struct IntersectNode { - TEdge *edge1; - TEdge *edge2; - IntPoint pt; - IntersectNode *next; -}; - -struct LocalMinima { - long64 Y; - TEdge *leftBound; - TEdge *rightBound; - LocalMinima *next; -}; - -struct Scanbeam { - long64 Y; - Scanbeam *next; -}; - -struct OutPt; //forward declaration - -struct OutRec { - int idx; - bool isHole; - OutRec *FirstLeft; //see comments in clipper.pas - PolyNode *polyNode; - OutPt *pts; - OutPt *bottomPt; -}; - -struct OutPt { - int idx; - IntPoint pt; - OutPt *next; - OutPt *prev; -}; - -struct JoinRec { - IntPoint pt1a; - IntPoint pt1b; - int poly1Idx; - IntPoint pt2a; - IntPoint pt2b; - int poly2Idx; -}; - -struct HorzJoinRec { - TEdge *edge; - int savedIdx; -}; - -struct IntRect { long64 left; long64 top; long64 right; long64 bottom; }; - -typedef std::vector < OutRec* > PolyOutList; -typedef std::vector < TEdge* > EdgeList; -typedef std::vector < JoinRec* > JoinList; -typedef std::vector < HorzJoinRec* > HorzJoinList; - -//ClipperBase is the ancestor to the Clipper class. It should not be -//instantiated directly. This class simply abstracts the conversion of sets of -//polygon coordinates into edge objects that are stored in a LocalMinima list. -class ClipperBase -{ -public: - ClipperBase(); - virtual ~ClipperBase(); - bool AddPolygon(const Polygon &pg, PolyType polyType); - bool AddPolygons( const Polygons &ppg, PolyType polyType); - virtual void Clear(); - IntRect GetBounds(); -protected: - void DisposeLocalMinimaList(); - TEdge* AddBoundsToLML(TEdge *e); - void PopLocalMinima(); - virtual void Reset(); - void InsertLocalMinima(LocalMinima *newLm); - LocalMinima *m_CurrentLM; - LocalMinima *m_MinimaList; - bool m_UseFullRange; - EdgeList m_edges; -}; - -class Clipper : public virtual ClipperBase -{ -public: - Clipper(); - ~Clipper(); - bool Execute(ClipType clipType, - Polygons &solution, - PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); - bool Execute(ClipType clipType, - PolyTree &polytree, - PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); - void Clear(); - bool ReverseSolution() {return m_ReverseOutput;}; - void ReverseSolution(bool value) {m_ReverseOutput = value;}; -protected: - void Reset(); - virtual bool ExecuteInternal(); -private: - PolyOutList m_PolyOuts; - JoinList m_Joins; - HorzJoinList m_HorizJoins; - ClipType m_ClipType; - Scanbeam *m_Scanbeam; - TEdge *m_ActiveEdges; - TEdge *m_SortedEdges; - IntersectNode *m_IntersectNodes; - bool m_ExecuteLocked; - PolyFillType m_ClipFillType; - PolyFillType m_SubjFillType; - bool m_ReverseOutput; - bool m_UsingPolyTree; - void DisposeScanbeamList(); - void SetWindingCount(TEdge& edge); - bool IsEvenOddFillType(const TEdge& edge) const; - bool IsEvenOddAltFillType(const TEdge& edge) const; - void InsertScanbeam(const long64 Y); - long64 PopScanbeam(); - void InsertLocalMinimaIntoAEL(const long64 botY); - void InsertEdgeIntoAEL(TEdge *edge); - void AddEdgeToSEL(TEdge *edge); - void CopyAELToSEL(); - void DeleteFromSEL(TEdge *e); - void DeleteFromAEL(TEdge *e); - void UpdateEdgeIntoAEL(TEdge *&e); - void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); - bool IsContributing(const TEdge& edge) const; - bool IsTopHorz(const long64 XPos); - void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); - void DoMaxima(TEdge *e, long64 topY); - void ProcessHorizontals(); - void ProcessHorizontal(TEdge *horzEdge); - void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - void AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - void AppendPolygon(TEdge *e1, TEdge *e2); - void DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt); - void DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt); - void DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt); - void IntersectEdges(TEdge *e1, TEdge *e2, - const IntPoint &pt, const IntersectProtects protects); - OutRec* CreateOutRec(); - void AddOutPt(TEdge *e, const IntPoint &pt); - void DisposeAllPolyPts(); - void DisposeOutRec(PolyOutList::size_type index); - bool ProcessIntersections(const long64 botY, const long64 topY); - void AddIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt); - void BuildIntersectList(const long64 botY, const long64 topY); - void ProcessIntersectList(); - void ProcessEdgesAtTopOfScanbeam(const long64 topY); - void BuildResult(Polygons& polys); - void BuildResult2(PolyTree& polytree); - void SetHoleState(TEdge *e, OutRec *OutRec); - void DisposeIntersectNodes(); - bool FixupIntersectionOrder(); - void FixupOutPolygon(OutRec &outRec); - bool IsHole(TEdge *e); - void FixHoleLinkage(OutRec &outRec); - void AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx = -1, int e2OutIdx = -1); - void ClearJoins(); - void AddHorzJoin(TEdge *e, int idx); - void ClearHorzJoins(); - bool JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2); - void FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx); - void JoinCommonEdges(); - void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); - void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); -}; - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -class clipperException : public std::exception -{ - public: - clipperException(const char* description): m_descr(description) {} - virtual ~clipperException() throw() {} - virtual const char* what() const throw() {return m_descr.c_str();} - private: - std::string m_descr; -}; -//------------------------------------------------------------------------------ - -} //ClipperLib namespace - -#endif //clipper_hpp - - +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.1.3a * +* Date : 22 January 2014 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2014 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#define CLIPPER_VERSION "6.1.3" + +//use_int32: When enabled 32bit ints are used instead of 64bit ints. This +//improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. +//#define use_xyz + +//use_lines: Enables line clipping. Adds a very minor cost to performance. +//#define use_lines + +//use_deprecated: Enables support for the obsolete OffsetPaths() function +//which has been replace with the ClipperOffset class. +#define use_deprecated + +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +//By far the most widely used winding rules for polygon filling are +//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +//see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +#ifdef use_int32 +typedef int cInt; +typedef unsigned int cUInt; +#else +typedef signed long long cInt; +typedef unsigned long long cUInt; +#endif + +struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; +#else + IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; +#endif + + friend inline bool operator== (const IntPoint& a, const IntPoint& b) + { + return a.X == b.X && a.Y == b.Y; + } + friend inline bool operator!= (const IntPoint& a, const IntPoint& b) + { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector< IntPoint > Path; +typedef std::vector< Path > Paths; + +inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} +inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} + +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); + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +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; + +class PolyNode +{ +public: + PolyNode(); + Path Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext() const; + bool IsHole() const; + bool IsOpen() const; + int ChildCount() const; +private: + 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 +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst() const; + void Clear(); + int Total() const; +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + +bool Orientation(const Path &poly); +double Area(const Path &poly); +int PointInPolygon(const IntPoint &pt, const Path &path); + +#ifdef use_deprecated + void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType_ endtype, double limit = 0); +#endif + +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); + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); +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 MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); +void MinkowskiSum(const Path& pattern, const Paths& paths, + Paths& solution, PolyFillType pathFillType, bool pathIsClosed); +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); + +void ReversePath(Path& p); +void ReversePaths(Paths& p); + +struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; + +//enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; + +//forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinima; +struct Scanbeam; +struct OutPt; +struct OutRec; +struct Join; + +typedef std::vector < OutRec* > PolyOutList; +typedef std::vector < TEdge* > EdgeList; +typedef std::vector < Join* > JoinList; +typedef std::vector < IntersectNode* > IntersectList; + + +//------------------------------------------------------------------------------ + +//ClipperBase is the ancestor to the Clipper class. It should not be +//instantiated directly. This class simply abstracts the conversion of sets of +//polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase +{ +public: + ClipperBase(); + virtual ~ClipperBase(); + bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + virtual void Clear(); + IntRect GetBounds(); + bool PreserveCollinear() {return m_PreserveCollinear;}; + void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; +protected: + void DisposeLocalMinimaList(); + 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); + void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); + LocalMinima *m_CurrentLM; + LocalMinima *m_MinimaList; + bool m_UseFullRange; + EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; +}; +//------------------------------------------------------------------------------ + +class Clipper : public virtual ClipperBase +{ +public: + Clipper(int initOptions = 0); + ~Clipper(); + bool Execute(ClipType clipType, + Paths &solution, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool ReverseSolution() {return m_ReverseOutput;}; + void ReverseSolution(bool value) {m_ReverseOutput = value;}; + bool StrictlySimple() {return m_StrictSimple;}; + void StrictlySimple(bool value) {m_StrictSimple = value;}; + //set the callback function for z value filling on intersections (otherwise Z is 0) +#ifdef use_xyz + void ZFillFunction(TZFillCallback zFillFunc); +#endif +protected: + void Reset(); + virtual bool ExecuteInternal(); +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; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + TZFillCallback m_ZFill; //custom callback +#endif + void SetWindingCount(TEdge& edge); + bool IsEvenOddFillType(const TEdge& edge) const; + bool IsEvenOddAltFillType(const TEdge& edge) const; + void InsertScanbeam(const cInt Y); + cInt PopScanbeam(); + void InsertLocalMinimaIntoAEL(const cInt botY); + void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); + void AddEdgeToSEL(TEdge *edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge& edge) const; + bool IsTopHorz(const cInt XPos); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DoMaxima(TEdge *e); + void PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam); + void ProcessHorizontals(bool IsTopOfScanbeam); + void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec* GetOutRec(int idx); + void AppendPolygon(TEdge *e1, TEdge *e2); + void IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, bool protect = false); + OutRec* CreateOutRec(); + OutPt* AddOutPt(TEdge *e, const IntPoint &pt); + void DisposeAllOutRecs(); + void DisposeOutRec(PolyOutList::size_type index); + bool ProcessIntersections(const cInt botY, const cInt topY); + void BuildIntersectList(const cInt botY, const cInt topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + void BuildResult(Paths& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *outrec); + void DisposeIntersectNodes(); + 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(Join *j, OutRec* outRec1, OutRec* outRec2); + void JoinCommonEdges(); + void DoSimplePolygons(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); +#ifdef use_xyz + void SetZ(IntPoint& pt, TEdge& e); +#endif +}; +//------------------------------------------------------------------------------ + +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: + clipperException(const char* description): m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char* what() const throw() {return m_descr.c_str();} + private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} //ClipperLib namespace + +#endif //clipper_hpp + + From d2fc7e1f510269b53adf2ccbf3c0f07ce1877b61 Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Tue, 27 Sep 2016 15:30:20 +0200 Subject: [PATCH 17/20] new version 0.2.1 npm ready --- .gitignore | 1 - package.json | 39 +++++++++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 0a453a4..a26ef03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ build -.DS_Store node_modules # Vim cruft diff --git a/package.json b/package.json index 6886995..653824f 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,34 @@ { - "name": "clipper", - "version": "v0.2.0", + "name": "rl3-clipper", + "version": "0.2.1", "main": "./lib/clipper.js", - "keywords": ["polygon", "polygons", "offset"], + "keywords": [ + "polygon", + "polygons", + "offset" + ], "description": "Clipper Library Bindings", - "author": { - "name" : "runlevel3 GmbH, Berlin", - "email" : "info@runlevel3.de", - "url" : "https://www.runlevel3.de/" - }, + "author": "runlevel3 GmbH, Berlin (https://www.runlevel3.de/)", "engines": { "node": ">=2.0.0" }, - "repository" : { - "type" : "git", - "url" : "https://github.com/rl3/node-clipper.git" + "repository": { + "type": "git", + "url": "git+https://github.com/rl3/node-clipper.git" + }, + "scripts": { + "install": "node-gyp rebuild" + }, + "gypfile": true, + "bugs": { + "url": "https://github.com/rl3/node-clipper/issues" + }, + "homepage": "https://github.com/rl3/node-clipper#readme", + "directories": { + "example": "examples" + }, + "license": "ISC", + "devDependencies": { + "node-gyp": "^3.4.0" } -} \ No newline at end of file +} From 78297996de63acad7e722bd441310c56bc5a8c04 Mon Sep 17 00:00:00 2001 From: steppicrew Date: Tue, 27 Sep 2016 15:40:05 +0200 Subject: [PATCH 18/20] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 4fd3166..00c447f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,13 @@ The wrapper exposes and extends functions of clipper library to node.js. The fun ## Installation +```bash +npm install --save rl3-clipper +``` + +## Build from source + +Clone the project and build the project ```bash node-gyp configure node-gyp build From e9dd4448aad96b2e4665804442da86d76ca6f258 Mon Sep 17 00:00:00 2001 From: steppicrew Date: Tue, 27 Sep 2016 15:41:36 +0200 Subject: [PATCH 19/20] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 00c447f..5ba49b1 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Example: ```javascript 'use strict'; -var clipper= require('../build/Release/clipper'); +var clipper= require('rl3-clipper'); var square= [ [ [0, 0], [0, 10], [10, 10], [10, 0] ] ]; @@ -95,7 +95,7 @@ Example: ```javascript 'use strict'; -var clipper= require('../build/Release/clipper'); +var clipper= require('rl3-clipper'); var squareOuter= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; @@ -122,7 +122,7 @@ See the [clipper documentation for offset](http://www.angusj.com/delphi/clipper/ ```javascript 'use strict'; -var clipper= require('../build/Release/clipper'); +var clipper= require('rl3-clipper'); var squareOuter= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; @@ -154,7 +154,7 @@ Generate minimum polygon from polyShape. Read comments in the example to see, ho ```javascript 'use strict'; -var clipper= require('../build/Release/clipper'); +var clipper= require('rl3-clipper'); var squareOuter= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; var squareInner= [ [3, 3], [3, 7], [7, 7], [7, 3] ]; @@ -191,7 +191,7 @@ Clip two polygons. The result may produce multiple polygons. See also the [clipp ```javascript 'use strict'; -var clipper= require('../build/Release/clipper'); +var clipper= require('rl3-clipper'); var util= require('util'); var outerSubject= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; @@ -230,7 +230,7 @@ Clean polygon. See also the [clipper documentation for clean](http://www.angusj. ```javascript 'use strict'; -var clipper= require('../build/Release/clipper'); +var clipper= require('rl3-clipper'); var outer= [ [10, 0], [100, 100], [0, 10], [0, 0] ]; var inner= [ [3, 3], [3, 7], [7, 6], [7, 3] ]; @@ -259,7 +259,7 @@ Simplify polygon. See also the [clipper documentation for simplify](http://www.a ```javascript 'use strict'; -var clipper= require('../build/Release/clipper'); +var clipper= require('rl3-clipper'); var outer= [ [10, 0], [100, 100], [101, 100], [100, 100], [0, 10], [0, 0] ]; var inner= [ [3, 3], [3, 7], [7, 6], [7, 3] ]; @@ -287,7 +287,7 @@ Fix orientation of all polygons an a polyShape array. ```javascript 'use strict'; -var clipper= require('../build/Release/clipper'); +var clipper= require('rl3-clipper'); var counterclockwise= [ [10, 0], [10, 10], [0, 10], [0, 0] ]; var clockwise= [ [3, 3], [7, 7], [3, 7], [7, 3] ]; From 11cefe77c06e7b5d7bfdc4c58d093a50e1ac4002 Mon Sep 17 00:00:00 2001 From: "runlevel3@goedel" Date: Tue, 27 Sep 2016 16:00:27 +0200 Subject: [PATCH 20/20] revert to old version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 653824f..fcea18a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "rl3-clipper", - "version": "0.2.1", + "name": "clipper", + "version": "0.2.0", "main": "./lib/clipper.js", "keywords": [ "polygon",