From 9d063e8f5d1b06b4f7ed7b61e413c10a3bc8c8f7 Mon Sep 17 00:00:00 2001 From: eismpesd Date: Mon, 12 May 2025 13:05:37 +0200 Subject: [PATCH] Clipper2 1.5.3 --- 3rdparty/README.md | 2 +- .../clipper2/include/clipper2/clipper.core.h | 85 +++++++++-- 3rdparty/clipper2/include/clipper2/clipper.h | 93 ++++++++---- .../include/clipper2/clipper.offset.h | 2 +- .../include/clipper2/clipper.version.h | 2 +- 3rdparty/clipper2/src/clipper.engine.cpp | 143 ++++++++++-------- 3rdparty/clipper2/src/clipper.offset.cpp | 29 ++-- 7 files changed, 232 insertions(+), 124 deletions(-) diff --git a/3rdparty/README.md b/3rdparty/README.md index b702b75f748..6e87fa461a3 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -32,7 +32,7 @@ ## Clipper2 - [![Upstream](https://img.shields.io/github/v/tag/AngusJohnson/Clipper2?label=Upstream)](https://github.com/AngusJohnson/Clipper2) -- Version: 1.5.2 +- Version: 1.5.3 - License: BSL-1.0 ## ConcurrentQueue diff --git a/3rdparty/clipper2/include/clipper2/clipper.core.h b/3rdparty/clipper2/include/clipper2/clipper.core.h index ab71aeb0727..97f31d950b0 100644 --- a/3rdparty/clipper2/include/clipper2/clipper.core.h +++ b/3rdparty/clipper2/include/clipper2/clipper.core.h @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 12 May 2024 * +* Date : 24 March 2025 * * Website : https://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Copyright : Angus Johnson 2010-2025 * * Purpose : Core Clipper Library structures and functions * * License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -248,6 +248,20 @@ namespace Clipper2Lib template using Paths = std::vector>; + template + Path& operator<<(Path& poly, const Point& p) + { + poly.emplace_back(p); + return poly; + } + + template + Paths& operator<<(Paths& polys, const Path& p) + { + polys.emplace_back(p); + return polys; + } + using Path64 = Path; using PathD = Path; using Paths64 = std::vector< Path64>; @@ -685,29 +699,30 @@ namespace Clipper2Lib return (x > 0) - (x < 0); } - struct MultiplyUInt64Result + struct UInt128Struct { - const uint64_t result = 0; - const uint64_t carry = 0; + const uint64_t lo = 0; + const uint64_t hi = 0; - bool operator==(const MultiplyUInt64Result& other) const + bool operator==(const UInt128Struct& other) const { - return result == other.result && carry == other.carry; + return lo == other.lo && hi == other.hi; }; }; - inline MultiplyUInt64Result Multiply(uint64_t a, uint64_t b) // #834, #835 + inline UInt128Struct Multiply(uint64_t a, uint64_t b) // #834, #835 { + // note to self - lamba expressions follow const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; }; const auto hi = [](uint64_t x) { return x >> 32; }; const uint64_t x1 = lo(a) * lo(b); const uint64_t x2 = hi(a) * lo(b) + hi(x1); const uint64_t x3 = lo(a) * hi(b) + lo(x2); - const uint64_t result = lo(x3) << 32 | lo(x1); - const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3); + const uint64_t lobits = lo(x3) << 32 | lo(x1); + const uint64_t hibits = hi(a) * hi(b) + hi(x2) + hi(x3); - return { result, carry }; + return { lobits, hibits }; } // returns true if (and only if) a * b == c * d @@ -724,14 +739,56 @@ namespace Clipper2Lib const auto abs_c = static_cast(std::abs(c)); const auto abs_d = static_cast(std::abs(d)); - const auto abs_ab = Multiply(abs_a, abs_b); - const auto abs_cd = Multiply(abs_c, abs_d); + const auto ab = Multiply(abs_a, abs_b); + const auto cd = Multiply(abs_c, abs_d); // nb: it's important to differentiate 0 values here from other values const auto sign_ab = TriSign(a) * TriSign(b); const auto sign_cd = TriSign(c) * TriSign(d); - return abs_ab == abs_cd && sign_ab == sign_cd; + return ab == cd && sign_ab == sign_cd; +#endif + } + + template + inline int CrossProductSign(const Point& pt1, const Point& pt2, const Point& pt3) + { + const auto a = pt2.x - pt1.x; + const auto b = pt3.y - pt2.y; + const auto c = pt2.y - pt1.y; + const auto d = pt3.x - pt2.x; + +#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX + const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b); + const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d); + if (ab > cd) return 1; + else if (ab < cd) return -1; + else return 0; +#else + // nb: unsigned values needed for calculating carry into 'hi' + const auto abs_a = static_cast(std::abs(a)); + const auto abs_b = static_cast(std::abs(b)); + const auto abs_c = static_cast(std::abs(c)); + const auto abs_d = static_cast(std::abs(d)); + + const auto ab = Multiply(abs_a, abs_b); + const auto cd = Multiply(abs_c, abs_d); + + const auto sign_ab = TriSign(a) * TriSign(b); + const auto sign_cd = TriSign(c) * TriSign(d); + + if (sign_ab == sign_cd) + { + int result; + if (ab.hi == cd.hi) + { + if (ab.lo == cd.lo) return 0; + result = (ab.lo > cd.lo) ? 1 : -1; + } + else result = (ab.hi > cd.hi) ? 1 : -1; + return (sign_ab > 0) ? result : -result; + } + return (sign_ab > sign_cd) ? 1 : -1; #endif } diff --git a/3rdparty/clipper2/include/clipper2/clipper.h b/3rdparty/clipper2/include/clipper2/clipper.h index b75bbd35323..701d1fac863 100644 --- a/3rdparty/clipper2/include/clipper2/clipper.h +++ b/3rdparty/clipper2/include/clipper2/clipper.h @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 27 April 2024 * +* Date : 5 March 2025 * * Website : https://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Copyright : Angus Johnson 2010-2025 * * Purpose : This module provides a simple interface to the Clipper Library * * License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -150,7 +150,7 @@ namespace Clipper2Lib { if (!delta) return paths; if (error_code) return PathsD(); const double scale = std::pow(10, precision); - ClipperOffset clip_offset(miter_limit, arc_tolerance); + ClipperOffset clip_offset(miter_limit, arc_tolerance * scale); clip_offset.AddPaths(ScalePaths(paths, scale, error_code), jt, et); if (error_code) return PathsD(); Paths64 solution; @@ -351,6 +351,29 @@ namespace Clipper2Lib { #endif } + inline size_t GetNext(size_t current, size_t high, + const std::vector& flags) + { + ++current; + while (current <= high && flags[current]) ++current; + if (current <= high) return current; + current = 0; + while (flags[current]) ++current; + return current; + } + + inline size_t GetPrior(size_t current, size_t high, + const std::vector& flags) + { + if (current == 0) current = high; + else --current; + while (current > 0 && flags[current]) --current; + if (!flags[current]) return current; + current = high; + while (flags[current]) --current; + return current; + } + } // end details namespace inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) @@ -611,29 +634,6 @@ namespace Clipper2Lib { return result; } - inline size_t GetNext(size_t current, size_t high, - const std::vector& flags) - { - ++current; - while (current <= high && flags[current]) ++current; - if (current <= high) return current; - current = 0; - while (flags[current]) ++current; - return current; - } - - inline size_t GetPrior(size_t current, size_t high, - const std::vector& flags) - { - if (current == 0) current = high; - else --current; - while (current > 0 && flags[current]) --current; - if (!flags[current]) return current; - current = high; - while (flags[current]) --current; - return current; - } - template inline Path SimplifyPath(const Path &path, double epsilon, bool isClosedPath = true) @@ -665,13 +665,13 @@ namespace Clipper2Lib { start = curr; do { - curr = GetNext(curr, high, flags); + curr = details::GetNext(curr, high, flags); } while (curr != start && distSqr[curr] > epsSqr); if (curr == start) break; } - prior = GetPrior(curr, high, flags); - next = GetNext(curr, high, flags); + prior = details::GetPrior(curr, high, flags); + next = details::GetNext(curr, high, flags); if (next == prior) break; // flag for removal the smaller of adjacent 'distances' @@ -680,14 +680,14 @@ namespace Clipper2Lib { prior2 = prior; prior = curr; curr = next; - next = GetNext(next, high, flags); + next = details::GetNext(next, high, flags); } else - prior2 = GetPrior(prior, high, flags); + prior2 = details::GetPrior(prior, high, flags); flags[curr] = true; curr = next; - next = GetNext(next, high, flags); + next = details::GetNext(next, high, flags); if (isClosedPath || ((curr != high) && (curr != 0))) distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); @@ -712,6 +712,35 @@ namespace Clipper2Lib { return result; } + + template + inline bool Path2ContainsPath1(const Path& path1, const Path& path2) + { + // precondition: paths must not intersect, except for + // transient (and presumed 'micro') path intersections + PointInPolygonResult pip = PointInPolygonResult::IsOn; + for (const Point& pt : path1) + { + switch (PointInPolygon(pt, path2)) + { + case PointInPolygonResult::IsOutside: + if (pip == PointInPolygonResult::IsOutside) return false; + pip = PointInPolygonResult::IsOutside; + break; + case PointInPolygonResult::IsInside: + if (pip == PointInPolygonResult::IsInside) return true; + pip = PointInPolygonResult::IsInside; + break; + default: + break; + } + } + if (pip != PointInPolygonResult::IsInside) return false; + // result is likely true but check midpoint + Point mp1 = GetBounds(path1).MidPoint(); + return PointInPolygon(mp1, path2) == PointInPolygonResult::IsInside; + } + template inline void RDP(const Path path, std::size_t begin, std::size_t end, double epsSqrd, std::vector& flags) diff --git a/3rdparty/clipper2/include/clipper2/clipper.offset.h b/3rdparty/clipper2/include/clipper2/clipper.offset.h index 90ccd5ec977..560af8a3ca9 100644 --- a/3rdparty/clipper2/include/clipper2/clipper.offset.h +++ b/3rdparty/clipper2/include/clipper2/clipper.offset.h @@ -35,7 +35,7 @@ class ClipperOffset { class Group { public: Paths64 paths_in; - std::optional lowest_path_idx{}; + std::optional lowest_path_idx{}; bool is_reversed = false; JoinType join_type; EndType end_type; diff --git a/3rdparty/clipper2/include/clipper2/clipper.version.h b/3rdparty/clipper2/include/clipper2/clipper.version.h index 661b0f1c588..6bd54981134 100644 --- a/3rdparty/clipper2/include/clipper2/clipper.version.h +++ b/3rdparty/clipper2/include/clipper2/clipper.version.h @@ -1,6 +1,6 @@ #ifndef CLIPPER_VERSION_H #define CLIPPER_VERSION_H -constexpr auto CLIPPER2_VERSION = "1.5.2"; +constexpr auto CLIPPER2_VERSION = "1.5.3"; #endif // CLIPPER_VERSION_H diff --git a/3rdparty/clipper2/src/clipper.engine.cpp b/3rdparty/clipper2/src/clipper.engine.cpp index 927e7a75809..b5d5b80e5d4 100644 --- a/3rdparty/clipper2/src/clipper.engine.cpp +++ b/3rdparty/clipper2/src/clipper.engine.cpp @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 September 2024 * +* Date : 4 May 2025 * * Website : https://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Copyright : Angus Johnson 2010-2025 * * Purpose : This is the main polygon clipping module * * License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -478,8 +478,7 @@ namespace Clipper2Lib { inline void SetOwner(OutRec* outrec, OutRec* new_owner) { //precondition1: new_owner is never null - while (new_owner->owner && !new_owner->owner->pts) - new_owner->owner = new_owner->owner->owner; + new_owner->owner = GetRealOutRec(new_owner->owner); OutRec* tmp = new_owner; while (tmp && tmp != outrec) tmp = tmp->owner; if (tmp) new_owner->owner = outrec->owner; @@ -532,9 +531,9 @@ namespace Clipper2Lib { 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; + int i = CrossProductSign(op2->prev->pt, op2->pt, pt); + if (i == 0) return PointInPolygonResult::IsOn; + if ((i < 0) == is_above) val = 1 - val; } is_above = !is_above; op2 = op2->next; @@ -542,9 +541,9 @@ namespace Clipper2Lib { 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; + int i = CrossProductSign(op2->prev->pt, op2->pt, pt); + if (i == 0) return PointInPolygonResult::IsOn; + if ((i < 0) == is_above) val = 1 - val; } if (val == 0) return PointInPolygonResult::IsOutside; @@ -574,30 +573,31 @@ namespace Clipper2Lib { return result; } - inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) + inline bool Path2ContainsPath1(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; + // this function accommodates rounding errors that + // can cause path micro intersections + PointInPolygonResult pip = PointInPolygonResult::IsOn; OutPt* op = op1; - do - { - result = PointInOpPolygon(op->pt, op2); - if (result == PointInPolygonResult::IsOutside) ++outside_cnt; - else if (result == PointInPolygonResult::IsInside) --outside_cnt; + do { + switch (PointInOpPolygon(op->pt, op2)) + { + case PointInPolygonResult::IsOutside: + if (pip == PointInPolygonResult::IsOutside) return false; + pip = PointInPolygonResult::IsOutside; + break; + case PointInPolygonResult::IsInside: + if (pip == PointInPolygonResult::IsInside) return true; + pip = PointInPolygonResult::IsInside; + break; + default: break; + } 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; + } while (op != op1); + // result unclear, so try again using cleaned paths + return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973) } - //------------------------------------------------------------------------------ - //------------------------------------------------------------------------------ - void AddLocMin(LocalMinimaList& list, Vertex& vert, PathType polytype, bool is_open) { @@ -1122,21 +1122,19 @@ namespace Clipper2Lib { return newcomer.curr_x > resident.curr_x; //get the turning direction a1.top, a2.bot, a2.top - double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); - if (d != 0) return d < 0; + int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top); + if (i != 0) return i < 0; //edges must be collinear to get here //for starting open paths, place them according to //the direction they're about to turn if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) { - return CrossProduct(newcomer.bot, - resident.top, NextVertex(resident)->pt) <= 0; + return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0); } else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) { - return CrossProduct(newcomer.bot, - newcomer.top, NextVertex(newcomer)->pt) >= 0; + return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0); } int64_t y = newcomer.bot.y; @@ -1151,7 +1149,7 @@ namespace Clipper2Lib { resident.bot, resident.top)) return true; else //compare turning direction of the alternate bound - return (CrossProduct(PrevPrevVertex(resident)->pt, + return (CrossProductSign(PrevPrevVertex(resident)->pt, newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; } @@ -1561,7 +1559,7 @@ namespace Clipper2Lib { FixSelfIntersects(outrec); } - void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp) + void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp) { // splitOp.prev -> splitOp && // splitOp.next -> splitOp.next.next are intersecting @@ -1626,7 +1624,7 @@ namespace Clipper2Lib { if (using_polytree_) { - if (Path1InsidePath2(prevOp, newOp)) + if (Path2ContainsPath1(prevOp, newOp)) { newOr->splits = new OutRecList(); newOr->splits->emplace_back(outrec); @@ -1648,19 +1646,32 @@ namespace Clipper2Lib { void ClipperBase::FixSelfIntersects(OutRec* outrec) { OutPt* op2 = outrec->pts; + if (op2->prev == op2->next->next) + return; // because triangles can't self-intersect for (; ; ) { - // triangles can't self-intersect - if (op2->prev == op2->next->next) break; if (SegmentsIntersect(op2->prev->pt, op2->pt, op2->next->pt, op2->next->next->pt)) { - if (op2 == outrec->pts || op2->next == outrec->pts) - outrec->pts = outrec->pts->prev; - DoSplitOp(outrec, op2); - if (!outrec->pts) break; - op2 = outrec->pts; - continue; + if (SegmentsIntersect(op2->prev->pt, + op2->pt, op2->next->next->pt, op2->next->next->next->pt)) + { + // adjacent intersections (ie a micro self-intersections) + op2 = DuplicateOp(op2, false); + op2->pt = op2->next->next->next->pt; + op2 = op2->next; + } + else + { + if (op2 == outrec->pts || op2->next == outrec->pts) + outrec->pts = outrec->pts->prev; + DoSplitOp(outrec, op2); + if (!outrec->pts) break; + op2 = outrec->pts; + if (op2->prev == op2->next->next) + break; // again, because triangles can't self-intersect + continue; + } } else op2 = op2->next; @@ -2258,7 +2269,6 @@ namespace Clipper2Lib { void MoveSplits(OutRec* fromOr, OutRec* toOr) { - if (!fromOr->splits) return; if (!toOr->splits) toOr->splits = new OutRecList(); OutRecList::iterator orIter = fromOr->splits->begin(); for (; orIter != fromOr->splits->end(); ++orIter) @@ -2266,7 +2276,6 @@ namespace Clipper2Lib { fromOr->splits->clear(); } - void ClipperBase::ProcessHorzJoins() { for (const HorzJoin& j : horz_join_list_) @@ -2295,8 +2304,8 @@ namespace Clipper2Lib { } if (using_polytree_) //#498, #520, #584, D#576, #618 - { - if (Path1InsidePath2(or1->pts, or2->pts)) + { + if (Path2ContainsPath1(or1->pts, or2->pts)) { //swap or1's & or2's pts OutPt* tmp = or1->pts; @@ -2307,7 +2316,7 @@ namespace Clipper2Lib { //or2 is now inside or1 or2->owner = or1; } - else if (Path1InsidePath2(or2->pts, or1->pts)) + else if (Path2ContainsPath1(or2->pts, or1->pts)) { or2->owner = or1; } @@ -2320,13 +2329,14 @@ namespace Clipper2Lib { else or2->owner = or1; } - else + else // joining, not splitting { or2->pts = nullptr; if (using_polytree_) { SetOwner(or2, or1); - MoveSplits(or2, or1); //#618 + if (or2->splits) + MoveSplits(or2, or1); //#618 } else or2->owner = or1; @@ -2932,20 +2942,24 @@ namespace Clipper2Lib { { for (auto split : *splits) { + if (!split->pts && split->splits && + CheckSplitOwner(outrec, split->splits)) return true; //#942 split = GetRealOutRec(split); - if(!split || split == outrec || split->recursive_split == outrec) continue; + if (!split || split == outrec || split->recursive_split == outrec) continue; split->recursive_split = outrec; // prevent infinite loops if (split->splits && CheckSplitOwner(outrec, split->splits)) - return true; - else if (CheckBounds(split) && - IsValidOwner(outrec, split) && - split->bounds.Contains(outrec->bounds) && - Path1InsidePath2(outrec->pts, split->pts)) - { - outrec->owner = split; //found in split - return true; - } + return true; + + if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) || + !Path2ContainsPath1(outrec->pts, split->pts)) continue; + + if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957) + split->owner = outrec->owner; + + outrec->owner = split; + return true; + } return false; } @@ -2962,7 +2976,7 @@ namespace Clipper2Lib { if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break; if (outrec->owner->pts && CheckBounds(outrec->owner) && outrec->owner->bounds.Contains(outrec->bounds) && - Path1InsidePath2(outrec->pts, outrec->owner->pts)) break; + Path2ContainsPath1(outrec->pts, outrec->owner->pts)) break; outrec->owner = outrec->owner->owner; } @@ -3025,6 +3039,7 @@ namespace Clipper2Lib { { OutRec* outrec = outrec_list_[i]; if (!outrec || !outrec->pts) continue; + if (outrec->is_open) { Path64 path; diff --git a/3rdparty/clipper2/src/clipper.offset.cpp b/3rdparty/clipper2/src/clipper.offset.cpp index 23e405ed1d5..72b43f17930 100644 --- a/3rdparty/clipper2/src/clipper.offset.cpp +++ b/3rdparty/clipper2/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 January 2025 * +* Date : 4 May 2025 * * Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2025 * * Purpose : Path Offset (Inflate/Shrink) * @@ -33,22 +33,28 @@ const double arc_const = 0.002; // <-- 1/500 // Miscellaneous methods //------------------------------------------------------------------------------ -std::optional GetLowestClosedPathIdx(const Paths64& paths) +void GetLowestClosedPathInfo(const Paths64& paths, std::optional& idx, bool& is_neg_area) { - std::optional result; + idx.reset(); Point64 botPt = Point64(INT64_MAX, INT64_MIN); for (size_t i = 0; i < paths.size(); ++i) { + double a = MAX_DBL; for (const Point64& pt : paths[i]) { - if ((pt.y < botPt.y) || + if ((pt.y < botPt.y) || ((pt.y == botPt.y) && (pt.x >= botPt.x))) continue; - result = i; + if (a == MAX_DBL) + { + a = Area(paths[i]); + if (a == 0) break; // invalid closed path, so break from inner loop + is_neg_area = a < 0; + } + idx = i; botPt.x = pt.x; botPt.y = pt.y; } } - return result; } inline double Hypot(double x, double y) @@ -141,15 +147,16 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType if (end_type == EndType::Polygon) { - lowest_path_idx = GetLowestClosedPathIdx(paths_in); + bool is_neg_area; + GetLowestClosedPathInfo(paths_in, lowest_path_idx, is_neg_area); // the lowermost path must be an outer path, so if its orientation is negative, // then flag the whole group is 'reversed' (will negate delta etc.) // as this is much more efficient than reversing every path. - is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0; + is_reversed = lowest_path_idx.has_value() && is_neg_area; } else { - lowest_path_idx = std::nullopt; + lowest_path_idx.reset(); is_reversed = false; } } @@ -597,10 +604,10 @@ void ClipperOffset::ExecuteInternal(double delta) if (!solution->size()) return; - bool paths_reversed = CheckReverseOrientation(); + bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; - c.PreserveCollinear(false); + c.PreserveCollinear(preserve_collinear_); //the solution should retain the orientation of the input c.ReverseSolution(reverse_solution_ != paths_reversed); #ifdef USINGZ