瀏覽代碼

Merge pull request #89025 from Chubercik/clipper2-1.3.0

clipper2: Update to 1.3.0
Rémi Verschelde 1 年之前
父節點
當前提交
7755079f39

+ 1 - 1
COPYRIGHT.txt

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

+ 2 - 2
modules/navigation/2d/nav_mesh_generator_2d.cpp

@@ -789,7 +789,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
 		const int rect_end_y = baking_rect.position[1] + baking_rect.size[1] + baking_rect_offset.y;
 
 		Rect64 clipper_rect = Rect64(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y);
-		RectClip rect_clip = RectClip(clipper_rect);
+		RectClip64 rect_clip = RectClip64(clipper_rect);
 
 		traversable_polygon_paths = rect_clip.Execute(traversable_polygon_paths);
 		obstruction_polygon_paths = rect_clip.Execute(obstruction_polygon_paths);
@@ -821,7 +821,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
 		const int rect_end_y = baking_rect.position[1] + baking_rect.size[1] + baking_rect_offset.y - border_size;
 
 		Rect64 clipper_rect = Rect64(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y);
-		RectClip rect_clip = RectClip(clipper_rect);
+		RectClip64 rect_clip = RectClip64(clipper_rect);
 
 		path_solution = rect_clip.Execute(path_solution);
 	}

+ 5 - 2
thirdparty/README.md

@@ -94,14 +94,17 @@ Files extracted from upstream source:
 ## clipper2
 
 - Upstream: https://github.com/AngusJohnson/Clipper2
-- Version: 1.2.2 (756c5079aacab5837e812a143c59dc48a09f22e7, 2023)
+- Version: 1.3.0 (98db5662e8dd1808a5a7b50c5605a2289bb390e8, 2023)
 - License: BSL 1.0
 
 Files extracted from upstream source:
 
-- `CPP/Clipper2Lib` folder
+- `CPP/Clipper2Lib/` folder (in root)
 - `LICENSE`
 
+Apply the patches in the `patches/` folder when syncing on newer upstream
+commits.
+
 
 ## cvtt
 

+ 93 - 95
thirdparty/clipper2/include/clipper2/clipper.core.h

@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  22 March 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"
 
 #define CLIPPER2_THROW(exception) std::abort()
 
@@ -44,15 +45,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
+
+#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;
@@ -72,6 +85,8 @@ namespace Clipper2Lib
       CLIPPER2_THROW(Clipper2Exception(scale_error));
     case non_pair_error_i:
       CLIPPER2_THROW(Clipper2Exception(non_pair_error));
+    case undefined_error_i:
+      CLIPPER2_THROW(Clipper2Exception(undefined_error));
     case range_error_i:
       CLIPPER2_THROW(Clipper2Exception(range_error));
     }
@@ -80,6 +95,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
@@ -132,10 +148,11 @@ namespace Clipper2Lib
       return Point(x * scale, y * scale, z);
     }
 
+    void SetZ(const int64_t z_value) { z = z_value; }
 
     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;
     }
 
@@ -172,7 +189,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
@@ -220,6 +237,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>
@@ -235,19 +260,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)
       {
@@ -255,11 +274,13 @@ namespace Clipper2Lib
       }
       else
       {
-        left = top = std::numeric_limits<T>::max();
-        right = bottom = -std::numeric_limits<int64_t>::max();
+        left = top = (std::numeric_limits<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; }
@@ -307,10 +328,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;
     }
   };
@@ -338,16 +362,22 @@ 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)
   {
-    auto xmin = std::numeric_limits<T>::max();
-    auto ymin = std::numeric_limits<T>::max();
+    auto xmin = (std::numeric_limits<T>::max)();
+    auto ymin = (std::numeric_limits<T>::max)();
     auto xmax = std::numeric_limits<T>::lowest();
     auto ymax = std::numeric_limits<T>::lowest();
     for (const auto& p : path)
@@ -363,8 +393,8 @@ namespace Clipper2Lib
   template <typename T>
   Rect<T> GetBounds(const Paths<T>& paths)
   {
-    auto xmin = std::numeric_limits<T>::max();
-    auto ymin = std::numeric_limits<T>::max();
+    auto xmin = (std::numeric_limits<T>::max)();
+    auto ymin = (std::numeric_limits<T>::max)();
     auto xmax = std::numeric_limits<T>::lowest();
     auto ymax = std::numeric_limits<T>::lowest();
     for (const Path<T>& path : paths)
@@ -428,7 +458,7 @@ namespace Clipper2Lib
   }
 
   template <typename T1, typename T2>
-  inline Path<T1> ScalePath(const Path<T2>& path, 
+  inline Path<T1> ScalePath(const Path<T2>& path,
     double scale, int& error_code)
   {
     return ScalePath<T1, T2>(path, scale, scale, error_code);
@@ -488,26 +518,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)
   {
@@ -560,48 +570,32 @@ namespace Clipper2Lib
   }
 
   template<typename T>
-  inline Path<T> StripDuplicates(const Path<T>& path, bool is_closed_path)
+  inline void StripDuplicates( Path<T>& path, bool is_closed_path)
   {
-    if (path.size() == 0) return Path<T>();
-    Path<T> result;
-    result.reserve(path.size());
-    typename Path<T>::const_iterator path_iter = path.cbegin();
-    Point<T> first_pt = *path_iter++, last_pt = first_pt;
-    result.push_back(first_pt);
-    for (; path_iter != path.cend(); ++path_iter)
-    {
-      if (*path_iter != last_pt)
-      {
-        last_pt = *path_iter;
-        result.push_back(last_pt);
-      }
-    }
-    if (!is_closed_path) return result;
-    while (result.size() > 1 && result.back() == first_pt) result.pop_back();
-    return result;
+    //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());
+    if (is_closed_path)
+      while (path.size() > 1 && path.back() == path.front()) path.pop_back();
   }
 
   template<typename T>
-  inline Paths<T> StripDuplicates(const Paths<T>& paths, bool is_closed_path)
+  inline void StripDuplicates( Paths<T>& paths, bool is_closed_path)
   {
-    Paths<T> result;
-    result.reserve(paths.size());
-    for (typename Paths<T>::const_iterator paths_citer = paths.cbegin();
-      paths_citer != paths.cend(); ++paths_citer)
+    for (typename Paths<T>::iterator paths_citer = paths.begin();
+      paths_citer != paths.end(); ++paths_citer)
     {
-      result.push_back(StripDuplicates(*paths_citer, is_closed_path));
+      StripDuplicates(*paths_citer, is_closed_path);
     }
-    return result;
   }
 
   // Miscellaneous ------------------------------------------------------------
 
   inline void CheckPrecision(int& precision, int& error_code)
   {
-    if (precision >= -8 && precision <= 8) return;
+    if (precision >= -MAX_DECIMAL_PRECISION && precision <= MAX_DECIMAL_PRECISION) return;
     error_code |= precision_error_i; // non-fatal error
-    DoError(precision_error_i);      // unless exceptions enabled
-    precision = precision > 8 ? 8 : -8;
+    DoError(precision_error_i);      // does nothing unless exceptions enabled
+    precision = precision > 0 ? MAX_DECIMAL_PRECISION : -MAX_DECIMAL_PRECISION;
   }
 
   inline void CheckPrecision(int& precision)
@@ -693,29 +687,27 @@ namespace Clipper2Lib
     //nb: This statement is premised on using Cartesian coordinates
     return Area<T>(poly) >= 0;
   }
-
-  inline int64_t CheckCastInt64(double val)
-  {
-    if ((val >= max_coord) || (val <= min_coord)) return INVALID;
-    else return static_cast<int64_t>(val);
-  }
-
+  
   inline bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b,
     const Point64& ln2a, const Point64& ln2b, Point64& ip)
   {  
     // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
-
     double dx1 = static_cast<double>(ln1b.x - ln1a.x);
     double dy1 = static_cast<double>(ln1b.y - ln1a.y);
     double dx2 = static_cast<double>(ln2b.x - ln2a.x);
     double dy2 = static_cast<double>(ln2b.y - ln2a.y);
+
     double det = dy1 * dx2 - dy2 * dx1;
-    if (det == 0.0) return 0;
-    double qx = dx1 * ln1a.y - dy1 * ln1a.x;
-    double qy = dx2 * ln2a.y - dy2 * ln2a.x;
-    ip.x = CheckCastInt64((dx1 * qy - dx2 * qx) / det);
-    ip.y = CheckCastInt64((dy1 * qy - dy2 * qx) / det);
-    return (ip.x != INVALID && ip.y != INVALID);
+    if (det == 0.0) return false;
+    double t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det;
+    if (t <= 0.0) ip = ln1a;        // ?? check further (see also #568)
+    else if (t >= 1.0) ip = ln1b;   // ?? check further
+    else
+    {
+      ip.x = static_cast<int64_t>(ln1a.x + t * dx1);
+      ip.y = static_cast<int64_t>(ln1a.y + t * dy1);
+    }
+    return true;
   }
 
   inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
