Update Clipper2 1.3.0 to 1.4.0 (#2030)

This commit is contained in:
aismann 2024-07-13 16:12:06 +02:00 committed by GitHub
parent 01d223a5c9
commit 94069cb5cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 831 additions and 591 deletions

2
3rdparty/README.md vendored
View File

@ -32,7 +32,7 @@
## Clipper2
- [![Upstream](https://img.shields.io/github/v/tag/AngusJohnson/Clipper2?label=Upstream)](https://github.com/AngusJohnson/Clipper2)
- Version: 1.3.0
- Version: 1.4.0
- License: BSL-1.0
## ConcurrentQueue

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"
namespace Clipper2Lib
@ -49,19 +50,19 @@ namespace Clipper2Lib
// error codes (2^n)
const int precision_error_i = 1; // non-fatal
const int scale_error_i = 2; // non-fatal
const int non_pair_error_i = 4; // non-fatal
const int undefined_error_i = 32; // fatal
const int scale_error_i = 2; // non-fatal
const int non_pair_error_i = 4; // non-fatal
const int undefined_error_i = 32; // fatal
const int range_error_i = 64;
#ifndef PI
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;
@ -72,7 +73,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)
@ -93,6 +94,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.
@ -111,8 +119,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_));
@ -136,11 +144,17 @@ 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);
}
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);
@ -159,8 +173,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_));
@ -178,7 +192,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
{
@ -242,6 +256,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 ------------------------------------------------------------------------
@ -273,10 +295,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; }
@ -327,7 +358,7 @@ namespace Clipper2Lib
};
bool operator==(const Rect<T>& other) const {
return left == other.left && right == other.right &&
return left == other.left && right == other.right &&
top == other.top && bottom == other.bottom;
}
@ -342,8 +373,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));
@ -352,32 +383,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;
@ -391,17 +414,52 @@ 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)
{
if (p.x < xmin) xmin = p.x;
if (p.x > xmax) xmax = p.x;
if (p.y < ymin) ymin = p.y;
if (p.y > ymax) ymax = p.y;
if (p.x < xmin) xmin = p.x;
if (p.x > xmax) xmax = p.x;
if (p.y < ymin) ymin = p.y;
if (p.y > ymax) ymax = p.y;
}
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);
}
@ -429,7 +487,7 @@ namespace Clipper2Lib
template <typename T1, typename T2>
inline Path<T1> ScalePath(const Path<T2>& path,
inline Path<T1> ScalePath(const Path<T2>& path,
double scale_x, double scale_y, int& error_code)
{
Path<T1> result;
@ -445,11 +503,11 @@ namespace Clipper2Lib
result.reserve(path.size());
#ifdef USINGZ
std::transform(path.begin(), path.end(), back_inserter(result),
[scale_x, scale_y](const auto& pt)
[scale_x, scale_y](const auto& pt)
{ return Point<T1>(pt.x * scale_x, pt.y * scale_y, pt.z); });
#else
std::transform(path.begin(), path.end(), back_inserter(result),
[scale_x, scale_y](const auto& pt)
[scale_x, scale_y](const auto& pt)
{ return Point<T1>(pt.x * scale_x, pt.y * scale_y); });
#endif
return result;
@ -463,20 +521,19 @@ namespace Clipper2Lib
}
template <typename T1, typename T2>
inline Paths<T1> ScalePaths(const Paths<T2>& paths,
inline Paths<T1> ScalePaths(const Paths<T2>& paths,
double scale_x, double scale_y, int& error_code)
{
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 ||
(r.bottom * scale_y) > max_coord)
{
{
error_code |= range_error_i;
DoError(range_error_i);
return result; // empty path
@ -491,7 +548,7 @@ namespace Clipper2Lib
}
template <typename T1, typename T2>
inline Paths<T1> ScalePaths(const Paths<T2>& paths,
inline Paths<T1> ScalePaths(const Paths<T2>& paths,
double scale, int& error_code)
{
return ScalePaths<T1, T2>(paths, scale, scale, error_code);
@ -588,20 +645,90 @@ 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)
{
#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));
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;
#endif
}
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 -
@ -633,15 +760,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>
@ -661,7 +790,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>
@ -679,16 +808,73 @@ namespace Clipper2Lib
template <typename T>
inline bool IsPositive(const Path<T>& poly)
{
// A curve has positive orientation [and area] if a region 'R'
// A curve has positive orientation [and area] if a region 'R'
// is on the left when traveling around the outside of 'R'.
//https://mathworld.wolfram.com/CurveOrientation.html
//nb: This statement is premised on using Cartesian coordinates
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);
double dy1 = static_cast<double>(ln1b.y - ln1a.y);
@ -698,15 +884,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)
@ -722,10 +937,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);
}
}
@ -741,7 +956,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)));
@ -768,7 +983,7 @@ namespace Clipper2Lib
return PointInPolygonResult::IsOutside;
bool is_above = first->y < pt.y, starting_above = is_above;
curr = first +1;
curr = first +1;
while (true)
{
if (curr == cend)
@ -777,7 +992,7 @@ namespace Clipper2Lib
cend = first;
curr = cbegin;
}
if (is_above)
{
while (curr != cend && curr->y < pt.y) ++curr;
@ -789,14 +1004,14 @@ namespace Clipper2Lib
if (curr == cend) continue;
}
if (curr == cbegin)
if (curr == cbegin)
prev = polygon.cend() - 1; //nb: NOT cend (since might equal first)
else
else
prev = curr - 1;
if (curr->y == pt.y)
{
if (curr->x == pt.x ||
if (curr->x == pt.x ||
(curr->y == prev->y &&
((pt.x < prev->x) != (pt.x < curr->x))))
return PointInPolygonResult::IsOn;
@ -820,7 +1035,7 @@ namespace Clipper2Lib
is_above = !is_above;
++curr;
}
if (is_above != starting_above)
{
cend = polygon.cend();

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 *
*******************************************************************************/
@ -33,7 +33,7 @@ namespace Clipper2Lib {
//Note: all clipping operations except for Difference are commutative.
enum class ClipType { None, Intersection, Union, Difference, Xor };
enum class PathType { Subject, Clip };
enum class JoinWith { None, Left, Right };
@ -41,7 +41,7 @@ namespace Clipper2Lib {
None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
};
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
{
return (enum VertexFlags)(uint32_t(a) & uint32_t(b));
}
@ -95,7 +95,7 @@ namespace Clipper2Lib {
Path64 path;
bool is_open = false;
~OutRec() {
~OutRec() {
if (splits) delete splits;
// nb: don't delete the split pointers
// as these are owned by ClipperBase's outrec_list_
@ -106,7 +106,7 @@ namespace Clipper2Lib {
//Important: UP and DOWN here are premised on Y-axis positive down
//displays, which is the orientation used in Clipper's development.
///////////////////////////////////////////////////////////////////
struct Active {
Point64 bot;
Point64 top;
@ -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);
@ -240,7 +240,7 @@ namespace Clipper2Lib {
void SwapPositionsInAEL(Active& edge1, Active& edge2);
OutRec* NewOutRec();
OutPt* AddOutPt(const Active &e, const Point64& pt);
OutPt* AddLocalMinPoly(Active &e1, Active &e2,
OutPt* AddLocalMinPoly(Active &e1, Active &e2,
const Point64& pt, bool is_new = false);
OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
void DoHorizontal(Active &horz);
@ -251,13 +251,13 @@ namespace Clipper2Lib {
void JoinOutrecPaths(Active &e1, Active &e2);
void FixSelfIntersects(OutRec* outrec);
void DoSplitOp(OutRec* outRec, OutPt* splitOp);
inline void AddTrialHorzJoin(OutPt* op);
void ConvertHorzSegsToJoins();
void ProcessHorzJoins();
void Split(Active& e, const Point64& pt);
inline void CheckJoinLeft(Active& e,
inline void CheckJoinLeft(Active& e,
const Point64& pt, bool check_curr_x = false);
inline void CheckJoinRight(Active& e,
const Point64& pt, bool check_curr_x = false);
@ -326,12 +326,12 @@ namespace Clipper2Lib {
const PolyPath* Parent() const { return parent_; }
bool IsHole() const
bool IsHole() const
{
unsigned lvl = Level();
//Even levels except level 0
return lvl && !(lvl & 1);
}
}
};
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
@ -343,15 +343,16 @@ 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);
}
PolyPath64* operator [] (size_t index) const
{
{
return childs_[index].get(); //std::unique_ptr
}
}
PolyPath64* Child(size_t index) const
{
@ -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,12 +399,25 @@ 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);
}
PolyPathD* operator [] (size_t index) const
{
{
return childs_[index].get();
}
@ -420,22 +431,15 @@ namespace Clipper2Lib {
void SetScale(double value) { scale_ = value; }
double Scale() const { return scale_; }
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
@ -488,7 +492,7 @@ namespace Clipper2Lib {
return Execute(clip_type, fill_rule, closed_paths, dummy);
}
bool Execute(ClipType clip_type, FillRule fill_rule,
bool Execute(ClipType clip_type, FillRule fill_rule,
Paths64& closed_paths, Paths64& open_paths)
{
closed_paths.clear();
@ -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,
@ -560,12 +564,12 @@ namespace Clipper2Lib {
void CheckCallback()
{
if(zCallbackD_)
// if the user defined float point callback has been assigned
// if the user defined float point callback has been assigned
// then assign the proxy callback function
ClipperBase::zCallback_ =
ClipperBase::zCallback_ =
std::bind(&ClipperD::ZCB, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5);
std::placeholders::_4, std::placeholders::_5);
else
ClipperBase::zCallback_ = nullptr;
}
@ -632,6 +636,6 @@ namespace Clipper2Lib {
};
} // namespace
} // namespace
#endif // CLIPPER_ENGINE_H

View File

@ -1,14 +1,14 @@
/*******************************************************************************
* 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 *
*******************************************************************************/
/*
/*
Boolean clipping:
cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4
fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3
@ -19,12 +19,12 @@
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
These are arrays of consecutive x and y path coordinates preceeded by
a pair of values containing the path's length (N) and a 0 value.
__________________________________
|counter|coord1|coord2|...|coordN|
@ -34,23 +34,24 @@ __________________________________
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 | |
_______________________________
CPolytree64 and CPolytreeD:
These are also arrays consisting of CPolyPath structures that represent
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.
CPolyPath64 and CPolyPathD:
These are simple arrays consisting of a series of path coordinates followed
by any number of child (ie nested) CPolyPath. Preceeding these are two values
These are simple arrays consisting of a series of path coordinates followed
by any number of child (ie nested) CPolyPath. Preceeding these are two values
indicating the length of the path (N) and the number of child CPolyPath (C).
____________________________________________________________
|counter|coord1|coord2|...|coordN| child1|child2|...|childC|
@ -58,19 +59,20 @@ ____________________________________________________________
____________________________________________________________
As mentioned above, the very first CPolyPath structure is just a container
that owns (both directly and indirectly) every other CPolyPath in the tree.
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.
These array structures are allocated in heap memory which will eventually
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).
*/
@ -128,7 +130,7 @@ inline Rect<T> CRectToRect(const CRect<T>& rect)
#ifdef _WIN32
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
#else
#define EXTERN_DLL_EXPORT extern "C"
#define EXTERN_DLL_EXPORT extern "C"
#endif
@ -173,8 +175,8 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
bool preserve_collinear = true, bool reverse_solution = false);
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
double delta, uint8_t jointype, uint8_t endtype,
double miter_limit = 2.0, double arc_tolerance = 0.0,
double delta, uint8_t jointype, uint8_t endtype,
double miter_limit = 2.0, double arc_tolerance = 0.0,
bool reverse_solution = false);
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
double delta, uint8_t jointype, uint8_t endtype,
@ -219,10 +221,10 @@ static size_t GetPolyPath64ArrayLen(const PolyPath64& pp)
return result;
}
static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree,
static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree,
size_t& cnt, size_t& array_len)
{
cnt = tree.Count(); // nb: top level count only
cnt = tree.Count(); // nb: top level count only
array_len = GetPolyPath64ArrayLen(tree);
}
@ -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);
@ -300,17 +319,17 @@ static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale)
{
Paths64 result;
if (!paths) return result;
double* v = paths;
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;
@ -362,7 +381,7 @@ EXTERN_DLL_EXPORT const char* Version()
return CLIPPER2_VERSION;
}
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPaths64& solution, CPaths64& solution_open,
@ -370,7 +389,7 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
{
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
Paths64 sub, sub_open, clp, sol, sol_open;
sub = ConvertCPaths(subjects);
sub_open = ConvertCPaths(subjects_open);
@ -382,7 +401,7 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open))
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open))
return -1; // clipping bug - should never happen :)
solution = CreateCPaths(sol);
solution_open = CreateCPaths(sol_open);
@ -455,7 +474,7 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
if (precision < -8 || precision > 8) return -5;
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
double scale = std::pow(10, precision);
int err = 0;
@ -485,10 +504,10 @@ EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
{
Paths64 pp;
pp = ConvertCPaths(paths);
ClipperOffset clip_offset( miter_limit,
ClipperOffset clip_offset( miter_limit,
arc_tolerance, reverse_solution);
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
Paths64 result;
Paths64 result;
clip_offset.Execute(delta, result);
return CreateCPaths(result);
}
@ -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 *
*******************************************************************************/
@ -24,7 +24,7 @@ namespace Clipper2Lib {
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips)
{
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
@ -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);
@ -58,12 +58,12 @@ namespace Clipper2Lib {
}
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
const PathsD& subjects, const PathsD& clips,
const PathsD& subjects, const PathsD& clips,
PolyTreeD& polytree, int precision = 2)
{
polytree.Clear();
int error_code = 0;
CheckPrecision(precision, error_code);
CheckPrecisionRange(precision, error_code);
if (error_code) return;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
@ -75,7 +75,7 @@ namespace Clipper2Lib {
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
}
inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
@ -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);
@ -145,11 +145,11 @@ namespace Clipper2Lib {
}
inline PathsD InflatePaths(const PathsD& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0,
JoinType jt, EndType et, double miter_limit = 2.0,
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,13 +219,13 @@ 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);
RectClip64 rc(r);
Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code);
if (error_code) return PathsD(); // ie: error_code result is lost
if (error_code) return PathsD(); // ie: error_code result is lost
return ScalePaths<double, int64_t>(
rc.Execute(pp), 1 / scale, error_code);
}
@ -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);
@ -290,8 +290,8 @@ namespace Clipper2Lib {
{
// return false if this child isn't fully contained by its parent
// checking for a single vertex outside is a bit too crude since
// it doesn't account for rounding errors. It's better to check
// checking for a single vertex outside is a bit too crude since
// it doesn't account for rounding errors. It's better to check
// for consecutive vertices found outside the parent's polygon.
int outsideCnt = 0;
@ -311,7 +311,7 @@ namespace Clipper2Lib {
return true;
}
static void OutlinePolyPath(std::ostream& os,
static void OutlinePolyPath(std::ostream& os,
size_t idx, bool isHole, size_t count, const std::string& preamble)
{
std::string plural = (count == 1) ? "." : "s.";
@ -342,7 +342,7 @@ namespace Clipper2Lib {
}
template<typename T, typename U>
inline constexpr void MakePathGeneric(const T an_array,
inline constexpr void MakePathGeneric(const T an_array,
size_t array_size, std::vector<U>& result)
{
result.reserve(array_size / 2);
@ -354,7 +354,7 @@ namespace Clipper2Lib {
#endif
}
} // end details namespace
} // end details namespace
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
{
@ -398,7 +398,7 @@ namespace Clipper2Lib {
inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
{
for (const auto& child : polytree)
if (child->Count() > 0 &&
if (child->Count() > 0 &&
!details::PolyPath64ContainsChildren(*child))
return false;
return true;
@ -471,7 +471,7 @@ namespace Clipper2Lib {
std::size_t size = N / 3;
Path64 result(size);
for (size_t i = 0; i < size; ++i)
result[i] = Point64(list[i * 3],
result[i] = Point64(list[i * 3],
list[i * 3 + 1], list[i * 3 + 2]);
return result;
}
@ -489,7 +489,7 @@ namespace Clipper2Lib {
list[i * 3 + 1], list[i * 3 + 2]);
else
for (size_t i = 0; i < size; ++i)
result[i] = PointD(list[i * 3], list[i * 3 + 1],
result[i] = PointD(list[i * 3], list[i * 3 + 1],
static_cast<int64_t>(list[i * 3 + 2]));
return result;
}
@ -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);
@ -580,23 +580,23 @@ namespace Clipper2Lib {
double cp = std::abs(CrossProduct(pt1, pt2, pt3));
return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
}
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,
return Ellipse(rect.MidPoint(),
static_cast<double>(rect.Width()) *0.5,
static_cast<double>(rect.Height()) * 0.5, steps);
}
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,19 +614,7 @@ 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,
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& flags)
{
++current;
@ -637,7 +625,7 @@ namespace Clipper2Lib {
return current;
}
inline size_t GetPrior(size_t current, size_t high,
inline size_t GetPrior(size_t current, size_t high,
const std::vector<bool>& flags)
{
if (current == 0) current = high;
@ -650,7 +638,7 @@ namespace Clipper2Lib {
}
template <typename T>
inline Path<T> SimplifyPath(const Path<T> &path,
inline Path<T> SimplifyPath(const Path<T> &path,
double epsilon, bool isClosedPath = true)
{
const size_t len = path.size(), high = len -1;
@ -665,7 +653,7 @@ namespace Clipper2Lib {
distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
}
else
else
{
distSqr[0] = MAX_DBL;
distSqr[high] = MAX_DBL;
@ -684,7 +672,7 @@ namespace Clipper2Lib {
} while (curr != start && distSqr[curr] > epsSqr);
if (curr == start) break;
}
prior = GetPrior(curr, high, flags);
next = GetNext(curr, high, flags);
if (next == prior) break;
@ -699,7 +687,7 @@ namespace Clipper2Lib {
}
else
prior2 = GetPrior(prior, high, flags);
flags[curr] = true;
curr = next;
next = GetNext(next, high, flags);
@ -717,7 +705,7 @@ namespace Clipper2Lib {
}
template <typename T>
inline Paths<T> SimplifyPaths(const Paths<T> &paths,
inline Paths<T> SimplifyPaths(const Paths<T> &paths,
double epsilon, bool isClosedPath = true)
{
Paths<T> result;

View File

@ -15,7 +15,7 @@
#include <string>
#include "clipper2/clipper.core.h"
namespace Clipper2Lib
namespace Clipper2Lib
{
namespace detail

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);
@ -83,7 +83,7 @@ private:
public:
explicit ClipperOffset(double miter_limit = 2.0,
double arc_tolerance = 0.0,
bool preserve_collinear = false,
bool preserve_collinear = false,
bool reverse_solution = false) :
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
preserve_collinear_(preserve_collinear),
@ -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,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;
@ -188,7 +189,7 @@ namespace Clipper2Lib {
}
//PrevPrevVertex: useful to get the (inverted Y-axis) top of the
//alternate edge (ie left or right bound) during edge insertion.
//alternate edge (ie left or right bound) during edge insertion.
inline Vertex* PrevPrevVertex(const Active& ae)
{
if (ae.wind_dx > 0)
@ -233,15 +234,15 @@ namespace Clipper2Lib {
Vertex* result = e.vertex_top;
if (e.wind_dx > 0)
while ((result->next->pt.y == result->pt.y) &&
((result->flags & (VertexFlags::OpenEnd |
((result->flags & (VertexFlags::OpenEnd |
VertexFlags::LocalMax)) == VertexFlags::None))
result = result->next;
else
while (result->prev->pt.y == result->pt.y &&
((result->flags & (VertexFlags::OpenEnd |
((result->flags & (VertexFlags::OpenEnd |
VertexFlags::LocalMax)) == VertexFlags::None))
result = result->prev;
if (!IsMaxima(*result)) result = nullptr; // not a maxima
if (!IsMaxima(*result)) result = nullptr; // not a maxima
return result;
}
@ -252,7 +253,7 @@ namespace Clipper2Lib {
while (result->next->pt.y == result->pt.y) result = result->next;
else
while (result->prev->pt.y == result->pt.y) result = result->prev;
if (!IsMaxima(*result)) result = nullptr; // not a maxima
if (!IsMaxima(*result)) result = nullptr; // not a maxima
return result;
}
@ -613,13 +614,13 @@ namespace Clipper2Lib {
list.push_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));
}
void AddPaths_(const Paths64& paths, PathType polytype, bool is_open,
void AddPaths_(const Paths64& paths, PathType polytype, bool is_open,
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;
@ -810,7 +811,7 @@ namespace Clipper2Lib {
void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip)
{
if (!zCallback_) return;
// prioritize subject over clip vertices by passing
// prioritize subject over clip vertices by passing
// subject vertices before clip vertices in the callback
if (GetPolyType(e1) == PathType::Subject)
{
@ -845,11 +846,11 @@ namespace Clipper2Lib {
if (is_open) has_open_paths_ = true;
minima_list_sorted_ = false;
AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_);
}
}
void ClipperBase::AddReuseableData(const ReuseableDataContainer64& reuseable_data)
void ClipperBase::AddReuseableData(const ReuseableDataContainer64& reuseable_data)
{
// nb: reuseable_data will continue to own the vertices
// nb: reuseable_data will continue to own the vertices
// and remains responsible for their clean up.
succeeded_ = false;
minima_list_sorted_ = false;
@ -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,
@ -1385,7 +1385,7 @@ namespace Clipper2Lib {
{
if (IsJoined(e1)) Split(e1, pt);
if (IsJoined(e2)) Split(e2, pt);
if (IsFront(e1) == IsFront(e2))
{
if (IsOpenEnd(e1))
@ -1409,7 +1409,7 @@ namespace Clipper2Lib {
{
Active* e = GetPrevHotEdge(e1);
if (!e)
outrec.owner = nullptr;
outrec.owner = nullptr;
else
SetOwner(&outrec, e->outrec);
// nb: outRec.owner here is likely NOT the real
@ -1476,7 +1476,7 @@ namespace Clipper2Lib {
e2.outrec->pts = e1.outrec->pts;
e1.outrec->pts = nullptr;
}
else
else
SetOwner(e2.outrec, e1.outrec);
//and e1 and e2 are maxima and are about to be dropped from the Actives list.
@ -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))
@ -1566,14 +1565,14 @@ namespace Clipper2Lib {
void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp)
{
// splitOp.prev -> splitOp &&
// splitOp.prev -> splitOp &&
// splitOp.next -> splitOp.next.next are intersecting
OutPt* prevOp = splitOp->prev;
OutPt* nextNextOp = splitOp->next->next;
outrec->pts = prevOp;
Point64 ip;
GetIntersectPoint(prevOp->pt, splitOp->pt,
GetSegmentIntersectPt(prevOp->pt, splitOp->pt,
splitOp->next->pt, nextNextOp->pt, ip);
#ifdef USINGZ
@ -1617,7 +1616,7 @@ namespace Clipper2Lib {
{
OutRec* newOr = NewOutRec();
newOr->owner = outrec->owner;
splitOp->outrec = newOr;
splitOp->next->outrec = newOr;
OutPt* newOp = new OutPt(ip, newOr);
@ -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)
@ -2065,7 +2111,7 @@ namespace Clipper2Lib {
e->next_in_sel = e->next_in_ael;
e->jump = e->next_in_sel;
if (e->join_with == JoinWith::Left)
e->curr_x = e->prev_in_ael->curr_x; // also avoids complications
e->curr_x = e->prev_in_ael->curr_x; // also avoids complications
else
e->curr_x = TopX(*e, top_y);
e = e->next_in_ael;
@ -2138,7 +2184,7 @@ namespace Clipper2Lib {
if (outrecHasEdges)
{
OutPt* opA = outrec->pts, * opZ = opA->next;
while (opP != opZ && opP->prev->pt.y == curr_y)
while (opP != opZ && opP->prev->pt.y == curr_y)
opP = opP->prev;
while (opN != opA && opN->next->pt.y == curr_y)
opN = opN->next;
@ -2150,7 +2196,7 @@ namespace Clipper2Lib {
while (opN->next != opP && opN->next->pt.y == curr_y)
opN = opN->next;
}
bool result =
bool result =
SetHorzSegHeadingForward(hs, opP, opN) &&
!hs.left_op->horz;
@ -2160,13 +2206,14 @@ namespace Clipper2Lib {
hs.right_op = nullptr; // (for sorting)
return result;
}
void ClipperBase::ConvertHorzSegsToJoins()
{
auto j = std::count_if(horz_seg_list_.begin(),
auto j = std::count_if(horz_seg_list_.begin(),
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;
@ -2207,8 +2254,8 @@ namespace Clipper2Lib {
DuplicateOp(hs1->left_op, false));
horz_join_list_.push_back(join);
}
}
}
}
}
}
void MoveSplits(OutRec* fromOr, OutRec* toOr)
@ -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
@ -2321,7 +2368,7 @@ namespace Clipper2Lib {
ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);
else if (abs_dx2 > 100)
ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);
else
else
{
if (ip.y < top_y) ip.y = top_y;
else ip.y = bot_y_;
@ -2453,7 +2500,7 @@ namespace Clipper2Lib {
horz_seg_list_.push_back(HorzSegment(op));
}
bool ClipperBase::ResetHorzDirection(const Active& horz,
bool ClipperBase::ResetHorzDirection(const Active& horz,
const Vertex* max_vertex, int64_t& horz_left, int64_t& horz_right)
{
if (horz.bot.x == horz.top.x)
@ -2536,8 +2583,8 @@ namespace Clipper2Lib {
if (IsHotEdge(horz) && IsJoined(*e))
Split(*e, e->top);
//if (IsHotEdge(horz) != IsHotEdge(*e))
// DoError(undefined_error_i);
//if (IsHotEdge(horz) != IsHotEdge(*e))
// DoError(undefined_error_i);
if (IsHotEdge(horz))
{
@ -2641,7 +2688,7 @@ namespace Clipper2Lib {
ResetHorzDirection(horz, vertex_max, horz_left, horz_right);
}
if (IsHotEdge(horz))
if (IsHotEdge(horz))
{
OutPt* op = AddOutPt(horz, horz.top);
AddTrialHorzJoin(op);
@ -2754,21 +2801,23 @@ namespace Clipper2Lib {
}
}
void ClipperBase::CheckJoinLeft(Active& e,
void ClipperBase::CheckJoinLeft(Active& e,
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
((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);
@ -2780,22 +2829,24 @@ namespace Clipper2Lib {
e.join_with = JoinWith::Left;
}
void ClipperBase::CheckJoinRight(Active& e,
void ClipperBase::CheckJoinRight(Active& e,
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
((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);
else if (e.outrec->idx < next->outrec->idx)
@ -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;
}
@ -2872,8 +2923,8 @@ namespace Clipper2Lib {
if (!outrec->pts) return false;
if (!outrec->bounds.IsEmpty()) return true;
CleanCollinear(outrec);
if (!outrec->pts ||
!BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){
if (!outrec->pts ||
!BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){
return false;}
outrec->bounds = GetBounds(outrec->path);
return true;
@ -2887,10 +2938,10 @@ namespace Clipper2Lib {
if(!split || split == outrec || split->recursive_split == outrec) continue;
split->recursive_split = outrec; // prevent infinite loops
if (split->splits && CheckSplitOwner(outrec, split->splits))
if (split->splits && CheckSplitOwner(outrec, split->splits))
return true;
else if (CheckBounds(split) &&
IsValidOwner(outrec, split) &&
else if (CheckBounds(split) &&
IsValidOwner(outrec, split) &&
split->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, split->pts))
{
@ -2919,7 +2970,7 @@ namespace Clipper2Lib {
if (outrec->owner)
{
if (!outrec->owner->polypath)
if (!outrec->owner->polypath)
RecursiveCheckOwners(outrec->owner, polypath);
outrec->polypath = outrec->owner->polypath->AddChild(outrec->path);
}
@ -2968,7 +3019,7 @@ namespace Clipper2Lib {
open_paths.resize(0);
if (has_open_paths_)
open_paths.reserve(outrec_list_.size());
// outrec_list_.size() is not static here because
// CheckBounds below can indirectly add additional
// OutRec (via FixOutRecPts & CleanCollinear)
@ -2991,7 +3042,7 @@ namespace Clipper2Lib {
bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale)
{
if (!op || op->next == op || (!isOpen && op->next == op->prev))
if (!op || op->next == op || (!isOpen && op->next == op->prev))
return false;
path.resize(0);
@ -3024,7 +3075,7 @@ namespace Clipper2Lib {
#else
path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale));
#endif
}
if (reverse)
op2 = op2->prev;

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)
{
++i;
if (!r.IsValid()) continue; // ignore invalid paths
else if (r.bottom > botPt.y || (r.bottom == botPt.y && r.left < botPt.x))
for (size_t i = 0; i < paths.size(); ++i)
{
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;
@ -96,14 +55,14 @@ inline bool AlmostZero(double value, double epsilon = 0.001)
return std::fabs(value) < epsilon;
}
inline double Hypot(double x, double y)
inline double Hypot(double x, double y)
{
//see https://stackoverflow.com/a/32436148/359538
return std::sqrt(x * x + y * y);
}
inline PointD NormalizeVector(const PointD& vec)
{
{
double h = Hypot(vec.x, vec.y);
if (AlmostZero(h)) return PointD(0,0);
double inverseHypot = 1 / h;
@ -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));
@ -284,7 +197,7 @@ void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
{
PointD vec;
if (j == k)
if (j == k)
vec = PointD(norms[j].y, -norms[j].x);
else
vec = GetAvgUnitVector(
@ -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)));
@ -343,7 +252,7 @@ void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a
void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle)
{
if (deltaCallback64_) {
// when deltaCallback64_ is assigned, group_delta_ won't be constant,
// when deltaCallback64_ is assigned, group_delta_ won't be constant,
// so we'll need to do the following calculations for *every* vertex.
double abs_delta = std::fabs(group_delta_);
double arcTol = (arc_tolerance_ > floating_point_tolerance ?
@ -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,18 +313,29 @@ 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)
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
{
// almost straight - less than 2.5 degree (#424, #482, #526 & #724)
// almost straight - less than 2.5 degree (#424, #482, #526 & #724)
DoMiter(path, j, k, cos_a);
}
else if (join_type_ == JoinType::Miter)
@ -435,9 +355,9 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size
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);
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);
}
void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
@ -445,8 +365,8 @@ void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
OffsetPolygon(group, 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());
@ -459,7 +379,7 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
{
// do the line start cap
if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
if (std::fabs(group_delta_) <= floating_point_tolerance)
path_out.push_back(path[0]);
else
@ -477,13 +397,13 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
break;
}
}
size_t highI = path.size() - 1;
// offset the left side going forward
for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
OffsetPoint(group, path, j, k);
// reverse normals
// reverse normals
for (size_t i = highI; i > 0; --i)
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
norms[0] = norms[highI];
@ -510,41 +430,34 @@ 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)
{
if (group.end_type == EndType::Polygon)
{
// a straight path (2 points) can now also be 'polygon' offset
// 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,19 +506,14 @@ void ClipperOffset::DoGroupOffset(Group& group)
for (auto& p : path_out) p.z = pt.z;
#endif
}
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;
solution->push_back(path_out);
continue;
} // end of offsetting a single point
if ((pathLen == 2) && (group.end_type == EndType::Joined))
end_type_ = (group.join_type == JoinType::Round) ?
EndType::Round :
end_type_ = (group.join_type == JoinType::Round) ?
EndType::Round :
EndType::Square;
BuildNormals(*path_in_it);
@ -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,40 +558,35 @@ 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
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));
}
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
2.0 / (miter_limit_ * miter_limit_);
delta_ = delta;
std::vector<Group>::iterator git;
for (git = groups_.begin(); git != groups_.end(); ++git)
else
{
DoGroupOffset(*git);
if (!error_code_) continue; // all OK
solution.clear();
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
2.0 / (miter_limit_ * miter_limit_);
delta_ = delta;
std::vector<Group>::iterator git;
for (git = groups_.begin(); git != groups_.end(); ++git)
{
DoGroupOffset(*git);
if (!error_code_) continue; // all OK
solution->clear();
}
}
}
void ClipperOffset::Execute(double delta, Paths64& paths)
{
paths.clear();
ExecuteInternal(delta);
if (!solution.size()) return;
if (!solution->size()) return;
bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
@ -677,41 +595,45 @@ void ClipperOffset::Execute(double delta, Paths64& paths)
//the solution should retain the orientation of the input
c.ReverseSolution(reverse_solution_ != paths_reversed);
#ifdef USINGZ
if (zCallback64_) { c.SetZCallback(zCallback64_); }
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 (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, paths);
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
c.Execute(ClipType::Union, FillRule::Positive, paths);
{
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);
}
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,
@ -125,7 +125,7 @@ namespace Clipper2Lib {
{
case Location::Left:
if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) return true;
else if ((p.y < rectPath[0].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip))
else if ((p.y < rectPath[0].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip))
{
loc = Location::Top;
return true;
@ -180,7 +180,7 @@ namespace Clipper2Lib {
else return false;
default: // loc == rInside
if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip))
if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip))
{
loc = Location::Left;
return true;
@ -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)
{
@ -420,31 +421,52 @@ namespace Clipper2Lib {
break; //inner loop
}
break;
} //switch
} //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;
@ -454,12 +476,12 @@ namespace Clipper2Lib {
if (i > highI) break;
Point64 ip, ip2;
Point64 prev_pt = (i) ?
path[static_cast<size_t>(i - 1)] :
Point64 prev_pt = (i) ?
path[static_cast<size_t>(i - 1)] :
path[highI];
crossing_loc = loc;
if (!GetIntersection(rect_as_path_,
if (!GetIntersection(rect_as_path_,
path[i], prev_pt, crossing_loc, ip))
{
// ie remaining outside
@ -470,7 +492,7 @@ namespace Clipper2Lib {
start_locs_.push_back(prev);
prev = GetAdjacentLocation(prev, isClockw);
} while (prev != loc);
crossing_loc = crossing_prev; // still not crossed
crossing_loc = crossing_prev; // still not crossed
}
else if (prev != Location::Inside && prev != loc)
{
@ -504,7 +526,7 @@ namespace Clipper2Lib {
}
else if (prev != Location::Inside)
{
// passing right through rect. 'ip' here will be the second
// passing right through rect. 'ip' here will be the second
// intersect pt but we'll also need the first intersect pt (ip2)
loc = prev;
GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2);
@ -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));
@ -648,7 +671,7 @@ namespace Clipper2Lib {
size_t i = 0, j = 0;
OutPt2* p1, * p2, * p1a, * p2a, * op, * op2;
while (i < cw.size())
while (i < cw.size())
{
p1 = cw[i];
if (!p1 || p1->next == p1->prev)
@ -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);
@ -854,7 +877,7 @@ namespace Clipper2Lib {
if (rect_.IsEmpty()) return result;
for (const Path64& path : paths)
{
{
if (path.size() < 3) continue;
path_bounds_ = GetBounds(path);
if (!rect_.Intersects(path_bounds_))
@ -868,9 +891,9 @@ 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_)
{
Path64 tmp = GetPath(op);
@ -925,14 +948,14 @@ 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;
if (!GetLocation(rect_, path[0], loc))
{
while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i;
if (i > highI)
if (i > highI)
{
// all of path must be inside fRect
for (const auto& pt : path) Add(pt);
@ -953,7 +976,7 @@ namespace Clipper2Lib {
Point64 prev_pt = path[static_cast<size_t>(i - 1)];
crossing_loc = loc;
if (!GetIntersection(rect_as_path_,
if (!GetIntersection(rect_as_path_,
path[i], prev_pt, crossing_loc, ip))
{
// ie remaining outside
@ -971,10 +994,10 @@ namespace Clipper2Lib {
}
else if (prev != Location::Inside)
{
// passing right through rect. 'ip' here will be the second
// passing right through rect. 'ip' here will be the second
// intersect pt but we'll also need the first intersect pt (ip2)
crossing_loc = prev;
GetIntersection(rect_as_path_,
GetIntersection(rect_as_path_,
prev_pt, path[i], crossing_loc, ip2);
Add(ip2, true);
Add(ip);
@ -991,14 +1014,14 @@ namespace Clipper2Lib {
{
Path64 result;
if (!op || op == op->next) return result;
op = op->next; // starting at path beginning
op = op->next; // starting at path beginning
result.push_back(op->pt);
OutPt2 *op2 = op->next;
while (op2 != op)
{
result.push_back(op2->pt);
op2 = op2->next;
}
}
return result;
}

View File

@ -67,6 +67,7 @@
### 3rdparty Updates
- Clipper2: 1.3.0 ==> 1.4.0
- simdjson: 3.9.2 ==> 3.9.5
- fmtlib: 10.2.1 ==> 11.0.1
- yasio: 4.2.2 ==> 4.2.3