From 1ff4b2b4eb2d04bcf4ff1f4b480dc8d7cff59eba Mon Sep 17 00:00:00 2001 From: Seth Hillbrand <seth@kipro-pcb.com> Date: Tue, 28 Nov 2023 16:01:43 -0800 Subject: [PATCH] Upgrade Clipper2 to 1.3.0 Fixes a number of minor inflation issues including slivers when overlapping points are inflated Fixes https://gitlab.com/kicad/code/kicad/-/issues/16182 (cherry picked from commit daf178b64f3931b3e5a22e395600c135bd21b1ad) Fixes https://gitlab.com/kicad/code/kicad/-/issues/16241 --- libs/kimath/src/geometry/shape_poly_set.cpp | 19 +- .../include/clipper2/clipper.core.h | 110 ++- .../include/clipper2/clipper.engine.h | 36 +- .../include/clipper2/clipper.export.h | 835 +++++++----------- .../Clipper2Lib/include/clipper2/clipper.h | 80 +- .../include/clipper2/clipper.minkowski.h | 4 +- .../include/clipper2/clipper.offset.h | 34 +- .../include/clipper2/clipper.rectclip.h | 5 +- .../include/clipper2/clipper.version.h | 6 + .../Clipper2Lib/src/clipper.engine.cpp | 99 ++- .../Clipper2Lib/src/clipper.offset.cpp | 463 +++++----- .../Clipper2Lib/src/clipper.rectclip.cpp | 130 ++- 12 files changed, 867 insertions(+), 954 deletions(-) create mode 100644 thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.version.h diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp index 49a080d905..edabe75ed5 100644 --- a/libs/kimath/src/geometry/shape_poly_set.cpp +++ b/libs/kimath/src/geometry/shape_poly_set.cpp @@ -499,7 +499,7 @@ bool SHAPE_POLY_SET::IsPolygonSelfIntersecting( int aPolygonIndex ) const break; int index_diff = std::abs( firstSegment.Index() - secondSegment.Index() ); - bool adjacent = ( index_diff == 1) || (index_diff == (segments.size() - 1) ); + bool adjacent = ( index_diff == 1) || (index_diff == ((int)segments.size() - 1) ); // Check whether the two segments built collide, only when they are not adjacent. if( !adjacent && firstSegment.Collide( secondSegment, 0 ) ) @@ -1009,28 +1009,27 @@ void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm JoinType joinType = JoinType::Round; // The way corners are offsetted double miterLimit = 2.0; // Smaller value when using jtMiter for joinType - JoinType miterFallback = JoinType::Square; switch( aCornerStrategy ) { - case ALLOW_ACUTE_CORNERS: + case CORNER_STRATEGY::ALLOW_ACUTE_CORNERS: joinType = JoinType::Miter; miterLimit = 10; // Allows large spikes break; - case CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered + case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered joinType = JoinType::Miter; break; - case ROUND_ACUTE_CORNERS: // Acute angles are rounded + case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded joinType = JoinType::Miter; break; - case CHAMFER_ALL_CORNERS: // All angles are chamfered. + case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered. joinType = JoinType::Square; break; - case ROUND_ALL_CORNERS: // All angles are rounded. + case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded. joinType = JoinType::Round; break; } @@ -1079,11 +1078,11 @@ void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY Paths64 paths; c.Execute( aAmount, paths ); - Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, false ); + Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, true ); Clipper64 c2; - c2.PreserveCollinear = false; - c2.ReverseSolution = false; + c2.PreserveCollinear( false ); + c2.ReverseSolution( false ); c2.AddSubject( paths ); c2.Execute(ClipType::Union, FillRule::Positive, tree); } diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h index 7a32d4c4aa..b3dddeeaa2 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 July 2023 * +* Date : 24 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -19,6 +19,7 @@ #include <algorithm> #include <climits> #include <numeric> +#include "clipper2/clipper.version.h" namespace Clipper2Lib { @@ -42,18 +43,27 @@ namespace Clipper2Lib "Invalid scale (either 0 or too large)"; static const char* non_pair_error = "There must be 2 values for each coordinate"; + static const char* undefined_error = + "There is an undefined error in Clipper2"; #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; + const int precision_error_i = 1; // non-fatal + const int scale_error_i = 2; // non-fatal + const int non_pair_error_i = 4; // non-fatal + const int undefined_error_i = 32; // fatal + const int range_error_i = 64; #ifndef PI static const double PI = 3.141592653589793238; #endif - static const int MAX_DECIMAL_PRECISION = 8; // see https://github.com/AngusJohnson/Clipper2/discussions/564 + +#ifdef CLIPPER2_MAX_PRECISION + const int MAX_DECIMAL_PRECISION = CLIPPER2_MAX_PRECISION; +#else + const int MAX_DECIMAL_PRECISION = 8; // see Discussions #564 +#endif + static const int64_t MAX_COORD = INT64_MAX >> 2; static const int64_t MIN_COORD = -MAX_COORD; static const int64_t INVALID = INT64_MAX; @@ -73,6 +83,8 @@ namespace Clipper2Lib throw Clipper2Exception(scale_error); case non_pair_error_i: throw Clipper2Exception(non_pair_error); + case undefined_error_i: + throw Clipper2Exception(undefined_error); case range_error_i: throw Clipper2Exception(range_error); } @@ -81,6 +93,7 @@ namespace Clipper2Lib #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 @@ -137,7 +150,7 @@ namespace Clipper2Lib friend std::ostream& operator<<(std::ostream& os, const Point& point) { - os << point.x << "," << point.y << "," << point.z << " "; + os << point.x << "," << point.y << "," << point.z; return os; } @@ -174,7 +187,7 @@ namespace Clipper2Lib friend std::ostream& operator<<(std::ostream& os, const Point& point) { - os << point.x << "," << point.y << " "; + os << point.x << "," << point.y; return os; } #endif @@ -222,6 +235,14 @@ namespace Clipper2Lib using Paths64 = std::vector< Path64>; using PathsD = std::vector< PathD>; + static const Point64 InvalidPoint64 = Point64( + (std::numeric_limits<int64_t>::max)(), + (std::numeric_limits<int64_t>::max)()); + static const PointD InvalidPointD = PointD( + (std::numeric_limits<double>::max)(), + (std::numeric_limits<double>::max)()); + + // Rect ------------------------------------------------------------------------ template <typename T> @@ -237,19 +258,13 @@ namespace Clipper2Lib 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) + Rect(bool is_valid = true) { if (is_valid) { @@ -258,10 +273,12 @@ namespace Clipper2Lib else { left = top = (std::numeric_limits<T>::max)(); - right = bottom = -(std::numeric_limits<int64_t>::max)(); + right = bottom = (std::numeric_limits<T>::lowest)(); } } + bool IsValid() const { return left != (std::numeric_limits<T>::max)(); } + T Width() const { return right - left; } T Height() const { return bottom - top; } void Width(T width) { right = left + width; } @@ -309,10 +326,13 @@ namespace Clipper2Lib ((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom)); }; + bool operator==(const Rect<T>& other) const { + return left == other.left && right == other.right && + top == other.top && bottom == other.bottom; + } + friend std::ostream& operator<<(std::ostream& os, const Rect<T>& rect) { - os << "(" - << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom - << ")"; + os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ") "; return os; } }; @@ -340,10 +360,16 @@ namespace Clipper2Lib 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); + static const Rect64 InvalidRect64 = Rect64( + (std::numeric_limits<int64_t>::max)(), + (std::numeric_limits<int64_t>::max)(), + (std::numeric_limits<int64_t>::lowest)(), + (std::numeric_limits<int64_t>::lowest)()); + static const RectD InvalidRectD = RectD( + (std::numeric_limits<double>::max)(), + (std::numeric_limits<double>::max)(), + (std::numeric_limits<double>::lowest)(), + (std::numeric_limits<double>::lowest)()); template <typename T> Rect<T> GetBounds(const Path<T>& path) @@ -490,26 +516,6 @@ namespace Clipper2Lib return result; } - inline PathD Path64ToPathD(const Path64& path) - { - return TransformPath<double, int64_t>(path); - } - - inline PathsD Paths64ToPathsD(const Paths64& paths) - { - return TransformPaths<double, int64_t>(paths); - } - - inline Path64 PathDToPath64(const PathD& path) - { - return TransformPath<int64_t, double>(path); - } - - inline Paths64 PathsDToPaths64(const PathsD& paths) - { - return TransformPaths<int64_t, double>(paths); - } - template<typename T> inline double Sqr(T val) { @@ -565,7 +571,7 @@ namespace Clipper2Lib inline void StripDuplicates( Path<T>& path, bool is_closed_path) { //https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A - path.erase(std::unique(path.begin(), path.end()),path.end()); + path.erase(std::unique(path.begin(), path.end()), path.end()); if (is_closed_path) while (path.size() > 1 && path.back() == path.front()) path.pop_back(); } @@ -723,8 +729,9 @@ namespace Clipper2Lib } } - inline Point64 GetClosestPointOnSegment(const Point64& offPt, - const Point64& seg1, const Point64& seg2) + template<typename T> + inline Point<T> GetClosestPointOnSegment(const Point<T>& offPt, + const Point<T>& seg1, const Point<T>& seg2) { if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1; double dx = static_cast<double>(seg2.x - seg1.x); @@ -734,9 +741,14 @@ namespace Clipper2Lib static_cast<double>(offPt.y - seg1.y) * dy) / (Sqr(dx) + Sqr(dy)); if (q < 0) q = 0; else if (q > 1) q = 1; - return Point64( - seg1.x + static_cast<int64_t>(nearbyint(q * dx)), - seg1.y + static_cast<int64_t>(nearbyint(q * dy))); + if constexpr (std::numeric_limits<T>::is_integer) + return Point<T>( + seg1.x + static_cast<T>(nearbyint(q * dx)), + seg1.y + static_cast<T>(nearbyint(q * dy))); + else + return Point<T>( + seg1.x + static_cast<T>(q * dx), + seg1.y + static_cast<T>(q * dy)); } enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h index 239f705804..a48f058e5b 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 July 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -10,8 +10,6 @@ #ifndef CLIPPER_ENGINE_H #define CLIPPER_ENGINE_H -constexpr auto CLIPPER2_VERSION = "1.2.2"; - #include <cstdlib> #include <stdint.h> //#541 #include <iostream> @@ -21,12 +19,11 @@ constexpr auto CLIPPER2_VERSION = "1.2.2"; #include <numeric> #include <memory> -#include "clipper.core.h" +#include "clipper2/clipper.core.h" #ifdef None #undef None #endif - namespace Clipper2Lib { struct Scanline; @@ -268,6 +265,8 @@ namespace Clipper2Lib { inline void CheckJoinRight(Active& e, const Point64& pt, bool check_curr_x = false); protected: + bool preserve_collinear_ = true; + bool reverse_solution_ = false; int error_code_ = 0; bool has_open_paths_ = false; bool succeeded_ = true; @@ -286,9 +285,11 @@ 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; + int ErrorCode() const { return error_code_; }; + void PreserveCollinear(bool val) { preserve_collinear_ = val; }; + bool PreserveCollinear() const { return preserve_collinear_;}; + void ReverseSolution(bool val) { reverse_solution_ = val; }; + bool ReverseSolution() const { return reverse_solution_; }; void Clear(); void AddReuseableData(const ReuseableDataContainer64& reuseable_data); #ifdef USINGZ @@ -350,12 +351,12 @@ namespace Clipper2Lib { childs_.resize(0); } - const PolyPath64* operator [] (size_t index) const + PolyPath64* operator [] (size_t index) const { return childs_[index].get(); //std::unique_ptr } - const PolyPath64* Child(size_t index) const + PolyPath64* Child(size_t index) const { return childs_[index].get(); } @@ -407,12 +408,12 @@ namespace Clipper2Lib { childs_.resize(0); } - const PolyPathD* operator [] (size_t index) const + PolyPathD* operator [] (size_t index) const { return childs_[index].get(); } - const PolyPathD* Child(size_t index) const + PolyPathD* Child(size_t index) const { return childs_[index].get(); } @@ -421,7 +422,8 @@ namespace Clipper2Lib { PolyPathDList::const_iterator end() const { return childs_.cend(); } void SetScale(double value) { scale_ = value; } - double Scale() { return scale_; } + double Scale() const { return scale_; } + PolyPathD* AddChild(const Path64& path) override { int error_code = 0; @@ -431,6 +433,14 @@ namespace Clipper2Lib { return result; } + PolyPathD* AddChild(const PathD& path) + { + auto p = std::make_unique<PolyPathD>(this); + PolyPathD* result = childs_.emplace_back(std::move(p)).get(); + result->polygon_ = path; + return result; + } + void Clear() override { childs_.resize(0); diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h index f5f81d2a8b..d7286132a4 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,39 +1,78 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 26 November 2023 * * Website : http://www.angusj.com * * 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 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' should be 0, with one exception explained below.) -// __________________________________ -// |counter|coord1|coord2|...|coordN| -// |N ,0 |x1, y1|x2, y2|...|xN, yN| -// __________________________________ -// -// 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 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 these structures don't need to be explained here. + +/* + Boolean clipping: + cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 + fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 + + Polygon offsetting (inflate/deflate): + jointype: Square=0, Bevel=1, Round=2, Miter=3 + endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4 + +The path structures used extensively in other parts of this library are all +based on std::vector classes. Since C++ classes can't be accessed by other +languages, these paths must be converted into simple C data structures that +can be understood by just about any programming language. And these C style +path structures are simple arrays of int64_t (CPath64) and double (CPathD). + +CPath64 and CPathD: +These are arrays of consecutive x and y path coordinates preceeded by +a pair of values containing the path's length (N) and a 0 value. +__________________________________ +|counter|coord1|coord2|...|coordN| +|N, 0 |x1, y1|x2, y2|...|xN, yN| +__________________________________ + +CPaths64 and CPathsD: +These are also arrays containing any number of consecutive CPath64 or +CPathD structures. But preceeding these consecutive paths, there is pair of +values that contain the total length of the array (A) structure and +the number (C) of CPath64 or CPathD it contains. +_______________________________ +|counter|path1|path2|...|pathC| +|A , C | | +_______________________________ + +CPolytree64 and CPolytreeD: +These are also arrays consisting of CPolyPath structures that represent +individual paths in a tree structure. However, the very first (ie top) +CPolyPath is just the tree container that won't have a path. And because +of that, its structure will be very slightly different from the remaining +CPolyPath. This difference will be discussed below. + +CPolyPath64 and CPolyPathD: +These are simple arrays consisting of a series of path coordinates followed +by any number of child (ie nested) CPolyPath. Preceeding these are two values +indicating the length of the path (N) and the number of child CPolyPath (C). +____________________________________________________________ +|counter|coord1|coord2|...|coordN| child1|child2|...|childC| +|N , C |x1, y1|x2, y2|...|xN, yN| | +____________________________________________________________ + +As mentioned above, the very first CPolyPath structure is just a container +that owns (both directly and indirectly) every other CPolyPath in the tree. +Since this first CPolyPath has no path, instead of a path length, its very +first value will contain the total length of the CPolytree array structure. + +All theses exported structures (CPaths64, CPathsD, CPolyTree64 & CPolyTreeD) +are arrays of type int64_t or double. And the first value in these arrays +will always contain the length of that array. + +These array structures are allocated in heap memory which will eventually +need to be released. But since applications dynamically linking to these +functions may use different memory managers, the only safe way to free up +this memory is to use the exported DisposeArray64 and DisposeArrayD +functions below. +*/ + #ifndef CLIPPER2_EXPORT_H #define CLIPPER2_EXPORT_H @@ -49,25 +88,14 @@ namespace Clipper2Lib { typedef int64_t* CPath64; -typedef int64_t** CPaths64; -typedef double* CPathD; -typedef double** CPathsD; +typedef int64_t* CPaths64; +typedef double* CPathD; +typedef double* CPathsD; -typedef struct CPolyPath64 { - CPath64 polygon; - uint32_t is_hole; - uint32_t child_count; - CPolyPath64* childs; -} -CPolyTree64; - -typedef struct CPolyPathD { - CPathD polygon; - uint32_t is_hole; - uint32_t child_count; - CPolyPathD* childs; -} -CPolyTreeD; +typedef int64_t* CPolyPath64; +typedef int64_t* CPolyTree64; +typedef double* CPolyPathD; +typedef double* CPolyTreeD; template <typename T> struct CRect { @@ -97,57 +125,53 @@ inline Rect<T> CRectToRect(const CRect<T>& rect) return result; } -#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) +#ifdef _WIN32 + #define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) +#else + #define EXTERN_DLL_EXPORT extern "C" +#endif + ////////////////////////////////////////////////////// -// EXPORTED FUNCTION DEFINITIONS +// EXPORTED FUNCTION DECLARATIONS ////////////////////////////////////////////////////// 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); +EXTERN_DLL_EXPORT void DisposeArray64(int64_t*& p) +{ + delete[] p; +} + +EXTERN_DLL_EXPORT void DisposeArrayD(double*& p) +{ + delete[] p; +} -// 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, + +EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, - CPolyTree64*& solution, CPaths64& solution_open, + CPolyTree64& sol_tree, 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, + +EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, - CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2, + 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, @@ -171,66 +195,173 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, // INTERNAL FUNCTIONS ////////////////////////////////////////////////////// -inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2); -inline CPath64 CreateCPath64(const Path64& p); -inline CPaths64 CreateCPaths64(const Paths64& pp); -inline Path64 ConvertCPath64(const CPath64& p); -inline Paths64 ConvertCPaths64(const CPaths64& pp); +template <typename T> +static void GetPathCountAndCPathsArrayLen(const Paths<T>& paths, + size_t& cnt, size_t& array_len) +{ + array_len = 2; + cnt = 0; + for (const Path<T>& path : paths) + if (path.size()) + { + array_len += path.size() * 2 + 2; + ++cnt; + } +} -inline CPathD CreateCPathD(size_t cnt1, size_t cnt2); -inline CPathD CreateCPathD(const PathD& p); -inline CPathsD CreateCPathsD(const PathsD& pp); -inline PathD ConvertCPathD(const CPathD& p); -inline PathsD ConvertCPathsD(const CPathsD& pp); +static size_t GetPolyPath64ArrayLen(const PolyPath64& pp) +{ + size_t result = 2; // poly_length + child_count + result += pp.Polygon().size() * 2; + //plus nested children :) + for (size_t i = 0; i < pp.Count(); ++i) + result += GetPolyPath64ArrayLen(*pp[i]); + return result; +} -// the following function avoid multiple conversions -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); +static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, + size_t& cnt, size_t& array_len) +{ + cnt = tree.Count(); // nb: top level count only + array_len = GetPolyPath64ArrayLen(tree); +} -inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt); -inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale); +template <typename T> +static T* CreateCPaths(const Paths<T>& paths) +{ + size_t cnt = 0, array_len = 0; + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); + T* result = new T[array_len], * v = result; + *v++ = array_len; + *v++ = cnt; + for (const Path<T>& path : paths) + { + if (!path.size()) continue; + *v++ = path.size(); + *v++ = 0; + for (const Point<T>& pt : path) + { + *v++ = pt.x; + *v++ = pt.y; + } + } + return result; +} + + +CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) +{ + if (!paths.size()) return nullptr; + size_t cnt, array_len; + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); + CPathsD result = new double[array_len], v = result; + *v++ = (double)array_len; + *v++ = (double)cnt; + for (const Path64& path : paths) + { + if (!path.size()) continue; + *v = (double)path.size(); + ++v; *v++ = 0; + for (const Point64& pt : path) + { + *v++ = pt.x * scale; + *v++ = pt.y * scale; + } + } + return result; +} + +template <typename T> +static Paths<T> ConvertCPaths(T* paths) +{ + Paths<T> result; + if (!paths) return result; + T* v = paths; ++v; + size_t cnt = *v++; + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + size_t cnt2 = *v; + v += 2; + Path<T> path; + path.reserve(cnt2); + for (size_t j = 0; j < cnt2; ++j) + { + T x = *v++, y = *v++; + path.push_back(Point<T>(x, y)); + } + result.push_back(path); + } + return result; +} + + +static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) +{ + Paths64 result; + if (!paths) return result; + double* v = paths; + ++v; // skip the first value (0) + int64_t cnt = (int64_t)*v++; + result.reserve(cnt); + for (int i = 0; i < cnt; ++i) + { + int64_t cnt2 = (int64_t)*v; + v += 2; + Path64 path; + path.reserve(cnt2); + for (int j = 0; j < cnt2; ++j) + { + double x = *v++ * scale; + double y = *v++ * scale; + path.push_back(Point64(x, y)); + } + result.push_back(path); + } + return result; +} + +template <typename T> +static void CreateCPolyPath(const PolyPath64* pp, T*& v, T scale) +{ + *v++ = static_cast<T>(pp->Polygon().size()); + *v++ = static_cast<T>(pp->Count()); + for (const Point64& pt : pp->Polygon()) + { + *v++ = static_cast<T>(pt.x * scale); + *v++ = static_cast<T>(pt.y * scale); + } + for (size_t i = 0; i < pp->Count(); ++i) + CreateCPolyPath(pp->Child(i), v, scale); +} + +template <typename T> +static T* CreateCPolyTree(const PolyTree64& tree, T scale) +{ + if (scale == 0) scale = 1; + size_t cnt, array_len; + GetPolytreeCountAndCStorageSize(tree, cnt, array_len); + if (!cnt) return nullptr; + // allocate storage + T* result = new T[array_len]; + T* v = result; + + *v++ = static_cast<T>(array_len); + *v++ = static_cast<T>(tree.Count()); + for (size_t i = 0; i < tree.Count(); ++i) + CreateCPolyPath(tree.Child(i), v, scale); + return result; +} + +////////////////////////////////////////////////////// +// EXPORTED FUNCTION DEFINITIONS +////////////////////////////////////////////////////// EXTERN_DLL_EXPORT const char* Version() { return CLIPPER2_VERSION; } -EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p) -{ - if (p) delete[] p; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp) -{ - if (!pp) return; - CPaths64 v = pp; - CPath64 cnts = *v; - const size_t cnt = static_cast<size_t>(cnts[1]); - for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 - DisposeExportedCPath64(*v++); - delete[] pp; - pp = nullptr; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p) -{ - if (p) delete[] p; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp) -{ - if (!pp) return; - CPathsD v = pp; - CPathD cnts = *v; - size_t cnt = static_cast<size_t>(cnts[1]); - for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 - DisposeExportedCPathD(*v++); - delete[] pp; - pp = nullptr; -} - EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, @@ -241,48 +372,48 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3; Paths64 sub, sub_open, clp, sol, sol_open; - sub = ConvertCPaths64(subjects); - sub_open = ConvertCPaths64(subjects_open); - clp = ConvertCPaths64(clips); + sub = ConvertCPaths(subjects); + sub_open = ConvertCPaths(subjects_open); + clp = ConvertCPaths(clips); Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); if (sub.size() > 0) clipper.AddSubject(sub); if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) return -1; // clipping bug - should never happen :) - solution = CreateCPaths64(sol); - solution_open = CreateCPaths64(sol_open); + solution = CreateCPaths(sol); + solution_open = CreateCPaths(sol_open); return 0; //success !! } -EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, +EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, - CPolyTree64*& solution, CPaths64& solution_open, + CPolyTree64& sol_tree, CPaths64& solution_open, bool preserve_collinear, bool reverse_solution) { if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4; if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3; Paths64 sub, sub_open, clp, sol_open; - sub = ConvertCPaths64(subjects); - sub_open = ConvertCPaths64(subjects_open); - clp = ConvertCPaths64(clips); + sub = ConvertCPaths(subjects); + sub_open = ConvertCPaths(subjects_open); + clp = ConvertCPaths(clips); - PolyTree64 pt; + PolyTree64 tree; Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); if (sub.size() > 0) clipper.AddSubject(sub); if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); - if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), pt, sol_open)) + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) return -1; // clipping bug - should never happen :) - solution = CreateCPolyTree64(pt); - solution_open = CreateCPaths64(sol_open); + sol_tree = CreateCPolyTree(tree, (int64_t)1); + solution_open = CreateCPaths(sol_open); return 0; //success !! } @@ -298,57 +429,54 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, const double scale = std::pow(10, precision); Paths64 sub, sub_open, clp, sol, sol_open; - sub = ConvertCPathsD(subjects, scale); - sub_open = ConvertCPathsD(subjects_open, scale); - clp = ConvertCPathsD(clips, scale); + sub = ConvertCPathsDToPaths64(subjects, scale); + sub_open = ConvertCPathsDToPaths64(subjects_open, scale); + clp = ConvertCPathsDToPaths64(clips, scale); Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); if (sub.size() > 0) clipper.AddSubject(sub); - if (sub_open.size() > 0) - clipper.AddOpenSubject(sub_open); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) return -1; - - if (sol.size() > 0) solution = CreateCPathsD(sol, 1 / scale); - if (sol_open.size() > 0) - solution_open = CreateCPathsD(sol_open, 1 / scale); + solution = CreateCPathsDFromPaths64(sol, 1 / scale); + solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale); return 0; } -EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, +EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, - CPolyTreeD*& solution, CPathsD& solution_open, int precision, + CPolyTreeD& solution, CPathsD& solution_open, int precision, bool preserve_collinear, bool reverse_solution) { if (precision < -8 || precision > 8) return -5; if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4; if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3; - const double scale = std::pow(10, precision); + double scale = std::pow(10, precision); + + int err = 0; Paths64 sub, sub_open, clp, sol_open; - sub = ConvertCPathsD(subjects, scale); - sub_open = ConvertCPathsD(subjects_open, scale); - clp = ConvertCPathsD(clips, scale); + sub = ConvertCPathsDToPaths64(subjects, scale); + sub_open = ConvertCPathsDToPaths64(subjects_open, scale); + clp = ConvertCPathsDToPaths64(clips, scale); - PolyTree64 sol; + PolyTree64 tree; Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); if (sub.size() > 0) clipper.AddSubject(sub); - if (sub_open.size() > 0) - clipper.AddOpenSubject(sub_open); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); - if (!clipper.Execute(ClipType(cliptype), - FillRule(fillrule), sol, sol_open)) return -1; + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) + return -1; // clipping bug - should never happen :) - solution = CreateCPolyTreeD(sol, 1 / scale); - if (sol_open.size() > 0) - solution_open = CreateCPathsD(sol_open, 1 / scale); - return 0; + solution = CreateCPolyTree(tree, 1/scale); + solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale); + return 0; //success !! } EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, @@ -356,14 +484,13 @@ EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, double arc_tolerance, bool reverse_solution) { Paths64 pp; - pp = ConvertCPaths64(paths); - + pp = ConvertCPaths(paths); ClipperOffset clip_offset( miter_limit, arc_tolerance, reverse_solution); clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); Paths64 result; clip_offset.Execute(delta, result); - return CreateCPaths64(result); + return CreateCPaths(result); } EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, @@ -372,13 +499,15 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, 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); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); Paths64 result; clip_offset.Execute(delta * scale, result); - return CreateCPathsD(result, 1/scale); + + return CreateCPathsDFromPaths64(result, 1 / scale); } EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths) @@ -386,9 +515,9 @@ EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths) if (CRectIsEmpty(rect) || !paths) return nullptr; Rect64 r64 = CRectToRect(rect); class RectClip64 rc(r64); - Paths64 pp = ConvertCPaths64(paths); + Paths64 pp = ConvertCPaths(paths); Paths64 result = rc.Execute(pp); - return CreateCPaths64(result); + return CreateCPaths(result); } EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision) @@ -399,10 +528,11 @@ EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int RectD r = CRectToRect(rect); Rect64 rec = ScaleRect<int64_t, double>(r, scale); - Paths64 pp = ConvertCPathsD(paths, scale); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); class RectClip64 rc(rec); Paths64 result = rc.Execute(pp); - return CreateCPathsD(result, 1/scale); + + return CreateCPathsDFromPaths64(result, 1 / scale); } EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, @@ -411,9 +541,9 @@ EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, if (CRectIsEmpty(rect) || !paths) return nullptr; Rect64 r = CRectToRect(rect); class RectClipLines64 rcl (r); - Paths64 pp = ConvertCPaths64(paths); + Paths64 pp = ConvertCPaths(paths); Paths64 result = rcl.Execute(pp); - return CreateCPaths64(result); + return CreateCPaths(result); } EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, @@ -421,350 +551,13 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, { if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; + const double scale = std::pow(10, precision); Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale); class RectClipLines64 rcl(r); - Paths64 pp = ConvertCPathsD(paths, scale); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); Paths64 result = rcl.Execute(pp); - return CreateCPathsD(result, 1/scale); -} - -inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2) -{ - // 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; - return result; -} - -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); - CPath64 v = result; - v += 2; // skip counters - for (const Point64& pt : p) - { - *v++ = pt.x; - *v++ = pt.y; - } - return result; -} - -inline Path64 ConvertCPath64(const CPath64& p) -{ - Path64 result; - if (p && *p) - { - CPath64 v = p; - const size_t cnt = static_cast<size_t>(p[0]); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(Point64(*v++, *v++)); - int64_t x = *v++; - int64_t y = *v++; - result.push_back(Point64(x, y)); - } - } - return result; -} - -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 - for (size_t i = 0; i < cnt; ++i) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - - CPaths64 result = new int64_t* [cnt2 + 1]; - CPaths64 v = result; - *v++ = CreateCPath64(0, cnt2); // assign a counter path - for (const Path64& p : pp) - { - *v = CreateCPath64(p); - if (*v) ++v; - } - return result; -} - -inline Paths64 ConvertCPaths64(const CPaths64& pp) -{ - Paths64 result; - if (pp) - { - CPaths64 v = pp; - CPath64 cnts = pp[0]; - const size_t cnt = static_cast<size_t>(cnts[1]); // nb 2nd cnt - ++v; // skip cnts - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPath64(*v++)); - } - return result; -} - -inline CPathD CreateCPathD(size_t cnt1, size_t cnt2) -{ - // 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<double>(cnt1); - result[1] = static_cast<double>(cnt2); - return result; -} - -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); - CPathD v = result; - v += 2; // skip counters - for (const PointD& pt : p) - { - *v++ = pt.x; - *v++ = pt.y; - } - return result; -} - -inline PathD ConvertCPathD(const CPathD& p) -{ - PathD result; - if (p) - { - CPathD v = p; - size_t cnt = static_cast<size_t>(v[0]); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(PointD(*v++, *v++)); - double x = *v++; - double y = *v++; - result.push_back(PointD(x, y)); - } - } - return result; -} - -inline CPathsD CreateCPathsD(const PathsD& pp) -{ - size_t cnt = pp.size(), cnt2 = cnt; - // don't allocate space for empty paths - for (size_t i = 0; i < cnt; ++i) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - CPathsD result = new double * [cnt2 + 1]; - CPathsD v = result; - *v++ = CreateCPathD(0, cnt2); // assign counter path - for (const PathD& p : pp) - { - *v = CreateCPathD(p); - if (*v) { ++v; } - } - return result; -} - -inline PathsD ConvertCPathsD(const CPathsD& pp) -{ - PathsD result; - if (pp) - { - CPathsD v = pp; - CPathD cnts = v[0]; - size_t cnt = static_cast<size_t>(cnts[1]); - ++v; // skip cnts path - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPathD(*v++)); - } - return result; -} - -inline Path64 ConvertCPathD(const CPathD& p, double scale) -{ - Path64 result; - if (p) - { - CPathD v = p; - size_t cnt = static_cast<size_t>(*v); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(PointD(*v++, *v++)); - double x = *v++ * scale; - double y = *v++ * scale; - result.push_back(Point64(x, y)); - } - } - return result; -} - -inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale) -{ - Paths64 result; - if (pp) - { - CPathsD v = pp; - CPathD cnts = v[0]; - size_t cnt = static_cast<size_t>(cnts[1]); - result.reserve(cnt); - ++v; // skip cnts path - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPathD(*v++, scale)); - } - return result; -} - -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); - CPathD v = result; - v += 2; // skip cnts - for (const Point64& pt : p) - { - *v++ = pt.x * scale; - *v++ = pt.y * scale; - } - return result; -} - -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) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - CPathsD result = new double* [cnt2 + 1]; - CPathsD v = result; - *v++ = CreateCPathD(0, cnt2); - for (const Path64& p : pp) - { - *v = CreateCPathD(p, scale); - if (*v) ++v; - } - return result; -} - -inline void InitCPolyPath64(CPolyTree64* cpt, - bool is_hole, const std::unique_ptr <PolyPath64>& pp) -{ - cpt->polygon = CreateCPath64(pp->Polygon()); - cpt->is_hole = is_hole; - size_t child_cnt = pp->Count(); - cpt->child_count = static_cast<uint32_t>(child_cnt); - cpt->childs = nullptr; - if (!child_cnt) return; - cpt->childs = new CPolyPath64[child_cnt]; - CPolyPath64* child = cpt->childs; - for (const std::unique_ptr <PolyPath64>& pp_child : *pp) - InitCPolyPath64(child++, !is_hole, pp_child); -} - -inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt) -{ - CPolyTree64* result = new CPolyTree64(); - result->polygon = nullptr; - result->is_hole = false; - size_t child_cnt = pt.Count(); - result->childs = nullptr; - result->child_count = static_cast<uint32_t>(child_cnt); - if (!child_cnt) return result; - result->childs = new CPolyPath64[child_cnt]; - CPolyPath64* child = result->childs; - for (const std::unique_ptr <PolyPath64>& pp : pt) - InitCPolyPath64(child++, true, pp); - return result; -} - -inline void DisposeCPolyPath64(CPolyPath64* cpp) -{ - if (!cpp->child_count) return; - CPolyPath64* child = cpp->childs; - for (size_t i = 0; i < cpp->child_count; ++i) - DisposeCPolyPath64(child); - delete[] cpp->childs; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt) -{ - if (!cpt) return; - DisposeCPolyPath64(cpt); - delete cpt; - cpt = nullptr; -} - -inline void InitCPolyPathD(CPolyTreeD* cpt, - bool is_hole, const std::unique_ptr <PolyPath64>& pp, double scale) -{ - cpt->polygon = CreateCPathD(pp->Polygon(), scale); - cpt->is_hole = is_hole; - size_t child_cnt = pp->Count(); - cpt->child_count = static_cast<uint32_t>(child_cnt); - cpt->childs = nullptr; - if (!child_cnt) return; - cpt->childs = new CPolyPathD[child_cnt]; - CPolyPathD* child = cpt->childs; - for (const std::unique_ptr <PolyPath64>& pp_child : *pp) - InitCPolyPathD(child++, !is_hole, pp_child, scale); -} - -inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale) -{ - CPolyTreeD* result = new CPolyTreeD(); - result->polygon = nullptr; - result->is_hole = false; - size_t child_cnt = pt.Count(); - result->child_count = static_cast<uint32_t>(child_cnt); - result->childs = nullptr; - if (!child_cnt) return result; - result->childs = new CPolyPathD[child_cnt]; - CPolyPathD* child = result->childs; - for (const std::unique_ptr <PolyPath64>& pp : pt) - InitCPolyPathD(child++, true, pp, scale); - return result; -} - -inline void DisposeCPolyPathD(CPolyPathD* cpp) -{ - if (!cpp->child_count) return; - CPolyPathD* child = cpp->childs; - for (size_t i = 0; i < cpp->child_count; ++i) - DisposeCPolyPathD(child++); - delete[] cpp->childs; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt) -{ - if (!cpt) return; - DisposeCPolyPathD(cpt); - delete cpt; - cpt = nullptr; + return CreateCPathsDFromPaths64(result, 1 / scale); } } // end Clipper2Lib namespace diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h index b5ac6aa9a1..0f516b60e8 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 18 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -14,11 +14,11 @@ #include <type_traits> #include <vector> -#include "clipper.core.h" -#include "clipper.engine.h" -#include "clipper.offset.h" -#include "clipper.minkowski.h" -#include "clipper.rectclip.h" +#include "clipper2/clipper.core.h" +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.offset.h" +#include "clipper2/clipper.minkowski.h" +#include "clipper2/clipper.rectclip.h" namespace Clipper2Lib { @@ -341,6 +341,19 @@ namespace Clipper2Lib { details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " "); } + template<typename T, typename U> + inline constexpr void MakePathGeneric(const T an_array, + size_t array_size, std::vector<U>& result) + { + result.reserve(array_size / 2); + for (size_t i = 0; i < array_size; i +=2) +#ifdef USINGZ + result.push_back( U{ an_array[i], an_array[i +1], 0} ); +#else + result.push_back( U{ an_array[i], an_array[i + 1]} ); +#endif + } + } // end details namespace inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) @@ -391,22 +404,6 @@ namespace Clipper2Lib { return true; } - namespace details { - - template<typename T, typename U> - inline constexpr void MakePathGeneric(const T list, size_t size, - std::vector<U>& result) - { - 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<typename T, typename std::enable_if< std::is_integral<T>::value && @@ -417,7 +414,7 @@ namespace Clipper2Lib { 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 + Path64 result; details::MakePathGeneric(list, size, result); return result; } @@ -431,7 +428,7 @@ namespace Clipper2Lib { { // 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); + Path64 result; details::MakePathGeneric(list, N, result); return result; } @@ -446,7 +443,7 @@ namespace Clipper2Lib { 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 + PathD result; details::MakePathGeneric(list, size, result); return result; } @@ -460,7 +457,7 @@ namespace Clipper2Lib { { // 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); + PathD result; details::MakePathGeneric(list, N, result); return result; } @@ -653,7 +650,7 @@ namespace Clipper2Lib { } template <typename T> - inline Path<T> SimplifyPath(const Path<T> path, + inline Path<T> SimplifyPath(const Path<T> &path, double epsilon, bool isClosedPath = true) { const size_t len = path.size(), high = len -1; @@ -662,7 +659,7 @@ namespace Clipper2Lib { std::vector<bool> flags(len); std::vector<double> distSqr(len); - size_t prior = high, curr = 0, start, next, prior2, next2; + size_t prior = high, curr = 0, start, next, prior2; if (isClosedPath) { distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); @@ -692,26 +689,25 @@ namespace Clipper2Lib { next = GetNext(curr, high, flags); if (next == prior) break; + // flag for removal the smaller of adjacent 'distances' 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 || isClosedPath) - distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); + prior2 = prior; + prior = curr; curr = next; + next = GetNext(next, high, flags); } else - { - flags[curr] = true; - curr = next; - next = GetNext(next, high, flags); prior2 = GetPrior(prior, high, flags); + + flags[curr] = true; + curr = next; + next = GetNext(next, high, flags); + + if (isClosedPath || ((curr != high) && (curr != 0))) distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); - if (prior != 0 || isClosedPath) - distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); - } + if (isClosedPath || ((prior != 0) && (prior != high))) + distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); } Path<T> result; result.reserve(len); @@ -721,7 +717,7 @@ namespace Clipper2Lib { } template <typename T> - inline Paths<T> SimplifyPaths(const Paths<T> paths, + inline Paths<T> SimplifyPaths(const Paths<T> &paths, double epsilon, bool isClosedPath = true) { Paths<T> result; diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h index 71c221bb50..ebddd08a59 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 28 January 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Minkowski Sum and Difference * @@ -13,7 +13,7 @@ #include <cstdlib> #include <vector> #include <string> -#include "clipper.core.h" +#include "clipper2/clipper.core.h" namespace Clipper2Lib { diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h index 8835fb0f45..30992bfa55 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 May 2023 * +* Date : 19 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -15,7 +15,9 @@ namespace Clipper2Lib { -enum class JoinType { Square, Round, Miter }; +enum class JoinType { Square, Bevel, Round, Miter }; +//Square : Joins are 'squared' at exactly the offset distance (more complex code) +//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster) enum class EndType {Polygon, Joined, Butt, Square, Round}; //Butt : offsets both sides of a path, with square blunt ends @@ -32,13 +34,13 @@ private: class Group { public: Paths64 paths_in; - Paths64 paths_out; - Path64 path; + std::vector<bool> is_hole_list; + std::vector<Rect64> bounds_list; + int lowest_path_idx = -1; 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) {} + Group(const Paths64& _paths, JoinType _join_type, EndType _end_type); }; int error_code_ = 0; @@ -49,9 +51,10 @@ private: double step_sin_ = 0.0; double step_cos_ = 0.0; PathD norms; + Path64 path_out; Paths64 solution; std::vector<Group> groups_; - JoinType join_type_ = JoinType::Square; + JoinType join_type_ = JoinType::Bevel; EndType end_type_ = EndType::Polygon; double miter_limit_ = 0.0; @@ -64,14 +67,17 @@ private: #endif DeltaCallback64 deltaCallback64_ = nullptr; - 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); + size_t CalcSolutionCapacity(); + bool CheckReverseOrientation(); + void DoBevel(const Path64& path, size_t j, size_t k); + void DoSquare(const Path64& path, size_t j, size_t k); + void DoMiter(const Path64& path, size_t j, size_t k, double cos_a); + void DoRound(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); - void OffsetPoint(Group& group, Path64& path, size_t j, size_t k); + void OffsetPolygon(Group& group, const Path64& path); + void OffsetOpenJoined(Group& group, const Path64& path); + void OffsetOpenPath(Group& group, const Path64& path); + void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k); void DoGroupOffset(Group &group); void ExecuteInternal(double delta); public: diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h index bcbe7f436c..ff043f25f0 100644 --- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -13,8 +13,7 @@ #include <cstdlib> #include <vector> #include <queue> -#include "clipper.h" -#include "clipper.core.h" +#include "clipper2/clipper.core.h" namespace Clipper2Lib { diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.version.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.version.h new file mode 100644 index 0000000000..d7644067e2 --- /dev/null +++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.version.h @@ -0,0 +1,6 @@ +#ifndef CLIPPER_VERSION_H +#define CLIPPER_VERSION_H + +constexpr auto CLIPPER2_VERSION = "1.3.0"; + +#endif // CLIPPER_VERSION_H diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp index bed48b1541..9358b74b70 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 27 August 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -15,6 +15,7 @@ #include <algorithm> #include "clipper2/clipper.engine.h" +#include "clipper2/clipper.h" // https://github.com/AngusJohnson/Clipper2/discussions/334 // #discussioncomment-4248602 @@ -1469,13 +1470,14 @@ namespace Clipper2Lib { 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; } + else + SetOwner(e2.outrec, e1.outrec); //and e1 and e2 are maxima and are about to be dropped from the Actives list. e1.outrec = nullptr; @@ -1541,7 +1543,7 @@ namespace Clipper2Lib { //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 || + op2->pt == op2->next->pt || !preserve_collinear_ || DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) { @@ -1706,6 +1708,28 @@ namespace Clipper2Lib { return op; } + 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 + } + inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) { @@ -1717,9 +1741,13 @@ namespace Clipper2Lib { if (IsJoined(*e)) Split(*e, e->bot); - if (IsHorizontal(*e)) return; - InsertScanline(e->top.y); + if (IsHorizontal(*e)) + { + if (!IsOpen(*e)) TrimHorz(*e, preserve_collinear_); + return; + } + InsertScanline(e->top.y); CheckJoinLeft(*e, e->bot); CheckJoinRight(*e, e->bot, true); // (#500) } @@ -2139,7 +2167,7 @@ namespace Clipper2Lib { horz_seg_list_.end(), [](HorzSegment& hs) { return UpdateHorzSegment(hs); }); if (j < 2) return; - std::sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); + std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2; HorzSegmentList::iterator hs_end = hs1 +j; @@ -2451,35 +2479,6 @@ namespace Clipper2Lib { } } - 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 * @@ -2505,10 +2504,10 @@ namespace Clipper2Lib { 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); + //// remove 180 deg.spikes and also simplify + //// consecutive horizontals when PreserveCollinear = true + //if (!horzIsOpen && vertex_max != horz.vertex_top) + // TrimHorz(horz, PreserveCollinear); int64_t horz_left, horz_right; bool is_left_to_right = @@ -2537,6 +2536,9 @@ namespace Clipper2Lib { if (IsHotEdge(horz) && IsJoined(*e)) Split(*e, e->top); + //if (IsHotEdge(horz) != IsHotEdge(*e)) + // DoError(undefined_error_i); + if (IsHotEdge(horz)) { while (horz.vertex_top != vertex_max) @@ -2591,6 +2593,7 @@ namespace Clipper2Lib { { IntersectEdges(horz, *e, pt); SwapPositionsInAEL(horz, *e); + CheckJoinLeft(*e, pt); horz.curr_x = e->curr_x; e = horz.next_in_ael; } @@ -2598,6 +2601,7 @@ namespace Clipper2Lib { { IntersectEdges(*e, horz, pt); SwapPositionsInAEL(*e, horz); + CheckJoinRight(*e, pt); horz.curr_x = e->curr_x; e = horz.prev_in_ael; } @@ -2633,9 +2637,6 @@ namespace Clipper2Lib { 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); } @@ -2872,7 +2873,7 @@ namespace Clipper2Lib { if (!outrec->bounds.IsEmpty()) return true; CleanCollinear(outrec); if (!outrec->pts || - !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)){ + !BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){ return false;} outrec->bounds = GetBounds(outrec->path); return true; @@ -2947,7 +2948,7 @@ namespace Clipper2Lib { Path64 path; if (solutionOpen && outrec->is_open) { - if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + if (BuildPath64(outrec->pts, reverse_solution_, true, path)) solutionOpen->emplace_back(std::move(path)); } else @@ -2955,7 +2956,7 @@ namespace Clipper2Lib { // nb: CleanCollinear can add to outrec_list_ CleanCollinear(outrec); //closed paths should always return a Positive orientation - if (BuildPath64(outrec->pts, ReverseSolution, false, path)) + if (BuildPath64(outrec->pts, reverse_solution_, false, path)) solutionClosed.emplace_back(std::move(path)); } } @@ -2978,7 +2979,7 @@ namespace Clipper2Lib { if (outrec->is_open) { Path64 path; - if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + if (BuildPath64(outrec->pts, reverse_solution_, true, path)) open_paths.push_back(path); continue; } @@ -3055,14 +3056,14 @@ namespace Clipper2Lib { PathD path; if (solutionOpen && outrec->is_open) { - if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + if (BuildPathD(outrec->pts, reverse_solution_, 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_)) + if (BuildPathD(outrec->pts, reverse_solution_, false, path, invScale_)) solutionClosed.emplace_back(std::move(path)); } } @@ -3084,7 +3085,7 @@ namespace Clipper2Lib { if (outrec->is_open) { PathD path; - if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_)) open_paths.push_back(path); continue; } diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp index 5faa26176d..0282aa49bb 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 7 August 2023 * +* Date : 28 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -20,38 +20,63 @@ const double floating_point_tolerance = 1e-12; // Miscellaneous methods //------------------------------------------------------------------------------ -void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx) +inline bool ToggleBoolIf(bool val, bool condition) { - idx = -1; - r = MaxInvalidRect64; - int64_t lpx = 0; - for (int i = 0; i < static_cast<int>(paths.size()); ++i) - for (const Point64& p : paths[i]) - { - 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; + return condition ? !val : val; } -bool IsSafeOffset(const Rect64& r, double abs_delta) +void GetMultiBounds(const Paths64& paths, std::vector<Rect64>& recList) { - 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; + recList.reserve(paths.size()); + for (const Path64& path : paths) + { + if (path.size() < 1) + { + recList.push_back(InvalidRect64); + continue; + } + int64_t x = path[0].x, y = path[0].y; + Rect64 r = Rect64(x, y, x, y); + for (const Point64& pt : path) + { + if (pt.y > r.bottom) r.bottom = pt.y; + else if (pt.y < r.top) r.top = pt.y; + if (pt.x > r.right) r.right = pt.x; + else if (pt.x < r.left) r.left = pt.x; + } + recList.push_back(r); + } +} + +bool ValidateBounds(std::vector<Rect64>& recList, double delta) +{ + int64_t int_delta = static_cast<int64_t>(delta); + int64_t big = MAX_COORD - int_delta; + int64_t small = MIN_COORD + int_delta; + for (const Rect64& r : recList) + { + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.left < small || r.right > big || + r.top < small || r.bottom > big) return false; + } + return true; +} + +int GetLowestClosedPathIdx(std::vector<Rect64>& boundsList) +{ + int i = -1, result = -1; + Point64 botPt = Point64(INT64_MAX, INT64_MIN); + for (const Rect64& r : boundsList) + { + ++i; + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.bottom > botPt.y || (r.bottom == botPt.y && r.left < botPt.x)) + { + botPt = Point64(r.left, r.bottom); + result = static_cast<int>(i); + } + } + return result; } PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) @@ -125,6 +150,44 @@ inline void NegatePath(PathD& path) } } + +//------------------------------------------------------------------------------ +// ClipperOffset::Group methods +//------------------------------------------------------------------------------ + +ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type): + paths_in(_paths), join_type(_join_type), end_type(_end_type) +{ + bool is_joined = + (end_type == EndType::Polygon) || + (end_type == EndType::Joined); + for (Path64& p: paths_in) + StripDuplicates(p, is_joined); + + // get bounds of each path --> bounds_list + GetMultiBounds(paths_in, bounds_list); + + if (end_type == EndType::Polygon) + { + is_hole_list.reserve(paths_in.size()); + for (const Path64& path : paths_in) + is_hole_list.push_back(Area(path) < 0); + lowest_path_idx = GetLowestClosedPathIdx(bounds_list); + // the lowermost path must be an outer path, so if its orientation is negative, + // then flag the whole group is 'reversed' (will negate delta etc.) + // as this is much more efficient than reversing every path. + is_reversed = (lowest_path_idx >= 0) && is_hole_list[lowest_path_idx]; + if (is_reversed) is_hole_list.flip(); + } + else + { + lowest_path_idx = -1; + is_reversed = false; + is_hole_list.resize(paths_in.size()); + } +} + + //------------------------------------------------------------------------------ // ClipperOffset methods //------------------------------------------------------------------------------ @@ -147,10 +210,10 @@ void ClipperOffset::BuildNormals(const Path64& path) norms.clear(); norms.reserve(path.size()); if (path.size() == 0) return; - Path64::const_iterator path_iter, path_last_iter = --path.cend(); - for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter) + Path64::const_iterator path_iter, path_stop_iter = --path.cend(); + for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter) norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1))); - norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin()))); + norms.push_back(GetUnitNormal(*path_stop_iter, *(path.cbegin()))); } inline PointD TranslatePoint(const PointD& pt, double dx, double dy) @@ -200,7 +263,25 @@ PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b, } } -void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k) +void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k) +{ + PointD pt1, pt2; + if (j == k) + { + double abs_delta = std::abs(group_delta_); + pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y); + pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y); + } + else + { + pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y); + pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y); + } + path_out.push_back(Point64(pt1)); + path_out.push_back(Point64(pt2)); +} + +void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k) { PointD vec; if (j == k) @@ -228,8 +309,8 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t 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)); + path_out.push_back(Point64(ReflectPoint(pt, ptQ))); + path_out.push_back(Point64(pt)); } else { @@ -238,28 +319,28 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t #ifdef USINGZ pt.z = ptQ.z; #endif - group.path.push_back(Point64(pt)); + path_out.push_back(Point64(pt)); //get the second intersect point through reflecion - group.path.push_back(Point64(ReflectPoint(pt, ptQ))); + path_out.push_back(Point64(ReflectPoint(pt, ptQ))); } } -void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a) +void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a) { double q = group_delta_ / (cos_a + 1); #ifdef USINGZ - group.path.push_back(Point64( + path_out.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_out.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) +void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle) { if (deltaCallback64_) { // when deltaCallback64_ is assigned, group_delta_ won't be constant, @@ -280,29 +361,25 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k if (j == k) offsetVec.Negate(); #ifdef USINGZ - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); + path_out.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)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); #endif - if (angle > -PI + 0.01) // avoid 180deg concave + int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456 + for (int i = 1; i < steps; ++i) // ie 1 less than steps { - int steps = static_cast<int>(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_); + 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)); + path_out.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)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); #endif - - } } - group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } -void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) +void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k) { // Let A = change in angle where edges join // A == 0: ie no change in angle (flat join) @@ -323,57 +400,51 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) } if (std::fabs(group_delta_) <= floating_point_tolerance) { - group.path.push_back(path[j]); + path_out.push_back(path[j]); return; } - if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) - { - DoMiter(group, path, j, k, cos_a); - } - else if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) + if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593) { // is concave - group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); + path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_)); // this extra point is the only (simple) way to ensure that // path reversals are fully cleaned with the trailing clipper - group.path.push_back(path[j]); // (#405) - group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + path_out.push_back(path[j]); // (#405) + path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + } + else if (cos_a > 0.999 && join_type_ != JoinType::Round) + { + // almost straight - less than 2.5 degree (#424, #482, #526 & #724) + DoMiter(path, j, k, 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); + // miter unless the angle is sufficiently acute to exceed ML + if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a); + else DoSquare(path, j, k); } - else if (cos_a > 0.99 || join_type_ == JoinType::Square) // 0.99 ~= 8.1 deg. - DoSquare(group, path, j, k); + else if (join_type_ == JoinType::Round) + DoRound(path, j, k, std::atan2(sin_a, cos_a)); + else if ( join_type_ == JoinType::Bevel) + DoBevel(path, j, k); else - DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); + DoSquare(path, j, k); } -void ClipperOffset::OffsetPolygon(Group& group, Path64& path) +void ClipperOffset::OffsetPolygon(Group& group, const Path64& path) { - // when the path is contracting, make sure - // there is sufficient space to do so. //#593 - // nb: this will have a small impact on performance - double a = Area(path); - // contracting when orientation is opposite offset direction - if ((a < 0) != (group_delta_ < 0)) - { - Rect64 rec = GetBounds(path); - if (std::fabs(group_delta_) * 2 > rec.Width()) return; - } - + path_out.clear(); for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j) OffsetPoint(group, path, j, k); - group.paths_out.push_back(group.path); + solution.push_back(path_out); } -void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) +void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path) { OffsetPolygon(group, path); - std::reverse(path.begin(), path.end()); + Path64 reverse_path(path); + std::reverse(reverse_path.begin(), reverse_path.end()); //rebuild normals // BuildNormals(path); std::reverse(norms.begin(), norms.end()); @@ -381,39 +452,28 @@ void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) norms.erase(norms.begin()); NegatePath(norms); - group.path.clear(); - OffsetPolygon(group, path); + OffsetPolygon(group, reverse_path); } -void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) +void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path) { // do the line start cap if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0); if (std::fabs(group_delta_) <= floating_point_tolerance) - group.path.push_back(path[0]); + path_out.push_back(path[0]); else { switch (end_type_) { case EndType::Butt: -#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_)); -#endif - group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_)); + DoBevel(path, 0, 0); break; case EndType::Round: - DoRound(group, path, 0, 0, PI); + DoRound(path, 0, 0, PI); break; default: - DoSquare(group, path, 0, 0); + DoSquare(path, 0, 0); break; } } @@ -433,64 +493,42 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) group_delta_ = deltaCallback64_(path, norms, highI, highI); if (std::fabs(group_delta_) <= floating_point_tolerance) - group.path.push_back(path[highI]); + path_out.push_back(path[highI]); else { switch (end_type_) { case EndType::Butt: -#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_)); -#endif - group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); + DoBevel(path, highI, highI); break; case EndType::Round: - DoRound(group, path, highI, highI, PI); + DoRound(path, highI, highI, PI); break; default: - DoSquare(group, path, highI, highI); + DoSquare(path, highI, highI); break; } } for (size_t j = highI, k = 0; j > 0; k = j, --j) OffsetPoint(group, path, j, k); - group.paths_out.push_back(group.path); + solution.push_back(path_out); } void ClipperOffset::DoGroupOffset(Group& group) { - 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 (group.end_type == EndType::Polygon) { - 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_ = std::abs(delta_) * 0.5; + // a straight path (2 points) can now also be 'polygon' offset + // where the ends will be treated as (180 deg.) joins + if (group.lowest_path_idx < 0) delta_ = std::abs(delta_); + group_delta_ = (group.is_reversed) ? -delta_ : delta_; } + else + group_delta_ = std::abs(delta_);// *0.5; double abs_delta = std::fabs(group_delta_); - // do range checking - if (!IsSafeOffset(r, abs_delta)) + if (!ValidateBounds(group.bounds_list, abs_delta)) { DoError(range_error_i); error_code_ |= range_error_i; @@ -500,10 +538,9 @@ void ClipperOffset::DoGroupOffset(Group& group) join_type_ = group.join_type; end_type_ = group.end_type; - if (!deltaCallback64_ && - (group.join_type == JoinType::Round || group.end_type == EndType::Round)) + if (group.join_type == JoinType::Round || group.end_type == EndType::Round) { - //calculate a sensible number of steps (for 360 deg for the given offset) + // calculate a sensible number of steps (for 360 deg for the given offset) // arcTol - when arc_tolerance_ is undefined (0), the amount of // curve imprecision that's allowed is based on the size of the // offset (delta). Obviously very large offsets will almost always @@ -519,61 +556,80 @@ void ClipperOffset::DoGroupOffset(Group& group) steps_per_rad_ = steps_per_360 / (2 * PI); } - bool is_joined = - (end_type_ == EndType::Polygon) || - (end_type_ == EndType::Joined); - Paths64::iterator path_iter; - for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter) + std::vector<Rect64>::const_iterator path_rect_it = group.bounds_list.cbegin(); + std::vector<bool>::const_iterator is_hole_it = group.is_hole_list.cbegin(); + Paths64::const_iterator path_in_it = group.paths_in.cbegin(); + for ( ; path_in_it != group.paths_in.cend(); ++path_in_it, ++path_rect_it, ++is_hole_it) { - Path64 &path = *path_iter; - StripDuplicates(path, is_joined); - Path64::size_type cnt = path.size(); - if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) - continue; + if (!path_rect_it->IsValid()) continue; + Path64::size_type pathLen = path_in_it->size(); + path_out.clear(); - group.path.clear(); - if (cnt == 1) // single point - only valid with open paths + if (pathLen == 1) // single point { if (group_delta_ < 1) continue; + const Point64& pt = (*path_in_it)[0]; //single vertex so build a circle or square ... if (group.join_type == JoinType::Round) { double radius = abs_delta; - group.path = Ellipse(path[0], radius, radius); + int steps = static_cast<int>(std::ceil(steps_per_rad_ * 2 * PI)); //#617 + path_out = Ellipse(pt, radius, radius, steps); #ifdef USINGZ - for (auto& p : group.path) p.z = path[0].z; + for (auto& p : path_out) p.z = pt.z; #endif } else { int d = (int)std::ceil(abs_delta); - r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); - group.path = r.AsPath(); + Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d); + path_out = r.AsPath(); #ifdef USINGZ - for (auto& p : group.path) p.z = path[0].z; + for (auto& p : path_out) p.z = pt.z; #endif } - 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; - } + solution.push_back(path_out); + continue; + } // end of offsetting a single point - BuildNormals(path); - if (end_type_ == EndType::Polygon) OffsetPolygon(group, path); - else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path); - else OffsetOpenPath(group, path); - } + // when shrinking outer paths, make sure they can shrink this far (#593) + // also when shrinking holes, make sure they too can shrink this far (#715) + if ((group_delta_ > 0) == ToggleBoolIf(*is_hole_it, group.is_reversed) && + (std::min(path_rect_it->Width(), path_rect_it->Height()) <= -group_delta_ * 2) ) + continue; + + if ((pathLen == 2) && (group.end_type == EndType::Joined)) + end_type_ = (group.join_type == JoinType::Round) ? + EndType::Round : + EndType::Square; + + BuildNormals(*path_in_it); + if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it); + else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it); + else OffsetOpenPath(group, *path_in_it); } - solution.reserve(solution.size() + group.paths_out.size()); - copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution)); - group.paths_out.clear(); +} + + +size_t ClipperOffset::CalcSolutionCapacity() +{ + size_t result = 0; + for (const Group& g : groups_) + result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size(); + return result; +} + +bool ClipperOffset::CheckReverseOrientation() +{ + // nb: this assumes there's consistency in orientation between groups + bool is_reversed_orientation = false; + for (const Group& g : groups_) + if (g.end_type == EndType::Polygon) + { + is_reversed_orientation = g.is_reversed; + break; + } + return is_reversed_orientation; } void ClipperOffset::ExecuteInternal(double delta) @@ -581,29 +637,29 @@ void ClipperOffset::ExecuteInternal(double delta) error_code_ = 0; solution.clear(); if (groups_.size() == 0) return; + solution.reserve(CalcSolutionCapacity()); - if (std::abs(delta) < 0.5) + if (std::abs(delta) < 0.5) // ie: offset is insignificant { + Paths64::size_type sol_size = 0; + for (const Group& group : groups_) sol_size += group.paths_in.size(); + solution.reserve(sol_size); 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)); - } - } - else - { - temp_lim_ = (miter_limit_ <= 1) ? - 2.0 : - 2.0 / (miter_limit_ * miter_limit_); + return; + } - delta_ = delta; - std::vector<Group>::iterator git; - for (git = groups_.begin(); git != groups_.end(); ++git) - { - DoGroupOffset(*git); - if (!error_code_) continue; // all OK - solution.clear(); - } + temp_lim_ = (miter_limit_ <= 1) ? + 2.0 : + 2.0 / (miter_limit_ * miter_limit_); + + delta_ = delta; + std::vector<Group>::iterator git; + for (git = groups_.begin(); git != groups_.end(); ++git) + { + DoGroupOffset(*git); + if (!error_code_) continue; // all OK + solution.clear(); } } @@ -614,19 +670,17 @@ void ClipperOffset::Execute(double delta, Paths64& paths) ExecuteInternal(delta); if (!solution.size()) return; - paths = solution; + bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; - c.PreserveCollinear = false; + c.PreserveCollinear(false); //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; + c.ReverseSolution(reverse_solution_ != paths_reversed); #ifdef USINGZ - if (zCallback64_) { - c.SetZCallback(zCallback64_); - } + if (zCallback64_) { c.SetZCallback(zCallback64_); } #endif c.AddSubject(solution); - if (groups_[0].is_reversed) + if (paths_reversed) c.Execute(ClipType::Union, FillRule::Negative, paths); else c.Execute(ClipType::Union, FillRule::Positive, paths); @@ -640,18 +694,21 @@ void ClipperOffset::Execute(double delta, PolyTree64& polytree) ExecuteInternal(delta); if (!solution.size()) return; + bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; - c.PreserveCollinear = false; + c.PreserveCollinear(false); //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; + c.ReverseSolution (reverse_solution_ != paths_reversed); #ifdef USINGZ if (zCallback64_) { c.SetZCallback(zCallback64_); } #endif c.AddSubject(solution); - if (groups_[0].is_reversed) + + + if (paths_reversed) c.Execute(ClipType::Union, FillRule::Negative, polytree); else c.Execute(ClipType::Union, FillRule::Positive, polytree); diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp index 221cd9bb49..9aa0fc0f76 100644 --- a/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp +++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 6 August 2023 * +* Date : 8 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -24,11 +24,11 @@ namespace Clipper2Lib { for (const Point64& pt : path2) { PointInPolygonResult pip = PointInPolygon(pt, path1); - switch (pip) + switch (pip) { case PointInPolygonResult::IsOutside: ++io_count; break; - case PointInPolygonResult::IsInside: --io_count; break; - default: continue; + case PointInPolygonResult::IsInside: --io_count; break; + default: continue; } if (std::abs(io_count) > 1) break; } @@ -66,6 +66,56 @@ namespace Clipper2Lib { return true; } + inline bool IsHorizontal(const Point64& pt1, const Point64& pt2) + { + return pt1.y == pt2.y; + } + + inline bool GetSegmentIntersection(const Point64& p1, + const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip) + { + double res1 = CrossProduct(p1, p3, p4); + double res2 = CrossProduct(p2, p3, p4); + if (res1 == 0) + { + ip = p1; + if (res2 == 0) return false; // segments are collinear + else if (p1 == p3 || p1 == p4) return true; + //else if (p2 == p3 || p2 == p4) { ip = p2; return true; } + else if (IsHorizontal(p3, p4)) return ((p1.x > p3.x) == (p1.x < p4.x)); + else return ((p1.y > p3.y) == (p1.y < p4.y)); + } + else if (res2 == 0) + { + ip = p2; + if (p2 == p3 || p2 == p4) return true; + else if (IsHorizontal(p3, p4)) return ((p2.x > p3.x) == (p2.x < p4.x)); + else return ((p2.y > p3.y) == (p2.y < p4.y)); + } + if ((res1 > 0) == (res2 > 0)) return false; + + double res3 = CrossProduct(p3, p1, p2); + double res4 = CrossProduct(p4, p1, p2); + if (res3 == 0) + { + ip = p3; + if (p3 == p1 || p3 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p3.x > p1.x) == (p3.x < p2.x)); + else return ((p3.y > p1.y) == (p3.y < p2.y)); + } + else if (res4 == 0) + { + ip = p4; + if (p4 == p1 || p4 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p4.x > p1.x) == (p4.x < p2.x)); + else return ((p4.y > p1.y) == (p4.y < p2.y)); + } + if ((res3 > 0) == (res4 > 0)) return false; + + // segments must intersect to get here + return GetIntersectPoint(p1, p2, p3, p4, ip); + } + inline bool GetIntersection(const Path64& rectPath, const Point64& p, const Point64& p2, Location& loc, Point64& ip) { @@ -74,100 +124,84 @@ namespace Clipper2Lib { switch (loc) { case Location::Left: - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); - else if (p.y < rectPath[0].y && - SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) return true; + else if ((p.y < rectPath[0].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; + return true; } else return false; - break; case Location::Top: - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); - else if (p.x < rectPath[0].x && - SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) return true; + else if ((p.x < rectPath[0].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; + return true; } - else if (p.x > rectPath[1].x && - SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; + return true; } else return false; - break; case Location::Right: - if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); - else if (p.y < rectPath[0].y && - SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) return true; + else if ((p.y < rectPath[1].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; + return true; } else return false; - break; case Location::Bottom: - if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); - else if (p.x < rectPath[3].x && - SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) return true; + else if ((p.x < rectPath[3].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; + return true; } - else if (p.x > rectPath[2].x && - SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; + return true; } else return false; - break; default: // loc == rInside - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; + return true; } else return false; - break; } - return true; } inline Location GetAdjacentLocation(Location loc, bool isClockwise)