@@ -739,8 +731,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);
@@ -750,9 +743,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 };

+ 48 - 21
thirdparty/clipper2/include/clipper2/clipper.engine.h

@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  26 March 2023                                                   *
+* Date      :  22 November 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  This is the main polygon clipping module                        *
@@ -10,9 +10,8 @@
 #ifndef CLIPPER_ENGINE_H
 #define CLIPPER_ENGINE_H
 
-constexpr auto CLIPPER2_VERSION = "1.2.2";
-
 #include <cstdlib>
+#include <stdint.h> //#541
 #include <iostream>
 #include <queue>
 #include <vector>
@@ -20,7 +19,7 @@ constexpr auto CLIPPER2_VERSION = "1.2.2";
 #include <numeric>
 #include <memory>
 
-#include "clipper.core.h"
+#include "clipper2/clipper.core.h"
 
 namespace Clipper2Lib {
 
@@ -91,10 +90,11 @@ namespace Clipper2Lib {
 		OutPt* pts = nullptr;
 		PolyPath* polypath = nullptr;
 		OutRecList* splits = nullptr;
+		OutRec* recursive_split = nullptr;
 		Rect64 bounds = {};
 		Path64 path;
 		bool is_open = false;
-		bool horz_done = false;
+
 		~OutRec() { 
 			if (splits) delete splits;
 			// nb: don't delete the split pointers
@@ -179,6 +179,20 @@ namespace Clipper2Lib {
 	typedef std::vector<LocalMinima_ptr> LocalMinimaList;
 	typedef std::vector<IntersectNode> IntersectNodeList;
 
+	// ReuseableDataContainer64 ------------------------------------------------
+
+	class ReuseableDataContainer64 {
+	private:
+		friend class ClipperBase;
+		LocalMinimaList minima_list_;
+		std::vector<Vertex*> vertex_lists_;
+		void AddLocMin(Vertex& vert, PathType polytype, bool is_open);
+	public:
+		virtual ~ReuseableDataContainer64();
+		void Clear();
+		void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
+	};
+
 	// ClipperBase -------------------------------------------------------------
 
 	class ClipperBase {
@@ -235,7 +249,6 @@ namespace Clipper2Lib {
 		void DoTopOfScanbeam(const int64_t top_y);
 		Active *DoMaxima(Active &e);
 		void JoinOutrecPaths(Active &e1, Active &e2);
-		void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec);
 		void FixSelfIntersects(OutRec* outrec);
 		void DoSplitOp(OutRec* outRec, OutPt* splitOp);
 		
@@ -249,6 +262,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;
@@ -256,8 +271,8 @@ namespace Clipper2Lib {
 		bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
 		void CleanCollinear(OutRec* outrec);
 		bool CheckBounds(OutRec* outrec);
+		bool CheckSplitOwner(OutRec* outrec, OutRecList* splits);
 		void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
-		void DeepCheckOwners(OutRec* outrec, PolyPath* polypath);
 #ifdef USINGZ
 		ZCallback64 zCallback_ = nullptr;
 		void SetZ(const Active& e1, const Active& e2, Point64& pt);
@@ -267,10 +282,13 @@ namespace Clipper2Lib {
 		void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
 	public:
 		virtual ~ClipperBase();
-		int ErrorCode() { return error_code_; };
-		bool PreserveCollinear = true;
-		bool ReverseSolution = false;
+		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
 		int64_t DefaultZ = 0;
 #endif
@@ -330,12 +348,12 @@ namespace Clipper2Lib {
 			childs_.resize(0);
 		}
 
-		const PolyPath64* operator [] (size_t index) const
+		PolyPath64* operator [] (size_t index) const
 		{ 
-			return childs_[index].get(); 
+			return childs_[index].get(); //std::unique_ptr
 		} 
 
-		const PolyPath64* Child(size_t index) const
+		PolyPath64* Child(size_t index) const
 		{
 			return childs_[index].get();
 		}
@@ -375,24 +393,24 @@ namespace Clipper2Lib {
 	class PolyPathD : public PolyPath {
 	private:
 		PolyPathDList childs_;
-		double inv_scale_;
+		double scale_;
 		PathD polygon_;
 	public:
 		explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
 		{
-			inv_scale_ = parent ? parent->inv_scale_ : 1.0;
+			scale_ = parent ? parent->scale_ : 1.0;
 		}
 
 		~PolyPathD() {
 			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();
 		}
@@ -400,14 +418,23 @@ namespace Clipper2Lib {
 		PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
 		PolyPathDList::const_iterator end() const { return childs_.cend(); }
 
-		void SetInvScale(double value) { inv_scale_ = value; }
-		double InvScale() { return inv_scale_; }
+		void SetScale(double value) { scale_ = value; }
+		double Scale() const { return scale_; }
+		
 		PolyPathD* AddChild(const Path64& path) override
 		{
 			int error_code = 0;
 			auto p = std::make_unique<PolyPathD>(this);
 			PolyPathD* result = childs_.emplace_back(std::move(p)).get();
-			result->polygon_ = ScalePath<double, int64_t>(path, inv_scale_, error_code);
+			result->polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
+			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;
 		}
 
@@ -595,7 +622,7 @@ namespace Clipper2Lib {
 			if (ExecuteInternal(clip_type, fill_rule, true))
 			{
 				polytree.Clear();
-				polytree.SetInvScale(invScale_);
+				polytree.SetScale(invScale_);
 				open_paths.clear();
 				BuildTreeD(polytree, open_paths);
 			}

+ 322 - 531
thirdparty/clipper2/include/clipper2/clipper.export.h

@@ -1,39 +1,78 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  23 March 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 struct CPolyPath64 {
-  CPath64       polygon;
-  uint32_t      is_hole;
-  uint32_t      child_count;
-  CPolyPath64*  childs;
-}
-CPolyTree64;
+typedef int64_t* CPaths64;
+typedef double*  CPathD;
+typedef double*  CPathsD;
 
-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);
-
-// 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 void DisposeArray64(int64_t*& p)
+{
+  delete[] p;
+}
+
+EXTERN_DLL_EXPORT void DisposeArrayD(double*& p)
+{
+  delete[] p;
+}
+
 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, 
@@ -157,78 +181,185 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
   int precision = 2, double miter_limit = 2.0,
   double arc_tolerance = 0.0, bool reverse_solution = false);
 
-// ExecuteRectClip & ExecuteRectClipLines:
-EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect,
-  const CPaths64 paths, bool convex_only = false);
-EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect,
-  const CPathsD paths, int precision = 2, bool convex_only = false);
-EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect,
+// RectClip & RectClipLines:
+EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
+  const CPaths64 paths);
+EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
+  const CPathsD paths, int precision = 2);
+EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
   const CPaths64 paths);
-EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect,
+EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
   const CPathsD paths, int precision = 2);
 
 //////////////////////////////////////////////////////
 // INTERNAL FUNCTIONS
 //////////////////////////////////////////////////////
 
-inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2);
-inline CPath64 CreateCPath64(const Path64& p);
-inline CPaths64 CreateCPaths64(const Paths64& pp);
-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;
+}
 
-EXTERN_DLL_EXPORT const char* Version()
+
+CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale)
 {
-  return CLIPPER2_VERSION;
+  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;
 }
 
-EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p)
+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)
 {
-  if (p) delete[] p;
+  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;
 }
 
-EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp)
+template <typename T>
+static void CreateCPolyPath(const PolyPath64* pp, T*& v, T scale)
 {
-  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;
+  *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);
 }
 
-EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p)
+template <typename T>
+static T* CreateCPolyTree(const PolyTree64& tree, T scale)
 {
-  if (p) delete[] p;
+  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;
 }
 
-EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp)
+//////////////////////////////////////////////////////
+// EXPORTED FUNCTION DEFINITIONS
+//////////////////////////////////////////////////////
+
+EXTERN_DLL_EXPORT const char* Version()
 {
-  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;
+  return CLIPPER2_VERSION;
 }
 
 EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, 
@@ -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,28 +499,28 @@ 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 ExecuteRectClip64(const CRect64& rect,
-  const CPaths64 paths, bool convex_only)
+EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths)
 {
   if (CRectIsEmpty(rect) || !paths) return nullptr;
   Rect64 r64 = CRectToRect(rect);
-  class RectClip rc(r64);
-  Paths64 pp = ConvertCPaths64(paths);
-  Paths64 result = rc.Execute(pp, convex_only);
-  return CreateCPaths64(result);
+  class RectClip64 rc(r64);
+  Paths64 pp = ConvertCPaths(paths);
+  Paths64 result = rc.Execute(pp);
+  return CreateCPaths(result);
 }
 
-EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect,
-  const CPathsD paths, int precision, bool convex_only)
+EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision)
 {
   if (CRectIsEmpty(rect) || !paths) return nullptr;
   if (precision < -8 || precision > 8) return nullptr;
@@ -401,372 +528,36 @@ EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect,
 
   RectD r = CRectToRect(rect);
   Rect64 rec = ScaleRect<int64_t, double>(r, scale);
-  Paths64 pp = ConvertCPathsD(paths, scale);
-  class RectClip rc(rec);
-  Paths64 result = rc.Execute(pp, convex_only);
-  return CreateCPathsD(result, 1/scale);
+  Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
+  class RectClip64 rc(rec);
+  Paths64 result = rc.Execute(pp);
+
+  return CreateCPathsDFromPaths64(result, 1 / scale);
 }
 
-EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect,
+EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
   const CPaths64 paths)
 {
   if (CRectIsEmpty(rect) || !paths) return nullptr;
   Rect64 r = CRectToRect(rect);
-  class RectClipLines rcl (r);
-  Paths64 pp = ConvertCPaths64(paths);
+  class RectClipLines64 rcl (r);
+  Paths64 pp = ConvertCPaths(paths);
   Paths64 result = rcl.Execute(pp);
-  return CreateCPaths64(result);
+  return CreateCPaths(result);
 }
 
-EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect,
+EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
   const CPathsD paths, int precision)
 {
   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 RectClipLines rcl(r);
-  Paths64 pp = ConvertCPathsD(paths, scale);
+  class RectClipLines64 rcl(r);
+  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

+ 147 - 142
thirdparty/clipper2/include/clipper2/clipper.h

@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  23 March 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 {
 
@@ -161,60 +161,61 @@ namespace Clipper2Lib {
     return ScalePaths<double, int64_t>(solution, 1 / scale, error_code);
   }
 
-  inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
+  template <typename T>
+  inline Path<T> TranslatePath(const Path<T>& path, T dx, T dy)
   {
-    Path64 result;
+    Path<T> result;
     result.reserve(path.size());
     std::transform(path.begin(), path.end(), back_inserter(result),
-      [dx, dy](const auto& pt) { return Point64(pt.x + dx, pt.y +dy); });
+      [dx, dy](const auto& pt) { return Point<T>(pt.x + dx, pt.y +dy); });
     return result;
   }
 
+  inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
+  {
+    return TranslatePath<int64_t>(path, dx, dy);
+  }
+
   inline PathD TranslatePath(const PathD& path, double dx, double dy)
   {
-    PathD result;
-    result.reserve(path.size());
-    std::transform(path.begin(), path.end(), back_inserter(result),
-      [dx, dy](const auto& pt) { return PointD(pt.x + dx, pt.y + dy); });
-    return result;
+    return TranslatePath<double>(path, dx, dy);
   }
 
-  inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
+  template <typename T>
+  inline Paths<T> TranslatePaths(const Paths<T>& paths, T dx, T dy)
   {
-    Paths64 result;
+    Paths<T> result;
     result.reserve(paths.size());
     std::transform(paths.begin(), paths.end(), back_inserter(result),
       [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
     return result;
   }
 
+  inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
+  {
+    return TranslatePaths<int64_t>(paths, dx, dy);
+  }
+
   inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
   {
-    PathsD result;
-    result.reserve(paths.size());
-    std::transform(paths.begin(), paths.end(), back_inserter(result),
-      [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
-    return result;
+    return TranslatePaths<double>(paths, dx, dy);
   }
 
-  inline Paths64 ExecuteRectClip(const Rect64& rect, 
-    const Paths64& paths, bool convex_only = false)
+  inline Paths64 RectClip(const Rect64& rect, const Paths64& paths)
   {
     if (rect.IsEmpty() || paths.empty()) return Paths64();
-    RectClip rc(rect);
-    return rc.Execute(paths, convex_only);
+    RectClip64 rc(rect);
+    return rc.Execute(paths);
   }
 
-  inline Paths64 ExecuteRectClip(const Rect64& rect,
-    const Path64& path, bool convex_only = false)
+  inline Paths64 RectClip(const Rect64& rect, const Path64& path)
   {
     if (rect.IsEmpty() || path.empty()) return Paths64();
-    RectClip rc(rect);
-    return rc.Execute(Paths64{ path }, convex_only);
+    RectClip64 rc(rect);
+    return rc.Execute(Paths64{ path });
   }
 
-  inline PathsD ExecuteRectClip(const RectD& rect,
-    const PathsD& paths, bool convex_only = false, int precision = 2)
+  inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
   {
     if (rect.IsEmpty() || paths.empty()) return PathsD();
     int error_code = 0;
@@ -222,37 +223,31 @@ namespace Clipper2Lib {
     if (error_code) return PathsD();
     const double scale = std::pow(10, precision);
     Rect64 r = ScaleRect<int64_t, double>(rect, scale);
-    RectClip rc(r);
+    RectClip64 rc(r);
     Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code);
     if (error_code) return PathsD(); // ie: error_code result is lost 
     return ScalePaths<double, int64_t>(
-      rc.Execute(pp, convex_only), 1 / scale, error_code);
+      rc.Execute(pp), 1 / scale, error_code);
   }
 
-  inline PathsD ExecuteRectClip(const RectD& rect,
-    const PathD& path, bool convex_only = false, int precision = 2)
+  inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2)
   {
-    return ExecuteRectClip(rect, PathsD{ path }, convex_only, precision);
+    return RectClip(rect, PathsD{ path }, precision);
   }
 
-  inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Paths64& lines)
+  inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines)
   {
     if (rect.IsEmpty() || lines.empty()) return Paths64();
-    RectClipLines rcl(rect);
+    RectClipLines64 rcl(rect);
     return rcl.Execute(lines);
   }
 
-  inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Path64& line)
-  {
-    return ExecuteRectClipLines(rect, Paths64{ line });
-  }
-
-  inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2)
+  inline Paths64 RectClipLines(const Rect64& rect, const Path64& line)
   {
-    return ExecuteRectClip(rect, PathsD{ line }, precision);
+    return RectClipLines(rect, Paths64{ line });
   }
 
-  inline PathsD ExecuteRectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
+  inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
   {
     if (rect.IsEmpty() || lines.empty()) return PathsD();
     int error_code = 0;
@@ -260,13 +255,18 @@ namespace Clipper2Lib {
     if (error_code) return PathsD();
     const double scale = std::pow(10, precision);
     Rect64 r = ScaleRect<int64_t, double>(rect, scale);
-    RectClipLines rcl(r);
+    RectClipLines64 rcl(r);
     Paths64 p = ScalePaths<int64_t, double>(lines, scale, error_code);
     if (error_code) return PathsD();
     p = rcl.Execute(p);
     return ScalePaths<double, int64_t>(p, 1 / scale, error_code);
   }
 
+  inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2)
+  {
+    return RectClipLines(rect, PathsD{ line }, precision);
+  }
+
   namespace details
   {
 
@@ -290,14 +290,9 @@ namespace Clipper2Lib {
       {
         // return false if this child isn't fully contained by its parent
 
-        // the following algorithm is a bit too crude, and doesn't account
-        // for rounding errors. A better algorithm is to return false when
-        // consecutive vertices are found outside the parent's polygon.
-
-        //const Path64& path = pp.Polygon();
-        //if (std::any_of(child->Polygon().cbegin(), child->Polygon().cend(),
-        //  [path](const auto& pt) {return (PointInPolygon(pt, path) ==
-        //    PointInPolygonResult::IsOutside); })) return false;
+        // checking for a single vertex outside is a bit too crude since 
+        // it doesn't account for rounding errors. It's better to check 
+        // for consecutive vertices found outside the parent's polygon.
 
         int outsideCnt = 0;
         for (const Point64& pt : child->Polygon())
@@ -317,74 +312,68 @@ namespace Clipper2Lib {
     }
 
     static void OutlinePolyPath(std::ostream& os, 
-      bool isHole, size_t count, const std::string& preamble)
+      size_t idx, bool isHole, size_t count, const std::string& preamble)
     {
       std::string plural = (count == 1) ? "." : "s.";
       if (isHole)
-      {
-        if (count)
-          os << preamble << "+- Hole with " << count <<
-          " nested polygon" << plural << std::endl;
-        else
-          os << preamble << "+- Hole" << std::endl;
-      }
+        os << preamble << "+- Hole (" << idx << ") contains " << count <<
+        " nested polygon" << plural << std::endl;
       else
-      {
-        if (count)
-          os << preamble << "+- Polygon with " << count <<
+        os << preamble << "+- Polygon (" << idx << ") contains " << count <<
           " hole" << plural << std::endl;
-        else
-          os << preamble << "+- Polygon" << std::endl;
-      }
     }
 
     static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp,
-      std::string preamble, bool last_child)
+      size_t idx, std::string preamble)
     {
-      OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble);
-      preamble += (!last_child) ? "|  " : "   ";
-      if (pp.Count())
-      {
-        PolyPath64List::const_iterator it = pp.begin();
-        for (; it < pp.end() - 1; ++it)
-          OutlinePolyPath64(os, **it, preamble, false);
-        OutlinePolyPath64(os, **it, preamble, true);
-      }
+      OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
+      for (size_t i = 0; i < pp.Count(); ++i)
+        if (pp.Child(i)->Count())
+          details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + "  ");
     }
 
     static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp,
