diff --git a/libs/kimath/include/geometry/shape_poly_set.h b/libs/kimath/include/geometry/shape_poly_set.h index 4ec8d82de8..4f983af5de 100644 --- a/libs/kimath/include/geometry/shape_poly_set.h +++ b/libs/kimath/include/geometry/shape_poly_set.h @@ -1397,9 +1397,9 @@ private: void importPaths( Clipper2Lib::Paths64& paths, const std::vector& aZValueBuffer, const std::vector& aArcBuffe ); - void importPolyPath( Clipper2Lib::PolyPath64* aPolyPath, - const std::vector& aZValueBuffer, - const std::vector& aArcBuffer ); + void importPolyPath( const std::unique_ptr& aPolyPath, + const std::vector& aZValueBuffer, + const std::vector& aArcBuffer ); void inflate1( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy ); void inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy ); diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp index 4c129b91c7..5ed38dc643 100644 --- a/libs/kimath/src/geometry/shape_poly_set.cpp +++ b/libs/kimath/src/geometry/shape_poly_set.cpp @@ -1070,17 +1070,9 @@ void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY c.ArcTolerance( std::abs( aAmount ) * coeff ); c.MiterLimit( miterLimit ); - c.MergeGroups( true ); - Paths64 solution = c.Execute( aAmount ); - // We get paths back but we need the tree to assign the holes to the correct - // outlines - Clipper64 c2; PolyTree64 tree; - c2.PreserveCollinear = false; - c2.ReverseSolution = false; - c2.AddSubject( solution ); - c2.Execute(ClipType::Union, FillRule::Positive, tree); + c.Execute( aAmount, tree ); importTree( tree, zValues, arcBuffer ); tree.Clear(); @@ -1120,9 +1112,9 @@ void SHAPE_POLY_SET::importTree( ClipperLib::PolyTree* tree, } -void SHAPE_POLY_SET::importPolyPath( Clipper2Lib::PolyPath64* aPolyPath, - const std::vector& aZValueBuffer, - const std::vector& aArcBuffer ) +void SHAPE_POLY_SET::importPolyPath( const std::unique_ptr& aPolyPath, + const std::vector& aZValueBuffer, + const std::vector& aArcBuffer ) { if( !aPolyPath->IsHole() ) { @@ -1130,11 +1122,11 @@ void SHAPE_POLY_SET::importPolyPath( Clipper2Lib::PolyPath64* aPolyPa paths.reserve( aPolyPath->Count() + 1 ); paths.emplace_back( aPolyPath->Polygon(), aZValueBuffer, aArcBuffer ); - for( Clipper2Lib::PolyPath64* child : *aPolyPath ) + for( const std::unique_ptr& child : *aPolyPath ) { paths.emplace_back( child->Polygon(), aZValueBuffer, aArcBuffer ); - for( Clipper2Lib::PolyPath64* grandchild : *child ) + for( const std::unique_ptr& grandchild : *child ) importPolyPath( grandchild, aZValueBuffer, aArcBuffer ); } @@ -1149,7 +1141,7 @@ void SHAPE_POLY_SET::importTree( Clipper2Lib::PolyTree64& tree, { m_polys.clear(); - for( Clipper2Lib::PolyPath64* n : tree ) + for( const std::unique_ptr& n : tree ) importPolyPath( n, aZValueBuffer, aArcBuffer ); } diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h index 9f4b5f21e4..c7522cb900 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2022 * +* Date : 22 March 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -17,617 +17,827 @@ #include #include #include -#include +#include +#include -namespace Clipper2Lib +namespace Clipper2Lib { - static double const PI = 3.141592653589793238; +#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS) - //By far the most widely used filling rules for polygons are EvenOdd - //and NonZero, sometimes called Alternate and Winding respectively. - //https://en.wikipedia.org/wiki/Nonzero-rule - enum class FillRule { EvenOdd, NonZero, Positive, Negative }; - -// Point ------------------------------------------------------------------------ + class Clipper2Exception : public std::exception { + public: + explicit Clipper2Exception(const char* description) : + m_descr(description) {} + virtual const char* what() const throw() override { return m_descr.c_str(); } + private: + std::string m_descr; + }; -template -struct Point { - T x; - T y; + static const char* precision_error = + "Precision exceeds the permitted range"; + static const char* range_error = + "Values exceed permitted range"; + static const char* scale_error = + "Invalid scale (either 0 or too large)"; + static const char* non_pair_error = + "There must be 2 values for each coordinate"; +#endif + + // 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 range_error_i = 64; + + static const double PI = 3.141592653589793238; + static const int64_t MAX_COORD = INT64_MAX >> 2; + static const int64_t MIN_COORD = -MAX_COORD; + static const int64_t INVALID = INT64_MAX; + const double max_coord = static_cast(MAX_COORD); + const double min_coord = static_cast(MIN_COORD); + + static const double MAX_DBL = (std::numeric_limits::max)(); + + static void DoError(int error_code) + { +#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS) + switch (error_code) + { + case precision_error_i: + throw Clipper2Exception(precision_error); + case scale_error_i: + throw Clipper2Exception(scale_error); + case non_pair_error_i: + throw Clipper2Exception(non_pair_error); + case range_error_i: + throw Clipper2Exception(range_error); + } +#else + ++error_code; // only to stop compiler warning +#endif + } + + //By far the most widely used filling rules for polygons are EvenOdd + //and NonZero, sometimes called Alternate and Winding respectively. + //https://en.wikipedia.org/wiki/Nonzero-rule + enum class FillRule { EvenOdd, NonZero, Positive, Negative }; + + // Point ------------------------------------------------------------------------ + + template + struct Point { + T x; + T y; #ifdef USINGZ - int64_t z; + int64_t z; - template - inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0) - { - if constexpr (std::numeric_limits::is_integer && - !std::numeric_limits::is_integer) - { - x = static_cast(std::round(x_)); - y = static_cast(std::round(y_)); - z = z_; - } - else - { - x = static_cast(x_); - y = static_cast(y_); - z = z_; - } - } + template + inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0) + { + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + x = static_cast(std::round(x_)); + y = static_cast(std::round(y_)); + z = z_; + } + else + { + x = static_cast(x_); + y = static_cast(y_); + z = z_; + } + } - explicit Point() : x(0), y(0), z(0) {}; + explicit Point() : x(0), y(0), z(0) {}; - template - Point(const T2 x_, const T2 y_, const int64_t z_ = 0) - { - Init(x_, y_); - z = z_; - } + template + Point(const T2 x_, const T2 y_, const int64_t z_ = 0) + { + Init(x_, y_); + z = z_; + } - template - explicit Point(const Point& p) - { - Init(p.x, p.y, p.z); - } + template + explicit Point(const Point& p) + { + Init(p.x, p.y, p.z); + } - Point operator * (const double scale) const - { - return Point(x * scale, y * scale, z); - } + Point operator * (const double scale) const + { + return Point(x * scale, y * scale, z); + } - friend std::ostream& operator<<(std::ostream& os, const Point& point) - { - os << point.x << "," << point.y << "," << point.z; - return os; - } + friend std::ostream& operator<<(std::ostream& os, const Point& point) + { + os << point.x << "," << point.y << "," << point.z << " "; + return os; + } #else - template - inline void Init(const T2 x_ = 0, const T2 y_ = 0) - { - if constexpr (std::numeric_limits::is_integer && - !std::numeric_limits::is_integer) - { - x = static_cast(std::round(x_)); - y = static_cast(std::round(y_)); - } - else - { - x = static_cast(x_); - y = static_cast(y_); - } - } + template + inline void Init(const T2 x_ = 0, const T2 y_ = 0) + { + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + x = static_cast(std::round(x_)); + y = static_cast(std::round(y_)); + } + else + { + x = static_cast(x_); + y = static_cast(y_); + } + } - explicit Point() : x(0), y(0) {}; + explicit Point() : x(0), y(0) {}; - template - Point(const T2 x_, const T2 y_) { Init(x_, y_); } + template + Point(const T2 x_, const T2 y_) { Init(x_, y_); } - template - explicit Point(const Point& p) { Init(p.x, p.y); } + template + explicit Point(const Point& p) { Init(p.x, p.y); } - Point operator * (const double scale) const - { - return Point(x * scale, y * scale); - } + Point operator * (const double scale) const + { + return Point(x * scale, y * scale); + } - friend std::ostream& operator<<(std::ostream& os, const Point& point) - { - os << point.x << "," << point.y; - return os; - } + friend std::ostream& operator<<(std::ostream& os, const Point& point) + { + os << point.x << "," << point.y << " "; + return os; + } #endif - friend bool operator==(const Point &a, const Point &b) - { - return a.x == b.x && a.y == b.y; - } + friend bool operator==(const Point& a, const Point& b) + { + return a.x == b.x && a.y == b.y; + } - friend bool operator!=(const Point& a, const Point& b) - { - return !(a == b); - } + friend bool operator!=(const Point& a, const Point& b) + { + return !(a == b); + } - inline Point operator-() const - { - return Point(-x,-y); - } + inline Point operator-() const + { + return Point(-x, -y); + } - inline Point operator+(const Point &b) const - { - return Point(x+b.x, y+b.y); - } + inline Point operator+(const Point& b) const + { + return Point(x + b.x, y + b.y); + } - inline Point operator-(const Point &b) const - { - return Point(x-b.x, y-b.y); - } + inline Point operator-(const Point& b) const + { + return Point(x - b.x, y - b.y); + } - inline void Negate() { x = -x; y = -y; } + inline void Negate() { x = -x; y = -y; } -}; + }; -//nb: using 'using' here (instead of typedef) as they can be used in templates -using Point64 = Point; -using PointD = Point; + //nb: using 'using' here (instead of typedef) as they can be used in templates + using Point64 = Point; + using PointD = Point; -template -using Path = std::vector>; -template -using Paths = std::vector>; + template + using Path = std::vector>; + template + using Paths = std::vector>; -using Path64 = Path; -using PathD = Path; -using Paths64 = std::vector< Path64>; -using PathsD = std::vector< PathD>; + using Path64 = Path; + using PathD = Path; + using Paths64 = std::vector< Path64>; + using PathsD = std::vector< PathD>; -template -std::ostream& operator << (std::ostream& outstream, const Path& path) -{ - if (!path.empty()) - { - auto pt = path.cbegin(), last = path.cend() - 1; - while (pt != last) - outstream << *pt++ << ", "; - outstream << *last << std::endl; - } - return outstream; -} + // Rect ------------------------------------------------------------------------ -template -std::ostream& operator << (std::ostream& outstream, const Paths& paths) -{ - for (auto p : paths) - outstream << p; - return outstream; -} + template + struct Rect; -template -inline Path ScalePath(const Path& path, double scale) -{ - Path result; - result.reserve(path.size()); + using Rect64 = Rect; + using RectD = Rect; + + template + struct Rect { + T left; + T top; + T right; + T bottom; + + Rect() : + left(0), + top(0), + right(0), + bottom(0) {} + + Rect(T l, T t, T r, T b) : + left(l), + top(t), + right(r), + bottom(b) {} + + Rect(bool is_valid) + { + if (is_valid) + { + left = right = top = bottom = 0; + } + else + { + left = top = std::numeric_limits::max(); + right = bottom = -std::numeric_limits::max(); + } + } + + T Width() const { return right - left; } + T Height() const { return bottom - top; } + void Width(T width) { right = left + width; } + void Height(T height) { bottom = top + height; } + + Point MidPoint() const + { + return Point((left + right) / 2, (top + bottom) / 2); + } + + Path AsPath() const + { + Path result; + result.reserve(4); + result.push_back(Point(left, top)); + result.push_back(Point(right, top)); + result.push_back(Point(right, bottom)); + result.push_back(Point(left, bottom)); + return result; + } + + bool Contains(const Point& pt) const + { + return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom; + } + + bool Contains(const Rect& rec) const + { + return rec.left >= left && rec.right <= right && + rec.top >= top && rec.bottom <= bottom; + } + + void Scale(double scale) { + left *= scale; + top *= scale; + right *= scale; + bottom *= scale; + } + + bool IsEmpty() const { return bottom <= top || right <= left; }; + + bool Intersects(const Rect& rec) const + { + return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) && + ((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom)); + }; + + friend std::ostream& operator<<(std::ostream& os, const Rect& rect) { + os << "(" + << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom + << ")"; + return os; + } + }; + + template + inline Rect ScaleRect(const Rect& rect, double scale) + { + Rect result; + + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + result.left = static_cast(std::round(rect.left * scale)); + result.top = static_cast(std::round(rect.top * scale)); + result.right = static_cast(std::round(rect.right * scale)); + result.bottom = static_cast(std::round(rect.bottom * scale)); + } + else + { + result.left = rect.left * scale; + result.top = rect.top * scale; + result.right = rect.right * scale; + result.bottom = rect.bottom * scale; + } + return result; + } + + static const Rect64 MaxInvalidRect64 = Rect64( + INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN); + static const RectD MaxInvalidRectD = RectD( + MAX_DBL, MAX_DBL, -MAX_DBL, -MAX_DBL); + + template + Rect GetBounds(const Path& path) + { + auto xmin = std::numeric_limits::max(); + auto ymin = std::numeric_limits::max(); + auto xmax = std::numeric_limits::lowest(); + auto ymax = std::numeric_limits::lowest(); + for (const auto& p : path) + { + 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(xmin, ymin, xmax, ymax); + } + + template + Rect GetBounds(const Paths& paths) + { + auto xmin = std::numeric_limits::max(); + auto ymin = std::numeric_limits::max(); + auto xmax = std::numeric_limits::lowest(); + auto ymax = std::numeric_limits::lowest(); + for (const Path& path : paths) + for (const Point& 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; + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + std::ostream& operator << (std::ostream& outstream, const Path& path) + { + if (!path.empty()) + { + auto pt = path.cbegin(), last = path.cend() - 1; + while (pt != last) + outstream << *pt++ << ", "; + outstream << *last << std::endl; + } + return outstream; + } + + template + std::ostream& operator << (std::ostream& outstream, const Paths& paths) + { + for (auto p : paths) + outstream << p; + return outstream; + } + + + template + inline Path ScalePath(const Path& path, + double scale_x, double scale_y, int& error_code) + { + Path result; + if (scale_x == 0 || scale_y == 0) + { + error_code |= scale_error_i; + DoError(scale_error_i); + // if no exception, treat as non-fatal error + if (scale_x == 0) scale_x = 1.0; + if (scale_y == 0) scale_y = 1.0; + } + + result.reserve(path.size()); #ifdef USINGZ - for (const Point& pt : path) - result.push_back(Point(pt.x * scale, pt.y * scale, pt.z)); + std::transform(path.begin(), path.end(), back_inserter(result), + [scale_x, scale_y](const auto& pt) + { return Point(pt.x * scale_x, pt.y * scale_y, pt.z); }); #else - for (const Point& pt : path) - result.push_back(Point(pt.x * scale, pt.y * scale)); + std::transform(path.begin(), path.end(), back_inserter(result), + [scale_x, scale_y](const auto& pt) + { return Point(pt.x * scale_x, pt.y * scale_y); }); #endif - return result; -} + return result; + } -template -inline Paths ScalePaths(const Paths& paths, double scale) -{ - Paths result; - result.reserve(paths.size()); - for (const Path& path : paths) - result.push_back(ScalePath(path, scale)); - return result; -} + template + inline Path ScalePath(const Path& path, + double scale, int& error_code) + { + return ScalePath(path, scale, scale, error_code); + } -template -inline Path TransformPath(const Path& path) -{ - Path result; - result.reserve(path.size()); - std::transform(path.cbegin(), path.cend(), std::back_inserter(result), - [](const Point& pt) {return Point(pt); }); - return result; -} + template + inline Paths ScalePaths(const Paths& paths, + double scale_x, double scale_y, int& error_code) + { + Paths result; -template -inline Paths TransformPaths(const Paths& paths) -{ - Paths result; - std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result), - [](const Path& path) {return TransformPath(path); }); - return result; -} + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + RectD r = GetBounds(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 + } + } -inline PathD Path64ToPathD(const Path64& path) -{ - return TransformPath(path); -} + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [=, &error_code](const auto& path) + { return ScalePath(path, scale_x, scale_y, error_code); }); + return result; + } -inline PathsD Paths64ToPathsD(const Paths64& paths) -{ - return TransformPaths(paths); -} + template + inline Paths ScalePaths(const Paths& paths, + double scale, int& error_code) + { + return ScalePaths(paths, scale, scale, error_code); + } -inline Path64 PathDToPath64(const PathD& path) -{ - return TransformPath(path); -} + template + inline Path TransformPath(const Path& path) + { + Path result; + result.reserve(path.size()); + std::transform(path.cbegin(), path.cend(), std::back_inserter(result), + [](const Point& pt) {return Point(pt); }); + return result; + } -inline Paths64 PathsDToPaths64(const PathsD& paths) -{ - return TransformPaths(paths); -} + template + inline Paths TransformPaths(const Paths& paths) + { + Paths result; + std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result), + [](const Path& path) {return TransformPath(path); }); + return result; + } -template -inline double Sqr(T val) -{ - return static_cast(val) * static_cast(val); -} + inline PathD Path64ToPathD(const Path64& path) + { + return TransformPath(path); + } -template -inline bool NearEqual(const Point& p1, - const Point& p2, double max_dist_sqrd) -{ - return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd; -} + inline PathsD Paths64ToPathsD(const Paths64& paths) + { + return TransformPaths(paths); + } -template -inline Path StripNearEqual(const Path& path, - double max_dist_sqrd, bool is_closed_path) -{ - if (path.size() == 0) return Path(); - Path result; - result.reserve(path.size()); - typename Path::const_iterator path_iter = path.cbegin(); - Point first_pt = *path_iter++, last_pt = first_pt; - result.push_back(first_pt); - for (; path_iter != path.cend(); ++path_iter) - { - if (!NearEqual(*path_iter, last_pt, max_dist_sqrd)) - { - last_pt = *path_iter; - result.push_back(last_pt); - } - } - if (!is_closed_path) return result; - while (result.size() > 1 && - NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back(); - return result; -} + inline Path64 PathDToPath64(const PathD& path) + { + return TransformPath(path); + } -template -inline Paths StripNearEqual(const Paths& paths, - double max_dist_sqrd, bool is_closed_path) -{ - Paths result; - result.reserve(paths.size()); - for (typename Paths::const_iterator paths_citer = paths.cbegin(); - paths_citer != paths.cend(); ++paths_citer) - { - result.push_back(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path)); - } - return result; -} + inline Paths64 PathsDToPaths64(const PathsD& paths) + { + return TransformPaths(paths); + } -template -inline Path StripDuplicates(const Path& path, bool is_closed_path) -{ - if (path.size() == 0) return Path(); - Path result; - result.reserve(path.size()); - typename Path::const_iterator path_iter = path.cbegin(); - Point first_pt = *path_iter++, last_pt = first_pt; - result.push_back(first_pt); - for (; path_iter != path.cend(); ++path_iter) - { - if (*path_iter != last_pt) - { - last_pt = *path_iter; - result.push_back(last_pt); - } - } - if (!is_closed_path) return result; - while (result.size() > 1 && result.back() == first_pt) result.pop_back(); - return result; -} + template + inline double Sqr(T val) + { + return static_cast(val) * static_cast(val); + } -template -inline Paths StripDuplicates(const Paths& paths, bool is_closed_path) -{ - Paths result; - result.reserve(paths.size()); - for (typename Paths::const_iterator paths_citer = paths.cbegin(); - paths_citer != paths.cend(); ++paths_citer) - { - result.push_back(StripDuplicates(*paths_citer, is_closed_path)); - } - return result; -} + template + inline bool NearEqual(const Point& p1, + const Point& p2, double max_dist_sqrd) + { + return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd; + } -// Rect ------------------------------------------------------------------------ + template + inline Path StripNearEqual(const Path& path, + double max_dist_sqrd, bool is_closed_path) + { + if (path.size() == 0) return Path(); + Path result; + result.reserve(path.size()); + typename Path::const_iterator path_iter = path.cbegin(); + Point first_pt = *path_iter++, last_pt = first_pt; + result.push_back(first_pt); + for (; path_iter != path.cend(); ++path_iter) + { + if (!NearEqual(*path_iter, last_pt, max_dist_sqrd)) + { + last_pt = *path_iter; + result.push_back(last_pt); + } + } + if (!is_closed_path) return result; + while (result.size() > 1 && + NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back(); + return result; + } -template -struct Rect; + template + inline Paths StripNearEqual(const Paths& paths, + double max_dist_sqrd, bool is_closed_path) + { + Paths result; + result.reserve(paths.size()); + for (typename Paths::const_iterator paths_citer = paths.cbegin(); + paths_citer != paths.cend(); ++paths_citer) + { + result.push_back(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path)); + } + return result; + } -using Rect64 = Rect; -using RectD = Rect; + template + inline Path StripDuplicates(const Path& path, bool is_closed_path) + { + if (path.size() == 0) return Path(); + Path result; + result.reserve(path.size()); + typename Path::const_iterator path_iter = path.cbegin(); + Point first_pt = *path_iter++, last_pt = first_pt; + result.push_back(first_pt); + for (; path_iter != path.cend(); ++path_iter) + { + if (*path_iter != last_pt) + { + last_pt = *path_iter; + result.push_back(last_pt); + } + } + if (!is_closed_path) return result; + while (result.size() > 1 && result.back() == first_pt) result.pop_back(); + return result; + } -template -struct Rect { - T left; - T top; - T right; - T bottom; + template + inline Paths StripDuplicates(const Paths& paths, bool is_closed_path) + { + Paths result; + result.reserve(paths.size()); + for (typename Paths::const_iterator paths_citer = paths.cbegin(); + paths_citer != paths.cend(); ++paths_citer) + { + result.push_back(StripDuplicates(*paths_citer, is_closed_path)); + } + return result; + } - Rect() : - left(0), - top(0), - right(0), - bottom(0) {} + // Miscellaneous ------------------------------------------------------------ - Rect(T l, T t, T r, T b) : - left(l), - top(t), - right(r), - bottom(b) {} + inline void CheckPrecision(int& precision, int& error_code) + { + if (precision >= -8 && precision <= 8) return; + error_code |= precision_error_i; // non-fatal error + DoError(precision_error_i); // unless exceptions enabled + precision = precision > 8 ? 8 : -8; + } + inline void CheckPrecision(int& precision) + { + int error_code = 0; + CheckPrecision(precision, error_code); + } - T Width() const { return right - left; } - T Height() const { return bottom - top; } - void Width(T width) { right = left + width; } - void Height(T height) { bottom = top + height; } + template + inline double CrossProduct(const Point& pt1, const Point& pt2, const Point& pt3) { + return (static_cast(pt2.x - pt1.x) * static_cast(pt3.y - + pt2.y) - static_cast(pt2.y - pt1.y) * static_cast(pt3.x - pt2.x)); + } - Point MidPoint() const - { - return Point((left + right) / 2, (top + bottom) / 2); - } + template + inline double CrossProduct(const Point& vec1, const Point& vec2) + { + return static_cast(vec1.y * vec2.x) - static_cast(vec2.y * vec1.x); + } - Path AsPath() const - { - Path result; - result.reserve(4); - result.push_back(Point(left, top)); - result.push_back(Point(right, top)); - result.push_back(Point(right, bottom)); - result.push_back(Point(left, bottom)); - return result; - } + template + inline double DotProduct(const Point& pt1, const Point& pt2, const Point& pt3) { + return (static_cast(pt2.x - pt1.x) * static_cast(pt3.x - pt2.x) + + static_cast(pt2.y - pt1.y) * static_cast(pt3.y - pt2.y)); + } - bool Contains(const Point& pt) const - { - return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom; - } + template + inline double DotProduct(const Point& vec1, const Point& vec2) + { + return static_cast(vec1.x * vec2.x) + static_cast(vec1.y * vec2.y); + } - bool Contains(const Rect& rec) const - { - return rec.left >= left && rec.right <= right && - rec.top >= top && rec.bottom <= bottom; - } + template + inline double DistanceSqr(const Point pt1, const Point pt2) + { + return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y); + } - void Scale(double scale) { - left *= scale; - top *= scale; - right *= scale; - bottom *= scale; - } + template + inline double DistanceFromLineSqrd(const Point& pt, const Point& ln1, const Point& ln2) + { + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = static_cast(ln1.y - ln2.y); + double B = static_cast(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); + } - bool IsEmpty() const { return bottom <= top || right <= left; }; + template + inline double Area(const Path& path) + { + size_t cnt = path.size(); + if (cnt < 3) return 0.0; + double a = 0.0; + typename Path::const_iterator it1, it2 = path.cend() - 1, stop = it2; + if (!(cnt & 1)) ++stop; + for (it1 = path.cbegin(); it1 != stop;) + { + a += static_cast(it2->y + it1->y) * (it2->x - it1->x); + it2 = it1 + 1; + a += static_cast(it1->y + it2->y) * (it1->x - it2->x); + it1 += 2; + } + if (cnt & 1) + a += static_cast(it2->y + it1->y) * (it2->x - it1->x); + return a * 0.5; + } - bool Intersects(const Rect& rec) const - { - return (std::max(left, rec.left) < std::min(right, rec.right)) && - (std::max(top, rec.top) < std::min(bottom, rec.bottom)); - }; + template + inline double Area(const Paths& paths) + { + double a = 0.0; + for (typename Paths::const_iterator paths_iter = paths.cbegin(); + paths_iter != paths.cend(); ++paths_iter) + { + a += Area(*paths_iter); + } + return a; + } - friend std::ostream &operator<<(std::ostream &os, const Rect &rect) { - os << "(" - << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom - << ")"; - return os; - } -}; + template + inline bool IsPositive(const Path& poly) + { + // 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(poly) >= 0; + } -template -inline Rect ScaleRect(const Rect& rect, double scale) -{ - Rect result; + inline int64_t CheckCastInt64(double val) + { + if ((val >= max_coord) || (val <= min_coord)) return INVALID; + else return static_cast(val); + } - if constexpr (std::numeric_limits::is_integer && - !std::numeric_limits::is_integer) - { - result.left = static_cast(std::round(rect.left * scale)); - result.top = static_cast(std::round(rect.top * scale)); - result.right = static_cast(std::round(rect.right * scale)); - result.bottom = static_cast(std::round(rect.bottom * scale)); - } - else - { - result.left = rect.left * scale; - result.top = rect.top * scale; - result.right = rect.right * scale; - result.bottom = rect.bottom * scale; - } - return result; -} + inline bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b, + const Point64& ln2a, const Point64& ln2b, Point64& ip) + { + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection -// clipper2Exception --------------------------------------------------------- + double dx1 = static_cast(ln1b.x - ln1a.x); + double dy1 = static_cast(ln1b.y - ln1a.y); + double dx2 = static_cast(ln2b.x - ln2a.x); + double dy2 = static_cast(ln2b.y - ln2a.y); + double det = dy1 * dx2 - dy2 * dx1; + if (det == 0.0) return 0; + double qx = dx1 * ln1a.y - dy1 * ln1a.x; + double qy = dx2 * ln2a.y - dy2 * ln2a.x; + ip.x = CheckCastInt64((dx1 * qy - dx2 * qx) / det); + ip.y = CheckCastInt64((dy1 * qy - dy2 * qx) / det); + return (ip.x != INVALID && ip.y != INVALID); + } -class Clipper2Exception : public std::exception { -public: - explicit Clipper2Exception(const char *description) : - m_descr(description) {} - virtual const char *what() const throw() override { return m_descr.c_str(); } + inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, + const Point64& seg2a, const Point64& seg2b, bool inclusive = false) + { + if (inclusive) + { + double res1 = CrossProduct(seg1a, seg2a, seg2b); + double res2 = CrossProduct(seg1b, seg2a, seg2b); + if (res1 * res2 > 0) return false; + double res3 = CrossProduct(seg2a, seg1a, seg1b); + double res4 = CrossProduct(seg2b, seg1a, seg1b); + if (res3 * res4 > 0) return false; + 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); + } + } -private: - std::string m_descr; -}; + inline Point64 GetClosestPointOnSegment(const Point64& offPt, + const Point64& seg1, const Point64& seg2) + { + if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1; + double dx = static_cast(seg2.x - seg1.x); + double dy = static_cast(seg2.y - seg1.y); + double q = + (static_cast(offPt.x - seg1.x) * dx + + static_cast(offPt.y - seg1.y) * dy) / + (Sqr(dx) + Sqr(dy)); + if (q < 0) q = 0; else if (q > 1) q = 1; + return Point64( + seg1.x + static_cast(nearbyint(q * dx)), + seg1.y + static_cast(nearbyint(q * dy))); + } -// Miscellaneous ------------------------------------------------------------ + enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; -template -inline double CrossProduct(const Point& pt1, const Point& pt2, const Point& pt3) { - return (static_cast(pt2.x - pt1.x) * static_cast(pt3.y - - pt2.y) - static_cast(pt2.y - pt1.y) * static_cast(pt3.x - pt2.x)); -} + template + inline PointInPolygonResult PointInPolygon(const Point& pt, const Path& polygon) + { + if (polygon.size() < 3) + return PointInPolygonResult::IsOutside; -template -inline double CrossProduct(const Point& vec1, const Point& vec2) -{ - return static_cast(vec1.y * vec2.x) - static_cast(vec2.y * vec1.x); -} + int val = 0; + typename Path::const_iterator cbegin = polygon.cbegin(), first = cbegin, curr, prev; + typename Path::const_iterator cend = polygon.cend(); -template -inline double DotProduct(const Point& pt1, const Point& pt2, const Point& pt3) { - return (static_cast(pt2.x - pt1.x) * static_cast(pt3.x - pt2.x) + - static_cast(pt2.y - pt1.y) * static_cast(pt3.y - pt2.y)); -} + while (first != cend && first->y == pt.y) ++first; + if (first == cend) // not a proper polygon + return PointInPolygonResult::IsOutside; -template -inline double DotProduct(const Point& vec1, const Point& vec2) -{ - return static_cast(vec1.x * vec2.x) + static_cast(vec1.y * vec2.y); -} + bool is_above = first->y < pt.y, starting_above = is_above; + curr = first +1; + while (true) + { + if (curr == cend) + { + if (cend == first || first == cbegin) break; + cend = first; + curr = cbegin; + } + + if (is_above) + { + while (curr != cend && curr->y < pt.y) ++curr; + if (curr == cend) continue; + } + else + { + while (curr != cend && curr->y > pt.y) ++curr; + if (curr == cend) continue; + } -template -inline double DistanceSqr(const Point pt1, const Point pt2) -{ - return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y); -} + if (curr == cbegin) + prev = polygon.cend() - 1; //nb: NOT cend (since might equal first) + else + prev = curr - 1; -template -inline double DistanceFromLineSqrd(const Point& pt, const Point& ln1, const Point& ln2) -{ - //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) - //see http://en.wikipedia.org/wiki/Perpendicular_distance - double A = static_cast(ln1.y - ln2.y); - double B = static_cast(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); -} + if (curr->y == pt.y) + { + if (curr->x == pt.x || + (curr->y == prev->y && + ((pt.x < prev->x) != (pt.x < curr->x)))) + return PointInPolygonResult::IsOn; + ++curr; + if (curr == first) break; + continue; + } -template -inline double Area(const Path& path) -{ - size_t cnt = path.size(); - if (cnt < 3) return 0.0; - double a = 0.0; - typename Path::const_iterator it1, it2 = path.cend() - 1, stop = it2; - if (!(cnt & 1)) ++stop; - for (it1 = path.cbegin(); it1 != stop;) - { - a += static_cast(it2->y + it1->y) * (it2->x - it1->x); - it2 = it1 + 1; - a += static_cast(it1->y + it2->y) * (it1->x - it2->x); - it1 += 2; - } - if (cnt & 1) - a += static_cast(it2->y + it1->y) * (it2->x - it1->x); - return a * 0.5; -} + if (pt.x < curr->x && pt.x < prev->x) + { + // we're only interested in edges crossing on the left + } + else if (pt.x > prev->x && pt.x > curr->x) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + ++curr; + } + + if (is_above != starting_above) + { + cend = polygon.cend(); + if (curr == cend) curr = cbegin; + if (curr == cbegin) prev = cend - 1; + else prev = curr - 1; + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } -template -inline double Area(const Paths& paths) -{ - double a = 0.0; - for (typename Paths::const_iterator paths_iter = paths.cbegin(); - paths_iter != paths.cend(); ++paths_iter) - { - a += Area(*paths_iter); - } - return a; -} - -template -inline bool IsPositive(const Path& poly) -{ - // 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(poly) >= 0; -} - -inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, - const Point64& seg2a, const Point64& seg2b, bool inclusive = false) -{ - if (inclusive) - { - double res1 = CrossProduct(seg1a, seg2a, seg2b); - double res2 = CrossProduct(seg1b, seg2a, seg2b); - if (res1 * res2 > 0) return false; - double res3 = CrossProduct(seg2a, seg1a, seg1b); - double res4 = CrossProduct(seg2b, seg1a, seg1b); - if (res3 * res4 > 0) return false; - return (res1 || res2 || res3 || res4); // ensures not collinear - } - else { - double dx1 = static_cast(seg1a.x - seg1b.x); - double dy1 = static_cast(seg1a.y - seg1b.y); - double dx2 = static_cast(seg2a.x - seg2b.x); - double dy2 = static_cast(seg2a.y - seg2b.y); - return (((dy1 * (seg2a.x - seg1a.x) - dx1 * (seg2a.y - seg1a.y)) * - (dy1 * (seg2b.x - seg1a.x) - dx1 * (seg2b.y - seg1a.y)) < 0) && - ((dy2 * (seg1a.x - seg2a.x) - dx2 * (seg1a.y - seg2a.y)) * - (dy2 * (seg1b.x - seg2a.x) - dx2 * (seg1b.y - seg2a.y)) < 0)); - } -} - - -enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; - -template -inline PointInPolygonResult PointInPolygon(const Point& pt, const Path& polygon) -{ - if (polygon.size() < 3) - return PointInPolygonResult::IsOutside; - - int val = 0; - typename Path::const_iterator start = polygon.cbegin(), cit = start; - typename Path::const_iterator cend = polygon.cend(), pit = cend - 1; - - while (pit->y == pt.y) - { - if (pit == start) return PointInPolygonResult::IsOutside; - --pit; - } - bool is_above = pit->y < pt.y; - - while (cit != cend) - { - if (is_above) - { - while (cit != cend && cit->y < pt.y) ++cit; - if (cit == cend) break; - } - else - { - while (cit != cend && cit->y > pt.y) ++cit; - if (cit == cend) break; - } - - if (cit == start) pit = cend - 1; - else pit = cit - 1; - - if (cit->y == pt.y) - { - if (cit->x == pt.x || (cit->y == pit->y && - ((pt.x < pit->x) != (pt.x < cit->x)))) - return PointInPolygonResult::IsOn; - ++cit; - continue; - } - - if (pt.x < cit->x && pt.x < pit->x) - { - // we're only interested in edges crossing on the left - } - else if (pt.x > pit->x && pt.x > cit->x) - val = 1 - val; // toggle val - else - { - double d = CrossProduct(*pit, *cit, pt); - if (d == 0) return PointInPolygonResult::IsOn; - if ((d < 0) == is_above) val = 1 - val; - } - is_above = !is_above; - ++cit; - } - return (val == 0) ? - PointInPolygonResult::IsOutside : - PointInPolygonResult::IsInside; -} + return (val == 0) ? + PointInPolygonResult::IsOutside : + PointInPolygonResult::IsInside; + } } // namespace diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h index 96f156218d..33e4aced2b 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2022 * +* Date : 2 March 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -10,13 +10,16 @@ #ifndef CLIPPER_ENGINE_H #define CLIPPER_ENGINE_H -#define CLIPPER2_VERSION "1.0.6" +constexpr auto CLIPPER2_VERSION = "1.2.1"; #include +#include #include -#include #include #include +#include +#include + #include "clipper.core.h" #ifdef None @@ -31,12 +34,13 @@ namespace Clipper2Lib { struct Vertex; struct LocalMinima; struct OutRec; - struct Joiner; + struct HorzSegment; //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 }; enum class VertexFlags : uint32_t { None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8 @@ -47,7 +51,7 @@ namespace Clipper2Lib { return (enum VertexFlags)(uint32_t(a) & uint32_t(b)); } - 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)); } @@ -64,7 +68,7 @@ namespace Clipper2Lib { OutPt* next = nullptr; OutPt* prev = nullptr; OutRec* outrec; - Joiner* joiner = nullptr; + HorzSegment* horz = nullptr; OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) { next = this; @@ -86,15 +90,20 @@ namespace Clipper2Lib { struct OutRec { size_t idx = 0; OutRec* owner = nullptr; - OutRecList* splits = nullptr; Active* front_edge = nullptr; Active* back_edge = nullptr; OutPt* pts = nullptr; PolyPath* polypath = nullptr; + OutRecList* splits = nullptr; Rect64 bounds = {}; Path64 path; bool is_open = false; - ~OutRec() { if (splits) delete splits; }; + bool horz_done = false; + ~OutRec() { + if (splits) delete splits; + // nb: don't delete the split pointers + // as these are owned by ClipperBase's outrec_list_ + }; }; /////////////////////////////////////////////////////////////////// @@ -126,6 +135,7 @@ namespace Clipper2Lib { Vertex* vertex_top = nullptr; LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti) bool is_left_bound = false; + JoinWith join_with = JoinWith::None; }; struct LocalMinima { @@ -140,11 +150,24 @@ namespace Clipper2Lib { Point64 pt; Active* edge1; Active* edge2; - IntersectNode() : pt(Point64(0, 0)), edge1(NULL), edge2(NULL) {} + IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {} IntersectNode(Active* e1, Active* e2, Point64& pt_) : - pt(pt_), edge1(e1), edge2(e2) - { - } + pt(pt_), edge1(e1), edge2(e2) {} + }; + + struct HorzSegment { + OutPt* left_op; + OutPt* right_op = nullptr; + bool left_to_right = true; + HorzSegment() : left_op(nullptr) { } + explicit HorzSegment(OutPt* op) : left_op(op) { } + }; + + struct HorzJoin { + OutPt* op1 = nullptr; + OutPt* op2 = nullptr; + HorzJoin() {}; + explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { } }; #ifdef USINGZ @@ -155,6 +178,11 @@ namespace Clipper2Lib { const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD; #endif + typedef std::vector HorzSegmentList; + typedef std::unique_ptr LocalMinima_ptr; + typedef std::vector LocalMinimaList; + typedef std::vector IntersectNodeList; + // ClipperBase ------------------------------------------------------------- class ClipperBase { @@ -167,21 +195,21 @@ namespace Clipper2Lib { bool using_polytree_ = false; Active* actives_ = nullptr; Active *sel_ = nullptr; - Joiner *horz_joiners_ = nullptr; - std::vector minima_list_; //pointers in case of memory reallocs - std::vector::iterator current_locmin_iter_; + LocalMinimaList minima_list_; //pointers in case of memory reallocs + LocalMinimaList::iterator current_locmin_iter_; std::vector vertex_lists_; std::priority_queue scanline_list_; - std::vector intersect_nodes_; - std::vector joiner_list_; //pointers in case of memory reallocs + IntersectNodeList intersect_nodes_; + HorzSegmentList horz_seg_list_; + std::vector horz_join_list_; void Reset(); - void InsertScanline(int64_t y); - bool PopScanline(int64_t &y); - bool PopLocalMinima(int64_t y, LocalMinima *&local_minima); + inline void InsertScanline(int64_t y); + inline bool PopScanline(int64_t &y); + inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima); void DisposeAllOutRecs(); void DisposeVerticesAndLocalMinima(); void DeleteEdges(Active*& e); - void AddLocMin(Vertex &vert, PathType polytype, bool is_open); + inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open); bool IsContributingClosed(const Active &e) const; inline bool IsContributingOpen(const Active &e) const; void SetWindCountForClosedPathEdge(Active &edge); @@ -200,38 +228,40 @@ namespace Clipper2Lib { bool BuildIntersectList(const int64_t top_y); void ProcessIntersectList(); void SwapPositionsInAEL(Active& edge1, Active& edge2); + OutRec* NewOutRec(); OutPt* AddOutPt(const Active &e, const Point64& pt); 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); - bool ResetHorzDirection(const Active &horz, const Active *max_pair, + bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex, int64_t &horz_left, int64_t &horz_right); void DoTopOfScanbeam(const int64_t top_y); Active *DoMaxima(Active &e); void JoinOutrecPaths(Active &e1, Active &e2); void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec); - bool ValidateClosedPathEx(OutPt*& outrec); - void CleanCollinear(OutRec* outrec); void FixSelfIntersects(OutRec* outrec); - OutPt* DoSplitOp(OutPt* outRecOp, OutPt* splitOp); - Joiner* GetHorzTrialParent(const OutPt* op); - bool OutPtInTrialHorzList(OutPt* op); - void SafeDisposeOutPts(OutPt*& op); - void SafeDeleteOutPtJoiners(OutPt* op); - void AddTrialHorzJoin(OutPt* op); - void DeleteTrialHorzJoin(OutPt* op); - void ConvertHorzTrialsToJoins(); - void AddJoin(OutPt* op1, OutPt* op2); - void DeleteJoin(Joiner* joiner); - void ProcessJoinerList(); - OutRec* ProcessJoin(Joiner* joiner); + 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, + const Point64& pt, bool check_curr_x = false); + inline void CheckJoinRight(Active& e, + const Point64& pt, bool check_curr_x = false); protected: + int error_code_ = 0; bool has_open_paths_ = false; bool succeeded_ = true; - std::vector outrec_list_; //pointers in case list memory reallocated + OutRecList outrec_list_; //pointers in case list memory reallocated bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); - bool DeepCheckOwner(OutRec* outrec, OutRec* owner); + void CleanCollinear(OutRec* outrec); + bool CheckBounds(OutRec* outrec); + void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath); + void DeepCheckOwners(OutRec* outrec, PolyPath* polypath); #ifdef USINGZ ZCallback64 zCallback_ = nullptr; void SetZ(const Active& e1, const Active& e2, Point64& pt); @@ -241,9 +271,13 @@ namespace Clipper2Lib { void AddPaths(const Paths64& paths, PathType polytype, bool is_open); public: virtual ~ClipperBase(); + int ErrorCode() { return error_code_; }; bool PreserveCollinear = true; bool ReverseSolution = false; void Clear(); +#ifdef USINGZ + int64_t DefaultZ = 0; +#endif }; // PolyPath / PolyTree -------------------------------------------------------- @@ -258,7 +292,7 @@ namespace Clipper2Lib { PolyPath* parent_; public: PolyPath(PolyPath* parent = nullptr): parent_(parent){} - virtual ~PolyPath() { Clear(); }; + virtual ~PolyPath() {}; //https://en.cppreference.com/w/cpp/language/rule_of_three PolyPath(const PolyPath&) = delete; PolyPath& operator=(const PolyPath&) = delete; @@ -273,145 +307,131 @@ namespace Clipper2Lib { virtual PolyPath* AddChild(const Path64& path) = 0; - virtual void Clear() {}; + virtual void Clear() = 0; virtual size_t Count() const { return 0; } const PolyPath* Parent() const { return parent_; } bool IsHole() const { - const PolyPath* pp = parent_; - bool is_hole = pp; - while (pp) { - is_hole = !is_hole; - pp = pp->parent_; - } - return is_hole; - } + unsigned lvl = Level(); + //Even levels except level 0 + return lvl && !(lvl & 1); + } }; + typedef typename std::vector> PolyPath64List; + typedef typename std::vector> PolyPathDList; + class PolyPath64 : public PolyPath { private: - std::vector childs_; + PolyPath64List childs_; Path64 polygon_; - typedef typename std::vector::const_iterator pp64_itor; public: - PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} - PolyPath64* operator [] (size_t index) { return static_cast(childs_[index]); } - pp64_itor begin() const { return childs_.cbegin(); } - pp64_itor end() const { return childs_.cend(); } + explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} + + ~PolyPath64() { + childs_.resize(0); + } + + const PolyPath64* operator [] (size_t index) const + { + return childs_[index].get(); + } + + const PolyPath64* Child(size_t index) const + { + return childs_[index].get(); + } + + PolyPath64List::const_iterator begin() const { return childs_.cbegin(); } + PolyPath64List::const_iterator end() const { return childs_.cend(); } PolyPath64* AddChild(const Path64& path) override { - PolyPath64* result = new PolyPath64(this); - childs_.push_back(result); + auto p = std::make_unique(this); + auto* result = childs_.emplace_back(std::move(p)).get(); result->polygon_ = path; return result; } void Clear() override { - for (PolyPath64* child : childs_) - { - child->Clear(); - delete child; - } childs_.resize(0); } - size_t Count() const override + size_t Count() const override { return childs_.size(); } - const Path64 Polygon() const { return polygon_; }; + const Path64& Polygon() const { return polygon_; }; double Area() const { - double result = Clipper2Lib::Area(polygon_); - for (const PolyPath64* child : childs_) - result += child->Area(); - return result; - } - - friend std::ostream& operator << (std::ostream& outstream, const PolyPath64& polypath) - { - const size_t level_indent = 4; - const size_t coords_per_line = 4; - const size_t last_on_line = coords_per_line - 1; - unsigned level = polypath.Level(); - if (level > 0) - { - std::string level_padding; - level_padding.insert(0, (level - 1) * level_indent, ' '); - std::string caption = polypath.IsHole() ? "Hole " : "Outer Polygon "; - std::string childs = polypath.Count() == 1 ? " child" : " children"; - outstream << level_padding.c_str() << caption << "with " << polypath.Count() << childs << std::endl; - outstream << level_padding; - size_t i = 0, highI = polypath.Polygon().size() - 1; - for (; i < highI; ++i) - { - outstream << polypath.Polygon()[i] << ' '; - if ((i % coords_per_line) == last_on_line) - outstream << std::endl << level_padding; - } - if (highI > 0) outstream << polypath.Polygon()[i]; - outstream << std::endl; - } - for (auto child : polypath) - outstream << *child; - return outstream; + return std::accumulate(childs_.cbegin(), childs_.cend(), + Clipper2Lib::Area(polygon_), + [](double a, const auto& child) {return a + child->Area(); }); } }; class PolyPathD : public PolyPath { private: - std::vector childs_; + PolyPathDList childs_; double inv_scale_; PathD polygon_; - typedef typename std::vector::const_iterator ppD_itor; public: - PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) + explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) { inv_scale_ = parent ? parent->inv_scale_ : 1.0; } - PolyPathD* operator [] (size_t index) - { - return static_cast(childs_[index]); + + ~PolyPathD() { + childs_.resize(0); } - ppD_itor begin() const { return childs_.cbegin(); } - ppD_itor end() const { return childs_.cend(); } + + const PolyPathD* operator [] (size_t index) const + { + return childs_[index].get(); + } + + const PolyPathD* Child(size_t index) const + { + return childs_[index].get(); + } + + PolyPathDList::const_iterator begin() const { return childs_.cbegin(); } + PolyPathDList::const_iterator end() const { return childs_.cend(); } void SetInvScale(double value) { inv_scale_ = value; } double InvScale() { return inv_scale_; } PolyPathD* AddChild(const Path64& path) override { - PolyPathD* result = new PolyPathD(this); - childs_.push_back(result); - result->polygon_ = ScalePath(path, inv_scale_); + int error_code = 0; + auto p = std::make_unique(this); + PolyPathD* result = childs_.emplace_back(std::move(p)).get(); + result->polygon_ = ScalePath(path, inv_scale_, error_code); return result; } void Clear() override { - for (const PolyPathD* child : childs_) delete child; childs_.resize(0); } - size_t Count() const override + size_t Count() const override { return childs_.size(); } - const PathD Polygon() const { return polygon_; }; + const PathD& Polygon() const { return polygon_; }; double Area() const { - double result = Clipper2Lib::Area(polygon_); - for (const PolyPathD* child : childs_) - result += child->Area(); - return result; + return std::accumulate(childs_.begin(), childs_.end(), + Clipper2Lib::Area(polygon_), + [](double a, const auto& child) {return a + child->Area(); }); } }; @@ -451,7 +471,7 @@ namespace Clipper2Lib { closed_paths.clear(); open_paths.clear(); if (ExecuteInternal(clip_type, fill_rule, false)) - BuildPaths64(closed_paths, &open_paths); + BuildPaths64(closed_paths, &open_paths); CleanUp(); return succeeded_; } @@ -480,19 +500,23 @@ namespace Clipper2Lib { private: double scale_ = 1.0, invScale_ = 1.0; #ifdef USINGZ - ZCallbackD zCallback_ = nullptr; + ZCallbackD zCallbackD_ = nullptr; #endif void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen); void BuildTreeD(PolyPathD& polytree, PathsD& open_paths); public: explicit ClipperD(int precision = 2) : ClipperBase() { - scale_ = std::pow(10, precision); + CheckPrecision(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::radix, + std::ilogb(std::pow(10, precision)) + 1); invScale_ = 1 / scale_; } #ifdef USINGZ - void SetZCallback(ZCallbackD cb) { zCallback_ = cb; }; + void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; }; void ZCB(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt) @@ -506,13 +530,13 @@ namespace Clipper2Lib { PointD e1t = PointD(e1top) * invScale_; PointD e2b = PointD(e2bot) * invScale_; PointD e2t = PointD(e2top) * invScale_; - zCallback_(e1b,e1t, e2b, e2t, tmp); + zCallbackD_(e1b,e1t, e2b, e2t, tmp); pt.z = tmp.z; // only update 'z' }; void CheckCallback() { - if(zCallback_) + if(zCallbackD_) // if the user defined float point callback has been assigned // then assign the proxy callback function ClipperBase::zCallback_ = @@ -527,17 +551,17 @@ namespace Clipper2Lib { void AddSubject(const PathsD& subjects) { - AddPaths(ScalePaths(subjects, scale_), PathType::Subject, false); + AddPaths(ScalePaths(subjects, scale_, error_code_), PathType::Subject, false); } void AddOpenSubject(const PathsD& open_subjects) { - AddPaths(ScalePaths(open_subjects, scale_), PathType::Subject, true); + AddPaths(ScalePaths(open_subjects, scale_, error_code_), PathType::Subject, true); } void AddClip(const PathsD& clips) { - AddPaths(ScalePaths(clips, scale_), PathType::Clip, false); + AddPaths(ScalePaths(clips, scale_, error_code_), PathType::Clip, false); } bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths) diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h index 9988df5ea2..38f7d1f401 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,23 +1,23 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 28 October 2022 * +* Date : 22 March 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ // The exported functions below refer to simple structures that // can be understood across multiple languages. Consequently -// Path64, PathD, Polytree64 etc are converted from classes +// Path64, PathD, Polytree64 etc are converted from C++ classes // (std::vector<> etc) into the following data structures: // // CPath64 (int64_t*) & CPathD (double_t*): // Path64 and PathD are converted into arrays of x,y coordinates. // However in these arrays the first x,y coordinate pair is a // counter with 'x' containing the number of following coordinate -// pairs. ('y' must always be 0.) -//__________________________________ +// pairs. ('y' should be 0, with one exception explained below.) +// __________________________________ // |counter|coord1|coord2|...|coordN| // |N ,0 |x1, y1|x2, y2|...|xN, yN| // __________________________________ @@ -25,19 +25,15 @@ // CPaths64 (int64_t**) & CPathsD (double_t**): // These are arrays of pointers to CPath64 and CPathD where // the first pointer is to a 'counter path'. This 'counter -// path' has a single x,y coord pair where 'y' contains -// the number of paths that follow (and with 'x' always 0). +// path' has a single x,y coord pair with 'y' (not 'x') +// containing the number of paths that follow. ('x' = 0). // _______________________________ // |counter|path1|path2|...|pathN| // |addr0 |addr1|addr2|...|addrN| (*addr0[0]=0; *addr0[1]=N) // _______________________________ // // The structures of CPolytree64 and CPolytreeD are defined -// below and they don't need to be repeated or explained here. -// -// Finally, the pointer structures created and exported through -// these functions can't safely be destroyed externally, so -// a number of 'dispose functions are also exported. +// below and these structures don't need to be explained here. #ifndef CLIPPER2_EXPORT_H #define CLIPPER2_EXPORT_H @@ -62,7 +58,7 @@ typedef struct CPolyPath64 { uint32_t is_hole; uint32_t child_count; CPolyPath64* childs; -} +} CPolyTree64; typedef struct CPolyPathD { @@ -70,7 +66,7 @@ typedef struct CPolyPathD { uint32_t is_hole; uint32_t child_count; CPolyPathD* childs; -} +} CPolyTreeD; template @@ -101,6 +97,80 @@ inline Rect CRectToRect(const CRect& rect) return result; } +#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) + +////////////////////////////////////////////////////// +// EXPORTED FUNCTION DEFINITIONS +////////////////////////////////////////////////////// + +EXTERN_DLL_EXPORT const char* Version(); + +// Some of the functions below will return data in the various CPath +// and CPolyTree structures which are pointers to heap allocated +// memory. Eventually this memory will need to be released with one +// of the following 'DisposeExported' functions. (This may be the +// only safe way to release this memory since the executable +// accessing these exported functions may use a memory manager that +// allocates and releases heap memory in a different way. Also, +// CPath structures that have been constructed by the executable +// should not be destroyed using these 'DisposeExported' functions.) +EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p); +EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp); +EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p); +EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp); +EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt); +EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt); + +// Boolean clipping: +// cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 +// fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 +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, + bool preserve_collinear = true, bool reverse_solution = false); +EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPolyTree64*& solution, CPaths64& solution_open, + bool preserve_collinear = true, bool reverse_solution = false); +EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPathsD& solution, CPathsD& solution_open, int precision = 2, + bool preserve_collinear = true, bool reverse_solution = false); +EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2, + bool preserve_collinear = true, bool reverse_solution = false); + +// Polygon offsetting (inflate/deflate): +// jointype: Square=0, Round=1, Miter=2 +// endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4 +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, + bool reverse_solution = false); +EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, + double delta, uint8_t jointype, uint8_t endtype, + int precision = 2, double miter_limit = 2.0, + double arc_tolerance = 0.0, bool reverse_solution = false); + +// RectClip & RectClipLines: +EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, + const CPaths64 paths, bool convex_only = false); +EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, + const CPathsD paths, int precision = 2, bool convex_only = false); +EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, + const CPaths64 paths); +EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, + const CPathsD paths, int precision = 2); + +////////////////////////////////////////////////////// +// INTERNAL FUNCTIONS +////////////////////////////////////////////////////// + inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2); inline CPath64 CreateCPath64(const Path64& p); inline CPaths64 CreateCPaths64(const Paths64& pp); @@ -114,17 +184,14 @@ inline PathD ConvertCPathD(const CPathD& p); inline PathsD ConvertCPathsD(const CPathsD& pp); // the following function avoid multiple conversions -inline Path64 ConvertCPathD(const CPathD& p, double scale); -inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale); inline CPathD CreateCPathD(const Path64& p, double scale); inline CPathsD CreateCPathsD(const Paths64& pp, double scale); +inline Path64 ConvertCPathD(const CPathD& p, double scale); +inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale); inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt); inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale); -#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) - - EXTERN_DLL_EXPORT const char* Version() { return CLIPPER2_VERSION; @@ -168,7 +235,7 @@ 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, - bool preserve_collinear = true, bool reverse_solution = false) + bool preserve_collinear, bool reverse_solution) { if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; @@ -195,7 +262,7 @@ EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, CPolyTree64*& solution, CPaths64& solution_open, - bool preserve_collinear = true, bool reverse_solution = false) + bool preserve_collinear, bool reverse_solution) { if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; @@ -222,8 +289,8 @@ EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, - CPathsD& solution, CPathsD& solution_open, int precision = 2, - bool preserve_collinear = true, bool reverse_solution = false) + CPathsD& solution, CPathsD& solution_open, int precision, + bool preserve_collinear, bool reverse_solution) { if (precision < -8 || precision > 8) return -5; if (cliptype > static_cast(ClipType::Xor)) return -4; @@ -254,8 +321,8 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, - CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2, - bool preserve_collinear = true, bool reverse_solution = false) + CPolyTreeD*& solution, CPathsD& solution_open, int precision, + bool preserve_collinear, bool reverse_solution) { if (precision < -8 || precision > 8) return -5; if (cliptype > static_cast(ClipType::Xor)) return -4; @@ -285,84 +352,58 @@ EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, } EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, - double delta, uint8_t jt, uint8_t et, double miter_limit = 2.0, - double arc_tolerance = 0.0, bool reverse_solution = false) + double delta, uint8_t jointype, uint8_t endtype, double miter_limit, + double arc_tolerance, bool reverse_solution) { Paths64 pp; pp = ConvertCPaths64(paths); ClipperOffset clip_offset( miter_limit, arc_tolerance, reverse_solution); - clip_offset.AddPaths(pp, JoinType(jt), EndType(et)); - Paths64 result = clip_offset.Execute(delta); + clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta, result); return CreateCPaths64(result); } EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, - double delta, uint8_t jt, uint8_t et, - double precision = 2, double miter_limit = 2.0, - double arc_tolerance = 0.0, bool reverse_solution = false) + double delta, uint8_t jointype, uint8_t endtype, + int precision, double miter_limit, + double arc_tolerance, bool reverse_solution) { if (precision < -8 || precision > 8 || !paths) return nullptr; const double scale = std::pow(10, precision); ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); Paths64 pp = ConvertCPathsD(paths, scale); - clip_offset.AddPaths(pp, JoinType(jt), EndType(et)); - Paths64 result = clip_offset.Execute(delta * scale); + clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta * scale, result); return CreateCPathsD(result, 1/scale); } EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, - const CPaths64 paths) + const CPaths64 paths, bool convex_only) { - log(rect.left); - log(rect.right); if (CRectIsEmpty(rect) || !paths) return nullptr; Rect64 r64 = CRectToRect(rect); class RectClip rc(r64); Paths64 pp = ConvertCPaths64(paths); - Paths64 result; - result.reserve(pp.size()); - for (const Path64& p : pp) - { - Rect64 pathRec = Bounds(p); - if (!r64.Intersects(pathRec)) continue; - - if (r64.Contains(pathRec)) - result.push_back(p); - else - { - Path64 p2 = rc.Execute(p); - if (!p2.empty()) result.push_back(std::move(p2)); - } - } + Paths64 result = rc.Execute(pp, convex_only); return CreateCPaths64(result); } EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, - const CPathsD paths, int precision = 2) + const CPathsD paths, int precision, bool convex_only) { if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; const double scale = std::pow(10, precision); - Rect64 r = ScaleRect(CRectToRect(rect), scale); + + RectD r = CRectToRect(rect); + Rect64 rec = ScaleRect(r, scale); Paths64 pp = ConvertCPathsD(paths, scale); - class RectClip rc(r); - Paths64 result; - result.reserve(pp.size()); - for (const Path64& p : pp) - { - Rect64 pathRec = Bounds(p); - if (!r.Intersects(pathRec)) continue; - - if (r.Contains(pathRec)) - result.push_back(p); - else - { - Path64 p2 = rc.Execute(p); - if (!p2.empty()) result.push_back(std::move(p2)); - } - } + class RectClip rc(rec); + Paths64 result = rc.Execute(pp, convex_only); return CreateCPathsD(result, 1/scale); } @@ -373,58 +414,27 @@ EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, Rect64 r = CRectToRect(rect); class RectClipLines rcl (r); Paths64 pp = ConvertCPaths64(paths); - Paths64 result; - result.reserve(pp.size()); - - for (const Path64& p : pp) - { - Rect64 pathRec = Bounds(p); - if (!r.Intersects(pathRec)) continue; - - if (r.Contains(pathRec)) - result.push_back(p); - else - { - Paths64 pp2 = rcl.Execute(p); - if (!pp2.empty()) - result.insert(result.end(), pp2.begin(), pp2.end()); - } - } + Paths64 result = rcl.Execute(pp); return CreateCPaths64(result); } EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, - const CPathsD paths, int precision = 2) + const CPathsD paths, int precision) { - Paths64 result; if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; const double scale = std::pow(10, precision); Rect64 r = ScaleRect(CRectToRect(rect), scale); class RectClipLines rcl(r); Paths64 pp = ConvertCPathsD(paths, scale); - - result.reserve(pp.size()); - for (const Path64& p : pp) - { - Rect64 pathRec = Bounds(p); - if (!r.Intersects(pathRec)) continue; - - if (r.Contains(pathRec)) - result.push_back(p); - else - { - Paths64 pp2 = rcl.Execute(p); - if (pp2.empty()) continue; - result.insert(result.end(), pp2.begin(), pp2.end()); - } - } + Paths64 result = rcl.Execute(pp); return CreateCPathsD(result, 1/scale); } inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2) { - // create a dummy counter path + // allocates memory for CPath64, fills in the counter, and + // returns the structure ready to be filled with path data CPath64 result = new int64_t[2 + cnt1 *2]; result[0] = cnt1; result[1] = cnt2; @@ -433,6 +443,8 @@ inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2) inline CPath64 CreateCPath64(const Path64& p) { + // allocates memory for CPath64, fills the counter + // and returns the memory filled with path data size_t cnt = p.size(); if (!cnt) return nullptr; CPath64 result = CreateCPath64(cnt, 0); @@ -469,6 +481,8 @@ inline Path64 ConvertCPath64(const CPath64& p) inline CPaths64 CreateCPaths64(const Paths64& pp) { + // allocates memory for multiple CPath64 and + // and returns this memory filled with path data size_t cnt = pp.size(), cnt2 = cnt; // don't allocate space for empty paths @@ -505,7 +519,8 @@ inline Paths64 ConvertCPaths64(const CPaths64& pp) inline CPathD CreateCPathD(size_t cnt1, size_t cnt2) { - // create a dummy path counter + // allocates memory for CPathD, fills in the counter, and + // returns the structure ready to be filled with path data CPathD result = new double[2 + cnt1 * 2]; result[0] = static_cast(cnt1); result[1] = static_cast(cnt2); @@ -514,6 +529,8 @@ inline CPathD CreateCPathD(size_t cnt1, size_t cnt2) inline CPathD CreateCPathD(const PathD& p) { + // allocates memory for CPath, fills the counter + // and returns the memory fills with path data size_t cnt = p.size(); if (!cnt) return nullptr; CPathD result = CreateCPathD(cnt, 0); @@ -621,6 +638,8 @@ inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale) inline CPathD CreateCPathD(const Path64& p, double scale) { + // allocates memory for CPathD, fills in the counter, and + // returns the structure filled with *scaled* path data size_t cnt = p.size(); if (!cnt) return nullptr; CPathD result = CreateCPathD(cnt, 0); @@ -636,6 +655,8 @@ inline CPathD CreateCPathD(const Path64& p, double scale) inline CPathsD CreateCPathsD(const Paths64& pp, double scale) { + // allocates memory for *multiple* CPathD, and + // returns the structure filled with scaled path data size_t cnt = pp.size(), cnt2 = cnt; // don't allocate space for empty paths for (size_t i = 0; i < cnt; ++i) @@ -653,17 +674,17 @@ inline CPathsD CreateCPathsD(const Paths64& pp, double scale) } inline void InitCPolyPath64(CPolyTree64* cpt, - bool is_hole, const PolyPath64* pp) + bool is_hole, const std::unique_ptr & pp) { cpt->polygon = CreateCPath64(pp->Polygon()); cpt->is_hole = is_hole; size_t child_cnt = pp->Count(); - cpt->child_count = child_cnt; + cpt->child_count = static_cast(child_cnt); cpt->childs = nullptr; if (!child_cnt) return; cpt->childs = new CPolyPath64[child_cnt]; CPolyPath64* child = cpt->childs; - for (const PolyPath64* pp_child : *pp) + for (const std::unique_ptr & pp_child : *pp) InitCPolyPath64(child++, !is_hole, pp_child); } @@ -674,11 +695,11 @@ inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt) result->is_hole = false; size_t child_cnt = pt.Count(); result->childs = nullptr; - result->child_count = child_cnt; + result->child_count = static_cast(child_cnt); if (!child_cnt) return result; result->childs = new CPolyPath64[child_cnt]; CPolyPath64* child = result->childs; - for (const PolyPath64* pp : pt) + for (const std::unique_ptr & pp : pt) InitCPolyPath64(child++, true, pp); return result; } @@ -701,17 +722,17 @@ EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt) } inline void InitCPolyPathD(CPolyTreeD* cpt, - bool is_hole, const PolyPath64* pp, double scale) + bool is_hole, const std::unique_ptr & pp, double scale) { cpt->polygon = CreateCPathD(pp->Polygon(), scale); cpt->is_hole = is_hole; size_t child_cnt = pp->Count(); - cpt->child_count = child_cnt; + cpt->child_count = static_cast(child_cnt); cpt->childs = nullptr; if (!child_cnt) return; cpt->childs = new CPolyPathD[child_cnt]; CPolyPathD* child = cpt->childs; - for (const PolyPath64* pp_child : *pp) + for (const std::unique_ptr & pp_child : *pp) InitCPolyPathD(child++, !is_hole, pp_child, scale); } @@ -726,7 +747,7 @@ inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale) if (!child_cnt) return result; result->childs = new CPolyPathD[child_cnt]; CPolyPathD* child = result->childs; - for (const PolyPath64* pp : pt) + for (const std::unique_ptr & pp : pt) InitCPolyPathD(child++, true, pp, scale); return result; } diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h index 3f211c27dc..009653c406 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2022 * +* Date : 9 February 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -11,6 +11,7 @@ #define CLIPPER_H #include +#include #include #include "clipper.core.h" @@ -21,21 +22,6 @@ namespace Clipper2Lib { - static const char* precision_error = - "Precision exceeds the permitted range"; - - static const Rect64 MaxInvalidRect64 = Rect64( - (std::numeric_limits::max)(), - (std::numeric_limits::max)(), - (std::numeric_limits::lowest)(), - (std::numeric_limits::lowest)()); - - static const RectD MaxInvalidRectD = RectD( - (std::numeric_limits::max)(), - (std::numeric_limits::max)(), - (std::numeric_limits::lowest)(), - (std::numeric_limits::lowest)()); - inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule, const Paths64& subjects, const Paths64& clips) { @@ -58,12 +44,13 @@ namespace Clipper2Lib { } inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule, - const PathsD& subjects, const PathsD& clips, int decimal_prec = 2) + const PathsD& subjects, const PathsD& clips, int precision = 2) { - if (decimal_prec > 8 || decimal_prec < -8) - throw Clipper2Exception(precision_error); + int error_code = 0; + CheckPrecision(precision, error_code); PathsD result; - ClipperD clipper(decimal_prec); + if (error_code) return result; + ClipperD clipper(precision); clipper.AddSubject(subjects); clipper.AddClip(clips); clipper.Execute(cliptype, fillrule, result); @@ -72,12 +59,13 @@ namespace Clipper2Lib { inline void BooleanOp(ClipType cliptype, FillRule fillrule, const PathsD& subjects, const PathsD& clips, - PolyTreeD& polytree, int decimal_prec = 2) + PolyTreeD& polytree, int precision = 2) { - if (decimal_prec > 8 || decimal_prec < -8) - throw Clipper2Exception(precision_error); - PathsD result; - ClipperD clipper(decimal_prec); + polytree.Clear(); + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return; + ClipperD clipper(precision); clipper.AddSubject(subjects); clipper.AddClip(clips); clipper.Execute(cliptype, fillrule, polytree); @@ -112,12 +100,13 @@ namespace Clipper2Lib { return result; } - inline PathsD Union(const PathsD& subjects, FillRule fillrule, int decimal_prec = 2) + inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2) { - if (decimal_prec > 8 || decimal_prec < -8) - throw Clipper2Exception(precision_error); PathsD result; - ClipperD clipper(decimal_prec); + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return result; + ClipperD clipper(precision); clipper.AddSubject(subjects); clipper.Execute(ClipType::Union, fillrule, result); return result; @@ -144,31 +133,40 @@ namespace Clipper2Lib { } inline Paths64 InflatePaths(const Paths64& paths, double delta, - JoinType jt, EndType et, double miter_limit = 2.0) + JoinType jt, EndType et, double miter_limit = 2.0, + double arc_tolerance = 0.0) { - ClipperOffset clip_offset(miter_limit); + if (!delta) return paths; + ClipperOffset clip_offset(miter_limit, arc_tolerance); clip_offset.AddPaths(paths, jt, et); - return clip_offset.Execute(delta); + Paths64 solution; + clip_offset.Execute(delta, solution); + return solution; } inline PathsD InflatePaths(const PathsD& paths, double delta, - JoinType jt, EndType et, double miter_limit = 2.0, double precision = 2) + JoinType jt, EndType et, double miter_limit = 2.0, + int precision = 2, double arc_tolerance = 0.0) { - if (precision < -8 || precision > 8) - throw Clipper2Exception(precision_error); + int error_code = 0; + CheckPrecision(precision, error_code); + if (!delta) return paths; + if (error_code) return PathsD(); const double scale = std::pow(10, precision); - ClipperOffset clip_offset(miter_limit); - clip_offset.AddPaths(ScalePaths(paths, scale), jt, et); - Paths64 tmp = clip_offset.Execute(delta * scale); - return ScalePaths(tmp, 1 / scale); + ClipperOffset clip_offset(miter_limit, arc_tolerance); + clip_offset.AddPaths(ScalePaths(paths, scale, error_code), jt, et); + if (error_code) return PathsD(); + Paths64 solution; + clip_offset.Execute(delta * scale, solution); + return ScalePaths(solution, 1 / scale, error_code); } inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy) { Path64 result; result.reserve(path.size()); - for (const Point64& pt : path) - result.push_back(Point64(pt.x + dx, pt.y + dy)); + std::transform(path.begin(), path.end(), back_inserter(result), + [dx, dy](const auto& pt) { return Point64(pt.x + dx, pt.y +dy); }); return result; } @@ -176,8 +174,8 @@ namespace Clipper2Lib { { PathD result; result.reserve(path.size()); - for (const PointD& pt : path) - result.push_back(PointD(pt.x + dx, pt.y + dy)); + std::transform(path.begin(), path.end(), back_inserter(result), + [dx, dy](const auto& pt) { return PointD(pt.x + dx, pt.y + dy); }); return result; } @@ -185,8 +183,8 @@ namespace Clipper2Lib { { Paths64 result; result.reserve(paths.size()); - for (const Path64& path : paths) - result.push_back(TranslatePath(path, dx, dy)); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); return result; } @@ -194,220 +192,79 @@ namespace Clipper2Lib { { PathsD result; result.reserve(paths.size()); - for (const PathD& path : paths) - result.push_back(TranslatePath(path, dx, dy)); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); return result; } - inline Rect64 Bounds(const Path64& path) - { - Rect64 rec = MaxInvalidRect64; - for (const Point64& pt : path) - { - if (pt.x < rec.left) rec.left = pt.x; - if (pt.x > rec.right) rec.right = pt.x; - if (pt.y < rec.top) rec.top = pt.y; - if (pt.y > rec.bottom) rec.bottom = pt.y; - } - if (rec.IsEmpty()) return Rect64(); - return rec; - } - - inline Rect64 Bounds(const Paths64& paths) - { - Rect64 rec = MaxInvalidRect64; - for (const Path64& path : paths) - for (const Point64& pt : path) - { - if (pt.x < rec.left) rec.left = pt.x; - if (pt.x > rec.right) rec.right = pt.x; - if (pt.y < rec.top) rec.top = pt.y; - if (pt.y > rec.bottom) rec.bottom = pt.y; - } - if (rec.IsEmpty()) return Rect64(); - return rec; - } - - inline RectD Bounds(const PathD& path) - { - RectD rec = MaxInvalidRectD; - for (const PointD& pt : path) - { - if (pt.x < rec.left) rec.left = pt.x; - if (pt.x > rec.right) rec.right = pt.x; - if (pt.y < rec.top) rec.top = pt.y; - if (pt.y > rec.bottom) rec.bottom = pt.y; - } - if (rec.IsEmpty()) return RectD(); - return rec; - } - - inline RectD Bounds(const PathsD& paths) - { - RectD rec = MaxInvalidRectD; - for (const PathD& path : paths) - for (const PointD& pt : path) - { - if (pt.x < rec.left) rec.left = pt.x; - if (pt.x > rec.right) rec.right = pt.x; - if (pt.y < rec.top) rec.top = pt.y; - if (pt.y > rec.bottom) rec.bottom = pt.y; - } - if (rec.IsEmpty()) return RectD(); - return rec; - } - - inline Path64 ClipRect(const Rect64& rect, const Path64& path) - { - if (rect.IsEmpty() || path.empty()) return Path64(); - Rect64 pathRec = Bounds(path); - if (!rect.Intersects(pathRec)) return Path64(); - if (rect.Contains(pathRec)) return path; - class RectClip rc(rect); - return rc.Execute(path); - } - - inline Paths64 ClipRect(const Rect64& rect, const Paths64& paths) + inline Paths64 RectClip(const Rect64& rect, + const Paths64& paths, bool convex_only = false) { if (rect.IsEmpty() || paths.empty()) return Paths64(); class RectClip rc(rect); - Paths64 result; - result.reserve(paths.size()); - - for (const Path64& p : paths) - { - Rect64 pathRec = Bounds(p); - if (!rect.Intersects(pathRec)) - continue; - else if (rect.Contains(pathRec)) - result.push_back(p); - else - { - Path64 p2 = rc.Execute(p); - if (!p2.empty()) result.push_back(std::move(p2)); - } - } - return result; + return rc.Execute(paths, convex_only); } - inline PathD ClipRect(const RectD& rect, const PathD& path, int precision = 2) + inline Paths64 RectClip(const Rect64& rect, + const Path64& path, bool convex_only = false) { - if (rect.IsEmpty() || path.empty() || - !rect.Contains(Bounds(path))) return PathD(); - if (precision < -8 || precision > 8) - throw Clipper2Exception(precision_error); - const double scale = std::pow(10, precision); - Rect64 r = ScaleRect(rect, scale); - class RectClip rc(r); - Path64 p = ScalePath(path, scale); - return ScalePath(rc.Execute(p), 1 / scale); + if (rect.IsEmpty() || path.empty()) return Paths64(); + class RectClip rc(rect); + return rc.Execute(Paths64{ path }, convex_only); } - inline PathsD ClipRect(const RectD& rect, const PathsD& paths, int precision = 2) + inline PathsD RectClip(const RectD& rect, + const PathsD& paths, bool convex_only = false, int precision = 2) { if (rect.IsEmpty() || paths.empty()) return PathsD(); - if (precision < -8 || precision > 8) - throw Clipper2Exception(precision_error); + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return PathsD(); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); class RectClip rc(r); - PathsD result; - result.reserve(paths.size()); - for (const PathD& path : paths) - { - RectD pathRec = Bounds(path); - if (!rect.Intersects(pathRec)) - continue; - else if (rect.Contains(pathRec)) - result.push_back(path); - else - { - Path64 p = ScalePath(path, scale); - p = rc.Execute(p); - if (!p.empty()) - result.push_back(ScalePath(p, 1 / scale)); - } - } - return result; + Paths64 pp = ScalePaths(paths, scale, error_code); + if (error_code) return PathsD(); // ie: error_code result is lost + return ScalePaths( + rc.Execute(pp, convex_only), 1 / scale, error_code); } - inline Paths64 ClipLinesRect(const Rect64& rect, const Path64& path) + inline PathsD RectClip(const RectD& rect, + const PathD& path, bool convex_only = false, int precision = 2) { - Paths64 result; - if (rect.IsEmpty() || path.empty()) return result; - Rect64 pathRec = Bounds(path); - if (!rect.Intersects(pathRec)) return result; - if (rect.Contains(pathRec)) - { - result.push_back(path); - return result; - } + return RectClip(rect, PathsD{ path }, convex_only, precision); + } + + inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines) + { + if (rect.IsEmpty() || lines.empty()) return Paths64(); class RectClipLines rcl(rect); - return rcl.Execute(path); + return rcl.Execute(lines); } - inline Paths64 ClipLinesRect(const Rect64& rect, const Paths64& paths) + inline Paths64 RectClipLines(const Rect64& rect, const Path64& line) { - Paths64 result; - if (rect.IsEmpty() || paths.empty()) return result; - class RectClipLines rcl(rect); - for (const Path64& p : paths) - { - Rect64 pathRec = Bounds(p); - if (!rect.Intersects(pathRec)) - continue; - else if (rect.Contains(pathRec)) - result.push_back(p); - else - { - Paths64 pp = rcl.Execute(p); - if (!pp.empty()) - result.insert(result.end(), pp.begin(), pp.end()); - } - } - return result; + return RectClipLines(rect, Paths64{ line }); } - inline PathsD ClipLinesRect(const RectD& rect, const PathD& path, int precision = 2) + inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2) { - if (rect.IsEmpty() || path.empty() || - !rect.Contains(Bounds(path))) return PathsD(); - if (precision < -8 || precision > 8) - throw Clipper2Exception(precision_error); + return RectClip(rect, PathsD{ line }, precision); + } + + inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2) + { + if (rect.IsEmpty() || lines.empty()) return PathsD(); + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return PathsD(); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); class RectClipLines rcl(r); - Path64 p = ScalePath(path, scale); - return ScalePaths(rcl.Execute(p), 1 / scale); - } - - inline PathsD ClipLinesRect(const RectD& rect, const PathsD& paths, int precision = 2) - { - PathsD result; - if (rect.IsEmpty() || paths.empty()) return result; - if (precision < -8 || precision > 8) - throw Clipper2Exception(precision_error); - const double scale = std::pow(10, precision); - Rect64 r = ScaleRect(rect, scale); - class RectClipLines rcl(r); - result.reserve(paths.size()); - for (const PathD& path : paths) - { - RectD pathRec = Bounds(path); - if (!rect.Intersects(pathRec)) - continue; - else if (rect.Contains(pathRec)) - result.push_back(path); - else - { - Path64 p = ScalePath(path, scale); - Paths64 pp = rcl.Execute(p); - if (pp.empty()) continue; - PathsD ppd = ScalePaths(pp, 1 / scale); - result.insert(result.end(), ppd.begin(), ppd.end()); - } - } - return result; + Paths64 p = ScalePaths(lines, scale, error_code); + if (error_code) return PathsD(); + p = rcl.Execute(p); + return ScalePaths(p, 1 / scale, error_code); } namespace details @@ -416,179 +273,219 @@ namespace Clipper2Lib { inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths) { paths.push_back(polypath.Polygon()); - for (const PolyPath* child : polypath) - PolyPathToPaths64(*(PolyPath64*)(child), paths); + for (const auto& child : polypath) + PolyPathToPaths64(*child, paths); } inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths) { paths.push_back(polypath.Polygon()); - for (const PolyPath* child : polypath) - PolyPathToPathsD(*(PolyPathD*)(child), paths); + for (const auto& child : polypath) + PolyPathToPathsD(*child, paths); } inline bool PolyPath64ContainsChildren(const PolyPath64& pp) { - for (auto ch : pp) + for (const auto& child : pp) { - PolyPath64* child = (PolyPath64*)ch; + // return false if this child isn't fully contained by its parent + + // the following algorithm is a bit too crude, and doesn't account + // for rounding errors. A better algorithm is to return false when + // consecutive vertices are found outside the parent's polygon. + + //const Path64& path = pp.Polygon(); + //if (std::any_of(child->Polygon().cbegin(), child->Polygon().cend(), + // [path](const auto& pt) {return (PointInPolygon(pt, path) == + // PointInPolygonResult::IsOutside); })) return false; + + int outsideCnt = 0; for (const Point64& pt : child->Polygon()) - if (PointInPolygon(pt, pp.Polygon()) == PointInPolygonResult::IsOutside) - return false; + { + PointInPolygonResult result = PointInPolygon(pt, pp.Polygon()); + if (result == PointInPolygonResult::IsInside) --outsideCnt; + else if (result == PointInPolygonResult::IsOutside) ++outsideCnt; + if (outsideCnt > 1) return false; + else if (outsideCnt < -1) break; + } + + // now check any nested children too if (child->Count() > 0 && !PolyPath64ContainsChildren(*child)) return false; } return true; } - inline bool GetInt(std::string::const_iterator& iter, const - std::string::const_iterator& end_iter, int64_t& val) + static void OutlinePolyPath(std::ostream& os, + bool isHole, size_t count, const std::string& preamble) { - val = 0; - bool is_neg = *iter == '-'; - if (is_neg) ++iter; - std::string::const_iterator start_iter = iter; - while (iter != end_iter && - ((*iter >= '0') && (*iter <= '9'))) + std::string plural = (count == 1) ? "." : "s."; + if (isHole) { - val = val * 10 + (static_cast(*iter++) - '0'); + if (count) + os << preamble << "+- Hole with " << count << + " nested polygon" << plural << std::endl; + else + os << preamble << "+- Hole" << std::endl; } - if (is_neg) val = -val; - return (iter != start_iter); - } - - inline bool GetFloat(std::string::const_iterator& iter, const - std::string::const_iterator& end_iter, double& val) - { - val = 0; - bool is_neg = *iter == '-'; - if (is_neg) ++iter; - int dec_pos = 1; - const std::string::const_iterator start_iter = iter; - while (iter != end_iter && (*iter == '.' || - ((*iter >= '0') && (*iter <= '9')))) + else { - if (*iter == '.') - { - if (dec_pos != 1) break; - dec_pos = 0; - ++iter; - continue; - } - if (dec_pos != 1) --dec_pos; - val = val * 10 + ((int64_t)(*iter++) - '0'); - } - if (iter == start_iter || dec_pos == 0) return false; - if (dec_pos < 0) - val *= std::pow(10, dec_pos); - if (is_neg) - val *= -1; - return true; - } - - inline void SkipWhiteSpace(std::string::const_iterator& iter, - const std::string::const_iterator& end_iter) - { - while (iter != end_iter && *iter <= ' ') ++iter; - } - - inline void SkipSpacesWithOptionalComma(std::string::const_iterator& iter, - const std::string::const_iterator& end_iter) - { - bool comma_seen = false; - while (iter != end_iter) - { - if (*iter == ' ') ++iter; - else if (*iter == ',') - { - if (comma_seen) return; // don't skip 2 commas! - comma_seen = true; - ++iter; - } - else return; + if (count) + os << preamble << "+- Polygon with " << count << + " hole" << plural << std::endl; + else + os << preamble << "+- Polygon" << std::endl; } } - inline bool has_one_match(const char c, char* chrs) + static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp, + std::string preamble, bool last_child) { - while (*chrs > 0 && c != *chrs) ++chrs; - if (!*chrs) return false; - *chrs = ' '; // only match once per char - return true; + OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); + preamble += (!last_child) ? "| " : " "; + if (pp.Count()) + { + PolyPath64List::const_iterator it = pp.begin(); + for (; it < pp.end() - 1; ++it) + OutlinePolyPath64(os, **it, preamble, false); + OutlinePolyPath64(os, **it, preamble, true); + } } - - inline void SkipUserDefinedChars(std::string::const_iterator& iter, - const std::string::const_iterator& end_iter, const std::string& skip_chars) + static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp, + std::string preamble, bool last_child) { - const size_t MAX_CHARS = 16; - char buff[MAX_CHARS] = {0}; - std::copy(skip_chars.cbegin(), skip_chars.cend(), &buff[0]); - while (iter != end_iter && - (*iter <= ' ' || has_one_match(*iter, buff))) ++iter; - return; + OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); + preamble += (!last_child) ? "| " : " "; + if (pp.Count()) + { + PolyPathDList::const_iterator it = pp.begin(); + for (; it < pp.end() - 1; ++it) + OutlinePolyPathD(os, **it, preamble, false); + OutlinePolyPathD(os, **it, preamble, true); + } } } // end details namespace + inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) + { + PolyPath64List::const_iterator it = pp.begin(); + for (; it < pp.end() - 1; ++it) + details::OutlinePolyPath64(os, **it, " ", false); + details::OutlinePolyPath64(os, **it, " ", true); + os << std::endl << std::endl; + if (!pp.Level()) os << std::endl; + return os; + } + + inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp) + { + PolyPathDList::const_iterator it = pp.begin(); + for (; it < pp.end() - 1; ++it) + details::OutlinePolyPathD(os, **it, " ", false); + details::OutlinePolyPathD(os, **it, " ", true); + os << std::endl << std::endl; + if (!pp.Level()) os << std::endl; + return os; + } + inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree) { Paths64 result; - for (auto child : polytree) - details::PolyPathToPaths64(*(PolyPath64*)(child), result); + for (const auto& child : polytree) + details::PolyPathToPaths64(*child, result); return result; } inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree) { PathsD result; - for (auto child : polytree) - details::PolyPathToPathsD(*(PolyPathD*)(child), result); + for (const auto& child : polytree) + details::PolyPathToPathsD(*child, result); return result; } inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree) { - for (auto child : polytree) + for (const auto& child : polytree) if (child->Count() > 0 && - !details::PolyPath64ContainsChildren(*(PolyPath64*)(child))) + !details::PolyPath64ContainsChildren(*child)) return false; return true; } - inline Path64 MakePath(const std::string& s) - { - const std::string skip_chars = " ,(){}[]"; - Path64 result; - std::string::const_iterator s_iter = s.cbegin(); - details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); - while (s_iter != s.cend()) + namespace details { + + template + inline constexpr void MakePathGeneric(const T list, size_t size, + std::vector& result) { - int64_t y = 0, x = 0; - if (!details::GetInt(s_iter, s.cend(), x)) break; - details::SkipSpacesWithOptionalComma(s_iter, s.cend()); - if (!details::GetInt(s_iter, s.cend(), y)) break; - result.push_back(Point64(x, y)); - details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); + for (size_t i = 0; i < size; ++i) +#ifdef USINGZ + result[i / 2] = U{list[i], list[++i], 0}; +#else + result[i / 2] = U{list[i], list[++i]}; +#endif } + + } // end details namespace + + template::value && + !std::is_same::value, bool + >::type = true> + inline Path64 MakePath(const std::vector& list) + { + const auto size = list.size() - list.size() % 2; + if (list.size() != size) + DoError(non_pair_error_i); // non-fatal without exception handling + Path64 result(size / 2); // else ignores unpaired value + details::MakePathGeneric(list, size, result); return result; } - - inline PathD MakePathD(const std::string& s) + + template::value && + !std::is_same::value, bool + >::type = true> + inline Path64 MakePath(const T(&list)[N]) { - const std::string skip_chars = " ,(){}[]"; - PathD result; - std::string::const_iterator s_iter = s.cbegin(); - details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); - while (s_iter != s.cend()) - { - double y = 0, x = 0; - if (!details::GetFloat(s_iter, s.cend(), x)) break; - details::SkipSpacesWithOptionalComma(s_iter, s.cend()); - if (!details::GetFloat(s_iter, s.cend(), y)) break; - result.push_back(PointD(x, y)); - details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); - } + // Make the compiler error on unpaired value (i.e. no runtime effects). + static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); + Path64 result(N / 2); + details::MakePathGeneric(list, N, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline PathD MakePathD(const std::vector& list) + { + const auto size = list.size() - list.size() % 2; + if (list.size() != size) + DoError(non_pair_error_i); // non-fatal without exception handling + PathD result(size / 2); // else ignores unpaired value + details::MakePathGeneric(list, size, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline PathD MakePathD(const T(&list)[N]) + { + // Make the compiler error on unpaired value (i.e. no runtime effects). + static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); + PathD result(N / 2); + details::MakePathGeneric(list, N, result); return result; } @@ -641,12 +538,14 @@ namespace Clipper2Lib { inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false) { - if (precision > 8 || precision < -8) - throw Clipper2Exception(precision_error); + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return PathD(); const double scale = std::pow(10, precision); - Path64 p = ScalePath(path, scale); + Path64 p = ScalePath(path, scale, error_code); + if (error_code) return PathD(); p = TrimCollinear(p, is_open_path); - return ScalePath(p, 1/scale); + return ScalePath(p, 1/scale, error_code); } template @@ -721,6 +620,108 @@ namespace Clipper2Lib { return Sqr(a * d - c * b) / (c * c + d * d); } + inline size_t GetNext(size_t current, size_t high, + const std::vector& flags) + { + ++current; + while (current <= high && flags[current]) ++current; + if (current <= high) return current; + current = 0; + while (flags[current]) ++current; + return current; + } + + inline size_t GetPrior(size_t current, size_t high, + const std::vector& flags) + { + if (current == 0) current = high; + else --current; + while (current > 0 && flags[current]) --current; + if (!flags[current]) return current; + current = high; + while (flags[current]) --current; + return current; + } + + template + inline Path SimplifyPath(const Path path, + double epsilon, bool isOpenPath = false) + { + const size_t len = path.size(), high = len -1; + const double epsSqr = Sqr(epsilon); + if (len < 4) return Path(path); + + std::vector flags(len); + std::vector distSqr(len); + size_t prior = high, curr = 0, start, next, prior2, next2; + if (isOpenPath) + { + distSqr[0] = MAX_DBL; + distSqr[high] = MAX_DBL; + } + else + { + distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); + distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); + } + for (size_t i = 1; i < high; ++i) + distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); + + for (;;) + { + if (distSqr[curr] > epsSqr) + { + start = curr; + do + { + curr = GetNext(curr, high, flags); + } while (curr != start && distSqr[curr] > epsSqr); + if (curr == start) break; + } + + prior = GetPrior(curr, high, flags); + next = GetNext(curr, high, flags); + if (next == prior) break; + + if (distSqr[next] < distSqr[curr]) + { + flags[next] = true; + next = GetNext(next, high, flags); + next2 = GetNext(next, high, flags); + distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); + if (next != high || !isOpenPath) + distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); + curr = next; + } + else + { + flags[curr] = true; + curr = next; + next = GetNext(next, high, flags); + prior2 = GetPrior(prior, high, flags); + distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); + if (prior != 0 || !isOpenPath) + distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); + } + } + Path result; + result.reserve(len); + for (typename Path::size_type i = 0; i < len; ++i) + if (!flags[i]) result.push_back(path[i]); + return result; + } + + template + inline Paths SimplifyPaths(const Paths paths, + double epsilon, bool isOpenPath = false) + { + Paths result; + result.reserve(paths.size()); + for (const auto& path : paths) + result.push_back(SimplifyPath(path, epsilon, isOpenPath)); + return result; + } + template inline void RDP(const Path path, std::size_t begin, std::size_t end, double epsSqrd, std::vector& flags) @@ -764,8 +765,9 @@ namespace Clipper2Lib { { Paths result; result.reserve(paths.size()); - for (const Path& path : paths) - result.push_back(RamerDouglasPeucker(path, epsilon)); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [epsilon](const auto& path) + { return RamerDouglasPeucker(path, epsilon); }); return result; } diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h index ca0ab6be81..71c221bb50 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 October 2022 * +* Date : 28 January 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : Minkowski Sum and Difference * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -92,11 +92,12 @@ namespace Clipper2Lib inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) { + int error_code = 0; double scale = pow(10, decimalPlaces); - Path64 pat64 = ScalePath(pattern, scale); - Path64 path64 = ScalePath(path, scale); + Path64 pat64 = ScalePath(pattern, scale, error_code); + Path64 path64 = ScalePath(path, scale, error_code); Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero); - return ScalePaths(tmp, 1 / scale); + return ScalePaths(tmp, 1 / scale, error_code); } inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed) @@ -106,11 +107,12 @@ namespace Clipper2Lib inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) { + int error_code = 0; double scale = pow(10, decimalPlaces); - Path64 pat64 = ScalePath(pattern, scale); - Path64 path64 = ScalePath(path, scale); + Path64 pat64 = ScalePath(pattern, scale, error_code); + Path64 path64 = ScalePath(path, scale, error_code); Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero); - return ScalePaths(tmp, 1 / scale); + return ScalePaths(tmp, 1 / scale, error_code); } } // Clipper2Lib namespace diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h index 4fd130bf4d..f5d47e07ee 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 October 2022 * +* Date : 22 March 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -11,6 +11,7 @@ #define CLIPPER_OFFSET_H_ #include "clipper.core.h" +#include "clipper.engine.h" namespace Clipper2Lib { @@ -23,47 +24,57 @@ enum class EndType {Polygon, Joined, Butt, Square, Round}; //Joined : offsets both sides of a path, with joined ends //Polygon: offsets only one side of a closed path + class ClipperOffset { private: class Group { public: - Paths64 paths_in_; - Paths64 paths_out_; - Path64 path_; - bool is_reversed_ = false; - JoinType join_type_; - EndType end_type_; - Group(const Paths64& paths, JoinType join_type, EndType end_type) : - paths_in_(paths), join_type_(join_type), end_type_(end_type) {} + Paths64 paths_in; + Paths64 paths_out; + Path64 path; + bool is_reversed = false; + JoinType join_type; + EndType end_type; + Group(const Paths64& _paths, JoinType _join_type, EndType _end_type) : + paths_in(_paths), join_type(_join_type), end_type(_end_type) {} }; + int error_code_ = 0; + double delta_ = 0.0; double group_delta_ = 0.0; double abs_group_delta_ = 0.0; double temp_lim_ = 0.0; double steps_per_rad_ = 0.0; + double step_sin_ = 0.0; + double step_cos_ = 0.0; PathD norms; Paths64 solution; std::vector groups_; JoinType join_type_ = JoinType::Square; - + EndType end_type_ = EndType::Polygon; + double miter_limit_ = 0.0; double arc_tolerance_ = 0.0; - bool merge_groups_ = true; bool preserve_collinear_ = false; bool reverse_solution_ = false; +#ifdef USINGZ + ZCallback64 zCallback64_ = nullptr; +#endif + void DoSquare(Group& group, const Path64& path, size_t j, size_t k); void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle); void BuildNormals(const Path64& path); void OffsetPolygon(Group& group, Path64& path); void OffsetOpenJoined(Group& group, Path64& path); - void OffsetOpenPath(Group& group, Path64& path, EndType endType); + void OffsetOpenPath(Group& group, Path64& path); void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k); - void DoGroupOffset(Group &group, double delta); + void DoGroupOffset(Group &group); + void ExecuteInternal(double delta); public: - ClipperOffset(double miter_limit = 2.0, + explicit ClipperOffset(double miter_limit = 2.0, double arc_tolerance = 0.0, bool preserve_collinear = false, bool reverse_solution = false) : @@ -73,13 +84,13 @@ public: ~ClipperOffset() { Clear(); }; + int ErrorCode() { return error_code_; }; void AddPath(const Path64& path, JoinType jt_, EndType et_); void AddPaths(const Paths64& paths, JoinType jt_, EndType et_); - void AddPath(const PathD &p, JoinType jt_, EndType et_); - void AddPaths(const PathsD &p, JoinType jt_, EndType et_); void Clear() { groups_.clear(); norms.clear(); }; - Paths64 Execute(double delta); + void Execute(double delta, Paths64& paths); + void Execute(double delta, PolyTree64& polytree); double MiterLimit() const { return miter_limit_; } void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; } @@ -88,19 +99,15 @@ public: double ArcTolerance() const { return arc_tolerance_; } void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; } - //MergeGroups: A path group is one or more paths added via the AddPath or - //AddPaths methods. By default these path groups will be offset - //independently of other groups and this may cause overlaps (intersections). - //However, when MergeGroups is enabled, any overlapping offsets will be - //merged (via a clipping union operation) to remove overlaps. - bool MergeGroups() const { return merge_groups_; } - void MergeGroups(bool merge_groups) { merge_groups_ = merge_groups; } - bool PreserveCollinear() const { return preserve_collinear_; } void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;} bool ReverseSolution() const { return reverse_solution_; } void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;} + +#ifdef USINGZ + void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; } +#endif }; } diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h index 98f43e0b2d..2a9bb35d08 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2022 * +* Date : 9 February 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -12,38 +12,70 @@ #include #include +#include #include "clipper.h" #include "clipper.core.h" -namespace Clipper2Lib +namespace Clipper2Lib { enum class Location { Left, Top, Right, Bottom, Inside }; + class OutPt2; + typedef std::vector OutPt2List; + + class OutPt2 { + public: + Point64 pt; + size_t owner_idx; + OutPt2List* edge; + OutPt2* next; + OutPt2* prev; + }; + + //------------------------------------------------------------------------------ + // RectClip + //------------------------------------------------------------------------------ + class RectClip { + private: + void ExecuteInternal(const Path64& path); + Path64 GetPath(OutPt2*& op); protected: const Rect64 rect_; - const Point64 mp_; - const Path64 rectPath_; - Path64 result_; + const Path64 rect_as_path_; + const Point64 rect_mp_; + Rect64 path_bounds_; + std::deque op_container_; + OutPt2List results_; // each path can be broken into multiples + OutPt2List edges_[8]; // clockwise and counter-clockwise std::vector start_locs_; - + void CheckEdges(); + void TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw); void GetNextLocation(const Path64& path, Location& loc, int& i, int highI); + OutPt2* Add(Point64 pt, bool start_new = false); void AddCorner(Location prev, Location curr); void AddCorner(Location& loc, bool isClockwise); public: - RectClip(const Rect64& rect) : + explicit RectClip(const Rect64& rect) : rect_(rect), - mp_(rect.MidPoint()), - rectPath_(rect.AsPath()) {} - Path64 Execute(const Path64& path); + rect_as_path_(rect.AsPath()), + rect_mp_(rect.MidPoint()) {} + Paths64 Execute(const Paths64& paths, bool convex_only = false); }; + //------------------------------------------------------------------------------ + // RectClipLines + //------------------------------------------------------------------------------ + class RectClipLines : public RectClip { + private: + void ExecuteInternal(const Path64& path); + Path64 GetPath(OutPt2*& op); public: - RectClipLines(const Rect64& rect) : RectClip(rect) {}; - Paths64 Execute(const Path64& path); + explicit RectClipLines(const Rect64& rect) : RectClip(rect) {}; + Paths64 Execute(const Paths64& paths); }; } // Clipper2Lib namespace diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp index 57ad7cad6a..63031df1ed 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp @@ -1,3543 +1,2976 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2022 * +* Date : 19 March 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -#include +#include #include #include #include #include #include + #include "clipper2/clipper.engine.h" +// https://github.com/AngusJohnson/Clipper2/discussions/334 +// #discussioncomment-4248602 +#if defined(_MSC_VER) && ( defined(_M_AMD64) || defined(_M_X64) ) +#include +#include +#define fmin(a,b) _mm_cvtsd_f64(_mm_min_sd(_mm_set_sd(a),_mm_set_sd(b))) +#define fmax(a,b) _mm_cvtsd_f64(_mm_max_sd(_mm_set_sd(a),_mm_set_sd(b))) +#define nearbyint(a) _mm_cvtsd_si64(_mm_set_sd(a)) /* Note: expression type is (int64_t) */ +#endif + namespace Clipper2Lib { - static const double FloatingPointTolerance = 1.0e-12; - static const Rect64 invalid_rect = Rect64( - std::numeric_limits::max(), - std::numeric_limits::max(), - -std::numeric_limits::max(), - -std::numeric_limits::max() - ); - - //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' (or sides if they're simple polygons). 'Local Minima' refer to - //vertices where descending bounds become ascending ones. - - struct Scanline { - int64_t y = 0; - Scanline* next = nullptr; - - explicit Scanline(int64_t y_) : y(y_) {} - }; - - struct Joiner { - int idx; - OutPt* op1; - OutPt* op2; - Joiner* next1; - Joiner* next2; - Joiner* nextH; - - explicit Joiner(OutPt* op1_, OutPt* op2_, Joiner* nexth) : - op1(op1_), op2(op2_), nextH(nexth) - { - idx = -1; - next1 = op1->joiner; - op1->joiner = this; - - if (op2) - { - next2 = op2->joiner; - op2->joiner = this; - } - else - next2 = nullptr; - } - - }; - - struct LocMinSorter { - inline bool operator()(const LocalMinima* locMin1, const LocalMinima* locMin2) - { - if (locMin2->vertex->pt.y != locMin1->vertex->pt.y) - return locMin2->vertex->pt.y < locMin1->vertex->pt.y; - else - return locMin2->vertex->pt.x < locMin1->vertex->pt.x; - } - }; - - inline bool IsOdd(int val) - { - return (val & 1) ? true : false; - } - - - inline bool IsHotEdge(const Active& e) - { - return (e.outrec); - } - - - inline bool IsOpen(const Active& e) - { - return (e.local_min->is_open); - } - - - inline bool IsOpenEnd(const Vertex& v) - { - return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) != - VertexFlags::None; - } - - - inline bool IsOpenEnd(const Active& ae) - { - return IsOpenEnd(*ae.vertex_top); - } - - - inline Active* GetPrevHotEdge(const Active& e) - { - Active* prev = e.prev_in_ael; - while (prev && (IsOpen(*prev) || !IsHotEdge(*prev))) - prev = prev->prev_in_ael; - return prev; - } - - inline bool IsFront(const Active& e) - { - return (&e == e.outrec->front_edge); - } - - inline bool IsInvalidPath(OutPt* op) - { - return (!op || op->next == op); - } - - /******************************************************************************* - * Dx: 0(90deg) * - * | * - * +inf (180deg) <--- o ---> -inf (0deg) * - *******************************************************************************/ - - inline double GetDx(const Point64& pt1, const Point64& pt2) - { - double dy = double(pt2.y - pt1.y); - if (dy != 0) - return double(pt2.x - pt1.x) / dy; - else if (pt2.x > pt1.x) - return -std::numeric_limits::max(); - else - return std::numeric_limits::max(); - } - - - inline int64_t TopX(const Active& ae, const int64_t currentY) - { - if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x; - else if (currentY == ae.bot.y) return ae.bot.x; - else return ae.bot.x + static_cast(std::nearbyint(ae.dx * (currentY - ae.bot.y))); - // nb: std::nearbyint (or std::round) substantially *improves* performance here - // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList(). - } - - - inline bool IsHorizontal(const Active& e) - { - return (e.top.y == e.bot.y); - } - - - inline bool IsHeadingRightHorz(const Active& e) - { - return e.dx == -std::numeric_limits::max(); - } - - - inline bool IsHeadingLeftHorz(const Active& e) - { - return e.dx == std::numeric_limits::max(); - } - - - inline void SwapActives(Active*& e1, Active*& e2) - { - Active* e = e1; - e1 = e2; - e2 = e; - } - - - inline PathType GetPolyType(const Active& e) - { - return e.local_min->polytype; - } - - - inline bool IsSamePolyType(const Active& e1, const Active& e2) - { - return e1.local_min->polytype == e2.local_min->polytype; - } - - - Point64 GetIntersectPoint(const Active& e1, const Active& e2) - { - double b1, b2; - if (e1.dx == e2.dx) return e1.top; - - if (e1.dx == 0) - { - if (IsHorizontal(e2)) return Point64(e1.bot.x, e2.bot.y); - b2 = e2.bot.y - (e2.bot.x / e2.dx); - return Point64(e1.bot.x, - static_cast(std::round(e1.bot.x / e2.dx + b2))); - } - else if (e2.dx == 0) - { - if (IsHorizontal(e1)) return Point64(e2.bot.x, e1.bot.y); - b1 = e1.bot.y - (e1.bot.x / e1.dx); - return Point64(e2.bot.x, - static_cast(std::round(e2.bot.x / e1.dx + b1))); - } - else - { - b1 = e1.bot.x - e1.bot.y * e1.dx; - b2 = e2.bot.x - e2.bot.y * e2.dx; - double q = (b2 - b1) / (e1.dx - e2.dx); - return (abs(e1.dx) < abs(e2.dx)) ? - Point64(static_cast((e1.dx * q + b1)), - static_cast((q))) : - Point64(static_cast((e2.dx * q + b2)), - static_cast((q))); - } - } - - - bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b, - const Point64& ln2a, const Point64& ln2b, PointD& ip) - { - ip = PointD(0, 0); - double m1, b1, m2, b2; - if (ln1b.x == ln1a.x) - { - if (ln2b.x == ln2a.x) return false; - m2 = static_cast(ln2b.y - ln2a.y) / - static_cast(ln2b.x - ln2a.x); - b2 = ln2a.y - m2 * ln2a.x; - ip.x = static_cast(ln1a.x); - ip.y = m2 * ln1a.x + b2; - } - else if (ln2b.x == ln2a.x) - { - m1 = static_cast(ln1b.y - ln1a.y) / - static_cast(ln1b.x - ln1a.x); - b1 = ln1a.y - m1 * ln1a.x; - ip.x = static_cast(ln2a.x); - ip.y = m1 * ln2a.x + b1; - } - else - { - m1 = static_cast(ln1b.y - ln1a.y) / - static_cast(ln1b.x - ln1a.x); - b1 = ln1a.y - m1 * ln1a.x; - m2 = static_cast(ln2b.y - ln2a.y) / - static_cast(ln2b.x - ln2a.x); - b2 = ln2a.y - m2 * ln2a.x; - if (std::abs(m1 - m2) > FloatingPointTolerance) - { - ip.x = (b2 - b1) / (m1 - m2); - ip.y = m1 * ip.x + b1; - } - else - { - ip.x = static_cast(ln1a.x + ln1b.x) / 2; - ip.y = static_cast(ln1a.y + ln1b.y) / 2; - } - } - return true; - } - - - inline void SetDx(Active& e) - { - e.dx = GetDx(e.bot, e.top); - } - - - inline Vertex* NextVertex(const Active& e) - { - if (e.wind_dx > 0) - return e.vertex_top->next; - else - return e.vertex_top->prev; - } - - - //PrevPrevVertex: useful to get the (inverted Y-axis) top of the - //alternate edge (ie left or right bound) during edge insertion. - inline Vertex* PrevPrevVertex(const Active& ae) - { - if (ae.wind_dx > 0) - return ae.vertex_top->prev->prev; - else - return ae.vertex_top->next->next; - } - - - inline Active* ExtractFromSEL(Active* ae) - { - Active* res = ae->next_in_sel; - if (res) - res->prev_in_sel = ae->prev_in_sel; - ae->prev_in_sel->next_in_sel = res; - return res; - } - - - inline void Insert1Before2InSEL(Active* ae1, Active* ae2) - { - ae1->prev_in_sel = ae2->prev_in_sel; - if (ae1->prev_in_sel) - ae1->prev_in_sel->next_in_sel = ae1; - ae1->next_in_sel = ae2; - ae2->prev_in_sel = ae1; - } - - inline bool IsMaxima(const Vertex& v) - { - return ((v.flags & VertexFlags::LocalMax) != VertexFlags::None); - } - - - inline bool IsMaxima(const Active& e) - { - return IsMaxima(*e.vertex_top); - } - - Vertex* GetCurrYMaximaVertex(const Active& e) - { - Vertex* result = e.vertex_top; - if (e.wind_dx > 0) - 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 - return result; - } - - Active* GetMaximaPair(const Active& e) - { - Active* e2; - e2 = e.next_in_ael; - while (e2) - { - if (e2->vertex_top == e.vertex_top) return e2; // Found! - e2 = e2->next_in_ael; - } - return nullptr; - } - - Active* GetHorzMaximaPair(const Active& horz, const Vertex* vert_max) - { - //we can't be sure whether the MaximaPair is on the left or right, so ... - Active* result = horz.prev_in_ael; - while (result && result->curr_x >= vert_max->pt.x) - { - if (result->vertex_top == vert_max) return result; // Found! - result = result->prev_in_ael; - } - result = horz.next_in_ael; - while (result && TopX(*result, horz.top.y) <= vert_max->pt.x) - { - if (result->vertex_top == vert_max) return result; // Found! - result = result->next_in_ael; - } - return nullptr; - } - - inline int PointCount(OutPt* op) - { - OutPt* op2 = op; - int cnt = 0; - do - { - op2 = op2->next; - ++cnt; - } while (op2 != op); - return cnt; - } - - - inline OutPt* InsertOp(const Point64& pt, OutPt* insertAfter) - { - OutPt* result = new OutPt(pt, insertAfter->outrec); - result->next = insertAfter->next; - insertAfter->next->prev = result; - insertAfter->next = result; - result->prev = insertAfter; - return result; - } - - - inline OutPt* DisposeOutPt(OutPt* op) - { - OutPt* result = op->next; - op->prev->next = op->next; - op->next->prev = op->prev; - delete op; - return result; - } - - - inline void DisposeOutPts(OutRec& outrec) - { - if (!outrec.pts) return; - OutPt* op2 = outrec.pts->next; - while (op2 != outrec.pts) - { - OutPt* tmp = op2->next; - delete op2; - op2 = tmp; - } - delete outrec.pts; - outrec.pts = nullptr; - } - - - bool IntersectListSort(const IntersectNode& a, const IntersectNode& b) - { - //note different inequality tests ... - return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y); - } - - - inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge) - { - outrec.front_edge = &start_edge; - outrec.back_edge = &end_edge; - } - - - void SwapOutrecs(Active& e1, Active& e2) - { - OutRec* or1 = e1.outrec; - OutRec* or2 = e2.outrec; - if (or1 == or2) - { - Active* e = or1->front_edge; - or1->front_edge = or1->back_edge; - or1->back_edge = e; - return; - } - if (or1) - { - if (&e1 == or1->front_edge) - or1->front_edge = &e2; - else - or1->back_edge = &e2; - } - if (or2) - { - if (&e2 == or2->front_edge) - or2->front_edge = &e1; - else - or2->back_edge = &e1; - } - e1.outrec = or2; - e2.outrec = or1; - } - - - double Area(OutPt* op) - { - //https://en.wikipedia.org/wiki/Shoelace_formula - double result = 0.0; - OutPt* op2 = op; - do - { - result += static_cast(op2->prev->pt.y + op2->pt.y) * - static_cast(op2->prev->pt.x - op2->pt.x); - op2 = op2->next; - } while (op2 != op); - return result * 0.5; - } - - inline double AreaTriangle(const Point64& pt1, - const Point64& pt2, const Point64& pt3) - { - return (static_cast(pt3.y + pt1.y) * static_cast(pt3.x - pt1.x) + - static_cast(pt1.y + pt2.y) * static_cast(pt1.x - pt2.x) + - static_cast(pt2.y + pt3.y) * static_cast(pt2.x - pt3.x)); - } - - void ReverseOutPts(OutPt* op) - { - if (!op) return; - - OutPt* op1 = op; - OutPt* op2; - - do - { - op2 = op1->next; - op1->next = op1->prev; - op1->prev = op2; - op1 = op2; - } while (op1 != op); - } - - - inline void SwapSides(OutRec& outrec) - { - Active* e2 = outrec.front_edge; - outrec.front_edge = outrec.back_edge; - outrec.back_edge = e2; - outrec.pts = outrec.pts->next; - } - - - inline OutRec* GetRealOutRec(OutRec* outrec) - { - while (outrec && !outrec->pts) outrec = outrec->owner; - return outrec; - } - - - inline void UncoupleOutRec(Active ae) - { - OutRec* outrec = ae.outrec; - if (!outrec) return; - outrec->front_edge->outrec = nullptr; - outrec->back_edge->outrec = nullptr; - outrec->front_edge = nullptr; - outrec->back_edge = nullptr; - } - - - inline bool AreReallyClose(const Point64& pt1, const Point64& pt2) - { - return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2); - } - - - inline bool IsValidClosedPath(const OutPt* op) - { - return (op && op->next != op && op->next != op->prev && - //also treat inconsequential polygons as invalid - !(op->next->next == op->prev && - (AreReallyClose(op->pt, op->next->pt) || - AreReallyClose(op->pt, op->prev->pt)))); - } - - inline bool OutrecIsAscending(const Active* hotEdge) - { - return (hotEdge == hotEdge->outrec->front_edge); - } - - inline void SwapFrontBackSides(OutRec& outrec) - { - Active* tmp = outrec.front_edge; - outrec.front_edge = outrec.back_edge; - outrec.back_edge = tmp; - outrec.pts = outrec.pts->next; - } - - inline bool EdgesAdjacentInAEL(const IntersectNode& inode) - { - return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2); - } - - inline bool TestJoinWithPrev1(const Active& e) - { - //this is marginally quicker than TestJoinWithPrev2 - //but can only be used when e.PrevInAEL.currX is accurate - return IsHotEdge(e) && !IsOpen(e) && - e.prev_in_ael && e.prev_in_ael->curr_x == e.curr_x && - IsHotEdge(*e.prev_in_ael) && !IsOpen(*e.prev_in_ael) && - (CrossProduct(e.prev_in_ael->top, e.bot, e.top) == 0); - } - - inline bool TestJoinWithPrev2(const Active& e, const Point64& curr_pt) - { - return IsHotEdge(e) && !IsOpen(e) && - e.prev_in_ael && !IsOpen(*e.prev_in_ael) && - IsHotEdge(*e.prev_in_ael) && (e.prev_in_ael->top.y < e.bot.y) && - (std::llabs(TopX(*e.prev_in_ael, curr_pt.y) - curr_pt.x) < 2) && - (CrossProduct(e.prev_in_ael->top, curr_pt, e.top) == 0); - } - - inline bool TestJoinWithNext1(const Active& e) - { - //this is marginally quicker than TestJoinWithNext2 - //but can only be used when e.NextInAEL.currX is accurate - return IsHotEdge(e) && !IsOpen(e) && - e.next_in_ael && (e.next_in_ael->curr_x == e.curr_x) && - IsHotEdge(*e.next_in_ael) && !IsOpen(*e.next_in_ael) && - (CrossProduct(e.next_in_ael->top, e.bot, e.top) == 0); - } - - inline bool TestJoinWithNext2(const Active& e, const Point64& curr_pt) - { - return IsHotEdge(e) && !IsOpen(e) && - e.next_in_ael && !IsOpen(*e.next_in_ael) && - IsHotEdge(*e.next_in_ael) && (e.next_in_ael->top.y < e.bot.y) && - (std::llabs(TopX(*e.next_in_ael, curr_pt.y) - curr_pt.x) < 2) && - (CrossProduct(e.next_in_ael->top, curr_pt, e.top) == 0); - } - - //------------------------------------------------------------------------------ - // ClipperBase methods ... - //------------------------------------------------------------------------------ - - ClipperBase::~ClipperBase() - { - Clear(); - } - - void ClipperBase::DeleteEdges(Active*& e) - { - while (e) - { - Active* e2 = e; - e = e->next_in_ael; - delete e2; - } - } - - void ClipperBase::CleanUp() - { - DeleteEdges(actives_); - scanline_list_ = std::priority_queue(); - intersect_nodes_.clear(); - DisposeAllOutRecs(); - } - - - void ClipperBase::Clear() - { - CleanUp(); - DisposeVerticesAndLocalMinima(); - current_locmin_iter_ = minima_list_.begin(); - minima_list_sorted_ = false; - has_open_paths_ = false; - } - - - void ClipperBase::Reset() - { - if (!minima_list_sorted_) - { - std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); - minima_list_sorted_ = true; - } - std::vector::const_reverse_iterator i; - for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) - InsertScanline((*i)->vertex->pt.y); - - current_locmin_iter_ = minima_list_.begin(); - actives_ = nullptr; - sel_ = nullptr; - succeeded_ = true; - } + 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. + + struct Scanline { + int64_t y = 0; + Scanline* next = nullptr; + + explicit Scanline(int64_t y_) : y(y_) {} + }; + + struct HorzSegSorter { + inline bool operator()(const HorzSegment& hs1, const HorzSegment& hs2) + { + if (!hs1.right_op || !hs2.right_op) return (hs1.right_op); + return hs2.left_op->pt.x > hs1.left_op->pt.x; + } + }; + + struct LocMinSorter { + inline bool operator()(const LocalMinima_ptr& locMin1, + const LocalMinima_ptr& locMin2) + { + if (locMin2->vertex->pt.y != locMin1->vertex->pt.y) + return locMin2->vertex->pt.y < locMin1->vertex->pt.y; + else + return locMin2->vertex->pt.x > locMin1->vertex->pt.x; + } + }; + + inline bool IsOdd(int val) + { + return (val & 1) ? true : false; + } + + + inline bool IsHotEdge(const Active& e) + { + return (e.outrec); + } + + + inline bool IsOpen(const Active& e) + { + return (e.local_min->is_open); + } + + + inline bool IsOpenEnd(const Vertex& v) + { + return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) != + VertexFlags::None; + } + + + inline bool IsOpenEnd(const Active& ae) + { + return IsOpenEnd(*ae.vertex_top); + } + + + inline Active* GetPrevHotEdge(const Active& e) + { + Active* prev = e.prev_in_ael; + while (prev && (IsOpen(*prev) || !IsHotEdge(*prev))) + prev = prev->prev_in_ael; + return prev; + } + + inline bool IsFront(const Active& e) + { + return (&e == e.outrec->front_edge); + } + + inline bool IsInvalidPath(OutPt* op) + { + return (!op || op->next == op); + } + + /******************************************************************************* + * Dx: 0(90deg) * + * | * + * +inf (180deg) <--- o ---> -inf (0deg) * + *******************************************************************************/ + + inline double GetDx(const Point64& pt1, const Point64& pt2) + { + double dy = double(pt2.y - pt1.y); + if (dy != 0) + return double(pt2.x - pt1.x) / dy; + else if (pt2.x > pt1.x) + return -std::numeric_limits::max(); + else + return std::numeric_limits::max(); + } + + inline int64_t TopX(const Active& ae, const int64_t currentY) + { + if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x; + else if (currentY == ae.bot.y) return ae.bot.x; + else return ae.bot.x + static_cast(nearbyint(ae.dx * (currentY - ae.bot.y))); + // nb: std::nearbyint (or std::round) substantially *improves* performance here + // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList(). + } + + + inline bool IsHorizontal(const Active& e) + { + return (e.top.y == e.bot.y); + } + + + inline bool IsHeadingRightHorz(const Active& e) + { + return e.dx == -std::numeric_limits::max(); + } + + + inline bool IsHeadingLeftHorz(const Active& e) + { + return e.dx == std::numeric_limits::max(); + } + + + inline void SwapActives(Active*& e1, Active*& e2) + { + Active* e = e1; + e1 = e2; + e2 = e; + } + + inline PathType GetPolyType(const Active& e) + { + return e.local_min->polytype; + } + + inline bool IsSamePolyType(const Active& e1, const Active& e2) + { + return e1.local_min->polytype == e2.local_min->polytype; + } + + inline void SetDx(Active& e) + { + e.dx = GetDx(e.bot, e.top); + } + + inline Vertex* NextVertex(const Active& e) + { + if (e.wind_dx > 0) + return e.vertex_top->next; + else + return e.vertex_top->prev; + } + + //PrevPrevVertex: useful to get the (inverted Y-axis) top of the + //alternate edge (ie left or right bound) during edge insertion. + inline Vertex* PrevPrevVertex(const Active& ae) + { + if (ae.wind_dx > 0) + return ae.vertex_top->prev->prev; + else + return ae.vertex_top->next->next; + } + + + inline Active* ExtractFromSEL(Active* ae) + { + Active* res = ae->next_in_sel; + if (res) + res->prev_in_sel = ae->prev_in_sel; + ae->prev_in_sel->next_in_sel = res; + return res; + } + + + inline void Insert1Before2InSEL(Active* ae1, Active* ae2) + { + ae1->prev_in_sel = ae2->prev_in_sel; + if (ae1->prev_in_sel) + ae1->prev_in_sel->next_in_sel = ae1; + ae1->next_in_sel = ae2; + ae2->prev_in_sel = ae1; + } + + inline bool IsMaxima(const Vertex& v) + { + return ((v.flags & VertexFlags::LocalMax) != VertexFlags::None); + } + + + inline bool IsMaxima(const Active& e) + { + return IsMaxima(*e.vertex_top); + } + + inline Vertex* GetCurrYMaximaVertex_Open(const Active& e) + { + Vertex* result = e.vertex_top; + if (e.wind_dx > 0) + while ((result->next->pt.y == result->pt.y) && + ((result->flags & (VertexFlags::OpenEnd | + VertexFlags::LocalMax)) == VertexFlags::None)) + result = result->next; + else + while (result->prev->pt.y == result->pt.y && + ((result->flags & (VertexFlags::OpenEnd | + VertexFlags::LocalMax)) == VertexFlags::None)) + result = result->prev; + if (!IsMaxima(*result)) result = nullptr; // not a maxima + return result; + } + + inline Vertex* GetCurrYMaximaVertex(const Active& e) + { + Vertex* result = e.vertex_top; + if (e.wind_dx > 0) + 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 + return result; + } + + Active* GetMaximaPair(const Active& e) + { + Active* e2; + e2 = e.next_in_ael; + while (e2) + { + if (e2->vertex_top == e.vertex_top) return e2; // Found! + e2 = e2->next_in_ael; + } + return nullptr; + } + + inline int PointCount(OutPt* op) + { + OutPt* op2 = op; + int cnt = 0; + do + { + op2 = op2->next; + ++cnt; + } while (op2 != op); + return cnt; + } + + inline OutPt* DuplicateOp(OutPt* op, bool insert_after) + { + OutPt* result = new OutPt(op->pt, op->outrec); + if (insert_after) + { + result->next = op->next; + result->next->prev = result; + result->prev = op; + op->next = result; + } + else + { + result->prev = op->prev; + result->prev->next = result; + result->next = op; + op->prev = result; + } + return result; + } + + inline OutPt* DisposeOutPt(OutPt* op) + { + OutPt* result = op->next; + op->prev->next = op->next; + op->next->prev = op->prev; + delete op; + return result; + } + + + inline void DisposeOutPts(OutRec* outrec) + { + OutPt* op = outrec->pts; + op->prev->next = nullptr; + while (op) + { + OutPt* tmp = op; + op = op->next; + delete tmp; + }; + outrec->pts = nullptr; + } + + + bool IntersectListSort(const IntersectNode& a, const IntersectNode& b) + { + //note different inequality tests ... + return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y); + } + + + inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge) + { + outrec.front_edge = &start_edge; + outrec.back_edge = &end_edge; + } + + + void SwapOutrecs(Active& e1, Active& e2) + { + OutRec* or1 = e1.outrec; + OutRec* or2 = e2.outrec; + if (or1 == or2) + { + Active* e = or1->front_edge; + or1->front_edge = or1->back_edge; + or1->back_edge = e; + return; + } + if (or1) + { + if (&e1 == or1->front_edge) + or1->front_edge = &e2; + else + or1->back_edge = &e2; + } + if (or2) + { + if (&e2 == or2->front_edge) + or2->front_edge = &e1; + else + or2->back_edge = &e1; + } + e1.outrec = or2; + e2.outrec = or1; + } + + + double Area(OutPt* op) + { + //https://en.wikipedia.org/wiki/Shoelace_formula + double result = 0.0; + OutPt* op2 = op; + do + { + result += static_cast(op2->prev->pt.y + op2->pt.y) * + static_cast(op2->prev->pt.x - op2->pt.x); + op2 = op2->next; + } while (op2 != op); + return result * 0.5; + } + + inline double AreaTriangle(const Point64& pt1, + const Point64& pt2, const Point64& pt3) + { + return (static_cast(pt3.y + pt1.y) * static_cast(pt3.x - pt1.x) + + static_cast(pt1.y + pt2.y) * static_cast(pt1.x - pt2.x) + + static_cast(pt2.y + pt3.y) * static_cast(pt2.x - pt3.x)); + } + + void ReverseOutPts(OutPt* op) + { + if (!op) return; + + OutPt* op1 = op; + OutPt* op2; + + do + { + op2 = op1->next; + op1->next = op1->prev; + op1->prev = op2; + op1 = op2; + } while (op1 != op); + } + + inline void SwapSides(OutRec& outrec) + { + Active* e2 = outrec.front_edge; + outrec.front_edge = outrec.back_edge; + outrec.back_edge = e2; + outrec.pts = outrec.pts->next; + } + + inline OutRec* GetRealOutRec(OutRec* outrec) + { + while (outrec && !outrec->pts) outrec = outrec->owner; + return outrec; + } + + + inline void UncoupleOutRec(Active ae) + { + OutRec* outrec = ae.outrec; + if (!outrec) return; + outrec->front_edge->outrec = nullptr; + outrec->back_edge->outrec = nullptr; + outrec->front_edge = nullptr; + outrec->back_edge = nullptr; + } + + + inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2) + { + return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2); + } + + inline bool IsVerySmallTriangle(const OutPt& op) + { + return op.next->next == op.prev && + (PtsReallyClose(op.prev->pt, op.next->pt) || + PtsReallyClose(op.pt, op.next->pt) || + PtsReallyClose(op.pt, op.prev->pt)); + } + + inline bool IsValidClosedPath(const OutPt* op) + { + return op && (op->next != op) && (op->next != op->prev) && + !IsVerySmallTriangle(*op); + } + + inline bool OutrecIsAscending(const Active* hotEdge) + { + return (hotEdge == hotEdge->outrec->front_edge); + } + + inline void SwapFrontBackSides(OutRec& outrec) + { + Active* tmp = outrec.front_edge; + outrec.front_edge = outrec.back_edge; + outrec.back_edge = tmp; + outrec.pts = outrec.pts->next; + } + + inline bool EdgesAdjacentInAEL(const IntersectNode& inode) + { + return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2); + } + + inline bool IsJoined(const Active& e) + { + return e.join_with != JoinWith::None; + } + + inline void SetOwner(OutRec* outrec, OutRec* new_owner) + { + //precondition1: new_owner is never null + while (new_owner->owner && !new_owner->owner->pts) + new_owner->owner = new_owner->owner->owner; + OutRec* tmp = new_owner; + while (tmp && tmp != outrec) tmp = tmp->owner; + if (tmp) new_owner->owner = outrec->owner; + outrec->owner = new_owner; + } + + //------------------------------------------------------------------------------ + // ClipperBase methods ... + //------------------------------------------------------------------------------ + + ClipperBase::~ClipperBase() + { + Clear(); + } + + void ClipperBase::DeleteEdges(Active*& e) + { + while (e) + { + Active* e2 = e; + e = e->next_in_ael; + delete e2; + } + } + + void ClipperBase::CleanUp() + { + DeleteEdges(actives_); + scanline_list_ = std::priority_queue(); + intersect_nodes_.clear(); + DisposeAllOutRecs(); + horz_seg_list_.clear(); + horz_join_list_.clear(); + } + + + void ClipperBase::Clear() + { + CleanUp(); + DisposeVerticesAndLocalMinima(); + current_locmin_iter_ = minima_list_.begin(); + minima_list_sorted_ = false; + has_open_paths_ = false; + } + + + void ClipperBase::Reset() + { + if (!minima_list_sorted_) + { + std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); + minima_list_sorted_ = true; + } + LocalMinimaList::const_reverse_iterator i; + for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) + InsertScanline((*i)->vertex->pt.y); + + current_locmin_iter_ = minima_list_.begin(); + actives_ = nullptr; + sel_ = nullptr; + succeeded_ = true; + } #ifdef USINGZ - void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) - { - if (!zCallback_) return; - // prioritize subject over clip vertices by passing - // subject vertices before clip vertices in the callback - if (GetPolyType(e1) == PathType::Subject) - { - if (ip == e1.bot) ip.z = e1.bot.z; - else if (ip == e1.top) ip.z = e1.top.z; - else if (ip == e2.bot) ip.z = e2.bot.z; - else if (ip == e2.top) ip.z = e2.top.z; - zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); - } - else - { - if (ip == e2.bot) ip.z = e2.bot.z; - else if (ip == e2.top) ip.z = e2.top.z; - else if (ip == e1.bot) ip.z = e1.bot.z; - else if (ip == e1.top) ip.z = e1.top.z; - zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); - } - } + void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) + { + if (!zCallback_) return; + // prioritize subject over clip vertices by passing + // subject vertices before clip vertices in the callback + if (GetPolyType(e1) == PathType::Subject) + { + if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else ip.z = DefaultZ; + zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); + } + else + { + if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else ip.z = DefaultZ; + zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); + } + } #endif - void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) - { - Paths64 tmp; - tmp.push_back(path); - AddPaths(tmp, polytype, is_open); - } - - - void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) - { - if (is_open) has_open_paths_ = true; - minima_list_sorted_ = false; - - Path64::size_type total_vertex_count = 0; - for (const Path64& path : paths) total_vertex_count += path.size(); - if (total_vertex_count == 0) return; - Vertex* vertices = new Vertex[total_vertex_count], *v = vertices; - for (const Path64& path : paths) - { - //for each path create a circular double linked list of vertices - Vertex *v0 = v, *curr_v = v, *prev_v = nullptr; - - v->prev = nullptr; - int cnt = 0; - for (const Point64& pt : path) - { - if (prev_v) - { - if (prev_v->pt == pt) continue; // ie skips duplicates - prev_v->next = curr_v; - } - curr_v->prev = prev_v; - curr_v->pt = pt; - curr_v->flags = VertexFlags::None; - prev_v = curr_v++; - cnt++; - } - if (!prev_v || !prev_v->prev) continue; - if (!is_open && prev_v->pt == v0->pt) - prev_v = prev_v->prev; - prev_v->next = v0; - v0->prev = prev_v; - v = curr_v; // ie get ready for next path - if (cnt < 2 || (cnt == 2 && !is_open)) continue; - - //now find and assign local minima - bool going_up, going_up0; - if (is_open) - { - curr_v = v0->next; - while (curr_v != v0 && curr_v->pt.y == v0->pt.y) - curr_v = curr_v->next; - going_up = curr_v->pt.y <= v0->pt.y; - if (going_up) - { - v0->flags = VertexFlags::OpenStart; - AddLocMin(*v0, polytype, true); - } - else - v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax; - } - else // closed path - { - prev_v = v0->prev; - while (prev_v != v0 && prev_v->pt.y == v0->pt.y) - prev_v = prev_v->prev; - if (prev_v == v0) - continue; // only open paths can be completely flat - going_up = prev_v->pt.y > v0->pt.y; - } - - going_up0 = going_up; - prev_v = v0; - curr_v = v0->next; - while (curr_v != v0) - { - if (curr_v->pt.y > prev_v->pt.y && going_up) - { - prev_v->flags = (prev_v->flags | VertexFlags::LocalMax); - going_up = false; - } - else if (curr_v->pt.y < prev_v->pt.y && !going_up) - { - going_up = true; - AddLocMin(*prev_v, polytype, is_open); - } - prev_v = curr_v; - curr_v = curr_v->next; - } - - if (is_open) - { - prev_v->flags = prev_v->flags | VertexFlags::OpenEnd; - if (going_up) - prev_v->flags = prev_v->flags | VertexFlags::LocalMax; - else - AddLocMin(*prev_v, polytype, is_open); - } - else if (going_up != going_up0) - { - if (going_up0) AddLocMin(*prev_v, polytype, false); - else prev_v->flags = prev_v->flags | VertexFlags::LocalMax; - } - } // end processing current path - - vertex_lists_.emplace_back(vertices); - } // end AddPaths - - - inline void ClipperBase::InsertScanline(int64_t y) - { - scanline_list_.push(y); - } - - - bool ClipperBase::PopScanline(int64_t& y) - { - if (scanline_list_.empty()) return false; - y = scanline_list_.top(); - scanline_list_.pop(); - while (!scanline_list_.empty() && y == scanline_list_.top()) - scanline_list_.pop(); // Pop duplicates. - return true; - } - - - bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima) - { - if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false; - local_minima = (*current_locmin_iter_++); - return true; - } - - - void ClipperBase::DisposeAllOutRecs() - { - for (auto outrec : outrec_list_) - { - if (outrec->pts) DisposeOutPts(*outrec); - delete outrec; - } - outrec_list_.resize(0); - } - - - void ClipperBase::DisposeVerticesAndLocalMinima() - { - for (auto lm : minima_list_) delete lm; - minima_list_.clear(); - for (auto v : vertex_lists_) delete[] v; - vertex_lists_.clear(); - } - - - void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open) - { - //make sure the vertex is added only once ... - if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; - - vert.flags = (vert.flags | VertexFlags::LocalMin); - minima_list_.push_back(new LocalMinima(&vert, polytype, is_open)); - } - - bool ClipperBase::IsContributingClosed(const Active & e) const - { - switch (fillrule_) - { - case FillRule::EvenOdd: - break; - case FillRule::NonZero: - if (abs(e.wind_cnt) != 1) return false; - break; - case FillRule::Positive: - if (e.wind_cnt != 1) return false; - break; - case FillRule::Negative: - if (e.wind_cnt != -1) return false; - break; - } - - switch (cliptype_) - { - case ClipType::None: - return false; - case ClipType::Intersection: - switch (fillrule_) - { - case FillRule::Positive: - return (e.wind_cnt2 > 0); - case FillRule::Negative: - return (e.wind_cnt2 < 0); - default: - return (e.wind_cnt2 != 0); - } - break; - - case ClipType::Union: - switch (fillrule_) - { - case FillRule::Positive: - return (e.wind_cnt2 <= 0); - case FillRule::Negative: - return (e.wind_cnt2 >= 0); - default: - return (e.wind_cnt2 == 0); - } - break; - - case ClipType::Difference: - bool result; - switch (fillrule_) - { - case FillRule::Positive: - result = (e.wind_cnt2 <= 0); - break; - case FillRule::Negative: - result = (e.wind_cnt2 >= 0); - break; - default: - result = (e.wind_cnt2 == 0); - } - if (GetPolyType(e) == PathType::Subject) - return result; - else - return !result; - break; - - case ClipType::Xor: return true; break; - } - return false; // we should never get here - } - - - inline bool ClipperBase::IsContributingOpen(const Active& e) const - { - bool is_in_clip, is_in_subj; - switch (fillrule_) - { - case FillRule::Positive: - is_in_clip = e.wind_cnt2 > 0; - is_in_subj = e.wind_cnt > 0; - break; - case FillRule::Negative: - is_in_clip = e.wind_cnt2 < 0; - is_in_subj = e.wind_cnt < 0; - break; - default: - is_in_clip = e.wind_cnt2 != 0; - is_in_subj = e.wind_cnt != 0; - } - - switch (cliptype_) - { - case ClipType::Intersection: return is_in_clip; - case ClipType::Union: return (!is_in_subj && !is_in_clip); - default: return !is_in_clip; - } - } - - - void ClipperBase::SetWindCountForClosedPathEdge(Active& e) - { - //Wind counts refer to polygon regions not edges, so here an edge's WindCnt - //indicates the higher of the wind counts for the two regions touching the - //edge. (NB Adjacent regions can only ever have their wind counts differ by - //one. Also, open paths have no meaningful wind directions or counts.) - - Active* e2 = e.prev_in_ael; - //find the nearest closed path edge of the same PolyType in AEL (heading left) - PathType pt = GetPolyType(e); - while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael; - - if (!e2) - { - e.wind_cnt = e.wind_dx; - e2 = actives_; - } - else if (fillrule_ == FillRule::EvenOdd) - { - e.wind_cnt = e.wind_dx; - e.wind_cnt2 = e2->wind_cnt2; - e2 = e2->next_in_ael; - } - else - { - //NonZero, positive, or negative filling here ... - //if e's WindCnt is in the SAME direction as its WindDx, then polygon - //filling will be on the right of 'e'. - //NB neither e2.WindCnt nor e2.WindDx should ever be 0. - if (e2->wind_cnt * e2->wind_dx < 0) - { - //opposite directions so 'e' is outside 'e2' ... - if (abs(e2->wind_cnt) > 1) - { - //outside prev poly but still inside another. - if (e2->wind_dx * e.wind_dx < 0) - //reversing direction so use the same WC - e.wind_cnt = e2->wind_cnt; - else - //otherwise keep 'reducing' the WC by 1 (ie towards 0) ... - e.wind_cnt = e2->wind_cnt + e.wind_dx; - } - else - //now outside all polys of same polytype so set own WC ... - e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx); - } - else - { - //'e' must be inside 'e2' - if (e2->wind_dx * e.wind_dx < 0) - //reversing direction so use the same WC - e.wind_cnt = e2->wind_cnt; - else - //otherwise keep 'increasing' the WC by 1 (ie away from 0) ... - e.wind_cnt = e2->wind_cnt + e.wind_dx; - } - e.wind_cnt2 = e2->wind_cnt2; - e2 = e2->next_in_ael; // ie get ready to calc WindCnt2 - } - - //update wind_cnt2 ... - if (fillrule_ == FillRule::EvenOdd) - while (e2 != &e) - { - if (GetPolyType(*e2) != pt && !IsOpen(*e2)) - e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0); - e2 = e2->next_in_ael; - } - else - while (e2 != &e) - { - if (GetPolyType(*e2) != pt && !IsOpen(*e2)) - e.wind_cnt2 += e2->wind_dx; - e2 = e2->next_in_ael; - } - } - - - void ClipperBase::SetWindCountForOpenPathEdge(Active& e) - { - Active* e2 = actives_; - if (fillrule_ == FillRule::EvenOdd) - { - int cnt1 = 0, cnt2 = 0; - while (e2 != &e) - { - if (GetPolyType(*e2) == PathType::Clip) - cnt2++; - else if (!IsOpen(*e2)) - cnt1++; - e2 = e2->next_in_ael; - } - e.wind_cnt = (IsOdd(cnt1) ? 1 : 0); - e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0); - } - else - { - while (e2 != &e) - { - if (GetPolyType(*e2) == PathType::Clip) - e.wind_cnt2 += e2->wind_dx; - else if (!IsOpen(*e2)) - e.wind_cnt += e2->wind_dx; - e2 = e2->next_in_ael; - } - } - } - - - bool IsValidAelOrder(const Active& resident, const Active& newcomer) - { - if (newcomer.curr_x != resident.curr_x) - return newcomer.curr_x > resident.curr_x; - - //get the turning direction a1.top, a2.bot, a2.top - double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); - if (d != 0) return d < 0; - - //edges must be collinear to get here - //for starting open paths, place them according to - //the direction they're about to turn - if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) - { - return CrossProduct(newcomer.bot, - resident.top, NextVertex(resident)->pt) <= 0; - } - else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) - { - return CrossProduct(newcomer.bot, - newcomer.top, NextVertex(newcomer)->pt) >= 0; - } - - int64_t y = newcomer.bot.y; - bool newcomerIsLeft = newcomer.is_left_bound; - - if (resident.bot.y != y || resident.local_min->vertex->pt.y != y) - return newcomer.is_left_bound; - //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 - //compare turning direction of the alternate bound - return (CrossProduct(PrevPrevVertex(resident)->pt, - newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; - } - - - void ClipperBase::InsertLeftEdge(Active& e) - { - Active* e2; - if (!actives_) - { - e.prev_in_ael = nullptr; - e.next_in_ael = nullptr; - actives_ = &e; - } - else if (!IsValidAelOrder(*actives_, e)) - { - e.prev_in_ael = nullptr; - e.next_in_ael = actives_; - actives_->prev_in_ael = &e; - actives_ = &e; - } - else - { - e2 = actives_; - while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e)) - e2 = e2->next_in_ael; - e.next_in_ael = e2->next_in_ael; - if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e; - e.prev_in_ael = e2; - e2->next_in_ael = &e; - } - } - - - void InsertRightEdge(Active& e, Active& e2) - { - e2.next_in_ael = e.next_in_ael; - if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2; - e2.prev_in_ael = &e; - e.next_in_ael = &e2; - } - - - void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y) - { - LocalMinima* local_minima; - Active* left_bound, * right_bound; - //Add any local minima (if any) at BotY ... - //nb: horizontal local minima edges should contain locMin.vertex.prev - - while (PopLocalMinima(bot_y, local_minima)) - { - if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::None) - { - left_bound = nullptr; - } - else - { - left_bound = new Active(); - left_bound->bot = local_minima->vertex->pt; - left_bound->curr_x = left_bound->bot.x; - left_bound->wind_cnt = 0, - left_bound->wind_cnt2 = 0, - left_bound->wind_dx = -1, - left_bound->vertex_top = local_minima->vertex->prev; // ie descending - left_bound->top = left_bound->vertex_top->pt; - left_bound->outrec = nullptr; - left_bound->local_min = local_minima; - SetDx(*left_bound); - } - - if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::None) - { - right_bound = nullptr; - } - else - { - right_bound = new Active(); - right_bound->bot = local_minima->vertex->pt; - right_bound->curr_x = right_bound->bot.x; - right_bound->wind_cnt = 0, - right_bound->wind_cnt2 = 0, - right_bound->wind_dx = 1, - right_bound->vertex_top = local_minima->vertex->next; // ie ascending - right_bound->top = right_bound->vertex_top->pt; - right_bound->outrec = nullptr; - right_bound->local_min = local_minima; - SetDx(*right_bound); - } - - //Currently LeftB is just the descending bound and RightB is the ascending. - //Now if the LeftB isn't on the left of RightB then we need swap them. - if (left_bound && right_bound) - { - if (IsHorizontal(*left_bound)) - { - if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound); - } - else if (IsHorizontal(*right_bound)) - { - if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound); - } - else if (left_bound->dx < right_bound->dx) - SwapActives(left_bound, right_bound); - } - else if (!left_bound) - { - left_bound = right_bound; - right_bound = nullptr; - } - - bool contributing; - left_bound->is_left_bound = true; - InsertLeftEdge(*left_bound); - - if (IsOpen(*left_bound)) - { - SetWindCountForOpenPathEdge(*left_bound); - contributing = IsContributingOpen(*left_bound); - } - else - { - SetWindCountForClosedPathEdge(*left_bound); - contributing = IsContributingClosed(*left_bound); - } - - if (right_bound) - { - right_bound->is_left_bound = false; - right_bound->wind_cnt = left_bound->wind_cnt; - right_bound->wind_cnt2 = left_bound->wind_cnt2; - InsertRightEdge(*left_bound, *right_bound); /////// - if (contributing) - { - AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true); - if (!IsHorizontal(*left_bound) && TestJoinWithPrev1(*left_bound)) - { - OutPt* op = AddOutPt(*left_bound->prev_in_ael, left_bound->bot); - AddJoin(op, left_bound->outrec->pts); - } - } - - while (right_bound->next_in_ael && - IsValidAelOrder(*right_bound->next_in_ael, *right_bound)) - { - IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot); - SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael); - } - - if (!IsHorizontal(*right_bound) && - TestJoinWithNext1(*right_bound)) - { - OutPt* op = AddOutPt(*right_bound->next_in_ael, right_bound->bot); - AddJoin(right_bound->outrec->pts, op); - } - - if (IsHorizontal(*right_bound)) - PushHorz(*right_bound); - else - InsertScanline(right_bound->top.y); - } - else if (contributing) - { - StartOpenPath(*left_bound, left_bound->bot); - } - - if (IsHorizontal(*left_bound)) - PushHorz(*left_bound); - else - InsertScanline(left_bound->top.y); - } // while (PopLocalMinima()) - } - - - inline void ClipperBase::PushHorz(Active& e) - { - e.next_in_sel = (sel_ ? sel_ : nullptr); - sel_ = &e; - } - - - inline bool ClipperBase::PopHorz(Active*& e) - { - e = sel_; - if (!e) return false; - sel_ = sel_->next_in_sel; - return true; - } - - - OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2, - const Point64& pt, bool is_new) - { - OutRec* outrec = new OutRec(); - outrec->idx = (unsigned)outrec_list_.size(); - outrec_list_.push_back(outrec); - outrec->pts = nullptr; - outrec->polypath = nullptr; - e1.outrec = outrec; - e2.outrec = outrec; - - //Setting the owner and inner/outer states (above) is an essential - //precursor to setting edge 'sides' (ie left and right sides of output - //polygons) and hence the orientation of output paths ... - - if (IsOpen(e1)) - { - outrec->owner = nullptr; - outrec->is_open = true; - if (e1.wind_dx > 0) - SetSides(*outrec, e1, e2); - else - SetSides(*outrec, e2, e1); - } - else - { - Active* prevHotEdge = GetPrevHotEdge(e1); - //e.windDx is the winding direction of the **input** paths - //and unrelated to the winding direction of output polygons. - //Output orientation is determined by e.outrec.frontE which is - //the ascending edge (see AddLocalMinPoly). - if (prevHotEdge) - { - outrec->owner = prevHotEdge->outrec; - if (OutrecIsAscending(prevHotEdge) == is_new) - SetSides(*outrec, e2, e1); - else - SetSides(*outrec, e1, e2); - } - else - { - outrec->owner = nullptr; - if (is_new) - SetSides(*outrec, e1, e2); - else - SetSides(*outrec, e2, e1); - } - } - - OutPt* op = new OutPt(pt, outrec); - outrec->pts = op; - return op; - } - - - OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt) - { - if (IsFront(e1) == IsFront(e2)) - { - if (IsOpenEnd(e1)) - SwapFrontBackSides(*e1.outrec); - else if (IsOpenEnd(e2)) - SwapFrontBackSides(*e2.outrec); - else - { - succeeded_ = false; - return nullptr; - } - } - - OutPt* result = AddOutPt(e1, pt); - if (e1.outrec == e2.outrec) - { - OutRec& outrec = *e1.outrec; - outrec.pts = result; - - UncoupleOutRec(e1); - if (!IsOpen(e1)) CleanCollinear(&outrec); - result = outrec.pts; - if (using_polytree_ && outrec.owner && !outrec.owner->front_edge) - outrec.owner = GetRealOutRec(outrec.owner->owner); - } - //and to preserve the winding orientation of outrec ... - else if (IsOpen(e1)) - { - if (e1.wind_dx < 0) - JoinOutrecPaths(e1, e2); - else - JoinOutrecPaths(e2, e1); - } - else if (e1.outrec->idx < e2.outrec->idx) - JoinOutrecPaths(e1, e2); - else - JoinOutrecPaths(e2, e1); - - return result; - } - - void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2) - { - //join e2 outrec path onto e1 outrec path and then delete e2 outrec path - //pointers. (NB Only very rarely do the joining ends share the same coords.) - OutPt* p1_st = e1.outrec->pts; - OutPt* p2_st = e2.outrec->pts; - OutPt* p1_end = p1_st->next; - OutPt* p2_end = p2_st->next; - if (IsFront(e1)) - { - p2_end->prev = p1_st; - p1_st->next = p2_end; - p2_st->next = p1_end; - p1_end->prev = p2_st; - e1.outrec->pts = p2_st; - e1.outrec->front_edge = e2.outrec->front_edge; - if (e1.outrec->front_edge) - e1.outrec->front_edge->outrec = e1.outrec; - } - else - { - p1_end->prev = p2_st; - p2_st->next = p1_end; - p1_st->next = p2_end; - p2_end->prev = p1_st; - e1.outrec->back_edge = e2.outrec->back_edge; - if (e1.outrec->back_edge) - e1.outrec->back_edge->outrec = e1.outrec; - } - - //an owner must have a lower idx otherwise - //it can't be a valid owner - if (e2.outrec->owner && e2.outrec->owner->idx < e1.outrec->idx) - { - if (!e1.outrec->owner || e2.outrec->owner->idx < e1.outrec->owner->idx) - e1.outrec->owner = e2.outrec->owner; - } - - //after joining, the e2.OutRec must contains no vertices ... - e2.outrec->front_edge = nullptr; - e2.outrec->back_edge = nullptr; - e2.outrec->pts = nullptr; - e2.outrec->owner = e1.outrec; - - if (IsOpenEnd(e1)) - { - e2.outrec->pts = e1.outrec->pts; - e1.outrec->pts = nullptr; - } - - //and e1 and e2 are maxima and are about to be dropped from the Actives list. - e1.outrec = nullptr; - e2.outrec = nullptr; - } - - - OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt) - { - OutPt* new_op = nullptr; - - //Outrec.OutPts: a circular doubly-linked-list of POutPt where ... - //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next - OutRec* outrec = e.outrec; - bool to_front = IsFront(e); - OutPt* op_front = outrec->pts; - OutPt* op_back = op_front->next; - - if (to_front && (pt == op_front->pt)) - new_op = op_front; - else if (!to_front && (pt == op_back->pt)) - new_op = op_back; - else - { - new_op = new OutPt(pt, outrec); - op_back->prev = new_op; - new_op->prev = op_front; - new_op->next = op_back; - op_front->next = new_op; - if (to_front) outrec->pts = new_op; - } - return new_op; - } - - - bool ClipperBase::ValidateClosedPathEx(OutPt*& outpt) - { - if (IsValidClosedPath(outpt)) return true; - if (outpt) SafeDisposeOutPts(outpt); - return false; - } - - - void ClipperBase::CleanCollinear(OutRec* outrec) - { - outrec = GetRealOutRec(outrec); - if (!outrec || outrec->is_open || - outrec->front_edge || !ValidateClosedPathEx(outrec->pts)) return; - - OutPt* startOp = outrec->pts, * op2 = startOp; - for (; ; ) - { - if (op2->joiner) return; - - //NB if preserveCollinear == true, then only remove 180 deg. spikes - if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) && - (op2->pt == op2->prev->pt || - op2->pt == op2->next->pt || !PreserveCollinear || - DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) - { - - if (op2 == outrec->pts) outrec->pts = op2->prev; - - op2 = DisposeOutPt(op2); - if (!ValidateClosedPathEx(op2)) - { - outrec->pts = nullptr; - return; - } - startOp = op2; - continue; - } - op2 = op2->next; - if (op2 == startOp) break; - } - FixSelfIntersects(outrec); - } - - OutPt* ClipperBase::DoSplitOp(OutPt* outRecOp, OutPt* splitOp) - { - OutPt* prevOp = splitOp->prev; - OutPt* nextNextOp = splitOp->next->next; - OutPt* result = prevOp; - PointD ipD; - GetIntersectPoint(prevOp->pt, - splitOp->pt, splitOp->next->pt, nextNextOp->pt, ipD); - Point64 ip = Point64(ipD); + void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) + { + Paths64 tmp; + tmp.push_back(path); + AddPaths(tmp, polytype, is_open); + } + + + void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) + { + if (is_open) has_open_paths_ = true; + minima_list_sorted_ = false; + + const auto total_vertex_count = + std::accumulate(paths.begin(), paths.end(), 0, + [](const auto& a, const Path64& path) + {return a + static_cast(path.size());}); + if (total_vertex_count == 0) return; + + Vertex* vertices = new Vertex[total_vertex_count], * v = vertices; + for (const Path64& path : paths) + { + //for each path create a circular double linked list of vertices + Vertex* v0 = v, * curr_v = v, * prev_v = nullptr; + + v->prev = nullptr; + int cnt = 0; + for (const Point64& pt : path) + { + if (prev_v) + { + if (prev_v->pt == pt) continue; // ie skips duplicates + prev_v->next = curr_v; + } + curr_v->prev = prev_v; + curr_v->pt = pt; + curr_v->flags = VertexFlags::None; + prev_v = curr_v++; + cnt++; + } + if (!prev_v || !prev_v->prev) continue; + if (!is_open && prev_v->pt == v0->pt) + prev_v = prev_v->prev; + prev_v->next = v0; + v0->prev = prev_v; + v = curr_v; // ie get ready for next path + if (cnt < 2 || (cnt == 2 && !is_open)) continue; + + //now find and assign local minima + bool going_up, going_up0; + if (is_open) + { + curr_v = v0->next; + while (curr_v != v0 && curr_v->pt.y == v0->pt.y) + curr_v = curr_v->next; + going_up = curr_v->pt.y <= v0->pt.y; + if (going_up) + { + v0->flags = VertexFlags::OpenStart; + AddLocMin(*v0, polytype, true); + } + else + v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax; + } + else // closed path + { + prev_v = v0->prev; + while (prev_v != v0 && prev_v->pt.y == v0->pt.y) + prev_v = prev_v->prev; + if (prev_v == v0) + continue; // only open paths can be completely flat + going_up = prev_v->pt.y > v0->pt.y; + } + + going_up0 = going_up; + prev_v = v0; + curr_v = v0->next; + while (curr_v != v0) + { + if (curr_v->pt.y > prev_v->pt.y && going_up) + { + prev_v->flags = (prev_v->flags | VertexFlags::LocalMax); + going_up = false; + } + else if (curr_v->pt.y < prev_v->pt.y && !going_up) + { + going_up = true; + AddLocMin(*prev_v, polytype, is_open); + } + prev_v = curr_v; + curr_v = curr_v->next; + } + + if (is_open) + { + prev_v->flags = prev_v->flags | VertexFlags::OpenEnd; + if (going_up) + prev_v->flags = prev_v->flags | VertexFlags::LocalMax; + else + AddLocMin(*prev_v, polytype, is_open); + } + else if (going_up != going_up0) + { + if (going_up0) AddLocMin(*prev_v, polytype, false); + else prev_v->flags = prev_v->flags | VertexFlags::LocalMax; + } + } // end processing current path + + vertex_lists_.emplace_back(vertices); + } // end AddPaths + + + void ClipperBase::InsertScanline(int64_t y) + { + scanline_list_.push(y); + } + + + bool ClipperBase::PopScanline(int64_t& y) + { + if (scanline_list_.empty()) return false; + y = scanline_list_.top(); + scanline_list_.pop(); + while (!scanline_list_.empty() && y == scanline_list_.top()) + scanline_list_.pop(); // Pop duplicates. + return true; + } + + + bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima) + { + if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false; + local_minima = (current_locmin_iter_++)->get(); + return true; + } + + void ClipperBase::DisposeAllOutRecs() + { + for (auto outrec : outrec_list_) + { + if (outrec->pts) DisposeOutPts(outrec); + delete outrec; + } + outrec_list_.resize(0); + } + + void ClipperBase::DisposeVerticesAndLocalMinima() + { + minima_list_.clear(); + for (auto v : vertex_lists_) delete[] v; + vertex_lists_.clear(); + } + + + void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + minima_list_.push_back(std::make_unique (&vert, polytype, is_open)); + } + + bool ClipperBase::IsContributingClosed(const Active& e) const + { + switch (fillrule_) + { + case FillRule::EvenOdd: + break; + case FillRule::NonZero: + if (abs(e.wind_cnt) != 1) return false; + break; + case FillRule::Positive: + if (e.wind_cnt != 1) return false; + break; + case FillRule::Negative: + if (e.wind_cnt != -1) return false; + break; + } + + switch (cliptype_) + { + case ClipType::None: + return false; + case ClipType::Intersection: + switch (fillrule_) + { + case FillRule::Positive: + return (e.wind_cnt2 > 0); + case FillRule::Negative: + return (e.wind_cnt2 < 0); + default: + return (e.wind_cnt2 != 0); + } + break; + + case ClipType::Union: + switch (fillrule_) + { + case FillRule::Positive: + return (e.wind_cnt2 <= 0); + case FillRule::Negative: + return (e.wind_cnt2 >= 0); + default: + return (e.wind_cnt2 == 0); + } + break; + + case ClipType::Difference: + bool result; + switch (fillrule_) + { + case FillRule::Positive: + result = (e.wind_cnt2 <= 0); + break; + case FillRule::Negative: + result = (e.wind_cnt2 >= 0); + break; + default: + result = (e.wind_cnt2 == 0); + } + if (GetPolyType(e) == PathType::Subject) + return result; + else + return !result; + break; + + case ClipType::Xor: return true; break; + } + return false; // we should never get here + } + + + inline bool ClipperBase::IsContributingOpen(const Active& e) const + { + bool is_in_clip, is_in_subj; + switch (fillrule_) + { + case FillRule::Positive: + is_in_clip = e.wind_cnt2 > 0; + is_in_subj = e.wind_cnt > 0; + break; + case FillRule::Negative: + is_in_clip = e.wind_cnt2 < 0; + is_in_subj = e.wind_cnt < 0; + break; + default: + is_in_clip = e.wind_cnt2 != 0; + is_in_subj = e.wind_cnt != 0; + } + + switch (cliptype_) + { + case ClipType::Intersection: return is_in_clip; + case ClipType::Union: return (!is_in_subj && !is_in_clip); + default: return !is_in_clip; + } + } + + + void ClipperBase::SetWindCountForClosedPathEdge(Active& e) + { + //Wind counts refer to polygon regions not edges, so here an edge's WindCnt + //indicates the higher of the wind counts for the two regions touching the + //edge. (NB Adjacent regions can only ever have their wind counts differ by + //one. Also, open paths have no meaningful wind directions or counts.) + + Active* e2 = e.prev_in_ael; + //find the nearest closed path edge of the same PolyType in AEL (heading left) + PathType pt = GetPolyType(e); + while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael; + + if (!e2) + { + e.wind_cnt = e.wind_dx; + e2 = actives_; + } + else if (fillrule_ == FillRule::EvenOdd) + { + e.wind_cnt = e.wind_dx; + e.wind_cnt2 = e2->wind_cnt2; + e2 = e2->next_in_ael; + } + else + { + //NonZero, positive, or negative filling here ... + //if e's WindCnt is in the SAME direction as its WindDx, then polygon + //filling will be on the right of 'e'. + //NB neither e2.WindCnt nor e2.WindDx should ever be 0. + if (e2->wind_cnt * e2->wind_dx < 0) + { + //opposite directions so 'e' is outside 'e2' ... + if (abs(e2->wind_cnt) > 1) + { + //outside prev poly but still inside another. + if (e2->wind_dx * e.wind_dx < 0) + //reversing direction so use the same WC + e.wind_cnt = e2->wind_cnt; + else + //otherwise keep 'reducing' the WC by 1 (ie towards 0) ... + e.wind_cnt = e2->wind_cnt + e.wind_dx; + } + else + //now outside all polys of same polytype so set own WC ... + e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx); + } + else + { + //'e' must be inside 'e2' + if (e2->wind_dx * e.wind_dx < 0) + //reversing direction so use the same WC + e.wind_cnt = e2->wind_cnt; + else + //otherwise keep 'increasing' the WC by 1 (ie away from 0) ... + e.wind_cnt = e2->wind_cnt + e.wind_dx; + } + e.wind_cnt2 = e2->wind_cnt2; + e2 = e2->next_in_ael; // ie get ready to calc WindCnt2 + } + + //update wind_cnt2 ... + if (fillrule_ == FillRule::EvenOdd) + while (e2 != &e) + { + if (GetPolyType(*e2) != pt && !IsOpen(*e2)) + e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0); + e2 = e2->next_in_ael; + } + else + while (e2 != &e) + { + if (GetPolyType(*e2) != pt && !IsOpen(*e2)) + e.wind_cnt2 += e2->wind_dx; + e2 = e2->next_in_ael; + } + } + + + void ClipperBase::SetWindCountForOpenPathEdge(Active& e) + { + Active* e2 = actives_; + if (fillrule_ == FillRule::EvenOdd) + { + int cnt1 = 0, cnt2 = 0; + while (e2 != &e) + { + if (GetPolyType(*e2) == PathType::Clip) + cnt2++; + else if (!IsOpen(*e2)) + cnt1++; + e2 = e2->next_in_ael; + } + e.wind_cnt = (IsOdd(cnt1) ? 1 : 0); + e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0); + } + else + { + while (e2 != &e) + { + if (GetPolyType(*e2) == PathType::Clip) + e.wind_cnt2 += e2->wind_dx; + else if (!IsOpen(*e2)) + e.wind_cnt += e2->wind_dx; + e2 = e2->next_in_ael; + } + } + } + + + bool IsValidAelOrder(const Active& resident, const Active& newcomer) + { + if (newcomer.curr_x != resident.curr_x) + return newcomer.curr_x > resident.curr_x; + + //get the turning direction a1.top, a2.bot, a2.top + double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); + if (d != 0) return d < 0; + + //edges must be collinear to get here + //for starting open paths, place them according to + //the direction they're about to turn + if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) + { + return CrossProduct(newcomer.bot, + resident.top, NextVertex(resident)->pt) <= 0; + } + else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) + { + return CrossProduct(newcomer.bot, + newcomer.top, NextVertex(newcomer)->pt) >= 0; + } + + int64_t y = newcomer.bot.y; + bool newcomerIsLeft = newcomer.is_left_bound; + + if (resident.bot.y != y || resident.local_min->vertex->pt.y != y) + return newcomer.is_left_bound; + //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 + //compare turning direction of the alternate bound + return (CrossProduct(PrevPrevVertex(resident)->pt, + newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; + } + + + void ClipperBase::InsertLeftEdge(Active& e) + { + Active* e2; + if (!actives_) + { + e.prev_in_ael = nullptr; + e.next_in_ael = nullptr; + actives_ = &e; + } + else if (!IsValidAelOrder(*actives_, e)) + { + e.prev_in_ael = nullptr; + e.next_in_ael = actives_; + actives_->prev_in_ael = &e; + actives_ = &e; + } + else + { + e2 = actives_; + while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e)) + e2 = e2->next_in_ael; + if (e2->join_with == JoinWith::Right) + e2 = e2->next_in_ael; + if (!e2) return; // should never happen and stops compiler warning :) + e.next_in_ael = e2->next_in_ael; + if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e; + e.prev_in_ael = e2; + e2->next_in_ael = &e; + } + } + + + void InsertRightEdge(Active& e, Active& e2) + { + e2.next_in_ael = e.next_in_ael; + if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2; + e2.prev_in_ael = &e; + e.next_in_ael = &e2; + } + + + void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y) + { + LocalMinima* local_minima; + Active* left_bound, * right_bound; + //Add any local minima (if any) at BotY ... + //nb: horizontal local minima edges should contain locMin.vertex.prev + + while (PopLocalMinima(bot_y, local_minima)) + { + if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::None) + { + left_bound = nullptr; + } + else + { + left_bound = new Active(); + left_bound->bot = local_minima->vertex->pt; + left_bound->curr_x = left_bound->bot.x; + left_bound->wind_dx = -1; + left_bound->vertex_top = local_minima->vertex->prev; // ie descending + left_bound->top = left_bound->vertex_top->pt; + left_bound->local_min = local_minima; + SetDx(*left_bound); + } + + if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::None) + { + right_bound = nullptr; + } + else + { + right_bound = new Active(); + right_bound->bot = local_minima->vertex->pt; + right_bound->curr_x = right_bound->bot.x; + right_bound->wind_dx = 1; + right_bound->vertex_top = local_minima->vertex->next; // ie ascending + right_bound->top = right_bound->vertex_top->pt; + right_bound->local_min = local_minima; + SetDx(*right_bound); + } + + //Currently LeftB is just the descending bound and RightB is the ascending. + //Now if the LeftB isn't on the left of RightB then we need swap them. + if (left_bound && right_bound) + { + if (IsHorizontal(*left_bound)) + { + if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound); + } + else if (IsHorizontal(*right_bound)) + { + if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound); + } + else if (left_bound->dx < right_bound->dx) + SwapActives(left_bound, right_bound); + } + else if (!left_bound) + { + left_bound = right_bound; + right_bound = nullptr; + } + + bool contributing; + left_bound->is_left_bound = true; + InsertLeftEdge(*left_bound); + + if (IsOpen(*left_bound)) + { + SetWindCountForOpenPathEdge(*left_bound); + contributing = IsContributingOpen(*left_bound); + } + else + { + SetWindCountForClosedPathEdge(*left_bound); + contributing = IsContributingClosed(*left_bound); + } + + if (right_bound) + { + right_bound->is_left_bound = false; + right_bound->wind_cnt = left_bound->wind_cnt; + right_bound->wind_cnt2 = left_bound->wind_cnt2; + InsertRightEdge(*left_bound, *right_bound); /////// + if (contributing) + { + AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true); + if (!IsHorizontal(*left_bound)) + CheckJoinLeft(*left_bound, left_bound->bot); + } + + while (right_bound->next_in_ael && + IsValidAelOrder(*right_bound->next_in_ael, *right_bound)) + { + IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot); + SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael); + } + + if (IsHorizontal(*right_bound)) + PushHorz(*right_bound); + else + { + CheckJoinRight(*right_bound, right_bound->bot); + InsertScanline(right_bound->top.y); + } + } + else if (contributing) + { + StartOpenPath(*left_bound, left_bound->bot); + } + + if (IsHorizontal(*left_bound)) + PushHorz(*left_bound); + else + InsertScanline(left_bound->top.y); + } // while (PopLocalMinima()) + } + + + inline void ClipperBase::PushHorz(Active& e) + { + e.next_in_sel = (sel_ ? sel_ : nullptr); + sel_ = &e; + } + + + inline bool ClipperBase::PopHorz(Active*& e) + { + e = sel_; + if (!e) return false; + sel_ = sel_->next_in_sel; + return true; + } + + + OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2, + const Point64& pt, bool is_new) + { + OutRec* outrec = NewOutRec(); + e1.outrec = outrec; + e2.outrec = outrec; + + if (IsOpen(e1)) + { + outrec->owner = nullptr; + outrec->is_open = true; + if (e1.wind_dx > 0) + SetSides(*outrec, e1, e2); + else + SetSides(*outrec, e2, e1); + } + else + { + Active* prevHotEdge = GetPrevHotEdge(e1); + //e.windDx is the winding direction of the **input** paths + //and unrelated to the winding direction of output polygons. + //Output orientation is determined by e.outrec.frontE which is + //the ascending edge (see AddLocalMinPoly). + if (prevHotEdge) + { + if (using_polytree_) + SetOwner(outrec, prevHotEdge->outrec); + if (OutrecIsAscending(prevHotEdge) == is_new) + SetSides(*outrec, e2, e1); + else + SetSides(*outrec, e1, e2); + } + else + { + outrec->owner = nullptr; + if (is_new) + SetSides(*outrec, e1, e2); + else + SetSides(*outrec, e2, e1); + } + } + + OutPt* op = new OutPt(pt, outrec); + outrec->pts = op; + return op; + } + + + OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt) + { + if (IsJoined(e1)) Split(e1, pt); + if (IsJoined(e2)) Split(e2, pt); + + if (IsFront(e1) == IsFront(e2)) + { + if (IsOpenEnd(e1)) + SwapFrontBackSides(*e1.outrec); + else if (IsOpenEnd(e2)) + SwapFrontBackSides(*e2.outrec); + else + { + succeeded_ = false; + return nullptr; + } + } + + OutPt* result = AddOutPt(e1, pt); + if (e1.outrec == e2.outrec) + { + OutRec& outrec = *e1.outrec; + outrec.pts = result; + + if (using_polytree_) + { + Active* e = GetPrevHotEdge(e1); + if (!e) + outrec.owner = nullptr; + else + SetOwner(&outrec, e->outrec); + // nb: outRec.owner here is likely NOT the real + // owner but this will be checked in DeepCheckOwner() + } + + UncoupleOutRec(e1); + result = outrec.pts; + if (outrec.owner && !outrec.owner->front_edge) + outrec.owner = GetRealOutRec(outrec.owner); + } + //and to preserve the winding orientation of outrec ... + else if (IsOpen(e1)) + { + if (e1.wind_dx < 0) + JoinOutrecPaths(e1, e2); + else + JoinOutrecPaths(e2, e1); + } + else if (e1.outrec->idx < e2.outrec->idx) + JoinOutrecPaths(e1, e2); + else + JoinOutrecPaths(e2, e1); + return result; + } + + void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2) + { + //join e2 outrec path onto e1 outrec path and then delete e2 outrec path + //pointers. (NB Only very rarely do the joining ends share the same coords.) + OutPt* p1_st = e1.outrec->pts; + OutPt* p2_st = e2.outrec->pts; + OutPt* p1_end = p1_st->next; + OutPt* p2_end = p2_st->next; + if (IsFront(e1)) + { + p2_end->prev = p1_st; + p1_st->next = p2_end; + p2_st->next = p1_end; + p1_end->prev = p2_st; + e1.outrec->pts = p2_st; + e1.outrec->front_edge = e2.outrec->front_edge; + if (e1.outrec->front_edge) + e1.outrec->front_edge->outrec = e1.outrec; + } + else + { + p1_end->prev = p2_st; + p2_st->next = p1_end; + p1_st->next = p2_end; + p2_end->prev = p1_st; + e1.outrec->back_edge = e2.outrec->back_edge; + if (e1.outrec->back_edge) + e1.outrec->back_edge->outrec = e1.outrec; + } + + //after joining, the e2.OutRec must contains no vertices ... + e2.outrec->front_edge = nullptr; + e2.outrec->back_edge = nullptr; + e2.outrec->pts = nullptr; + SetOwner(e2.outrec, e1.outrec); + + if (IsOpenEnd(e1)) + { + e2.outrec->pts = e1.outrec->pts; + e1.outrec->pts = nullptr; + } + + //and e1 and e2 are maxima and are about to be dropped from the Actives list. + e1.outrec = nullptr; + e2.outrec = nullptr; + } + + OutRec* ClipperBase::NewOutRec() + { + OutRec* result = new OutRec(); + result->idx = outrec_list_.size(); + outrec_list_.push_back(result); + result->pts = nullptr; + result->owner = nullptr; + result->polypath = nullptr; + result->is_open = false; + return result; + } + + + OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt) + { + OutPt* new_op = nullptr; + + //Outrec.OutPts: a circular doubly-linked-list of POutPt where ... + //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next + OutRec* outrec = e.outrec; + bool to_front = IsFront(e); + OutPt* op_front = outrec->pts; + OutPt* op_back = op_front->next; + + if (to_front) + { + if (pt == op_front->pt) + return op_front; + } + else if (pt == op_back->pt) + return op_back; + + new_op = new OutPt(pt, outrec); + op_back->prev = new_op; + new_op->prev = op_front; + new_op->next = op_back; + op_front->next = new_op; + if (to_front) outrec->pts = new_op; + return new_op; + } + + + void ClipperBase::CleanCollinear(OutRec* outrec) + { + outrec = GetRealOutRec(outrec); + if (!outrec || outrec->is_open) return; + if (!IsValidClosedPath(outrec->pts)) + { + DisposeOutPts(outrec); + return; + } + + OutPt* startOp = outrec->pts, * op2 = startOp; + for (; ; ) + { + //NB if preserveCollinear == true, then only remove 180 deg. spikes + if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) && + (op2->pt == op2->prev->pt || + op2->pt == op2->next->pt || !PreserveCollinear || + DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) + { + + if (op2 == outrec->pts) outrec->pts = op2->prev; + + op2 = DisposeOutPt(op2); + if (!IsValidClosedPath(op2)) + { + DisposeOutPts(outrec); + return; + } + startOp = op2; + continue; + } + op2 = op2->next; + if (op2 == startOp) break; + } + FixSelfIntersects(outrec); + } + + void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* 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, + splitOp->next->pt, nextNextOp->pt, ip); + #ifdef USINGZ - if (zCallback_) - zCallback_(prevOp->pt, splitOp->pt, splitOp->next->pt, nextNextOp->pt, ip); + if (zCallback_) zCallback_(prevOp->pt, splitOp->pt, + splitOp->next->pt, nextNextOp->pt, ip); #endif - double area1 = Area(outRecOp); - double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt); + double area1 = Area(outrec->pts); + double absArea1 = std::fabs(area1); + if (absArea1 < 2) + { + DisposeOutPts(outrec); + return; + } - if (ip == prevOp->pt || ip == nextNextOp->pt) - { - nextNextOp->prev = prevOp; - prevOp->next = nextNextOp; - } - else - { - OutPt* newOp2 = new OutPt(ip, prevOp->outrec); - newOp2->prev = prevOp; - newOp2->next = nextNextOp; - nextNextOp->prev = newOp2; - prevOp->next = newOp2; - } + // nb: area1 is the path's area *before* splitting, whereas area2 is + // the area of the triangle containing splitOp & splitOp.next. + // So the only way for these areas to have the same sign is if + // the split triangle is larger than the path containing prevOp or + // if there's more than one self=intersection. + double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt); + double absArea2 = std::fabs(area2); - SafeDeleteOutPtJoiners(splitOp->next); - SafeDeleteOutPtJoiners(splitOp); + // de-link splitOp and splitOp.next from the path + // while inserting the intersection point + if (ip == prevOp->pt || ip == nextNextOp->pt) + { + nextNextOp->prev = prevOp; + prevOp->next = nextNextOp; + } + else + { + OutPt* newOp2 = new OutPt(ip, prevOp->outrec); + newOp2->prev = prevOp; + newOp2->next = nextNextOp; + nextNextOp->prev = newOp2; + prevOp->next = newOp2; + } - double absArea2 = std::abs(area2); - if ((absArea2 >= 1) && - ((absArea2 > std::abs(area1) || ((area2 > 0) == (area1 > 0))))) - { - OutRec* newOutRec = new OutRec(); - newOutRec->idx = outrec_list_.size(); - outrec_list_.push_back(newOutRec); - newOutRec->owner = prevOp->outrec->owner; - newOutRec->polypath = nullptr; - splitOp->outrec = newOutRec; - splitOp->next->outrec = newOutRec; + if (absArea2 >= 1 && + (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) + { + OutRec* newOr = NewOutRec(); + newOr->owner = outrec->owner; + + if (using_polytree_) + { + if (!outrec->splits) outrec->splits = new OutRecList(); + outrec->splits->push_back(newOr); + } - OutPt* newOp = new OutPt(ip, newOutRec); - newOp->prev = splitOp->next; - newOp->next = splitOp; - newOutRec->pts = newOp; - splitOp->prev = newOp; - splitOp->next->next = newOp; - } - else - { - delete splitOp->next; - delete splitOp; - } - return result; - } + splitOp->outrec = newOr; + splitOp->next->outrec = newOr; + OutPt* newOp = new OutPt(ip, newOr); + newOp->prev = splitOp->next; + newOp->next = splitOp; + newOr->pts = newOp; + splitOp->prev = newOp; + splitOp->next->next = newOp; + } + else + { + delete splitOp->next; + delete splitOp; + } + } + + void ClipperBase::FixSelfIntersects(OutRec* outrec) + { + OutPt* op2 = outrec->pts; + for (; ; ) + { + // triangles can't self-intersect + if (op2->prev == op2->next->next) break; + if (SegmentsIntersect(op2->prev->pt, + op2->pt, op2->next->pt, op2->next->next->pt)) + { + if (op2 == outrec->pts || op2->next == outrec->pts) + outrec->pts = outrec->pts->prev; + DoSplitOp(outrec, op2); + if (!outrec->pts) break; + op2 = outrec->pts; + continue; + } + else + op2 = op2->next; + + if (op2 == outrec->pts) break; + } + } - void ClipperBase::FixSelfIntersects(OutRec* outrec) - { - OutPt* op2 = outrec->pts; - for (; ; ) - { - // triangles can't self-intersect - if (op2->prev == op2->next->next) break; - if (SegmentsIntersect(op2->prev->pt, - op2->pt, op2->next->pt, op2->next->next->pt)) - { - if (op2 == outrec->pts || op2->next == outrec->pts) - outrec->pts = outrec->pts->prev; - op2 = DoSplitOp(outrec->pts, op2); - outrec->pts = op2; - continue; - } - else - op2 = op2->next; - - if (op2 == outrec->pts) break; - } - } + inline void UpdateOutrecOwner(OutRec* outrec) + { + OutPt* opCurr = outrec->pts; + for (; ; ) + { + opCurr->outrec = outrec; + opCurr = opCurr->next; + if (opCurr == outrec->pts) return; + } + } - inline void UpdateOutrecOwner(OutRec* outrec) - { - OutPt* opCurr = outrec->pts; - for (; ; ) - { - opCurr->outrec = outrec; - opCurr = opCurr->next; - if (opCurr == outrec->pts) return; - } - } + OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt) + { + OutRec* outrec = NewOutRec(); + outrec->is_open = true; + + if (e.wind_dx > 0) + { + outrec->front_edge = &e; + outrec->back_edge = nullptr; + } + else + { + outrec->front_edge = nullptr; + outrec->back_edge = &e; + } + + e.outrec = outrec; + + OutPt* op = new OutPt(pt, outrec); + outrec->pts = op; + return op; + } - void ClipperBase::SafeDisposeOutPts(OutPt*& op) - { - OutRec* outrec = GetRealOutRec(op->outrec); - if (outrec->front_edge) - outrec->front_edge->outrec = nullptr; - if (outrec->back_edge) - outrec->back_edge->outrec = nullptr; + inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) + { + e->bot = e->top; + e->vertex_top = NextVertex(*e); + e->top = e->vertex_top->pt; + e->curr_x = e->bot.x; + SetDx(*e); - op->prev->next = nullptr; - while (op) - { - SafeDeleteOutPtJoiners(op); - OutPt* tmp = op->next; - delete op; - op = tmp; - } - outrec->pts = nullptr; - } + if (IsJoined(*e)) Split(*e, e->bot); + + if (IsHorizontal(*e)) return; + InsertScanline(e->top.y); + + CheckJoinLeft(*e, e->bot); + CheckJoinRight(*e, e->bot); + } + + Active* FindEdgeWithMatchingLocMin(Active* e) + { + Active* result = e->next_in_ael; + while (result) + { + if (result->local_min == e->local_min) return result; + else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr; + else result = result->next_in_ael; + } + result = e->prev_in_ael; + while (result) + { + if (result->local_min == e->local_min) return result; + else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr; + else result = result->prev_in_ael; + } + return result; + } - void ClipperBase::CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec) - { - double area1 = Area(op1); - double area2 = Area(op2); - bool signs_change = (area1 > 0) == (area2 < 0); + OutPt* 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; + Active* edge_o, * edge_c; + if (IsOpen(e1)) + { + edge_o = &e1; + edge_c = &e2; + } + else + { + edge_o = &e2; + edge_c = &e1; + } + if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety - if (area1 == 0 || (signs_change && std::abs(area1) < 2)) - { - SafeDisposeOutPts(op1); - outrec.pts = op2; - } - else if (area2 == 0 || (signs_change && std::abs(area2) < 2)) - { - SafeDisposeOutPts(op2); - outrec.pts = op1; - } - else - { - OutRec* newOr = new OutRec(); - newOr->idx = outrec_list_.size(); - outrec_list_.push_back(newOr); - newOr->polypath = nullptr; + if (abs(edge_c->wind_cnt) != 1) return nullptr; + switch (cliptype_) + { + case ClipType::Union: + if (!IsHotEdge(*edge_c)) return nullptr; + break; + default: + if (edge_c->local_min->polytype == PathType::Subject) + return nullptr; + } - if (using_polytree_) - { - if (!outrec.splits) outrec.splits = new OutRecList(); - outrec.splits->push_back(newOr); - } + 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; + } - if (std::abs(area1) >= std::abs(area2)) - { - outrec.pts = op1; - newOr->pts = op2; - } - else - { - outrec.pts = op2; - newOr->pts = op1; - } - - if ((area1 > 0) == (area2 > 0)) - newOr->owner = outrec.owner; - else - newOr->owner = &outrec; - - UpdateOutrecOwner(newOr); - CleanCollinear(newOr); - } - } - - - OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt) - { - OutRec* outrec = new OutRec(); - outrec->idx = outrec_list_.size(); - outrec_list_.push_back(outrec); - outrec->owner = nullptr; - outrec->is_open = true; - outrec->pts = nullptr; - outrec->polypath = nullptr; - - if (e.wind_dx > 0) - { - outrec->front_edge = &e; - outrec->back_edge = nullptr; - } - else - { - outrec->front_edge = nullptr; - outrec->back_edge =& e; - } - - e.outrec = outrec; - - OutPt* op = new OutPt(pt, outrec); - outrec->pts = op; - return op; - } - - - inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) - { - e->bot = e->top; - e->vertex_top = NextVertex(*e); - e->top = e->vertex_top->pt; - e->curr_x = e->bot.x; - SetDx(*e); - if (IsHorizontal(*e)) return; - InsertScanline(e->top.y); - if (TestJoinWithPrev1(*e)) - { - OutPt* op1 = AddOutPt(*e->prev_in_ael, e->bot); - OutPt* op2 = AddOutPt(*e, e->bot); - AddJoin(op1, op2); - } - } - - - Active* FindEdgeWithMatchingLocMin(Active* e) - { - Active* result = e->next_in_ael; - while (result) - { - if (result->local_min == e->local_min) return result; - else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr; - else result = result->next_in_ael; - } - result = e->prev_in_ael; - while (result) - { - if (result->local_min == e->local_min) return result; - else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr; - else result = result->prev_in_ael; - } - return result; - } - - - OutPt* 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; - - Active* edge_o, * edge_c; - if (IsOpen(e1)) - { - edge_o = &e1; - edge_c = &e2; - } - else - { - edge_o = &e2; - edge_c = &e1; - } - - if (abs(edge_c->wind_cnt) != 1) return nullptr; - switch (cliptype_) - { - case ClipType::Union: - if (!IsHotEdge(*edge_c)) return nullptr; - break; - default: - if (edge_c->local_min->polytype == PathType::Subject) - return nullptr; - } - - 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; - } - - //toggle contribution ... - if (IsHotEdge(*edge_o)) - { - OutPt* resultOp = AddOutPt(*edge_o, pt); + //toggle contribution ... + if (IsHotEdge(*edge_o)) + { + OutPt* resultOp = AddOutPt(*edge_o, pt); #ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, resultOp->pt); #endif - if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; - else edge_o->outrec->back_edge = nullptr; - edge_o->outrec = nullptr; - return resultOp; - } - - //horizontal edges can pass under open paths at a LocMins - else if (pt == edge_o->local_min->vertex->pt && - !IsOpenEnd(*edge_o->local_min->vertex)) - { - //find the other side of the LocMin and - //if it's 'hot' join up with it ... - Active* e3 = FindEdgeWithMatchingLocMin(edge_o); - if (e3 && IsHotEdge(*e3)) - { - edge_o->outrec = e3->outrec; - if (edge_o->wind_dx > 0) - SetSides(*e3->outrec, *edge_o, *e3); - else - SetSides(*e3->outrec, *e3, *edge_o); - return e3->outrec->pts; - } - else - return StartOpenPath(*edge_o, pt); - } - else - return StartOpenPath(*edge_o, pt); - } + if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; + else edge_o->outrec->back_edge = nullptr; + edge_o->outrec = nullptr; + return resultOp; + } + //horizontal edges can pass under open paths at a LocMins + else if (pt == edge_o->local_min->vertex->pt && + !IsOpenEnd(*edge_o->local_min->vertex)) + { + //find the other side of the LocMin and + //if it's 'hot' join up with it ... + Active* e3 = FindEdgeWithMatchingLocMin(edge_o); + if (e3 && IsHotEdge(*e3)) + { + edge_o->outrec = e3->outrec; + if (edge_o->wind_dx > 0) + SetSides(*e3->outrec, *edge_o, *e3); + else + SetSides(*e3->outrec, *e3, *edge_o); + return e3->outrec->pts; + } + else + return StartOpenPath(*edge_o, pt); + } + else + return StartOpenPath(*edge_o, pt); + } - //MANAGING CLOSED PATHS FROM HERE ON - - //UPDATE WINDING COUNTS... + //MANAGING CLOSED PATHS FROM HERE ON - int old_e1_windcnt, old_e2_windcnt; - if (e1.local_min->polytype == e2.local_min->polytype) - { - if (fillrule_ == FillRule::EvenOdd) - { - old_e1_windcnt = e1.wind_cnt; - e1.wind_cnt = e2.wind_cnt; - e2.wind_cnt = old_e1_windcnt; - } - else - { - if (e1.wind_cnt + e2.wind_dx == 0) - e1.wind_cnt = -e1.wind_cnt; - else - e1.wind_cnt += e2.wind_dx; - if (e2.wind_cnt - e1.wind_dx == 0) - e2.wind_cnt = -e2.wind_cnt; - else - e2.wind_cnt -= e1.wind_dx; - } - } - else - { - if (fillrule_ != FillRule::EvenOdd) - { - e1.wind_cnt2 += e2.wind_dx; - e2.wind_cnt2 -= e1.wind_dx; - } - else - { - e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0); - e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0); - } - } + if (IsJoined(e1)) Split(e1, pt); + if (IsJoined(e2)) Split(e2, pt); - switch (fillrule_) - { - case FillRule::EvenOdd: - case FillRule::NonZero: - old_e1_windcnt = abs(e1.wind_cnt); - old_e2_windcnt = abs(e2.wind_cnt); - break; - default: - if (fillrule_ == fillpos) - { - old_e1_windcnt = e1.wind_cnt; - old_e2_windcnt = e2.wind_cnt; - } - else - { - old_e1_windcnt = -e1.wind_cnt; - old_e2_windcnt = -e2.wind_cnt; - } - break; - } + //UPDATE WINDING COUNTS... - 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; + int old_e1_windcnt, old_e2_windcnt; + if (e1.local_min->polytype == e2.local_min->polytype) + { + if (fillrule_ == FillRule::EvenOdd) + { + old_e1_windcnt = e1.wind_cnt; + e1.wind_cnt = e2.wind_cnt; + e2.wind_cnt = old_e1_windcnt; + } + else + { + if (e1.wind_cnt + e2.wind_dx == 0) + e1.wind_cnt = -e1.wind_cnt; + else + e1.wind_cnt += e2.wind_dx; + if (e2.wind_cnt - e1.wind_dx == 0) + e2.wind_cnt = -e2.wind_cnt; + else + e2.wind_cnt -= e1.wind_dx; + } + } + else + { + if (fillrule_ != FillRule::EvenOdd) + { + e1.wind_cnt2 += e2.wind_dx; + e2.wind_cnt2 -= e1.wind_dx; + } + else + { + e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0); + e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0); + } + } - if ((!IsHotEdge(e1) && !e1_windcnt_in_01) || (!IsHotEdge(e2) && !e2_windcnt_in_01)) - { - return nullptr; - } - - //NOW PROCESS THE INTERSECTION ... - OutPt* resultOp = nullptr; - //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); + switch (fillrule_) + { + case FillRule::EvenOdd: + case FillRule::NonZero: + old_e1_windcnt = abs(e1.wind_cnt); + old_e2_windcnt = abs(e2.wind_cnt); + break; + default: + if (fillrule_ == fillpos) + { + old_e1_windcnt = e1.wind_cnt; + old_e2_windcnt = e2.wind_cnt; + } + else + { + old_e1_windcnt = -e1.wind_cnt; + old_e2_windcnt = -e2.wind_cnt; + } + break; + } + + 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; + } + + //NOW PROCESS THE INTERSECTION ... + OutPt* resultOp = nullptr; + //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 - if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); #endif - } - else if (IsFront(e1) || (e1.outrec == e2.outrec)) - { - //this 'else if' condition isn't strictly needed but - //it's sensible to split polygons that ony touch at - //a common vertex (not at common edges). + } + else if (IsFront(e1) || (e1.outrec == e2.outrec)) + { + //this 'else if' condition isn't strictly needed but + //it's sensible to split polygons that ony touch at + //a common vertex (not at common edges). - resultOp = AddLocalMaxPoly(e1, e2, pt); - OutPt* op2 = AddLocalMinPoly(e1, e2, pt); + resultOp = AddLocalMaxPoly(e1, e2, pt); #ifdef USINGZ - if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); - if (zCallback_) SetZ(e1, e2, op2->pt); -#endif - if (resultOp && resultOp->pt == op2->pt && - !IsHorizontal(e1) && !IsHorizontal(e2) && - (CrossProduct(e1.bot, resultOp->pt, e2.bot) == 0)) - AddJoin(resultOp, op2); - } - else - { - resultOp = AddOutPt(e1, pt); -#ifdef USINGZ - OutPt* op2 = AddOutPt(e2, pt); - if (zCallback_) - { - SetZ(e1, e2, resultOp->pt); - SetZ(e1, e2, op2->pt); - } + OutPt* op2 = AddLocalMinPoly(e1, e2, pt); + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, op2->pt); #else - AddOutPt(e2, pt); + AddLocalMinPoly(e1, e2, pt); #endif - SwapOutrecs(e1, e2); - } - } - else if (IsHotEdge(e1)) - { - resultOp = AddOutPt(e1, pt); + } + else + { + resultOp = AddOutPt(e1, pt); #ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); + OutPt* op2 = AddOutPt(e2, pt); + if (zCallback_) + { + SetZ(e1, e2, resultOp->pt); + SetZ(e1, e2, op2->pt); + } +#else + AddOutPt(e2, pt); #endif - SwapOutrecs(e1, e2); - } - else if (IsHotEdge(e2)) - { - resultOp = AddOutPt(e2, pt); + SwapOutrecs(e1, e2); + } + } + else if (IsHotEdge(e1)) + { + resultOp = AddOutPt(e1, pt); #ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, resultOp->pt); #endif - SwapOutrecs(e1, e2); - } - else - { - int64_t e1Wc2, e2Wc2; - switch (fillrule_) - { - case FillRule::EvenOdd: - case FillRule::NonZero: - e1Wc2 = abs(e1.wind_cnt2); - e2Wc2 = abs(e2.wind_cnt2); - break; - default: - if (fillrule_ == fillpos) - { - e1Wc2 = e1.wind_cnt2; - e2Wc2 = e2.wind_cnt2; - } - else - { - e1Wc2 = -e1.wind_cnt2; - e2Wc2 = -e2.wind_cnt2; - } - break; - } - - if (!IsSamePolyType(e1, e2)) - { - resultOp = AddLocalMinPoly(e1, e2, pt, false); + SwapOutrecs(e1, e2); + } + else if (IsHotEdge(e2)) + { + resultOp = AddOutPt(e2, pt); #ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, resultOp->pt); #endif - } - else if (old_e1_windcnt == 1 && old_e2_windcnt == 1) - { - resultOp = nullptr; - switch (cliptype_) - { - case ClipType::Union: - if (e1Wc2 <= 0 && e2Wc2 <= 0) - resultOp = AddLocalMinPoly(e1, e2, pt, false); - break; - case ClipType::Difference: - if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - { - resultOp = AddLocalMinPoly(e1, e2, pt, false); - } - break; - case ClipType::Xor: - resultOp = AddLocalMinPoly(e1, e2, pt, false); - break; - default: - if (e1Wc2 > 0 && e2Wc2 > 0) - resultOp = AddLocalMinPoly(e1, e2, pt, false); - break; - } + SwapOutrecs(e1, e2); + } + else + { + int64_t e1Wc2, e2Wc2; + switch (fillrule_) + { + case FillRule::EvenOdd: + case FillRule::NonZero: + e1Wc2 = abs(e1.wind_cnt2); + e2Wc2 = abs(e2.wind_cnt2); + break; + default: + if (fillrule_ == fillpos) + { + e1Wc2 = e1.wind_cnt2; + e2Wc2 = e2.wind_cnt2; + } + else + { + e1Wc2 = -e1.wind_cnt2; + e2Wc2 = -e2.wind_cnt2; + } + break; + } + + if (!IsSamePolyType(e1, e2)) + { + resultOp = AddLocalMinPoly(e1, e2, pt, false); #ifdef USINGZ - if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, resultOp->pt); #endif - } - } - return resultOp; - } - - - inline void ClipperBase::DeleteFromAEL(Active& e) - { - Active* prev = e.prev_in_ael; - Active* next = e.next_in_ael; - if (!prev && !next && (&e != actives_)) return; // already deleted - if (prev) - prev->next_in_ael = next; - else - actives_ = next; - if (next) next->prev_in_ael = prev; - delete& e; - } - - - inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) - { - Active* e = actives_; - sel_ = e; - while (e) - { - e->prev_in_sel = e->prev_in_ael; - e->next_in_sel = e->next_in_ael; - e->jump = e->next_in_sel; - e->curr_x = TopX(*e, top_y); - e = e->next_in_ael; - } - } - - - bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees) - { - cliptype_ = ct; - fillrule_ = fillrule; - using_polytree_ = use_polytrees; - Reset(); - int64_t y; - if (ct == ClipType::None || !PopScanline(y)) return true; - - while (succeeded_) - { - InsertLocalMinimaIntoAEL(y); - Active* e; - while (PopHorz(e)) DoHorizontal(*e); - if (horz_joiners_) ConvertHorzTrialsToJoins(); - bot_y_ = y; // bot_y_ == bottom of scanbeam - if (!PopScanline(y)) break; // y new top of scanbeam - DoIntersections(y); - DoTopOfScanbeam(y); - while (PopHorz(e)) DoHorizontal(*e); - } - ProcessJoinerList(); - return succeeded_; - } - - void ClipperBase::DoIntersections(const int64_t top_y) - { - if (BuildIntersectList(top_y)) - { - ProcessIntersectList(); - intersect_nodes_.clear(); - } - } - - void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y) - { - Point64 pt = GetIntersectPoint(e1, e2); - - //rounding errors can occasionally place the calculated intersection - //point either below or above the scanbeam, so check and correct ... - if (pt.y > bot_y_) - { - //e.curr.y is still the bottom of scanbeam - pt.y = bot_y_; - //use the more vertical of the 2 edges to derive pt.x ... - if (abs(e1.dx) < abs(e2.dx)) - pt.x = TopX(e1, bot_y_); - else - pt.x = TopX(e2, bot_y_); - } - else if (pt.y < top_y) - { - //top_y is at the top of the scanbeam - pt.y = top_y; - if (e1.top.y == top_y) - pt.x = e1.top.x; - else if (e2.top.y == top_y) - pt.x = e2.top.x; - else if (abs(e1.dx) < abs(e2.dx)) - pt.x = e1.curr_x; - else - pt.x = e2.curr_x; - } - - intersect_nodes_.push_back(IntersectNode(&e1, &e2, pt)); - } - - - bool ClipperBase::BuildIntersectList(const int64_t top_y) - { - if (!actives_ || !actives_->next_in_ael) return false; - - //Calculate edge positions at the top of the current scanbeam, and from this - //we will determine the intersections required to reach these new positions. - AdjustCurrXAndCopyToSEL(top_y); - //Find all edge intersections in the current scanbeam using a stable merge - //sort that ensures only adjacent edges are intersecting. Intersect info is - //stored in FIntersectList ready to be processed in ProcessIntersectList. - //Re merge sorts see https://stackoverflow.com/a/46319131/359538 - - Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp; - - while (left && left->jump) - { - Active* prev_base = nullptr; - while (left && left->jump) - { - curr_base = left; - right = left->jump; - l_end = right; - r_end = right->jump; - left->jump = r_end; - while (left != l_end && right != r_end) - { - if (right->curr_x < left->curr_x) - { - tmp = right->prev_in_sel; - for (; ; ) - { - AddNewIntersectNode(*tmp, *right, top_y); - if (tmp == left) break; - tmp = tmp->prev_in_sel; - } - - tmp = right; - right = ExtractFromSEL(tmp); - l_end = right; - Insert1Before2InSEL(tmp, left); - if (left == curr_base) - { - curr_base = tmp; - curr_base->jump = r_end; - if (!prev_base) sel_ = curr_base; - else prev_base->jump = curr_base; - } - } - else left = left->next_in_sel; - } - prev_base = curr_base; - left = r_end; - } - left = sel_; - } - return intersect_nodes_.size() > 0; - } - - void ClipperBase::ProcessIntersectList() - { - //We now have a list of intersections required so that edges will be - //correctly positioned at the top of the scanbeam. However, it's important - //that edge intersections are processed from the bottom up, but it's also - //crucial that intersections only occur between adjacent edges. - - //First we do a quicksort so intersections proceed in a bottom up order ... - std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort); - //Now as we process these intersections, we must sometimes adjust the order - //to ensure that intersecting edges are always adjacent ... - - std::vector::iterator node_iter, node_iter2; - for (node_iter = intersect_nodes_.begin(); - node_iter != intersect_nodes_.end(); ++node_iter) - { - if (!EdgesAdjacentInAEL(*node_iter)) - { - node_iter2 = node_iter + 1; - while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2; - std::swap(*node_iter, *node_iter2); - } - - IntersectNode& node = *node_iter; - IntersectEdges(*node.edge1, *node.edge2, node.pt); - SwapPositionsInAEL(*node.edge1, *node.edge2); - - if (TestJoinWithPrev2(*node.edge2, node.pt)) - { - OutPt* op1 = AddOutPt(*node.edge2->prev_in_ael, node.pt); - OutPt* op2 = AddOutPt(*node.edge2, node.pt); - if (op1 != op2) AddJoin(op1, op2); - } - else if (TestJoinWithNext2(*node.edge1, node.pt)) - { - OutPt* op1 = AddOutPt(*node.edge1, node.pt); - OutPt* op2 = AddOutPt(*node.edge1->next_in_ael, node.pt); - if (op1 != op2) AddJoin(op1, op2); - } - } - } - - - void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2) - { - //preconditon: e1 must be immediately to the left of e2 - Active* next = e2.next_in_ael; - if (next) next->prev_in_ael = &e1; - Active* prev = e1.prev_in_ael; - if (prev) prev->next_in_ael = &e2; - e2.prev_in_ael = prev; - e2.next_in_ael = &e1; - e1.prev_in_ael = &e2; - e1.next_in_ael = next; - if (!e2.prev_in_ael) actives_ = &e2; - } - - - bool ClipperBase::ResetHorzDirection(const Active& horz, - const Active* max_pair, int64_t& horz_left, int64_t& horz_right) - { - if (horz.bot.x == horz.top.x) - { - //the horizontal edge is going nowhere ... - horz_left = horz.curr_x; - horz_right = horz.curr_x; - Active* e = horz.next_in_ael; - while (e && e != max_pair) e = e->next_in_ael; - return e != nullptr; - } - else if (horz.curr_x < horz.top.x) - { - horz_left = horz.curr_x; - horz_right = horz.top.x; - return true; - } - else - { - horz_left = horz.top.x; - horz_right = horz.curr_x; - return false; // right to left - } - } - - inline bool HorzIsSpike(const Active& horzEdge) - { - Point64 nextPt = NextVertex(horzEdge)->pt; - return (nextPt.y == horzEdge.bot.y) && - (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x); - } - - inline void TrimHorz(Active& horzEdge, bool preserveCollinear) - { - bool wasTrimmed = false; - Point64 pt = NextVertex(horzEdge)->pt; - while (pt.y == horzEdge.top.y) - { - //always trim 180 deg. spikes (in closed paths) - //but otherwise break if preserveCollinear = true - if (preserveCollinear && - ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x))) - break; - - horzEdge.vertex_top = NextVertex(horzEdge); - horzEdge.top = pt; - wasTrimmed = true; - if (IsMaxima(horzEdge)) break; - pt = NextVertex(horzEdge)->pt; - } - - if (wasTrimmed) SetDx(horzEdge); // +/-infinity - } - - - void ClipperBase::DoHorizontal(Active& horz) - /******************************************************************************* - * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * - * bottom of a scanbeam) are processed as if layered.The order in which HEs * - * are processed doesn't matter. HEs intersect with the bottom vertices of * - * other HEs[#] and with non-horizontal edges [*]. Once these intersections * - * are completed, intermediate HEs are 'promoted' to the next edge in their * - * bounds, and they in turn may be intersected[%] by other HEs. * - * * - * eg: 3 horizontals at a scanline: / | / / * - * | / | (HE3)o ========%========== o * - * o ======= o(HE2) / | / / * - * o ============#=========*======*========#=========o (HE1) * - * / | / | / * - *******************************************************************************/ - { - Point64 pt; - bool horzIsOpen = IsOpen(horz); - int64_t y = horz.bot.y; - Vertex* vertex_max = nullptr; - Active* max_pair = nullptr; - - if (!horzIsOpen) - { - vertex_max = GetCurrYMaximaVertex(horz); - if (vertex_max) - { - max_pair = GetHorzMaximaPair(horz, vertex_max); - //remove 180 deg.spikes and also simplify - //consecutive horizontals when PreserveCollinear = true - if (vertex_max != horz.vertex_top) - TrimHorz(horz, PreserveCollinear); - } - } - - int64_t horz_left, horz_right; - bool is_left_to_right = - ResetHorzDirection(horz, max_pair, horz_left, horz_right); - - if (IsHotEdge(horz)) - AddOutPt(horz, Point64(horz.curr_x, y)); - - OutPt* op; - while (true) // loop through consec. horizontal edges - { - if (horzIsOpen && IsMaxima(horz) && !IsOpenEnd(horz)) - { - vertex_max = GetCurrYMaximaVertex(horz); - if (vertex_max) - max_pair = GetHorzMaximaPair(horz, vertex_max); - } - - Active* e; - if (is_left_to_right) e = horz.next_in_ael; - else e = horz.prev_in_ael; - - while (e) - { - - if (e == max_pair) - { - if (IsHotEdge(horz)) - { - while (horz.vertex_top != e->vertex_top) - { - AddOutPt(horz, horz.top); - UpdateEdgeIntoAEL(&horz); - } - op = AddLocalMaxPoly(horz, *e, horz.top); - if (op && !IsOpen(horz) && op->pt == horz.top) - AddTrialHorzJoin(op); - } - DeleteFromAEL(*e); - DeleteFromAEL(horz); - return; - } - - //if horzEdge is a maxima, keep going until we reach - //its maxima pair, otherwise check for break conditions - if (vertex_max != horz.vertex_top || IsOpenEnd(horz)) - { - //otherwise stop when 'ae' is beyond the end of the horizontal line - if ((is_left_to_right && e->curr_x > horz_right) || - (!is_left_to_right && e->curr_x < horz_left)) break; - - if (e->curr_x == horz.top.x && !IsHorizontal(*e)) - { - pt = NextVertex(horz)->pt; - if (is_left_to_right) - { - //with open paths we'll only break once past horz's end - if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) - { - if (TopX(*e, pt.y) > pt.x) break; - } - //otherwise we'll only break when horz's outslope is greater than e's - else if (TopX(*e, pt.y) >= pt.x) break; - } - else - { - if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) - { - if (TopX(*e, pt.y) < pt.x) break; - } - else if (TopX(*e, pt.y) <= pt.x) break; - } - } - } - - pt = Point64(e->curr_x, horz.bot.y); - - if (is_left_to_right) - { - op = IntersectEdges(horz, *e, pt); - SwapPositionsInAEL(horz, *e); - // todo: check if op->pt == pt test is still needed - // expect op != pt only after AddLocalMaxPoly when horz.outrec == nullptr - if (IsHotEdge(horz) && op && !IsOpen(horz) && op->pt == pt) - AddTrialHorzJoin(op); - - if (!IsHorizontal(*e) && TestJoinWithPrev1(*e)) - { - op = AddOutPt(*e->prev_in_ael, pt); - OutPt* op2 = AddOutPt(*e, pt); - AddJoin(op, op2); - } - - horz.curr_x = e->curr_x; - e = horz.next_in_ael; - } - else - { - op = IntersectEdges(*e, horz, pt); - SwapPositionsInAEL(*e, horz); - - if (IsHotEdge(horz) && op && - !IsOpen(horz) && op->pt == pt) - AddTrialHorzJoin(op); - - if (!IsHorizontal(*e) && TestJoinWithNext1(*e)) - { - op = AddOutPt(*e, pt); - OutPt* op2 = AddOutPt(*e->next_in_ael, pt); - AddJoin(op, op2); - } - - horz.curr_x = e->curr_x; - e = horz.prev_in_ael; - } - } - - //check if we've finished with (consecutive) horizontals ... - if (horzIsOpen && IsOpenEnd(horz)) // ie open at top - { - if (IsHotEdge(horz)) - { - AddOutPt(horz, horz.top); - if (IsFront(horz)) - horz.outrec->front_edge = nullptr; - else - horz.outrec->back_edge = nullptr; - horz.outrec = nullptr; - } - DeleteFromAEL(horz); - return; - } - else if (NextVertex(horz)->pt.y != horz.top.y) - break; - - //still more horizontals in bound to process ... - if (IsHotEdge(horz)) - AddOutPt(horz, horz.top); - UpdateEdgeIntoAEL(&horz); - - if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz)) - TrimHorz(horz, true); - - is_left_to_right = - ResetHorzDirection(horz, max_pair, horz_left, horz_right); - } - - if (IsHotEdge(horz)) - { - op = AddOutPt(horz, horz.top); - if (!IsOpen(horz)) - AddTrialHorzJoin(op); - } - else - op = nullptr; - - if ((horzIsOpen && !IsOpenEnd(horz)) || - (!horzIsOpen && vertex_max != horz.vertex_top)) - { - UpdateEdgeIntoAEL(&horz); // this is the end of an intermediate horiz. - if (IsOpen(horz)) return; - - if (is_left_to_right && TestJoinWithNext1(horz)) - { - OutPt* op2 = AddOutPt(*horz.next_in_ael, horz.bot); - AddJoin(op, op2); - } - else if (!is_left_to_right && TestJoinWithPrev1(horz)) - { - OutPt* op2 = AddOutPt(*horz.prev_in_ael, horz.bot); - AddJoin(op2, op); - } - } - else if (IsHotEdge(horz)) - AddLocalMaxPoly(horz, *max_pair, horz.top); - else - { - DeleteFromAEL(*max_pair); - DeleteFromAEL(horz); - } - } - - - void ClipperBase::DoTopOfScanbeam(const int64_t y) - { - sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below) - Active* e = actives_; - while (e) - { - //nb: 'e' will never be horizontal here - if (e->top.y == y) - { - e->curr_x = e->top.x; - if (IsMaxima(*e)) - { - e = DoMaxima(*e); // TOP OF BOUND (MAXIMA) - continue; - } - else - { - //INTERMEDIATE VERTEX ... - if (IsHotEdge(*e)) AddOutPt(*e, e->top); - UpdateEdgeIntoAEL(e); - if (IsHorizontal(*e)) - PushHorz(*e); // horizontals are processed later - } - } - else // i.e. not the top of the edge - e->curr_x = TopX(*e, y); - - e = e->next_in_ael; - } - } - - - Active* ClipperBase::DoMaxima(Active& e) - { - Active* next_e, * prev_e, * max_pair; - prev_e = e.prev_in_ael; - next_e = e.next_in_ael; - if (IsOpenEnd(e)) - { - if (IsHotEdge(e)) AddOutPt(e, e.top); - if (!IsHorizontal(e)) - { - if (IsHotEdge(e)) - { - if (IsFront(e)) - e.outrec->front_edge = nullptr; - else - e.outrec->back_edge = nullptr; - e.outrec = nullptr; - } - DeleteFromAEL(e); - } - return next_e; - } - else - { - max_pair = GetMaximaPair(e); - if (!max_pair) return next_e; // eMaxPair is horizontal - } - - //only non-horizontal maxima here. - //process any edges between maxima pair ... - while (next_e != max_pair) - { - IntersectEdges(e, *next_e, e.top); - SwapPositionsInAEL(e, *next_e); - next_e = e.next_in_ael; - } - - if (IsOpen(e)) - { - if (IsHotEdge(e)) - AddLocalMaxPoly(e, *max_pair, e.top); - DeleteFromAEL(*max_pair); - DeleteFromAEL(e); - return (prev_e ? prev_e->next_in_ael : actives_); - } - - //here E.next_in_ael == ENext == EMaxPair ... - if (IsHotEdge(e)) - AddLocalMaxPoly(e, *max_pair, e.top); - - DeleteFromAEL(e); - DeleteFromAEL(*max_pair); - return (prev_e ? prev_e->next_in_ael : actives_); - } - - - void ClipperBase::SafeDeleteOutPtJoiners(OutPt* op) - { - Joiner* joiner = op->joiner; - if (!joiner) return; - - while (joiner) - { - if (joiner->idx < 0) - DeleteTrialHorzJoin(op); - else if (horz_joiners_) - { - if (OutPtInTrialHorzList(joiner->op1)) - DeleteTrialHorzJoin(joiner->op1); - if (OutPtInTrialHorzList(joiner->op2)) - DeleteTrialHorzJoin(joiner->op2); - DeleteJoin(joiner); - } - else - DeleteJoin(joiner); - joiner = op->joiner; - } - } - - - Joiner* ClipperBase::GetHorzTrialParent(const OutPt* op) - { - Joiner* joiner = op->joiner; - while (joiner) - { - if (joiner->op1 == op) - { - if (joiner->next1 && joiner->next1->idx < 0) return joiner; - else joiner = joiner->next1; - } - else - { - if (joiner->next2 && joiner->next2->idx < 0) return joiner; - else joiner = joiner->next1; - } - } - return joiner; - } - - - bool ClipperBase::OutPtInTrialHorzList(OutPt* op) - { - return op->joiner && ((op->joiner->idx < 0) || GetHorzTrialParent(op)); - } - - - void ClipperBase::AddTrialHorzJoin(OutPt* op) - { - //make sure 'op' isn't added more than once - if (!op->outrec->is_open && !OutPtInTrialHorzList(op)) - horz_joiners_ = new Joiner(op, nullptr, horz_joiners_); - } - - - Joiner* FindTrialJoinParent(Joiner*& joiner, const OutPt* op) - { - Joiner* parent = joiner; - while (parent) - { - if (op == parent->op1) - { - if (parent->next1 && parent->next1->idx < 0) - { - joiner = parent->next1; - return parent; - } - parent = parent->next1; - } - else - { - if (parent->next2 && parent->next2->idx < 0) - { - joiner = parent->next2; - return parent; - } - parent = parent->next2; - } - } - return nullptr; - } - - - void ClipperBase::DeleteTrialHorzJoin(OutPt* op) - { - if (!horz_joiners_) return; - - Joiner* joiner = op->joiner; - Joiner* parentH, * parentOp = nullptr; - while (joiner) - { - if (joiner->idx < 0) - { - //first remove joiner from FHorzTrials - if (joiner == horz_joiners_) - horz_joiners_ = joiner->nextH; - else - { - parentH = horz_joiners_; - while (parentH->nextH != joiner) - parentH = parentH->nextH; - parentH->nextH = joiner->nextH; - } - - //now remove joiner from op's joiner list - if (!parentOp) - { - //joiner must be first one in list - op->joiner = joiner->next1; - delete joiner; - joiner = op->joiner; - } - else - { - //the trial joiner isn't first - if (op == parentOp->op1) - parentOp->next1 = joiner->next1; - else - parentOp->next2 = joiner->next1; - delete joiner; - joiner = parentOp; - } - } - else - { - //not a trial join so look further along the linked list - parentOp = FindTrialJoinParent(joiner, op); - if (!parentOp) break; - } - //loop in case there's more than one trial join - } - } - - - inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2) - { - OutRec* outrec = GetRealOutRec(op->outrec); - op2 = op; - if (outrec->front_edge) - { - while (op->prev != outrec->pts && - op->prev->pt.y == op->pt.y) op = op->prev; - while (op2 != outrec->pts && - op2->next->pt.y == op2->pt.y) op2 = op2->next; - return op2 != op; - } - else - { - while (op->prev != op2 && op->prev->pt.y == op->pt.y) - op = op->prev; - while (op2->next != op && op2->next->pt.y == op2->pt.y) - op2 = op2->next; - return op2 != op && op2->next != op; - } - } - - - inline bool HorzEdgesOverlap(int64_t x1a, int64_t x1b, int64_t x2a, int64_t x2b) - { - const int64_t minOverlap = 2; - if (x1a > x1b + minOverlap) - { - if (x2a > x2b + minOverlap) - return !((x1a <= x2b) || (x2a <= x1b)); - else - return !((x1a <= x2a) || (x2b <= x1b)); - } - else if (x1b > x1a + minOverlap) - { - if (x2a > x2b + minOverlap) - return !((x1b <= x2b) || (x2a <= x1a)); - else - return !((x1b <= x2a) || (x2b <= x1a)); - } - else - return false; - } - - - inline bool ValueBetween(int64_t val, int64_t end1, int64_t end2) - { - //NB accommodates axis aligned between where end1 == end2 - return ((val != end1) == (val != end2)) && - ((val > end1) == (val < end2)); - } - - - inline bool ValueEqualOrBetween(int64_t val, int64_t end1, int64_t end2) - { - return (val == end1) || (val == end2) || ((val > end1) == (val < end2)); - } - - - inline bool PointBetween(Point64 pt, Point64 corner1, Point64 corner2) - { - //NB points may not be collinear - return ValueBetween(pt.x, corner1.x, corner2.x) && - ValueBetween(pt.y, corner1.y, corner2.y); - } - - inline bool PointEqualOrBetween(Point64 pt, Point64 corner1, Point64 corner2) - { - //NB points may not be collinear - return ValueEqualOrBetween(pt.x, corner1.x, corner2.x) && - ValueEqualOrBetween(pt.y, corner1.y, corner2.y); - } - - - Joiner* FindJoinParent(const Joiner* joiner, OutPt* op) - { - Joiner* result = op->joiner; - for (; ; ) - { - if (op == result->op1) - { - if (result->next1 == joiner) return result; - else result = result->next1; - } - else - { - if (result->next2 == joiner) return result; - else result = result->next2; - } - } - } - - - void ClipperBase::ConvertHorzTrialsToJoins() - { - while (horz_joiners_) - { - Joiner* joiner = horz_joiners_; - horz_joiners_ = horz_joiners_->nextH; - OutPt* op1a = joiner->op1; - if (op1a->joiner == joiner) - { - op1a->joiner = joiner->next1; - } - else - { - Joiner* joinerParent = FindJoinParent(joiner, op1a); - if (joinerParent->op1 == op1a) - joinerParent->next1 = joiner->next1; - else - joinerParent->next2 = joiner->next1; - } - delete joiner; - - OutPt* op1b; - if (!GetHorzExtendedHorzSeg(op1a, op1b)) - { - CleanCollinear(op1a->outrec); - continue; - } - - bool joined = false; - joiner = horz_joiners_; - while (joiner) - { - OutPt* op2a = joiner->op1, * op2b; - if (GetHorzExtendedHorzSeg(op2a, op2b) && - HorzEdgesOverlap(op1a->pt.x, op1b->pt.x, op2a->pt.x, op2b->pt.x)) - { - //overlap found so promote to a 'real' join - joined = true; - if (op1a->pt == op2b->pt) - AddJoin(op1a, op2b); - else if (op1b->pt == op2a->pt) - AddJoin(op1b, op2a); - else if (op1a->pt == op2a->pt) - AddJoin(op1a, op2a); - else if (op1b->pt == op2b->pt) - AddJoin(op1b, op2b); - else if (ValueBetween(op1a->pt.x, op2a->pt.x, op2b->pt.x)) - AddJoin(op1a, InsertOp(op1a->pt, op2a)); - else if (ValueBetween(op1b->pt.x, op2a->pt.x, op2b->pt.x)) - AddJoin(op1b, InsertOp(op1b->pt, op2a)); - else if (ValueBetween(op2a->pt.x, op1a->pt.x, op1b->pt.x)) - AddJoin(op2a, InsertOp(op2a->pt, op1a)); - else if (ValueBetween(op2b->pt.x, op1a->pt.x, op1b->pt.x)) - AddJoin(op2b, InsertOp(op2b->pt, op1a)); - break; - } - joiner = joiner->nextH; - } - if (!joined) - CleanCollinear(op1a->outrec); - } - } - - - void ClipperBase::AddJoin(OutPt* op1, OutPt* op2) - { - if ((op1->outrec == op2->outrec) && ((op1 == op2) || - //unless op1.next or op1.prev crosses the start-end divide - //don't waste time trying to join adjacent vertices - ((op1->next == op2) && (op1 != op1->outrec->pts)) || - ((op2->next == op1) && (op2 != op1->outrec->pts)))) return; - - Joiner* j = new Joiner(op1, op2, nullptr); - j->idx = static_cast(joiner_list_.size()); - joiner_list_.push_back(j); - } - - - void ClipperBase::DeleteJoin(Joiner* joiner) - { - //This method deletes a single join, and it doesn't check for or - //delete trial horz. joins. For that, use the following method. - OutPt* op1 = joiner->op1, * op2 = joiner->op2; - - Joiner* parent_joiner; - if (op1->joiner != joiner) - { - parent_joiner = FindJoinParent(joiner, op1); - if (parent_joiner->op1 == op1) - parent_joiner->next1 = joiner->next1; - else - parent_joiner->next2 = joiner->next1; - } - else - op1->joiner = joiner->next1; - - if (op2->joiner != joiner) - { - parent_joiner = FindJoinParent(joiner, op2); - if (parent_joiner->op1 == op2) - parent_joiner->next1 = joiner->next2; - else - parent_joiner->next2 = joiner->next2; - } - else - op2->joiner = joiner->next2; - - joiner_list_[joiner->idx] = nullptr; - delete joiner; - } - - - void ClipperBase::ProcessJoinerList() - { - for (Joiner* j : joiner_list_) - { - if (!j) continue; - if (succeeded_) - { - OutRec* outrec = ProcessJoin(j); - CleanCollinear(outrec); - } - else - delete j; - } - - joiner_list_.resize(0); - } - - - bool CheckDisposeAdjacent(OutPt*& op, const OutPt* guard, OutRec& outRec) - { - bool result = false; - while (op->prev != op) - { - if (op->pt == op->prev->pt && op != guard && - op->prev->joiner && !op->joiner) - { - if (op == outRec.pts) outRec.pts = op->prev; - op = DisposeOutPt(op); - op = op->prev; - } - else - break; - } - - while (op->next != op) - { - if (op->pt == op->next->pt && op != guard && - op->next->joiner && !op->joiner) - { - if (op == outRec.pts) outRec.pts = op->prev; - op = DisposeOutPt(op); - op = op->prev; - } - else - break; - } - return result; - } - - - inline bool IsValidPath(OutPt* op) - { - return (op && op->next != op); - } - - - bool CollinearSegsOverlap(const Point64& seg1a, const Point64& seg1b, - const Point64& seg2a, const Point64& seg2b) - { - //precondition: seg1 and seg2 are collinear - if (seg1a.x == seg1b.x) - { - if (seg2a.x != seg1a.x || seg2a.x != seg2b.x) return false; - } - else if (seg1a.x < seg1b.x) - { - if (seg2a.x < seg2b.x) - { - if (seg2a.x >= seg1b.x || seg2b.x <= seg1a.x) return false; - } - else - { - if (seg2b.x >= seg1b.x || seg2a.x <= seg1a.x) return false; - } - } - else - { - if (seg2a.x < seg2b.x) - { - if (seg2a.x >= seg1a.x || seg2b.x <= seg1b.x) return false; - } - else - { - if (seg2b.x >= seg1a.x || seg2a.x <= seg1b.x) return false; - } - } - - if (seg1a.y == seg1b.y) - { - if (seg2a.y != seg1a.y || seg2a.y != seg2b.y) return false; - } - else if (seg1a.y < seg1b.y) - { - if (seg2a.y < seg2b.y) - { - if (seg2a.y >= seg1b.y || seg2b.y <= seg1a.y) return false; - } - else - { - if (seg2b.y >= seg1b.y || seg2a.y <= seg1a.y) return false; - } - } - else - { - if (seg2a.y < seg2b.y) - { - if (seg2a.y >= seg1a.y || seg2b.y <= seg1b.y) return false; - } - else - { - if (seg2b.y >= seg1a.y || seg2a.y <= seg1b.y) return false; - } - } - return true; - } - - OutRec* ClipperBase::ProcessJoin(Joiner* joiner) - { - OutPt* op1 = joiner->op1, * op2 = joiner->op2; - OutRec* or1 = GetRealOutRec(op1->outrec); - OutRec* or2 = GetRealOutRec(op2->outrec); - DeleteJoin(joiner); - - if (or2->pts == nullptr) return or1; - else if (!IsValidClosedPath(op2)) - { - SafeDisposeOutPts(op2); - return or1; - } - else if ((or1->pts == nullptr) || !IsValidClosedPath(op1)) - { - SafeDisposeOutPts(op1); - return or2; - } - else if (or1 == or2 && - ((op1 == op2) || (op1->next == op2) || (op1->prev == op2))) return or1; - - CheckDisposeAdjacent(op1, op2, *or1); - CheckDisposeAdjacent(op2, op1, *or2); - if (op1->next == op2 || op2->next == op1) return or1; - OutRec* result = or1; - - for (; ; ) - { - if (!IsValidPath(op1) || !IsValidPath(op2) || - (or1 == or2 && (op1->prev == op2 || op1->next == op2))) return or1; - - if (op1->prev->pt == op2->next->pt || - ((CrossProduct(op1->prev->pt, op1->pt, op2->next->pt) == 0) && - CollinearSegsOverlap(op1->prev->pt, op1->pt, op2->pt, op2->next->pt))) - { - if (or1 == or2) - { - //SPLIT REQUIRED - //make sure op1.prev and op2.next match positions - //by inserting an extra vertex if needed - if (op1->prev->pt != op2->next->pt) - { - if (PointEqualOrBetween(op1->prev->pt, op2->pt, op2->next->pt)) - op2->next = InsertOp(op1->prev->pt, op2); - else - op1->prev = InsertOp(op2->next->pt, op1->prev); - } - - //current to new - //op1.p[opA] >>> op1 ... opA \ / op1 - //op2.n[opB] <<< op2 ... opB / \ op2 - OutPt* opA = op1->prev, * opB = op2->next; - opA->next = opB; - opB->prev = opA; - op1->prev = op2; - op2->next = op1; - CompleteSplit(op1, opA, *or1); - } - else - { - //JOIN, NOT SPLIT - OutPt* opA = op1->prev, * opB = op2->next; - opA->next = opB; - opB->prev = opA; - op1->prev = op2; - op2->next = op1; - - //SafeDeleteOutPtJoiners(op2); - //DisposeOutPt(op2); - - if (or1->idx < or2->idx) - { - or1->pts = op1; - or2->pts = nullptr; - if (or1->owner && (!or2->owner || - or2->owner->idx < or1->owner->idx)) - or1->owner = or2->owner; - or2->owner = or1; - } - else - { - result = or2; - or2->pts = op1; - or1->pts = nullptr; - if (or2->owner && (!or1->owner || - or1->owner->idx < or2->owner->idx)) - or2->owner = or1->owner; - or1->owner = or2; - } - } - break; - } - else if (op1->next->pt == op2->prev->pt || - ((CrossProduct(op1->next->pt, op2->pt, op2->prev->pt) == 0) && - CollinearSegsOverlap(op1->next->pt, op1->pt, op2->pt, op2->prev->pt))) - { - if (or1 == or2) - { - //SPLIT REQUIRED - //make sure op2.prev and op1.next match positions - //by inserting an extra vertex if needed - if (op2->prev->pt != op1->next->pt) - { - if (PointEqualOrBetween(op2->prev->pt, op1->pt, op1->next->pt)) - op1->next = InsertOp(op2->prev->pt, op1); - else - op2->prev = InsertOp(op1->next->pt, op2->prev); - } - - //current to new - //op2.p[opA] >>> op2 ... opA \ / op2 - //op1.n[opB] <<< op1 ... opB / \ op1 - OutPt* opA = op2->prev, * opB = op1->next; - opA->next = opB; - opB->prev = opA; - op2->prev = op1; - op1->next = op2; - CompleteSplit(op1, opA, *or1); - } - else - { - //JOIN, NOT SPLIT - OutPt* opA = op1->next, * opB = op2->prev; - opA->prev = opB; - opB->next = opA; - op1->next = op2; - op2->prev = op1; - - //SafeDeleteOutPtJoiners(op2); - //DisposeOutPt(op2); - - if (or1->idx < or2->idx) - { - or1->pts = op1; - or2->pts = nullptr; - if (or1->owner && (!or2->owner || - or2->owner->idx < or1->owner->idx)) - or1->owner = or2->owner; - or2->owner = or1; - } - else - { - result = or2; - or2->pts = op1; - or1->pts = nullptr; - if (or2->owner && (!or1->owner || - or1->owner->idx < or2->owner->idx)) - or2->owner = or1->owner; - or1->owner = or2; - } - } - break; - } - else if (PointBetween(op1->next->pt, op2->pt, op2->prev->pt) && - DistanceFromLineSqrd(op1->next->pt, op2->pt, op2->prev->pt) < 2.01) - { - InsertOp(op1->next->pt, op2->prev); - continue; - } - else if (PointBetween(op2->next->pt, op1->pt, op1->prev->pt) && - DistanceFromLineSqrd(op2->next->pt, op1->pt, op1->prev->pt) < 2.01) - { - InsertOp(op2->next->pt, op1->prev); - continue; - } - else if (PointBetween(op1->prev->pt, op2->pt, op2->next->pt) && - DistanceFromLineSqrd(op1->prev->pt, op2->pt, op2->next->pt) < 2.01) - { - InsertOp(op1->prev->pt, op2); - continue; - } - else if (PointBetween(op2->prev->pt, op1->pt, op1->next->pt) && - DistanceFromLineSqrd(op2->prev->pt, op1->pt, op1->next->pt) < 2.01) - { - InsertOp(op2->prev->pt, op1); - continue; - } - - //something odd needs tidying up - if (CheckDisposeAdjacent(op1, op2, *or1)) continue; - else if (CheckDisposeAdjacent(op2, op1, *or1)) continue; - else if (op1->prev->pt != op2->next->pt && - (DistanceSqr(op1->prev->pt, op2->next->pt) < 2.01)) - { - op1->prev->pt = op2->next->pt; - continue; - } - else if (op1->next->pt != op2->prev->pt && - (DistanceSqr(op1->next->pt, op2->prev->pt) < 2.01)) - { - op2->prev->pt = op1->next->pt; - continue; - } - else - { - //OK, there doesn't seem to be a way to join after all - //so just tidy up the polygons - or1->pts = op1; - if (or2 != or1) - { - or2->pts = op2; - CleanCollinear(or2); - } - break; - } - } - return result; - - } - - inline bool Path1InsidePath2(const OutRec* or1, const OutRec* or2) - { - PointInPolygonResult result = PointInPolygonResult::IsOn; - OutPt* op = or1->pts; - do - { - result = PointInPolygon(op->pt, or2->path); - if (result != PointInPolygonResult::IsOn) break; - op = op->next; - } while (op != or1->pts); - if (result == PointInPolygonResult::IsOn) - return Area(op) < Area(or2->pts); - else - return result == PointInPolygonResult::IsInside; - } - - inline Rect64 GetBounds(const Path64& path) - { - if (path.empty()) return Rect64(); - Rect64 result = invalid_rect; - for(const Point64& pt : path) - { - if (pt.x < result.left) result.left = pt.x; - if (pt.x > result.right) result.right = pt.x; - if (pt.y < result.top) result.top = pt.y; - if (pt.y > result.bottom) result.bottom = pt.y; - } - return result; - } - - bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path) - { - if (op->next == op || (!isOpen && op->next == op->prev)) return false; - path.resize(0); - Point64 lastPt; - OutPt* op2; - if (reverse) - { - lastPt = op->pt; - op2 = op->prev; - } - else - { - op = op->next; - lastPt = op->pt; - op2 = op->next; - } - path.push_back(lastPt); - - while (op2 != op) - { - if (op2->pt != lastPt) - { - lastPt = op2->pt; - path.push_back(lastPt); - } - if (reverse) - op2 = op2->prev; - else - op2 = op2->next; - } - return true; - } - - bool ClipperBase::DeepCheckOwner(OutRec* outrec, OutRec* owner) - { - if (owner->bounds.IsEmpty()) owner->bounds = GetBounds(owner->path); - bool is_inside_owner_bounds = owner->bounds.Contains(outrec->bounds); - - // while looking for the correct owner, check the owner's - // splits **before** checking the owner itself because - // splits can occur internally, and checking the owner - // first would miss the inner split's true ownership - if (owner->splits) - { - for (OutRec* split : *owner->splits) - { - split = GetRealOutRec(split); - if (!split || split->idx <= owner->idx || split == outrec) continue; - - if (split->splits && DeepCheckOwner(outrec, split)) return true; - - if (!split->path.size()) - BuildPath64(split->pts, ReverseSolution, false, split->path); - if (split->bounds.IsEmpty()) split->bounds = GetBounds(split->path); - - if (split->bounds.Contains(outrec->bounds) && - Path1InsidePath2(outrec, split)) - { - outrec->owner = split; - return true; - } - } - } - - // only continue past here when not inside recursion - if (owner != outrec->owner) return false; - - for (;;) - { - if (is_inside_owner_bounds && Path1InsidePath2(outrec, outrec->owner)) - return true; - // otherwise keep trying with owner's owner - outrec->owner = outrec->owner->owner; - if (!outrec->owner) return true; // true or false - is_inside_owner_bounds = outrec->owner->bounds.Contains(outrec->bounds); - } - } - - void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen) - { - solutionClosed.resize(0); - solutionClosed.reserve(outrec_list_.size()); - if (solutionOpen) - { - solutionOpen->resize(0); - solutionOpen->reserve(outrec_list_.size()); - } - - for (OutRec* outrec : outrec_list_) - { - if (outrec->pts == nullptr) continue; - - Path64 path; - if (solutionOpen && outrec->is_open) - { - if (BuildPath64(outrec->pts, ReverseSolution, true, path)) - solutionOpen->emplace_back(std::move(path)); - } - else - { - //closed paths should always return a Positive orientation - if (BuildPath64(outrec->pts, ReverseSolution, false, path)) - solutionClosed.emplace_back(std::move(path)); - } - } - } - - void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths) - { - polytree.Clear(); - open_paths.resize(0); - if (has_open_paths_) - open_paths.reserve(outrec_list_.size()); - - for (OutRec* outrec : outrec_list_) - { - if (!outrec || !outrec->pts) continue; - if (outrec->is_open) - { - Path64 path; - if (BuildPath64(outrec->pts, ReverseSolution, true, path)) - open_paths.push_back(path); - continue; - } - - if (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) - continue; - if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); - outrec->owner = GetRealOutRec(outrec->owner); - if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); - - // swap the order when a child preceeds its owner - // (because owners must preceed children in polytrees) - if (outrec->owner && outrec->idx < outrec->owner->idx) - { - OutRec* tmp = outrec->owner; - outrec_list_[outrec->owner->idx] = outrec; - outrec_list_[outrec->idx] = tmp; - size_t tmp_idx = outrec->idx; - outrec->idx = tmp->idx; - tmp->idx = tmp_idx; - outrec = tmp; - outrec->owner = GetRealOutRec(outrec->owner); - BuildPath64(outrec->pts, ReverseSolution, false, outrec->path); - if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); - if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); - } - - PolyPath* owner_polypath; - if (outrec->owner && outrec->owner->polypath) - owner_polypath = outrec->owner->polypath; - else - owner_polypath = &polytree; - outrec->polypath = owner_polypath->AddChild(outrec->path); - } - } - - bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) - { - if (op->next == op || (!isOpen && op->next == op->prev)) return false; - path.resize(0); - Point64 lastPt; - OutPt* op2; - if (reverse) - { - lastPt = op->pt; - op2 = op->prev; - } - else - { - op = op->next; - lastPt = op->pt; - op2 = op->next; - } - path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); - - while (op2 != op) - { - if (op2->pt != lastPt) - { - lastPt = op2->pt; - path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); - } - if (reverse) - op2 = op2->prev; - else - op2 = op2->next; - } - return true; - } - - void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen) - { - solutionClosed.resize(0); - solutionClosed.reserve(outrec_list_.size()); - if (solutionOpen) - { - solutionOpen->resize(0); - solutionOpen->reserve(outrec_list_.size()); - } - - for (OutRec* outrec : outrec_list_) - { - if (outrec->pts == nullptr) continue; - - PathD path; - if (solutionOpen && outrec->is_open) - { - if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) - solutionOpen->emplace_back(std::move(path)); - } - else - { - //closed paths should always return a Positive orientation - if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_)) - solutionClosed.emplace_back(std::move(path)); - } - } - } - - void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths) - { - polytree.Clear(); - open_paths.resize(0); - if (has_open_paths_) - open_paths.reserve(outrec_list_.size()); - - for (OutRec* outrec : outrec_list_) - { - if (!outrec || !outrec->pts) continue; - if (outrec->is_open) - { - PathD path; - if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) - open_paths.push_back(path); - continue; - } - - if (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) - continue; - if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); - outrec->owner = GetRealOutRec(outrec->owner); - if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); - - // swap the order when a child preceeds its owner - // (because owners must preceed children in polytrees) - if (outrec->owner && outrec->idx < outrec->owner->idx) - { - OutRec* tmp = outrec->owner; - outrec_list_[outrec->owner->idx] = outrec; - outrec_list_[outrec->idx] = tmp; - size_t tmp_idx = outrec->idx; - outrec->idx = tmp->idx; - tmp->idx = tmp_idx; - outrec = tmp; - outrec->owner = GetRealOutRec(outrec->owner); - BuildPath64(outrec->pts, ReverseSolution, false, outrec->path); - if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); - if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); - } - - PolyPath* owner_polypath; - if (outrec->owner && outrec->owner->polypath) - owner_polypath = outrec->owner->polypath; - else - owner_polypath = &polytree; - outrec->polypath = owner_polypath->AddChild(outrec->path); - } - } + } + else if (old_e1_windcnt == 1 && old_e2_windcnt == 1) + { + resultOp = nullptr; + switch (cliptype_) + { + case ClipType::Union: + if (e1Wc2 <= 0 && e2Wc2 <= 0) + resultOp = AddLocalMinPoly(e1, e2, pt, false); + break; + case ClipType::Difference: + if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + { + resultOp = AddLocalMinPoly(e1, e2, pt, false); + } + break; + case ClipType::Xor: + resultOp = AddLocalMinPoly(e1, e2, pt, false); + break; + default: + if (e1Wc2 > 0 && e2Wc2 > 0) + resultOp = AddLocalMinPoly(e1, e2, pt, false); + break; + } +#ifdef USINGZ + if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt); +#endif + } + } + return resultOp; + } + + inline void ClipperBase::DeleteFromAEL(Active& e) + { + Active* prev = e.prev_in_ael; + Active* next = e.next_in_ael; + if (!prev && !next && (&e != actives_)) return; // already deleted + if (prev) + prev->next_in_ael = next; + else + actives_ = next; + if (next) next->prev_in_ael = prev; + delete& e; + } + + + inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) + { + Active* e = actives_; + sel_ = e; + while (e) + { + e->prev_in_sel = e->prev_in_ael; + 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 + else + e->curr_x = TopX(*e, top_y); + e = e->next_in_ael; + } + } + + bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees) + { + cliptype_ = ct; + fillrule_ = fillrule; + using_polytree_ = use_polytrees; + Reset(); + int64_t y; + if (ct == ClipType::None || !PopScanline(y)) return true; + + while (succeeded_) + { + InsertLocalMinimaIntoAEL(y); + Active* e; + while (PopHorz(e)) DoHorizontal(*e); + if (horz_seg_list_.size() > 0) + { + ConvertHorzSegsToJoins(); + horz_seg_list_.clear(); + } + bot_y_ = y; // bot_y_ == bottom of scanbeam + if (!PopScanline(y)) break; // y new top of scanbeam + DoIntersections(y); + DoTopOfScanbeam(y); + while (PopHorz(e)) DoHorizontal(*e); + } + if (succeeded_) ProcessHorzJoins(); + return succeeded_; + } + + inline void FixOutRecPts(OutRec* outrec) + { + OutPt* op = outrec->pts; + do { + op->outrec = outrec; + op = op->next; + } while (op != outrec->pts); + } + + inline Rect64 GetBounds(OutPt* op) + { + Rect64 result(op->pt.x, op->pt.y, op->pt.x, op->pt.y); + OutPt* op2 = op->next; + while (op2 != op) + { + if (op2->pt.x < result.left) result.left = op2->pt.x; + else if (op2->pt.x > result.right) result.right = op2->pt.x; + if (op2->pt.y < result.top) result.top = op2->pt.y; + else if (op2->pt.y > result.bottom) result.bottom = op2->pt.y; + op2 = op2->next; + } + return result; + } + + static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) + { + if (op == op->next || op->prev == op->next) + return PointInPolygonResult::IsOutside; + + OutPt* op2 = op; + do + { + if (op->pt.y != pt.y) break; + op = op->next; + } while (op != op2); + if (op->pt.y == pt.y) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = op->pt.y < pt.y, starting_above = is_above; + int val = 0; + op2 = op->next; + while (op2 != op) + { + if (is_above) + while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; + else + while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; + if (op2 == op) break; + + // must have touched or crossed the pt.Y horizonal + // and this must happen an even number of times + + if (op2->pt.y == pt.y) // touching the horizontal + { + if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && + (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) + return PointInPolygonResult::IsOn; + + op2 = op2->next; + if (op2 == op) break; + continue; + } + + if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); + // do nothing because + // we're only interested in edges crossing on the left + else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + op2 = op2->next; + } + + if (is_above != starting_above) + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + if (val == 0) return PointInPolygonResult::IsOutside; + else return PointInPolygonResult::IsInside; + } + + inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) + { + // we need to make some accommodation for rounding errors + // so we won't jump if the first vertex is found outside + int outside_cnt = 0; + OutPt* op = op1; + do + { + PointInPolygonResult result = PointInOpPolygon(op->pt, op2); + if (result == PointInPolygonResult::IsOutside) ++outside_cnt; + else if (result == PointInPolygonResult::IsInside) --outside_cnt; + op = op->next; + } while (op != op1 && std::abs(outside_cnt) < 2); + if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); + // since path1's location is still equivocal, check its midpoint + Point64 mp = GetBounds(op).MidPoint(); + return PointInOpPolygon(mp, op2) == PointInPolygonResult::IsInside; + } + + inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN) + { + if (opP->pt.x == opN->pt.x) return false; + if (opP->pt.x < opN->pt.x) + { + hs.left_op = opP; + hs.right_op = opN; + hs.left_to_right = true; + } + else + { + hs.left_op = opN; + hs.right_op = opP; + hs.left_to_right = false; + } + return true; + } + + inline bool UpdateHorzSegment(HorzSegment& hs) + { + OutPt* op = hs.left_op; + OutRec* outrec = GetRealOutRec(op->outrec); + bool outrecHasEdges = outrec->front_edge; + int64_t curr_y = op->pt.y; + OutPt* opP = op, * opN = op; + if (outrecHasEdges) + { + OutPt* opA = outrec->pts, * opZ = opA->next; + while (opP != opZ && opP->prev->pt.y == curr_y) + opP = opP->prev; + while (opN != opA && opN->next->pt.y == curr_y) + opN = opN->next; + } + else + { + while (opP->prev != opN && opP->prev->pt.y == curr_y) + opP = opP->prev; + while (opN->next != opP && opN->next->pt.y == curr_y) + opN = opN->next; + } + bool result = + SetHorzSegHeadingForward(hs, opP, opN) && + !hs.left_op->horz; + + if (result) + hs.left_op->horz = &hs; + else + hs.right_op = nullptr; // (for sorting) + return result; + } + + void ClipperBase::ConvertHorzSegsToJoins() + { + auto j = std::count_if(horz_seg_list_.begin(), + horz_seg_list_.end(), + [](HorzSegment& hs) { return UpdateHorzSegment(hs); }); + if (j < 2) return; + std::sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); + + HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2; + HorzSegmentList::iterator hs_end = hs1 +j; + HorzSegmentList::iterator hs_end1 = hs_end - 1; + + for (; hs1 != hs_end1; ++hs1) + { + for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2) + { + if (hs2->left_op->pt.x >= hs1->right_op->pt.x) break; + if (hs2->left_to_right == hs1->left_to_right || + (hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue; + int64_t curr_y = hs1->left_op->pt.y; + if (hs1->left_to_right) + { + while (hs1->left_op->next->pt.y == curr_y && + hs1->left_op->next->pt.x <= hs2->left_op->pt.x) + hs1->left_op = hs1->left_op->next; + while (hs2->left_op->prev->pt.y == curr_y && + hs2->left_op->prev->pt.x <= hs1->left_op->pt.x) + hs2->left_op = hs2->left_op->prev; + HorzJoin join = HorzJoin( + DuplicateOp(hs1->left_op, true), + DuplicateOp(hs2->left_op, false)); + horz_join_list_.push_back(join); + } + else + { + while (hs1->left_op->prev->pt.y == curr_y && + hs1->left_op->prev->pt.x <= hs2->left_op->pt.x) + hs1->left_op = hs1->left_op->prev; + while (hs2->left_op->next->pt.y == curr_y && + hs2->left_op->next->pt.x <= hs1->left_op->pt.x) + hs2->left_op = hs2->left_op->next; + HorzJoin join = HorzJoin( + DuplicateOp(hs2->left_op, true), + DuplicateOp(hs1->left_op, false)); + horz_join_list_.push_back(join); + } + } + } + } + + void ClipperBase::ProcessHorzJoins() + { + for (const HorzJoin& j : horz_join_list_) + { + OutRec* or1 = GetRealOutRec(j.op1->outrec); + OutRec* or2 = GetRealOutRec(j.op2->outrec); + + OutPt* op1b = j.op1->next; + OutPt* op2b = j.op2->prev; + j.op1->next = j.op2; + j.op2->prev = j.op1; + op1b->prev = op2b; + op2b->next = op1b; + + if (or1 == or2) + { + or2 = new OutRec(); + or2->pts = op1b; + FixOutRecPts(or2); + if (or1->pts->outrec == or2) + { + or1->pts = j.op1; + or1->pts->outrec = or1; + } + + if (using_polytree_) + { + if (Path1InsidePath2(or2->pts, or1->pts)) + SetOwner(or2, or1); + else if (Path1InsidePath2(or1->pts, or2->pts)) + SetOwner(or1, or2); + else + or2->owner = or1; + } + else + or2->owner = or1; + + outrec_list_.push_back(or2); + } + else + { + or2->pts = nullptr; + if (using_polytree_) + SetOwner(or2, or1); + else + or2->owner = or1; + } + } + } + + void ClipperBase::DoIntersections(const int64_t top_y) + { + if (BuildIntersectList(top_y)) + { + ProcessIntersectList(); + intersect_nodes_.clear(); + } + } + + void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y) + { + Point64 ip; + if (!GetIntersectPoint(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 + //point either below or above the scanbeam, so check and correct ... + if (ip.y > bot_y_ || ip.y < top_y) + { + double abs_dx1 = std::fabs(e1.dx); + double abs_dx2 = std::fabs(e2.dx); + if (abs_dx1 > 100 && abs_dx2 > 100) + { + if (abs_dx1 > abs_dx2) + ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); + else + ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); + } + else if (abs_dx1 > 100) + ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); + else if (abs_dx2 > 100) + ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); + else + { + if (ip.y < top_y) ip.y = top_y; + else ip.y = bot_y_; + if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y); + else ip.x = TopX(e2, ip.y); + } + } + intersect_nodes_.push_back(IntersectNode(&e1, &e2, ip)); + } + + bool ClipperBase::BuildIntersectList(const int64_t top_y) + { + if (!actives_ || !actives_->next_in_ael) return false; + + //Calculate edge positions at the top of the current scanbeam, and from this + //we will determine the intersections required to reach these new positions. + AdjustCurrXAndCopyToSEL(top_y); + //Find all edge intersections in the current scanbeam using a stable merge + //sort that ensures only adjacent edges are intersecting. Intersect info is + //stored in FIntersectList ready to be processed in ProcessIntersectList. + //Re merge sorts see https://stackoverflow.com/a/46319131/359538 + + Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp; + + while (left && left->jump) + { + Active* prev_base = nullptr; + while (left && left->jump) + { + curr_base = left; + right = left->jump; + l_end = right; + r_end = right->jump; + left->jump = r_end; + while (left != l_end && right != r_end) + { + if (right->curr_x < left->curr_x) + { + tmp = right->prev_in_sel; + for (; ; ) + { + AddNewIntersectNode(*tmp, *right, top_y); + if (tmp == left) break; + tmp = tmp->prev_in_sel; + } + + tmp = right; + right = ExtractFromSEL(tmp); + l_end = right; + Insert1Before2InSEL(tmp, left); + if (left == curr_base) + { + curr_base = tmp; + curr_base->jump = r_end; + if (!prev_base) sel_ = curr_base; + else prev_base->jump = curr_base; + } + } + else left = left->next_in_sel; + } + prev_base = curr_base; + left = r_end; + } + left = sel_; + } + return intersect_nodes_.size() > 0; + } + + void ClipperBase::ProcessIntersectList() + { + //We now have a list of intersections required so that edges will be + //correctly positioned at the top of the scanbeam. However, it's important + //that edge intersections are processed from the bottom up, but it's also + //crucial that intersections only occur between adjacent edges. + + //First we do a quicksort so intersections proceed in a bottom up order ... + std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort); + //Now as we process these intersections, we must sometimes adjust the order + //to ensure that intersecting edges are always adjacent ... + + IntersectNodeList::iterator node_iter, node_iter2; + for (node_iter = intersect_nodes_.begin(); + node_iter != intersect_nodes_.end(); ++node_iter) + { + if (!EdgesAdjacentInAEL(*node_iter)) + { + node_iter2 = node_iter + 1; + while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2; + std::swap(*node_iter, *node_iter2); + } + + IntersectNode& node = *node_iter; + IntersectEdges(*node.edge1, *node.edge2, node.pt); + SwapPositionsInAEL(*node.edge1, *node.edge2); + + node.edge1->curr_x = node.pt.x; + node.edge2->curr_x = node.pt.x; + CheckJoinLeft(*node.edge2, node.pt, true); + CheckJoinRight(*node.edge1, node.pt, true); + } + } + + void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2) + { + //preconditon: e1 must be immediately to the left of e2 + Active* next = e2.next_in_ael; + if (next) next->prev_in_ael = &e1; + Active* prev = e1.prev_in_ael; + if (prev) prev->next_in_ael = &e2; + e2.prev_in_ael = prev; + e2.next_in_ael = &e1; + e1.prev_in_ael = &e2; + e1.next_in_ael = next; + if (!e2.prev_in_ael) actives_ = &e2; + } + + inline OutPt* GetLastOp(const Active& hot_edge) + { + OutRec* outrec = hot_edge.outrec; + OutPt* result = outrec->pts; + if (&hot_edge != outrec->front_edge) + result = result->next; + return result; + } + + void ClipperBase::AddTrialHorzJoin(OutPt* op) + { + if (op->outrec->is_open) return; + horz_seg_list_.push_back(HorzSegment(op)); + } + + 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) + { + //the horizontal edge is going nowhere ... + horz_left = horz.curr_x; + horz_right = horz.curr_x; + Active* e = horz.next_in_ael; + while (e && e->vertex_top != max_vertex) e = e->next_in_ael; + return e != nullptr; + } + else if (horz.curr_x < horz.top.x) + { + horz_left = horz.curr_x; + horz_right = horz.top.x; + return true; + } + else + { + horz_left = horz.top.x; + horz_right = horz.curr_x; + return false; // right to left + } + } + + inline bool HorzIsSpike(const Active& horzEdge) + { + Point64 nextPt = NextVertex(horzEdge)->pt; + return (nextPt.y == horzEdge.bot.y) && + (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x); + } + + inline void TrimHorz(Active& horzEdge, bool preserveCollinear) + { + bool wasTrimmed = false; + Point64 pt = NextVertex(horzEdge)->pt; + while (pt.y == horzEdge.top.y) + { + //always trim 180 deg. spikes (in closed paths) + //but otherwise break if preserveCollinear = true + if (preserveCollinear && + ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x))) + break; + + horzEdge.vertex_top = NextVertex(horzEdge); + horzEdge.top = pt; + wasTrimmed = true; + if (IsMaxima(horzEdge)) break; + pt = NextVertex(horzEdge)->pt; + } + + if (wasTrimmed) SetDx(horzEdge); // +/-infinity + } + + void ClipperBase::DoHorizontal(Active& horz) + /******************************************************************************* + * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * + * bottom of a scanbeam) are processed as if layered.The order in which HEs * + * are processed doesn't matter. HEs intersect with the bottom vertices of * + * other HEs[#] and with non-horizontal edges [*]. Once these intersections * + * are completed, intermediate HEs are 'promoted' to the next edge in their * + * bounds, and they in turn may be intersected[%] by other HEs. * + * * + * eg: 3 horizontals at a scanline: / | / / * + * | / | (HE3)o ========%========== o * + * o ======= o(HE2) / | / / * + * o ============#=========*======*========#=========o (HE1) * + * / | / | / * + *******************************************************************************/ + { + Point64 pt; + bool horzIsOpen = IsOpen(horz); + int64_t y = horz.bot.y; + Vertex* vertex_max; + if (horzIsOpen) + vertex_max = GetCurrYMaximaVertex_Open(horz); + else + vertex_max = GetCurrYMaximaVertex(horz); + + // remove 180 deg.spikes and also simplify + // consecutive horizontals when PreserveCollinear = true + if (vertex_max && !horzIsOpen && vertex_max != horz.vertex_top) + TrimHorz(horz, PreserveCollinear); + + int64_t horz_left, horz_right; + bool is_left_to_right = + ResetHorzDirection(horz, vertex_max, horz_left, horz_right); + + if (IsHotEdge(horz)) + { +#ifdef USINGZ + OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z)); +#else + OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y)); +#endif + AddTrialHorzJoin(op); + } + OutRec* currHorzOutrec = horz.outrec; + + while (true) // loop through consec. horizontal edges + { + Active* e; + if (is_left_to_right) e = horz.next_in_ael; + else e = horz.prev_in_ael; + + while (e) + { + if (e->vertex_top == vertex_max) + { + if (IsHotEdge(horz) && IsJoined(*e)) + Split(*e, e->top); + + if (IsHotEdge(horz)) + { + while (horz.vertex_top != vertex_max) + { + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + } + if (is_left_to_right) + AddLocalMaxPoly(horz, *e, horz.top); + else + AddLocalMaxPoly(*e, horz, horz.top); + } + DeleteFromAEL(*e); + DeleteFromAEL(horz); + return; + } + + //if horzEdge is a maxima, keep going until we reach + //its maxima pair, otherwise check for break conditions + if (vertex_max != horz.vertex_top || IsOpenEnd(horz)) + { + //otherwise stop when 'ae' is beyond the end of the horizontal line + if ((is_left_to_right && e->curr_x > horz_right) || + (!is_left_to_right && e->curr_x < horz_left)) break; + + if (e->curr_x == horz.top.x && !IsHorizontal(*e)) + { + pt = NextVertex(horz)->pt; + if (is_left_to_right) + { + //with open paths we'll only break once past horz's end + if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) + { + if (TopX(*e, pt.y) > pt.x) break; + } + //otherwise we'll only break when horz's outslope is greater than e's + else if (TopX(*e, pt.y) >= pt.x) break; + } + else + { + if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) + { + if (TopX(*e, pt.y) < pt.x) break; + } + else if (TopX(*e, pt.y) <= pt.x) break; + } + } + } + + pt = Point64(e->curr_x, horz.bot.y); + if (is_left_to_right) + { + IntersectEdges(horz, *e, pt); + SwapPositionsInAEL(horz, *e); + horz.curr_x = e->curr_x; + e = horz.next_in_ael; + } + else + { + IntersectEdges(*e, horz, pt); + SwapPositionsInAEL(*e, horz); + horz.curr_x = e->curr_x; + e = horz.prev_in_ael; + } + + if (horz.outrec && horz.outrec != currHorzOutrec) + { + currHorzOutrec = horz.outrec; + //nb: The outrec containining the op returned by IntersectEdges + //above may no longer be associated with horzEdge. + AddTrialHorzJoin(GetLastOp(horz)); + } + } + + //check if we've finished with (consecutive) horizontals ... + if (horzIsOpen && IsOpenEnd(horz)) // ie open at top + { + if (IsHotEdge(horz)) + { + AddOutPt(horz, horz.top); + if (IsFront(horz)) + horz.outrec->front_edge = nullptr; + else + horz.outrec->back_edge = nullptr; + horz.outrec = nullptr; + } + DeleteFromAEL(horz); + return; + } + else if (NextVertex(horz)->pt.y != horz.top.y) + break; + + //still more horizontals in bound to process ... + if (IsHotEdge(horz)) + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + + if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz)) + TrimHorz(horz, true); + + is_left_to_right = + ResetHorzDirection(horz, vertex_max, horz_left, horz_right); + } + + if (IsHotEdge(horz)) AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz. + } + + void ClipperBase::DoTopOfScanbeam(const int64_t y) + { + sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below) + Active* e = actives_; + while (e) + { + //nb: 'e' will never be horizontal here + if (e->top.y == y) + { + e->curr_x = e->top.x; + if (IsMaxima(*e)) + { + e = DoMaxima(*e); // TOP OF BOUND (MAXIMA) + continue; + } + else + { + //INTERMEDIATE VERTEX ... + if (IsHotEdge(*e)) AddOutPt(*e, e->top); + UpdateEdgeIntoAEL(e); + if (IsHorizontal(*e)) + PushHorz(*e); // horizontals are processed later + } + } + else // i.e. not the top of the edge + e->curr_x = TopX(*e, y); + + e = e->next_in_ael; + } + } + + + Active* ClipperBase::DoMaxima(Active& e) + { + Active* next_e, * prev_e, * max_pair; + prev_e = e.prev_in_ael; + next_e = e.next_in_ael; + if (IsOpenEnd(e)) + { + if (IsHotEdge(e)) AddOutPt(e, e.top); + if (!IsHorizontal(e)) + { + if (IsHotEdge(e)) + { + if (IsFront(e)) + e.outrec->front_edge = nullptr; + else + e.outrec->back_edge = nullptr; + e.outrec = nullptr; + } + DeleteFromAEL(e); + } + return next_e; + } + + max_pair = GetMaximaPair(e); + if (!max_pair) return next_e; // eMaxPair is horizontal + + if (IsJoined(e)) Split(e, e.top); + if (IsJoined(*max_pair)) Split(*max_pair, max_pair->top); + + //only non-horizontal maxima here. + //process any edges between maxima pair ... + while (next_e != max_pair) + { + IntersectEdges(e, *next_e, e.top); + SwapPositionsInAEL(e, *next_e); + next_e = e.next_in_ael; + } + + if (IsOpen(e)) + { + if (IsHotEdge(e)) + AddLocalMaxPoly(e, *max_pair, e.top); + DeleteFromAEL(*max_pair); + DeleteFromAEL(e); + return (prev_e ? prev_e->next_in_ael : actives_); + } + + // e.next_in_ael== max_pair ... + if (IsHotEdge(e)) + AddLocalMaxPoly(e, *max_pair, e.top); + + DeleteFromAEL(e); + DeleteFromAEL(*max_pair); + return (prev_e ? prev_e->next_in_ael : actives_); + } + + void ClipperBase::Split(Active& e, const Point64& pt) + { + if (e.join_with == JoinWith::Right) + { + e.join_with = JoinWith::None; + e.next_in_ael->join_with = JoinWith::None; + AddLocalMinPoly(e, *e.next_in_ael, pt, true); + } + else + { + e.join_with = JoinWith::None; + e.prev_in_ael->join_with = JoinWith::None; + AddLocalMinPoly(*e.prev_in_ael, e, pt, true); + } + } + + 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) || + pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) // avoid trivial joins + return; + + if (check_curr_x) + { + if (DistanceFromLineSqrd(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 (e.outrec->idx == prev->outrec->idx) + AddLocalMaxPoly(*prev, e, pt); + else if (e.outrec->idx < prev->outrec->idx) + JoinOutrecPaths(e, *prev); + else + JoinOutrecPaths(*prev, e); + prev->join_with = JoinWith::Right; + e.join_with = JoinWith::Left; + } + + 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) || + pt.y < e.top.y +2 || pt.y < next->top.y +2) // avoids trivial joins + return; + + if (check_curr_x) + { + if (DistanceFromLineSqrd(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 (e.outrec->idx == next->outrec->idx) + AddLocalMaxPoly(e, *next, pt); + else if (e.outrec->idx < next->outrec->idx) + JoinOutrecPaths(e, *next); + else + JoinOutrecPaths(*next, e); + e.join_with = JoinWith::Right; + next->join_with = JoinWith::Left; + } + + inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2) + { + OutRec* outrec = GetRealOutRec(op->outrec); + op2 = op; + if (outrec->front_edge) + { + while (op->prev != outrec->pts && + op->prev->pt.y == op->pt.y) op = op->prev; + while (op2 != outrec->pts && + op2->next->pt.y == op2->pt.y) op2 = op2->next; + return op2 != op; + } + else + { + while (op->prev != op2 && op->prev->pt.y == op->pt.y) + op = op->prev; + while (op2->next != op && op2->next->pt.y == op2->pt.y) + op2 = op2->next; + return op2 != op && op2->next != op; + } + } + + bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path) + { + if (!op || op->next == op || (!isOpen && op->next == op->prev)) + return false; + + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } + path.push_back(lastPt); + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; + path.push_back(lastPt); + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + + if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; + else return true; + } + + bool ClipperBase::CheckBounds(OutRec* outrec) + { + if (!outrec->pts) return false; + if (!outrec->bounds.IsEmpty()) return true; + CleanCollinear(outrec); + if (!outrec->pts || + !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) + return false; + outrec->bounds = GetBounds(outrec->path); + return true; + } + + void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath) + { + // pre-condition: outrec will have valid bounds + // post-condition: if a valid path, outrec will have a polypath + + if (outrec->polypath || outrec->bounds.IsEmpty()) return; + + while (outrec->owner && + (!outrec->owner->pts || !CheckBounds(outrec->owner))) + outrec->owner = outrec->owner->owner; + + if (outrec->owner && !outrec->owner->polypath) + RecursiveCheckOwners(outrec->owner, polypath); + + while (outrec->owner) + if (outrec->owner->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, outrec->owner->pts)) + break; // found - owner contain outrec! + else + outrec->owner = outrec->owner->owner; + + if (outrec->owner) + outrec->polypath = outrec->owner->polypath->AddChild(outrec->path); + else + outrec->polypath = polypath->AddChild(outrec->path); + } + + void ClipperBase::DeepCheckOwners(OutRec* outrec, PolyPath* polypath) + { + RecursiveCheckOwners(outrec, polypath); + + while (outrec->owner && outrec->owner->splits) + { + OutRec* split = nullptr; + for (auto s : *outrec->owner->splits) + { + split = GetRealOutRec(s); + if (split && split != outrec && + split != outrec->owner && CheckBounds(split) && + split->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, split->pts)) + { + RecursiveCheckOwners(split, polypath); + outrec->owner = split; //found in split + break; // inner 'for' loop + } + else + split = nullptr; + } + if (!split) break; + } + } + + void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + // nb: outrec_list_.size() may change in the following + // while loop because polygons may be split during + // calls to CleanCollinear which calls FixSelfIntersects + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (outrec->pts == nullptr) continue; + + Path64 path; + if (solutionOpen && outrec->is_open) + { + if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + // nb: CleanCollinear can add to outrec_list_ + CleanCollinear(outrec); + //closed paths should always return a Positive orientation + if (BuildPath64(outrec->pts, ReverseSolution, false, path)) + solutionClosed.emplace_back(std::move(path)); + } + } + } + + void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths) + { + polytree.Clear(); + 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) + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + Path64 path; + if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + open_paths.push_back(path); + continue; + } + + if (CheckBounds(outrec)) + DeepCheckOwners(outrec, &polytree); + } + } + + bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) + { + if (!op || op->next == op || (!isOpen && op->next == op->prev)) + return false; + + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } +#ifdef USINGZ + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z)); +#else + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); +#endif + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; +#ifdef USINGZ + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z)); +#else + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); +#endif + + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; + return true; + } + + void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + // outrec_list_.size() is not static here because + // CleanCollinear below can indirectly add additional + // OutRec (via FixOutRecPts) + for (std::size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (outrec->pts == nullptr) continue; + + PathD path; + if (solutionOpen && outrec->is_open) + { + if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + CleanCollinear(outrec); + //closed paths should always return a Positive orientation + if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_)) + solutionClosed.emplace_back(std::move(path)); + } + } + } + + void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths) + { + polytree.Clear(); + open_paths.resize(0); + if (has_open_paths_) + open_paths.reserve(outrec_list_.size()); + + for (OutRec* outrec : outrec_list_) + { + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + PathD path; + if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + open_paths.push_back(path); + continue; + } + + if (CheckBounds(outrec)) + DeepCheckOwners(outrec, &polytree); + } + } } // namespace clipper2lib diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp index a19a6ff459..78cd82376a 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 October 2022 * +* Date : 22 March 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -20,20 +20,38 @@ const double floating_point_tolerance = 1e-12; // Miscellaneous methods //------------------------------------------------------------------------------ -Paths64::size_type GetLowestPolygonIdx(const Paths64& paths) +void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx) { - Paths64::size_type result = 0; - Point64 lp = Point64(static_cast(0), - std::numeric_limits::min()); - - for (Paths64::size_type i = 0 ; i < paths.size(); ++i) + idx = -1; + r = MaxInvalidRect64; + int64_t lpx = 0; + for (int i = 0; i < static_cast(paths.size()); ++i) for (const Point64& p : paths[i]) - { - if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue; - result = i; - lp = p; - } - return result; + { + if (p.y >= r.bottom) + { + if (p.y > r.bottom || p.x < lpx) + { + idx = i; + lpx = p.x; + r.bottom = p.y; + } + } + else if (p.y < r.top) r.top = p.y; + if (p.x > r.right) r.right = p.x; + else if (p.x < r.left) r.left = p.x; + } + //if (idx < 0) r = Rect64(0, 0, 0, 0); + //if (r.top == INT64_MIN) r.bottom = r.top; + //if (r.left == INT64_MIN) r.left = r.right; +} + +bool IsSafeOffset(const Rect64& r, double abs_delta) +{ + return r.left > min_coord + abs_delta && + r.right < max_coord - abs_delta && + r.top > min_coord + abs_delta && + r.bottom < max_coord - abs_delta; } PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) @@ -80,12 +98,32 @@ inline bool IsClosedPath(EndType et) inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta) { +#ifdef USINGZ + return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z); +#else return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta); +#endif } inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta) { +#ifdef USINGZ + return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z); +#else return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta); +#endif +} + +inline void NegatePath(PathD& path) +{ + for (PointD& pt : path) + { + pt.x = -pt.x; + pt.y = -pt.y; +#ifdef USINGZ + pt.z = pt.z; +#endif + } } //------------------------------------------------------------------------------ @@ -105,19 +143,6 @@ void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_) groups_.push_back(Group(paths, jt_, et_)); } -void ClipperOffset::AddPath(const Clipper2Lib::PathD& path, JoinType jt_, EndType et_) -{ - PathsD paths; - paths.push_back(path); - AddPaths(paths, jt_, et_); -} - -void ClipperOffset::AddPaths(const PathsD& paths, JoinType jt_, EndType et_) -{ - if (paths.size() == 0) return; - groups_.push_back(Group(PathsDToPaths64(paths), jt_, et_)); -} - void ClipperOffset::BuildNormals(const Path64& path) { norms.clear(); @@ -131,12 +156,20 @@ void ClipperOffset::BuildNormals(const Path64& path) 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, @@ -190,47 +223,68 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t { 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 //get the second intersect point through reflecion - group.path_.push_back(Point64(ReflectPoint(pt, ptQ))); - group.path_.push_back(Point64(pt)); + group.path.push_back(Point64(ReflectPoint(pt, ptQ))); + group.path.push_back(Point64(pt)); } else { PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_); PointD pt = IntersectPoint(pt1, pt2, pt3, pt4); - group.path_.push_back(Point64(pt)); +#ifdef USINGZ + pt.z = ptQ.z; +#endif + group.path.push_back(Point64(pt)); //get the second intersect point through reflecion - group.path_.push_back(Point64(ReflectPoint(pt, ptQ))); + group.path.push_back(Point64(ReflectPoint(pt, ptQ))); } } void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a) { double q = group_delta_ / (cos_a + 1); - group.path_.push_back(Point64( +#ifdef USINGZ + group.path.push_back(Point64( + path[j].x + (norms[k].x + norms[j].x) * q, + path[j].y + (norms[k].y + norms[j].y) * q, + path[j].z)); +#else + group.path.push_back(Point64( path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q)); +#endif } void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle) { - //even though angle may be negative this is a convex join Point64 pt = path[j]; - int steps = static_cast(std::ceil(steps_per_rad_ * std::abs(angle))); - double step_sin = std::sin(angle / steps); - double step_cos = std::cos(angle / steps); - - PointD pt2 = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); - if (j == k) pt2.Negate(); + PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); - group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y)); - for (int i = 0; i < steps; i++) + if (j == k) offsetVec.Negate(); +#ifdef USINGZ + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); +#else + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); +#endif + if (angle > -PI + 0.01) // avoid 180deg concave { - pt2 = PointD(pt2.x * step_cos - step_sin * pt2.y, - pt2.x * step_sin + pt2.y * step_cos); - group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y)); + int steps = static_cast(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456 + for (int i = 1; i < steps; ++i) // ie 1 less than steps + { + offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, + offsetVec.x * step_sin_ + offsetVec.y * step_cos_); +#ifdef USINGZ + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); +#else + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); +#endif + + } } - group.path_.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) @@ -248,72 +302,78 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) if (sin_a > 1.0) sin_a = 1.0; else if (sin_a < -1.0) sin_a = -1.0; - bool almostNoAngle = AlmostZero(sin_a) && cos_a > 0; - // when there's almost no angle of deviation or it's concave - if (almostNoAngle || (sin_a * group_delta_ < 0)) + if (cos_a > 0.99) // almost straight - less than 8 degrees { - Point64 p1 = Point64( - path[j].x + norms[k].x * group_delta_, - path[j].y + norms[k].y * group_delta_); - Point64 p2 = Point64( - path[j].x + norms[j].x * group_delta_, - path[j].y + norms[j].y * group_delta_); - group.path_.push_back(p1); - if (p1 != p2) - { - // when concave add an extra vertex to ensure neat clipping - if (!almostNoAngle) group.path_.push_back(path[j]); - group.path_.push_back(p2); - } + group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); + if (cos_a < 0.9998) // greater than 1 degree (#424) + group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); // (#418) } - else // it's convex + else if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) { - if (join_type_ == JoinType::Round) - DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); - else if (join_type_ == JoinType::Miter) - { - // miter unless the angle is so acute the miter would exceeds ML - if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); - else DoSquare(group, path, j, k); - } - // don't bother squaring angles that deviate < ~20 degrees because - // squaring will be indistinguishable from mitering and just be a lot slower - else if (cos_a > 0.9) - DoMiter(group, path, j, k, cos_a); - else - DoSquare(group, path, j, k); + // is concave + group.path.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 + group.path.push_back(path[j]); // (#405) + group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + } + else if (join_type_ == JoinType::Round) + DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); + else if (join_type_ == JoinType::Miter) + { + // miter unless the angle is so acute the miter would exceeds ML + if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); + else DoSquare(group, path, j, k); } + // don't bother squaring angles that deviate < ~20 degrees because + // squaring will be indistinguishable from mitering and just be a lot slower + else if (cos_a > 0.9) + DoMiter(group, path, j, k, cos_a); + else + DoSquare(group, path, j, k); + k = j; } void ClipperOffset::OffsetPolygon(Group& group, Path64& path) { - group.path_.clear(); for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i) OffsetPoint(group, path, i, j); - group.paths_out_.push_back(group.path_); + group.paths_out.push_back(group.path); } void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) { OffsetPolygon(group, path); std::reverse(path.begin(), path.end()); - BuildNormals(path); + + //rebuild normals // BuildNormals(path); + std::reverse(norms.begin(), norms.end()); + norms.push_back(norms[0]); + norms.erase(norms.begin()); + NegatePath(norms); + + group.path.clear(); OffsetPolygon(group, path); } -void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type) +void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) { - group.path_.clear(); - // do the line start cap - switch (end_type) + switch (end_type_) { case EndType::Butt: - group.path_.push_back(Point64( +#ifdef USINGZ + group.path.push_back(Point64( + path[0].x - norms[0].x * group_delta_, + path[0].y - norms[0].y * group_delta_, + path[0].z)); +#else + group.path.push_back(Point64( path[0].x - norms[0].x * group_delta_, path[0].y - norms[0].y * group_delta_)); - group.path_.push_back(GetPerpendic(path[0], norms[0], group_delta_)); +#endif + group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_)); break; case EndType::Round: DoRound(group, path, 0,0, PI); @@ -335,13 +395,20 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type) norms[0] = norms[highI]; // do the line end cap - switch (end_type) + switch (end_type_) { case EndType::Butt: - group.path_.push_back(Point64( +#ifdef USINGZ + group.path.push_back(Point64( + path[highI].x - norms[highI].x * group_delta_, + path[highI].y - norms[highI].y * group_delta_, + path[highI].z)); +#else + group.path.push_back(Point64( path[highI].x - norms[highI].x * group_delta_, path[highI].y - norms[highI].y * group_delta_)); - group.path_.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); +#endif + group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); break; case EndType::Round: DoRound(group, path, highI, highI, PI); @@ -353,133 +420,199 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type) for (size_t i = highI, k = 0; i > 0; --i) OffsetPoint(group, path, i, k); - group.paths_out_.push_back(group.path_); + group.paths_out.push_back(group.path); } -void ClipperOffset::DoGroupOffset(Group& group, double delta) +void ClipperOffset::DoGroupOffset(Group& group) { - if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5; - bool isClosedPaths = IsClosedPath(group.end_type_); + Rect64 r; + int idx = -1; + //the lowermost polygon must be an outer polygon. So we can use that as the + //designated orientation for outer polygons (needed for tidy-up clipping) + GetBoundsAndLowestPolyIdx(group.paths_in, r, idx); + if (idx < 0) return; - if (isClosedPaths) + if (group.end_type == EndType::Polygon) { - //the lowermost polygon must be an outer polygon. So we can use that as the - //designated orientation for outer polygons (needed for tidy-up clipping) - Paths64::size_type lowestIdx = GetLowestPolygonIdx(group.paths_in_); - // nb: don't use the default orientation here ... - double area = Area(group.paths_in_[lowestIdx]); - if (area == 0) return; - group.is_reversed_ = (area < 0); - if (group.is_reversed_) delta = -delta; + double area = Area(group.paths_in[idx]); + //if (area == 0) return; // probably unhelpful (#430) + group.is_reversed = (area < 0); + if (group.is_reversed) group_delta_ = -delta_; + else group_delta_ = delta_; } else - group.is_reversed_ = false; - - group_delta_ = delta; - abs_group_delta_ = std::abs(group_delta_); - join_type_ = group.join_type_; - - double arcTol = (arc_tolerance_ > floating_point_tolerance ? arc_tolerance_ - : std::log10(2 + abs_group_delta_) * default_arc_tolerance); // empirically derived - -//calculate a sensible number of steps (for 360 deg for the given offset - if (group.join_type_ == JoinType::Round || group.end_type_ == EndType::Round) { - steps_per_rad_ = PI / std::acos(1 - arcTol / abs_group_delta_) / (PI *2); + group.is_reversed = false; + group_delta_ = std::abs(delta_) * 0.5; + } + abs_group_delta_ = std::fabs(group_delta_); + + // do range checking + if (!IsSafeOffset(r, abs_group_delta_)) + { + DoError(range_error_i); + error_code_ |= range_error_i; + return; } - bool is_closed_path = IsClosedPath(group.end_type_); - Paths64::const_iterator path_iter; - for(path_iter = group.paths_in_.cbegin(); path_iter != group.paths_in_.cend(); ++path_iter) - { - Path64 path = StripDuplicates(*path_iter, is_closed_path); - Path64::size_type cnt = path.size(); - if (cnt == 0) continue; + join_type_ = group.join_type; + end_type_ = group.end_type; + //calculate a sensible number of steps (for 360 deg for the given offset + if (group.join_type == JoinType::Round || group.end_type == EndType::Round) + { + // 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 + double arcTol = (arc_tolerance_ > floating_point_tolerance ? + std::min(abs_group_delta_, arc_tolerance_) : + std::log10(2 + abs_group_delta_) * default_arc_tolerance); + double steps_per_360 = PI / std::acos(1 - arcTol / abs_group_delta_); + if (steps_per_360 > abs_group_delta_ * PI) + steps_per_360 = abs_group_delta_ * PI; //ie avoids excessive precision + + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 *PI); + } + + bool is_joined = + (end_type_ == EndType::Polygon) || + (end_type_ == EndType::Joined); + Paths64::const_iterator path_iter; + for(path_iter = group.paths_in.cbegin(); path_iter != group.paths_in.cend(); ++path_iter) + { + Path64 path = StripDuplicates(*path_iter, is_joined); + Path64::size_type cnt = path.size(); + if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) + continue; + + group.path.clear(); if (cnt == 1) // single point - only valid with open paths { - group.path_ = Path64(); + if (group_delta_ < 1) continue; //single vertex so build a circle or square ... - if (group.join_type_ == JoinType::Round) + if (group.join_type == JoinType::Round) { double radius = abs_group_delta_; - group.path_ = Ellipse(path[0], radius, radius); + group.path = Ellipse(path[0], radius, radius); +#ifdef USINGZ + for (auto& p : group.path) p.z = path[0].z; +#endif } else { int d = (int)std::ceil(abs_group_delta_); - Rect64 r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); - group.path_ = r.AsPath(); + r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); + group.path = r.AsPath(); +#ifdef USINGZ + for (auto& p : group.path) p.z = path[0].z; +#endif } - group.paths_out_.push_back(group.path_); + group.paths_out.push_back(group.path); } else { + if ((cnt == 2) && (group.end_type == EndType::Joined)) + { + if (group.join_type == JoinType::Round) + end_type_ = EndType::Round; + else + end_type_ = EndType::Square; + } + BuildNormals(path); - if (group.end_type_ == EndType::Polygon) OffsetPolygon(group, path); - else if (group.end_type_ == EndType::Joined) OffsetOpenJoined(group, path); - else OffsetOpenPath(group, path, group.end_type_); + if (end_type_ == EndType::Polygon) OffsetPolygon(group, path); + else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path); + else OffsetOpenPath(group, path); } } - - if (!merge_groups_) - { - //clean up self-intersections ... - Clipper64 c; - c.PreserveCollinear = false; - //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != group.is_reversed_; - c.AddSubject(group.paths_out_); - if (group.is_reversed_) - c.Execute(ClipType::Union, FillRule::Negative, group.paths_out_); - else - c.Execute(ClipType::Union, FillRule::Positive, group.paths_out_); - } - - solution.reserve(solution.size() + group.paths_out_.size()); - copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution)); - group.paths_out_.clear(); + solution.reserve(solution.size() + group.paths_out.size()); + copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution)); + group.paths_out.clear(); } -Paths64 ClipperOffset::Execute(double delta) +void ClipperOffset::ExecuteInternal(double delta) { + error_code_ = 0; solution.clear(); - if (std::abs(delta) < default_arc_tolerance) + if (groups_.size() == 0) return; + + if (std::abs(delta) < 0.5) { for (const Group& group : groups_) { - solution.reserve(solution.size() + group.paths_in_.size()); - copy(group.paths_in_.begin(), group.paths_in_.end(), back_inserter(solution)); + solution.reserve(solution.size() + group.paths_in.size()); + copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution)); } - return solution; - } - - temp_lim_ = (miter_limit_ <= 1) ? - 2.0 : - 2.0 / (miter_limit_ * miter_limit_); - - std::vector::iterator groups_iter; - for (groups_iter = groups_.begin(); - groups_iter != groups_.end(); ++groups_iter) + } + else { - DoGroupOffset(*groups_iter, delta); - } + temp_lim_ = (miter_limit_ <= 1) ? + 2.0 : + 2.0 / (miter_limit_ * miter_limit_); - if (merge_groups_ && groups_.size() > 0) - { - //clean up self-intersections ... - Clipper64 c; - c.PreserveCollinear = false; - //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_; - - c.AddSubject(solution); - if (groups_[0].is_reversed_) - c.Execute(ClipType::Union, FillRule::Negative, solution); - else - c.Execute(ClipType::Union, FillRule::Positive, solution); + delta_ = delta; + std::vector::iterator git; + for (git = groups_.begin(); git != groups_.end(); ++git) + { + DoGroupOffset(*git); + if (!error_code_) continue; // all OK + solution.clear(); + } } - return solution; +} + +void ClipperOffset::Execute(double delta, Paths64& paths) +{ + paths.clear(); + + ExecuteInternal(delta); + if (!solution.size()) return; + + paths = solution; + //clean up self-intersections ... + Clipper64 c; + c.PreserveCollinear = false; + //the solution should retain the orientation of the input + c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; +#ifdef USINGZ + if (zCallback64_) { + c.SetZCallback(zCallback64_); + } +#endif + c.AddSubject(solution); + if (groups_[0].is_reversed) + c.Execute(ClipType::Union, FillRule::Negative, paths); + else + c.Execute(ClipType::Union, FillRule::Positive, paths); +} + + +void ClipperOffset::Execute(double delta, PolyTree64& polytree) +{ + polytree.Clear(); + + ExecuteInternal(delta); + if (!solution.size()) return; + + //clean up self-intersections ... + Clipper64 c; + c.PreserveCollinear = false; + //the solution should retain the orientation of the input + c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; +#ifdef USINGZ + if (zCallback64_) { + c.SetZCallback(zCallback64_); + } +#endif + c.AddSubject(solution); + if (groups_[0].is_reversed) + c.Execute(ClipType::Union, FillRule::Negative, polytree); + else + c.Execute(ClipType::Union, FillRule::Positive, polytree); } } // namespace diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp index 0f6bb9c519..959972b440 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2022 * +* Date : 14 February 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -17,15 +17,22 @@ namespace Clipper2Lib { // Miscellaneous methods //------------------------------------------------------------------------------ - inline PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2) + inline bool Path1ContainsPath2(const Path64& path1, const Path64& path2) { - PointInPolygonResult result = PointInPolygonResult::IsOn; - for(const Point64& pt : path2) + int io_count = 0; + // precondition: no (significant) overlap + for (const Point64& pt : path2) { - result = PointInPolygon(pt, path1); - if (result != PointInPolygonResult::IsOn) break; + PointInPolygonResult pip = PointInPolygon(pt, path1); + switch (pip) + { + case PointInPolygonResult::IsOutside: ++io_count; break; + case PointInPolygonResult::IsInside: --io_count; break; + default: continue; + } + if (std::abs(io_count) > 1) break; } - return result; + return io_count <= 0; } inline bool GetLocation(const Rect64& rec, @@ -59,39 +66,6 @@ namespace Clipper2Lib { return true; } - Point64 GetIntersectPoint64(const Point64& ln1a, const Point64& ln1b, - const Point64& ln2a, const Point64& ln2b) - { - // see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/ - if (ln1b.x == ln1a.x) - { - if (ln2b.x == ln2a.x) return Point64(); // parallel lines - double m2 = static_cast(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x); - double b2 = ln2a.y - m2 * ln2a.x; - return Point64(ln1a.x, static_cast(std::round(m2 * ln1a.x + b2))); - } - else if (ln2b.x == ln2a.x) - { - double m1 = static_cast(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x); - double b1 = ln1a.y - m1 * ln1a.x; - return Point64(ln2a.x, static_cast(std::round(m1 * ln2a.x + b1))); - } - else - { - double m1 = static_cast(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x); - double b1 = ln1a.y - m1 * ln1a.x; - double m2 = static_cast(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x); - double b2 = ln2a.y - m2 * ln2a.x; - if (std::fabs(m1 - m2) > 1.0E-15) - { - double x = (b2 - b1) / (m1 - m2); - return Point64(x, m1 * x + b1); - } - else - return Point64((ln1a.x + ln1b.x) * 0.5, (ln1a.y + ln1b.y) * 0.5); - } - } - inline bool GetIntersection(const Path64& rectPath, const Point64& p, const Point64& p2, Location& loc, Point64& ip) { @@ -101,16 +75,16 @@ namespace Clipper2Lib { { case Location::Left: if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); + GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); else if (p.y < rectPath[0].y && SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); + GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; } else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); + GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; } else return false; @@ -118,34 +92,34 @@ namespace Clipper2Lib { case Location::Top: if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); + GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); else if (p.x < rectPath[0].x && SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); + GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; } else if (p.x > rectPath[1].x && SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); + GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; } else return false; - break; + break; case Location::Right: if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) - ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); + GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); else if (p.y < rectPath[0].y && SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); + GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; } else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); + GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; } else return false; @@ -153,17 +127,17 @@ namespace Clipper2Lib { case Location::Bottom: if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) - ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); + GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); else if (p.x < rectPath[3].x && SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); + GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; } else if (p.x > rectPath[2].x && SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); + GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; } else return false; @@ -172,28 +146,27 @@ namespace Clipper2Lib { default: // loc == rInside if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); + GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; } else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); + GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; } else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); + GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; } else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) { - ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); + GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; } else return false; break; } - return true; } @@ -214,7 +187,7 @@ namespace Clipper2Lib { } inline bool IsClockwise(Location prev, Location curr, - Point64 prev_pt, Point64 curr_pt, Point64 rect_mp) + const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp) { if (AreOpposites(prev, curr)) return CrossProduct(prev_pt, rect_mp, curr_pt) < 0; @@ -222,34 +195,147 @@ namespace Clipper2Lib { return HeadingClockwise(prev, curr); } + inline OutPt2* UnlinkOp(OutPt2* op) + { + if (op->next == op) return nullptr; + op->prev->next = op->next; + op->next->prev = op->prev; + return op->next; + } + + inline OutPt2* UnlinkOpBack(OutPt2* op) + { + if (op->next == op) return nullptr; + op->prev->next = op->next; + op->next->prev = op->prev; + return op->prev; + } + + inline uint32_t GetEdgesForPt(const Point64& pt, const Rect64& rec) + { + uint32_t result = 0; + if (pt.x == rec.left) result = 1; + else if (pt.x == rec.right) result = 4; + if (pt.y == rec.top) result += 2; + else if (pt.y == rec.bottom) result += 8; + return result; + } + + inline bool IsHeadingClockwise(const Point64& pt1, const Point64& pt2, int edgeIdx) + { + switch (edgeIdx) + { + case 0: return pt2.y < pt1.y; + case 1: return pt2.x > pt1.x; + case 2: return pt2.y > pt1.y; + default: return pt2.x < pt1.x; + } + } + + inline bool HasHorzOverlap(const Point64& left1, const Point64& right1, + const Point64& left2, const Point64& right2) + { + return (left1.x < right2.x) && (right1.x > left2.x); + } + + inline bool HasVertOverlap(const Point64& top1, const Point64& bottom1, + const Point64& top2, const Point64& bottom2) + { + return (top1.y < bottom2.y) && (bottom1.y > top2.y); + } + + inline void AddToEdge(OutPt2List& edge, OutPt2* op) + { + if (op->edge) return; + op->edge = &edge; + edge.push_back(op); + } + + inline void UncoupleEdge(OutPt2* op) + { + if (!op->edge) return; + for (size_t i = 0; i < op->edge->size(); ++i) + { + OutPt2* op2 = (*op->edge)[i]; + if (op2 == op) + { + (*op->edge)[i] = nullptr; + break; + } + } + op->edge = nullptr; + } + + inline void SetNewOwner(OutPt2* op, size_t new_idx) + { + op->owner_idx = new_idx; + OutPt2* op2 = op->next; + while (op2 != op) + { + op2->owner_idx = new_idx; + op2 = op2->next; + } + } + //---------------------------------------------------------------------------- // RectClip64 //---------------------------------------------------------------------------- + OutPt2* RectClip::Add(Point64 pt, bool start_new) + { + // 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(results_.size()) - 1; + OutPt2* result; + if (curr_idx < 0 || start_new) + { + result = &op_container_.emplace_back(OutPt2()); + result->pt = pt; + result->next = result; + result->prev = result; + results_.push_back(result); + } + else + { + OutPt2* prevOp = results_[curr_idx]; + if (prevOp->pt == pt) return prevOp; + result = &op_container_.emplace_back(OutPt2()); + result->owner_idx = curr_idx; + result->pt = pt; + result->next = prevOp->next; + prevOp->next->prev = result; + prevOp->next = result; + result->prev = prevOp; + results_[curr_idx] = result; + } + return result; + } + void RectClip::AddCorner(Location prev, Location curr) { if (HeadingClockwise(prev, curr)) - result_.push_back(rectPath_[static_cast(prev)]); + Add(rect_as_path_[static_cast(prev)]); else - result_.push_back(rectPath_[static_cast(curr)]); + Add(rect_as_path_[static_cast(curr)]); } void RectClip::AddCorner(Location& loc, bool isClockwise) { if (isClockwise) { - result_.push_back(rectPath_[static_cast(loc)]); + Add(rect_as_path_[static_cast(loc)]); loc = GetAdjacentLocation(loc, true); } else { loc = GetAdjacentLocation(loc, false); - result_.push_back(rectPath_[static_cast(loc)]); + Add(rect_as_path_[static_cast(loc)]); } } void RectClip::GetNextLocation(const Path64& path, - Location& loc, int& i, int highI) + Location& loc, int& i, int highI) { switch (loc) { @@ -296,19 +382,15 @@ namespace Clipper2Lib { else if (path[i].x > rect_.right) loc = Location::Right; else if (path[i].y > rect_.bottom) loc = Location::Bottom; else if (path[i].y < rect_.top) loc = Location::Top; - else { result_.push_back(path[i]); ++i; continue; } + else { Add(path[i]); ++i; continue; } break; //inner loop } break; } //switch } - Path64 RectClip::Execute(const Path64& path) + void RectClip::ExecuteInternal(const Path64& path) { - if (rect_.IsEmpty() || path.size() < 3) return Path64(); - - result_.clear(); - start_locs_.clear(); int i = 0, highI = static_cast(path.size()) - 1; Location prev = Location::Inside, loc; Location crossing_loc = Location::Inside; @@ -317,11 +399,16 @@ namespace Clipper2Lib { { i = highI - 1; while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i; - if (i < 0) return path; + 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 starting_loc = loc; + Location startingLoc = loc; /////////////////////////////////////////////////// while (i <= highI) @@ -333,16 +420,18 @@ namespace Clipper2Lib { if (i > highI) break; Point64 ip, ip2; - Point64 prev_pt = (i) ? path[static_cast(i - 1)] : path[highI]; + Point64 prev_pt = (i) ? + path[static_cast(i - 1)] : + path[highI]; crossing_loc = loc; - if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip)) + if (!GetIntersection(rect_as_path_, + path[i], prev_pt, crossing_loc, ip)) { // ie remaining outside - if (crossing_prev == Location::Inside) { - bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_); + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); do { start_locs_.push_back(prev); prev = GetAdjacentLocation(prev, isClockw); @@ -351,7 +440,7 @@ namespace Clipper2Lib { } else if (prev != Location::Inside && prev != loc) { - bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_); + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); do { AddCorner(prev, isClockw); } while (prev != loc); @@ -373,7 +462,7 @@ namespace Clipper2Lib { } else if (prev != crossing_loc) { - bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], mp_); + bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], rect_mp_); do { AddCorner(prev, isClockw); } while (prev != crossing_loc); @@ -384,7 +473,7 @@ namespace Clipper2Lib { // 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(rectPath_, prev_pt, path[i], loc, ip2); + GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2); if (crossing_prev != Location::Inside) AddCorner(crossing_prev, loc); @@ -395,7 +484,7 @@ namespace Clipper2Lib { } loc = crossing_loc; - result_.push_back(ip2); + Add(ip2); if (ip == ip2) { // it's very likely that path[i] is on rect @@ -412,23 +501,33 @@ namespace Clipper2Lib { first_cross_ = crossing_loc; } - result_.push_back(ip); + Add(ip); } //while i <= highI /////////////////////////////////////////////////// if (first_cross_ == Location::Inside) { - if (starting_loc == Location::Inside) return path; - Rect64 tmp_rect = Bounds(path); - if (tmp_rect.Contains(rect_) && - Path1ContainsPath2(path, rectPath_) != - PointInPolygonResult::IsOutside) return rectPath_; - else - return Path64(); + // path never intersects + if (startingLoc != Location::Inside) + { + // path is outside rect + // but being outside, it still may not contain rect + if (path_bounds_.Contains(rect_) && + Path1ContainsPath2(path, rect_as_path_)) + { + // yep, the path does fully contain rect + // so add rect to the solution + for (size_t j = 0; j < 4; ++j) + { + Add(rect_as_path_[j]); + // we may well need to do some splitting later, so + AddToEdge(edges_[j * 2], results_[0]); + } + } + } } - - if (loc != Location::Inside && + else if (loc != Location::Inside && (loc != first_cross_ || start_locs_.size() > 2)) { if (start_locs_.size() > 0) @@ -445,54 +544,375 @@ namespace Clipper2Lib { if (loc != first_cross_) AddCorner(loc, HeadingClockwise(loc, first_cross_)); } - - if (result_.size() < 3) return Path64(); - - // tidy up duplicates and collinear segments - Path64 res; - res.reserve(result_.size()); - size_t k = 0; highI = static_cast(result_.size()) - 1; - Point64 prev_pt = result_[highI]; - res.push_back(result_[0]); - Path64::const_iterator cit; - for (cit = result_.cbegin() + 1; cit != result_.cend(); ++cit) - { - if (CrossProduct(prev_pt, res[k], *cit)) - { - prev_pt = res[k++]; - res.push_back(*cit); - } - else - res[k] = *cit; - } - - if (k < 2) return Path64(); - // and a final check for collinearity - else if (!CrossProduct(res[0], res[k - 1], res[k])) res.pop_back(); - return res; } - Paths64 RectClipLines::Execute(const Path64& path) + void RectClip::CheckEdges() + { + for (size_t i = 0; i < results_.size(); ++i) + { + OutPt2* op = results_[i]; + if (!op) continue; + OutPt2* op2 = op; + do + { + if (!CrossProduct(op2->prev->pt, + op2->pt, op2->next->pt)) + { + if (op2 == op) + { + op2 = UnlinkOpBack(op2); + if (!op2) break; + op = op2->prev; + } + else + { + op2 = UnlinkOpBack(op2); + if (!op2) break; + } + } + else + op2 = op2->next; + } while (op2 != op); + + if (!op2) + { + results_[i] = nullptr; + continue; + } + results_[i] = op; // safety first + + uint32_t edgeSet1 = GetEdgesForPt(op->prev->pt, rect_); + op2 = op; + do + { + uint32_t edgeSet2 = GetEdgesForPt(op2->pt, rect_); + if (edgeSet2 && !op2->edge) + { + uint32_t combinedSet = (edgeSet1 & edgeSet2); + for (int j = 0; j < 4; ++j) + { + if (combinedSet & (1 << j)) + { + if (IsHeadingClockwise(op2->prev->pt, op2->pt, j)) + AddToEdge(edges_[j * 2], op2); + else + AddToEdge(edges_[j * 2 + 1], op2); + } + } + } + edgeSet1 = edgeSet2; + op2 = op2->next; + } while (op2 != op); + } + } + + void RectClip::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw) + { + if (ccw.empty()) return; + bool isHorz = ((idx == 1) || (idx == 3)); + bool cwIsTowardLarger = ((idx == 1) || (idx == 2)); + size_t i = 0, j = 0; + OutPt2* p1, * p2, * p1a, * p2a, * op, * op2; + + while (i < cw.size()) + { + p1 = cw[i]; + if (!p1 || p1->next == p1->prev) + { + cw[i++]->edge = nullptr; + j = 0; + continue; + } + + size_t jLim = ccw.size(); + while (j < jLim && + (!ccw[j] || ccw[j]->next == ccw[j]->prev)) ++j; + + if (j == jLim) + { + ++i; + j = 0; + continue; + } + + if (cwIsTowardLarger) + { + // p1 >>>> p1a; + // p2 <<<< p2a; + p1 = cw[i]->prev; + p1a = cw[i]; + p2 = ccw[j]; + p2a = ccw[j]->prev; + } + else + { + // p1 <<<< p1a; + // p2 >>>> p2a; + p1 = cw[i]; + p1a = cw[i]->prev; + p2 = ccw[j]->prev; + p2a = ccw[j]; + } + + if ((isHorz && !HasHorzOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)) || + (!isHorz && !HasVertOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt))) + { + ++j; + continue; + } + + // to get here we're either splitting or rejoining + bool isRejoining = cw[i]->owner_idx != ccw[j]->owner_idx; + + if (isRejoining) + { + results_[p2->owner_idx] = nullptr; + SetNewOwner(p2, p1->owner_idx); + } + + // do the split or re-join + if (cwIsTowardLarger) + { + // p1 >> | >> p1a; + // p2 << | << p2a; + p1->next = p2; + p2->prev = p1; + p1a->prev = p2a; + p2a->next = p1a; + } + else + { + // p1 << | << p1a; + // p2 >> | >> p2a; + p1->prev = p2; + p2->next = p1; + p1a->next = p2a; + p2a->prev = p1a; + } + + if (!isRejoining) + { + size_t new_idx = results_.size(); + results_.push_back(p1a); + SetNewOwner(p1a, new_idx); + } + + if (cwIsTowardLarger) + { + op = p2; + op2 = p1a; + } + else + { + op = p1; + op2 = p2a; + } + results_[op->owner_idx] = op; + results_[op2->owner_idx] = op2; + + // and now lots of work to get ready for the next loop + + bool opIsLarger, op2IsLarger; + if (isHorz) // X + { + opIsLarger = op->pt.x > op->prev->pt.x; + op2IsLarger = op2->pt.x > op2->prev->pt.x; + } + else // Y + { + opIsLarger = op->pt.y > op->prev->pt.y; + op2IsLarger = op2->pt.y > op2->prev->pt.y; + } + + if ((op->next == op->prev) || + (op->pt == op->prev->pt)) + { + if (op2IsLarger == cwIsTowardLarger) + { + cw[i] = op2; + ccw[j++] = nullptr; + } + else + { + ccw[j] = op2; + cw[i++] = nullptr; + } + } + else if ((op2->next == op2->prev) || + (op2->pt == op2->prev->pt)) + { + if (opIsLarger == cwIsTowardLarger) + { + cw[i] = op; + ccw[j++] = nullptr; + } + else + { + ccw[j] = op; + cw[i++] = nullptr; + } + } + else if (opIsLarger == op2IsLarger) + { + if (opIsLarger == cwIsTowardLarger) + { + cw[i] = op; + UncoupleEdge(op2); + AddToEdge(cw, op2); + ccw[j++] = nullptr; + } + else + { + cw[i++] = nullptr; + ccw[j] = op2; + UncoupleEdge(op); + AddToEdge(ccw, op); + j = 0; + } + } + else + { + if (opIsLarger == cwIsTowardLarger) + cw[i] = op; + else + ccw[j] = op; + if (op2IsLarger == cwIsTowardLarger) + cw[i] = op2; + else + ccw[j] = op2; + } + } + } + + Path64 RectClip::GetPath(OutPt2*& op) + { + if (!op || op->next == op->prev) return Path64(); + + OutPt2* op2 = op->next; + while (op2 && op2 != op) + { + if (CrossProduct(op2->prev->pt, + op2->pt, op2->next->pt) == 0) + { + op = op2->prev; + op2 = UnlinkOp(op2); + } + else + op2 = op2->next; + } + op = op2; // needed for op cleanup + if (!op2) return Path64(); + + Path64 result; + result.push_back(op->pt); + op2 = op->next; + while (op2 != op) + { + result.push_back(op2->pt); + op2 = op2->next; + } + return result; + } + + Paths64 RectClip::Execute(const Paths64& paths, bool convex_only) { - result_.clear(); Paths64 result; - if (rect_.IsEmpty() || path.size() == 0) return result; + if (rect_.IsEmpty()) return result; + + for (const auto& path : paths) + { + if (path.size() < 3) continue; + path_bounds_ = GetBounds(path); + if (!rect_.Intersects(path_bounds_)) + continue; // the path must be completely outside rect_ + else if (rect_.Contains(path_bounds_)) + { + // the path must be completely inside rect_ + result.push_back(path); + continue; + } + + ExecuteInternal(path); + if (!convex_only) + { + CheckEdges(); + for (int i = 0; i < 4; ++i) + TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]); + } + + for (OutPt2*& op : results_) + { + Path64 tmp = GetPath(op); + if (!tmp.empty()) + result.emplace_back(tmp); + } + + //clean up after every loop + op_container_ = std::deque(); + results_.clear(); + for (OutPt2List edge : edges_) edge.clear(); + start_locs_.clear(); + } + return result; + } + + //------------------------------------------------------------------------------ + // RectClipLines + //------------------------------------------------------------------------------ + + Paths64 RectClipLines::Execute(const Paths64& paths) + { + Paths64 result; + if (rect_.IsEmpty()) return result; + + for (const auto& path : paths) + { + if (path.size() < 2) continue; + Rect64 pathrec = GetBounds(path); + + if (!rect_.Intersects(pathrec)) continue; + + ExecuteInternal(path); + + for (OutPt2*& op : results_) + { + Path64 tmp = GetPath(op); + if (!tmp.empty()) + result.emplace_back(tmp); + } + results_.clear(); + + op_container_ = std::deque(); + start_locs_.clear(); + } + return result; + } + + void RectClipLines::ExecuteInternal(const Path64& path) + { + if (rect_.IsEmpty() || path.size() < 2) return; + + results_.clear(); + op_container_ = std::deque(); + start_locs_.clear(); int i = 1, highI = static_cast(path.size()) - 1; Location prev = Location::Inside, loc; - Location crossing_loc = Location::Inside; + Location crossing_loc; if (!GetLocation(rect_, path[0], loc)) { while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i; - if (i > highI) { - result.push_back(path); - return result; + if (i > highI) + { + // all of path must be inside fRect + for (const auto& pt : path) Add(pt); + return; } if (prev == Location::Inside) loc = Location::Inside; i = 1; } - if (loc == Location::Inside) result_.push_back(path[0]); + if (loc == Location::Inside) Add(path[0]); /////////////////////////////////////////////////// while (i <= highI) @@ -504,7 +924,8 @@ namespace Clipper2Lib { Point64 prev_pt = path[static_cast(i - 1)]; crossing_loc = loc; - if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip)) + if (!GetIntersection(rect_as_path_, + path[i], prev_pt, crossing_loc, ip)) { // ie remaining outside ++i; @@ -517,30 +938,38 @@ namespace Clipper2Lib { if (loc == Location::Inside) // path must be entering rect { - result_.push_back(ip); + Add(ip, true); } else if (prev != Location::Inside) { // 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(rectPath_, prev_pt, path[i], crossing_loc, ip2); - result_.push_back(ip2); - result_.push_back(ip); - result.push_back(result_); - result_.clear(); + GetIntersection(rect_as_path_, + prev_pt, path[i], crossing_loc, ip2); + Add(ip2, true); + Add(ip); } else // path must be exiting rect { - result_.push_back(ip); - result.push_back(result_); - result_.clear(); + Add(ip); } } //while i <= highI /////////////////////////////////////////////////// + } - if (result_.size() > 1) - result.push_back(result_); + Path64 RectClipLines::GetPath(OutPt2*& op) + { + Path64 result; + if (!op || op == op->next) return result; + 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; }