diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h index 9fd53d033b..7a32d4c4aa 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 8 April 2023 * +* Date : 26 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -53,6 +53,7 @@ namespace Clipper2Lib #ifndef PI static const double PI = 3.141592653589793238; #endif + static const int MAX_DECIMAL_PRECISION = 8; // see https://github.com/AngusJohnson/Clipper2/discussions/564 static const int64_t MAX_COORD = INT64_MAX >> 2; static const int64_t MIN_COORD = -MAX_COORD; static const int64_t INVALID = INT64_MAX; @@ -132,6 +133,7 @@ namespace Clipper2Lib return Point(x * scale, y * scale, z); } + void SetZ(const int64_t z_value) { z = z_value; } friend std::ostream& operator<<(std::ostream& os, const Point& point) { @@ -255,8 +257,8 @@ namespace Clipper2Lib } else { - left = top = std::numeric_limits::max(); - right = bottom = -std::numeric_limits::max(); + left = top = (std::numeric_limits::max)(); + right = bottom = -(std::numeric_limits::max)(); } } @@ -346,8 +348,8 @@ namespace Clipper2Lib template Rect GetBounds(const Path& path) { - auto xmin = std::numeric_limits::max(); - auto ymin = std::numeric_limits::max(); + auto xmin = (std::numeric_limits::max)(); + auto ymin = (std::numeric_limits::max)(); auto xmax = std::numeric_limits::lowest(); auto ymax = std::numeric_limits::lowest(); for (const auto& p : path) @@ -363,8 +365,8 @@ namespace Clipper2Lib template Rect GetBounds(const Paths& paths) { - auto xmin = std::numeric_limits::max(); - auto ymin = std::numeric_limits::max(); + auto xmin = (std::numeric_limits::max)(); + auto ymin = (std::numeric_limits::max)(); auto xmax = std::numeric_limits::lowest(); auto ymax = std::numeric_limits::lowest(); for (const Path& path : paths) @@ -582,10 +584,10 @@ namespace Clipper2Lib inline void CheckPrecision(int& precision, int& error_code) { - if (precision >= -8 && precision <= 8) return; + if (precision >= -MAX_DECIMAL_PRECISION && precision <= MAX_DECIMAL_PRECISION) return; error_code |= precision_error_i; // non-fatal error - DoError(precision_error_i); // unless exceptions enabled - precision = precision > 8 ? 8 : -8; + DoError(precision_error_i); // does nothing unless exceptions enabled + precision = precision > 0 ? MAX_DECIMAL_PRECISION : -MAX_DECIMAL_PRECISION; } inline void CheckPrecision(int& precision) @@ -677,29 +679,27 @@ namespace Clipper2Lib //nb: This statement is premised on using Cartesian coordinates return Area(poly) >= 0; } - - inline int64_t CheckCastInt64(double val) - { - if ((val >= max_coord) || (val <= min_coord)) return INVALID; - else return static_cast(val); - } - + inline bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b, const Point64& ln2a, const Point64& ln2b, Point64& ip) { // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection - double dx1 = static_cast(ln1b.x - ln1a.x); double dy1 = static_cast(ln1b.y - ln1a.y); double dx2 = static_cast(ln2b.x - ln2a.x); double dy2 = static_cast(ln2b.y - ln2a.y); + double det = dy1 * dx2 - dy2 * dx1; - if (det == 0.0) return 0; - double qx = dx1 * ln1a.y - dy1 * ln1a.x; - double qy = dx2 * ln2a.y - dy2 * ln2a.x; - ip.x = CheckCastInt64((dx1 * qy - dx2 * qx) / det); - ip.y = CheckCastInt64((dy1 * qy - dy2 * qx) / det); - return (ip.x != INVALID && ip.y != INVALID); + if (det == 0.0) return false; + double t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det; + if (t <= 0.0) ip = ln1a; // ?? check further (see also #568) + else if (t >= 1.0) ip = ln1b; // ?? check further + else + { + ip.x = static_cast(ln1a.x + t * dx1); + ip.y = static_cast(ln1a.y + t * dy1); + } + return true; } inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h index 9d755f9fd4..239f705804 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 May 2023 * +* Date : 26 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -13,7 +13,7 @@ constexpr auto CLIPPER2_VERSION = "1.2.2"; #include -#include +#include //#541 #include #include #include @@ -26,6 +26,7 @@ constexpr auto CLIPPER2_VERSION = "1.2.2"; #ifdef None #undef None #endif + namespace Clipper2Lib { struct Scanline; @@ -95,10 +96,11 @@ namespace Clipper2Lib { OutPt* pts = nullptr; PolyPath* polypath = nullptr; OutRecList* splits = nullptr; + OutRec* recursive_split = nullptr; Rect64 bounds = {}; Path64 path; bool is_open = false; - bool horz_done = false; + ~OutRec() { if (splits) delete splits; // nb: don't delete the split pointers @@ -253,7 +255,6 @@ namespace Clipper2Lib { void DoTopOfScanbeam(const int64_t top_y); Active *DoMaxima(Active &e); void JoinOutrecPaths(Active &e1, Active &e2); - void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec); void FixSelfIntersects(OutRec* outrec); void DoSplitOp(OutRec* outRec, OutPt* splitOp); diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h index 5bfab9c893..f5f81d2a8b 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h @@ -159,9 +159,9 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, // RectClip & RectClipLines: EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, - const CPaths64 paths, bool convex_only = false); + const CPaths64 paths); EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, - const CPathsD paths, int precision = 2, bool convex_only = false); + const CPathsD paths, int precision = 2); EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, const CPaths64 paths); EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h index 808f49cc7a..b5ac6aa9a1 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -312,75 +312,55 @@ namespace Clipper2Lib { } static void OutlinePolyPath(std::ostream& os, - bool isHole, size_t count, const std::string& preamble) + size_t idx, bool isHole, size_t count, const std::string& preamble) { std::string plural = (count == 1) ? "." : "s."; if (isHole) - { - if (count) - os << preamble << "+- Hole with " << count << - " nested polygon" << plural << std::endl; - else - os << preamble << "+- Hole" << std::endl; - } + os << preamble << "+- Hole (" << idx << ") contains " << count << + " nested polygon" << plural << std::endl; else - { - if (count) - os << preamble << "+- Polygon with " << count << + os << preamble << "+- Polygon (" << idx << ") contains " << count << " hole" << plural << std::endl; - else - os << preamble << "+- Polygon" << std::endl; - } } static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp, - std::string preamble, bool last_child) + size_t idx, std::string preamble) { - OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); - preamble += (!last_child) ? "| " : " "; - if (pp.Count()) - { - PolyPath64List::const_iterator it = pp.begin(); - for (; it < pp.end() - 1; ++it) - OutlinePolyPath64(os, **it, preamble, false); - OutlinePolyPath64(os, **it, preamble, true); - } + OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble); + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + " "); } static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp, - std::string preamble, bool last_child) + size_t idx, std::string preamble) { - OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); - preamble += (!last_child) ? "| " : " "; - if (pp.Count()) - { - PolyPathDList::const_iterator it = pp.begin(); - for (; it < pp.end() - 1; ++it) - OutlinePolyPathD(os, **it, preamble, false); - OutlinePolyPathD(os, **it, preamble, true); - } + OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble); + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " "); } } // end details namespace inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) { - os << std::endl << "Polytree root" << std::endl; - PolyPath64List::const_iterator it = pp.begin(); - for (; it < pp.end() - 1; ++it) - details::OutlinePolyPath64(os, **it, " ", false); - details::OutlinePolyPath64(os, **it, " ", true); + std::string plural = (pp.Count() == 1) ? " polygon." : " polygons."; + os << std::endl << "Polytree with " << pp.Count() << plural << std::endl; + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPath64(os, *pp.Child(i), i, " "); os << std::endl << std::endl; - if (!pp.Level()) os << std::endl; return os; } inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp) { - PolyPathDList::const_iterator it = pp.begin(); - for (; it < pp.end() - 1; ++it) - details::OutlinePolyPathD(os, **it, " ", false); - details::OutlinePolyPathD(os, **it, " ", true); + std::string plural = (pp.Count() == 1) ? " polygon." : " polygons."; + os << std::endl << "Polytree with " << pp.Count() << plural << std::endl; + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPathD(os, *pp.Child(i), i, " "); os << std::endl << std::endl; if (!pp.Level()) os << std::endl; return os; diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp index b8837669ba..99a734d9f4 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 20 May 2023 * +* Date : 5 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -419,6 +419,13 @@ namespace Clipper2Lib { return outrec; } + inline bool IsValidOwner(OutRec* outrec, OutRec* testOwner) + { + // prevent outrec owning itself either directly or indirectly + while (testOwner && testOwner != outrec) testOwner = testOwner->owner; + return !testOwner; + } + inline void UncoupleOutRec(Active ae) { OutRec* outrec = ae.outrec; @@ -483,6 +490,115 @@ namespace Clipper2Lib { outrec->owner = new_owner; } + static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) + { + if (op == op->next || op->prev == op->next) + return PointInPolygonResult::IsOutside; + + OutPt* op2 = op; + do + { + if (op->pt.y != pt.y) break; + op = op->next; + } while (op != op2); + if (op->pt.y == pt.y) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = op->pt.y < pt.y, starting_above = is_above; + int val = 0; + op2 = op->next; + while (op2 != op) + { + if (is_above) + while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; + else + while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; + if (op2 == op) break; + + // must have touched or crossed the pt.Y horizonal + // and this must happen an even number of times + + if (op2->pt.y == pt.y) // touching the horizontal + { + if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && + (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) + return PointInPolygonResult::IsOn; + + op2 = op2->next; + if (op2 == op) break; + continue; + } + + if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); + // do nothing because + // we're only interested in edges crossing on the left + else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + op2 = op2->next; + } + + if (is_above != starting_above) + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + if (val == 0) return PointInPolygonResult::IsOutside; + else return PointInPolygonResult::IsInside; + } + + inline Path64 GetCleanPath(OutPt* op) + { + Path64 result; + OutPt* op2 = op; + while (op2->next != op && + ((op2->pt.x == op2->next->pt.x && op2->pt.x == op2->prev->pt.x) || + (op2->pt.y == op2->next->pt.y && op2->pt.y == op2->prev->pt.y))) op2 = op2->next; + result.push_back(op2->pt); + OutPt* prevOp = op2; + op2 = op2->next; + while (op2 != op) + { + if ((op2->pt.x != op2->next->pt.x || op2->pt.x != prevOp->pt.x) && + (op2->pt.y != op2->next->pt.y || op2->pt.y != prevOp->pt.y)) + { + result.push_back(op2->pt); + prevOp = op2; + } + op2 = op2->next; + } + return result; + } + + inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) + { + // we need to make some accommodation for rounding errors + // so we won't jump if the first vertex is found outside + PointInPolygonResult result; + int outside_cnt = 0; + OutPt* op = op1; + do + { + result = PointInOpPolygon(op->pt, op2); + if (result == PointInPolygonResult::IsOutside) ++outside_cnt; + else if (result == PointInPolygonResult::IsInside) --outside_cnt; + op = op->next; + } while (op != op1 && std::abs(outside_cnt) < 2); + if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); + // since path1's location is still equivocal, check its midpoint + Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint(); + Path64 path2 = GetCleanPath(op2); + return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside; + } + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ @@ -600,8 +716,6 @@ namespace Clipper2Lib { vertexLists.emplace_back(vertices); } - - //------------------------------------------------------------------------------ // ReuseableDataContainer64 methods ... //------------------------------------------------------------------------------ @@ -677,7 +791,7 @@ namespace Clipper2Lib { { if (!minima_list_sorted_) { - std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); + std::stable_sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); //#594 minima_list_sorted_ = true; } LocalMinimaList::const_reverse_iterator i; @@ -1298,7 +1412,7 @@ namespace Clipper2Lib { else SetOwner(&outrec, e->outrec); // nb: outRec.owner here is likely NOT the real - // owner but this will be checked in DeepCheckOwner() + // owner but this will be checked in RecursiveCheckOwners() } UncoupleOutRec(e1); @@ -1472,11 +1586,6 @@ namespace Clipper2Lib { return; } - // nb: area1 is the path's area *before* splitting, whereas area2 is - // the area of the triangle containing splitOp & splitOp.next. - // So the only way for these areas to have the same sign is if - // the split triangle is larger than the path containing prevOp or - // if there's more than one self=intersection. double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt); double absArea2 = std::fabs(area2); @@ -1496,18 +1605,17 @@ namespace Clipper2Lib { prevOp->next = newOp2; } + // area1 is the path's area *before* splitting, whereas area2 is + // the area of the triangle containing splitOp & splitOp.next. + // So the only way for these areas to have the same sign is if + // the split triangle is larger than the path containing prevOp or + // if there's more than one self-intersection. if (absArea2 >= 1 && (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) { OutRec* newOr = NewOutRec(); newOr->owner = outrec->owner; - if (using_polytree_) - { - if (!outrec->splits) outrec->splits = new OutRecList(); - outrec->splits->push_back(newOr); - } - splitOp->outrec = newOr; splitOp->next->outrec = newOr; OutPt* newOp = new OutPt(ip, newOr); @@ -1516,6 +1624,20 @@ namespace Clipper2Lib { newOr->pts = newOp; splitOp->prev = newOp; splitOp->next->next = newOp; + + if (using_polytree_) + { + if (Path1InsidePath2(prevOp, newOp)) + { + newOr->splits = new OutRecList(); + newOr->splits->push_back(outrec); + } + else + { + if (!outrec->splits) outrec->splits = new OutRecList(); + outrec->splits->push_back(newOr); + } + } } else { @@ -1960,105 +2082,6 @@ namespace Clipper2Lib { } while (op != outrec->pts); } - inline Rect64 GetBounds(OutPt* op) - { - Rect64 result(op->pt.x, op->pt.y, op->pt.x, op->pt.y); - OutPt* op2 = op->next; - while (op2 != op) - { - if (op2->pt.x < result.left) result.left = op2->pt.x; - else if (op2->pt.x > result.right) result.right = op2->pt.x; - if (op2->pt.y < result.top) result.top = op2->pt.y; - else if (op2->pt.y > result.bottom) result.bottom = op2->pt.y; - op2 = op2->next; - } - return result; - } - - static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) - { - if (op == op->next || op->prev == op->next) - return PointInPolygonResult::IsOutside; - - OutPt* op2 = op; - do - { - if (op->pt.y != pt.y) break; - op = op->next; - } while (op != op2); - if (op->pt.y == pt.y) // not a proper polygon - return PointInPolygonResult::IsOutside; - - bool is_above = op->pt.y < pt.y, starting_above = is_above; - int val = 0; - op2 = op->next; - while (op2 != op) - { - if (is_above) - while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; - else - while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; - if (op2 == op) break; - - // must have touched or crossed the pt.Y horizonal - // and this must happen an even number of times - - if (op2->pt.y == pt.y) // touching the horizontal - { - if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && - (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) - return PointInPolygonResult::IsOn; - - op2 = op2->next; - if (op2 == op) break; - continue; - } - - if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); - // do nothing because - // we're only interested in edges crossing on the left - else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) - val = 1 - val; // toggle val - else - { - double d = CrossProduct(op2->prev->pt, op2->pt, pt); - if (d == 0) return PointInPolygonResult::IsOn; - if ((d < 0) == is_above) val = 1 - val; - } - is_above = !is_above; - op2 = op2->next; - } - - if (is_above != starting_above) - { - double d = CrossProduct(op2->prev->pt, op2->pt, pt); - if (d == 0) return PointInPolygonResult::IsOn; - if ((d < 0) == is_above) val = 1 - val; - } - - if (val == 0) return PointInPolygonResult::IsOutside; - else return PointInPolygonResult::IsInside; - } - - inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) - { - // we need to make some accommodation for rounding errors - // so we won't jump if the first vertex is found outside - int outside_cnt = 0; - OutPt* op = op1; - do - { - PointInPolygonResult result = PointInOpPolygon(op->pt, op2); - if (result == PointInPolygonResult::IsOutside) ++outside_cnt; - else if (result == PointInPolygonResult::IsInside) --outside_cnt; - op = op->next; - } while (op != op1 && std::abs(outside_cnt) < 2); - if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); - // since path1's location is still equivocal, check its midpoint - Point64 mp = GetBounds(op).MidPoint(); - return PointInOpPolygon(mp, op2) == PointInPolygonResult::IsInside; - } - inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN) { if (opP->pt.x == opN->pt.x) return false; @@ -2126,8 +2149,8 @@ namespace Clipper2Lib { { for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2) { - if (hs2->left_op->pt.x >= hs1->right_op->pt.x) break; - if (hs2->left_to_right == hs1->left_to_right || + if ((hs2->left_op->pt.x >= hs1->right_op->pt.x) || + (hs2->left_to_right == hs1->left_to_right) || (hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue; int64_t curr_y = hs1->left_op->pt.y; if (hs1->left_to_right) @@ -2160,6 +2183,7 @@ namespace Clipper2Lib { } } + void ClipperBase::ProcessHorzJoins() { for (const HorzJoin& j : horz_join_list_) @@ -2174,7 +2198,7 @@ namespace Clipper2Lib { op1b->prev = op2b; op2b->next = op1b; - if (or1 == or2) + if (or1 == or2) // 'join' is really a split { or2 = NewOutRec(); or2->pts = op1b; @@ -2185,21 +2209,18 @@ namespace Clipper2Lib { or1->pts->outrec = or1; } - if (using_polytree_) + if (using_polytree_) //#498, #520, #584, D#576 { - if (Path1InsidePath2(or2->pts, or1->pts)) + if (Path1InsidePath2(or1->pts, or2->pts)) + { + or2->owner = or1->owner; + SetOwner(or1, or2); + } + else { SetOwner(or2, or1); if (!or1->splits) or1->splits = new OutRecList(); - or1->splits->push_back(or2); //(#520) - } - else if (Path1InsidePath2(or1->pts, or2->pts)) - SetOwner(or1, or2); - else - { - if (!or1->splits) or1->splits = new OutRecList(); - or1->splits->push_back(or2); //(#498) - or2->owner = or1; + or1->splits->push_back(or2); } } else @@ -2478,7 +2499,6 @@ namespace Clipper2Lib { #endif AddTrialHorzJoin(op); } - OutRec* currHorzOutrec = horz.outrec; while (true) // loop through consec. horizontal edges { @@ -2558,9 +2578,8 @@ namespace Clipper2Lib { e = horz.prev_in_ael; } - if (horz.outrec && horz.outrec != currHorzOutrec) + if (horz.outrec) { - currHorzOutrec = horz.outrec; //nb: The outrec containining the op returned by IntersectEdges //above may no longer be associated with horzEdge. AddTrialHorzJoin(GetLastOp(horz)); @@ -2597,7 +2616,12 @@ namespace Clipper2Lib { ResetHorzDirection(horz, vertex_max, horz_left, horz_right); } - if (IsHotEdge(horz)) AddOutPt(horz, horz.top); + if (IsHotEdge(horz)) + { + OutPt* op = AddOutPt(horz, horz.top); + AddTrialHorzJoin(op); + } + UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz. } @@ -2824,8 +2848,8 @@ namespace Clipper2Lib { if (!outrec->bounds.IsEmpty()) return true; CleanCollinear(outrec); if (!outrec->pts || - !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) - return false; + !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)){ + return false;} outrec->bounds = GetBounds(outrec->path); return true; } @@ -2834,9 +2858,15 @@ namespace Clipper2Lib { { for (auto split : *splits) { - if(split == outrec || split == outrec->owner) continue; - else if (split->splits && CheckSplitOwner(outrec, split->splits)) return true; - else if (CheckBounds(split) && split->bounds.Contains(outrec->bounds) && + split = GetRealOutRec(split); + if(!split || split->recursive_split == outrec) continue; + split->recursive_split = outrec; // prevent infinite loops + + if (split->splits && CheckSplitOwner(outrec, split->splits)) + return true; + else if (CheckBounds(split) && + IsValidOwner(outrec, split) && + split->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec->pts, split->pts)) { outrec->owner = split; //found in split @@ -3021,8 +3051,11 @@ namespace Clipper2Lib { if (has_open_paths_) open_paths.reserve(outrec_list_.size()); - for (OutRec* outrec : outrec_list_) + // outrec_list_.size() is not static here because + // BuildPathD below can indirectly add additional OutRec //#607 + for (size_t i = 0; i < outrec_list_.size(); ++i) { + OutRec* outrec = outrec_list_[i]; if (!outrec || !outrec->pts) continue; if (outrec->is_open) { diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp index 2cefb0ed26..5faa26176d 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 7 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -78,8 +78,7 @@ inline double Hypot(double x, double y) } inline PointD NormalizeVector(const PointD& vec) -{ - +{ double h = Hypot(vec.x, vec.y); if (AlmostZero(h)) return PointD(0,0); double inverseHypot = 1 / h; @@ -318,7 +317,10 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) if (sin_a > 1.0) sin_a = 1.0; else if (sin_a < -1.0) sin_a = -1.0; - if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, j, k); + if (deltaCallback64_) { + group_delta_ = deltaCallback64_(path, norms, j, k); + if (group.is_reversed) group_delta_ = -group_delta_; + } if (std::fabs(group_delta_) <= floating_point_tolerance) { group.path.push_back(path[j]); @@ -352,6 +354,17 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) void ClipperOffset::OffsetPolygon(Group& group, Path64& path) { + // when the path is contracting, make sure + // there is sufficient space to do so. //#593 + // nb: this will have a small impact on performance + double a = Area(path); + // contracting when orientation is opposite offset direction + if ((a < 0) != (group_delta_ < 0)) + { + Rect64 rec = GetBounds(path); + if (std::fabs(group_delta_) * 2 > rec.Width()) return; + } + for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j) OffsetPoint(group, path, j, k); group.paths_out.push_back(group.path); @@ -602,7 +615,6 @@ void ClipperOffset::Execute(double delta, Paths64& paths) if (!solution.size()) return; paths = solution; - /**/ //clean up self-intersections ... Clipper64 c; c.PreserveCollinear = false; @@ -618,7 +630,6 @@ void ClipperOffset::Execute(double delta, Paths64& paths) c.Execute(ClipType::Union, FillRule::Negative, paths); else c.Execute(ClipType::Union, FillRule::Positive, paths); - /**/ } diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp index 49cb21ec0c..221cd9bb49 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 6 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -474,7 +474,7 @@ namespace Clipper2Lib { // intersect pt but we'll also need the first intersect pt (ip2) loc = prev; GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2); - if (crossing_prev != Location::Inside) + if (crossing_prev != Location::Inside && crossing_prev != loc) //579 AddCorner(crossing_prev, loc); if (first_cross_ == Location::Inside) @@ -847,7 +847,7 @@ namespace Clipper2Lib { //clean up after every loop op_container_ = std::deque(); results_.clear(); - for (OutPt2List edge : edges_) edge.clear(); + for (OutPt2List &edge : edges_) edge.clear(); start_locs_.clear(); } return result;