-      std::string preamble, bool last_child)
+      size_t idx, std::string preamble)
     {
-      OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble);
-      preamble += (!last_child) ? "|  " : "   ";
-      if (pp.Count())
-      {
-        PolyPathDList::const_iterator it = pp.begin();
-        for (; it < pp.end() - 1; ++it)
-          OutlinePolyPathD(os, **it, preamble, false);
-        OutlinePolyPathD(os, **it, preamble, true);
-      }
+      OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
+      for (size_t i = 0; i < pp.Count(); ++i)
+        if (pp.Child(i)->Count())
+          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)
   {
-    PolyPath64List::const_iterator it = pp.begin();
-    for (; it < pp.end() - 1; ++it)
-      details::OutlinePolyPath64(os, **it, "   ", false);
-    details::OutlinePolyPath64(os, **it, "   ", true);
+    std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
+    os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
+      for (size_t i = 0; i < pp.Count(); ++i)
+        if (pp.Child(i)->Count())
+          details::OutlinePolyPath64(os, *pp.Child(i), i, "  ");
     os << std::endl << std::endl;
-    if (!pp.Level()) os << std::endl;
     return os;
   }
 
   inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp)
   {
-    PolyPathDList::const_iterator it = pp.begin();
-    for (; it < pp.end() - 1; ++it)
-      details::OutlinePolyPathD(os, **it, "   ", false);
-    details::OutlinePolyPathD(os, **it, "   ", true);
+    std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
+    os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
+    for (size_t i = 0; i < pp.Count(); ++i)
+      if (pp.Child(i)->Count())
+        details::OutlinePolyPathD(os, *pp.Child(i), i, "  ");
     os << std::endl << std::endl;
     if (!pp.Level()) os << std::endl;
     return os;
@@ -415,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 &&
@@ -441,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;
   }
@@ -455,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;
   }
@@ -470,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;
   }
@@ -484,11 +457,44 @@ 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;
   }
 
+#ifdef USINGZ
+  template<typename T2, std::size_t N>
+  inline Path64 MakePathZ(const T2(&list)[N])
+  {
+    static_assert(N % 3 == 0 && std::numeric_limits<T2>::is_integer,
+      "MakePathZ requires integer values in multiples of 3");
+    std::size_t size = N / 3;
+    Path64 result(size);
+    for (size_t i = 0; i < size; ++i)
+      result[i] = Point64(list[i * 3], 
+        list[i * 3 + 1], list[i * 3 + 2]);
+    return result;
+  }
+
+  template<typename T2, std::size_t N>
+  inline PathD MakePathZD(const T2(&list)[N])
+  {
+    static_assert(N % 3 == 0,
+      "MakePathZD requires values in multiples of 3");
+    std::size_t size = N / 3;
+    PathD result(size);
+    if constexpr (std::numeric_limits<T2>::is_integer)
+      for (size_t i = 0; i < size; ++i)
+        result[i] = PointD(list[i * 3],
+          list[i * 3 + 1], list[i * 3 + 2]);
+    else
+      for (size_t i = 0; i < size; ++i)
+        result[i] = PointD(list[i * 3], list[i * 3 + 1], 
+          static_cast<int64_t>(list[i * 3 + 2]));
+    return result;
+  }
+#endif
+
   inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
   {
     size_t len = p.size();
@@ -644,8 +650,8 @@ namespace Clipper2Lib {
   }
 
   template <typename T>
-  inline Path<T> SimplifyPath(const Path<T> path, 
-    double epsilon, bool isOpenPath = false)
+  inline Path<T> SimplifyPath(const Path<T> &path, 
+    double epsilon, bool isClosedPath = true)
   {
     const size_t len = path.size(), high = len -1;
     const double epsSqr = Sqr(epsilon);
@@ -653,16 +659,16 @@ namespace Clipper2Lib {
 
     std::vector<bool> flags(len);
     std::vector<double> distSqr(len);
-    size_t prior = high, curr = 0, start, next, prior2, next2;
-    if (isOpenPath)
+    size_t prior = high, curr = 0, start, next, prior2;
+    if (isClosedPath)
     {
-      distSqr[0] = MAX_DBL;
-      distSqr[high] = MAX_DBL;
+      distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
+      distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
     }
     else 
     {
-      distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
-      distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
+      distSqr[0] = MAX_DBL;
+      distSqr[high] = MAX_DBL;
     }
     for (size_t i = 1; i < high; ++i)
       distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
@@ -683,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 || !isOpenPath)
-          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 || !isOpenPath)
-          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);
@@ -712,13 +717,13 @@ namespace Clipper2Lib {
   }
 
   template <typename T>
-  inline Paths<T> SimplifyPaths(const Paths<T> paths, 
-    double epsilon, bool isOpenPath = false)
+  inline Paths<T> SimplifyPaths(const Paths<T> &paths, 
+    double epsilon, bool isClosedPath = true)
   {
     Paths<T> result;
     result.reserve(paths.size());
     for (const auto& path : paths)
-      result.push_back(SimplifyPath(path, epsilon, isOpenPath));
+      result.push_back(SimplifyPath(path, epsilon, isClosedPath));
     return result;
   }
 

+ 2 - 2
thirdparty/clipper2/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 
 {

+ 26 - 16
thirdparty/clipper2/include/clipper2/clipper.offset.h

@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  22 March 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
@@ -24,6 +26,7 @@ enum class EndType {Polygon, Joined, Butt, Square, Round};
 //Joined : offsets both sides of a path, with joined ends
 //Polygon: offsets only one side of a closed path
 
+typedef std::function<double(const Path64& path, const PathD& path_normals, size_t curr_idx, size_t prev_idx)> DeltaCallback64;
 
 class ClipperOffset {
 private:
@@ -31,27 +34,27 @@ 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;
 	double delta_ = 0.0;
 	double group_delta_ = 0.0;
-	double abs_group_delta_ = 0.0;
 	double temp_lim_ = 0.0;
 	double steps_per_rad_ = 0.0;
 	double step_sin_ = 0.0;
 	double step_cos_ = 0.0;
 	PathD norms;
+	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;
@@ -62,15 +65,19 @@ private:
 #ifdef USINGZ
 	ZCallback64 zCallback64_ = nullptr;
 #endif
-
-	void DoSquare(Group& group, const Path64& path, size_t j, size_t k);
-	void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a);
-	void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle);
+	DeltaCallback64 deltaCallback64_ = nullptr;
+
+	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:
@@ -91,6 +98,7 @@ public:
 	
 	void Execute(double delta, Paths64& paths);
 	void Execute(double delta, PolyTree64& polytree);
