1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

clipper2: Update to 1.5.3

This commit is contained in:
Jakub Marcowski
2025-06-01 20:37:06 +02:00
parent 64b09905c7
commit 256fa39e51
8 changed files with 238 additions and 165 deletions

View File

@@ -120,7 +120,7 @@ Files extracted from upstream source:
## clipper2 ## clipper2
- Upstream: https://github.com/AngusJohnson/Clipper2 - Upstream: https://github.com/AngusJohnson/Clipper2
- Version: 1.5.2 (6901921c4be75126d1de60bfd24bd86a61319fd0, 2025) - Version: 1.5.3 (fa165fe8364b7d0e5d5db2182369b8c82348f4ea, 2025)
- License: BSL 1.0 - License: BSL 1.0
Files extracted from upstream source: Files extracted from upstream source:
@@ -131,7 +131,6 @@ Files extracted from upstream source:
Patches: Patches:
- `0001-disable-exceptions.patch` (GH-80796) - `0001-disable-exceptions.patch` (GH-80796)
- `0002-llvm-disable-int128-math.patch` (GH-95964)
## cvtt ## cvtt

View File

@@ -1,8 +1,8 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 12 May 2024 * * Date : 24 March 2025 *
* Website : https://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : Core Clipper Library structures and functions * * Purpose : Core Clipper Library structures and functions *
* License : https://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
@@ -250,6 +250,20 @@ namespace Clipper2Lib
template <typename T> template <typename T>
using Paths = std::vector<Path<T>>; using Paths = std::vector<Path<T>>;
template <typename T, typename T2=T>
Path<T>& operator<<(Path<T>& poly, const Point<T2>& p)
{
poly.emplace_back(p);
return poly;
}
template <typename T>
Paths<T>& operator<<(Paths<T>& polys, const Path<T>& p)
{
polys.emplace_back(p);
return polys;
}
using Path64 = Path<int64_t>; using Path64 = Path<int64_t>;
using PathD = Path<double>; using PathD = Path<double>;
using Paths64 = std::vector< Path64>; using Paths64 = std::vector< Path64>;
@@ -687,56 +701,97 @@ namespace Clipper2Lib
return (x > 0) - (x < 0); return (x > 0) - (x < 0);
} }
struct MultiplyUInt64Result struct UInt128Struct
{ {
const uint64_t result = 0; const uint64_t lo = 0;
const uint64_t carry = 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 lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
const auto hi = [](uint64_t x) { return x >> 32; }; const auto hi = [](uint64_t x) { return x >> 32; };
const uint64_t x1 = lo(a) * lo(b); const uint64_t x1 = lo(a) * lo(b);
const uint64_t x2 = hi(a) * lo(b) + hi(x1); const uint64_t x2 = hi(a) * lo(b) + hi(x1);
const uint64_t x3 = lo(a) * hi(b) + lo(x2); const uint64_t x3 = lo(a) * hi(b) + lo(x2);
const uint64_t result = lo(x3) << 32 | lo(x1); const uint64_t lobits = lo(x3) << 32 | lo(x1);
const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3); 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 // returns true if (and only if) a * b == c * d
inline bool ProductsAreEqual(int64_t a, int64_t b, int64_t c, int64_t d) inline bool ProductsAreEqual(int64_t a, int64_t b, int64_t c, int64_t d)
{ {
// Work around LLVM issue: https://github.com/llvm/llvm-project/issues/16778 #if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX
// Details: https://github.com/godotengine/godot/pull/95964#issuecomment-2306581804 const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b);
// #if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d);
// const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b); return ab == cd;
// const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d); #else
// return ab == cd;
// #else
// nb: unsigned values needed for calculating overflow carry // nb: unsigned values needed for calculating overflow carry
const auto abs_a = static_cast<uint64_t>(std::abs(a)); const auto abs_a = static_cast<uint64_t>(std::abs(a));
const auto abs_b = static_cast<uint64_t>(std::abs(b)); const auto abs_b = static_cast<uint64_t>(std::abs(b));
const auto abs_c = static_cast<uint64_t>(std::abs(c)); const auto abs_c = static_cast<uint64_t>(std::abs(c));
const auto abs_d = static_cast<uint64_t>(std::abs(d)); const auto abs_d = static_cast<uint64_t>(std::abs(d));
const auto abs_ab = Multiply(abs_a, abs_b); const auto ab = Multiply(abs_a, abs_b);
const auto abs_cd = Multiply(abs_c, abs_d); const auto cd = Multiply(abs_c, abs_d);
// nb: it's important to differentiate 0 values here from other values // nb: it's important to differentiate 0 values here from other values
const auto sign_ab = TriSign(a) * TriSign(b); const auto sign_ab = TriSign(a) * TriSign(b);
const auto sign_cd = TriSign(c) * TriSign(d); 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 #endif
}
template <typename T>
inline int CrossProductSign(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& 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<uint64_t>(std::abs(a));
const auto abs_b = static_cast<uint64_t>(std::abs(b));
const auto abs_c = static_cast<uint64_t>(std::abs(c));
const auto abs_d = static_cast<uint64_t>(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
} }
template <typename T> template <typename T>

View File

@@ -1,8 +1,8 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 27 April 2024 * * Date : 5 March 2025 *
* Website : https://www.angusj.com * * 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 * * Purpose : This module provides a simple interface to the Clipper Library *
* License : https://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
@@ -150,7 +150,7 @@ namespace Clipper2Lib {
if (!delta) return paths; if (!delta) return paths;
if (error_code) return PathsD(); if (error_code) return PathsD();
const double scale = std::pow(10, precision); 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<int64_t,double>(paths, scale, error_code), jt, et); clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
if (error_code) return PathsD(); if (error_code) return PathsD();
Paths64 solution; Paths64 solution;
@@ -351,6 +351,29 @@ namespace Clipper2Lib {
#endif #endif
} }
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& 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<bool>& 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 } // end details namespace
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
@@ -611,29 +634,6 @@ namespace Clipper2Lib {
return result; return result;
} }
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& 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<bool>& 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 <typename T> template <typename T>
inline Path<T> SimplifyPath(const Path<T> &path, inline Path<T> SimplifyPath(const Path<T> &path,
double epsilon, bool isClosedPath = true) double epsilon, bool isClosedPath = true)
@@ -665,13 +665,13 @@ namespace Clipper2Lib {
start = curr; start = curr;
do do
{ {
curr = GetNext(curr, high, flags); curr = details::GetNext(curr, high, flags);
} while (curr != start && distSqr[curr] > epsSqr); } while (curr != start && distSqr[curr] > epsSqr);
if (curr == start) break; if (curr == start) break;
} }
prior = GetPrior(curr, high, flags); prior = details::GetPrior(curr, high, flags);
next = GetNext(curr, high, flags); next = details::GetNext(curr, high, flags);
if (next == prior) break; if (next == prior) break;
// flag for removal the smaller of adjacent 'distances' // flag for removal the smaller of adjacent 'distances'
@@ -680,14 +680,14 @@ namespace Clipper2Lib {
prior2 = prior; prior2 = prior;
prior = curr; prior = curr;
curr = next; curr = next;
next = GetNext(next, high, flags); next = details::GetNext(next, high, flags);
} }
else else
prior2 = GetPrior(prior, high, flags); prior2 = details::GetPrior(prior, high, flags);
flags[curr] = true; flags[curr] = true;
curr = next; curr = next;
next = GetNext(next, high, flags); next = details::GetNext(next, high, flags);
if (isClosedPath || ((curr != high) && (curr != 0))) if (isClosedPath || ((curr != high) && (curr != 0)))
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
@@ -712,6 +712,35 @@ namespace Clipper2Lib {
return result; return result;
} }
template <typename T>
inline bool Path2ContainsPath1(const Path<T>& path1, const Path<T>& path2)
{
// precondition: paths must not intersect, except for
// transient (and presumed 'micro') path intersections
PointInPolygonResult pip = PointInPolygonResult::IsOn;
for (const Point<T>& 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<T> mp1 = GetBounds(path1).MidPoint();
return PointInPolygon(mp1, path2) == PointInPolygonResult::IsInside;
}
template <typename T> template <typename T>
inline void RDP(const Path<T> path, std::size_t begin, inline void RDP(const Path<T> path, std::size_t begin,
std::size_t end, double epsSqrd, std::vector<bool>& flags) std::size_t end, double epsSqrd, std::vector<bool>& flags)

View File

@@ -35,7 +35,7 @@ private:
class Group { class Group {
public: public:
Paths64 paths_in; Paths64 paths_in;
std::optional<size_t> lowest_path_idx{}; std::optional<size_t> lowest_path_idx{};
bool is_reversed = false; bool is_reversed = false;
JoinType join_type; JoinType join_type;
EndType end_type; EndType end_type;

View File

@@ -1,6 +1,6 @@
#ifndef CLIPPER_VERSION_H #ifndef CLIPPER_VERSION_H
#define 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 #endif // CLIPPER_VERSION_H

View File

@@ -1,32 +0,0 @@
diff --git a/thirdparty/clipper2/include/clipper2/clipper.core.h b/thirdparty/clipper2/include/clipper2/clipper.core.h
index 110bee4c10..aa003bf032 100644
--- a/thirdparty/clipper2/include/clipper2/clipper.core.h
+++ b/thirdparty/clipper2/include/clipper2/clipper.core.h
@@ -715,11 +715,13 @@ namespace Clipper2Lib
// returns true if (and only if) a * b == c * d
inline bool ProductsAreEqual(int64_t a, int64_t b, int64_t c, int64_t d)
{
-#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);
- return ab == cd;
-#else
+// Work around LLVM issue: https://github.com/llvm/llvm-project/issues/16778
+// Details: https://github.com/godotengine/godot/pull/95964#issuecomment-2306581804
+// #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);
+// return ab == cd;
+// #else
// nb: unsigned values needed for calculating overflow carry
const auto abs_a = static_cast<uint64_t>(std::abs(a));
const auto abs_b = static_cast<uint64_t>(std::abs(b));
@@ -734,7 +736,7 @@ namespace Clipper2Lib
const auto sign_cd = TriSign(c) * TriSign(d);
return abs_ab == abs_cd && sign_ab == sign_cd;
-#endif
+// #endif
}
template <typename T>

View File

@@ -1,8 +1,8 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 17 September 2024 * * Date : 4 May 2025 *
* Website : https://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : This is the main polygon clipping module * * Purpose : This is the main polygon clipping module *
* License : https://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
@@ -478,8 +478,7 @@ namespace Clipper2Lib {
inline void SetOwner(OutRec* outrec, OutRec* new_owner) inline void SetOwner(OutRec* outrec, OutRec* new_owner)
{ {
//precondition1: new_owner is never null //precondition1: new_owner is never null
while (new_owner->owner && !new_owner->owner->pts) new_owner->owner = GetRealOutRec(new_owner->owner);
new_owner->owner = new_owner->owner->owner;
OutRec* tmp = new_owner; OutRec* tmp = new_owner;
while (tmp && tmp != outrec) tmp = tmp->owner; while (tmp && tmp != outrec) tmp = tmp->owner;
if (tmp) new_owner->owner = outrec->owner; if (tmp) new_owner->owner = outrec->owner;
@@ -532,9 +531,9 @@ namespace Clipper2Lib {
val = 1 - val; // toggle val val = 1 - val; // toggle val
else else
{ {
double d = CrossProduct(op2->prev->pt, op2->pt, pt); int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
if (d == 0) return PointInPolygonResult::IsOn; if (i == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val; if ((i < 0) == is_above) val = 1 - val;
} }
is_above = !is_above; is_above = !is_above;
op2 = op2->next; op2 = op2->next;
@@ -542,9 +541,9 @@ namespace Clipper2Lib {
if (is_above != starting_above) if (is_above != starting_above)
{ {
double d = CrossProduct(op2->prev->pt, op2->pt, pt); int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
if (d == 0) return PointInPolygonResult::IsOn; if (i == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val; if ((i < 0) == is_above) val = 1 - val;
} }
if (val == 0) return PointInPolygonResult::IsOutside; if (val == 0) return PointInPolygonResult::IsOutside;
@@ -574,30 +573,31 @@ namespace Clipper2Lib {
return result; 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 // this function accommodates rounding errors that
// so we won't jump if the first vertex is found outside // can cause path micro intersections
PointInPolygonResult result; PointInPolygonResult pip = PointInPolygonResult::IsOn;
int outside_cnt = 0;
OutPt* op = op1; OutPt* op = op1;
do do {
{ switch (PointInOpPolygon(op->pt, op2))
result = PointInOpPolygon(op->pt, op2); {
if (result == PointInPolygonResult::IsOutside) ++outside_cnt; case PointInPolygonResult::IsOutside:
else if (result == PointInPolygonResult::IsInside) --outside_cnt; 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; op = op->next;
} while (op != op1 && std::abs(outside_cnt) < 2); } while (op != op1);
if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); // result unclear, so try again using cleaned paths
// since path1's location is still equivocal, check its midpoint return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973)
Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint();
Path64 path2 = GetCleanPath(op2);
return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside;
} }
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void AddLocMin(LocalMinimaList& list, void AddLocMin(LocalMinimaList& list,
Vertex& vert, PathType polytype, bool is_open) Vertex& vert, PathType polytype, bool is_open)
{ {
@@ -1122,21 +1122,19 @@ namespace Clipper2Lib {
return newcomer.curr_x > resident.curr_x; return newcomer.curr_x > resident.curr_x;
//get the turning direction a1.top, a2.bot, a2.top //get the turning direction a1.top, a2.bot, a2.top
double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top);
if (d != 0) return d < 0; if (i != 0) return i < 0;
//edges must be collinear to get here //edges must be collinear to get here
//for starting open paths, place them according to //for starting open paths, place them according to
//the direction they're about to turn //the direction they're about to turn
if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))
{ {
return CrossProduct(newcomer.bot, return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0);
resident.top, NextVertex(resident)->pt) <= 0;
} }
else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))
{ {
return CrossProduct(newcomer.bot, return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0);
newcomer.top, NextVertex(newcomer)->pt) >= 0;
} }
int64_t y = newcomer.bot.y; int64_t y = newcomer.bot.y;
@@ -1151,7 +1149,7 @@ namespace Clipper2Lib {
resident.bot, resident.top)) return true; resident.bot, resident.top)) return true;
else else
//compare turning direction of the alternate bound //compare turning direction of the alternate bound
return (CrossProduct(PrevPrevVertex(resident)->pt, return (CrossProductSign(PrevPrevVertex(resident)->pt,
newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;
} }
@@ -1561,7 +1559,7 @@ namespace Clipper2Lib {
FixSelfIntersects(outrec); FixSelfIntersects(outrec);
} }
void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp) void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp)
{ {
// splitOp.prev -> splitOp && // splitOp.prev -> splitOp &&
// splitOp.next -> splitOp.next.next are intersecting // splitOp.next -> splitOp.next.next are intersecting
@@ -1626,7 +1624,7 @@ namespace Clipper2Lib {
if (using_polytree_) if (using_polytree_)
{ {
if (Path1InsidePath2(prevOp, newOp)) if (Path2ContainsPath1(prevOp, newOp))
{ {
newOr->splits = new OutRecList(); newOr->splits = new OutRecList();
newOr->splits->emplace_back(outrec); newOr->splits->emplace_back(outrec);
@@ -1648,19 +1646,32 @@ namespace Clipper2Lib {
void ClipperBase::FixSelfIntersects(OutRec* outrec) void ClipperBase::FixSelfIntersects(OutRec* outrec)
{ {
OutPt* op2 = outrec->pts; OutPt* op2 = outrec->pts;
if (op2->prev == op2->next->next)
return; // because triangles can't self-intersect
for (; ; ) for (; ; )
{ {
// triangles can't self-intersect
if (op2->prev == op2->next->next) break;
if (SegmentsIntersect(op2->prev->pt, if (SegmentsIntersect(op2->prev->pt,
op2->pt, op2->next->pt, op2->next->next->pt)) op2->pt, op2->next->pt, op2->next->next->pt))
{ {
if (op2 == outrec->pts || op2->next == outrec->pts) if (SegmentsIntersect(op2->prev->pt,
outrec->pts = outrec->pts->prev; op2->pt, op2->next->next->pt, op2->next->next->next->pt))
DoSplitOp(outrec, op2); {
if (!outrec->pts) break; // adjacent intersections (ie a micro self-intersections)
op2 = outrec->pts; op2 = DuplicateOp(op2, false);
continue; 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 else
op2 = op2->next; op2 = op2->next;
@@ -2258,7 +2269,6 @@ namespace Clipper2Lib {
void MoveSplits(OutRec* fromOr, OutRec* toOr) void MoveSplits(OutRec* fromOr, OutRec* toOr)
{ {
if (!fromOr->splits) return;
if (!toOr->splits) toOr->splits = new OutRecList(); if (!toOr->splits) toOr->splits = new OutRecList();
OutRecList::iterator orIter = fromOr->splits->begin(); OutRecList::iterator orIter = fromOr->splits->begin();
for (; orIter != fromOr->splits->end(); ++orIter) for (; orIter != fromOr->splits->end(); ++orIter)
@@ -2266,7 +2276,6 @@ namespace Clipper2Lib {
fromOr->splits->clear(); fromOr->splits->clear();
} }
void ClipperBase::ProcessHorzJoins() void ClipperBase::ProcessHorzJoins()
{ {
for (const HorzJoin& j : horz_join_list_) for (const HorzJoin& j : horz_join_list_)
@@ -2295,8 +2304,8 @@ namespace Clipper2Lib {
} }
if (using_polytree_) //#498, #520, #584, D#576, #618 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 //swap or1's & or2's pts
OutPt* tmp = or1->pts; OutPt* tmp = or1->pts;
@@ -2307,7 +2316,7 @@ namespace Clipper2Lib {
//or2 is now inside or1 //or2 is now inside or1
or2->owner = or1; or2->owner = or1;
} }
else if (Path1InsidePath2(or2->pts, or1->pts)) else if (Path2ContainsPath1(or2->pts, or1->pts))
{ {
or2->owner = or1; or2->owner = or1;
} }
@@ -2320,13 +2329,14 @@ namespace Clipper2Lib {
else else
or2->owner = or1; or2->owner = or1;
} }
else else // joining, not splitting
{ {
or2->pts = nullptr; or2->pts = nullptr;
if (using_polytree_) if (using_polytree_)
{ {
SetOwner(or2, or1); SetOwner(or2, or1);
MoveSplits(or2, or1); //#618 if (or2->splits)
MoveSplits(or2, or1); //#618
} }
else else
or2->owner = or1; or2->owner = or1;
@@ -2932,20 +2942,24 @@ namespace Clipper2Lib {
{ {
for (auto split : *splits) for (auto split : *splits)
{ {
if (!split->pts && split->splits &&
CheckSplitOwner(outrec, split->splits)) return true; //#942
split = GetRealOutRec(split); 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 split->recursive_split = outrec; // prevent infinite loops
if (split->splits && CheckSplitOwner(outrec, split->splits)) if (split->splits && CheckSplitOwner(outrec, split->splits))
return true; return true;
else if (CheckBounds(split) &&
IsValidOwner(outrec, split) && if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) ||
split->bounds.Contains(outrec->bounds) && !Path2ContainsPath1(outrec->pts, split->pts)) continue;
Path1InsidePath2(outrec->pts, split->pts))
{ if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957)
outrec->owner = split; //found in split split->owner = outrec->owner;
return true;
} outrec->owner = split;
return true;
} }
return false; return false;
} }
@@ -2962,7 +2976,7 @@ namespace Clipper2Lib {
if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break; if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;
if (outrec->owner->pts && CheckBounds(outrec->owner) && if (outrec->owner->pts && CheckBounds(outrec->owner) &&
outrec->owner->bounds.Contains(outrec->bounds) && 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; outrec->owner = outrec->owner->owner;
} }
@@ -3025,6 +3039,7 @@ namespace Clipper2Lib {
{ {
OutRec* outrec = outrec_list_[i]; OutRec* outrec = outrec_list_[i];
if (!outrec || !outrec->pts) continue; if (!outrec || !outrec->pts) continue;
if (outrec->is_open) if (outrec->is_open)
{ {
Path64 path; Path64 path;

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 22 January 2025 * * Date : 4 May 2025 *
* Website : https://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2025 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : Path Offset (Inflate/Shrink) * * Purpose : Path Offset (Inflate/Shrink) *
@@ -33,22 +33,28 @@ const double arc_const = 0.002; // <-- 1/500
// Miscellaneous methods // Miscellaneous methods
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths) void GetLowestClosedPathInfo(const Paths64& paths, std::optional<size_t>& idx, bool& is_neg_area)
{ {
std::optional<size_t> result; idx.reset();
Point64 botPt = Point64(INT64_MAX, INT64_MIN); Point64 botPt = Point64(INT64_MAX, INT64_MIN);
for (size_t i = 0; i < paths.size(); ++i) for (size_t i = 0; i < paths.size(); ++i)
{ {
double a = MAX_DBL;
for (const Point64& pt : paths[i]) 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; ((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.x = pt.x;
botPt.y = pt.y; botPt.y = pt.y;
} }
} }
return result;
} }
inline double Hypot(double x, double y) 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) 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, // 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.) // then flag the whole group is 'reversed' (will negate delta etc.)
// as this is much more efficient than reversing every path. // 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 else
{ {
lowest_path_idx = std::nullopt; lowest_path_idx.reset();
is_reversed = false; is_reversed = false;
} }
} }
@@ -597,10 +604,10 @@ void ClipperOffset::ExecuteInternal(double delta)
if (!solution->size()) return; if (!solution->size()) return;
bool paths_reversed = CheckReverseOrientation(); bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ... //clean up self-intersections ...
Clipper64 c; Clipper64 c;
c.PreserveCollinear(false); c.PreserveCollinear(preserve_collinear_);
//the solution should retain the orientation of the input //the solution should retain the orientation of the input
c.ReverseSolution(reverse_solution_ != paths_reversed); c.ReverseSolution(reverse_solution_ != paths_reversed);
#ifdef USINGZ #ifdef USINGZ