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

clipper2: Update to 1.4.0

This commit is contained in:
Jakub Marcowski
2024-08-22 18:23:26 +02:00
parent d09d82d433
commit 1bd52fed76
16 changed files with 872 additions and 617 deletions

View File

@@ -186,7 +186,7 @@ License: MPL-2.0
Files: ./thirdparty/clipper2/
Comment: Clipper2
Copyright: 2010-2023, Angus Johnson
Copyright: 2010-2024, Angus Johnson
License: BSL-1.0
Files: ./thirdparty/cvtt/

View File

@@ -111,7 +111,7 @@ Files extracted from upstream source:
## clipper2
- Upstream: https://github.com/AngusJohnson/Clipper2
- Version: 1.3.0 (98db5662e8dd1808a5a7b50c5605a2289bb390e8, 2023)
- Version: 1.4.0 (736ddb0b53d97fd5f65dd3d9bbf8a0993eaf387c, 2024)
- License: BSL 1.0
Files extracted from upstream source:

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 24 November 2023 *
* Date : 12 May 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : Core Clipper Library structures and functions *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -19,6 +19,7 @@
#include <algorithm>
#include <climits>
#include <numeric>
#include <optional>
#include "clipper2/clipper.version.h"
#define CLIPPER2_THROW(exception) std::abort()
@@ -60,10 +61,10 @@ namespace Clipper2Lib
static const double PI = 3.141592653589793238;
#endif
#ifdef CLIPPER2_MAX_PRECISION
const int MAX_DECIMAL_PRECISION = CLIPPER2_MAX_PRECISION;
#ifdef CLIPPER2_MAX_DECIMAL_PRECISION
const int CLIPPER2_MAX_DEC_PRECISION = CLIPPER2_MAX_DECIMAL_PRECISION;
#else
const int MAX_DECIMAL_PRECISION = 8; // see Discussions #564
const int CLIPPER2_MAX_DEC_PRECISION = 8; // see Discussions #564
#endif
static const int64_t MAX_COORD = INT64_MAX >> 2;
@@ -74,7 +75,7 @@ namespace Clipper2Lib
static const double MAX_DBL = (std::numeric_limits<double>::max)();
static void DoError(int error_code)
static void DoError([[maybe_unused]] int error_code)
{
#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS)
switch (error_code)
@@ -95,6 +96,13 @@ namespace Clipper2Lib
#endif
}
// can we call std::round on T? (default false) (#824)
template <typename T, typename = void>
struct is_round_invocable : std::false_type {};
template <typename T>
struct is_round_invocable<T, std::void_t<decltype(std::round(std::declval<T>()))>> : std::true_type {};
//By far the most widely used filling rules for polygons are EvenOdd
//and NonZero, sometimes called Alternate and Winding respectively.
@@ -113,8 +121,8 @@ namespace Clipper2Lib
template <typename T2>
inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0)
{
if constexpr (std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T2>::is_integer)
if constexpr (std::is_integral_v<T> &&
is_round_invocable<T2>::value && !std::is_integral_v<T2>)
{
x = static_cast<T>(std::round(x_));
y = static_cast<T>(std::round(y_));
@@ -143,6 +151,12 @@ namespace Clipper2Lib
Init(p.x, p.y, p.z);
}
template <typename T2>
explicit Point(const Point<T2>& p, int64_t z_)
{
Init(p.x, p.y, z_);
}
Point operator * (const double scale) const
{
return Point(x * scale, y * scale, z);
@@ -161,8 +175,8 @@ namespace Clipper2Lib
template <typename T2>
inline void Init(const T2 x_ = 0, const T2 y_ = 0)
{
if constexpr (std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T2>::is_integer)
if constexpr (std::is_integral_v<T> &&
is_round_invocable<T2>::value && !std::is_integral_v<T2>)
{
x = static_cast<T>(std::round(x_));
y = static_cast<T>(std::round(y_));
@@ -244,6 +258,14 @@ namespace Clipper2Lib
(std::numeric_limits<double>::max)(),
(std::numeric_limits<double>::max)());
template<typename T>
static inline Point<T> MidPoint(const Point<T>& p1, const Point<T>& p2)
{
Point<T> result;
result.x = (p1.x + p2.x) / 2;
result.y = (p1.y + p2.y) / 2;
return result;
}
// Rect ------------------------------------------------------------------------
@@ -275,10 +297,19 @@ namespace Clipper2Lib
else
{
left = top = (std::numeric_limits<T>::max)();
right = bottom = (std::numeric_limits<T>::lowest)();
right = bottom = std::numeric_limits<T>::lowest();
}
}
static Rect<T> InvalidRect()
{
return {
(std::numeric_limits<T>::max)(),
(std::numeric_limits<T>::max)(),
std::numeric_limits<T>::lowest(),
std::numeric_limits<T>::lowest() };
}
bool IsValid() const { return left != (std::numeric_limits<T>::max)(); }
T Width() const { return right - left; }
@@ -344,8 +375,8 @@ namespace Clipper2Lib
{
Rect<T1> result;
if constexpr (std::numeric_limits<T1>::is_integer &&
!std::numeric_limits<T2>::is_integer)
if constexpr (std::is_integral_v<T1> &&
is_round_invocable<T2>::value && !std::is_integral_v<T2>)
{
result.left = static_cast<T1>(std::round(rect.left * scale));
result.top = static_cast<T1>(std::round(rect.top * scale));
@@ -354,32 +385,24 @@ namespace Clipper2Lib
}
else
{
result.left = rect.left * scale;
result.top = rect.top * scale;
result.right = rect.right * scale;
result.bottom = rect.bottom * scale;
result.left = static_cast<T1>(rect.left * scale);
result.top = static_cast<T1>(rect.top * scale);
result.right = static_cast<T1>(rect.right * scale);
result.bottom = static_cast<T1>(rect.bottom * scale);
}
return result;
}
static const Rect64 InvalidRect64 = Rect64(
(std::numeric_limits<int64_t>::max)(),
(std::numeric_limits<int64_t>::max)(),
(std::numeric_limits<int64_t>::lowest)(),
(std::numeric_limits<int64_t>::lowest)());
static const RectD InvalidRectD = RectD(
(std::numeric_limits<double>::max)(),
(std::numeric_limits<double>::max)(),
(std::numeric_limits<double>::lowest)(),
(std::numeric_limits<double>::lowest)());
static const Rect64 InvalidRect64 = Rect64::InvalidRect();
static const RectD InvalidRectD = RectD::InvalidRect();
template <typename T>
Rect<T> GetBounds(const Path<T>& path)
{
auto xmin = (std::numeric_limits<T>::max)();
auto ymin = (std::numeric_limits<T>::max)();
auto xmax = std::numeric_limits<T>::lowest();
auto ymax = std::numeric_limits<T>::lowest();
T xmin = (std::numeric_limits<T>::max)();
T ymin = (std::numeric_limits<T>::max)();
T xmax = std::numeric_limits<T>::lowest();
T ymax = std::numeric_limits<T>::lowest();
for (const auto& p : path)
{
if (p.x < xmin) xmin = p.x;
@@ -393,10 +416,10 @@ namespace Clipper2Lib
template <typename T>
Rect<T> GetBounds(const Paths<T>& paths)
{
auto xmin = (std::numeric_limits<T>::max)();
auto ymin = (std::numeric_limits<T>::max)();
auto xmax = std::numeric_limits<T>::lowest();
auto ymax = std::numeric_limits<T>::lowest();
T xmin = (std::numeric_limits<T>::max)();
T ymin = (std::numeric_limits<T>::max)();
T xmax = std::numeric_limits<T>::lowest();
T ymax = std::numeric_limits<T>::lowest();
for (const Path<T>& path : paths)
for (const Point<T>& p : path)
{
@@ -408,6 +431,41 @@ namespace Clipper2Lib
return Rect<T>(xmin, ymin, xmax, ymax);
}
template <typename T, typename T2>
Rect<T> GetBounds(const Path<T2>& path)
{
T xmin = (std::numeric_limits<T>::max)();
T ymin = (std::numeric_limits<T>::max)();
T xmax = std::numeric_limits<T>::lowest();
T ymax = std::numeric_limits<T>::lowest();
for (const auto& p : path)
{
if (p.x < xmin) xmin = static_cast<T>(p.x);
if (p.x > xmax) xmax = static_cast<T>(p.x);
if (p.y < ymin) ymin = static_cast<T>(p.y);
if (p.y > ymax) ymax = static_cast<T>(p.y);
}
return Rect<T>(xmin, ymin, xmax, ymax);
}
template <typename T, typename T2>
Rect<T> GetBounds(const Paths<T2>& paths)
{
T xmin = (std::numeric_limits<T>::max)();
T ymin = (std::numeric_limits<T>::max)();
T xmax = std::numeric_limits<T>::lowest();
T ymax = std::numeric_limits<T>::lowest();
for (const Path<T2>& path : paths)
for (const Point<T2>& p : path)
{
if (p.x < xmin) xmin = static_cast<T>(p.x);
if (p.x > xmax) xmax = static_cast<T>(p.x);
if (p.y < ymin) ymin = static_cast<T>(p.y);
if (p.y > ymax) ymax = static_cast<T>(p.y);
}
return Rect<T>(xmin, ymin, xmax, ymax);
}
template <typename T>
std::ostream& operator << (std::ostream& outstream, const Path<T>& path)
{
@@ -470,10 +528,9 @@ namespace Clipper2Lib
{
Paths<T1> result;
if constexpr (std::numeric_limits<T1>::is_integer &&
!std::numeric_limits<T2>::is_integer)
if constexpr (std::is_integral_v<T1>)
{
RectD r = GetBounds(paths);
RectD r = GetBounds<double, T2>(paths);
if ((r.left * scale_x) < min_coord ||
(r.right * scale_x) > max_coord ||
(r.top * scale_y) < min_coord ||
@@ -590,20 +647,94 @@ namespace Clipper2Lib
// Miscellaneous ------------------------------------------------------------
inline void CheckPrecision(int& precision, int& error_code)
inline void CheckPrecisionRange(int& precision, int& error_code)
{
if (precision >= -MAX_DECIMAL_PRECISION && precision <= MAX_DECIMAL_PRECISION) return;
if (precision >= -CLIPPER2_MAX_DEC_PRECISION &&
precision <= CLIPPER2_MAX_DEC_PRECISION) return;
error_code |= precision_error_i; // non-fatal error
DoError(precision_error_i); // does nothing unless exceptions enabled
precision = precision > 0 ? MAX_DECIMAL_PRECISION : -MAX_DECIMAL_PRECISION;
DoError(precision_error_i); // does nothing when exceptions are disabled
precision = precision > 0 ? CLIPPER2_MAX_DEC_PRECISION : -CLIPPER2_MAX_DEC_PRECISION;
}
inline void CheckPrecision(int& precision)
inline void CheckPrecisionRange(int& precision)
{
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
}
inline int TriSign(int64_t x) // returns 0, 1 or -1
{
return (x > 0) - (x < 0);
}
struct MultiplyUInt64Result
{
const uint64_t result = 0;
const uint64_t carry = 0;
bool operator==(const MultiplyUInt64Result& other) const
{
return result == other.result && carry == other.carry;
};
};
inline MultiplyUInt64Result Multiply(uint64_t a, uint64_t b) // #834, #835
{
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);
return { result, carry };
}
// 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)
{
// -- GODOT start --
// #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
// -- GODOT end --
// 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));
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_ab = Multiply(abs_a, abs_b);
const auto abs_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;
// -- GODOT start --
// #endif
// -- GODOT end --
}
template <typename T>
inline bool IsCollinear(const Point<T>& pt1,
const Point<T>& sharedPt, const Point<T>& pt2) // #777
{
const auto a = sharedPt.x - pt1.x;
const auto b = pt2.y - sharedPt.y;
const auto c = sharedPt.y - pt1.y;
const auto d = pt2.x - sharedPt.x;
// When checking for collinearity with very large coordinate values
// then ProductsAreEqual is more accurate than using CrossProduct.
return ProductsAreEqual(a, b, c, d);
}
template <typename T>
inline double CrossProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.y -
@@ -635,15 +766,17 @@ namespace Clipper2Lib
}
template <typename T>
inline double DistanceFromLineSqrd(const Point<T>& pt, const Point<T>& ln1, const Point<T>& ln2)
inline double PerpendicDistFromLineSqrd(const Point<T>& pt,
const Point<T>& line1, const Point<T>& line2)
{
//perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
//see http://en.wikipedia.org/wiki/Perpendicular_distance
double A = static_cast<double>(ln1.y - ln2.y);
double B = static_cast<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);
double a = static_cast<double>(pt.x - line1.x);
double b = static_cast<double>(pt.y - line1.y);
double c = static_cast<double>(line2.x - line1.x);
double d = static_cast<double>(line2.y - line1.y);
if (c == 0 && d == 0) return 0;
return Sqr(a * d - c * b) / (c * c + d * d);
}
template <typename T>
@@ -663,7 +796,7 @@ namespace Clipper2Lib
}
if (cnt & 1)
a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
return a * 0.5;
return (a * 0.5);
}
template <typename T>
@@ -688,8 +821,65 @@ namespace Clipper2Lib
return Area<T>(poly) >= 0;
}
inline bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b,
const Point64& ln2a, const Point64& ln2b, Point64& ip)
#if CLIPPER2_HI_PRECISION
// caution: this will compromise performance
// https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253
// See also CPP/BenchMark/GetIntersectPtBenchmark.cpp
#define CC_MIN(x,y) ((x)>(y)?(y):(x))
#define CC_MAX(x,y) ((x)<(y)?(y):(x))
template<typename T>
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
{
double ln1dy = static_cast<double>(ln1b.y - ln1a.y);
double ln1dx = static_cast<double>(ln1a.x - ln1b.x);
double ln2dy = static_cast<double>(ln2b.y - ln2a.y);
double ln2dx = static_cast<double>(ln2a.x - ln2b.x);
double det = (ln2dy * ln1dx) - (ln1dy * ln2dx);
if (det == 0.0) return false;
T bb0minx = CC_MIN(ln1a.x, ln1b.x);
T bb0miny = CC_MIN(ln1a.y, ln1b.y);
T bb0maxx = CC_MAX(ln1a.x, ln1b.x);
T bb0maxy = CC_MAX(ln1a.y, ln1b.y);
T bb1minx = CC_MIN(ln2a.x, ln2b.x);
T bb1miny = CC_MIN(ln2a.y, ln2b.y);
T bb1maxx = CC_MAX(ln2a.x, ln2b.x);
T bb1maxy = CC_MAX(ln2a.y, ln2b.y);
if constexpr (std::is_integral_v<T>)
{
int64_t originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) >> 1;
int64_t originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) >> 1;
double ln0c = (ln1dy * static_cast<double>(ln1a.x - originx)) +
(ln1dx * static_cast<double>(ln1a.y - originy));
double ln1c = (ln2dy * static_cast<double>(ln2a.x - originx)) +
(ln2dx * static_cast<double>(ln2a.y - originy));
double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det;
double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det;
ip.x = originx + (T)nearbyint(hitx);
ip.y = originy + (T)nearbyint(hity);
}
else
{
double originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) / 2.0;
double originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) / 2.0;
double ln0c = (ln1dy * static_cast<double>(ln1a.x - originx)) +
(ln1dx * static_cast<double>(ln1a.y - originy));
double ln1c = (ln2dy * static_cast<double>(ln2a.x - originx)) +
(ln2dx * static_cast<double>(ln2a.y - originy));
double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det;
double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det;
ip.x = originx + static_cast<T>(hitx);
ip.y = originy + static_cast<T>(hity);
}
return true;
}
#else
template<typename T>
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
{
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
double dx1 = static_cast<double>(ln1b.x - ln1a.x);
@@ -700,15 +890,44 @@ namespace Clipper2Lib
double det = dy1 * dx2 - dy2 * dx1;
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
if (t <= 0.0) ip = ln1a;
else if (t >= 1.0) ip = ln1b;
else
{
ip.x = static_cast<int64_t>(ln1a.x + t * dx1);
ip.y = static_cast<int64_t>(ln1a.y + t * dy1);
ip.x = static_cast<T>(ln1a.x + t * dx1);
ip.y = static_cast<T>(ln1a.y + t * dy1);
}
return true;
}
#endif
template<typename T>
inline Point<T> TranslatePoint(const Point<T>& pt, double dx, double dy)
{
#ifdef USINGZ
return Point<T>(pt.x + dx, pt.y + dy, pt.z);
#else
return Point<T>(pt.x + dx, pt.y + dy);
#endif
}
template<typename T>
inline Point<T> ReflectPoint(const Point<T>& pt, const Point<T>& pivot)
{
#ifdef USINGZ
return Point<T>(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z);
#else
return Point<T>(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
#endif
}
template<typename T>
inline int GetSign(const T& val)
{
if (!val) return 0;
return (val > 0) ? 1 : -1;
}
inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
const Point64& seg2a, const Point64& seg2b, bool inclusive = false)
@@ -724,10 +943,10 @@ namespace Clipper2Lib
return (res1 || res2 || res3 || res4); // ensures not collinear
}
else {
return (CrossProduct(seg1a, seg2a, seg2b) *
CrossProduct(seg1b, seg2a, seg2b) < 0) &&
(CrossProduct(seg2a, seg1a, seg1b) *
CrossProduct(seg2b, seg1a, seg1b) < 0);
return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) *
GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) &&
(GetSign(CrossProduct(seg2a, seg1a, seg1b)) *
GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0);
}
}
@@ -743,7 +962,7 @@ namespace Clipper2Lib
static_cast<double>(offPt.y - seg1.y) * dy) /
(Sqr(dx) + Sqr(dy));
if (q < 0) q = 0; else if (q > 1) q = 1;
if constexpr (std::numeric_limits<T>::is_integer)
if constexpr (std::is_integral_v<T>)
return Point<T>(
seg1.x + static_cast<T>(nearbyint(q * dx)),
seg1.y + static_cast<T>(nearbyint(q * dy)));

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 22 November 2023 *
* Date : 5 July 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : This is the main polygon clipping module *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -230,7 +230,7 @@ namespace Clipper2Lib {
inline bool PopHorz(Active *&e);
inline OutPt* StartOpenPath(Active &e, const Point64& pt);
inline void UpdateEdgeIntoAEL(Active *e);
OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt);
void IntersectEdges(Active &e1, Active &e2, const Point64& pt);
inline void DeleteFromAEL(Active &e);
inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
void DoIntersections(const int64_t top_y);
@@ -343,6 +343,7 @@ namespace Clipper2Lib {
Path64 polygon_;
public:
explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; }
~PolyPath64() {
childs_.resize(0);
@@ -363,10 +364,7 @@ namespace Clipper2Lib {
PolyPath64* AddChild(const Path64& path) override
{
auto p = std::make_unique<PolyPath64>(this);
auto* result = childs_.emplace_back(std::move(p)).get();
result->polygon_ = path;
return result;
return childs_.emplace_back(std::make_unique<PolyPath64>(this, path)).get();
}
void Clear() override
@@ -401,6 +399,19 @@ namespace Clipper2Lib {
scale_ = parent ? parent->scale_ : 1.0;
}
explicit PolyPathD(PolyPathD* parent, const Path64& path) : PolyPath(parent)
{
scale_ = parent ? parent->scale_ : 1.0;
int error_code = 0;
polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
}
explicit PolyPathD(PolyPathD* parent, const PathD& path) : PolyPath(parent)
{
scale_ = parent ? parent->scale_ : 1.0;
polygon_ = path;
}
~PolyPathD() {
childs_.resize(0);
}
@@ -423,19 +434,12 @@ namespace Clipper2Lib {
PolyPathD* AddChild(const Path64& path) override
{
int error_code = 0;
auto p = std::make_unique<PolyPathD>(this);
PolyPathD* result = childs_.emplace_back(std::move(p)).get();
result->polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
return result;
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
}
PolyPathD* AddChild(const PathD& path)
{
auto p = std::make_unique<PolyPathD>(this);
PolyPathD* result = childs_.emplace_back(std::move(p)).get();
result->polygon_ = path;
return result;
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
}
void Clear() override
@@ -530,7 +534,7 @@ namespace Clipper2Lib {
public:
explicit ClipperD(int precision = 2) : ClipperBase()
{
CheckPrecision(precision, error_code_);
CheckPrecisionRange(precision, error_code_);
// to optimize scaling / descaling precision
// set the scale to a power of double's radix (2) (#25)
scale_ = std::pow(std::numeric_limits<double>::radix,

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 26 November 2023 *
* Date : 14 May 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : This module exports the Clipper2 Library (ie DLL/so) *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -19,9 +19,9 @@
The path structures used extensively in other parts of this library are all
based on std::vector classes. Since C++ classes can't be accessed by other
languages, these paths must be converted into simple C data structures that
can be understood by just about any programming language. And these C style
path structures are simple arrays of int64_t (CPath64) and double (CPathD).
languages, these paths are converted into very simple array data structures
(of either int64_t for CPath64 or double for CPathD) that can be parsed by
just about any programming language.
CPath64 and CPathD:
These are arrays of consecutive x and y path coordinates preceeded by
@@ -34,8 +34,9 @@ __________________________________
CPaths64 and CPathsD:
These are also arrays containing any number of consecutive CPath64 or
CPathD structures. But preceeding these consecutive paths, there is pair of
values that contain the total length of the array (A) structure and
the number (C) of CPath64 or CPathD it contains.
values that contain the total length of the array structure (A) and the
number of CPath64 or CPathD it contains (C). The space these structures will
occupy in memory = A * sizeof(int64_t) or A * sizeof(double) respectively.
_______________________________
|counter|path1|path2|...|pathC|
|A , C | |
@@ -44,7 +45,7 @@ _______________________________
CPolytree64 and CPolytreeD:
These are also arrays consisting of CPolyPath structures that represent
individual paths in a tree structure. However, the very first (ie top)
CPolyPath is just the tree container that won't have a path. And because
CPolyPath is just the tree container that doesn't have a path. And because
of that, its structure will be very slightly different from the remaining
CPolyPath. This difference will be discussed below.
@@ -60,17 +61,18 @@ ____________________________________________________________
As mentioned above, the very first CPolyPath structure is just a container
that owns (both directly and indirectly) every other CPolyPath in the tree.
Since this first CPolyPath has no path, instead of a path length, its very
first value will contain the total length of the CPolytree array structure.
first value will contain the total length of the CPolytree array (not its
total bytes length).
All theses exported structures (CPaths64, CPathsD, CPolyTree64 & CPolyTreeD)
are arrays of type int64_t or double. And the first value in these arrays
will always contain the length of that array.
Again, all theses exported structures (CPaths64, CPathsD, CPolyTree64 &
CPolyTreeD) are arrays of either type int64_t or double, and the first
value in these arrays will always be the length of that array.
These array structures are allocated in heap memory which will eventually
need to be released. But since applications dynamically linking to these
functions may use different memory managers, the only safe way to free up
this memory is to use the exported DisposeArray64 and DisposeArrayD
functions below.
need to be released. However, since applications dynamically linking to
these functions may use different memory managers, the only safe way to
free up this memory is to use the exported DisposeArray64 and
DisposeArrayD functions (see below).
*/
@@ -271,17 +273,34 @@ CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale)
return result;
}
template <typename T>
static Path<T> ConvertCPath(T* path)
{
Path<T> result;
if (!path) return result;
T* v = path;
size_t cnt = static_cast<size_t>(*v);
v += 2; // skip 0 value
result.reserve(cnt);
for (size_t j = 0; j < cnt; ++j)
{
T x = *v++, y = *v++;
result.push_back(Point<T>(x, y));
}
return result;
}
template <typename T>
static Paths<T> ConvertCPaths(T* paths)
{
Paths<T> result;
if (!paths) return result;
T* v = paths; ++v;
size_t cnt = *v++;
size_t cnt = static_cast<size_t>(*v++);
result.reserve(cnt);
for (size_t i = 0; i < cnt; ++i)
{
size_t cnt2 = *v;
size_t cnt2 = static_cast<size_t>(*v);
v += 2;
Path<T> path;
path.reserve(cnt2);
@@ -302,15 +321,15 @@ static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale)
if (!paths) return result;
double* v = paths;
++v; // skip the first value (0)
int64_t cnt = (int64_t)*v++;
size_t cnt = static_cast<size_t>(*v++);
result.reserve(cnt);
for (int i = 0; i < cnt; ++i)
for (size_t i = 0; i < cnt; ++i)
{
int64_t cnt2 = (int64_t)*v;
size_t cnt2 = static_cast<size_t>(*v);
v += 2;
Path64 path;
path.reserve(cnt2);
for (int j = 0; j < cnt2; ++j)
for (size_t j = 0; j < cnt2; ++j)
{
double x = *v++ * scale;
double y = *v++ * scale;
@@ -560,6 +579,22 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
return CreateCPathsDFromPaths64(result, 1 / scale);
}
EXTERN_DLL_EXPORT CPaths64 MinkowskiSum64(const CPath64& cpattern, const CPath64& cpath, bool is_closed)
{
Path64 path = ConvertCPath(cpath);
Path64 pattern = ConvertCPath(cpattern);
Paths64 solution = MinkowskiSum(pattern, path, is_closed);
return CreateCPaths(solution);
}
EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath64& cpath, bool is_closed)
{
Path64 path = ConvertCPath(cpath);
Path64 pattern = ConvertCPath(cpattern);
Paths64 solution = MinkowskiDiff(pattern, path, is_closed);
return CreateCPaths(solution);
}
} // end Clipper2Lib namespace
#endif // CLIPPER2_EXPORT_H

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 18 November 2023 *
* Date : 27 April 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : This module provides a simple interface to the Clipper Library *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -47,7 +47,7 @@ namespace Clipper2Lib {
const PathsD& subjects, const PathsD& clips, int precision = 2)
{
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
PathsD result;
if (error_code) return result;
ClipperD clipper(precision);
@@ -63,7 +63,7 @@ namespace Clipper2Lib {
{
polytree.Clear();
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
if (error_code) return;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
@@ -104,7 +104,7 @@ namespace Clipper2Lib {
{
PathsD result;
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
if (error_code) return result;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
@@ -149,7 +149,7 @@ namespace Clipper2Lib {
int precision = 2, double arc_tolerance = 0.0)
{
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
if (!delta) return paths;
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
@@ -219,7 +219,7 @@ namespace Clipper2Lib {
{
if (rect.IsEmpty() || paths.empty()) return PathsD();
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
@@ -251,7 +251,7 @@ namespace Clipper2Lib {
{
if (rect.IsEmpty() || lines.empty()) return PathsD();
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
@@ -510,9 +510,9 @@ namespace Clipper2Lib {
if (!is_open_path)
{
while (srcIt != stop && !CrossProduct(*stop, *srcIt, *(srcIt + 1)))
while (srcIt != stop && IsCollinear(*stop, *srcIt, *(srcIt + 1)))
++srcIt;
while (srcIt != stop && !CrossProduct(*(stop - 1), *stop, *srcIt))
while (srcIt != stop && IsCollinear(*(stop - 1), *stop, *srcIt))
--stop;
if (srcIt == stop) return Path64();
}
@@ -521,7 +521,7 @@ namespace Clipper2Lib {
dst.push_back(*prevIt);
for (; srcIt != stop; ++srcIt)
{
if (CrossProduct(*prevIt, *srcIt, *(srcIt + 1)))
if (!IsCollinear(*prevIt, *srcIt, *(srcIt + 1)))
{
prevIt = srcIt;
dst.push_back(*prevIt);
@@ -530,12 +530,12 @@ namespace Clipper2Lib {
if (is_open_path)
dst.push_back(*srcIt);
else if (CrossProduct(*prevIt, *stop, dst[0]))
else if (!IsCollinear(*prevIt, *stop, dst[0]))
dst.push_back(*stop);
else
{
while (dst.size() > 2 &&
!CrossProduct(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
IsCollinear(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
dst.pop_back();
if (dst.size() < 3) return Path64();
}
@@ -545,7 +545,7 @@ namespace Clipper2Lib {
inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
{
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
if (error_code) return PathD();
const double scale = std::pow(10, precision);
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
@@ -582,7 +582,7 @@ namespace Clipper2Lib {
}
template <typename T>
inline Path<T> Ellipse(const Rect<T>& rect, int steps = 0)
inline Path<T> Ellipse(const Rect<T>& rect, size_t steps = 0)
{
return Ellipse(rect.MidPoint(),
static_cast<double>(rect.Width()) *0.5,
@@ -591,12 +591,12 @@ namespace Clipper2Lib {
template <typename T>
inline Path<T> Ellipse(const Point<T>& center,
double radiusX, double radiusY = 0, int steps = 0)
double radiusX, double radiusY = 0, size_t steps = 0)
{
if (radiusX <= 0) return Path<T>();
if (radiusY <= 0) radiusY = radiusX;
if (steps <= 2)
steps = static_cast<int>(PI * sqrt((radiusX + radiusY) / 2));
steps = static_cast<size_t>(PI * sqrt((radiusX + radiusY) / 2));
double si = std::sin(2 * PI / steps);
double co = std::cos(2 * PI / steps);
@@ -604,7 +604,7 @@ namespace Clipper2Lib {
Path<T> result;
result.reserve(steps);
result.push_back(Point<T>(center.x + radiusX, static_cast<double>(center.y)));
for (int i = 1; i < steps; ++i)
for (size_t i = 1; i < steps; ++i)
{
result.push_back(Point<T>(center.x + radiusX * dx, center.y + radiusY * dy));
double x = dx * co - dy * si;
@@ -614,18 +614,6 @@ namespace Clipper2Lib {
return result;
}
template <typename T>
inline double PerpendicDistFromLineSqrd(const Point<T>& pt,
const Point<T>& line1, const Point<T>& line2)
{
double a = static_cast<double>(pt.x - line1.x);
double b = static_cast<double>(pt.y - line1.y);
double c = static_cast<double>(line2.x - line1.x);
double d = static_cast<double>(line2.y - line1.y);
if (c == 0 && d == 0) return 0;
return Sqr(a * d - c * b) / (c * c + d * d);
}
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& flags)
{

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 19 November 2023 *
* Date : 24 March 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : Path Offset (Inflate/Shrink) *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -34,9 +34,7 @@ private:
class Group {
public:
Paths64 paths_in;
std::vector<bool> is_hole_list;
std::vector<Rect64> bounds_list;
int lowest_path_idx = -1;
std::optional<size_t> lowest_path_idx{};
bool is_reversed = false;
JoinType join_type;
EndType end_type;
@@ -52,7 +50,8 @@ private:
double step_cos_ = 0.0;
PathD norms;
Path64 path_out;
Paths64 solution;
Paths64* solution = nullptr;
PolyTree64* solution_tree = nullptr;
std::vector<Group> groups_;
JoinType join_type_ = JoinType::Bevel;
EndType end_type_ = EndType::Polygon;
@@ -64,9 +63,10 @@ private:
#ifdef USINGZ
ZCallback64 zCallback64_ = nullptr;
void ZCB(const Point64& bot1, const Point64& top1,
const Point64& bot2, const Point64& top2, Point64& ip);
#endif
DeltaCallback64 deltaCallback64_ = nullptr;
size_t CalcSolutionCapacity();
bool CheckReverseOrientation();
void DoBevel(const Path64& path, size_t j, size_t k);
@@ -91,7 +91,7 @@ public:
~ClipperOffset() { Clear(); };
int ErrorCode() { return error_code_; };
int ErrorCode() const { return error_code_; };
void AddPath(const Path64& path, JoinType jt_, EndType et_);
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
void Clear() { groups_.clear(); norms.clear(); };

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 1 November 2023 *
* Date : 5 July 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : FAST rectangular clipping *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -18,6 +18,7 @@
namespace Clipper2Lib
{
// Location: the order is important here, see StartLocsIsClockwise()
enum class Location { Left, Top, Right, Bottom, Inside };
class OutPt2;
@@ -26,10 +27,10 @@ namespace Clipper2Lib
class OutPt2 {
public:
Point64 pt;
size_t owner_idx;
OutPt2List* edge;
OutPt2* next;
OutPt2* prev;
size_t owner_idx = 0;
OutPt2List* edge = nullptr;
OutPt2* next = nullptr;
OutPt2* prev = nullptr;
};
//------------------------------------------------------------------------------
@@ -50,9 +51,9 @@ namespace Clipper2Lib
OutPt2List edges_[8]; // clockwise and counter-clockwise
std::vector<Location> start_locs_;
void CheckEdges();
void TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw);
void TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw);
void GetNextLocation(const Path64& path,
Location& loc, int& i, int highI);
Location& loc, size_t& i, size_t highI);
OutPt2* Add(Point64 pt, bool start_new = false);
void AddCorner(Location prev, Location curr);
void AddCorner(Location& loc, bool isClockwise);

View File

@@ -1,6 +1,6 @@
#ifndef CLIPPER_VERSION_H
#define CLIPPER_VERSION_H
constexpr auto CLIPPER2_VERSION = "1.3.0";
constexpr auto CLIPPER2_VERSION = "1.4.0";
#endif // CLIPPER_VERSION_H

View File

@@ -1,9 +1,9 @@
diff --git a/thirdparty/clipper2/include/clipper2/clipper.core.h b/thirdparty/clipper2/include/clipper2/clipper.core.h
index b3dddeeaa2..a77cdad5f4 100644
index 925c04685e..d0d159b949 100644
--- a/thirdparty/clipper2/include/clipper2/clipper.core.h
+++ b/thirdparty/clipper2/include/clipper2/clipper.core.h
@@ -21,6 +21,8 @@
#include <numeric>
@@ -22,6 +22,8 @@
#include <optional>
#include "clipper2/clipper.version.h"
+#define CLIPPER2_THROW(exception) std::abort()
@@ -11,7 +11,7 @@ index b3dddeeaa2..a77cdad5f4 100644
namespace Clipper2Lib
{
@@ -78,18 +80,18 @@ namespace Clipper2Lib
@@ -79,18 +81,18 @@ namespace Clipper2Lib
switch (error_code)
{
case precision_error_i:

View File

@@ -1,22 +0,0 @@
diff --git a/thirdparty/clipper2/include/clipper2/clipper.core.h b/thirdparty/clipper2/include/clipper2/clipper.core.h
index a77cdad5f4..0de7c3720e 100644
--- a/thirdparty/clipper2/include/clipper2/clipper.core.h
+++ b/thirdparty/clipper2/include/clipper2/clipper.core.h
@@ -138,7 +138,7 @@ namespace Clipper2Lib
}
template <typename T2>
- explicit Point<T>(const Point<T2>& p)
+ explicit Point(const Point<T2>& p)
{
Init(p.x, p.y, p.z);
}
@@ -180,7 +180,7 @@ namespace Clipper2Lib
Point(const T2 x_, const T2 y_) { Init(x_, y_); }
template <typename T2>
- explicit Point<T>(const Point<T2>& p) { Init(p.x, p.y); }
+ explicit Point(const Point<T2>& p) { Init(p.x, p.y); }
Point operator * (const double scale) const
{

View File

@@ -0,0 +1,34 @@
diff --git a/thirdparty/clipper2/include/clipper2/clipper.core.h b/thirdparty/clipper2/include/clipper2/clipper.core.h
index 67dd731af6..0f69bf2d9f 100644
--- a/thirdparty/clipper2/include/clipper2/clipper.core.h
+++ b/thirdparty/clipper2/include/clipper2/clipper.core.h
@@ -695,11 +695,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
+// -- GODOT start --
+// #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
+// -- GODOT end --
// 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));
@@ -714,7 +716,9 @@ namespace Clipper2Lib
const auto sign_cd = TriSign(c) * TriSign(d);
return abs_ab == abs_cd && sign_ab == sign_cd;
-#endif
+// -- GODOT start --
+// #endif
+// -- GODOT end --
}
template <typename T>

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 22 November 2023 *
* Date : 27 April 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : This is the main polygon clipping module *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -31,11 +31,11 @@ namespace Clipper2Lib {
static const Rect64 invalid_rect = Rect64(false);
// Every closed path (or polygon) is made up of a series of vertices forming
// edges that alternate between going up (relative to the Y-axis) and going
// down. Edges consecutively going up or consecutively going down are called
// 'bounds' (ie sides if they're simple polygons). 'Local Minima' refer to
// vertices where descending bounds become ascending ones.
// Every closed path (ie polygon) is made up of a series of vertices forming edge
// 'bounds' that alternate between ascending bounds (containing edges going up
// relative to the Y-axis) and descending bounds. 'Local Minima' refers to
// vertices where ascending and descending bounds join at the bottom, and
// 'Local Maxima' are where ascending and descending bounds join at the top.
struct Scanline {
int64_t y = 0;
@@ -63,6 +63,7 @@ namespace Clipper2Lib {
}
};
inline bool IsOdd(int val)
{
return (val & 1) ? true : false;
@@ -617,9 +618,9 @@ namespace Clipper2Lib {
std::vector<Vertex*>& vertexLists, LocalMinimaList& locMinList)
{
const auto total_vertex_count =
std::accumulate(paths.begin(), paths.end(), 0,
std::accumulate(paths.begin(), paths.end(), size_t(0),
[](const auto& a, const Path64& path)
{return a + static_cast<unsigned>(path.size()); });
{return a + path.size(); });
if (total_vertex_count == 0) return;
Vertex* vertices = new Vertex[total_vertex_count], * v = vertices;
@@ -1117,7 +1118,6 @@ namespace Clipper2Lib {
}
}
bool IsValidAelOrder(const Active& resident, const Active& newcomer)
{
if (newcomer.curr_x != resident.curr_x)
@@ -1149,8 +1149,8 @@ namespace Clipper2Lib {
//resident must also have just been inserted
else if (resident.is_left_bound != newcomerIsLeft)
return newcomerIsLeft;
else if (CrossProduct(PrevPrevVertex(resident)->pt,
resident.bot, resident.top) == 0) return true;
else if (IsCollinear(PrevPrevVertex(resident)->pt,
resident.bot, resident.top)) return true;
else
//compare turning direction of the alternate bound
return (CrossProduct(PrevPrevVertex(resident)->pt,
@@ -1526,7 +1526,6 @@ namespace Clipper2Lib {
return new_op;
}
void ClipperBase::CleanCollinear(OutRec* outrec)
{
outrec = GetRealOutRec(outrec);
@@ -1541,7 +1540,7 @@ namespace Clipper2Lib {
for (; ; )
{
//NB if preserveCollinear == true, then only remove 180 deg. spikes
if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) &&
if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt) &&
(op2->pt == op2->prev->pt ||
op2->pt == op2->next->pt || !preserve_collinear_ ||
DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0))
@@ -1573,7 +1572,7 @@ namespace Clipper2Lib {
outrec->pts = prevOp;
Point64 ip;
GetIntersectPoint(prevOp->pt, splitOp->pt,
GetSegmentIntersectPt(prevOp->pt, splitOp->pt,
splitOp->next->pt, nextNextOp->pt, ip);
#ifdef USINGZ
@@ -1772,12 +1771,12 @@ namespace Clipper2Lib {
}
OutPt* ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt)
void ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt)
{
//MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2)))
{
if (IsOpen(e1) && IsOpen(e2)) return nullptr;
if (IsOpen(e1) && IsOpen(e2)) return;
Active* edge_o, * edge_c;
if (IsOpen(e1))
{
@@ -1791,29 +1790,40 @@ namespace Clipper2Lib {
}
if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety
if (abs(edge_c->wind_cnt) != 1) return nullptr;
if (abs(edge_c->wind_cnt) != 1) return;
switch (cliptype_)
{
case ClipType::Union:
if (!IsHotEdge(*edge_c)) return nullptr;
if (!IsHotEdge(*edge_c)) return;
break;
default:
if (edge_c->local_min->polytype == PathType::Subject)
return nullptr;
return;
}
switch (fillrule_)
{
case FillRule::Positive: if (edge_c->wind_cnt != 1) return nullptr; break;
case FillRule::Negative: if (edge_c->wind_cnt != -1) return nullptr; break;
default: if (std::abs(edge_c->wind_cnt) != 1) return nullptr; break;
case FillRule::Positive:
if (edge_c->wind_cnt != 1) return;
break;
case FillRule::Negative:
if (edge_c->wind_cnt != -1) return;
break;
default:
if (std::abs(edge_c->wind_cnt) != 1) return;
}
#ifdef USINGZ
OutPt* resultOp;
#endif
//toggle contribution ...
if (IsHotEdge(*edge_o))
{
#ifdef USINGZ
resultOp = AddOutPt(*edge_o, pt);
#else
AddOutPt(*edge_o, pt);
#endif
if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr;
else edge_o->outrec->back_edge = nullptr;
edge_o->outrec = nullptr;
@@ -1833,18 +1843,26 @@ namespace Clipper2Lib {
SetSides(*e3->outrec, *edge_o, *e3);
else
SetSides(*e3->outrec, *e3, *edge_o);
return e3->outrec->pts;
return;
}
else
#ifdef USINGZ
resultOp = StartOpenPath(*edge_o, pt);
#else
StartOpenPath(*edge_o, pt);
#endif
}
else
#ifdef USINGZ
resultOp = StartOpenPath(*edge_o, pt);
#else
StartOpenPath(*edge_o, pt);
#endif
#ifdef USINGZ
if (zCallback_) SetZ(*edge_o, *edge_c, resultOp->pt);
#endif
return resultOp;
return;
} // end of an open path intersection
//MANAGING CLOSED PATHS FROM HERE ON
@@ -1913,22 +1931,25 @@ namespace Clipper2Lib {
const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1;
const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1;
if ((!IsHotEdge(e1) && !e1_windcnt_in_01) || (!IsHotEdge(e2) && !e2_windcnt_in_01))
{
return nullptr;
}
if ((!IsHotEdge(e1) && !e1_windcnt_in_01) ||
(!IsHotEdge(e2) && !e2_windcnt_in_01))
return;
//NOW PROCESS THE INTERSECTION ...
#ifdef USINGZ
OutPt* resultOp = nullptr;
#endif
//if both edges are 'hot' ...
if (IsHotEdge(e1) && IsHotEdge(e2))
{
if ((old_e1_windcnt != 0 && old_e1_windcnt != 1) || (old_e2_windcnt != 0 && old_e2_windcnt != 1) ||
(e1.local_min->polytype != e2.local_min->polytype && cliptype_ != ClipType::Xor))
{
resultOp = AddLocalMaxPoly(e1, e2, pt);
#ifdef USINGZ
resultOp = AddLocalMaxPoly(e1, e2, pt);
if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);
#else
AddLocalMaxPoly(e1, e2, pt);
#endif
}
else if (IsFront(e1) || (e1.outrec == e2.outrec))
@@ -1937,19 +1958,20 @@ namespace Clipper2Lib {
//it's sensible to split polygons that ony touch at
//a common vertex (not at common edges).
resultOp = AddLocalMaxPoly(e1, e2, pt);
#ifdef USINGZ
resultOp = AddLocalMaxPoly(e1, e2, pt);
OutPt* op2 = AddLocalMinPoly(e1, e2, pt);
if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);
if (zCallback_) SetZ(e1, e2, op2->pt);
#else
AddLocalMaxPoly(e1, e2, pt);
AddLocalMinPoly(e1, e2, pt);
#endif
}
else
{
resultOp = AddOutPt(e1, pt);
#ifdef USINGZ
resultOp = AddOutPt(e1, pt);
OutPt* op2 = AddOutPt(e2, pt);
if (zCallback_)
{
@@ -1957,6 +1979,7 @@ namespace Clipper2Lib {
SetZ(e1, e2, op2->pt);
}
#else
AddOutPt(e1, pt);
AddOutPt(e2, pt);
#endif
SwapOutrecs(e1, e2);
@@ -1964,17 +1987,21 @@ namespace Clipper2Lib {
}
else if (IsHotEdge(e1))
{
resultOp = AddOutPt(e1, pt);
#ifdef USINGZ
resultOp = AddOutPt(e1, pt);
if (zCallback_) SetZ(e1, e2, resultOp->pt);
#else
AddOutPt(e1, pt);
#endif
SwapOutrecs(e1, e2);
}
else if (IsHotEdge(e2))
{
resultOp = AddOutPt(e2, pt);
#ifdef USINGZ
resultOp = AddOutPt(e2, pt);
if (zCallback_) SetZ(e1, e2, resultOp->pt);
#else
AddOutPt(e2, pt);
#endif
SwapOutrecs(e1, e2);
}
@@ -2004,33 +2031,53 @@ namespace Clipper2Lib {
if (!IsSamePolyType(e1, e2))
{
resultOp = AddLocalMinPoly(e1, e2, pt, false);
#ifdef USINGZ
resultOp = AddLocalMinPoly(e1, e2, pt, false);
if (zCallback_) SetZ(e1, e2, resultOp->pt);
#else
AddLocalMinPoly(e1, e2, pt, false);
#endif
}
else if (old_e1_windcnt == 1 && old_e2_windcnt == 1)
{
#ifdef USINGZ
resultOp = nullptr;
#endif
switch (cliptype_)
{
case ClipType::Union:
if (e1Wc2 <= 0 && e2Wc2 <= 0)
#ifdef USINGZ
resultOp = AddLocalMinPoly(e1, e2, pt, false);
#else
AddLocalMinPoly(e1, e2, pt, false);
#endif
break;
case ClipType::Difference:
if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
{
#ifdef USINGZ
resultOp = AddLocalMinPoly(e1, e2, pt, false);
#else
AddLocalMinPoly(e1, e2, pt, false);
#endif
}
break;
case ClipType::Xor:
#ifdef USINGZ
resultOp = AddLocalMinPoly(e1, e2, pt, false);
#else
AddLocalMinPoly(e1, e2, pt, false);
#endif
break;
default:
if (e1Wc2 > 0 && e2Wc2 > 0)
#ifdef USINGZ
resultOp = AddLocalMinPoly(e1, e2, pt, false);
#else
AddLocalMinPoly(e1, e2, pt, false);
#endif
break;
}
#ifdef USINGZ
@@ -2038,7 +2085,6 @@ namespace Clipper2Lib {
#endif
}
}
return resultOp;
}
inline void ClipperBase::DeleteFromAEL(Active& e)
@@ -2167,6 +2213,7 @@ namespace Clipper2Lib {
horz_seg_list_.end(),
[](HorzSegment& hs) { return UpdateHorzSegment(hs); });
if (j < 2) return;
std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter());
HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2;
@@ -2301,7 +2348,7 @@ namespace Clipper2Lib {
void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
{
Point64 ip;
if (!GetIntersectPoint(e1.bot, e1.top, e2.bot, e2.top, ip))
if (!GetSegmentIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
ip = Point64(e1.curr_x, top_y); //parallel edges
//rounding errors can occasionally place the calculated intersection
@@ -2758,17 +2805,19 @@ namespace Clipper2Lib {
const Point64& pt, bool check_curr_x)
{
Active* prev = e.prev_in_ael;
if (IsOpen(e) || !IsHotEdge(e) || !prev ||
IsOpen(*prev) || !IsHotEdge(*prev)) return;
if (!prev ||
!IsHotEdge(e) || !IsHotEdge(*prev) ||
IsHorizontal(e) || IsHorizontal(*prev) ||
IsOpen(e) || IsOpen(*prev) ) return;
if ((pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) &&
((e.bot.y > pt.y) || (prev->bot.y > pt.y))) return; // avoid trivial joins
if (check_curr_x)
{
if (DistanceFromLineSqrd(pt, prev->bot, prev->top) > 0.25) return;
if (PerpendicDistFromLineSqrd(pt, prev->bot, prev->top) > 0.25) return;
}
else if (e.curr_x != prev->curr_x) return;
if (CrossProduct(e.top, pt, prev->top)) return;
if (!IsCollinear(e.top, pt, prev->top)) return;
if (e.outrec->idx == prev->outrec->idx)
AddLocalMaxPoly(*prev, e, pt);
@@ -2784,17 +2833,19 @@ namespace Clipper2Lib {
const Point64& pt, bool check_curr_x)
{
Active* next = e.next_in_ael;
if (IsOpen(e) || !IsHotEdge(e) ||
!next || IsOpen(*next) || !IsHotEdge(*next)) return;
if (!next ||
!IsHotEdge(e) || !IsHotEdge(*next) ||
IsHorizontal(e) || IsHorizontal(*next) ||
IsOpen(e) || IsOpen(*next)) return;
if ((pt.y < e.top.y +2 || pt.y < next->top.y +2) &&
((e.bot.y > pt.y) || (next->bot.y > pt.y))) return; // avoid trivial joins
if (check_curr_x)
{
if (DistanceFromLineSqrd(pt, next->bot, next->top) > 0.35) return;
if (PerpendicDistFromLineSqrd(pt, next->bot, next->top) > 0.35) return;
}
else if (e.curr_x != next->curr_x) return;
if (CrossProduct(e.top, pt, next->top)) return;
if (!IsCollinear(e.top, pt, next->top)) return;
if (e.outrec->idx == next->outrec->idx)
AddLocalMaxPoly(e, *next, pt);
@@ -2863,7 +2914,7 @@ namespace Clipper2Lib {
op2 = op2->next;
}
if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false;
if (!isOpen && path.size() == 3 && IsVerySmallTriangle(*op2)) return false;
else return true;
}

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 28 November 2023 *
* Date : 17 April 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : Path Offset (Inflate/Shrink) *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -20,60 +20,19 @@ const double floating_point_tolerance = 1e-12;
// Miscellaneous methods
//------------------------------------------------------------------------------
inline bool ToggleBoolIf(bool val, bool condition)
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
{
return condition ? !val : val;
}
void GetMultiBounds(const Paths64& paths, std::vector<Rect64>& recList)
{
recList.reserve(paths.size());
for (const Path64& path : paths)
{
if (path.size() < 1)
{
recList.push_back(InvalidRect64);
continue;
}
int64_t x = path[0].x, y = path[0].y;
Rect64 r = Rect64(x, y, x, y);
for (const Point64& pt : path)
{
if (pt.y > r.bottom) r.bottom = pt.y;
else if (pt.y < r.top) r.top = pt.y;
if (pt.x > r.right) r.right = pt.x;
else if (pt.x < r.left) r.left = pt.x;
}
recList.push_back(r);
}
}
bool ValidateBounds(std::vector<Rect64>& recList, double delta)
{
int64_t int_delta = static_cast<int64_t>(delta);
int64_t big = MAX_COORD - int_delta;
int64_t small = MIN_COORD + int_delta;
for (const Rect64& r : recList)
{
if (!r.IsValid()) continue; // ignore invalid paths
else if (r.left < small || r.right > big ||
r.top < small || r.bottom > big) return false;
}
return true;
}
int GetLowestClosedPathIdx(std::vector<Rect64>& boundsList)
{
int i = -1, result = -1;
std::optional<size_t> result;
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
for (const Rect64& r : boundsList)
for (size_t i = 0; i < paths.size(); ++i)
{
++i;
if (!r.IsValid()) continue; // ignore invalid paths
else if (r.bottom > botPt.y || (r.bottom == botPt.y && r.left < botPt.x))
for (const Point64& pt : paths[i])
{
botPt = Point64(r.left, r.bottom);
result = static_cast<int>(i);
if ((pt.y < botPt.y) ||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
result = i;
botPt.x = pt.x;
botPt.y = pt.y;
}
}
return result;
@@ -164,30 +123,21 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType
for (Path64& p: paths_in)
StripDuplicates(p, is_joined);
// get bounds of each path --> bounds_list
GetMultiBounds(paths_in, bounds_list);
if (end_type == EndType::Polygon)
{
is_hole_list.reserve(paths_in.size());
for (const Path64& path : paths_in)
is_hole_list.push_back(Area(path) < 0);
lowest_path_idx = GetLowestClosedPathIdx(bounds_list);
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
// 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 >= 0) && is_hole_list[lowest_path_idx];
if (is_reversed) is_hole_list.flip();
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
}
else
{
lowest_path_idx = -1;
lowest_path_idx = std::nullopt;
is_reversed = false;
is_hole_list.resize(paths_in.size());
}
}
//------------------------------------------------------------------------------
// ClipperOffset methods
//------------------------------------------------------------------------------
@@ -216,66 +166,29 @@ void ClipperOffset::BuildNormals(const Path64& path)
norms.push_back(GetUnitNormal(*path_stop_iter, *(path.cbegin())));
}
inline PointD TranslatePoint(const PointD& pt, double dx, double dy)
{
#ifdef USINGZ
return PointD(pt.x + dx, pt.y + dy, pt.z);
#else
return PointD(pt.x + dx, pt.y + dy);
#endif
}
inline PointD ReflectPoint(const PointD& pt, const PointD& pivot)
{
#ifdef USINGZ
return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z);
#else
return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
#endif
}
PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
const PointD& pt2a, const PointD& pt2b)
{
if (pt1a.x == pt1b.x) //vertical
{
if (pt2a.x == pt2b.x) return PointD(0, 0);
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
double b2 = pt2a.y - m2 * pt2a.x;
return PointD(pt1a.x, m2 * pt1a.x + b2);
}
else if (pt2a.x == pt2b.x) //vertical
{
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
double b1 = pt1a.y - m1 * pt1a.x;
return PointD(pt2a.x, m1 * pt2a.x + b1);
}
else
{
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
double b1 = pt1a.y - m1 * pt1a.x;
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
double b2 = pt2a.y - m2 * pt2a.x;
if (m1 == m2) return PointD(0, 0);
double x = (b2 - b1) / (m1 - m2);
return PointD(x, m1 * x + b1);
}
}
void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
{
PointD pt1, pt2;
if (j == k)
{
double abs_delta = std::abs(group_delta_);
#ifdef USINGZ
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
#else
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
#endif
}
else
{
#ifdef USINGZ
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z);
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z);
#else
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
#endif
}
path_out.push_back(Point64(pt1));
path_out.push_back(Point64(pt2));
@@ -304,10 +217,8 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
if (j == k)
{
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
#ifdef USINGZ
pt.z = ptQ.z;
#endif
PointD pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
//get the second intersect point through reflecion
path_out.push_back(Point64(ReflectPoint(pt, ptQ)));
path_out.push_back(Point64(pt));
@@ -315,10 +226,8 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
else
{
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
#ifdef USINGZ
pt.z = ptQ.z;
#endif
PointD pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
path_out.push_back(Point64(pt));
//get the second intersect point through reflecion
path_out.push_back(Point64(ReflectPoint(pt, ptQ)));
@@ -387,7 +296,7 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size
// sin(A) < 0: right turning
// cos(A) < 0: change in angle is more than 90 degree
if (path[j] == path[k]) { k = j; return; }
if (path[j] == path[k]) return;
double sin_a = CrossProduct(norms[j], norms[k]);
double cos_a = DotProduct(norms[j], norms[k]);
@@ -404,14 +313,25 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size
return;
}
if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
{
// is concave
// is concave (so insert 3 points that will create a negative region)
#ifdef USINGZ
path_out.push_back(Point64(GetPerpendic(path[j], norms[k], group_delta_), path[j].z));
#else
path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_));
// this extra point is the only (simple) way to ensure that
// path reversals are fully cleaned with the trailing clipper
path_out.push_back(path[j]); // (#405)
#endif
// this extra point is the only simple way to ensure that path reversals
// (ie over-shrunk paths) are fully cleaned out with the trailing union op.
// However it's probably safe to skip this whenever an angle is almost flat.
if (cos_a < 0.99) path_out.push_back(path[j]); // (#405)
#ifdef USINGZ
path_out.push_back(Point64(GetPerpendic(path[j], norms[j], group_delta_), path[j].z));
#else
path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
#endif
}
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
{
@@ -437,7 +357,7 @@ void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
path_out.clear();
for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
OffsetPoint(group, path, j, k);
solution.push_back(path_out);
solution->push_back(path_out);
}
void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
@@ -446,7 +366,7 @@ void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
Path64 reverse_path(path);
std::reverse(reverse_path.begin(), reverse_path.end());
//rebuild normals // BuildNormals(path);
//rebuild normals
std::reverse(norms.begin(), norms.end());
norms.push_back(norms[0]);
norms.erase(norms.begin());
@@ -510,9 +430,9 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
}
}
for (size_t j = highI, k = 0; j > 0; k = j, --j)
for (size_t j = highI -1, k = highI; j > 0; k = j, --j)
OffsetPoint(group, path, j, k);
solution.push_back(path_out);
solution->push_back(path_out);
}
void ClipperOffset::DoGroupOffset(Group& group)
@@ -521,30 +441,23 @@ void ClipperOffset::DoGroupOffset(Group& group)
{
// a straight path (2 points) can now also be 'polygon' offset
// where the ends will be treated as (180 deg.) joins
if (group.lowest_path_idx < 0) delta_ = std::abs(delta_);
if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_);
group_delta_ = (group.is_reversed) ? -delta_ : delta_;
}
else
group_delta_ = std::abs(delta_);// *0.5;
double abs_delta = std::fabs(group_delta_);
if (!ValidateBounds(group.bounds_list, abs_delta))
{
DoError(range_error_i);
error_code_ |= range_error_i;
return;
}
join_type_ = group.join_type;
end_type_ = group.end_type;
if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
{
// calculate a sensible number of steps (for 360 deg for the given offset)
// arcTol - when arc_tolerance_ is undefined (0), the amount of
// curve imprecision that's allowed is based on the size of the
// offset (delta). Obviously very large offsets will almost always
// require much less precision. See also offset_triginometry2.svg
// calculate the number of steps required to approximate a circle
// (see http://www.angusj.com/clipper2/Docs/Trigonometry.htm)
// arcTol - when arc_tolerance_ is undefined (0) then curve imprecision
// will be relative to the size of the offset (delta). Obviously very
//large offsets will almost always require much less precision.
double arcTol = (arc_tolerance_ > floating_point_tolerance ?
std::min(abs_delta, arc_tolerance_) :
std::log10(2 + abs_delta) * default_arc_tolerance);
@@ -556,24 +469,29 @@ void ClipperOffset::DoGroupOffset(Group& group)
steps_per_rad_ = steps_per_360 / (2 * PI);
}
std::vector<Rect64>::const_iterator path_rect_it = group.bounds_list.cbegin();
std::vector<bool>::const_iterator is_hole_it = group.is_hole_list.cbegin();
//double min_area = PI * Sqr(group_delta_);
Paths64::const_iterator path_in_it = group.paths_in.cbegin();
for ( ; path_in_it != group.paths_in.cend(); ++path_in_it, ++path_rect_it, ++is_hole_it)
for ( ; path_in_it != group.paths_in.cend(); ++path_in_it)
{
if (!path_rect_it->IsValid()) continue;
Path64::size_type pathLen = path_in_it->size();
path_out.clear();
if (pathLen == 1) // single point
{
if (deltaCallback64_)
{
group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
if (group.is_reversed) group_delta_ = -group_delta_;
abs_delta = std::fabs(group_delta_);
}
if (group_delta_ < 1) continue;
const Point64& pt = (*path_in_it)[0];
//single vertex so build a circle or square ...
if (group.join_type == JoinType::Round)
{
double radius = abs_delta;
int steps = static_cast<int>(std::ceil(steps_per_rad_ * 2 * PI)); //#617
size_t steps = steps_per_rad_ > 0 ? static_cast<size_t>(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617
path_out = Ellipse(pt, radius, radius, steps);
#ifdef USINGZ
for (auto& p : path_out) p.z = pt.z;
@@ -588,16 +506,11 @@ void ClipperOffset::DoGroupOffset(Group& group)
for (auto& p : path_out) p.z = pt.z;
#endif
}
solution.push_back(path_out);
solution->push_back(path_out);
continue;
} // end of offsetting a single point
// when shrinking outer paths, make sure they can shrink this far (#593)
// also when shrinking holes, make sure they too can shrink this far (#715)
if ((group_delta_ > 0) == ToggleBoolIf(*is_hole_it, group.is_reversed) &&
(std::min(path_rect_it->Width(), path_rect_it->Height()) <= -group_delta_ * 2) )
continue;
if ((pathLen == 2) && (group.end_type == EndType::Joined))
end_type_ = (group.join_type == JoinType::Round) ?
EndType::Round :
@@ -610,6 +523,16 @@ void ClipperOffset::DoGroupOffset(Group& group)
}
}
#ifdef USINGZ
void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1,
const Point64& bot2, const Point64& top2, Point64& ip)
{
if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z;
else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z;
else if (top1.z && (top1.z == top2.z)) ip.z = top1.z;
else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip);
}
#endif
size_t ClipperOffset::CalcSolutionCapacity()
{
@@ -635,19 +558,19 @@ bool ClipperOffset::CheckReverseOrientation()
void ClipperOffset::ExecuteInternal(double delta)
{
error_code_ = 0;
solution.clear();
if (groups_.size() == 0) return;
solution.reserve(CalcSolutionCapacity());
solution->reserve(CalcSolutionCapacity());
if (std::abs(delta) < 0.5) // ie: offset is insignificant
{
Paths64::size_type sol_size = 0;
for (const Group& group : groups_) sol_size += group.paths_in.size();
solution.reserve(sol_size);
solution->reserve(sol_size);
for (const Group& group : groups_)
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution));
return;
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution));
}
else
{
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
@@ -659,59 +582,58 @@ void ClipperOffset::ExecuteInternal(double delta)
{
DoGroupOffset(*git);
if (!error_code_) continue; // all OK
solution.clear();
solution->clear();
}
}
if (!solution->size()) return;
bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear(false);
//the solution should retain the orientation of the input
c.ReverseSolution(reverse_solution_ != paths_reversed);
#ifdef USINGZ
auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5);
c.SetZCallback(fp);
#endif
c.AddSubject(*solution);
if (solution_tree)
{
if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, *solution_tree);
else
c.Execute(ClipType::Union, FillRule::Positive, *solution_tree);
}
else
{
if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, *solution);
else
c.Execute(ClipType::Union, FillRule::Positive, *solution);
}
}
void ClipperOffset::Execute(double delta, Paths64& paths)
{
paths.clear();
solution = &paths;
solution_tree = nullptr;
ExecuteInternal(delta);
if (!solution.size()) return;
bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear(false);
//the solution should retain the orientation of the input
c.ReverseSolution(reverse_solution_ != paths_reversed);
#ifdef USINGZ
if (zCallback64_) { c.SetZCallback(zCallback64_); }
#endif
c.AddSubject(solution);
if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, paths);
else
c.Execute(ClipType::Union, FillRule::Positive, paths);
}
void ClipperOffset::Execute(double delta, PolyTree64& polytree)
{
polytree.Clear();
solution_tree = &polytree;
solution = new Paths64();
ExecuteInternal(delta);
if (!solution.size()) return;
bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear(false);
//the solution should retain the orientation of the input
c.ReverseSolution (reverse_solution_ != paths_reversed);
#ifdef USINGZ
if (zCallback64_) {
c.SetZCallback(zCallback64_);
}
#endif
c.AddSubject(solution);
if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, polytree);
else
c.Execute(ClipType::Union, FillRule::Positive, polytree);
delete solution;
solution = nullptr;
}
void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)

View File

@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 8 September 2023 *
* Date : 5 July 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : FAST rectangular clipping *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -71,7 +71,7 @@ namespace Clipper2Lib {
return pt1.y == pt2.y;
}
inline bool GetSegmentIntersection(const Point64& p1,
bool GetSegmentIntersection(const Point64& p1,
const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip)
{
double res1 = CrossProduct(p1, p3, p4);
@@ -113,7 +113,7 @@ namespace Clipper2Lib {
if ((res3 > 0) == (res4 > 0)) return false;
// segments must intersect to get here
return GetIntersectPoint(p1, p2, p3, p4, ip);
return GetSegmentIntersectPt(p1, p2, p3, p4, ip);
}
inline bool GetIntersection(const Path64& rectPath,
@@ -320,9 +320,9 @@ namespace Clipper2Lib {
// this method is only called by InternalExecute.
// Later splitting & rejoining won't create additional op's,
// though they will change the (non-storage) results_ count.
int curr_idx = static_cast<int>(results_.size()) - 1;
size_t curr_idx = results_.size();
OutPt2* result;
if (curr_idx < 0 || start_new)
if (curr_idx == 0 || start_new)
{
result = &op_container_.emplace_back(OutPt2());
result->pt = pt;
@@ -332,6 +332,7 @@ namespace Clipper2Lib {
}
else
{
--curr_idx;
OutPt2* prevOp = results_[curr_idx];
if (prevOp->pt == pt) return prevOp;
result = &op_container_.emplace_back(OutPt2());
@@ -349,27 +350,27 @@ namespace Clipper2Lib {
void RectClip64::AddCorner(Location prev, Location curr)
{
if (HeadingClockwise(prev, curr))
Add(rect_as_path_[static_cast<int>(prev)]);
Add(rect_as_path_[static_cast<size_t>(prev)]);
else
Add(rect_as_path_[static_cast<int>(curr)]);
Add(rect_as_path_[static_cast<size_t>(curr)]);
}
void RectClip64::AddCorner(Location& loc, bool isClockwise)
{
if (isClockwise)
{
Add(rect_as_path_[static_cast<int>(loc)]);
Add(rect_as_path_[static_cast<size_t>(loc)]);
loc = GetAdjacentLocation(loc, true);
}
else
{
loc = GetAdjacentLocation(loc, false);
Add(rect_as_path_[static_cast<int>(loc)]);
Add(rect_as_path_[static_cast<size_t>(loc)]);
}
}
void RectClip64::GetNextLocation(const Path64& path,
Location& loc, int& i, int highI)
Location& loc, size_t& i, size_t highI)
{
switch (loc)
{
@@ -423,28 +424,49 @@ namespace Clipper2Lib {
} //switch
}
bool StartLocsAreClockwise(const std::vector<Location>& startlocs)
{
int result = 0;
for (size_t i = 1; i < startlocs.size(); ++i)
{
int d = static_cast<int>(startlocs[i]) - static_cast<int>(startlocs[i - 1]);
switch (d)
{
case -1: result -= 1; break;
case 1: result += 1; break;
case -3: result += 1; break;
case 3: result -= 1; break;
}
}
return result > 0;
}
void RectClip64::ExecuteInternal(const Path64& path)
{
int i = 0, highI = static_cast<int>(path.size()) - 1;
if (path.size() < 1)
return;
size_t highI = path.size() - 1;
Location prev = Location::Inside, loc;
Location crossing_loc = Location::Inside;
Location first_cross_ = Location::Inside;
if (!GetLocation(rect_, path[highI], loc))
{
i = highI - 1;
while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i;
if (i < 0)
size_t i = highI;
while (i > 0 && !GetLocation(rect_, path[i - 1], prev))
--i;
if (i == 0)
{
// all of path must be inside fRect
for (const auto& pt : path) Add(pt);
return;
}
if (prev == Location::Inside) loc = Location::Inside;
i = 0;
}
Location startingLoc = loc;
Location starting_loc = loc;
///////////////////////////////////////////////////
size_t i = 0;
while (i <= highI)
{
prev = loc;
@@ -543,7 +565,7 @@ namespace Clipper2Lib {
if (first_cross_ == Location::Inside)
{
// path never intersects
if (startingLoc != Location::Inside)
if (starting_loc != Location::Inside)
{
// path is outside rect
// but being outside, it still may not contain rect
@@ -552,11 +574,13 @@ namespace Clipper2Lib {
{
// yep, the path does fully contain rect
// so add rect to the solution
bool is_clockwise_path = StartLocsAreClockwise(start_locs_);
for (size_t j = 0; j < 4; ++j)
{
Add(rect_as_path_[j]);
size_t k = is_clockwise_path ? j : 3 - j; // reverses result path
Add(rect_as_path_[k]);
// we may well need to do some splitting later, so
AddToEdge(edges_[j * 2], results_[0]);
AddToEdge(edges_[k * 2], results_[0]);
}
}
}
@@ -589,8 +613,7 @@ namespace Clipper2Lib {
OutPt2* op2 = op;
do
{
if (!CrossProduct(op2->prev->pt,
op2->pt, op2->next->pt))
if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt))
{
if (op2 == op)
{
@@ -640,7 +663,7 @@ namespace Clipper2Lib {
}
}
void RectClip64::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw)
void RectClip64::TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw)
{
if (ccw.empty()) return;
bool isHorz = ((idx == 1) || (idx == 3));
@@ -825,8 +848,8 @@ namespace Clipper2Lib {
OutPt2* op2 = op->next;
while (op2 && op2 != op)
{
if (CrossProduct(op2->prev->pt,
op2->pt, op2->next->pt) == 0)
if (IsCollinear(op2->prev->pt,
op2->pt, op2->next->pt))
{
op = op2->prev;
op2 = UnlinkOp(op2);
@@ -868,7 +891,7 @@ namespace Clipper2Lib {
ExecuteInternal(path);
CheckEdges();
for (int i = 0; i < 4; ++i)
for (size_t i = 0; i < 4; ++i)
TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]);
for (OutPt2*& op : results_)
@@ -925,7 +948,7 @@ namespace Clipper2Lib {
op_container_ = std::deque<OutPt2>();
start_locs_.clear();
int i = 1, highI = static_cast<int>(path.size()) - 1;
size_t i = 1, highI = path.size() - 1;
Location prev = Location::Inside, loc;
Location crossing_loc;