+	void Execute(DeltaCallback64 delta_cb, Paths64& paths);
 
 	double MiterLimit() const { return miter_limit_; }
 	void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
@@ -108,6 +116,8 @@ public:
 #ifdef USINGZ
 	void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; }
 #endif
+	void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; }
+
 };
 
 }

+ 9 - 10
thirdparty/clipper2/include/clipper2/clipper.rectclip.h

@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  9 February 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
 {
@@ -34,10 +33,10 @@ namespace Clipper2Lib
   };
 
   //------------------------------------------------------------------------------
-  // RectClip
+  // RectClip64
   //------------------------------------------------------------------------------
 
-  class RectClip {
+  class RectClip64 {
   private:
     void ExecuteInternal(const Path64& path);
     Path64 GetPath(OutPt2*& op);
@@ -58,23 +57,23 @@ namespace Clipper2Lib
     void AddCorner(Location prev, Location curr);
     void AddCorner(Location& loc, bool isClockwise);
   public:
-    explicit RectClip(const Rect64& rect) :
+    explicit RectClip64(const Rect64& rect) :
       rect_(rect),
       rect_as_path_(rect.AsPath()),
       rect_mp_(rect.MidPoint()) {}
-    Paths64 Execute(const Paths64& paths, bool convex_only = false);
+    Paths64 Execute(const Paths64& paths);
   };
 
   //------------------------------------------------------------------------------
-  // RectClipLines
+  // RectClipLines64
   //------------------------------------------------------------------------------
 
-  class RectClipLines : public RectClip {
+  class RectClipLines64 : public RectClip64 {
   private:
     void ExecuteInternal(const Path64& path);
     Path64 GetPath(OutPt2*& op);
   public:
-    explicit RectClipLines(const Rect64& rect) : RectClip(rect) {};
+    explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
     Paths64 Execute(const Paths64& paths);
   };
 

+ 6 - 0
thirdparty/clipper2/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

+ 7 - 4
thirdparty/clipper2/clipper2-exceptions.patch → thirdparty/clipper2/patches/clipper2-exceptions.patch

@@ -1,17 +1,17 @@
 diff --git a/thirdparty/clipper2/include/clipper2/clipper.core.h b/thirdparty/clipper2/include/clipper2/clipper.core.h
-index c7522cb900..086d1b659c 100644
+index b3dddeeaa2..a77cdad5f4 100644
 --- a/thirdparty/clipper2/include/clipper2/clipper.core.h
 +++ b/thirdparty/clipper2/include/clipper2/clipper.core.h
-@@ -20,6 +20,8 @@
- #include <climits>
+@@ -21,6 +21,8 @@
  #include <numeric>
+ #include "clipper2/clipper.version.h"
  
 +#define CLIPPER2_THROW(exception) std::abort()
 +
  namespace Clipper2Lib
  {
  
-@@ -65,16 +67,16 @@ namespace Clipper2Lib
+@@ -78,18 +80,18 @@ namespace Clipper2Lib
      switch (error_code)
      {
      case precision_error_i:
@@ -23,6 +23,9 @@ index c7522cb900..086d1b659c 100644
      case non_pair_error_i:
 -      throw Clipper2Exception(non_pair_error);
 +      CLIPPER2_THROW(Clipper2Exception(non_pair_error));
+     case undefined_error_i:
+-      throw Clipper2Exception(undefined_error);
++      CLIPPER2_THROW(Clipper2Exception(undefined_error));
      case range_error_i:
 -      throw Clipper2Exception(range_error);
 +      CLIPPER2_THROW(Clipper2Exception(range_error));

文件差異過大導致無法顯示
+ 414 - 271
thirdparty/clipper2/src/clipper.engine.cpp


+ 333 - 228
thirdparty/clipper2/src/clipper.offset.cpp

@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  22 March 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])
+	return condition ? !val : val;
+}
+
+void GetMultiBounds(const Paths64& paths, std::vector<Rect64>& recList)
+{
+	recList.reserve(paths.size());
+	for (const Path64& path : paths)
+	{ 
+		if (path.size() < 1)
 		{
-			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;
+			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;
 		}
-	//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;
+		recList.push_back(r);
+	}
 }
 
-bool IsSafeOffset(const Rect64& r, double abs_delta)
+bool ValidateBounds(std::vector<Rect64>& recList, double delta)
 {
-	return r.left > min_coord + abs_delta &&
-		r.right < max_coord - abs_delta &&
-		r.top > min_coord + abs_delta &&
-		r.bottom < max_coord - abs_delta;
+	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)
@@ -78,8 +103,7 @@ inline double Hypot(double x, double y)
 }
 
 inline PointD NormalizeVector(const PointD& vec)
-{
-	
+{	
 	double h = Hypot(vec.x, vec.y);
 	if (AlmostZero(h)) return PointD(0,0);
 	double inverseHypot = 1 / h;
@@ -126,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
 //------------------------------------------------------------------------------
@@ -148,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)
@@ -201,19 +263,39 @@ 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) 
-		vec = PointD(norms[0].y, -norms[0].x);
+		vec = PointD(norms[j].y, -norms[j].x);
 	else
 		vec = GetAvgUnitVector(
 			PointD(-norms[k].y, norms[k].x),
 			PointD(norms[j].y, -norms[j].x));
 
+	double abs_delta = std::abs(group_delta_);
+
 	// now offset the original vertex delta units along unit vector
 	PointD ptQ = PointD(path[j]);
-	ptQ = TranslatePoint(ptQ, abs_group_delta_ * vec.x, abs_group_delta_ * vec.y);
+	ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y);
 	// get perpendicular vertices
 	PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
 	PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
@@ -227,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
 	{
@@ -237,57 +319,67 @@ 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, 
+		// so we'll need to do the following calculations for *every* vertex.
+		double abs_delta = std::fabs(group_delta_);
+		double arcTol = (arc_tolerance_ > floating_point_tolerance ?
+			std::min(abs_delta, arc_tolerance_) :
+			std::log10(2 + abs_delta) * default_arc_tolerance);
+		double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
+		step_sin_ = std::sin(2 * PI / steps_per_360);
+		step_cos_ = std::cos(2 * PI / steps_per_360);
+		if (group_delta_ < 0.0) step_sin_ = -step_sin_;
+		steps_per_rad_ = steps_per_360 / (2 * PI);
+	}
+
 	Point64 pt = path[j];
 	PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
 
 	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)
@@ -302,50 +394,57 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
 	if (sin_a > 1.0) sin_a = 1.0;
 	else if (sin_a < -1.0) sin_a = -1.0;
 
-	if (cos_a > 0.99) // almost straight - less than 8 degrees
+	if (deltaCallback64_) {
+		group_delta_ = deltaCallback64_(path, norms, j, k);
+		if (group.is_reversed) group_delta_ = -group_delta_;
+	}
+	if (std::fabs(group_delta_) <= floating_point_tolerance)
 	{
-		group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
-		if (cos_a < 0.9998) // greater than 1 degree (#424)
-			group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); // (#418)
+		path_out.push_back(path[j]);
+		return;
 	}
-	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_));
-	}	
-	else if (join_type_ == JoinType::Round)
-		DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
+	  // path reversals are fully cleaned with the trailing clipper		
+		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);
 	}
-	// don't bother squaring angles that deviate < ~20 degrees because
-	// squaring will be indistinguishable from mitering and just be a lot slower
-	else if (cos_a > 0.9)
-		DoMiter(group, path, j, k, cos_a);
+	else 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
-		DoSquare(group, path, j, k);
-
-	k = j;
+		DoSquare(path, j, k);
 }
 
-void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
+void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
 {
-	for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i)
-		OffsetPoint(group, path, i, j);
-	group.paths_out.push_back(group.path);
+	path_out.clear();
+	for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j)
+		OffsetPoint(group, path, j, k);
+	solution.push_back(path_out);
 }
 
-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());
@@ -353,41 +452,36 @@ 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
-	switch (end_type_)
+	if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
+	
+	if (std::fabs(group_delta_) <= floating_point_tolerance)
+		path_out.push_back(path[0]);
+	else
 	{
-	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_));
-		break;
-	case EndType::Round:
-		DoRound(group, path, 0,0, PI);
-		break;
-	default:
-		DoSquare(group, path, 0, 0);
-		break;
+		switch (end_type_)
+		{
+		case EndType::Butt:
+			DoBevel(path, 0, 0);
+			break;
+		case EndType::Round:
+			DoRound(path, 0, 0, PI);
+			break;
+		default:
+			DoSquare(path, 0, 0);
+			break;
+		}
 	}
-
+	
 	size_t highI = path.size() - 1;
-
 	// offset the left side going forward
-	for (Path64::size_type i = 1, k = 0; i < highI; ++i)
-		OffsetPoint(group, path, i, k);
+	for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
+		OffsetPoint(group, path, j, k);
 
 	// reverse normals 
 	for (size_t i = highI; i > 0; --i)
@@ -395,60 +489,46 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
 	norms[0] = norms[highI];
 
 	// do the line end cap
-	switch (end_type_)
+	if (deltaCallback64_)
+		group_delta_ = deltaCallback64_(path, norms, highI, highI);
+
+	if (std::fabs(group_delta_) <= floating_point_tolerance)
+		path_out.push_back(path[highI]);
+	else
 	{
-	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_));
-		break;
-	case EndType::Round:
-		DoRound(group, path, highI, highI, PI);
-		break;
-	default:
-		DoSquare(group, path, highI, highI);
-		break;
+		switch (end_type_)
+		{
+		case EndType::Butt:
+			DoBevel(path, highI, highI);
+			break;
+		case EndType::Round:
+			DoRound(path, highI, highI, PI);
+			break;
+		default:
+			DoSquare(path, highI, highI);
+			break;
+		}
 	}
 
-	for (size_t i = highI, k = 0; i > 0; --i)
-		OffsetPoint(group, path, i, k);
-	group.paths_out.push_back(group.path);
+	for (size_t j = highI, k = 0; j > 0; k = j, --j)
+		OffsetPoint(group, path, j, k);
+	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_;
 	}
-	abs_group_delta_ = std::fabs(group_delta_);
+	else
+		group_delta_ = std::abs(delta_);// *0.5;
 
-	// do range checking
-	if (!IsSafeOffset(r, abs_group_delta_))
+	double abs_delta = std::fabs(group_delta_);
+	if (!ValidateBounds(group.bounds_list, abs_delta))
 	{
 		DoError(range_error_i);
 		error_code_ |= range_error_i;
@@ -458,80 +538,98 @@ void ClipperOffset::DoGroupOffset(Group& group)
 	join_type_	= group.join_type;
 	end_type_ = group.end_type;
 
-	//calculate a sensible number of steps (for 360 deg for the given offset
 	if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
 	{
+		// calculate a sensible number of steps (for 360 deg for the given offset)
 		// arcTol - when arc_tolerance_ is undefined (0), the amount of 
 		// curve imprecision that's allowed is based on the size of the 
 		// offset (delta). Obviously very large offsets will almost always 
 		// require much less precision. See also offset_triginometry2.svg
 		double arcTol = (arc_tolerance_ > floating_point_tolerance ?
-			std::min(abs_group_delta_, arc_tolerance_) :
-			std::log10(2 + abs_group_delta_) * default_arc_tolerance); 
-		double steps_per_360 = PI / std::acos(1 - arcTol / abs_group_delta_);
-		if (steps_per_360 > abs_group_delta_ * PI)
-			steps_per_360 = abs_group_delta_ * PI;  //ie avoids excessive precision
+			std::min(abs_delta, arc_tolerance_) :
+			std::log10(2 + abs_delta) * default_arc_tolerance);
 
+		double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
 		step_sin_ = std::sin(2 * PI / steps_per_360);
 		step_cos_ = std::cos(2 * PI / steps_per_360);
-		if (group_delta_ < 0.0) step_sin_ = -step_sin_;		
-		steps_per_rad_ = steps_per_360 / (2 *PI);
+		if (group_delta_ < 0.0) step_sin_ = -step_sin_;
+		steps_per_rad_ = steps_per_360 / (2 * PI);
 	}
 
-	bool is_joined =
-		(end_type_ == EndType::Polygon) ||
-		(end_type_ == EndType::Joined);
-	Paths64::const_iterator path_iter;
-	for(path_iter = group.paths_in.cbegin(); path_iter != group.paths_in.cend(); ++path_iter)
+	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 = StripDuplicates(*path_iter, 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_group_delta_;
-				group.path = Ellipse(path[0], radius, radius);
+				double radius = abs_delta;
+				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_group_delta_);
-				r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
-				group.path = r.AsPath();
+				int d = (int)std::ceil(abs_delta);
+				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 
+
+		// 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);
+	}
+}
+
+
+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;
+}
 
-			BuildNormals(path);
-			if (end_type_ == EndType::Polygon) OffsetPolygon(group, path);
-			else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
-			else OffsetOpenPath(group, path);
+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;
 		}
-	}
-	solution.reserve(solution.size() + group.paths_out.size());
-	copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution));
-	group.paths_out.clear();
+	return is_reversed_orientation;
 }
 
 void ClipperOffset::ExecuteInternal(double delta)
@@ -539,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();
 	}
 }
 
@@ -572,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);
@@ -598,21 +694,30 @@ 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);
 }
 
+void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
+{
+	deltaCallback64_ = delta_cb;
+	Execute(1.0, paths);
+}
+
 } // namespace

+ 104 - 75
thirdparty/clipper2/src/clipper.rectclip.cpp

@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  14 February 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)
@@ -281,7 +315,7 @@ namespace Clipper2Lib {
   // RectClip64
   //----------------------------------------------------------------------------
 
-  OutPt2* RectClip::Add(Point64 pt, bool start_new)
+  OutPt2* RectClip64::Add(Point64 pt, bool start_new)
   {
     // this method is only called by InternalExecute.
     // Later splitting & rejoining won't create additional op's,
@@ -312,7 +346,7 @@ namespace Clipper2Lib {
     return result;
   }
 
-  void RectClip::AddCorner(Location prev, Location curr)
+  void RectClip64::AddCorner(Location prev, Location curr)
   {
     if (HeadingClockwise(prev, curr))
       Add(rect_as_path_[static_cast<int>(prev)]);
@@ -320,7 +354,7 @@ namespace Clipper2Lib {
       Add(rect_as_path_[static_cast<int>(curr)]);
   }
 
-  void RectClip::AddCorner(Location& loc, bool isClockwise)
+  void RectClip64::AddCorner(Location& loc, bool isClockwise)
   {
     if (isClockwise)
     {
@@ -334,7 +368,7 @@ namespace Clipper2Lib {
     }
   }
 
-  void RectClip::GetNextLocation(const Path64& path,
+  void RectClip64::GetNextLocation(const Path64& path,
     Location& loc, int& i, int highI)
   {
     switch (loc)
@@ -389,7 +423,7 @@ namespace Clipper2Lib {
     } //switch          
   }
 
-  void RectClip::ExecuteInternal(const Path64& path)
+  void RectClip64::ExecuteInternal(const Path64& path)
   {
     int i = 0, highI = static_cast<int>(path.size()) - 1;
     Location prev = Location::Inside, loc;
@@ -474,7 +508,7 @@ namespace Clipper2Lib {
         // intersect pt but we'll also need the first intersect pt (ip2)
         loc = prev;
         GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2);
-        if (crossing_prev != Location::Inside)
+        if (crossing_prev != Location::Inside && crossing_prev != loc) //579
           AddCorner(crossing_prev, loc);
 
         if (first_cross_ == Location::Inside)
@@ -546,7 +580,7 @@ namespace Clipper2Lib {
     }
   }
 
-  void RectClip::CheckEdges()
+  void RectClip64::CheckEdges()
   {
     for (size_t i = 0; i < results_.size(); ++i)
     {
@@ -606,7 +640,7 @@ namespace Clipper2Lib {
     }
   }
 
-  void RectClip::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw)
+  void RectClip64::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw)
   {
     if (ccw.empty()) return;
     bool isHorz = ((idx == 1) || (idx == 3));
@@ -619,7 +653,7 @@ namespace Clipper2Lib {
       p1 = cw[i];
       if (!p1 || p1->next == p1->prev)
       {
-        cw[i++]->edge = nullptr;
+        cw[i++] = nullptr;
         j = 0;
         continue;
       }
@@ -784,7 +818,7 @@ namespace Clipper2Lib {
     }
   }
 
-  Path64 RectClip::GetPath(OutPt2*& op)
+  Path64 RectClip64::GetPath(OutPt2*& op)
   {
     if (!op || op->next == op->prev) return Path64();
 
@@ -814,13 +848,13 @@ namespace Clipper2Lib {
     return result;
   }
 
-  Paths64 RectClip::Execute(const Paths64& paths, bool convex_only)
+  Paths64 RectClip64::Execute(const Paths64& paths)
   {
     Paths64 result;
     if (rect_.IsEmpty()) return result;
 
-    for (const auto& path : paths)
-    {
+    for (const Path64& path : paths)
+    {      
       if (path.size() < 3) continue;
       path_bounds_ = GetBounds(path);
       if (!rect_.Intersects(path_bounds_))
@@ -833,13 +867,10 @@ namespace Clipper2Lib {
       }
 
       ExecuteInternal(path);
-      if (!convex_only)
-      {
-        CheckEdges();
-        for (int i = 0; i < 4; ++i)
-          TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]);
-      }
-
+      CheckEdges();
+      for (int i = 0; i < 4; ++i)
+        TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]);
+  
       for (OutPt2*& op :  results_)
       {
         Path64 tmp = GetPath(op);
@@ -850,26 +881,24 @@ namespace Clipper2Lib {
       //clean up after every loop
       op_container_ = std::deque<OutPt2>();
       results_.clear();
-      for (OutPt2List edge : edges_) edge.clear();
+      for (OutPt2List &edge : edges_) edge.clear();
       start_locs_.clear();
     }
     return result;
   }
 
   //------------------------------------------------------------------------------
-  // RectClipLines
+  // RectClipLines64
   //------------------------------------------------------------------------------
 
-  Paths64 RectClipLines::Execute(const Paths64& paths)
+  Paths64 RectClipLines64::Execute(const Paths64& paths)
   {
     Paths64 result;
     if (rect_.IsEmpty()) return result;
 
     for (const auto& path : paths)
     {
-      if (path.size() < 2) continue;
       Rect64 pathrec = GetBounds(path);
-
       if (!rect_.Intersects(pathrec)) continue;
 
       ExecuteInternal(path);
@@ -888,7 +917,7 @@ namespace Clipper2Lib {
     return result;
   }
 
-  void RectClipLines::ExecuteInternal(const Path64& path)
+  void RectClipLines64::ExecuteInternal(const Path64& path)
   {
     if (rect_.IsEmpty() || path.size() < 2) return;
 
@@ -958,7 +987,7 @@ namespace Clipper2Lib {
     ///////////////////////////////////////////////////
   }
 
-  Path64 RectClipLines::GetPath(OutPt2*& op)
+  Path64 RectClipLines64::GetPath(OutPt2*& op)
   {
     Path64 result;
     if (!op || op == op->next) return result;

部分文件因文件數量過多而無法顯示