Browse Source

Updated clipper to 1.3.0.a4ae9e4.

Brucey 1 năm trước cách đây
mục cha
commit
331ed967a3
100 tập tin đã thay đổi với 10625 bổ sung5506 xóa
  1. 80 0
      polygon.mod/clipper2/CPP/BenchMark/CMakeLists.txt
  2. 579 0
      polygon.mod/clipper2/CPP/BenchMark/GetIntersectPtBenchmark.cpp
  3. 524 0
      polygon.mod/clipper2/CPP/BenchMark/PointInPolygonBenchmark.cpp
  4. 6 0
      polygon.mod/clipper2/CPP/BenchMark/README.md
  5. 73 0
      polygon.mod/clipper2/CPP/BenchMark/StripDuplicateBenchmark.cpp
  6. 189 93
      polygon.mod/clipper2/CPP/CMakeLists.txt
  7. 507 241
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.core.h
  8. 178 129
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.engine.h
  9. 334 598
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.export.h
  10. 363 355
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.h
  11. 12 10
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h
  12. 54 36
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.offset.h
  13. 48 17
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h
  14. 6 0
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.version.h
  15. 422 275
      polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.engine.cpp
  16. 426 260
      polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.offset.cpp
  17. 621 129
      polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.rectclip.cpp
  18. 133 0
      polygon.mod/clipper2/CPP/Examples/Benchmarks/Benchmarks.cpp
  19. 0 210
      polygon.mod/clipper2/CPP/Examples/ConsoleDemo1/ConsoleDemo1.cpp
  20. 0 92
      polygon.mod/clipper2/CPP/Examples/ConsoleDemo2/ConsoleDemo2.cpp
  21. 47 34
      polygon.mod/clipper2/CPP/Examples/Inflate/Inflate.cpp
  22. 0 0
      polygon.mod/clipper2/CPP/Examples/Inflate/rabbit.svg
  23. 74 0
      polygon.mod/clipper2/CPP/Examples/MemLeakTest/MemLeakTest.cpp
  24. 102 0
      polygon.mod/clipper2/CPP/Examples/PolygonSamples/PolygonSamples.cpp
  25. 153 0
      polygon.mod/clipper2/CPP/Examples/RandomClipping/RandomClipping.cpp
  26. 46 72
      polygon.mod/clipper2/CPP/Examples/RectClipping/RectClipping.cpp
  27. 63 0
      polygon.mod/clipper2/CPP/Examples/SimpleClipping/SimpleClipping.cpp
  28. 168 0
      polygon.mod/clipper2/CPP/Examples/UnionClipping/UnionClipping.cpp
  29. 26 28
      polygon.mod/clipper2/CPP/Examples/UsingZ/UsingZ.cpp
  30. 161 0
      polygon.mod/clipper2/CPP/Examples/VariableOffset/VariableOffset.cpp
  31. 6 5
      polygon.mod/clipper2/CPP/GoogleTest in Visual Studio.txt
  32. 178 0
      polygon.mod/clipper2/CPP/Tests/TestExportHeaders.cpp
  33. 4 14
      polygon.mod/clipper2/CPP/Tests/TestLines.cpp
  34. 26 17
      polygon.mod/clipper2/CPP/Tests/TestOffsetOrientation.cpp
  35. 605 0
      polygon.mod/clipper2/CPP/Tests/TestOffsets.cpp
  36. 3 7
      polygon.mod/clipper2/CPP/Tests/TestOrientation.cpp
  37. 52 49
      polygon.mod/clipper2/CPP/Tests/TestPolygons.cpp
  38. 216 0
      polygon.mod/clipper2/CPP/Tests/TestPolytreeHoles.cpp
  39. 0 31
      polygon.mod/clipper2/CPP/Tests/TestPolytreeHoles1.cpp
  40. 0 132
      polygon.mod/clipper2/CPP/Tests/TestPolytreeHoles2.cpp
  41. 4 10
      polygon.mod/clipper2/CPP/Tests/TestPolytreeIntersection.cpp
  42. 5 11
      polygon.mod/clipper2/CPP/Tests/TestPolytreeUnion.cpp
  43. 16 91
      polygon.mod/clipper2/CPP/Tests/TestRandomPaths.cpp
  44. 30 27
      polygon.mod/clipper2/CPP/Tests/TestRectClip.cpp
  45. 11 0
      polygon.mod/clipper2/CPP/Tests/TestSimplifyPath.cpp
  46. 9 17
      polygon.mod/clipper2/CPP/Tests/TestTrimCollinear.cpp
  47. 5 0
      polygon.mod/clipper2/CPP/Tests/TestWindows.cpp
  48. 22 21
      polygon.mod/clipper2/CPP/Utils/ClipFileLoad.cpp
  49. 8 6
      polygon.mod/clipper2/CPP/Utils/ClipFileSave.cpp
  50. 8 4
      polygon.mod/clipper2/CPP/Utils/ClipFileSave.h
  51. 56 0
      polygon.mod/clipper2/CPP/Utils/Colors.h
  52. 38 0
      polygon.mod/clipper2/CPP/Utils/CommonUtils.h
  53. 38 45
      polygon.mod/clipper2/CPP/Utils/Timer.h
  54. 36 17
      polygon.mod/clipper2/CPP/Utils/clipper.svg.cpp
  55. 5 3
      polygon.mod/clipper2/CPP/Utils/clipper.svg.h
  56. 3 3
      polygon.mod/clipper2/CPP/Utils/clipper.svg.utils.h
  57. 6 0
      polygon.mod/clipper2/CPP/clipper.version.in
  58. 0 1
      polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs
  59. 1 1
      polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Clipper2Lib.Benchmark.csproj
  60. 1 1
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/ConsoleDemo.csproj
  61. 192 36
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs
  62. 1 1
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/InflateDemo.csproj
  63. 59 31
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs
  64. 77 60
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/Main.cs
  65. 2 1
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/RectClipDemo.csproj
  66. 1 1
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestLines.cs
  67. 19 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs
  68. 34 24
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolygons.cs
  69. 154 104
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Core.cs
  70. 365 334
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Engine.cs
  71. 0 1
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Minkowski.cs
  72. 465 220
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Offset.cs
  73. 666 152
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.RectClip.cs
  74. 311 128
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.cs
  75. BIN
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper2.snk
  76. 31 33
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper2Lib.csproj
  77. 126 0
      polygon.mod/clipper2/CSharp/Clipper2Lib/HashCode.cs
  78. 79 0
      polygon.mod/clipper2/CSharp/USINGZ.TestApp/Program.cs
  79. 23 0
      polygon.mod/clipper2/CSharp/USINGZ.TestApp/UsingZTestApp.csproj
  80. 31 0
      polygon.mod/clipper2/CSharp/USINGZ.TestApp/UsingZ_TestApp.sln
  81. 8 3
      polygon.mod/clipper2/CSharp/USINGZ/Clipper2LibZ.csproj
  82. 13 13
      polygon.mod/clipper2/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs
  83. 15 12
      polygon.mod/clipper2/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj
  84. 75 0
      polygon.mod/clipper2/CSharp/Utils/Colors/Clipper.Colors.cs
  85. 14 0
      polygon.mod/clipper2/CSharp/Utils/Colors/Clipper.Colors.csproj
  86. 60 31
      polygon.mod/clipper2/CSharp/Utils/SVG/Clipper.SVG.Utils.cs
  87. 59 15
      polygon.mod/clipper2/CSharp/Utils/SVG/Clipper.SVG.cs
  88. 14 11
      polygon.mod/clipper2/CSharp/Utils/SVG/Clipper2.SVG.csproj
  89. 0 0
      polygon.mod/clipper2/DLL/CSharp_TestApp/COMPILE AND ADD Clipper2_64.dll HERE
  90. 11 0
      polygon.mod/clipper2/DLL/CSharp_TestApp/CSharp_TestApp.csproj
  91. 25 0
      polygon.mod/clipper2/DLL/CSharp_TestApp/CSharp_TestApp.sln
  92. 291 0
      polygon.mod/clipper2/DLL/CSharp_TestApp/Program.cs
  93. BIN
      polygon.mod/clipper2/DLL/CSharp_TestApp/polytree_sample.png
  94. 0 0
      polygon.mod/clipper2/DLL/CSharp_TestApp2/COMPILE AND ADD Clipper2_64.dll HERE
  95. 16 0
      polygon.mod/clipper2/DLL/CSharp_TestApp2/CSharp_TestApp2.csproj
  96. 63 0
      polygon.mod/clipper2/DLL/CSharp_TestApp2/CSharp_TestApp2.sln
  97. 184 0
      polygon.mod/clipper2/DLL/CSharp_TestApp2/Program.cs
  98. 0 0
      polygon.mod/clipper2/DLL/Delphi_TestApp/COMPILE AND ADD Clipper2_64.dll HERE
  99. 0 663
      polygon.mod/clipper2/DLL/Delphi_TestApp/SvgWriter.pas
  100. 354 540
      polygon.mod/clipper2/DLL/Delphi_TestApp/Test_DLL.dpr

+ 80 - 0
polygon.mod/clipper2/CPP/BenchMark/CMakeLists.txt

@@ -0,0 +1,80 @@
+cmake_minimum_required(VERSION 3.15)
+project(Clipper2_benchmarks VERSION 1.0 LANGUAGES C CXX)
+
+if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17)
+    set(CMAKE_CXX_STANDARD 17)
+endif()
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+# fetch the google benchmark library
+include(FetchContent)
+set(BENCHMARK_ENABLE_GTEST_TESTS OFF)
+set(BENCHMARK_ENABLE_TESTING OFF)
+message("start fetching the googlebenchmark")
+FetchContent_Declare(googlebenchmark
+        GIT_REPOSITORY https://github.com/google/benchmark.git
+        GIT_TAG v1.7.1
+)
+
+FetchContent_MakeAvailable(
+        googlebenchmark)
+set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+message("fetching is done")
+
+set(benchmark_srcs
+    GetIntersectPtBenchmark.cpp
+    PointInPolygonBenchmark.cpp
+    StripDuplicateBenchmark.cpp
+    # more to add
+)
+
+set(CLIPPER2_INC
+  ${CLIPPER2_INC_FOLDER}/clipper.h
+  ${CLIPPER2_INC_FOLDER}/clipper.version.h
+  ${CLIPPER2_INC_FOLDER}/clipper.core.h
+)
+
+add_library(Clipper2_bm INTERFACE)
+target_include_directories(Clipper2_bm INTERFACE CLIPPER2_INC)
+
+set(CLIPPER2_UTILS_INC
+  ../Utils/clipper.svg.h
+  ../Utils/ClipFileLoad.h
+  ../Utils/ClipFileSave.h
+  ../Utils/Timer.h
+  ../Utils/Colors.h
+  ../Utils/CommonUtils.h
+)
+set(CLIPPER2_UTILS_SRC
+  ../Utils/clipper.svg.cpp
+  ../Utils/ClipFileLoad.cpp
+  ../Utils/ClipFileSave.cpp
+)
+set(CLIPPER2_UTILS "")
+  list(APPEND CLIPPER2_UTILS Clipper2utils_bm)
+  add_library(Clipper2utils_bm STATIC ${CLIPPER2_UTILS_INC} ${CLIPPER2_UTILS_SRC})
+  target_include_directories(Clipper2utils_bm
+    PUBLIC ../Clipper2Lib/include
+    PUBLIC ../Utils
+  )
+  target_link_libraries(Clipper2utils_bm PUBLIC Clipper2_bm)
+
+# add each benchmark from the benchmark_srcs
+foreach(benchmark ${benchmark_srcs})
+    get_filename_component(benchmark_target ${benchmark} NAME_WE)
+
+    message(STATUS "${PROJECT_NAME} add benchmark ${benchmark_target}")
+    add_executable(${benchmark_target} ${benchmark})
+
+    target_include_directories(${benchmark_target}
+      PUBLIC ../Clipper2Lib/include
+      PUBLIC ../Utils
+    )
+
+    target_link_libraries(${benchmark_target}
+        benchmark::benchmark
+        Clipper2_bm
+        Clipper2utils_bm
+    )
+endforeach()

+ 579 - 0
polygon.mod/clipper2/CPP/BenchMark/GetIntersectPtBenchmark.cpp

@@ -0,0 +1,579 @@
+#include "benchmark/benchmark.h"
+#include "clipper2/clipper.h"
+#include "clipper2/clipper.core.h"
+#include "CommonUtils.h"
+#include "ClipFileLoad.h"
+#include <iomanip> 
+#include <cstdlib>
+#include <random>
+
+using namespace Clipper2Lib;
+
+enum TextColor {
+  reset = 0,
+  //normal text colors ...
+  red = 31, green = 32, yellow = 33, blue = 34, magenta = 35, cyan = 36, white = 37,
+  //bold text colors ...
+  red_bold = 91, green_bold = 92, yellow_bold = 93, blue_bold = 94,
+  magenta_bold = 95, cyan_bold = 96, white_bold = 97
+};
+
+//////////////////////////////////////////////////////////////////////////////////////
+// SetConsoleTextColor: a simple class to adjust Console Text Colors (Windows & Linux)
+//////////////////////////////////////////////////////////////////////////////////////
+
+struct SetConsoleTextColor
+{
+private:
+  TextColor _color;
+public:
+  SetConsoleTextColor(TextColor color) : _color(color) {};
+
+  static friend std::ostream& operator<< (std::ostream& out, SetConsoleTextColor const& scc)
+  {
+    return out << "\x1B[" << scc._color << "m";
+  }
+};
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Int128 - class that very minimally supports 128bit integer math
+//////////////////////////////////////////////////////////////////////////////////////
+
+class Int128
+{
+public:
+  uint64_t lo;
+  int64_t hi;
+
+  Int128(int64_t _lo = 0)
+  {
+    lo = (uint64_t)_lo;
+    if (_lo < 0)  hi = -1; else hi = 0;
+  }
+
+  Int128(const Int128& val) : lo(val.lo), hi(val.hi) {}
+
+  Int128(const int64_t& _hi, const uint64_t& _lo) : lo(_lo), hi(_hi) {}
+
+  Int128& operator = (const int64_t& val)
+  {
+    lo = (uint64_t)val;
+    if (val < 0) hi = -1; else hi = 0;
+    return *this;
+  }
+
+  bool operator == (const Int128& val) const
+  {
+    return (hi == val.hi && lo == val.lo);
+  }
+
+  bool operator != (const Int128& val) const
+  {
+    return !(*this == val);
+  }
+
+  bool operator > (const Int128& val) const
+  {
+    return (hi != val.hi) ? hi > val.hi: lo > val.lo;
+  }
+
+  bool operator < (const Int128& val) const
+  {
+    return (hi != val.hi) ? hi < val.hi : lo < val.lo;
+  }
+
+  bool operator >= (const Int128& val) const
+  {
+    return !(*this < val);
+  }
+
+  bool operator <= (const Int128& val) const
+  {
+    return !(*this > val);
+  }
+
+  bool is_zero() const
+  {
+    return (hi == 0 && lo == 0);
+  }
+
+  bool is_negative() const
+  {
+    return (hi < 0);
+  }
+
+  Int128& operator += (const Int128& rhs)
+  {
+    hi += rhs.hi;
+    lo += rhs.lo;
+    if (lo < rhs.lo) hi++;
+    return *this;
+  }
+
+  Int128 operator + (const Int128& rhs) const
+  {
+    Int128 result(*this);
+    result += rhs;
+    return result;
+  }
+
+  Int128& operator -= (const Int128& rhs)
+  {
+    *this += -rhs;
+    return *this;
+  }
+
+  Int128 operator - (const Int128& rhs) const
+  {
+    Int128 result(*this);
+    result -= rhs;
+    return result;
+  }
+
+  Int128 operator-() const //unary negation
+  {
+    return  (lo == 0) ? Int128(-hi, 0) : Int128(~hi, ~lo + 1);
+  }
+
+  void negate()
+  {
+    if (lo == 0) hi = -hi;
+    else { hi = ~hi; lo = ~lo + 1; }
+  }
+
+  operator double() const
+  {
+    const double shift64 = 18446744073709551616.0; //2^64
+    if (hi < 0)
+    {
+      if (lo == 0) return (double)hi * shift64;
+      else return -(double)(~lo + ~hi * shift64);
+    }
+    else
+      return (double)(lo + hi * shift64);
+  }
+};
+
+static inline Int128 multiply(int64_t lhs, int64_t rhs)
+{
+  bool negate = (lhs < 0) != (rhs < 0);
+
+  if (lhs < 0) lhs = -lhs;
+  uint64_t int1Hi = uint64_t(lhs) >> 32;
+  uint64_t int1Lo = uint64_t(lhs & 0xFFFFFFFF);
+
+  if (rhs < 0) rhs = -rhs;
+  uint64_t int2Hi = uint64_t(rhs) >> 32;
+  uint64_t int2Lo = uint64_t(rhs & 0xFFFFFFFF);
+
+  uint64_t a = int1Hi * int2Hi;
+  uint64_t b = int1Lo * int2Lo;
+  uint64_t c = int1Hi * int2Lo + int1Lo * int2Hi;
+
+  Int128 result;
+  result.hi = a + (c >> 32);
+  result.lo = c << 32;
+  result.lo += b;
+  if (result.lo < b) result.hi++;
+  return negate ? -result : result;
+};
+
+static int64_t divide(Int128 dividend, Int128 divisor)
+{
+  // this function assumes that the parameter values will 
+  // generate a result that fits into a 64bit integer.
+  bool negate = (divisor.hi < 0) != (dividend.hi < 0);
+  if (dividend.hi < 0) dividend = -dividend;
+  if (divisor.hi < 0) divisor = -divisor;
+  if (divisor.lo == 0 && divisor.hi == 0)
+    throw "Int128: divide by zero error";
+
+  if (dividend == divisor) return negate ? -1 : 1;
+  if (divisor > dividend) return 0;
+
+  Int128 cntr = Int128(1);
+  while (divisor.hi >= 0 && divisor <= dividend)
+  {
+    divisor.hi <<= 1;
+    if ((int64_t)divisor.lo < 0) divisor.hi++;
+    divisor.lo <<= 1;
+
+    cntr.hi <<= 1;
+    if ((int64_t)cntr.lo < 0) cntr.hi++;
+    cntr.lo <<= 1;
+  }
+  divisor.lo >>= 1;
+  if (divisor.hi & 1)
+    divisor.lo |= 0x8000000000000000LL;
+  divisor.hi >>= 1;
+
+  cntr.lo >>= 1;
+  if (cntr.hi & 1)
+    cntr.lo |= 0x8000000000000000LL;
+  cntr.hi >>= 1;
+
+  Int128 result = Int128(0);
+  while (cntr.hi != 0 || cntr.lo != 0)
+  {
+    if (dividend >= divisor)
+    {
+      dividend -= divisor;
+      result.hi |= cntr.hi;
+      result.lo |= cntr.lo;
+    }
+    divisor.lo >>= 1;
+    if (divisor.hi & 1)
+      divisor.lo |= 0x8000000000000000LL;
+    divisor.hi >>= 1;
+
+    cntr.lo >>= 1;
+    if (cntr.hi & 1)
+      cntr.lo |= 0x8000000000000000LL;
+    cntr.hi >>= 1;
+  }
+  if (result.hi || (int64_t)result.lo < 0) 
+    return negate ? INT64_MIN : INT64_MAX;
+  else
+    return negate ? -(int64_t)result.lo : result.lo;
+}
+
+static inline int64_t muldiv(Int128 lhs, int64_t rhs, Int128 divisor)
+{
+  // this function assumes that the parameter values will 
+  // generate a result that fits into a 64bit integer.
+  int64_t sign = (lhs.is_negative() != divisor.is_negative()) != (rhs < 0) ? -2 : 2;
+  if (lhs.is_negative()) lhs.negate();
+  if (divisor.is_negative()) divisor.negate();
+  if (rhs < 0) rhs = -rhs;
+
+  // if 'lhs' is very large, then 'divisor' will be very large too
+  while (lhs.hi && divisor.hi)
+  {
+    // divide dividend and divisor by 2 ...
+    lhs.lo >>= 1;
+    if (lhs.hi & 1)
+      lhs.lo |= 0x8000000000000000LL;
+    lhs.hi >>= 1;
+    divisor.lo >>= 1;
+    if (divisor.hi & 1)
+      divisor.lo |= 0x8000000000000000LL;
+    divisor.hi >>= 1;
+  }
+
+  lhs.lo >>= 1; // divide by 2 to avoid casting a 'sign' bit
+  Int128 result = multiply((int64_t)lhs.lo, rhs);
+  result.hi += lhs.hi * rhs;
+  return divide(result, divisor) * sign; // and multiplies by 2
+};
+
+/////////////////////////////////////////////////////////
+// Several GetIntersectPoint functions for testing
+/////////////////////////////////////////////////////////
+
+const int number_of_test_functions = 4;
+
+typedef std::function<bool(const Point64&, const Point64&,
+  const Point64&, const Point64&, Point64&)> GipFunction;
+
+// GIP_Current: This is Clipper2's current GetIntersectPoint.
+// It's definitely the fastest function, but its accuracy declines 
+// a little when using very large 64bit integers (eg +/-10e17).
+static bool GIP_Current(const Point64& ln1a, const Point64& ln1b,
+  const Point64& ln2a, const Point64& ln2b, Point64& ip)
+{
+  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 false;
+  double t = ((double)(ln1a.x - ln2a.x) * dy2 - (double)(ln1a.y - ln2a.y) * dx2) / det;
+  if (t <= 0.0) ip = ln1a;
+  else if (t >= 1.0) ip = ln1b;
+  else
+  {
+    ip.x = static_cast<int64_t>(ln1a.x + t * dx1);
+    ip.y = static_cast<int64_t>(ln1a.y + t * dy1);
+  }
+  return true;
+}
+
+// GIP_Func_F: This is mathVertexLineLineIntersection_F
+// https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253
+#define CC_MIN(x,y) ((x)>(y)?(y):(x))
+#define CC_MAX(x,y) ((x)<(y)?(y):(x))
+
+static bool GIP_Func_F(const Point64& ln1a, const Point64& ln1b,
+  const Point64& ln2a, const Point64& ln2b, Point64& ip)
+{
+  double ln1dy = (double)(ln1b.y - ln1a.y);
+  double ln1dx = (double)(ln1a.x - ln1b.x);
+  double ln2dy = (double)(ln2b.y - ln2a.y);
+  double ln2dx = (double)(ln2a.x - ln2b.x);
+  double det = (ln2dy * ln1dx) - (ln1dy * ln2dx);
+  if (det == 0.0) return false;
+  int64_t bb0minx = CC_MIN(ln1a.x, ln1b.x);
+  int64_t bb0miny = CC_MIN(ln1a.y, ln1b.y);
+  int64_t bb0maxx = CC_MAX(ln1a.x, ln1b.x);
+  int64_t bb0maxy = CC_MAX(ln1a.y, ln1b.y);
+  int64_t bb1minx = CC_MIN(ln2a.x, ln2b.x);
+  int64_t bb1miny = CC_MIN(ln2a.y, ln2b.y);
+  int64_t bb1maxx = CC_MAX(ln2a.x, ln2b.x);
+  int64_t bb1maxy = CC_MAX(ln2a.y, ln2b.y);
+  int64_t originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) >> 1;
+  int64_t originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) >> 1;
+  double ln0c = (ln1dy * (double)(ln1a.x - originx)) + (ln1dx * (double)(ln1a.y - originy));
+  double ln1c = (ln2dy * (double)(ln2a.x - originx)) + (ln2dx * (double)(ln2a.y - originy));
+  double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det;
+  double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det;
+  ip.x = originx + (int64_t)nearbyint(hitx);
+  ip.y = originy + (int64_t)nearbyint(hity);
+  return true;
+}
+
+// GIP_F_Mod: GIP_Func_F except replaces nearbyint with static casts.
+// Surprisingly, while this function is faster that GIP_Func_F here, 
+// it's much slower than both GIP_Func_F and GIP_Current when using it 
+// as a replacement for GetIntersectPoint() in clipper.core.h.
+static bool GIP_F_Mod(const Point64& ln1a, const Point64& ln1b,
+  const Point64& ln2a, const Point64& ln2b, Point64& ip)
+{
+  double ln1dy = (double)(ln1b.y - ln1a.y);
+  double ln1dx = (double)(ln1a.x - ln1b.x);
+  double ln2dy = (double)(ln2b.y - ln2a.y);
+  double ln2dx = (double)(ln2a.x - ln2b.x);
+  double det = (ln2dy * ln1dx) - (ln1dy * ln2dx);
+  if (det == 0.0) return false;
+  int64_t bb0minx = CC_MIN(ln1a.x, ln1b.x);
+  int64_t bb0miny = CC_MIN(ln1a.y, ln1b.y);
+  int64_t bb0maxx = CC_MAX(ln1a.x, ln1b.x);
+  int64_t bb0maxy = CC_MAX(ln1a.y, ln1b.y);
+  int64_t bb1minx = CC_MIN(ln2a.x, ln2b.x);
+  int64_t bb1miny = CC_MIN(ln2a.y, ln2b.y);
+  int64_t bb1maxx = CC_MAX(ln2a.x, ln2b.x);
+  int64_t bb1maxy = CC_MAX(ln2a.y, ln2b.y);
+  int64_t originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) >> 1;
+  int64_t originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) >> 1;
+  double ln0c = (ln1dy * (double)(ln1a.x - originx)) + (ln1dx * (double)(ln1a.y - originy));
+  double ln1c = (ln2dy * (double)(ln2a.x - originx)) + (ln2dx * (double)(ln2a.y - originy));
+  double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det;
+  double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det;
+  ip.x = originx + static_cast<int64_t>(hitx);
+  ip.y = originy + static_cast<int64_t>(hity);
+  return true;
+}
+
+// GIP_128: GetIntersectPoint using 128bit integer precision 
+// This function is the most precise, but it's also very slow.
+static bool GIP_128(const Point64& ln1a, const Point64& ln1b,
+  const Point64& ln2a, const Point64& ln2b, Point64& ip)
+{
+  int64_t dx1 = ln1b.x - ln1a.x;
+  int64_t dy1 = ln1b.y - ln1a.y;
+  int64_t dx2 = ln2b.x - ln2a.x;
+  int64_t dy2 = ln2b.y - ln2a.y;
+  Int128 det = multiply(dy1, dx2) - multiply(dy2, dx1);
+  if (det.is_zero()) return false;
+
+  Int128 t_num = multiply(ln1a.x - ln2a.x, dy2) - multiply(ln1a.y - ln2a.y, dx2);
+  bool is_negative = t_num.is_negative() != det.is_negative();
+  if (t_num.is_zero() || is_negative)
+    ip = ln1a;
+  else if (t_num.is_negative() == -t_num > -det)
+    ip = ln1b;
+  else
+  {
+    ip.x = ln1a.x + muldiv(t_num, dx1, det);
+    ip.y = ln1a.y + muldiv(t_num, dy1, det);
+  }
+  return true;
+}
+
+static inline GipFunction GetGipFunc(int64_t index)
+{
+  switch (index)
+  {
+  case 0: return GIP_Current; 
+  case 1: return GIP_Func_F;
+  case 2: return GIP_F_Mod;
+  case 3: return GIP_128; 
+  default: throw "Invalid function!";
+  }
+}
+
+static inline std::string GetGipFuncName(int64_t index)
+{
+  switch (index)
+  {
+  case 0: return "GIP_Current";
+  case 1: return "GIP_Func_F "; 
+  case 2: return "GIP_F_Mod  "; 
+  case 3: return "GIP_128    "; 
+  default: throw "Invalid function!";
+  }
+}
+
+/////////////////////////////////////////////////////////
+// Other miscellaneous functions
+/////////////////////////////////////////////////////////
+
+double GetSineFrom3Points(const Point64& a, const Point64& b, const Point64& c)
+{
+  double dpB = DotProduct(a, b, c);
+  double SqrCosB = dpB * dpB / (DistanceSqr(a, b) * DistanceSqr(b, c));
+  double cos2B = SqrCosB * 2 - 1; // trig. itentity
+  return std::sqrt(1 - cos2B);    // sin(B) = Sqrt(1-cos(2B))
+}
+
+static inline Point64 MakeRandomPoint(int64_t min_val, int64_t max_val)
+{
+  std::random_device rd;
+  std::mt19937 gen(rd());
+  std::uniform_int_distribution<int64_t> x(min_val, max_val);
+  std::uniform_int_distribution<int64_t> y(min_val, max_val);
+  return Point64(x(gen), y(gen));
+}
+
+/////////////////////////////////////////////////////////
+// global data storage
+/////////////////////////////////////////////////////////
+
+struct TestRecord
+{
+public:
+  Point64 actual, pt1, pt2, pt3, pt4;
+  std::vector<Point64> results;
+  TestRecord(const Point64& intersect_pt,
+    const Point64& p1, const Point64& p2, const Point64& p3, const Point64& p4) :
+    actual(intersect_pt), pt1(p1), pt2(p2), pt3(p3), pt4(p4) {
+    results.resize(number_of_test_functions);
+  };
+};
+
+std::vector<TestRecord> tests;
+typedef std::vector<TestRecord>::iterator test_iter;
+
+/////////////////////////////////////////////////////////
+// Benchmark callback functions
+/////////////////////////////////////////////////////////
+
+static void BM_GIP(benchmark::State& state)
+{
+  int64_t idx = state.range(0);
+  state.SetLabel(GetGipFuncName(idx));
+  GipFunction func = GetGipFunc(idx);
+  for (auto _ : state)
+  {
+    for (test_iter test = tests.begin(); test != tests.end(); ++test)
+    {
+      Point64 ip;
+      func(test->pt1, test->pt2, test->pt3, test->pt4, ip);
+      test->results[idx] = ip;
+    }
+  }
+}
+
+static void CustomArguments(benchmark::internal::Benchmark* b)
+{
+  for (int i = 0; i < number_of_test_functions; ++i) 
+    b->Args({ i });
+}
+
+/////////////////////////////////////////////////////////
+// Main Entry
+/////////////////////////////////////////////////////////
+
+int main(int argc, char** argv)
+{
+  //setup benchmarking ...
+  benchmark::Initialize(0, nullptr);
+  BENCHMARK(BM_GIP)->Apply(CustomArguments);
+
+  // the closer test segments are to collinear, the less accurate 
+  // calculations will be in determining their intersection points.
+  const double min_angle_degrees = 0.5;
+  const double sine_min_angle = std::sin(min_angle_degrees *PI / 180.0);
+
+  bool first_pass = true;
+  for (int current_pow10 = 12; current_pow10 <= 18; ++current_pow10)
+  {
+    // using random coordinates that are restricted to the specified 
+    // power of 10 range, create multiple TestRecords containing 
+    // segment pairs that intersect at their midpoints
+    int64_t max_coord = static_cast<int64_t>(pow(10, current_pow10));
+    for (int64_t i = 0; i < 100000; ++i)
+    {
+      Point64 ip1 = MakeRandomPoint(-max_coord, max_coord);
+      Point64 ip2 = MakeRandomPoint(-max_coord, max_coord);
+      Point64 actual = MidPoint(ip1, ip2);
+      Point64 ip3 = MakeRandomPoint(-max_coord, max_coord);
+      Point64 ip4 = ReflectPoint(ip3, actual);
+
+      // Exclude segments that are **almost** collinear.
+      if (std::abs(GetSineFrom3Points(ip1, actual, ip3)) < sine_min_angle) continue;      
+      // Alternatively, just exclude segments that are collinear
+      //if (!CrossProduct(ip1, actual, ip3)) continue;
+
+      tests.push_back(TestRecord(actual, ip1, ip2, ip3, ip4));
+    }
+
+    if (first_pass)
+    {
+      // only benchmark the GetIntersectPoint functions once because changing
+      // the maximum range of coordinates won't affect function performance.
+      first_pass = false;
+      std::cout << std::endl << SetConsoleTextColor(green_bold) <<
+        "Benchmark GetIntersectPoint performance ... " << SetConsoleTextColor(reset) <<
+        std::endl << std::endl;
+      benchmark::RunSpecifiedBenchmarks();
+
+      std::cout << std::endl << std::endl << SetConsoleTextColor(green_bold) <<
+        "Compare function accuracy ..." << SetConsoleTextColor(reset) << std::endl <<
+        "and show how it deteriorates when using very large coordinate ranges." << std::endl <<
+        "Distance error is the distance between the calculated and actual intersection points." << std::endl <<
+        "(The largest errors will occur whenever the segments are close to collinear.)" << std::endl;
+    }
+    else
+    {
+      for (int i = 0; i < number_of_test_functions; ++i)
+      {
+        // although we're not benchmarking, we still need to collect the calculated
+        // intersect points of each TestRecord for each participating function.
+        // (In first_pass above, benchmark::RunSpecifiedBenchmarks() does this internally.)
+        Point64 ip;
+        GipFunction gip_func = GetGipFunc(i);
+        for (test_iter test = tests.begin(); test != tests.end(); ++test)
+        {
+          gip_func(test->pt1, test->pt2, test->pt3, test->pt4, ip);
+          test->results[i] = ip;
+        }
+      }
+    }
+
+    double avg_dists[number_of_test_functions] = { 0 };
+    double worst_dists[number_of_test_functions] = { 0 };
+
+    for (test_iter test = tests.begin(); test != tests.end(); ++test)
+      for (int i = 0; i < number_of_test_functions; ++i)
+      {
+        double dist = Distance(test->actual, test->results[i]);
+        avg_dists[i] += dist;
+        if (dist > worst_dists[i])  worst_dists[i] = dist;
+      }
+
+    std::cout << std::endl << SetConsoleTextColor(cyan_bold) <<
+      "Coordinate ranges between  +/-10^" << current_pow10 <<
+      SetConsoleTextColor(reset) << std::endl;
+
+    for (int i = 0; i < number_of_test_functions; ++i)
+    {
+      avg_dists[i] /= tests.size();
+      std::cout << std::fixed << GetGipFuncName(i) <<
+        ": average distance error = " << std::setprecision(2) << avg_dists[i] <<
+        "; largest dist. = " << std::setprecision(0) << worst_dists[i] << std::endl;
+    }
+    tests.clear();
+  }
+  return 0;
+}

+ 524 - 0
polygon.mod/clipper2/CPP/BenchMark/PointInPolygonBenchmark.cpp

@@ -0,0 +1,524 @@
+#include "benchmark/benchmark.h"
+#include "clipper2/clipper.h"
+#include "CommonUtils.h"
+#include "ClipFileLoad.h"
+#include <iostream>
+#include <cstdlib>
+#include <ctime>
+
+using namespace Clipper2Lib;
+
+enum ConsoleTextColor {
+  reset = 0,
+  //normal text colors ...
+  red = 31, green = 32, yellow = 33, blue = 34, magenta = 35, cyan = 36, white = 37,
+  //bold text colors ...
+  red_bold = 91, green_bold = 92, yellow_bold = 93, blue_bold = 94,
+  magenta_bold = 95, cyan_bold = 96, white_bold = 97
+};
+
+//////////////////////////////////////////////////////////////////////////////////////
+// SetConsoleTextColor: a simple class to adjust Console Text Colors (Windows & Linux)
+//////////////////////////////////////////////////////////////////////////////////////
+
+struct SetConsoleTextColor
+{
+private:
+  ConsoleTextColor _color;
+public:
+  SetConsoleTextColor(ConsoleTextColor color) : _color(color) {};
+
+  static friend std::ostream& operator<< (std::ostream& out, SetConsoleTextColor const& scc)
+  {
+    return out << "\x1B[" << scc._color << "m";
+  }
+};
+//////////////////////////////////////////////////////////////////////////////////////
+
+
+typedef std::function<PointInPolygonResult(const Point64&, const Path64&)> PipFunction;
+
+/////////////////////////////////////////////////////////
+// PIP1: This is the current Clipper2 PointInPolygon code
+/////////////////////////////////////////////////////////
+inline PointInPolygonResult PIP1(const Point64& pt, const Path64& polygon)
+{
+  int val = 0;
+  typename Path64::const_iterator cbegin = polygon.cbegin(), first = cbegin, curr, prev;
+  typename Path64::const_iterator cend = polygon.cend();
+
+  while (first != cend && first->y == pt.y) ++first;
+  if (first == cend) // not a proper polygon
+    return PointInPolygonResult::IsOutside;
+
+  bool is_above = first->y < pt.y, starting_above = is_above;
+  curr = first + 1;
+  while (true)
+  {
+    if (curr == cend)
+    {
+      if (cend == first || first == cbegin) break;
+      cend = first;
+      curr = cbegin;
+    }
+
+    if (is_above)
+    {
+      while (curr != cend && curr->y < pt.y) ++curr;
+      if (curr == cend) continue;
+    }
+    else
+    {
+      while (curr != cend && curr->y > pt.y) ++curr;
+      if (curr == cend) continue;
+    }
+
+    if (curr == cbegin)
+      prev = polygon.cend() - 1;
+    else
+      prev = curr - 1;
+
+    if (curr->y == pt.y)
+    {
+      if (curr->x == pt.x ||
+        (curr->y == prev->y &&
+          ((pt.x < prev->x) != (pt.x < curr->x))))
+        return PointInPolygonResult::IsOn;
+      ++curr;
+      if (curr == first) break;
+      continue;
+    }
+
+    if (pt.x < curr->x && pt.x < prev->x)
+    {
+      // we're only interested in edges crossing on the left
+    }
+    else if (pt.x > prev->x && pt.x > curr->x)
+      val = 1 - val; // toggle val
+    else
+    {
+      double d = CrossProduct(*prev, *curr, pt);
+      if (d == 0) return PointInPolygonResult::IsOn;
+      if ((d < 0) == is_above) val = 1 - val;
+    }
+    is_above = !is_above;
+    ++curr;
+  }
+
+  if (is_above != starting_above)
+  {
+    cend = polygon.cend();
+    if (curr == cend) curr = cbegin;
+    if (curr == cbegin) prev = cend - 1;
+    else prev = curr - 1;
+    double d = CrossProduct(*prev, *curr, pt);
+    if (d == 0) return PointInPolygonResult::IsOn;
+    if ((d < 0) == is_above) val = 1 - val;
+  }
+
+  return (val == 0) ?
+    PointInPolygonResult::IsOutside :
+    PointInPolygonResult::IsInside;
+}
+
+
+/////////////////////////////////////////////////////////
+// PIP2: This is a not fully tested modification of the
+// current Clipper2 PointInPolygon code. It's a little
+// simpler and also marginally faster.
+/////////////////////////////////////////////////////////
+inline PointInPolygonResult PIP2(const Point64& pt, const Path64& polygon)
+{
+  if (!polygon.size()) return PointInPolygonResult::IsOutside;
+  Path64::const_iterator cend = polygon.cend();
+  Path64::const_iterator last = cend - 1;
+  Path64::const_iterator first = polygon.cbegin();
+  Path64::const_iterator curr = first;
+  Path64::const_iterator prev = last;
+
+  bool is_above;
+  if (prev->y == pt.y)
+  {
+    if (pt == *prev) return PointInPolygonResult::IsOn;
+    if ((curr->y == pt.y) &&  ((curr->x == pt.x) ||
+      ((pt.x > prev->x) == (pt.x < curr->x))))
+        return PointInPolygonResult::IsOn;
+    Path64::const_reverse_iterator  pr = polygon.crbegin() +1;
+    while (pr != polygon.crend() && pr->y == pt.y) ++pr;
+    is_above = pr == polygon.crend() || pr->y < pt.y;
+  }
+  else is_above = prev->y < pt.y;
+
+  int val = 0;
+  while (curr != cend)
+  {
+    if (is_above)
+    {
+      while (curr != cend && curr->y < pt.y) ++curr;
+      if (curr == cend) break;
+    }
+    else
+    {
+      while (curr != cend && curr->y > pt.y) ++curr;
+      if (curr == cend) break;
+    }
+
+    prev = (curr == first) ? last : curr - 1;
+    if (curr->y == pt.y)
+    {
+      if ((curr->x == pt.x) || ((curr->y == prev->y) &&
+        ((pt.x > prev->x) == (pt.x < curr->x))))
+          return PointInPolygonResult::IsOn;
+      ++curr;
+      continue;
+    }
+
+    if (pt.x < curr->x && pt.x < prev->x)
+    {
+      // we're only interested in edges crossing on the left
+    }
+    else if (pt.x > prev->x && pt.x > curr->x)
+      ++val;
+    else
+    {
+      double d = CrossProduct(*prev, *curr, pt); //avoids integer overflow
+      if (d == 0) return PointInPolygonResult::IsOn;
+      if ((d < 0) == is_above) ++val;
+    }
+    is_above = !is_above;
+    ++curr;
+  }
+
+  return (val % 2) ?
+    PointInPolygonResult::IsInside :
+    PointInPolygonResult::IsOutside;
+}
+
+/////////////////////////////////////////////////////////
+// PIP3: An entirely different algorithm for comparision.
+// "Optimal Reliable Point-in-Polygon Test and
+// Differential Coding Boolean Operations on Polygons"
+// by Jianqiang Hao et al.
+// Symmetry 2018, 10(10), 477; https://doi.org/10.3390/sym10100477
+/////////////////////////////////////////////////////////
+static PointInPolygonResult PIP3(const Point64&pt, const Path64&path)
+{
+  if (!path.size()) return PointInPolygonResult::IsOutside;
+  int64_t x1, y1, x2, y2;
+  int k = 0;
+  Path64::const_iterator itPrev = path.cend() - 1;
+  Path64::const_iterator itCurr = path.cbegin();
+  for ( ; itCurr != path.cend(); ++itCurr)
+  {
+    y1 = itPrev->y - pt.y;
+    y2 = itCurr->y - pt.y;
+    if (((y1 < 0) && (y2 < 0)) || ((y1 > 0) && (y2 > 0)))
+    {
+      itPrev = itCurr;
+      continue;
+    }
+
+    x1 = itPrev->x - pt.x;
+    x2 = itCurr->x - pt.x;
+    if ((y1 <= 0) && (y2 > 0))
+    {
+      //double f = double(x1) * y2 - double(x2) * y1; // avoids int overflow
+      int64_t f = x1 * y2 - x2 * y1;
+      if (f > 0) ++k;
+      else if (f == 0) return PointInPolygonResult::IsOn;
+    }
+    else if ((y1 > 0) && (y2 <= 0))
+    {
+      int64_t f = x1 * y2 - x2 * y1;
+      if (f < 0) ++k;
+      else if (f == 0) return PointInPolygonResult::IsOn;
+    }
+    else if (((y2 == 0) && (y1 < 0)) || ((y1 == 0) && (y2 < 0)))
+    {
+      int64_t f = x1 * y2 - x2 * y1;
+      if (f == 0) return PointInPolygonResult::IsOn;
+    }
+    else if ((y1 == 0) && (y2 == 0) &&
+      (((x2 <= 0) && (x1 >= 0)) || ((x1 <= 0) && (x2 >= 0))))
+        return PointInPolygonResult::IsOn;
+    itPrev = itCurr;
+  }
+  if (k % 2) return PointInPolygonResult::IsInside;
+  return PointInPolygonResult::IsOutside;
+}
+
+
+/////////////////////////////////////////////////////////
+// global data structures
+/////////////////////////////////////////////////////////
+
+const Path64 points_of_interest_outside = 
+  MakePath({ 21887,10420, 21726,10825, 21662,10845, 21617,10890 });
+const Path64 points_of_interest_inside =
+  MakePath({ 21887,10430, 21843,10520, 21810,10686, 21900,10461 });
+
+Point64 mp;
+Paths64 paths;
+std::vector < std::vector<PointInPolygonResult> > pipResults;
+
+
+/////////////////////////////////////////////////////////
+// Benchmark callback functions
+/////////////////////////////////////////////////////////
+
+static void BM_PIP1(benchmark::State& state)
+{
+  int64_t idx = state.range(0);
+  for (auto _ : state)
+    pipResults[0][idx] = PIP1(mp, paths[idx]);
+
+}
+
+static void BM_PIP2(benchmark::State& state)
+{
+  int64_t idx = state.range(0);
+  for (auto _ : state)
+    pipResults[1][idx] = PIP2(mp, paths[idx]);
+}
+
+static void BM_PIP3(benchmark::State& state)
+{
+  int64_t idx = state.range(0);
+  for (auto _ : state)
+    pipResults[2][idx] = PIP3(mp, paths[idx]);
+}
+
+/////////////////////////////////////////////////////////
+// Miscellaneous functions
+/////////////////////////////////////////////////////////
+
+static void CustomArguments(benchmark::internal::Benchmark* b)
+{
+  for (int i = 0; i < paths.size(); ++i) b->Args({ i });
+}
+
+inline PipFunction GetPIPFunc(int index)
+{
+  PipFunction result;
+  switch (index)
+  {
+    case 0: result = PIP1; break;
+    case 1: result = PIP2; break;
+    case 2: result = PIP3; break;
+    default: throw "oops! - wrong function!";
+  }
+  return result;
+}
+
+/////////////////////////////////////////////////////////
+// Error checking functions
+/////////////////////////////////////////////////////////
+
+static void DoErrorTest1_internal(const Path64& pts_of_int, const Paths64& paths,
+  PipFunction pip_func, PointInPolygonResult expected)
+{
+  Path64 error_points;
+
+  for (Point64 poi : pts_of_int)
+  {
+    size_t inside_cnt = 0;
+    for (const Path64& path : paths)
+      if (pip_func(poi, path) == PointInPolygonResult::IsInside) ++inside_cnt;
+    switch (expected)
+    {
+    case PointInPolygonResult::IsInside:
+      if (inside_cnt != 1) error_points.push_back(poi); break;
+    case PointInPolygonResult::IsOutside:
+      if (inside_cnt) error_points.push_back(poi); break;
+    }
+  }
+
+  if (error_points.size())
+  {
+    size_t high_error = error_points.size() - 1;
+    std::cout << SetConsoleTextColor(red_bold) << " Errors at ";
+    for (size_t i = 0; i < high_error; ++i) std::cout << "(" << error_points[i] << "), ";
+    std::cout << "(" << error_points[high_error] << ")." << SetConsoleTextColor(reset) << std::endl;
+  }
+  else
+    std::cout << " No errors found." << std::endl;
+}
+
+static void DoErrorTest1(int index)
+{
+  PipFunction pip_func = GetPIPFunc(index);
+
+  std::cout << SetConsoleTextColor(green_bold) <<
+    "Testing PIP" << index +1 << "/outside:" << SetConsoleTextColor(reset);
+  DoErrorTest1_internal(points_of_interest_outside, paths,
+    pip_func, PointInPolygonResult::IsOutside);
+
+  std::cout << SetConsoleTextColor(green_bold) <<
+    "Testing PIP" << index +1 << "/inside :" << SetConsoleTextColor(reset);
+  DoErrorTest1_internal(points_of_interest_inside, paths,
+    pip_func, PointInPolygonResult::IsInside);
+}
+
+static void DoErrorTest2(int index)
+{
+  PipFunction pip_func = GetPIPFunc(index);
+
+  std::vector<size_t> errors;
+  std::cout << SetConsoleTextColor(green_bold) <<
+    "Testing PIP" << index +1 << SetConsoleTextColor(reset) <<":";
+  for (size_t i = 0; i < paths.size(); ++i)
+    if (pip_func(mp, paths[i]) != pipResults[0][i]) errors.push_back(i);
+  if (errors.size())
+  {
+    size_t high_error = errors.size() - 1;
+    std::cout << SetConsoleTextColor(red_bold) << " Error in ";
+    for (size_t i = 0; i < high_error; ++i)
+      std::cout << errors[i] << " and ";
+    std::cout << errors[high_error] << "." <<
+      SetConsoleTextColor(reset) << std::endl;
+  }
+  else
+    std::cout << " No errors found." << std::endl;
+}
+
+
+/////////////////////////////////////////////////////////
+// Main Entry
+/////////////////////////////////////////////////////////
+
+int main(int argc, char** argv)
+{
+  std::cout << SetConsoleTextColor(cyan_bold) <<
+    "Simple error checks ..." << SetConsoleTextColor(reset) <<
+    std::endl;
+
+  //////////////////////////////////////////////////////////////
+  // 1. Very basic error testing 
+  //////////////////////////////////////////////////////////////
+
+  std::cout << std::endl << SetConsoleTextColor(yellow_bold) <<
+    "Tests for errors #1:" << SetConsoleTextColor(reset) << std::endl << 
+    "(Reusing 'TestPolytreeHoles' tests)" << std::endl << std::endl;
+
+  // 1a. use const paths (PolytreeHoleOwner2.txt) with changing points of interest
+
+  Paths64 subject, subject_open, clip;
+  int64_t _, __;
+  ClipType ___;
+  FillRule ____;
+  const std::string test_file = "../../../../../Tests/PolytreeHoleOwner2.txt";
+  if (!FileExists(test_file)) return 1;
+  std::ifstream ifs(test_file);
+  if (!ifs || !ifs.good()) return 1;
+  LoadTestNum(ifs, 1, paths, subject_open, clip, _, __, ___, ____);
+  ifs.close();
+    
+  for (int i = 0; i < 3; ++i) DoErrorTest1(i);
+
+  // 1b. Use a const point of interest (10,10) against various paths
+
+  std::cout << std::endl << SetConsoleTextColor(yellow_bold) <<
+    "Tests for errors #2:" << SetConsoleTextColor(reset) << std::endl <<
+    "(Testing with 'unusual' polygons)" << std::endl << std::endl;
+
+  mp = Point64(10, 10);
+  paths.clear();
+  pipResults.clear();
+  pipResults.resize(1);
+  paths.push_back({}); // ie test an empty path
+  pipResults[0].push_back(PointInPolygonResult::IsOutside);
+  paths.push_back(MakePath({ 100,10, 200,10 }));
+  pipResults[0].push_back(PointInPolygonResult::IsOutside);
+  paths.push_back(MakePath({ 100,10, 200,10, 10,10, 20,20 }));
+  pipResults[0].push_back(PointInPolygonResult::IsOn);
+  paths.push_back(MakePath({ 10,10 }));
+  pipResults[0].push_back(PointInPolygonResult::IsOn);
+  paths.push_back(MakePath({ 100,10 }));
+  pipResults[0].push_back(PointInPolygonResult::IsOutside);
+  paths.push_back(MakePath({ 100,10, 110,20, 200,10, 10,10, 20,20 }));
+  pipResults[0].push_back(PointInPolygonResult::IsOn);
+  paths.push_back(MakePath({ 100,10, 110,20, 200,10, 20,20 }));
+  pipResults[0].push_back(PointInPolygonResult::IsOutside);
+  paths.push_back(MakePath({ 200,0, 0,0, 10,20, 200,0, 20,0 }));
+  pipResults[0].push_back(PointInPolygonResult::IsInside);
+  paths.push_back(MakePath({ 0,0, 20,20, 100,0 }));
+  pipResults[0].push_back(PointInPolygonResult::IsOn);
+
+  for (int i = 0; i < 3; ++i) DoErrorTest2(i);
+  std::cout << std::endl;
+
+  // 2. Benchmark functions
+
+  std::cout << std::endl << SetConsoleTextColor(cyan_bold) <<
+    "Benchmarking ..." << SetConsoleTextColor(reset) << std::endl;
+  std::cout << "Note: function performance varies depending on the proportion of edges" << 
+    std::endl << "that intersect with an imaginary horizontal line passing through the" <<
+    std::endl << "point of interest." << std::endl << std::endl;
+
+  unsigned int width = 600000, height = 400000, count = 10000000;
+  mp = Point64(width / 2, height / 2);
+
+
+  std::cout << std::endl << SetConsoleTextColor(yellow_bold) <<
+    "Benchmarks 1:" << SetConsoleTextColor(reset) << std::endl;
+
+  paths.clear();
+  for (int i = 0; i < 5; ++i)
+    paths.push_back(Ellipse(mp, width / 2.0, height / 2.0, count));
+  std::cout << "A single elliptical path (" <<
+    width << " x " << height << ")" << std::endl <<
+    "Edge count =  " << count << ". " << std::endl <<
+    "Point (" << mp << ")" << std::endl << std::endl;
+
+  pipResults.clear();
+  pipResults.resize(3);
+  for (size_t i = 0; i < 3; ++i) pipResults[i].resize(paths.size());
+
+  benchmark::Initialize(0, nullptr);
+  BENCHMARK(BM_PIP1)->Apply(CustomArguments); // current Clipper2
+  BENCHMARK(BM_PIP2)->Apply(CustomArguments); // modified Clipper2
+  BENCHMARK(BM_PIP3)->Apply(CustomArguments); // Hao et al. (2018)
+  benchmark::RunSpecifiedBenchmarks(benchmark::CreateDefaultDisplayReporter());
+  
+  std::cout << std::endl << std::endl << SetConsoleTextColor(yellow_bold) <<
+    "Benchmarks 2:" << SetConsoleTextColor(reset) << std::endl;
+
+  std::cout << "A random self-intersecting polygon (" <<
+    width << " x " << height << ")" << std::endl <<
+    "Edge count =  " << count << ". " << std::endl <<
+    "Point (" << mp << ")" << std::endl << std::endl;
+
+  paths.clear();
+  for (int i = 0; i < 5; ++i)
+    paths.push_back(MakeRandomPoly(width, height, count));
+
+  pipResults.clear();
+  pipResults.resize(3);
+  for (size_t i = 0; i < 3; ++i) pipResults[i].resize(paths.size());
+
+  // rerun benchmarks using different polygons
+  benchmark::RunSpecifiedBenchmarks(benchmark::CreateDefaultDisplayReporter());
+
+  std::cout << std::endl;
+  // compare results to ensure they all agree :)
+  const std::string bad_filename = "test_pip_";
+  for (size_t i = 0; i < pipResults[0].size(); ++i)
+  {
+    if ((pipResults[0][i] == pipResults[1][i]) &&
+      (pipResults[0][i] == pipResults[2][i])) continue;
+
+    if (pipResults[0][i] != pipResults[1][i])
+      std::cout << "PIP2 returned the " << SetConsoleTextColor(red_bold) << "wrong " <<
+      SetConsoleTextColor(reset) << "result:" << std::endl;
+    if (pipResults[0][i] != pipResults[2][i])
+      std::cout << "PIP3 returned the " << SetConsoleTextColor(red_bold) << "wrong " <<
+      SetConsoleTextColor(reset) << "result:" << std::endl;
+
+    std::cout << "Problematic PIP path saved to - " << bad_filename << i << ".txt" << std::endl;
+    std::ofstream of(bad_filename);
+    of << paths[i] << std::endl;
+    of.close();
+    break;
+  }
+
+}

+ 6 - 0
polygon.mod/clipper2/CPP/BenchMark/README.md

@@ -0,0 +1,6 @@
+# google benchmark
+this can be enabled by setting the option in the `CPP/CMakeLists.txt`
+
+```cmake
+option(USE_EXTERNAL_GBENCHMARK "Use the googlebenchmark" ON)
+```

+ 73 - 0
polygon.mod/clipper2/CPP/BenchMark/StripDuplicateBenchmark.cpp

@@ -0,0 +1,73 @@
+#include "benchmark/benchmark.h"
+#include "clipper2/clipper.h"
+#include "CommonUtils.h"
+#include <iostream>
+
+using namespace Clipper2Lib;
+
+// globals 
+Paths64 test_paths;
+
+// Previous (slow) StripDuplicates function - copies path
+template <typename T>
+inline Path<T> StripDuplicates1(const 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;
+}
+
+// Current StripDuplicates function - modifies the path in-place (ie avoids copying)
+template<typename T>
+inline void StripDuplicates2(Path<T>& path, bool is_closed_path)
+{
+  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();
+}
+
+static void StripDuplicates_OLD(benchmark::State &state)
+{
+  for (auto _ : state) 
+  {
+    for (Path64& p: test_paths)
+      p = StripDuplicates1(p, true);
+  }
+}
+
+static void StripDuplicates_NEW(benchmark::State &state) 
+{
+  for (auto _ : state) {
+    for (Path64& p : test_paths)
+      StripDuplicates2(p, true); 
+  }
+}
+
+
+int main(int argc, char** argv)
+{  
+  const size_t max_paths = 5;
+  const int width = 6000, height = 4000, count = 10000;
+  test_paths.reserve(max_paths);
+  for (size_t i = 1; i <= max_paths; ++i)
+    test_paths.push_back(MakeRandomPoly(width, height, count));
+
+  benchmark::Initialize(0, nullptr);
+  BENCHMARK(StripDuplicates_OLD);
+  BENCHMARK(StripDuplicates_NEW);
+  benchmark::RunSpecifiedBenchmarks(benchmark::CreateDefaultDisplayReporter());
+}

+ 189 - 93
polygon.mod/clipper2/CPP/CMakeLists.txt

@@ -1,28 +1,48 @@
-cmake_minimum_required(VERSION 3.10)
-project(Clipper2 VERSION 1.0.6 LANGUAGES C CXX)
+cmake_minimum_required(VERSION 3.15)
+project(Clipper2 VERSION 1.3.0 LANGUAGES C CXX)
 
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-set(CMAKE_CXX_STANDARD 17)
+if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17)
+    set(CMAKE_CXX_STANDARD 17)
+endif()
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_CXX_EXTENSIONS OFF)
 set_property(GLOBAL PROPERTY USE_FOLDERS ON)
 
+# CLIPPER2_HI_PRECISION: See GetIntersectPoint() in clipper.core.h 
+option(CLIPPER2_HI_PRECISION "Caution: enabling this will compromise performance" OFF)
+
 option(CLIPPER2_UTILS "Build utilities" ON)
 option(CLIPPER2_EXAMPLES "Build examples" ON)
 option(CLIPPER2_TESTS "Build tests" ON)
 option(USE_EXTERNAL_GTEST "Use system-wide installed GoogleTest" OFF)
+option(USE_EXTERNAL_GBENCHMARK "Use the googlebenchmark" OFF)
 option(BUILD_SHARED_LIBS "Build shared libs" OFF)
+set(CLIPPER2_USINGZ "ON" CACHE STRING "Build Clipper2Z, either \"ON\" or \"OFF\" or \"ONLY\"")
+
+# CLIPPER2_MAX_DECIMAL_PRECISION: maximum decimal precision when scaling PathsD to Paths64.
+# Caution: excessive scaling will increase the likelihood of integer overflow errors.
+set(CLIPPER2_MAX_DECIMAL_PRECISION 8 CACHE STRING "Maximum decimal precision range")
+
+
+if (APPLE)
+    set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib")
+endif ()
 
 include(GNUInstallDirs)
+set(CLIPPER2_INC_FOLDER ${PROJECT_SOURCE_DIR}/Clipper2Lib/include/clipper2)
+configure_file(clipper.version.in 
+  ${CLIPPER2_INC_FOLDER}/clipper.version.h NEWLINE_STYLE UNIX)
 
 set(CLIPPER2_INC
-  Clipper2Lib/include/clipper2/clipper.h
-  Clipper2Lib/include/clipper2/clipper.core.h
-  Clipper2Lib/include/clipper2/clipper.engine.h
-  Clipper2Lib/include/clipper2/clipper.export.h
-  Clipper2Lib/include/clipper2/clipper.minkowski.h
-  Clipper2Lib/include/clipper2/clipper.offset.h
-  Clipper2Lib/include/clipper2/clipper.rectclip.h
+  ${CLIPPER2_INC_FOLDER}/clipper.h
+  ${CLIPPER2_INC_FOLDER}/clipper.version.h
+  ${CLIPPER2_INC_FOLDER}/clipper.core.h
+  ${CLIPPER2_INC_FOLDER}/clipper.engine.h
+  ${CLIPPER2_INC_FOLDER}/clipper.export.h
+  ${CLIPPER2_INC_FOLDER}/clipper.minkowski.h
+  ${CLIPPER2_INC_FOLDER}/clipper.offset.h
+  ${CLIPPER2_INC_FOLDER}/clipper.rectclip.h
 )
 
 set(CLIPPER2_SRC
@@ -31,37 +51,57 @@ set(CLIPPER2_SRC
   Clipper2Lib/src/clipper.rectclip.cpp
 )
 
-set(PCFILE "${CMAKE_CURRENT_BINARY_DIR}/Clipper2.pc")
-set(PCFILEZ "${CMAKE_CURRENT_BINARY_DIR}/Clipper2Z.pc")
+set(CLIPPER2_LIBS "") # one or both of Clipper2/Clipper2Z
 
-# 2d version of Clipper2
-add_library(Clipper2 ${CLIPPER2_INC} ${CLIPPER2_SRC})
+# primary Clipper2 library
+if (NOT (CLIPPER2_USINGZ STREQUAL "ONLY"))
+  list(APPEND CLIPPER2_LIBS Clipper2)
+  add_library(Clipper2 ${CLIPPER2_INC} ${CLIPPER2_SRC})
 
-target_include_directories(Clipper2
-  PUBLIC Clipper2Lib/include
-)
+  target_compile_definitions(
+    Clipper2 PUBLIC
+    CLIPPER2_MAX_DECIMAL_PRECISION=${CLIPPER2_MAX_DECIMAL_PRECISION}
+    $<$<BOOL:${CLIPPER2_HI_PRECISION}>:CLIPPER2_HI_PRECISION>
+  )
 
-# Clipper2 but with USINGZ defined
-add_library(Clipper2Z ${CLIPPER2_INC} ${CLIPPER2_SRC})
 
-target_compile_definitions(Clipper2Z PUBLIC USINGZ)
+  target_include_directories(Clipper2
+    PUBLIC Clipper2Lib/include
+  )
 
-target_include_directories(Clipper2Z
-  PUBLIC Clipper2Lib/include
-)
+  if (MSVC)
+    target_compile_options(Clipper2 PRIVATE /W4 /WX)
+  else()
+    target_compile_options(Clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror)
+    target_link_libraries(Clipper2 PUBLIC -lm)
+  endif()
+endif()
 
-if (MSVC)
-  target_compile_options(Clipper2 PRIVATE /W4 /WX)
-  target_compile_options(Clipper2Z PRIVATE /W4 /WX)
-else()
-  target_compile_options(Clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror)
-  target_link_libraries(Clipper2 PUBLIC -lm)
+# secondary Clipper2 library with USINGZ defined (if required)
+if (NOT (CLIPPER2_USINGZ STREQUAL "OFF"))
+  list(APPEND CLIPPER2_LIBS Clipper2Z)
+  add_library(Clipper2Z ${CLIPPER2_INC} ${CLIPPER2_SRC})
+
+  target_compile_definitions(
+    Clipper2Z PUBLIC
+    USINGZ
+    CLIPPER2_MAX_DECIMAL_PRECISION=${CLIPPER2_MAX_DECIMAL_PRECISION}
+    $<$<BOOL:${CLIPPER2_HI_PRECISION}>:CLIPPER2_HI_PRECISION>
+  )
+
+  target_include_directories(Clipper2Z
+    PUBLIC Clipper2Lib/include
+  )
 
-  target_compile_options(Clipper2Z PRIVATE -Wall -Wextra -Wpedantic -Werror)
-  target_link_libraries(Clipper2Z PUBLIC -lm)
+  if (MSVC)
+    target_compile_options(Clipper2Z PRIVATE /W4 /WX)
+  else()
+    target_compile_options(Clipper2Z PRIVATE -Wall -Wextra -Wpedantic -Werror)
+    target_link_libraries(Clipper2Z PUBLIC -lm)
+  endif()
 endif()
 
-set_target_properties(Clipper2 Clipper2Z PROPERTIES FOLDER Libraries
+set_target_properties(${CLIPPER2_LIBS} PROPERTIES FOLDER Libraries
                                          VERSION ${PROJECT_VERSION}
                                          SOVERSION ${PROJECT_VERSION_MAJOR}
                                          PUBLIC_HEADER "${CLIPPER2_INC}"
@@ -72,31 +112,42 @@ if(CLIPPER2_UTILS OR CLIPPER2_TESTS OR CLIPPER2_EXAMPLES)
     Utils/clipper.svg.h
     Utils/ClipFileLoad.h
     Utils/ClipFileSave.h
+    Utils/Timer.h
+    Utils/Colors.h
+    Utils/CommonUtils.h
   )
   set(CLIPPER2_UTILS_SRC
     Utils/clipper.svg.cpp
     Utils/ClipFileLoad.cpp
     Utils/ClipFileSave.cpp
   )
+  set(CLIPPER2_UTILS "") # one or both of Clipper2utils/Clipper2Zutils
 
-  add_library(Clipper2utils STATIC ${CLIPPER2_UTILS_INC} ${CLIPPER2_UTILS_SRC})
+  if (NOT (CLIPPER2_USINGZ STREQUAL "ONLY"))
+    list(APPEND CLIPPER2_UTILS Clipper2utils)
+    add_library(Clipper2utils STATIC ${CLIPPER2_UTILS_INC} ${CLIPPER2_UTILS_SRC})
 
-  target_link_libraries(Clipper2utils PUBLIC Clipper2)
-  target_include_directories(Clipper2utils
-    PUBLIC Utils
-  )
+    target_link_libraries(Clipper2utils PUBLIC Clipper2)
+    target_include_directories(Clipper2utils
+      PUBLIC Utils
+    )
+  endif()
 
-  add_library(Clipper2Zutils STATIC ${CLIPPER2_UTILS_INC} ${CLIPPER2_UTILS_SRC})
+  if (NOT (CLIPPER2_USINGZ STREQUAL "OFF"))
+    list(APPEND CLIPPER2_UTILS Clipper2Zutils)
+    add_library(Clipper2Zutils STATIC ${CLIPPER2_UTILS_INC} ${CLIPPER2_UTILS_SRC})
 
-  target_link_libraries(Clipper2Zutils PUBLIC Clipper2Z)
-  target_include_directories(Clipper2Zutils
-    PUBLIC Utils
-  )
+    target_link_libraries(Clipper2Zutils PUBLIC Clipper2Z)
+    target_include_directories(Clipper2Zutils
+      PUBLIC Utils
+    )
+  endif()
 
-  set_target_properties(Clipper2utils Clipper2Zutils PROPERTIES FOLDER Libraries)
+  set_target_properties(${CLIPPER2_UTILS} PROPERTIES FOLDER Libraries)
   if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-    target_compile_options(Clipper2utils PRIVATE -Wno-unused-variable -Wno-unused-function)
-    target_compile_options(Clipper2Zutils PRIVATE -Wno-unused-variable -Wno-unused-function)
+    foreach(lib ${CLIPPER2_UTILS})
+      target_compile_options(${lib} PRIVATE -Wno-unused-variable -Wno-unused-function)
+    endforeach()
   endif()
 endif()
 
@@ -104,37 +155,50 @@ if(CLIPPER2_EXAMPLES)
   ##########################################################################
   ##########################################################################
 
-  add_executable(ConsoleDemo1 Examples/ConsoleDemo1/ConsoleDemo1.cpp)
-  target_link_libraries(ConsoleDemo1 PRIVATE Clipper2 Clipper2utils)
-
-  add_executable(ConsoleDemo2 Examples/ConsoleDemo2/ConsoleDemo2.cpp)
-  target_link_libraries(ConsoleDemo2 PRIVATE Clipper2 Clipper2utils)
-
-  file(COPY Examples/InflateDemo/rabbit.svg DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ )
-
-  add_executable(InflateDemo1 Examples/InflateDemo/InflateDemo1.cpp)
-  target_link_libraries(InflateDemo1 PRIVATE Clipper2 Clipper2utils)
+  set(ALL_EXAMPLES "") # 2d and 3d examples (if enabled)
+
+  if (NOT (CLIPPER2_USINGZ STREQUAL "ONLY"))
+    set(EXAMPLES
+      Benchmarks
+      Inflate
+      MemLeakTest
+      PolygonSamples
+      RandomClipping
+      UnionClipping
+      RectClipping
+      SimpleClipping
+      VariableOffset
+    )
+
+    foreach(ex ${EXAMPLES})
+      add_executable(${ex} Examples/${ex}/${ex}.cpp)
+      target_link_libraries(${ex} PRIVATE Clipper2 Clipper2utils)
+    endforeach()
+
+    file(COPY Examples/Inflate/rabbit.svg DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ )
+    list(APPEND ALL_EXAMPLES ${EXAMPLES})
+  endif()
 
-  add_executable(RectClipDemo1 Examples/RectClipDemo/RectClipDemo1.cpp)
-  target_link_libraries(RectClipDemo1 PRIVATE Clipper2 Clipper2utils)
+  if (NOT (CLIPPER2_USINGZ STREQUAL "OFF"))
+    set(EXAMPLESZ "UsingZ")
+    foreach(ex ${EXAMPLESZ})
+      add_executable(${ex} Examples/${ex}/${ex}.cpp)
+      target_link_libraries(${ex} PRIVATE Clipper2Z Clipper2Zutils)
+    endforeach()
 
-  add_executable(UsingZ1 Examples/UsingZ/UsingZ1.cpp)
-  target_link_libraries(UsingZ1 PRIVATE Clipper2Z Clipper2Zutils)
+    list(APPEND ALL_EXAMPLES ${EXAMPLESZ})
+  endif()
 
-  set_target_properties(ConsoleDemo1 ConsoleDemo2 InflateDemo1 RectClipDemo1 UsingZ1 PROPERTIES FOLDER Examples)
+  set_target_properties(${ALL_EXAMPLES} PROPERTIES FOLDER Examples)
   if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-    target_compile_options(ConsoleDemo1 PRIVATE -Wno-unused-result -Wno-unused-function)
-    target_compile_options(ConsoleDemo2 PRIVATE -Wno-unused-result -Wno-unused-function)
-    target_compile_options(InflateDemo1 PRIVATE -Wno-unused-result -Wno-unused-function)
-    target_compile_options(RectClipDemo1 PRIVATE -Wno-unused-result -Wno-unused-function)
-    target_compile_options(UsingZ1 PRIVATE -Wno-unused-result -Wno-unused-function)
+    foreach(ex ${ALL_EXAMPLES})
+      target_compile_options(${ex} PRIVATE -Wno-unused-variable -Wno-unused-function)
+    endforeach()
   endif()
 endif()
 
-
 if(CLIPPER2_TESTS)
   # See: https://cliutils.gitlab.io/modern-cmake/chapters/testing/googletest.html
-
   enable_testing()
   if (WIN32)
     set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
@@ -148,56 +212,88 @@ else()
   add_subdirectory("${PROJECT_SOURCE_DIR}/Tests/googletest/")
   set_target_properties(gtest gtest_main PROPERTIES FOLDER GTest)
 endif()
+
+  if(TARGET gtest AND TARGET gtest_main)
+    set(gtest_libs gtest gtest_main)
+  elseif(TARGET GTest::gtest AND TARGET GTest::gtest_main)
+    set(gtest_libs GTest::gtest GTest::gtest_main)
+  endif()
+
   set(ClipperTests_SRC
+    Tests/TestExportHeaders.cpp
     Tests/TestLines.cpp
+    Tests/TestOffsets.cpp
     Tests/TestOffsetOrientation.cpp
     Tests/TestOrientation.cpp
     Tests/TestPolygons.cpp
-    Tests/TestPolytreeHoles1.cpp
-    Tests/TestPolytreeHoles2.cpp
+    Tests/TestPolytreeHoles.cpp
     Tests/TestPolytreeIntersection.cpp
     Tests/TestPolytreeUnion.cpp
     Tests/TestRandomPaths.cpp
     Tests/TestRectClip.cpp
+    Tests/TestSimplifyPath.cpp
     Tests/TestTrimCollinear.cpp
+    Tests/TestWindows.cpp
   )
-  add_executable(ClipperTests ${ClipperTests_SRC})
-  target_link_libraries(ClipperTests gtest gtest_main Clipper2 Clipper2utils)
 
-  add_executable(ClipperTestsZ ${ClipperTests_SRC})
-  target_link_libraries(ClipperTestsZ gtest gtest_main Clipper2Z Clipper2Zutils)
+  set(CLIPPER2_TESTS "") # one or both of ClipperTests/ClipperTestsZ
 
-  if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-    target_compile_options(ClipperTests PRIVATE -Wno-unused-variable -Wno-unused-function)
-    target_compile_options(ClipperTestsZ PRIVATE -Wno-unused-variable -Wno-unused-function)
+  if (NOT (CLIPPER2_USINGZ STREQUAL "ONLY"))
+    list(APPEND CLIPPER2_TESTS "ClipperTests")
+    add_executable(ClipperTests ${ClipperTests_SRC})
+    target_link_libraries(ClipperTests ${gtest_libs} Clipper2 Clipper2utils)
+
+    gtest_discover_tests(ClipperTests
+      # set a working directory to your project root so that you can find test data via paths relative to the project root
+      WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/../Tests
+      PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}"
+    )
   endif()
 
-  set_target_properties(ClipperTests ClipperTestsZ PROPERTIES FOLDER Tests)
+  if (NOT (CLIPPER2_USINGZ STREQUAL "OFF"))
+    list(APPEND CLIPPER2_TESTS "ClipperTestsZ")
+    add_executable(ClipperTestsZ ${ClipperTests_SRC})
+    target_link_libraries(ClipperTestsZ ${gtest_libs} Clipper2Z Clipper2Zutils)
+
+    gtest_discover_tests(ClipperTestsZ
+      # set a working directory so your project root so that you can find test data via paths relative to the project root
+      WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/../Tests
+      PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}"
+      TEST_SUFFIX "_USINGZ"
+    )
+  endif()
 
-  gtest_discover_tests(ClipperTests
-        # set a working directory so your project root so that you can find test data via paths relative to the project root
-        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/../Tests
-        PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}"
-  )
+  if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+    foreach(ts ${CLIPPER2_TESTS})
+      target_compile_options(${ts} PRIVATE -Wno-unused-variable -Wno-unused-function)
+    endforeach()
+  endif()
 
-  gtest_discover_tests(ClipperTestsZ
-    # set a working directory so your project root so that you can find test data via paths relative to the project root
-    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/../Tests
-    PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}"
-    TEST_SUFFIX "_USINGZ"
-  )
+  set_target_properties(${CLIPPER2_TESTS} PROPERTIES FOLDER Tests)
 
   file(COPY ../Tests/PolytreeHoleOwner.txt DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ )
   file(COPY ../Tests/PolytreeHoleOwner2.txt DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ )
   file(COPY ../Tests/Lines.txt DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ )
   file(COPY ../Tests/Polygons.txt DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ )
+  file(COPY ../Tests/Offsets.txt DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ )
 endif()
 
-CONFIGURE_FILE(Clipper2.pc.cmakein "${PCFILE}" @ONLY)
-set(PCFILE_LIB_SUFFIX "Z")
-CONFIGURE_FILE(Clipper2.pc.cmakein "${PCFILEZ}" @ONLY)
+if(USE_EXTERNAL_GBENCHMARK)
+  add_subdirectory(BenchMark)
+endif()
 
-install(TARGETS Clipper2 Clipper2Z
+set(CLIPPER2_PCFILES "")
+foreach(lib ${CLIPPER2_LIBS})
+  set(pc "${CMAKE_CURRENT_BINARY_DIR}/${lib}.pc")
+  list(APPEND CLIPPER2_PCFILES ${pc})
+  CONFIGURE_FILE(Clipper2.pc.cmakein "${pc}" @ONLY)
+endforeach()
+
+install(TARGETS ${CLIPPER2_LIBS}
         PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/clipper2
 )
-install(FILES ${PCFILE} ${PCFILEZ} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+install(FILES ${CLIPPER2_PCFILES} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+
+# disable exceptions
+#string(REGEX REPLACE "/W[3|4]" "/w" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+#add_definitions(-D_HAS_EXCEPTIONS=0) # for STL

+ 507 - 241
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.core.h

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  21 November 2022                                                *
+* Date      :  14 February 2024                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2024                                         *
 * Purpose   :  Core Clipper Library structures and functions                   *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -10,6 +10,7 @@
 #ifndef CLIPPER_CORE_H
 #define CLIPPER_CORE_H
 
+#include <cstdint>
 #include <cstdlib>
 #include <cmath>
 #include <vector>
@@ -17,18 +18,81 @@
 #include <iostream>
 #include <algorithm>
 #include <climits>
+#include <numeric>
+#include "clipper2/clipper.version.h"
 
 namespace Clipper2Lib
 {
-#ifdef __cpp_exceptions
+
+#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS)
+
+  class Clipper2Exception : public std::exception {
+  public:
+    explicit Clipper2Exception(const char* description) :
+      m_descr(description) {}
+    virtual const char* what() const throw() override { return m_descr.c_str(); }
+  private:
+    std::string m_descr;
+  };
+
   static const char* precision_error =
     "Precision exceeds the permitted range";
+  static const char* range_error =
+    "Values exceed permitted range";
+  static const char* scale_error =
+    "Invalid scale (either 0 or too large)";
+  static const char* non_pair_error =
+    "There must be 2 values for each coordinate";
+  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 undefined_error_i   = 32; // fatal
+  const int range_error_i       = 64;
+
+#ifndef PI
   static const double PI = 3.141592653589793238;
+#endif
+
+#ifdef CLIPPER2_MAX_DECIMAL_PRECISION
+  const int CLIPPER2_MAX_DEC_PRECISION = CLIPPER2_MAX_DECIMAL_PRECISION;
+#else
+  const int CLIPPER2_MAX_DEC_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;
+  const double max_coord = static_cast<double>(MAX_COORD);
+  const double min_coord = static_cast<double>(MIN_COORD);
+
+  static const double MAX_DBL = (std::numeric_limits<double>::max)();
+
+  static void DoError([[maybe_unused]] int error_code)
+  {
+#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS)
+    switch (error_code)
+    {
+    case precision_error_i:
+      throw Clipper2Exception(precision_error);
+    case scale_error_i:
+      throw Clipper2Exception(scale_error);
+    case non_pair_error_i:
+      throw Clipper2Exception(non_pair_error);
+    case undefined_error_i:
+      throw Clipper2Exception(undefined_error);
+    case range_error_i:
+      throw Clipper2Exception(range_error);
+    }
+#else
+    ++error_code; // only to stop compiler warning
+#endif
+  }
+
 
   //By far the most widely used filling rules for polygons are EvenOdd
   //and NonZero, sometimes called Alternate and Winding respectively.
@@ -82,10 +146,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;
     }
 
@@ -122,7 +187,7 @@ namespace Clipper2Lib
 
     friend std::ostream& operator<<(std::ostream& os, const Point& point)
     {
-      os << point.x << " " << point.y;
+      os << point.x << "," << point.y;
       return os;
     }
 #endif
@@ -170,6 +235,221 @@ 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)());
+
+  template<typename T>
+  static inline Point<T> MidPoint(const Point<T>& p1, const Point<T>& p2)
+  {
+    Point<T> result;
+    result.x = (p1.x + p2.x) / 2;
+    result.y = (p1.y + p2.y) / 2;
+    return result;
+  }
+
+  // Rect ------------------------------------------------------------------------
+
+  template <typename T>
+  struct Rect;
+
+  using Rect64 = Rect<int64_t>;
+  using RectD = Rect<double>;
+
+  template <typename T>
+  struct Rect {
+    T left;
+    T top;
+    T right;
+    T bottom;
+
+    Rect(T l, T t, T r, T b) :
+      left(l),
+      top(t),
+      right(r),
+      bottom(b) {}
+
+    Rect(bool is_valid = true)
+    {
+      if (is_valid)
+      {
+        left = right = top = bottom = 0;
+      }
+      else
+      {
+        left = top = (std::numeric_limits<T>::max)();
+        right = bottom = std::numeric_limits<T>::lowest();
+      }
+    }
+
+    static Rect<T> InvalidRect()
+    {
+      return {
+        (std::numeric_limits<T>::max)(),
+        (std::numeric_limits<T>::max)(),
+        std::numeric_limits<T>::lowest(),
+        std::numeric_limits<T>::lowest() };
+    }
+
+    bool IsValid() const { return left != (std::numeric_limits<T>::max)(); }
+
+    T Width() const { return right - left; }
+    T Height() const { return bottom - top; }
+    void Width(T width) { right = left + width; }
+    void Height(T height) { bottom = top + height; }
+
+    Point<T> MidPoint() const
+    {
+      return Point<T>((left + right) / 2, (top + bottom) / 2);
+    }
+
+    Path<T> AsPath() const
+    {
+      Path<T> result;
+      result.reserve(4);
+      result.push_back(Point<T>(left, top));
+      result.push_back(Point<T>(right, top));
+      result.push_back(Point<T>(right, bottom));
+      result.push_back(Point<T>(left, bottom));
+      return result;
+    }
+
+    bool Contains(const Point<T>& pt) const
+    {
+      return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom;
+    }
+
+    bool Contains(const Rect<T>& rec) const
+    {
+      return rec.left >= left && rec.right <= right &&
+        rec.top >= top && rec.bottom <= bottom;
+    }
+
+    void Scale(double scale) {
+      left *= scale;
+      top *= scale;
+      right *= scale;
+      bottom *= scale;
+    }
+
+    bool IsEmpty() const { return bottom <= top || right <= left; };
+
+    bool Intersects(const Rect<T>& rec) const
+    {
+      return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) &&
+        ((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 << ") ";
+      return os;
+    }
+  };
+
+  template <typename T1, typename T2>
+  inline Rect<T1> ScaleRect(const Rect<T2>& rect, double scale)
+  {
+    Rect<T1> result;
+
+    if constexpr (std::numeric_limits<T1>::is_integer &&
+      !std::numeric_limits<T2>::is_integer)
+    {
+      result.left = static_cast<T1>(std::round(rect.left * scale));
+      result.top = static_cast<T1>(std::round(rect.top * scale));
+      result.right = static_cast<T1>(std::round(rect.right * scale));
+      result.bottom = static_cast<T1>(std::round(rect.bottom * scale));
+    }
+    else
+    {
+      result.left = rect.left * scale;
+      result.top = rect.top * scale;
+      result.right = rect.right * scale;
+      result.bottom = rect.bottom * scale;
+    }
+    return result;
+  }
+
+  static const Rect64 InvalidRect64 = Rect64::InvalidRect();
+  static const RectD InvalidRectD = RectD::InvalidRect();
+
+  template <typename T>
+  Rect<T> GetBounds(const Path<T>& path)
+  {
+    T xmin = (std::numeric_limits<T>::max)();
+    T ymin = (std::numeric_limits<T>::max)();
+    T xmax = std::numeric_limits<T>::lowest();
+    T ymax = std::numeric_limits<T>::lowest();
+    for (const auto& p : path)
+    {
+      if (p.x < xmin) xmin = p.x;
+      if (p.x > xmax) xmax = p.x;
+      if (p.y < ymin) ymin = p.y;
+      if (p.y > ymax) ymax = p.y;
+    }
+    return Rect<T>(xmin, ymin, xmax, ymax);
+  }
+
+  template <typename T>
+  Rect<T> GetBounds(const Paths<T>& paths)
+  {
+    T xmin = (std::numeric_limits<T>::max)();
+    T ymin = (std::numeric_limits<T>::max)();
+    T xmax = std::numeric_limits<T>::lowest();
+    T ymax = std::numeric_limits<T>::lowest();
+    for (const Path<T>& path : paths)
+      for (const Point<T>& p : path)
+      {
+        if (p.x < xmin) xmin = p.x;
+        if (p.x > xmax) xmax = p.x;
+        if (p.y < ymin) ymin = p.y;
+        if (p.y > ymax) ymax = p.y;
+      }
+    return Rect<T>(xmin, ymin, xmax, ymax);
+  }
+
+  template <typename T, typename T2>
+  Rect<T> GetBounds(const Path<T2>& path)
+  {
+    T xmin = (std::numeric_limits<T>::max)();
+    T ymin = (std::numeric_limits<T>::max)();
+    T xmax = std::numeric_limits<T>::lowest();
+    T ymax = std::numeric_limits<T>::lowest();
+    for (const auto& p : path)
+    {
+      if (p.x < xmin) xmin = p.x;
+      if (p.x > xmax) xmax = p.x;
+      if (p.y < ymin) ymin = p.y;
+      if (p.y > ymax) ymax = p.y;
+    }
+    return Rect<T>(xmin, ymin, xmax, ymax);
+  }
+
+  template <typename T, typename T2>
+  Rect<T> GetBounds(const Paths<T2>& paths)
+  {
+    T xmin = (std::numeric_limits<T>::max)();
+    T ymin = (std::numeric_limits<T>::max)();
+    T xmax = std::numeric_limits<T>::lowest();
+    T ymax = std::numeric_limits<T>::lowest();
+    for (const Path<T2>& path : paths)
+      for (const Point<T2>& p : path)
+      {
+        if (p.x < xmin) xmin = p.x;
+        if (p.x > xmax) xmax = p.x;
+        if (p.y < ymin) ymin = p.y;
+        if (p.y > ymax) ymax = p.y;
+      }
+    return Rect<T>(xmin, ymin, xmax, ymax);
+  }
+
   template <typename T>
   std::ostream& operator << (std::ostream& outstream, const Path<T>& path)
   {
@@ -191,41 +471,73 @@ namespace Clipper2Lib
     return outstream;
   }
 
+
   template <typename T1, typename T2>
-  inline Path<T1> ScalePath(const Path<T2>& path, double scale_x, double scale_y)
+  inline Path<T1> ScalePath(const Path<T2>& path,
+    double scale_x, double scale_y, int& error_code)
   {
     Path<T1> result;
+    if (scale_x == 0 || scale_y == 0)
+    {
+      error_code |= scale_error_i;
+      DoError(scale_error_i);
+      // if no exception, treat as non-fatal error
+      if (scale_x == 0) scale_x = 1.0;
+      if (scale_y == 0) scale_y = 1.0;
+    }
+
     result.reserve(path.size());
 #ifdef USINGZ
-    for (const Point<T2>& pt : path)
-      result.push_back(Point<T1>(pt.x * scale_x, pt.y * scale_y, pt.z));
+    std::transform(path.begin(), path.end(), back_inserter(result),
+      [scale_x, scale_y](const auto& pt)
+      { return Point<T1>(pt.x * scale_x, pt.y * scale_y, pt.z); });
 #else
-    for (const Point<T2>& pt : path)
-      result.push_back(Point<T1>(pt.x * scale_x, pt.y * scale_y));
+    std::transform(path.begin(), path.end(), back_inserter(result),
+      [scale_x, scale_y](const auto& pt)
+      { return Point<T1>(pt.x * scale_x, pt.y * scale_y); });
 #endif
     return result;
   }
 
   template <typename T1, typename T2>
-  inline Path<T1> ScalePath(const Path<T2>& path, double scale)
+  inline Path<T1> ScalePath(const Path<T2>& path,
+    double scale, int& error_code)
   {
-    return ScalePath<T1, T2>(path, scale, scale);
+    return ScalePath<T1, T2>(path, scale, scale, error_code);
   }
 
   template <typename T1, typename T2>
-  inline Paths<T1> ScalePaths(const Paths<T2>& paths, double scale_x, double scale_y)
+  inline Paths<T1> ScalePaths(const Paths<T2>& paths,
+    double scale_x, double scale_y, int& error_code)
   {
     Paths<T1> result;
+
+    if constexpr (std::numeric_limits<T1>::is_integer)
+    {
+      RectD r = GetBounds<double, T2>(paths);
+      if ((r.left * scale_x) < min_coord ||
+        (r.right * scale_x) > max_coord ||
+        (r.top * scale_y) < min_coord ||
+        (r.bottom * scale_y) > max_coord)
+      {
+        error_code |= range_error_i;
+        DoError(range_error_i);
+        return result; // empty path
+      }
+    }
+
     result.reserve(paths.size());
-    for (const Path<T2>& path : paths)
-      result.push_back(ScalePath<T1, T2>(path, scale_x, scale_y));
+    std::transform(paths.begin(), paths.end(), back_inserter(result),
+      [=, &error_code](const auto& path)
+      { return ScalePath<T1, T2>(path, scale_x, scale_y, error_code); });
     return result;
   }
 
   template <typename T1, typename T2>
-  inline Paths<T1> ScalePaths(const Paths<T2>& paths, double scale)
+  inline Paths<T1> ScalePaths(const Paths<T2>& paths,
+    double scale, int& error_code)
   {
-    return ScalePaths<T1, T2>(paths, scale, scale);
+    return ScalePaths<T1, T2>(paths, scale, scale, error_code);
   }
 
   template <typename T1, typename T2>
@@ -247,26 +559,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)
   {
@@ -319,169 +611,39 @@ 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;
   }
 
-  // Rect ------------------------------------------------------------------------
-
-  template <typename T>
-  struct Rect;
-
-  using Rect64 = Rect<int64_t>;
-  using RectD = Rect<double>;
-
-  template <typename T>
-  struct Rect {
-    T left;
-    T top;
-    T right;
-    T bottom;
-
-    Rect() :
-      left(0),
-      top(0),
-      right(0),
-      bottom(0) {}
-
-    Rect(T l, T t, T r, T b) :
-      left(l),
-      top(t),
-      right(r),
-      bottom(b) {}
-
-
-    T Width() const { return right - left; }
-    T Height() const { return bottom - top; }
-    void Width(T width) { right = left + width; }
-    void Height(T height) { bottom = top + height; }
-
-    Point<T> MidPoint() const
-    {
-      return Point<T>((left + right) / 2, (top + bottom) / 2);
-    }
-
-    Path<T> AsPath() const
-    {
-      Path<T> result;
-      result.reserve(4);
-      result.push_back(Point<T>(left, top));
-      result.push_back(Point<T>(right, top));
-      result.push_back(Point<T>(right, bottom));
-      result.push_back(Point<T>(left, bottom));
-      return result;
-    }
-
-    bool Contains(const Point<T>& pt) const
-    {
-      return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom;
-    }
-
-    bool Contains(const Rect<T>& rec) const
-    {
-      return rec.left >= left && rec.right <= right &&
-        rec.top >= top && rec.bottom <= bottom;
-    }
-
-    void Scale(double scale) {
-      left *= scale;
-      top *= scale;
-      right *= scale;
-      bottom *= scale;
-    }
-
-    bool IsEmpty() const { return bottom <= top || right <= left; };
-
-    bool Intersects(const Rect<T>& rec) const
-    {
-      return (std::max(left, rec.left) < std::min(right, rec.right)) &&
-        (std::max(top, rec.top) < std::min(bottom, rec.bottom));
-    };
-
-    friend std::ostream& operator<<(std::ostream& os, const Rect<T>& rect) {
-      os << "("
-        << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom
-        << ")";
-      return os;
-    }
-  };
+  // Miscellaneous ------------------------------------------------------------
 
-  template <typename T1, typename T2>
-  inline Rect<T1> ScaleRect(const Rect<T2>& rect, double scale)
+  inline void CheckPrecisionRange(int& precision, int& error_code)
   {
-    Rect<T1> result;
-
-    if constexpr (std::numeric_limits<T1>::is_integer &&
-      !std::numeric_limits<T2>::is_integer)
-    {
-      result.left = static_cast<T1>(std::round(rect.left * scale));
-      result.top = static_cast<T1>(std::round(rect.top * scale));
-      result.right = static_cast<T1>(std::round(rect.right * scale));
-      result.bottom = static_cast<T1>(std::round(rect.bottom * scale));
-    }
-    else
-    {
-      result.left = rect.left * scale;
-      result.top = rect.top * scale;
-      result.right = rect.right * scale;
-      result.bottom = rect.bottom * scale;
-    }
-    return result;
+    if (precision >= -CLIPPER2_MAX_DEC_PRECISION &&
+      precision <= CLIPPER2_MAX_DEC_PRECISION) return;
+    error_code |= precision_error_i; // non-fatal error
+    DoError(precision_error_i);      // does nothing when exceptions are disabled
+    precision = precision > 0 ? CLIPPER2_MAX_DEC_PRECISION : -CLIPPER2_MAX_DEC_PRECISION;
   }
 
-  // clipper2Exception ---------------------------------------------------------
-
-#ifdef __cpp_exceptions
-  class Clipper2Exception : public std::exception {
-  public:
-    explicit Clipper2Exception(const char* description) :
-      m_descr(description) {}
-    virtual const char* what() const throw() { return m_descr.c_str(); }
-  private:
-    std::string m_descr;
-  };
-#endif
-
-  // Miscellaneous ------------------------------------------------------------
-
-  inline void CheckPrecision(int& precision)
+  inline void CheckPrecisionRange(int& precision)
   {
-    if (precision >= -8 && precision <= 8) return;
-#ifdef __cpp_exceptions
-    throw Clipper2Exception(precision_error);
-#else
-    precision = precision > 8 ? 8 : -8;
-#endif
+    int error_code = 0;
+    CheckPrecisionRange(precision, error_code);
   }
 
   template <typename T>
@@ -515,15 +677,17 @@ namespace Clipper2Lib
   }
 
   template <typename T>
-  inline double DistanceFromLineSqrd(const Point<T>& pt, const Point<T>& ln1, const Point<T>& ln2)
+  inline double PerpendicDistFromLineSqrd(const Point<T>& pt,
+    const Point<T>& line1, const Point<T>& line2)
   {
     //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
     //see http://en.wikipedia.org/wiki/Perpendicular_distance
-    double A = static_cast<double>(ln1.y - ln2.y);
-    double B = static_cast<double>(ln2.x - ln1.x);
-    double C = A * ln1.x + B * ln1.y;
-    C = A * pt.x + B * pt.y - C;
-    return (C * C) / (A * A + B * B);
+    double a = static_cast<double>(pt.x - line1.x);
+    double b = static_cast<double>(pt.y - line1.y);
+    double c = static_cast<double>(line2.x - line1.x);
+    double d = static_cast<double>(line2.y - line1.y);
+    if (c == 0 && d == 0) return 0;
+    return Sqr(a * d - c * b) / (c * c + d * d);
   }
 
   template <typename T>
@@ -543,7 +707,7 @@ namespace Clipper2Lib
     }
     if (cnt & 1)
       a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
-    return a * 0.5;
+    return (a * 0.5);
   }
 
   template <typename T>
@@ -561,38 +725,112 @@ namespace Clipper2Lib
   template <typename T>
   inline bool IsPositive(const Path<T>& poly)
   {
-    // A curve has positive orientation [and area] if a region 'R' 
+    // A curve has positive orientation [and area] if a region 'R'
     // is on the left when traveling around the outside of 'R'.
     //https://mathworld.wolfram.com/CurveOrientation.html
     //nb: This statement is premised on using Cartesian coordinates
     return Area<T>(poly) >= 0;
   }
 
-  static const double max_coord = static_cast<double>(MAX_COORD);
-  static const double min_coord = static_cast<double>(MIN_COORD);
-
-  inline int64_t CheckCastInt64(double val)
+#if CLIPPER2_HI_PRECISION
+  // caution: this will compromise performance
+  // https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253
+  // See also CPP/BenchMark/GetIntersectPtBenchmark.cpp
+  #define CC_MIN(x,y) ((x)>(y)?(y):(x))
+  #define CC_MAX(x,y) ((x)<(y)?(y):(x))
+  template<typename T>
+  inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
+    const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
+  {
+    double ln1dy = static_cast<double>(ln1b.y - ln1a.y);
+    double ln1dx = static_cast<double>(ln1a.x - ln1b.x);
+    double ln2dy = static_cast<double>(ln2b.y - ln2a.y);
+    double ln2dx = static_cast<double>(ln2a.x - ln2b.x);
+    double det = (ln2dy * ln1dx) - (ln1dy * ln2dx);
+    if (det == 0.0) return false;
+    T bb0minx = CC_MIN(ln1a.x, ln1b.x);
+    T bb0miny = CC_MIN(ln1a.y, ln1b.y);
+    T bb0maxx = CC_MAX(ln1a.x, ln1b.x);
+    T bb0maxy = CC_MAX(ln1a.y, ln1b.y);
+    T bb1minx = CC_MIN(ln2a.x, ln2b.x);
+    T bb1miny = CC_MIN(ln2a.y, ln2b.y);
+    T bb1maxx = CC_MAX(ln2a.x, ln2b.x);
+    T bb1maxy = CC_MAX(ln2a.y, ln2b.y);
+
+    if constexpr (std::numeric_limits<T>::is_integer)
+    {
+      int64_t originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) >> 1;
+      int64_t originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) >> 1;
+      double ln0c = (ln1dy * static_cast<double>(ln1a.x - originx)) +
+        (ln1dx * static_cast<double>(ln1a.y - originy));
+      double ln1c = (ln2dy * static_cast<double>(ln2a.x - originx)) +
+        (ln2dx * static_cast<double>(ln2a.y - originy));
+      double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det;
+      double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det;
+
+      ip.x = originx + (T)nearbyint(hitx);
+      ip.y = originy + (T)nearbyint(hity);
+    }
+    else
+    {
+      double originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) / 2.0;
+      double originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) / 2.0;
+      double ln0c = (ln1dy * static_cast<double>(ln1a.x - originx)) +
+        (ln1dx * static_cast<double>(ln1a.y - originy));
+      double ln1c = (ln2dy * static_cast<double>(ln2a.x - originx)) +
+        (ln2dx * static_cast<double>(ln2a.y - originy));
+      double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det;
+      double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det;
+
+      ip.x = originx + static_cast<T>(hitx);
+      ip.y = originy + static_cast<T>(hity);
+    }
+    return true;
+}
+#else
+  template<typename T>
+  inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
+    const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
   {
-    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;
+    else if (t >= 1.0) ip = ln1b;
+    else
+    {
+      ip.x = static_cast<T>(ln1a.x + t * dx1);
+      ip.y = static_cast<T>(ln1a.y + t * dy1);
+  }
+    return true;
+  }
+#endif
+
+  template<typename T>
+  inline Point<T> TranslatePoint(const Point<T>& pt, double dx, double dy)
+  {
+#ifdef USINGZ
+    return Point<T>(pt.x + dx, pt.y + dy, pt.z);
+#else
+    return Point<T>(pt.x + dx, pt.y + dy);
+#endif
+  }
+
+
+  template<typename T>
+  inline Point<T> ReflectPoint(const Point<T>& pt, const Point<T>& pivot)
+  {
+#ifdef USINGZ
+    return Point<T>(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z);
+#else
+    return Point<T>(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
+#endif
   }
 
   inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
@@ -616,8 +854,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);
@@ -627,9 +866,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 };
@@ -641,56 +885,78 @@ namespace Clipper2Lib
       return PointInPolygonResult::IsOutside;
 
     int val = 0;
-    typename Path<T>::const_iterator start = polygon.cbegin(), cit = start;
-    typename Path<T>::const_iterator cend = polygon.cend(), pit = cend - 1;
+    typename Path<T>::const_iterator cbegin = polygon.cbegin(), first = cbegin, curr, prev;
+    typename Path<T>::const_iterator cend = polygon.cend();
 
-    while (pit->y == pt.y)
-    {
-      if (pit == start) return PointInPolygonResult::IsOutside;
-      --pit;
-    }
-    bool is_above = pit->y < pt.y;
+    while (first != cend && first->y == pt.y) ++first;
+    if (first == cend) // not a proper polygon
+      return PointInPolygonResult::IsOutside;
 
-    while (cit != cend)
+    bool is_above = first->y < pt.y, starting_above = is_above;
+    curr = first +1;
+    while (true)
     {
+      if (curr == cend)
+      {
+        if (cend == first || first == cbegin) break;
+        cend = first;
+        curr = cbegin;
+      }
+
       if (is_above)
       {
-        while (cit != cend && cit->y < pt.y) ++cit;
-        if (cit == cend) break;
+        while (curr != cend && curr->y < pt.y) ++curr;
+        if (curr == cend) continue;
       }
       else
       {
-        while (cit != cend && cit->y > pt.y) ++cit;
-        if (cit == cend) break;
+        while (curr != cend && curr->y > pt.y) ++curr;
+        if (curr == cend) continue;
       }
 
-      if (cit == start) pit = cend - 1;
-      else  pit = cit - 1;
+      if (curr == cbegin)
+        prev = polygon.cend() - 1; //nb: NOT cend (since might equal first)
+      else
+        prev = curr - 1;
 
-      if (cit->y == pt.y)
+      if (curr->y == pt.y)
       {
-        if (cit->x == pt.x || (cit->y == pit->y &&
-          ((pt.x < pit->x) != (pt.x < cit->x))))
-          return PointInPolygonResult::IsOn;
-        ++cit;
+        if (curr->x == pt.x ||
+          (curr->y == prev->y &&
+            ((pt.x < prev->x) != (pt.x < curr->x))))
+              return PointInPolygonResult::IsOn;
+        ++curr;
+        if (curr == first) break;
         continue;
       }
 
-      if (pt.x < cit->x && pt.x < pit->x)
+      if (pt.x < curr->x && pt.x < prev->x)
       {
         // we're only interested in edges crossing on the left
       }
-      else if (pt.x > pit->x && pt.x > cit->x)
+      else if (pt.x > prev->x && pt.x > curr->x)
         val = 1 - val; // toggle val
       else
       {
-        double d = CrossProduct(*pit, *cit, pt);
+        double d = CrossProduct(*prev, *curr, pt);
         if (d == 0) return PointInPolygonResult::IsOn;
         if ((d < 0) == is_above) val = 1 - val;
       }
       is_above = !is_above;
-      ++cit;
+      ++curr;
     }
+
+    if (is_above != starting_above)
+    {
+      cend = polygon.cend();
+      if (curr == cend) curr = cbegin;
+      if (curr == cbegin) prev = cend - 1;
+      else prev = curr - 1;
+      double d = CrossProduct(*prev, *curr, pt);
+      if (d == 0) return PointInPolygonResult::IsOn;
+      if ((d < 0) == is_above) val = 1 - val;
+    }
+
     return (val == 0) ?
       PointInPolygonResult::IsOutside :
       PointInPolygonResult::IsInside;

+ 178 - 129
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.engine.h

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  19 November 2022                                                *
+* Date      :  13 December 2023                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  This is the main polygon clipping module                        *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -10,15 +10,16 @@
 #ifndef CLIPPER_ENGINE_H
 #define CLIPPER_ENGINE_H
 
-constexpr auto CLIPPER2_VERSION = "1.0.6";
-
 #include <cstdlib>
+#include <stdint.h> //#541
+#include <iostream>
 #include <queue>
-#include <stdexcept>
 #include <vector>
 #include <functional>
+#include <numeric>
 #include <memory>
-#include "clipper.core.h"
+
+#include "clipper2/clipper.core.h"
 
 namespace Clipper2Lib {
 
@@ -28,23 +29,24 @@ namespace Clipper2Lib {
 	struct Vertex;
 	struct LocalMinima;
 	struct OutRec;
-	struct Joiner;
+	struct HorzSegment;
 
 	//Note: all clipping operations except for Difference are commutative.
 	enum class ClipType { None, Intersection, Union, Difference, Xor };
-	
+
 	enum class PathType { Subject, Clip };
+	enum class JoinWith { None, Left, Right };
 
 	enum class VertexFlags : uint32_t {
 		None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
 	};
 
-	constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b) 
+	constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
 	{
 		return (enum VertexFlags)(uint32_t(a) & uint32_t(b));
 	}
 
-	constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b) 
+	constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b)
 	{
 		return (enum VertexFlags)(uint32_t(a) | uint32_t(b));
 	}
@@ -61,7 +63,7 @@ namespace Clipper2Lib {
 		OutPt*	next = nullptr;
 		OutPt*	prev = nullptr;
 		OutRec* outrec;
-		Joiner* joiner = nullptr;
+		HorzSegment* horz = nullptr;
 
 		OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
 			next = this;
@@ -83,22 +85,28 @@ namespace Clipper2Lib {
 	struct OutRec {
 		size_t idx = 0;
 		OutRec* owner = nullptr;
-		OutRecList* splits = nullptr;
 		Active* front_edge = nullptr;
 		Active* back_edge = nullptr;
 		OutPt* pts = nullptr;
 		PolyPath* polypath = nullptr;
+		OutRecList* splits = nullptr;
+		OutRec* recursive_split = nullptr;
 		Rect64 bounds = {};
 		Path64 path;
 		bool is_open = false;
-		~OutRec() { if (splits) delete splits; };
+
+		~OutRec() {
+			if (splits) delete splits;
+			// nb: don't delete the split pointers
+			// as these are owned by ClipperBase's outrec_list_
+		};
 	};
 
 	///////////////////////////////////////////////////////////////////
 	//Important: UP and DOWN here are premised on Y-axis positive down
 	//displays, which is the orientation used in Clipper's development.
 	///////////////////////////////////////////////////////////////////
-	
+
 	struct Active {
 		Point64 bot;
 		Point64 top;
@@ -123,6 +131,7 @@ namespace Clipper2Lib {
 		Vertex* vertex_top = nullptr;
 		LocalMinima* local_min = nullptr;  // the bottom of an edge 'bound' (also Vatti)
 		bool is_left_bound = false;
+		JoinWith join_with = JoinWith::None;
 	};
 
 	struct LocalMinima {
@@ -137,11 +146,24 @@ namespace Clipper2Lib {
 		Point64 pt;
 		Active* edge1;
 		Active* edge2;
-		IntersectNode() : pt(Point64(0, 0)), edge1(NULL), edge2(NULL) {}
+		IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {}
 			IntersectNode(Active* e1, Active* e2, Point64& pt_) :
-			pt(pt_), edge1(e1), edge2(e2)
-		{
-		}
+			pt(pt_), edge1(e1), edge2(e2) {}
+	};
+
+	struct HorzSegment {
+		OutPt* left_op;
+		OutPt* right_op = nullptr;
+		bool left_to_right = true;
+		HorzSegment() : left_op(nullptr) { }
+		explicit HorzSegment(OutPt* op) : left_op(op) { }
+	};
+
+	struct HorzJoin {
+		OutPt* op1 = nullptr;
+		OutPt* op2 = nullptr;
+		HorzJoin() {};
+		explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
 	};
 
 #ifdef USINGZ
@@ -152,6 +174,25 @@ namespace Clipper2Lib {
 		const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
 #endif
 
+	typedef std::vector<HorzSegment> HorzSegmentList;
+	typedef std::unique_ptr<LocalMinima> LocalMinima_ptr;
+	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 {
@@ -164,21 +205,21 @@ namespace Clipper2Lib {
 		bool using_polytree_ = false;
 		Active* actives_ = nullptr;
 		Active *sel_ = nullptr;
-		Joiner *horz_joiners_ = nullptr;
-		std::vector<LocalMinima*> minima_list_;		//pointers in case of memory reallocs
-		std::vector<LocalMinima*>::iterator current_locmin_iter_;
+		LocalMinimaList minima_list_;		//pointers in case of memory reallocs
+		LocalMinimaList::iterator current_locmin_iter_;
 		std::vector<Vertex*> vertex_lists_;
 		std::priority_queue<int64_t> scanline_list_;
-		std::vector<IntersectNode> intersect_nodes_; 
-		std::vector<Joiner*> joiner_list_;				//pointers in case of memory reallocs
+		IntersectNodeList intersect_nodes_;
+    HorzSegmentList horz_seg_list_;
+		std::vector<HorzJoin> horz_join_list_;
 		void Reset();
-		void InsertScanline(int64_t y);
-		bool PopScanline(int64_t &y);
-		bool PopLocalMinima(int64_t y, LocalMinima *&local_minima);
+		inline void InsertScanline(int64_t y);
+		inline bool PopScanline(int64_t &y);
+		inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima);
 		void DisposeAllOutRecs();
 		void DisposeVerticesAndLocalMinima();
 		void DeleteEdges(Active*& e);
-		void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
+		inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
 		bool IsContributingClosed(const Active &e) const;
 		inline bool IsContributingOpen(const Active &e) const;
 		void SetWindCountForClosedPathEdge(Active &edge);
@@ -197,38 +238,41 @@ namespace Clipper2Lib {
 		bool BuildIntersectList(const int64_t top_y);
 		void ProcessIntersectList();
 		void SwapPositionsInAEL(Active& edge1, Active& edge2);
+		OutRec* NewOutRec();
 		OutPt* AddOutPt(const Active &e, const Point64& pt);
-		OutPt* AddLocalMinPoly(Active &e1, Active &e2, 
+		OutPt* AddLocalMinPoly(Active &e1, Active &e2,
 			const Point64& pt, bool is_new = false);
 		OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
 		void DoHorizontal(Active &horz);
-		bool ResetHorzDirection(const Active &horz, const Active *max_pair,
+		bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex,
 			int64_t &horz_left, int64_t &horz_right);
 		void DoTopOfScanbeam(const int64_t top_y);
 		Active *DoMaxima(Active &e);
 		void JoinOutrecPaths(Active &e1, Active &e2);
-		void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec);
-		bool ValidateClosedPathEx(OutPt*& outrec);
-		void CleanCollinear(OutRec* outrec);
 		void FixSelfIntersects(OutRec* outrec);
 		void DoSplitOp(OutRec* outRec, OutPt* splitOp);
-		Joiner* GetHorzTrialParent(const OutPt* op);
-		bool OutPtInTrialHorzList(OutPt* op);
-		void SafeDisposeOutPts(OutPt*& op);
-		void SafeDeleteOutPtJoiners(OutPt* op);
-		void AddTrialHorzJoin(OutPt* op);
-		void DeleteTrialHorzJoin(OutPt* op);
-		void ConvertHorzTrialsToJoins();
-		void AddJoin(OutPt* op1, OutPt* op2);
-		void DeleteJoin(Joiner* joiner);
-		void ProcessJoinerList();
-		OutRec* ProcessJoin(Joiner* joiner);
+
+		inline void AddTrialHorzJoin(OutPt* op);
+		void ConvertHorzSegsToJoins();
+		void ProcessHorzJoins();
+
+		void Split(Active& e, const Point64& pt);
+		inline void CheckJoinLeft(Active& e,
+			const Point64& pt, bool check_curr_x = false);
+		inline void CheckJoinRight(Active& e,
+			const Point64& pt, bool check_curr_x = false);
 	protected:
+		bool preserve_collinear_ = true;
+		bool reverse_solution_ = false;
+		int error_code_ = 0;
 		bool has_open_paths_ = false;
 		bool succeeded_ = true;
-		std::vector<OutRec*> outrec_list_; //pointers in case list memory reallocated
+		OutRecList outrec_list_; //pointers in case list memory reallocated
 		bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
-		bool DeepCheckOwner(OutRec* outrec, OutRec* owner);
+		void CleanCollinear(OutRec* outrec);
+		bool CheckBounds(OutRec* outrec);
+		bool CheckSplitOwner(OutRec* outrec, OutRecList* splits);
+		void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
 #ifdef USINGZ
 		ZCallback64 zCallback_ = nullptr;
 		void SetZ(const Active& e1, const Active& e2, Point64& pt);
@@ -238,9 +282,13 @@ namespace Clipper2Lib {
 		void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
 	public:
 		virtual ~ClipperBase();
-		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
@@ -258,7 +306,7 @@ namespace Clipper2Lib {
 		PolyPath* parent_;
 	public:
 		PolyPath(PolyPath* parent = nullptr): parent_(parent){}
-		virtual ~PolyPath() { Clear(); };		
+		virtual ~PolyPath() {};
 		//https://en.cppreference.com/w/cpp/language/rule_of_three
 		PolyPath(const PolyPath&) = delete;
 		PolyPath& operator=(const PolyPath&) = delete;
@@ -273,33 +321,45 @@ namespace Clipper2Lib {
 
 		virtual PolyPath* AddChild(const Path64& path) = 0;
 
-		virtual void Clear() {};
+		virtual void Clear() = 0;
 		virtual size_t Count() const { return 0; }
 
 		const PolyPath* Parent() const { return parent_; }
 
-		bool IsHole() const 
+		bool IsHole() const
 		{
-			const PolyPath* pp = parent_;
-			bool is_hole = pp;
-			while (pp) {
-				is_hole = !is_hole;
-				pp = pp->parent_;
-			}
-			return is_hole;
+			unsigned lvl = Level();
+			//Even levels except level 0
+			return lvl && !(lvl & 1);
 		}
 	};
 
+	typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
+	typedef typename std::vector<std::unique_ptr<PolyPathD>>  PolyPathDList;
+
 	class PolyPath64 : public PolyPath {
 	private:
-		std::vector<std::unique_ptr<PolyPath64>> childs_;
+		PolyPath64List childs_;
 		Path64 polygon_;
-		typedef typename std::vector<std::unique_ptr<PolyPath64>>::const_iterator pp64_itor;
 	public:
-		PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
-		PolyPath64* operator [] (size_t index) { return childs_[index].get(); }
-		pp64_itor begin() const { return childs_.cbegin(); }
-		pp64_itor end() const { return childs_.cend(); }
+		explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
+
+		~PolyPath64() {
+			childs_.resize(0);
+		}
+
+		PolyPath64* operator [] (size_t index) const
+		{
+			return childs_[index].get(); //std::unique_ptr
+		}
+
+		PolyPath64* Child(size_t index) const
+		{
+			return childs_[index].get();
+		}
+
+		PolyPath64List::const_iterator begin() const { return childs_.cbegin(); }
+		PolyPath64List::const_iterator end() const { return childs_.cend(); }
 
 		PolyPath64* AddChild(const Path64& path) override
 		{
@@ -314,7 +374,7 @@ namespace Clipper2Lib {
 			childs_.resize(0);
 		}
 
-		size_t Count() const  override
+		size_t Count() const override
 		{
 			return childs_.size();
 		}
@@ -323,68 +383,58 @@ namespace Clipper2Lib {
 
 		double Area() const
 		{
-			double result = Clipper2Lib::Area<int64_t>(polygon_);
-			for (const auto& child : childs_)
-				result += child->Area();
-			return result;
-		}
-
-		friend std::ostream& operator << (std::ostream& outstream, const PolyPath64& polypath)
-		{
-			const size_t level_indent = 4;
-			const size_t coords_per_line = 4;
-			const size_t last_on_line = coords_per_line - 1;
-			unsigned level = polypath.Level();
-			if (level > 0)
-			{
-				std::string level_padding;
-				level_padding.insert(0, (level - 1) * level_indent, ' ');
-				std::string caption = polypath.IsHole() ? "Hole " : "Outer Polygon ";
-				std::string childs = polypath.Count() == 1 ? " child" : " children";
-				outstream << level_padding.c_str() << caption << "with " << polypath.Count() << childs << std::endl;
-				outstream << level_padding;
-				size_t i = 0, highI = polypath.Polygon().size() - 1;
-				for (; i < highI; ++i)
-				{
-					outstream << polypath.Polygon()[i] << ' ';
-					if ((i % coords_per_line) == last_on_line)
-						outstream << std::endl << level_padding;
-				}
-				if (highI > 0) outstream << polypath.Polygon()[i];
-				outstream << std::endl;
-			}
-			for (auto& child : polypath)
-				outstream << *child;
-			return outstream;
+			return std::accumulate(childs_.cbegin(), childs_.cend(),
+				Clipper2Lib::Area<int64_t>(polygon_),
+				[](double a, const auto& child) {return a + child->Area(); });
 		}
 
 	};
 
 	class PolyPathD : public PolyPath {
 	private:
-		std::vector<std::unique_ptr<PolyPathD>> childs_;
-		double inv_scale_;
+		PolyPathDList childs_;
+		double scale_;
 		PathD polygon_;
-		typedef typename std::vector<std::unique_ptr<PolyPathD>>::const_iterator ppD_itor;
 	public:
-		PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) 
+		explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
+		{
+			scale_ = parent ? parent->scale_ : 1.0;
+		}
+
+		~PolyPathD() {
+			childs_.resize(0);
+		}
+
+		PolyPathD* operator [] (size_t index) const
 		{
-			inv_scale_ = parent ? parent->inv_scale_ : 1.0;
+			return childs_[index].get();
 		}
-		PolyPathD* operator [] (size_t index) 
-		{ 
+
+		PolyPathD* Child(size_t index) const
+		{
 			return childs_[index].get();
 		}
-		ppD_itor begin() const { return childs_.cbegin(); }
-		ppD_itor end() const { return childs_.cend(); }
 
-		void SetInvScale(double value) { inv_scale_ = value; }
-		double InvScale() { return inv_scale_; }
+		PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
+		PolyPathDList::const_iterator end() const { return childs_.cend(); }
+
+		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_);
+			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;
 		}
 
@@ -393,7 +443,7 @@ namespace Clipper2Lib {
 			childs_.resize(0);
 		}
 
-		size_t Count() const  override
+		size_t Count() const override
 		{
 			return childs_.size();
 		}
@@ -402,10 +452,9 @@ namespace Clipper2Lib {
 
 		double Area() const
 		{
-			double result = Clipper2Lib::Area<double>(polygon_);
-			for (const auto& child : childs_)
-				result += child->Area();
-			return result;
+			return std::accumulate(childs_.begin(), childs_.end(),
+				Clipper2Lib::Area<double>(polygon_),
+				[](double a, const auto& child) {return a + child->Area(); });
 		}
 	};
 
@@ -439,13 +488,13 @@ namespace Clipper2Lib {
 			return Execute(clip_type, fill_rule, closed_paths, dummy);
 		}
 
-		bool Execute(ClipType clip_type, FillRule fill_rule, 
+		bool Execute(ClipType clip_type, FillRule fill_rule,
 			Paths64& closed_paths, Paths64& open_paths)
 		{
 			closed_paths.clear();
 			open_paths.clear();
 			if (ExecuteInternal(clip_type, fill_rule, false))
-				BuildPaths64(closed_paths, &open_paths);
+					BuildPaths64(closed_paths, &open_paths);
 			CleanUp();
 			return succeeded_;
 		}
@@ -474,14 +523,14 @@ namespace Clipper2Lib {
 	private:
 		double scale_ = 1.0, invScale_ = 1.0;
 #ifdef USINGZ
-		ZCallbackD zCallback_ = nullptr;
+		ZCallbackD zCallbackD_ = nullptr;
 #endif
 		void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
 		void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
 	public:
 		explicit ClipperD(int precision = 2) : ClipperBase()
 		{
-			CheckPrecision(precision);
+			CheckPrecisionRange(precision, error_code_);
 			// to optimize scaling / descaling precision
 			// set the scale to a power of double's radix (2) (#25)
 			scale_ = std::pow(std::numeric_limits<double>::radix,
@@ -490,7 +539,7 @@ namespace Clipper2Lib {
 		}
 
 #ifdef USINGZ
-		void SetZCallback(ZCallbackD cb) { zCallback_ = cb; };
+		void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
 
 		void ZCB(const Point64& e1bot, const Point64& e1top,
 			const Point64& e2bot, const Point64& e2top, Point64& pt)
@@ -504,19 +553,19 @@ namespace Clipper2Lib {
 			PointD e1t = PointD(e1top) * invScale_;
 			PointD e2b = PointD(e2bot) * invScale_;
 			PointD e2t = PointD(e2top) * invScale_;
-			zCallback_(e1b,e1t, e2b, e2t, tmp);
+			zCallbackD_(e1b,e1t, e2b, e2t, tmp);
 			pt.z = tmp.z; // only update 'z'
 		};
 
 		void CheckCallback()
 		{
-			if(zCallback_)
-				// if the user defined float point callback has been assigned 
+			if(zCallbackD_)
+				// if the user defined float point callback has been assigned
 				// then assign the proxy callback function
-				ClipperBase::zCallback_ = 
+				ClipperBase::zCallback_ =
 					std::bind(&ClipperD::ZCB, this, std::placeholders::_1,
 					std::placeholders::_2, std::placeholders::_3,
-					std::placeholders::_4, std::placeholders::_5); 
+					std::placeholders::_4, std::placeholders::_5);
 			else
 				ClipperBase::zCallback_ = nullptr;
 		}
@@ -525,17 +574,17 @@ namespace Clipper2Lib {
 
 		void AddSubject(const PathsD& subjects)
 		{
-			AddPaths(ScalePaths<int64_t, double>(subjects, scale_), PathType::Subject, false);
+			AddPaths(ScalePaths<int64_t, double>(subjects, scale_, error_code_), PathType::Subject, false);
 		}
 
 		void AddOpenSubject(const PathsD& open_subjects)
 		{
-			AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_), PathType::Subject, true);
+			AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_, error_code_), PathType::Subject, true);
 		}
 
 		void AddClip(const PathsD& clips)
 		{
-			AddPaths(ScalePaths<int64_t, double>(clips, scale_), PathType::Clip, false);
+			AddPaths(ScalePaths<int64_t, double>(clips, scale_, error_code_), PathType::Clip, false);
 		}
 
 		bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
@@ -573,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);
 			}
@@ -583,6 +632,6 @@ namespace Clipper2Lib {
 
 	};
 
-}  // namespace 
+}  // namespace
 
 #endif  // CLIPPER_ENGINE_H

+ 334 - 598
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.export.h

@@ -1,39 +1,79 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  11 December 2022                                                *
+* Date      :  14 February 2024                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2024                                         *
 * 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 are converted into very simple array data structures 
+(of either int64_t for CPath64 or double for CPathD) that can be parsed by 
+just about any programming language.
+
+CPath64 and CPathD:
+These are arrays of consecutive x and y path coordinates preceeded by
+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 structure (A) and the 
+number of CPath64 or CPathD it contains (C). The space these structures will
+occupy in memory = A * sizeof(int64_t) or  A * sizeof(double) respectively. 
+_______________________________
+|counter|path1|path2|...|pathC|
+|A  , C |                     |
+_______________________________
+
+CPolytree64 and CPolytreeD:
+These are also arrays consisting of CPolyPath structures that represent
+individual paths in a tree structure. However, the very first (ie top)
+CPolyPath is just the tree container that doesn't have a path. And because
+of that, its structure will be very slightly different from the remaining
+CPolyPath. This difference will be discussed below.
+
+CPolyPath64 and CPolyPathD:
+These are simple arrays consisting of a series of path coordinates followed
+by any number of child (ie nested) CPolyPath. Preceeding these are two values
+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.
+
+Again, all theses exported structures (CPaths64, CPathsD, CPolyTree64 & 
+CPolyTreeD) are arrays of either type int64_t or double, and the first 
+value in these arrays will always be the length of that array.
+
+These array structures are allocated in heap memory which will eventually
+need to be released. However, since applications dynamically linking to 
+these functions may use different memory managers, the only safe way to 
+free up this memory is to use the exported DisposeArray64 and 
+DisposeArrayD functions (see below).
+*/
+
 
 #ifndef CLIPPER2_EXPORT_H
 #define CLIPPER2_EXPORT_H
@@ -49,25 +89,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,60 +126,56 @@ 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, 
+  double delta, uint8_t jointype, uint8_t endtype,
+  double miter_limit = 2.0, double arc_tolerance = 0.0,
   bool reverse_solution = false);
 EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
   double delta, uint8_t jointype, uint8_t endtype,
@@ -171,67 +196,174 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
 // INTERNAL FUNCTIONS
 //////////////////////////////////////////////////////
 
-inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2);
-inline CPath64 CreateCPath64(const Path64& p);
-inline CPaths64 CreateCPaths64(const Paths64& pp);
-inline Path64 ConvertCPath64(const CPath64& p);
-inline Paths64 ConvertCPaths64(const CPaths64& pp);
+template <typename T>
+static void GetPathCountAndCPathsArrayLen(const Paths<T>& paths,
+  size_t& cnt, size_t& array_len)
+{
+  array_len = 2;
+  cnt = 0;
+  for (const Path<T>& path : paths)
+    if (path.size())
+    {
+      array_len += path.size() * 2 + 2;
+      ++cnt;
+    }
+}
 
-inline CPathD CreateCPathD(size_t cnt1, size_t cnt2);
-inline CPathD CreateCPathD(const PathD& p);
-inline CPathsD CreateCPathsD(const PathsD& pp);
-inline PathD ConvertCPathD(const CPathD& p);
-inline PathsD ConvertCPathsD(const CPathsD& pp);
+static size_t GetPolyPath64ArrayLen(const PolyPath64& pp)
+{
+  size_t result = 2; // poly_length + child_count
+  result += pp.Polygon().size() * 2;
+  //plus nested children :)
+  for (size_t i = 0; i < pp.Count(); ++i)
+    result += GetPolyPath64ArrayLen(*pp[i]);
+  return result;
+}
+
+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);
+}
+
+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;
+}
 
-// 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);
 
-inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt);
-inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale);
+CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale)
+{
+  if (!paths.size()) return nullptr;
+  size_t cnt, array_len;
+  GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
+  CPathsD result = new double[array_len], v = result;
+  *v++ = (double)array_len;
+  *v++ = (double)cnt;
+  for (const Path64& path : paths)
+  {
+    if (!path.size()) continue;
+    *v = (double)path.size();
+    ++v; *v++ = 0;
+    for (const Point64& pt : path)
+    {
+      *v++ = pt.x * scale;
+      *v++ = pt.y * scale;
+    }
+  }
+  return result;
+}
 
-EXTERN_DLL_EXPORT const char* Version()
+template <typename T>
+static Paths<T> ConvertCPaths(T* paths)
 {
-  return CLIPPER2_VERSION;
+  Paths<T> result;
+  if (!paths) return result;
+  T* v = paths; ++v;
+  size_t cnt = static_cast<size_t>(*v++);
+  result.reserve(cnt);
+  for (size_t i = 0; i < cnt; ++i)
+  {
+    size_t cnt2 = static_cast<size_t>(*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;
 }
 
-EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p)
+
+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)
+  size_t cnt = static_cast<size_t>(*v++);
+  result.reserve(cnt);
+  for (size_t i = 0; i < cnt; ++i)
+  {
+    size_t cnt2 = static_cast<size_t>(*v);
+    v += 2;
+    Path64 path;
+    path.reserve(cnt2);
+    for (size_t 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, 
+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,
@@ -239,50 +371,50 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
 {
   if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
   if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
-  
+
   Paths64 sub, sub_open, clp, sol, sol_open;
-  sub       = 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)) 
+  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 +430,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,13 +485,13 @@ EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
   double arc_tolerance, bool reverse_solution)
 {
   Paths64 pp;
-  pp = ConvertCPaths64(paths);
-
-  ClipperOffset clip_offset( miter_limit, 
+  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);
-  return CreateCPaths64(result);
+  Paths64 result;
+  clip_offset.Execute(delta, result);
+  return CreateCPaths(result);
 }
 
 EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
@@ -371,460 +500,67 @@ 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);
-  return CreateCPathsD(result, 1/scale);
+  Paths64 result;
+  clip_offset.Execute(delta * scale, result);
+
+  return CreateCPathsDFromPaths64(result, 1 / scale);
 }
 
-EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
-  const CPaths64 paths)
+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;
-  result.reserve(pp.size());
-  for (const Path64& p : pp)
-  {
-    Rect64 pathRec = Bounds(p);
-    if (!r64.Intersects(pathRec)) continue;
-    
-    if (r64.Contains(pathRec))
-      result.push_back(p);
-    else
-    {
-      Path64 p2 = rc.Execute(p);
-      if (!p2.empty()) result.push_back(std::move(p2));
-    }
-  }
-  return CreateCPaths64(result);
+  class RectClip64 rc(r64);
+  Paths64 pp = ConvertCPaths(paths);
+  Paths64 result = rc.Execute(pp);
+  return CreateCPaths(result);
 }
 
-EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
-  const CPathsD paths, int precision)
+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;
   const double scale = std::pow(10, precision);
-  Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
-  Paths64 pp = ConvertCPathsD(paths, scale);
-  class RectClip rc(r);
-  Paths64 result;
-  result.reserve(pp.size());
-  for (const Path64& p : pp)
-  {
-    Rect64 pathRec = Bounds(p);
-    if (!r.Intersects(pathRec)) continue;
-    
-    if (r.Contains(pathRec))
-      result.push_back(p);
-    else
-    {
-      Path64 p2 = rc.Execute(p);
-      if (!p2.empty()) result.push_back(std::move(p2));
-    }
-  }
-  return CreateCPathsD(result, 1/scale);
+
+  RectD r = CRectToRect(rect);
+  Rect64 rec = ScaleRect<int64_t, double>(r, scale);
+  Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
+  class RectClip64 rc(rec);
+  Paths64 result = rc.Execute(pp);
+
+  return CreateCPathsDFromPaths64(result, 1 / scale);
 }
 
-EXTERN_DLL_EXPORT CPaths64 RectClipLines64(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);
-  Paths64 result;
-  result.reserve(pp.size());
-
-  for (const Path64& p : pp)
-  {
-    Rect64 pathRec = Bounds(p);
-    if (!r.Intersects(pathRec)) continue;
-    
-    if (r.Contains(pathRec))
-      result.push_back(p);
-    else
-    {
-      Paths64 pp2 = rcl.Execute(p);
-      if (!pp2.empty())
-        result.insert(result.end(), pp2.begin(), pp2.end());
-    }
-  }
-  return CreateCPaths64(result);
+  class RectClipLines64 rcl (r);
+  Paths64 pp = ConvertCPaths(paths);
+  Paths64 result = rcl.Execute(pp);
+  return CreateCPaths(result);
 }
 
-EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, 
+EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
   const CPathsD paths, int precision)
 {
-  Paths64 result;
   if (CRectIsEmpty(rect) || !paths) return nullptr;
   if (precision < -8 || precision > 8) return nullptr;
+
   const double scale = std::pow(10, precision);
   Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
-  class RectClipLines rcl(r);
-  Paths64 pp = ConvertCPathsD(paths, scale);
-
-  result.reserve(pp.size());
-  for (const Path64& p : pp)
-  {
-    Rect64 pathRec = Bounds(p);
-    if (!r.Intersects(pathRec)) continue;
-    
-    if (r.Contains(pathRec))
-      result.push_back(p);
-    else
-    {
-      Paths64 pp2 = rcl.Execute(p);
-      if (pp2.empty()) continue;
-      result.insert(result.end(), pp2.begin(), pp2.end());
-    }
-  }
-  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;
+  class RectClipLines64 rcl(r);
+  Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
+  Paths64 result = rcl.Execute(pp);
+  return CreateCPathsDFromPaths64(result, 1 / scale);
 }
 
 }  // end Clipper2Lib namespace
-  
+
 #endif  // CLIPPER2_EXPORT_H

+ 363 - 355
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.h

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  29 October 2022                                                 *
+* Date      :  13 December 2023                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  This module provides a simple interface to the Clipper Library  *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -11,31 +11,20 @@
 #define CLIPPER_H
 
 #include <cstdlib>
+#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 {
 
-  static const Rect64 MaxInvalidRect64 = 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 MaxInvalidRectD = RectD(
-      (std::numeric_limits<double>::max)(),
-      (std::numeric_limits<double>::max)(),
-      (std::numeric_limits<double>::lowest)(),
-      (std::numeric_limits<double>::lowest)());
-
   inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
     const Paths64& subjects, const Paths64& clips)
-  {    
+  {
     Paths64 result;
     Clipper64 clipper;
     clipper.AddSubject(subjects);
@@ -55,11 +44,13 @@ namespace Clipper2Lib {
   }
 
   inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule,
-    const PathsD& subjects, const PathsD& clips, int decimal_prec = 2)
+    const PathsD& subjects, const PathsD& clips, int precision = 2)
   {
-    CheckPrecision(decimal_prec);
+    int error_code = 0;
+    CheckPrecisionRange(precision, error_code);
     PathsD result;
-    ClipperD clipper(decimal_prec);
+    if (error_code) return result;
+    ClipperD clipper(precision);
     clipper.AddSubject(subjects);
     clipper.AddClip(clips);
     clipper.Execute(cliptype, fillrule, result);
@@ -67,12 +58,14 @@ namespace Clipper2Lib {
   }
 
   inline void BooleanOp(ClipType cliptype, FillRule fillrule,
-    const PathsD& subjects, const PathsD& clips, 
-    PolyTreeD& polytree, int decimal_prec = 2)
-  {
-    CheckPrecision(decimal_prec);
-    PathsD result;
-    ClipperD clipper(decimal_prec);
+    const PathsD& subjects, const PathsD& clips,
+    PolyTreeD& polytree, int precision = 2)
+  {
+    polytree.Clear();
+    int error_code = 0;
+    CheckPrecisionRange(precision, error_code);
+    if (error_code) return;
+    ClipperD clipper(precision);
     clipper.AddSubject(subjects);
     clipper.AddClip(clips);
     clipper.Execute(cliptype, fillrule, polytree);
@@ -82,7 +75,7 @@ namespace Clipper2Lib {
   {
     return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
   }
-  
+
   inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
   {
     return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
@@ -107,11 +100,13 @@ namespace Clipper2Lib {
     return result;
   }
 
-  inline PathsD Union(const PathsD& subjects, FillRule fillrule, int decimal_prec = 2)
+  inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2)
   {
-    CheckPrecision(decimal_prec);
     PathsD result;
-    ClipperD clipper(decimal_prec);
+    int error_code = 0;
+    CheckPrecisionRange(precision, error_code);
+    if (error_code) return result;
+    ClipperD clipper(precision);
     clipper.AddSubject(subjects);
     clipper.Execute(ClipType::Union, fillrule, result);
     return result;
@@ -138,265 +133,138 @@ namespace Clipper2Lib {
   }
 
   inline Paths64 InflatePaths(const Paths64& paths, double delta,
-    JoinType jt, EndType et, double miter_limit = 2.0)
+    JoinType jt, EndType et, double miter_limit = 2.0,
+    double arc_tolerance = 0.0)
   {
-    ClipperOffset clip_offset(miter_limit);
+    if (!delta) return paths;
+    ClipperOffset clip_offset(miter_limit, arc_tolerance);
     clip_offset.AddPaths(paths, jt, et);
-    return clip_offset.Execute(delta);
+    Paths64 solution;
+    clip_offset.Execute(delta, solution);
+    return solution;
   }
 
   inline PathsD InflatePaths(const PathsD& paths, double delta,
-    JoinType jt, EndType et, double miter_limit = 2.0, int precision = 2)
+    JoinType jt, EndType et, double miter_limit = 2.0,
+    int precision = 2, double arc_tolerance = 0.0)
   {
-    CheckPrecision(precision);
+    int error_code = 0;
+    CheckPrecisionRange(precision, error_code);
+    if (!delta) return paths;
+    if (error_code) return PathsD();
     const double scale = std::pow(10, precision);
-    ClipperOffset clip_offset(miter_limit);
-    clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale), jt, et);
-    Paths64 tmp = clip_offset.Execute(delta * scale);
-    return ScalePaths<double, int64_t>(tmp, 1 / scale);
+    ClipperOffset clip_offset(miter_limit, arc_tolerance);
+    clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
+    if (error_code) return PathsD();
+    Paths64 solution;
+    clip_offset.Execute(delta * scale, solution);
+    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());
-    for (const Point64& pt : path)
-      result.push_back(Point64(pt.x + dx, pt.y + dy));
+    std::transform(path.begin(), path.end(), back_inserter(result),
+      [dx, dy](const auto& pt) { return Point<T>(pt.x + dx, pt.y +dy); });
     return result;
   }
 
-  inline PathD TranslatePath(const PathD& path, double dx, double dy)
+  inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
   {
-    PathD result;
-    result.reserve(path.size());
-    for (const PointD& pt : path)
-      result.push_back(PointD(pt.x + dx, pt.y + dy));
-    return result;
+    return TranslatePath<int64_t>(path, dx, dy);
   }
 
-  inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
+  inline PathD TranslatePath(const PathD& path, double dx, double dy)
   {
-    Paths64 result;
-    result.reserve(paths.size());
-    for (const Path64& path : paths)
-      result.push_back(TranslatePath(path, dx, dy));
-    return result;
+    return TranslatePath<double>(path, dx, dy);
   }
 
-  inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
+  template <typename T>
+  inline Paths<T> TranslatePaths(const Paths<T>& paths, T dx, T dy)
   {
-    PathsD result;
+    Paths<T> result;
     result.reserve(paths.size());
-    for (const PathD& path : paths)
-      result.push_back(TranslatePath(path, dx, dy));
+    std::transform(paths.begin(), paths.end(), back_inserter(result),
+      [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
     return result;
   }
 
-  inline Rect64 Bounds(const Path64& path)
-  {
-    Rect64 rec = MaxInvalidRect64;
-    for (const Point64& pt : path)
-    {
-      if (pt.x < rec.left) rec.left = pt.x;
-      if (pt.x > rec.right) rec.right = pt.x;
-      if (pt.y < rec.top) rec.top = pt.y;
-      if (pt.y > rec.bottom) rec.bottom = pt.y;
-    }
-    if (rec.IsEmpty()) return Rect64();
-    return rec;
-  }
-  
-  inline Rect64 Bounds(const Paths64& paths)
-  {
-    Rect64 rec = MaxInvalidRect64;
-    for (const Path64& path : paths)
-      for (const Point64& pt : path)
-      {
-        if (pt.x < rec.left) rec.left = pt.x;
-        if (pt.x > rec.right) rec.right = pt.x;
-        if (pt.y < rec.top) rec.top = pt.y;
-        if (pt.y > rec.bottom) rec.bottom = pt.y;
-      }
-    if (rec.IsEmpty()) return Rect64();
-    return rec;
-  }
-
-  inline RectD Bounds(const PathD& path)
+  inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
   {
-    RectD rec = MaxInvalidRectD;
-    for (const PointD& pt : path)
-    {
-      if (pt.x < rec.left) rec.left = pt.x;
-      if (pt.x > rec.right) rec.right = pt.x;
-      if (pt.y < rec.top) rec.top = pt.y;
-      if (pt.y > rec.bottom) rec.bottom = pt.y;
-    }
-    if (rec.IsEmpty()) return RectD();
-    return rec;
+    return TranslatePaths<int64_t>(paths, dx, dy);
   }
 
-  inline RectD Bounds(const PathsD& paths)
+  inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
   {
-    RectD rec = MaxInvalidRectD;
-    for (const PathD& path : paths)
-      for (const PointD& pt : path)
-      {
-        if (pt.x < rec.left) rec.left = pt.x;
-        if (pt.x > rec.right) rec.right = pt.x;
-        if (pt.y < rec.top) rec.top = pt.y;
-        if (pt.y > rec.bottom) rec.bottom = pt.y;
-      }
-    if (rec.IsEmpty()) return RectD();
-    return rec;
+    return TranslatePaths<double>(paths, dx, dy);
   }
 
-  inline Path64 RectClip(const Rect64& rect, const Path64& path)
-  {
-    if (rect.IsEmpty() || path.empty()) return Path64();
-    Rect64 pathRec = Bounds(path);
-    if (!rect.Intersects(pathRec)) return Path64();
-    if (rect.Contains(pathRec)) return path;
-    class RectClip rc(rect);
-    return rc.Execute(path);
-  }
-  
   inline Paths64 RectClip(const Rect64& rect, const Paths64& paths)
   {
     if (rect.IsEmpty() || paths.empty()) return Paths64();
-    class RectClip rc(rect);
-    Paths64 result;
-    result.reserve(paths.size());
-
-    for (const Path64& p : paths)
-    {
-      Rect64 pathRec = Bounds(p);
-      if (!rect.Intersects(pathRec)) 
-        continue;
-      else if (rect.Contains(pathRec))
-        result.push_back(p);
-      else
-      {
-        Path64 p2 = rc.Execute(p);
-        if (!p2.empty()) result.push_back(std::move(p2));
-      }
-    }
-    return result;
+    RectClip64 rc(rect);
+    return rc.Execute(paths);
   }
 
-  inline PathD RectClip(const RectD& rect, const PathD& path, int precision = 2)
+  inline Paths64 RectClip(const Rect64& rect, const Path64& path)
   {
-    if (rect.IsEmpty() || path.empty() ||
-      !rect.Contains(Bounds(path))) return PathD();
-    CheckPrecision(precision);
-    const double scale = std::pow(10, precision);
-    Rect64 r = ScaleRect<int64_t, double>(rect, scale);
-    class RectClip rc(r);
-    Path64 p = ScalePath<int64_t, double>(path, scale);
-    return ScalePath<double, int64_t>(rc.Execute(p), 1 / scale);
+    if (rect.IsEmpty() || path.empty()) return Paths64();
+    RectClip64 rc(rect);
+    return rc.Execute(Paths64{ path });
   }
 
   inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
   {
     if (rect.IsEmpty() || paths.empty()) return PathsD();
-    CheckPrecision(precision);
+    int error_code = 0;
+    CheckPrecisionRange(precision, error_code);
+    if (error_code) return PathsD();
     const double scale = std::pow(10, precision);
     Rect64 r = ScaleRect<int64_t, double>(rect, scale);
-    class RectClip rc(r);
-    PathsD result;
-    result.reserve(paths.size());
-    for (const PathD& path : paths) 
-    {
-      RectD pathRec = Bounds(path);
-      if (!rect.Intersects(pathRec))
-        continue;
-      else if (rect.Contains(pathRec))
-        result.push_back(path);
-      else
-      {
-        Path64 p = ScalePath<int64_t, double>(path, scale);
-        p = rc.Execute(p);
-        if (!p.empty()) 
-          result.push_back(ScalePath<double, int64_t>(p, 1 / scale));
-      }
-    }
-    return result;
+    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), 1 / scale, error_code);
   }
 
-  inline Paths64 RectClipLines(const Rect64& rect, const Path64& path)
+  inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2)
   {
-    Paths64 result;
-    if (rect.IsEmpty() || path.empty()) return result;
-    Rect64 pathRec = Bounds(path);
-    if (!rect.Intersects(pathRec)) return result;
-    if (rect.Contains(pathRec)) 
-    {
-      result.push_back(path);
-      return result;
-    }
-    class RectClipLines rcl(rect);
-    return rcl.Execute(path);
+    return RectClip(rect, PathsD{ path }, precision);
   }
 
-  inline Paths64 RectClipLines(const Rect64& rect, const Paths64& paths)
+  inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines)
   {
-    Paths64 result;
-    if (rect.IsEmpty() || paths.empty()) return result;
-    class RectClipLines rcl(rect);
-    for (const Path64& p : paths)
-    {
-      Rect64 pathRec = Bounds(p);
-      if (!rect.Intersects(pathRec))
-        continue;
-      else if (rect.Contains(pathRec))
-        result.push_back(p);
-      else
-      {
-        Paths64 pp = rcl.Execute(p);
-        if (!pp.empty()) 
-          result.insert(result.end(), pp.begin(), pp.end());
-      }
-    }
-    return result;
+    if (rect.IsEmpty() || lines.empty()) return Paths64();
+    RectClipLines64 rcl(rect);
+    return rcl.Execute(lines);
   }
 
-  inline PathsD RectClipLines(const RectD& rect, const PathD& path, int precision = 2)
+  inline Paths64 RectClipLines(const Rect64& rect, const Path64& line)
   {
-    if (rect.IsEmpty() || path.empty() ||
-      !rect.Contains(Bounds(path))) return PathsD();
-    CheckPrecision(precision);
-    const double scale = std::pow(10, precision);
-    Rect64 r = ScaleRect<int64_t, double>(rect, scale);
-    class RectClipLines rcl(r);
-    Path64 p = ScalePath<int64_t, double>(path, scale);
-    return ScalePaths<double, int64_t>(rcl.Execute(p), 1 / scale);
+    return RectClipLines(rect, Paths64{ line });
   }
 
-  inline PathsD RectClipLines(const RectD& rect, const PathsD& paths, int precision = 2)
+  inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
   {
-    PathsD result;
-    if (rect.IsEmpty() || paths.empty()) return result;
-    CheckPrecision(precision);
+    if (rect.IsEmpty() || lines.empty()) return PathsD();
+    int error_code = 0;
+    CheckPrecisionRange(precision, error_code);
+    if (error_code) return PathsD();
     const double scale = std::pow(10, precision);
     Rect64 r = ScaleRect<int64_t, double>(rect, scale);
-    class RectClipLines rcl(r);
-    result.reserve(paths.size());
-    for (const PathD& path : paths)
-    {
-      RectD pathRec = Bounds(path);
-      if (!rect.Intersects(pathRec))
-        continue;
-      else if (rect.Contains(pathRec))
-        result.push_back(path);
-      else
-      {
-        Path64 p = ScalePath<int64_t, double>(path, scale);
-        Paths64 pp = rcl.Execute(p);
-        if (pp.empty()) continue;
-        PathsD ppd = ScalePaths<double, int64_t>(pp, 1 / scale);
-        result.insert(result.end(), ppd.begin(), ppd.end());
-      }
-    }
-    return result;
+    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
@@ -420,104 +288,96 @@ namespace Clipper2Lib {
     {
       for (const auto& child : pp)
       {
+        // return false if this child isn't fully contained by its parent
+
+        // checking for a single vertex outside is a bit too crude since
+        // it doesn't account for rounding errors. It's better to check
+        // for consecutive vertices found outside the parent's polygon.
+
+        int outsideCnt = 0;
         for (const Point64& pt : child->Polygon())
-          if (PointInPolygon(pt, pp.Polygon()) == PointInPolygonResult::IsOutside)
-            return false;
+        {
+          PointInPolygonResult result = PointInPolygon(pt, pp.Polygon());
+          if (result == PointInPolygonResult::IsInside) --outsideCnt;
+          else if (result == PointInPolygonResult::IsOutside) ++outsideCnt;
+          if (outsideCnt > 1) return false;
+          else if (outsideCnt < -1) break;
+        }
+
+        // now check any nested children too
         if (child->Count() > 0 && !PolyPath64ContainsChildren(*child))
           return false;
       }
       return true;
     }
 
-    inline bool GetInt(std::string::const_iterator& iter, const
-      std::string::const_iterator& end_iter, int64_t& val)
+    static void OutlinePolyPath(std::ostream& os,
+      size_t idx, bool isHole, size_t count, const std::string& preamble)
     {
-      val = 0;
-      bool is_neg = *iter == '-';
-      if (is_neg) ++iter;
-      std::string::const_iterator start_iter = iter;
-      while (iter != end_iter &&
-        ((*iter >= '0') && (*iter <= '9')))
-      {
-        val = val * 10 + (static_cast<int64_t>(*iter++) - '0');
-      }
-      if (is_neg) val = -val;
-      return (iter != start_iter);
-    }
-
-    inline bool GetFloat(std::string::const_iterator& iter, const 
-      std::string::const_iterator& end_iter, double& val)
-    {
-      val = 0;
-      bool is_neg = *iter == '-';
-      if (is_neg) ++iter;
-      int dec_pos = 1;
-      const std::string::const_iterator start_iter = iter;
-      while (iter != end_iter && (*iter == '.' ||
-        ((*iter >= '0') && (*iter <= '9'))))
-      {
-        if (*iter == '.')
-        {
-          if (dec_pos != 1) break;
-          dec_pos = 0;
-          ++iter;
-          continue;
-        }
-        if (dec_pos != 1) --dec_pos;
-        val = val * 10 + ((int64_t)(*iter++) - '0');
-      }
-      if (iter == start_iter || dec_pos == 0) return false;
-      if (dec_pos < 0)
-        val *= std::pow(10, dec_pos);
-      if (is_neg)
-        val *= -1;
-      return true;
+      std::string plural = (count == 1) ? "." : "s.";
+      if (isHole)
+        os << preamble << "+- Hole (" << idx << ") contains " << count <<
+        " nested polygon" << plural << std::endl;
+      else
+        os << preamble << "+- Polygon (" << idx << ") contains " << count <<
+          " hole" << plural << std::endl;
     }
 
-    inline void SkipWhiteSpace(std::string::const_iterator& iter, 
-      const std::string::const_iterator& end_iter)
+    static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp,
+      size_t idx, std::string preamble)
     {
-      while (iter != end_iter && *iter <= ' ') ++iter;
+      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 + "  ");
     }
 
-    inline void SkipSpacesWithOptionalComma(std::string::const_iterator& iter, 
-      const std::string::const_iterator& end_iter)
+    static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp,
+      size_t idx, std::string preamble)
     {
-      bool comma_seen = false;
-      while (iter != end_iter)
-      {
-        if (*iter == ' ') ++iter;
-        else if (*iter == ',')
-        {
-          if (comma_seen) return; // don't skip 2 commas!
-          comma_seen = true;
-          ++iter;
-        }
-        else return;                
-      }
+      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 + "  ");
     }
 
-    inline bool has_one_match(const char c, char* chrs)
+    template<typename T, typename U>
+    inline constexpr void MakePathGeneric(const T an_array,
+      size_t array_size, std::vector<U>& result)
     {
-      while (*chrs > 0 && c != *chrs) ++chrs;
-      if (!*chrs) return false;
-      *chrs = ' '; // only match once per char
-      return true;
+      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 void SkipUserDefinedChars(std::string::const_iterator& iter,
-      const std::string::const_iterator& end_iter, const std::string& skip_chars)
-    {
-      const size_t MAX_CHARS = 16;
-      char buff[MAX_CHARS] = {0};
-      std::copy(skip_chars.cbegin(), skip_chars.cend(), &buff[0]);
-      while (iter != end_iter && 
-        (*iter <= ' ' || has_one_match(*iter, buff))) ++iter;
-      return;
-    }
+  inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
+  {
+    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;
+    return os;
+  }
 
-  } // end details namespace 
+  inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp)
+  {
+    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;
+  }
 
   inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
   {
@@ -538,48 +398,103 @@ namespace Clipper2Lib {
   inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
   {
     for (const auto& child : polytree)
-      if (child->Count() > 0 && 
+      if (child->Count() > 0 &&
         !details::PolyPath64ContainsChildren(*child))
           return false;
     return true;
   }
 
-  inline Path64 MakePath(const std::string& s)
+  template<typename T,
+    typename std::enable_if<
+      std::is_integral<T>::value &&
+      !std::is_same<char, T>::value, bool
+    >::type = true>
+  inline Path64 MakePath(const std::vector<T>& list)
   {
-    const std::string skip_chars = " ,(){}[]";
+    const auto size = list.size() - list.size() % 2;
+    if (list.size() != size)
+      DoError(non_pair_error_i);  // non-fatal without exception handling
     Path64 result;
-    std::string::const_iterator s_iter = s.cbegin();
-    details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
-    while (s_iter != s.cend())
-    {
-      int64_t y = 0, x = 0;
-      if (!details::GetInt(s_iter, s.cend(), x)) break;
-      details::SkipSpacesWithOptionalComma(s_iter, s.cend());
-      if (!details::GetInt(s_iter, s.cend(), y)) break;
-      result.push_back(Point64(x, y));
-      details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
-    }
+    details::MakePathGeneric(list, size, result);
     return result;
   }
-  
-  inline PathD MakePathD(const std::string& s)
+
+  template<typename T, std::size_t N,
+    typename std::enable_if<
+      std::is_integral<T>::value &&
+      !std::is_same<char, T>::value, bool
+    >::type = true>
+  inline Path64 MakePath(const T(&list)[N])
   {
-    const std::string skip_chars = " ,(){}[]";
+    // Make the compiler error on unpaired value (i.e. no runtime effects).
+    static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
+    Path64 result;
+    details::MakePathGeneric(list, N, result);
+    return result;
+  }
+
+  template<typename T,
+    typename std::enable_if<
+      std::is_arithmetic<T>::value &&
+      !std::is_same<char, T>::value, bool
+    >::type = true>
+  inline PathD MakePathD(const std::vector<T>& list)
+  {
+    const auto size = list.size() - list.size() % 2;
+    if (list.size() != size)
+      DoError(non_pair_error_i);  // non-fatal without exception handling
     PathD result;
-    std::string::const_iterator s_iter = s.cbegin();
-    details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
-    while (s_iter != s.cend())
-    {
-      double y = 0, x = 0;
-      if (!details::GetFloat(s_iter, s.cend(), x)) break;
-      details::SkipSpacesWithOptionalComma(s_iter, s.cend());
-      if (!details::GetFloat(s_iter, s.cend(), y)) break;
-      result.push_back(PointD(x, y));
-      details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
-    }
+    details::MakePathGeneric(list, size, result);
+    return result;
+  }
+
+  template<typename T, std::size_t N,
+    typename std::enable_if<
+      std::is_arithmetic<T>::value &&
+      !std::is_same<char, T>::value, bool
+    >::type = true>
+  inline PathD MakePathD(const T(&list)[N])
+  {
+    // Make the compiler error on unpaired value (i.e. no runtime effects).
+    static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
+    PathD result;
+    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();
@@ -629,11 +544,14 @@ namespace Clipper2Lib {
 
   inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
   {
-    CheckPrecision(precision);
+    int error_code = 0;
+    CheckPrecisionRange(precision, error_code);
+    if (error_code) return PathD();
     const double scale = std::pow(10, precision);
-    Path64 p = ScalePath<int64_t, double>(path, scale);
+    Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
+    if (error_code) return PathD();
     p = TrimCollinear(p, is_open_path);
-    return ScalePath<double, int64_t>(p, 1/scale);
+    return ScalePath<double, int64_t>(p, 1/scale, error_code);
   }
 
   template <typename T>
@@ -662,12 +580,12 @@ namespace Clipper2Lib {
     double cp = std::abs(CrossProduct(pt1, pt2, pt3));
     return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
   }
-  
+
   template <typename T>
   inline Path<T> Ellipse(const Rect<T>& rect, int steps = 0)
   {
-    return Ellipse(rect.MidPoint(), 
-      static_cast<double>(rect.Width()) *0.5, 
+    return Ellipse(rect.MidPoint(),
+      static_cast<double>(rect.Width()) *0.5,
       static_cast<double>(rect.Height()) * 0.5, steps);
   }
 
@@ -696,16 +614,105 @@ namespace Clipper2Lib {
     return result;
   }
 
+  inline size_t GetNext(size_t current, size_t high,
+    const std::vector<bool>& flags)
+  {
+    ++current;
+    while (current <= high && flags[current]) ++current;
+    if (current <= high) return current;
+    current = 0;
+    while (flags[current]) ++current;
+    return current;
+  }
+
+  inline size_t GetPrior(size_t current, size_t high,
+    const std::vector<bool>& flags)
+  {
+    if (current == 0) current = high;
+    else --current;
+    while (current > 0 && flags[current]) --current;
+    if (!flags[current]) return current;
+    current = high;
+    while (flags[current]) --current;
+    return current;
+  }
+
   template <typename T>
-  inline double PerpendicDistFromLineSqrd(const Point<T>& pt,
-    const Point<T>& line1, const Point<T>& line2)
+  inline Path<T> SimplifyPath(const Path<T> &path,
+    double epsilon, bool isClosedPath = true)
   {
-    double a = static_cast<double>(pt.x - line1.x);
-    double b = static_cast<double>(pt.y - line1.y);
-    double c = static_cast<double>(line2.x - line1.x);
-    double d = static_cast<double>(line2.y - line1.y);
-    if (c == 0 && d == 0) return 0;
-    return Sqr(a * d - c * b) / (c * c + d * d);
+    const size_t len = path.size(), high = len -1;
+    const double epsSqr = Sqr(epsilon);
+    if (len < 4) return Path<T>(path);
+
+    std::vector<bool> flags(len);
+    std::vector<double> distSqr(len);
+    size_t prior = high, curr = 0, start, next, prior2;
+    if (isClosedPath)
+    {
+      distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
+      distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
+    }
+    else
+    {
+      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]);
+
+    for (;;)
+    {
+      if (distSqr[curr] > epsSqr)
+      {
+        start = curr;
+        do
+        {
+          curr = GetNext(curr, high, flags);
+        } while (curr != start && distSqr[curr] > epsSqr);
+        if (curr == start) break;
+      }
+
+      prior = GetPrior(curr, high, flags);
+      next = GetNext(curr, high, flags);
+      if (next == prior) break;
+
+      // flag for removal the smaller of adjacent 'distances'
+      if (distSqr[next] < distSqr[curr])
+      {
+        prior2 = prior;
+        prior = curr;
+        curr = next;
+        next = GetNext(next, high, flags);
+      }
+      else
+        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 (isClosedPath || ((prior != 0) && (prior != high)))
+        distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
+    }
+    Path<T> result;
+    result.reserve(len);
+    for (typename Path<T>::size_type i = 0; i < len; ++i)
+      if (!flags[i]) result.push_back(path[i]);
+    return result;
+  }
+
+  template <typename T>
+  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, isClosedPath));
+    return result;
   }
 
   template <typename T>
@@ -751,8 +758,9 @@ namespace Clipper2Lib {
   {
     Paths<T> result;
     result.reserve(paths.size());
-    for (const Path<T>& path : paths)
-      result.push_back(RamerDouglasPeucker<T>(path, epsilon));
+    std::transform(paths.begin(), paths.end(), back_inserter(result),
+      [epsilon](const auto& path)
+      { return RamerDouglasPeucker<T>(path, epsilon); });
     return result;
   }
 

+ 12 - 10
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  15 October 2022                                                 *
+* Date      :  1 November 2023                                                 *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  Minkowski Sum and Difference                                    *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -13,9 +13,9 @@
 #include <cstdlib>
 #include <vector>
 #include <string>
-#include "clipper.core.h"
+#include "clipper2/clipper.core.h"
 
-namespace Clipper2Lib 
+namespace Clipper2Lib
 {
 
   namespace detail
@@ -92,11 +92,12 @@ namespace Clipper2Lib
 
   inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
   {
+    int error_code = 0;
     double scale = pow(10, decimalPlaces);
-    Path64 pat64 = ScalePath<int64_t, double>(pattern, scale);
-    Path64 path64 = ScalePath<int64_t, double>(path, scale);
+    Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
+    Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
     Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
-    return ScalePaths<double, int64_t>(tmp, 1 / scale);
+    return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
   }
 
   inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed)
@@ -106,11 +107,12 @@ namespace Clipper2Lib
 
   inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
   {
+    int error_code = 0;
     double scale = pow(10, decimalPlaces);
-    Path64 pat64 = ScalePath<int64_t, double>(pattern, scale); 
-    Path64 path64 = ScalePath<int64_t, double>(path, scale);
+    Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
+    Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
     Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
-    return ScalePaths<double, int64_t>(tmp, 1 / scale);
+    return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
   }
 
 } // Clipper2Lib namespace

+ 54 - 36
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.offset.h

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  15 October 2022                                                 *
+* Date      :  14 February 2024                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2024                                         *
 * Purpose   :  Path Offset (Inflate/Shrink)                                    *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -11,10 +11,13 @@
 #define CLIPPER_OFFSET_H_
 
 #include "clipper.core.h"
+#include "clipper.engine.h"
 
 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
@@ -23,49 +26,65 @@ 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:
 
 	class Group {
 	public:
-		Paths64 paths_in_;
-		Paths64 paths_out_;
-		Path64 path_;
-		bool is_reversed_ = false;
-		JoinType join_type_;
-		EndType end_type_;
-		Group(const Paths64& paths, JoinType join_type, EndType end_type) :
-			paths_in_(paths), join_type_(join_type), end_type_(end_type) {}
+		Paths64 paths_in;
+		std::vector<bool> is_hole_list;
+		std::vector<double> areas_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);
 	};
 
+	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;
 	double arc_tolerance_ = 0.0;
-	bool merge_groups_ = true;
 	bool preserve_collinear_ = false;
 	bool reverse_solution_ = false;
 
-	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);
+#ifdef USINGZ
+	ZCallback64 zCallback64_ = nullptr;
+#endif
+	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, EndType endType);
-	void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k);
-	void DoGroupOffset(Group &group, double delta);
+	void OffsetPolygon(Group& group, const Path64& path, bool is_shrinking, double area);
+	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:
-	ClipperOffset(double miter_limit = 2.0,
+	explicit ClipperOffset(double miter_limit = 2.0,
 		double arc_tolerance = 0.0,
-		bool preserve_collinear = false, 
+		bool preserve_collinear = false,
 		bool reverse_solution = false) :
 		miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
 		preserve_collinear_(preserve_collinear),
@@ -73,13 +92,14 @@ public:
 
 	~ClipperOffset() { Clear(); };
 
+	int ErrorCode() { return error_code_; };
 	void AddPath(const Path64& path, JoinType jt_, EndType et_);
 	void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
-	void AddPath(const PathD &p, JoinType jt_, EndType et_);
-	void AddPaths(const PathsD &p, JoinType jt_, EndType et_);
 	void Clear() { groups_.clear(); norms.clear(); };
 	
-	Paths64 Execute(double delta);
+	void Execute(double delta, Paths64& paths);
+	void Execute(double delta, PolyTree64& polytree);
+	void Execute(DeltaCallback64 delta_cb, Paths64& paths);
 
 	double MiterLimit() const { return miter_limit_; }
 	void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
@@ -88,19 +108,17 @@ public:
 	double ArcTolerance() const { return arc_tolerance_; }
 	void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; }
 
-	//MergeGroups: A path group is one or more paths added via the AddPath or
-	//AddPaths methods. By default these path groups will be offset
-	//independently of other groups and this may cause overlaps (intersections).
-	//However, when MergeGroups is enabled, any overlapping offsets will be
-	//merged (via a clipping union operation) to remove overlaps.
-	bool MergeGroups() const { return merge_groups_; }
-	void MergeGroups(bool merge_groups) { merge_groups_ = merge_groups; }
-
 	bool PreserveCollinear() const { return preserve_collinear_; }
 	void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
 	
 	bool ReverseSolution() const { return reverse_solution_; }
 	void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
+
+#ifdef USINGZ
+	void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; }
+#endif
+	void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; }
+
 };
 
 }

+ 48 - 17
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  26 October 2022                                                 *
+* Date      :  1 November 2023                                                 *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  FAST rectangular clipping                                       *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -12,38 +12,69 @@
 
 #include <cstdlib>
 #include <vector>
-#include "clipper.h"
-#include "clipper.core.h"
+#include <queue>
+#include "clipper2/clipper.core.h"
 
-namespace Clipper2Lib 
+namespace Clipper2Lib
 {
 
   enum class Location { Left, Top, Right, Bottom, Inside };
 
-  class RectClip {
+  class OutPt2;
+  typedef std::vector<OutPt2*> OutPt2List;
+
+  class OutPt2 {
+  public:
+    Point64 pt;
+    size_t owner_idx;
+    OutPt2List* edge;
+    OutPt2* next;
+    OutPt2* prev;
+  };
+
+  //------------------------------------------------------------------------------
+  // RectClip64
+  //------------------------------------------------------------------------------
+
+  class RectClip64 {
+  private:
+    void ExecuteInternal(const Path64& path);
+    Path64 GetPath(OutPt2*& op);
   protected:
     const Rect64 rect_;
-    const Point64 mp_;
-    const Path64 rectPath_;
-    Path64 result_;
+    const Path64 rect_as_path_;
+    const Point64 rect_mp_;
+    Rect64 path_bounds_;
+    std::deque<OutPt2> op_container_;
+    OutPt2List results_;  // each path can be broken into multiples
+    OutPt2List edges_[8]; // clockwise and counter-clockwise
     std::vector<Location> start_locs_;
-
+    void CheckEdges();
+    void TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw);
     void GetNextLocation(const Path64& path,
       Location& loc, int& i, int highI);
+    OutPt2* Add(Point64 pt, bool start_new = false);
     void AddCorner(Location prev, Location curr);
     void AddCorner(Location& loc, bool isClockwise);
   public:
-    RectClip(const Rect64& rect) :
+    explicit RectClip64(const Rect64& rect) :
       rect_(rect),
-      mp_(rect.MidPoint()),
-      rectPath_(rect.AsPath()) {}
-    Path64 Execute(const Path64& path);
+      rect_as_path_(rect.AsPath()),
+      rect_mp_(rect.MidPoint()) {}
+    Paths64 Execute(const Paths64& paths);
   };
 
-  class RectClipLines : public RectClip {
+  //------------------------------------------------------------------------------
+  // RectClipLines64
+  //------------------------------------------------------------------------------
+
+  class RectClipLines64 : public RectClip64 {
+  private:
+    void ExecuteInternal(const Path64& path);
+    Path64 GetPath(OutPt2*& op);
   public:
-    RectClipLines(const Rect64& rect) : RectClip(rect) {};
-    Paths64 Execute(const Path64& path);
+    explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
+    Paths64 Execute(const Paths64& paths);
   };
 
 } // Clipper2Lib namespace

+ 6 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.version.h

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

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 422 - 275
polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.engine.cpp


+ 426 - 260
polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.offset.cpp

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  15 October 2022                                                 *
+* Date      :  14 February 2024                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2024                                         *
 * Purpose   :  Path Offset (Inflate/Shrink)                                    *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -20,19 +20,21 @@ const double floating_point_tolerance = 1e-12;
 // Miscellaneous methods
 //------------------------------------------------------------------------------
 
-Paths64::size_type GetLowestPolygonIdx(const Paths64& paths)
+int GetLowestClosedPathIdx(const Paths64& paths)
 {
-	Paths64::size_type result = 0;
-	Point64 lp = Point64(static_cast<int64_t>(0), 
-		std::numeric_limits<int64_t>::min());
-
-	for (Paths64::size_type i = 0 ; i < paths.size(); ++i)
-		for (const Point64& p : paths[i])
-		{ 
-			if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue;
-			result = i;
-			lp = p;
-		}	
+	int result = -1;
+	Point64 botPt = Point64(INT64_MAX, INT64_MIN);
+	for (size_t i = 0; i < paths.size(); ++i)
+	{
+		for (const Point64& pt : paths[i])
+		{
+			if ((pt.y < botPt.y) || 
+				((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
+		  result = static_cast<int>(i);
+			botPt.x = pt.x;
+			botPt.y = pt.y;
+		}
+	}
 	return result;
 }
 
@@ -53,7 +55,7 @@ inline bool AlmostZero(double value, double epsilon = 0.001)
 	return std::fabs(value) < epsilon;
 }
 
-inline double Hypot(double x, double y) 
+inline double Hypot(double x, double y)
 {
 	//see https://stackoverflow.com/a/32436148/359538
 	return std::sqrt(x * x + y * y);
@@ -61,7 +63,6 @@ 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;
@@ -80,14 +81,75 @@ inline bool IsClosedPath(EndType et)
 
 inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
 {
+#ifdef USINGZ
+	return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
+#else
 	return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
+#endif
 }
 
 inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
 {
+#ifdef USINGZ
+	return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
+#else
 	return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
+#endif
 }
 
+inline void NegatePath(PathD& path)
+{
+	for (PointD& pt : path)
+	{
+		pt.x = -pt.x;
+		pt.y = -pt.y;
+#ifdef USINGZ
+		pt.z = pt.z;
+#endif
+	}
+}
+
+
+//------------------------------------------------------------------------------
+// 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);
+
+	if (end_type == EndType::Polygon)
+	{
+		is_hole_list.reserve(paths_in.size());
+		areas_list.reserve(paths_in.size());
+		for (const Path64& path : paths_in)
+		{
+			double a = Area(path);
+			areas_list.push_back(a);
+			is_hole_list.push_back(a < 0);
+		}
+		lowest_path_idx = GetLowestClosedPathIdx(paths_in);
+		// the lowermost path must be an outer path, so if its orientation is negative,
+		// then flag the whole group is 'reversed' (will negate delta etc.)
+		// as this is much more efficient than reversing every path.
+		is_reversed = (lowest_path_idx >= 0) && is_hole_list[lowest_path_idx];
+		if (is_reversed) is_hole_list.flip();
+	}
+	else
+	{
+		lowest_path_idx = -1;
+		is_reversed = false;
+		is_hole_list.resize(paths_in.size());
+		areas_list.resize(paths_in.size());
+	}
+}
+
+
 //------------------------------------------------------------------------------
 // ClipperOffset methods
 //------------------------------------------------------------------------------
@@ -105,82 +167,60 @@ void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
 	groups_.push_back(Group(paths, jt_, et_));
 }
 
-void ClipperOffset::AddPath(const Clipper2Lib::PathD& path, JoinType jt_, EndType et_)
-{
-	PathsD paths;
-	paths.push_back(path);
-	AddPaths(paths, jt_, et_);
-}
-
-void ClipperOffset::AddPaths(const PathsD& paths, JoinType jt_, EndType et_)
-{
-	if (paths.size() == 0) return;
-	groups_.push_back(Group(PathsDToPaths64(paths), jt_, et_));
-}
-
 void ClipperOffset::BuildNormals(const Path64& path)
 {
 	norms.clear();
 	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)
+void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
 {
-	return PointD(pt.x + dx, pt.y + dy);
-}
-
-inline PointD ReflectPoint(const PointD& pt, const PointD& pivot)
-{
-	return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
-}
-
-PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
-	const PointD& pt2a, const PointD& pt2b)
-{
-	if (pt1a.x == pt1b.x) //vertical
-	{
-		if (pt2a.x == pt2b.x) return PointD(0, 0);
-
-		double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
-		double b2 = pt2a.y - m2 * pt2a.x;
-		return PointD(pt1a.x, m2 * pt1a.x + b2);
-	}
-	else if (pt2a.x == pt2b.x) //vertical
+	PointD pt1, pt2;
+	if (j == k)
 	{
-		double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
-		double b1 = pt1a.y - m1 * pt1a.x;
-		return PointD(pt2a.x, m1 * pt2a.x + b1);
+		double abs_delta = std::abs(group_delta_);
+#ifdef USINGZ
+		pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
+		pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
+#else
+		pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
+		pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
+#endif
 	}
 	else
 	{
-		double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
-		double b1 = pt1a.y - m1 * pt1a.x;
-		double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
-		double b2 = pt2a.y - m2 * pt2a.x;
-		if (m1 == m2) return PointD(0, 0);
-		double x = (b2 - b1) / (m1 - m2);
-		return PointD(x, m1 * x + b1);
+#ifdef USINGZ
+		pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z);
+		pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z);
+#else
+		pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
+		pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
+#endif
 	}
+	path_out.push_back(Point64(pt1));
+	path_out.push_back(Point64(pt2));
 }
 
-void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k)
+void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
 {
 	PointD vec;
-	if (j == k) 
-		vec = PointD(norms[0].y, -norms[0].x);
+	if (j == k)
+		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);
@@ -189,51 +229,78 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t
 	if (j == k)
 	{
 		PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
-		PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
+		PointD pt = ptQ;
+		GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
 		//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
 	{
 		PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
-		PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
-		group.path_.push_back(Point64(pt));
+		PointD pt = ptQ;
+		GetSegmentIntersectPt(pt1, pt2, pt3, pt4, 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);
-	group.path_.push_back(Point64(
+#ifdef USINGZ
+	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
+	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)
 {
-	//even though angle may be negative this is a convex join
-	Point64 pt = path[j];
-	int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle)));
-	double step_sin = std::sin(angle / steps);
-	double step_cos = std::cos(angle / steps);
-	
-	PointD pt2 = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
-	if (j == k) pt2.Negate();
+	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);
+	}
 
-	group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
-	for (int i = 0; i < steps; i++)
+	Point64 pt = path[j];
+	PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
+
+	if (j == k) offsetVec.Negate();
+#ifdef USINGZ
+	path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
+#else
+	path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
+#endif
+	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
 	{
-		pt2 = PointD(pt2.x * step_cos - step_sin * pt2.y,
-			pt2.x * step_sin + pt2.y * step_cos);
-		group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
+		offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
+			offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
+#ifdef USINGZ
+		path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
+#else
+		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)
@@ -241,245 +308,344 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
 	// sin(A) < 0: right turning
 	// cos(A) < 0: change in angle is more than 90 degree
 
-	if (path[j] == path[k]) { k = j; return; }
+	if (path[j] == path[k]) return;
 
 	double sin_a = CrossProduct(norms[j], norms[k]);
 	double cos_a = DotProduct(norms[j], norms[k]);
 	if (sin_a > 1.0) sin_a = 1.0;
 	else if (sin_a < -1.0) sin_a = -1.0;
 
-	bool almostNoAngle = AlmostZero(sin_a) && cos_a > 0;
-	// when there's almost no angle of deviation or it's concave
-	if (almostNoAngle || (sin_a * group_delta_ < 0))
+	if (deltaCallback64_) {
+		group_delta_ = deltaCallback64_(path, norms, j, k);
+		if (group.is_reversed) group_delta_ = -group_delta_;
+	}
+	if (std::fabs(group_delta_) <= floating_point_tolerance)
 	{
-		Point64 p1 = Point64(
-			path[j].x + norms[k].x * group_delta_,
-			path[j].y + norms[k].y * group_delta_);
-		Point64 p2 = Point64(
-			path[j].x + norms[j].x * group_delta_,
-			path[j].y + norms[j].y * group_delta_);
-		group.path_.push_back(p1);
-		if (p1 != p2)
-		{
-			// when concave add an extra vertex to ensure neat clipping
-			if (!almostNoAngle) group.path_.push_back(path[j]);
-			group.path_.push_back(p2);
-		}
+		path_out.push_back(path[j]);
+		return;
 	}
-	else // it's convex 
+
+	if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
 	{
-		if (join_type_ == JoinType::Round)
-			DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
-		else if (join_type_ == JoinType::Miter)
-		{
-			// miter unless the angle is so acute the miter would exceeds ML
-			if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
-			else DoSquare(group, path, j, k);
-		}
-		// don't bother squaring angles that deviate < ~20 degrees because
-		// squaring will be indistinguishable from mitering and just be a lot slower
-		else if (cos_a > 0.9)
-			DoMiter(group, path, j, k, cos_a);			
-		else
-			DoSquare(group, path, j, k);			
+		// is concave
+		path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_));
+		// this extra point is the only (simple) way to ensure that
+		// path reversals are fully cleaned with the trailing clipper
+		path_out.push_back(path[j]); // (#405)
+		path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
 	}
-	k = j;
+	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 sufficiently acute to exceed ML
+		if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
+		else DoSquare(path, j, k);
+	}
+	else if (join_type_ == JoinType::Round)
+		DoRound(path, j, k, std::atan2(sin_a, cos_a));
+	else if ( join_type_ == JoinType::Bevel)
+		DoBevel(path, j, k);
+	else
+		DoSquare(path, j, k);
 }
 
-void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
+void ClipperOffset::OffsetPolygon(Group& group, const Path64& path, bool is_shrinking, double area)
 {
-	group.path_.clear();
-	for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i)
-		OffsetPoint(group, path, i, j);
-	group.paths_out_.push_back(group.path_);
+	path_out.clear();
+	for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
+		OffsetPoint(group, path, j, k);
+
+	// make sure that polygon areas aren't reversing which would indicate
+	// that the polygon has shrunk too far and that it should be discarded.
+	// See also - #593 & #715
+	if (is_shrinking && area // area == 0.0 when JoinType::Joined
+		&& ((area < 0) != (Area(path_out) < 0))) return;
+	
+	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());
-	BuildNormals(path);
-	OffsetPolygon(group, path);
+	OffsetPolygon(group, path, false, 0);
+	Path64 reverse_path(path);
+	std::reverse(reverse_path.begin(), reverse_path.end());
+
+	//rebuild normals 
+	std::reverse(norms.begin(), norms.end());
+	norms.push_back(norms[0]);
+	norms.erase(norms.begin());
+	NegatePath(norms);
+
+	OffsetPolygon(group, reverse_path, true, 0);
 }
 
-void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type)
+void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
 {
-	group.path_.clear();
-
 	// 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:
-		group.path_.push_back(Point64(
-			path[0].x - norms[0].x * group_delta_,
-			path[0].y - norms[0].y * group_delta_));
-		group.path_.push_back(GetPerpendic(path[0], norms[0], group_delta_));
-		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 
+	// reverse normals
 	for (size_t i = highI; i > 0; --i)
 		norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
 	norms[0] = norms[highI];
 
 	// 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:
-		group.path_.push_back(Point64(
-			path[highI].x - norms[highI].x * group_delta_,
-			path[highI].y - norms[highI].y * group_delta_));
-		group.path_.push_back(GetPerpendic(path[highI], norms[highI], group_delta_));
-		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, double delta)
+void ClipperOffset::DoGroupOffset(Group& group)
 {
-	if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5;
-	bool isClosedPaths = IsClosedPath(group.end_type_);
-
-	if (isClosedPaths)
+	if (group.end_type == EndType::Polygon)
 	{
-		//the lowermost polygon must be an outer polygon. So we can use that as the
-		//designated orientation for outer polygons (needed for tidy-up clipping)
-		Paths64::size_type lowestIdx = GetLowestPolygonIdx(group.paths_in_);
-    // nb: don't use the default orientation here ...
-		double area = Area(group.paths_in_[lowestIdx]);
-		if (area == 0) return;	
-		group.is_reversed_ = (area < 0);
-		if (group.is_reversed_) delta = -delta;
-	} 
+		// a straight path (2 points) can now also be 'polygon' offset
+		// where the ends will be treated as (180 deg.) joins
+		if (group.lowest_path_idx < 0) delta_ = std::abs(delta_);
+		group_delta_ = (group.is_reversed) ? -delta_ : delta_;
+	}
 	else
-		group.is_reversed_ = false;
-
-	group_delta_ = delta;
-	abs_group_delta_ = std::abs(group_delta_);
-	join_type_ = group.join_type_;
+		group_delta_ = std::abs(delta_);// *0.5;
 
-	double arcTol = (arc_tolerance_ > floating_point_tolerance ? arc_tolerance_
-		: std::log10(2 + abs_group_delta_) * default_arc_tolerance); // empirically derived
+	double abs_delta = std::fabs(group_delta_);
+	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)
+	if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
 	{
-		steps_per_rad_ = PI / std::acos(1 - arcTol / abs_group_delta_) / (PI *2);
+		// 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_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);
 	}
 
-	bool is_closed_path = IsClosedPath(group.end_type_);
-	Paths64::const_iterator path_iter;
-	for(path_iter = group.paths_in_.cbegin(); path_iter != group.paths_in_.cend(); ++path_iter)
+	double min_area = PI * Sqr(group_delta_);
+	std::vector<bool>::const_iterator is_hole_it = group.is_hole_list.cbegin();
+	std::vector<double>::const_iterator area_it = group.areas_list.cbegin();
+	Paths64::const_iterator path_in_it = group.paths_in.cbegin();
+	for ( ; path_in_it != group.paths_in.cend(); ++path_in_it, ++is_hole_it, ++area_it)
 	{
-		Path64 path = StripDuplicates(*path_iter, is_closed_path);
-		Path64::size_type cnt = path.size();
-		if (cnt == 0) continue;
+		bool is_shrinking = 
+			(group.end_type == EndType::Polygon) &&
+			(group.is_reversed == ((group_delta_ < 0) == *is_hole_it));
+		if (is_shrinking && (std::fabs(*area_it) < min_area)) continue;
+
+		Path64::size_type pathLen = path_in_it->size();
+		path_out.clear();
 
-		if (cnt == 1) // single point - only valid with open paths
+		if (pathLen == 1) // single point
 		{
-			group.path_ = Path64();
+			if (deltaCallback64_)
+			{
+				group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
+				if (group.is_reversed) group_delta_ = -group_delta_;
+				abs_delta = std::fabs(group_delta_);
+			}
+
+			if (group_delta_ < 1) continue;
+			const Point64& pt = (*path_in_it)[0];
 			//single vertex so build a circle or square ...
-			if (group.join_type_ == JoinType::Round)
+			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 : path_out) p.z = pt.z;
+#endif
 			}
 			else
 			{
-				int d = (int)std::ceil(abs_group_delta_);
-				Rect64 r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
-				group.path_ = r.AsPath();
+				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 : path_out) p.z = pt.z;
+#endif
 			}
-			group.paths_out_.push_back(group.path_);
-		}
-		else
-		{
-			BuildNormals(path);
-			if (group.end_type_ == EndType::Polygon) OffsetPolygon(group, path);
-			else if (group.end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
-			else OffsetOpenPath(group, path, group.end_type_);
-		}
-	}
 
-	if (!merge_groups_)
-	{
-		//clean up self-intersections ...
-		Clipper64 c;
-		c.PreserveCollinear = false;
-		//the solution should retain the orientation of the input
-		c.ReverseSolution = reverse_solution_ != group.is_reversed_;
-		c.AddSubject(group.paths_out_);
-		if (group.is_reversed_)
-			c.Execute(ClipType::Union, FillRule::Negative, group.paths_out_);
-		else
-			c.Execute(ClipType::Union, FillRule::Positive, group.paths_out_);
+			solution.push_back(path_out);
+			continue;
+		} // end of offsetting a single point
+
+		if ((pathLen == 2) && (group.end_type == EndType::Joined))
+			end_type_ = (group.join_type == JoinType::Round) ?
+			  EndType::Round :
+			  EndType::Square;
+
+		BuildNormals(*path_in_it);
+		if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it, is_shrinking, *area_it);
+		else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it);
+		else OffsetOpenPath(group, *path_in_it);
 	}
+}
 
-	solution.reserve(solution.size() + group.paths_out_.size());
-	copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution));
-	group.paths_out_.clear();
+
+size_t ClipperOffset::CalcSolutionCapacity()
+{
+	size_t result = 0;
+	for (const Group& g : groups_)
+		result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
+	return result;
 }
 
-Paths64 ClipperOffset::Execute(double delta)
+bool ClipperOffset::CheckReverseOrientation()
 {
+	// nb: this assumes there's consistency in orientation between groups
+	bool is_reversed_orientation = false;
+	for (const Group& g : groups_)
+		if (g.end_type == EndType::Polygon)
+		{
+			is_reversed_orientation = g.is_reversed;
+			break;
+		}
+	return is_reversed_orientation;
+}
+
+void ClipperOffset::ExecuteInternal(double delta)
+{
+	error_code_ = 0;
 	solution.clear();
-	if (std::abs(delta) < default_arc_tolerance)
+	if (groups_.size() == 0) return;
+	solution.reserve(CalcSolutionCapacity());
+
+	if (std::abs(delta) < 0.5) // ie: offset is insignificant
 	{
+		Paths64::size_type sol_size = 0;
+		for (const Group& group : groups_) sol_size += group.paths_in.size();
+		solution.reserve(sol_size);
 		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));
-		}
-		return solution;
+			copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution));
+		return;
 	}
 
-	temp_lim_ = (miter_limit_ <= 1) ? 
-		2.0 : 
+	temp_lim_ = (miter_limit_ <= 1) ?
+		2.0 :
 		2.0 / (miter_limit_ * miter_limit_);
 
-	std::vector<Group>::iterator groups_iter;
-	for (groups_iter = groups_.begin(); 
-		groups_iter != groups_.end(); ++groups_iter)
+	delta_ = delta;
+	std::vector<Group>::iterator git;
+	for (git = groups_.begin(); git != groups_.end(); ++git)
 	{
-		DoGroupOffset(*groups_iter, delta);
+		DoGroupOffset(*git);
+		if (!error_code_) continue; // all OK
+		solution.clear();
 	}
+}
 
-	if (merge_groups_ && groups_.size() > 0)
-	{
-		//clean up self-intersections ...
-		Clipper64 c;
-		c.PreserveCollinear = false;
-		//the solution should retain the orientation of the input
-		c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_;
-
-		c.AddSubject(solution);
-		if (groups_[0].is_reversed_)
-			c.Execute(ClipType::Union, FillRule::Negative, solution);
-		else
-			c.Execute(ClipType::Union, FillRule::Positive, solution);
+void ClipperOffset::Execute(double delta, Paths64& paths)
+{
+	paths.clear();
+
+	ExecuteInternal(delta);
+	if (!solution.size()) return;
+
+	bool paths_reversed = CheckReverseOrientation();
+	//clean up self-intersections ...
+	Clipper64 c;
+	c.PreserveCollinear(false);
+	//the solution should retain the orientation of the input
+	c.ReverseSolution(reverse_solution_ != paths_reversed);
+#ifdef USINGZ
+	if (zCallback64_) { c.SetZCallback(zCallback64_); }
+#endif
+	c.AddSubject(solution);
+	if (paths_reversed)
+		c.Execute(ClipType::Union, FillRule::Negative, paths);
+	else
+		c.Execute(ClipType::Union, FillRule::Positive, paths);
+}
+
+
+void ClipperOffset::Execute(double delta, PolyTree64& polytree)
+{
+	polytree.Clear();
+
+	ExecuteInternal(delta);
+	if (!solution.size()) return;
+
+	bool paths_reversed = CheckReverseOrientation();
+	//clean up self-intersections ...
+	Clipper64 c;
+	c.PreserveCollinear(false);
+	//the solution should retain the orientation of the input
+	c.ReverseSolution (reverse_solution_ != paths_reversed);
+#ifdef USINGZ
+	if (zCallback64_) {
+		c.SetZCallback(zCallback64_);
 	}
-	return solution;
+#endif
+	c.AddSubject(solution);
+
+
+	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

+ 621 - 129
polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.rectclip.cpp

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  16 November 2022                                                *
+* Date      :  8 September 2023                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  FAST rectangular clipping                                       *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -17,15 +17,22 @@ namespace Clipper2Lib {
   // Miscellaneous methods
   //------------------------------------------------------------------------------
 
-  inline PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2)
+  inline bool Path1ContainsPath2(const Path64& path1, const Path64& path2)
   {
-    PointInPolygonResult result = PointInPolygonResult::IsOn;
-    for(const Point64& pt : path2)
+    int io_count = 0;
+    // precondition: no (significant) overlap
+    for (const Point64& pt : path2)
     {
-      result = PointInPolygon(pt, path1);
-      if (result != PointInPolygonResult::IsOn) break;
+      PointInPolygonResult pip = PointInPolygon(pt, path1);
+      switch (pip)
+      {
+      case PointInPolygonResult::IsOutside: ++io_count; break;
+      case PointInPolygonResult::IsInside: --io_count; break;
+      default: continue;
+      }
+      if (std::abs(io_count) > 1) break;
     }
-    return result;
+    return io_count <= 0;
   }
 
   inline bool GetLocation(const Rect64& rec,
@@ -59,6 +66,56 @@ namespace Clipper2Lib {
     return true;
   }
 
+  inline bool IsHorizontal(const Point64& pt1, const Point64& pt2)
+  {
+    return pt1.y == pt2.y;
+  }
+
+  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 GetSegmentIntersectPt(p1, p2, p3, p4, ip);
+  }
+
   inline bool GetIntersection(const Path64& rectPath,
     const Point64& p, const Point64& p2, Location& loc, Point64& ip)
   {
@@ -67,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)
@@ -180,7 +221,7 @@ namespace Clipper2Lib {
   }
 
   inline bool IsClockwise(Location prev, Location curr,
-    Point64 prev_pt, Point64 curr_pt, Point64 rect_mp)
+    const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
   {
     if (AreOpposites(prev, curr))
       return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
@@ -188,34 +229,147 @@ namespace Clipper2Lib {
       return HeadingClockwise(prev, curr);
   }
 
+  inline OutPt2* UnlinkOp(OutPt2* op)
+  {
+    if (op->next == op) return nullptr;
+    op->prev->next = op->next;
+    op->next->prev = op->prev;
+    return op->next;
+  }
+
+  inline OutPt2* UnlinkOpBack(OutPt2* op)
+  {
+    if (op->next == op) return nullptr;
+    op->prev->next = op->next;
+    op->next->prev = op->prev;
+    return op->prev;
+  }
+
+  inline uint32_t GetEdgesForPt(const Point64& pt, const Rect64& rec)
+  {
+    uint32_t result = 0;
+    if (pt.x == rec.left) result = 1;
+    else if (pt.x == rec.right) result = 4;
+    if (pt.y == rec.top) result += 2;
+    else if (pt.y == rec.bottom) result += 8;
+    return result;
+  }
+
+  inline bool IsHeadingClockwise(const Point64& pt1, const Point64& pt2, int edgeIdx)
+  {
+    switch (edgeIdx)
+    {
+    case 0: return pt2.y < pt1.y;
+    case 1: return pt2.x > pt1.x;
+    case 2: return pt2.y > pt1.y;
+    default: return pt2.x < pt1.x;
+    }
+  }
+
+  inline bool HasHorzOverlap(const Point64& left1, const Point64& right1,
+    const Point64& left2, const Point64& right2)
+  {
+    return (left1.x < right2.x) && (right1.x > left2.x);
+  }
+
+  inline bool HasVertOverlap(const Point64& top1, const Point64& bottom1,
+    const Point64& top2, const Point64& bottom2)
+  {
+    return (top1.y < bottom2.y) && (bottom1.y > top2.y);
+  }
+
+  inline void AddToEdge(OutPt2List& edge, OutPt2* op)
+  {
+    if (op->edge) return;
+    op->edge = &edge;
+    edge.push_back(op);
+  }
+
+  inline void UncoupleEdge(OutPt2* op)
+  {
+    if (!op->edge) return;
+    for (size_t i = 0; i < op->edge->size(); ++i)
+    {
+      OutPt2* op2 = (*op->edge)[i];
+      if (op2 == op)
+      {
+        (*op->edge)[i] = nullptr;
+        break;
+      }
+    }
+    op->edge = nullptr;
+  }
+
+  inline void SetNewOwner(OutPt2* op, size_t new_idx)
+  {
+    op->owner_idx = new_idx;
+    OutPt2* op2 = op->next;
+    while (op2 != op)
+    {
+      op2->owner_idx = new_idx;
+      op2 = op2->next;
+    }
+  }
+
   //----------------------------------------------------------------------------
   // RectClip64
   //----------------------------------------------------------------------------
 
-  void RectClip::AddCorner(Location prev, Location curr)
+  OutPt2* RectClip64::Add(Point64 pt, bool start_new)
+  {
+    // this method is only called by InternalExecute.
+    // Later splitting & rejoining won't create additional op's,
+    // though they will change the (non-storage) results_ count.
+    int curr_idx = static_cast<int>(results_.size()) - 1;
+    OutPt2* result;
+    if (curr_idx < 0 || start_new)
+    {
+      result = &op_container_.emplace_back(OutPt2());
+      result->pt = pt;
+      result->next = result;
+      result->prev = result;
+      results_.push_back(result);
+    }
+    else
+    {
+      OutPt2* prevOp = results_[curr_idx];
+      if (prevOp->pt == pt)  return prevOp;
+      result = &op_container_.emplace_back(OutPt2());
+      result->owner_idx = curr_idx;
+      result->pt = pt;
+      result->next = prevOp->next;
+      prevOp->next->prev = result;
+      prevOp->next = result;
+      result->prev = prevOp;
+      results_[curr_idx] = result;
+    }
+    return result;
+  }
+
+  void RectClip64::AddCorner(Location prev, Location curr)
   {
     if (HeadingClockwise(prev, curr))
-      result_.push_back(rectPath_[static_cast<int>(prev)]);
+      Add(rect_as_path_[static_cast<int>(prev)]);
     else
-      result_.push_back(rectPath_[static_cast<int>(curr)]);
+      Add(rect_as_path_[static_cast<int>(curr)]);
   }
 
-  void RectClip::AddCorner(Location& loc, bool isClockwise)
+  void RectClip64::AddCorner(Location& loc, bool isClockwise)
   {
     if (isClockwise)
     {
-      result_.push_back(rectPath_[static_cast<int>(loc)]);
+      Add(rect_as_path_[static_cast<int>(loc)]);
       loc = GetAdjacentLocation(loc, true);
     }
     else
     {
       loc = GetAdjacentLocation(loc, false);
-      result_.push_back(rectPath_[static_cast<int>(loc)]);
+      Add(rect_as_path_[static_cast<int>(loc)]);
     }
   }
 
-  void RectClip::GetNextLocation(const Path64& path,
-      Location& loc, int& i, int highI)
+  void RectClip64::GetNextLocation(const Path64& path,
+    Location& loc, int& i, int highI)
   {
     switch (loc)
     {
@@ -262,19 +416,15 @@ namespace Clipper2Lib {
         else if (path[i].x > rect_.right) loc = Location::Right;
         else if (path[i].y > rect_.bottom) loc = Location::Bottom;
         else if (path[i].y < rect_.top) loc = Location::Top;
-        else { result_.push_back(path[i]); ++i; continue; }
+        else { Add(path[i]); ++i; continue; }
         break; //inner loop
       }
       break;
-    } //switch          
+    } //switch
   }
 
-  Path64 RectClip::Execute(const Path64& path)
+  void RectClip64::ExecuteInternal(const Path64& path)
   {
-    if (rect_.IsEmpty() || path.size() < 3) return Path64();
-
-    result_.clear();
-    start_locs_.clear();
     int i = 0, highI = static_cast<int>(path.size()) - 1;
     Location prev = Location::Inside, loc;
     Location crossing_loc = Location::Inside;
@@ -283,11 +433,16 @@ namespace Clipper2Lib {
     {
       i = highI - 1;
       while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i;
-      if (i < 0) return path;
+      if (i < 0)
+      {
+        // all of path must be inside fRect
+        for (const auto& pt : path) Add(pt);
+        return;
+      }
       if (prev == Location::Inside) loc = Location::Inside;
       i = 0;
     }
-    Location starting_loc = loc;
+    Location startingLoc = loc;
 
     ///////////////////////////////////////////////////
     while (i <= highI)
@@ -299,25 +454,27 @@ namespace Clipper2Lib {
 
       if (i > highI) break;
       Point64 ip, ip2;
-      Point64 prev_pt = (i) ? path[static_cast<size_t>(i - 1)] : path[highI];
+      Point64 prev_pt = (i) ?
+        path[static_cast<size_t>(i - 1)] :
+        path[highI];
 
       crossing_loc = loc;
-      if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip))
+      if (!GetIntersection(rect_as_path_,
+        path[i], prev_pt, crossing_loc, ip))
       {
         // ie remaining outside
-
         if (crossing_prev == Location::Inside)
         {
-          bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
+          bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_);
           do {
             start_locs_.push_back(prev);
             prev = GetAdjacentLocation(prev, isClockw);
           } while (prev != loc);
-          crossing_loc = crossing_prev; // still not crossed 
+          crossing_loc = crossing_prev; // still not crossed
         }
         else if (prev != Location::Inside && prev != loc)
         {
-          bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
+          bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_);
           do {
             AddCorner(prev, isClockw);
           } while (prev != loc);
@@ -339,7 +496,7 @@ namespace Clipper2Lib {
         }
         else if (prev != crossing_loc)
         {
-          bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], mp_);
+          bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], rect_mp_);
           do {
             AddCorner(prev, isClockw);
           } while (prev != crossing_loc);
@@ -347,11 +504,11 @@ namespace Clipper2Lib {
       }
       else if (prev != Location::Inside)
       {
-        // passing right through rect. 'ip' here will be the second 
+        // passing right through rect. 'ip' here will be the second
         // intersect pt but we'll also need the first intersect pt (ip2)
         loc = prev;
-        GetIntersection(rectPath_, prev_pt, path[i], loc, ip2);
-        if (crossing_prev != Location::Inside)
+        GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2);
+        if (crossing_prev != Location::Inside && crossing_prev != loc) //579
           AddCorner(crossing_prev, loc);
 
         if (first_cross_ == Location::Inside)
@@ -361,7 +518,7 @@ namespace Clipper2Lib {
         }
 
         loc = crossing_loc;
-        result_.push_back(ip2);
+        Add(ip2);
         if (ip == ip2)
         {
           // it's very likely that path[i] is on rect
@@ -378,23 +535,33 @@ namespace Clipper2Lib {
           first_cross_ = crossing_loc;
       }
 
-      result_.push_back(ip);
+      Add(ip);
 
     } //while i <= highI
     ///////////////////////////////////////////////////
 
     if (first_cross_ == Location::Inside)
     {
-      if (starting_loc == Location::Inside) return path;
-      Rect64 tmp_rect = Bounds(path);
-      if (tmp_rect.Contains(rect_) &&
-        Path1ContainsPath2(path, rectPath_) !=
-        PointInPolygonResult::IsOutside) return rectPath_;
-      else
-        return Path64();
+      // path never intersects
+      if (startingLoc != Location::Inside)
+      {
+        // path is outside rect
+        // but being outside, it still may not contain rect
+        if (path_bounds_.Contains(rect_) &&
+          Path1ContainsPath2(path, rect_as_path_))
+        {
+          // yep, the path does fully contain rect
+          // so add rect to the solution
+          for (size_t j = 0; j < 4; ++j)
+          {
+            Add(rect_as_path_[j]);
+            // we may well need to do some splitting later, so
+            AddToEdge(edges_[j * 2], results_[0]);
+          }
+        }
+      }
     }
-
-    if (loc != Location::Inside &&
+    else if (loc != Location::Inside &&
       (loc != first_cross_ || start_locs_.size() > 2))
     {
       if (start_locs_.size() > 0)
@@ -411,54 +578,370 @@ namespace Clipper2Lib {
       if (loc != first_cross_)
         AddCorner(loc, HeadingClockwise(loc, first_cross_));
     }
+  }
+
+  void RectClip64::CheckEdges()
+  {
+    for (size_t i = 0; i < results_.size(); ++i)
+    {
+      OutPt2* op = results_[i];
+      if (!op) continue;
+      OutPt2* op2 = op;
+      do
+      {
+        if (!CrossProduct(op2->prev->pt,
+          op2->pt, op2->next->pt))
+        {
+          if (op2 == op)
+          {
+            op2 = UnlinkOpBack(op2);
+            if (!op2) break;
+            op = op2->prev;
+          }
+          else
+          {
+            op2 = UnlinkOpBack(op2);
+            if (!op2) break;
+          }
+        }
+        else
+          op2 = op2->next;
+      } while (op2 != op);
+
+      if (!op2)
+      {
+        results_[i] = nullptr;
+        continue;
+      }
+      results_[i] = op; // safety first
+
+      uint32_t edgeSet1 = GetEdgesForPt(op->prev->pt, rect_);
+      op2 = op;
+      do
+      {
+        uint32_t edgeSet2 = GetEdgesForPt(op2->pt, rect_);
+        if (edgeSet2 && !op2->edge)
+        {
+          uint32_t combinedSet = (edgeSet1 & edgeSet2);
+          for (int j = 0; j < 4; ++j)
+          {
+            if (combinedSet & (1 << j))
+            {
+              if (IsHeadingClockwise(op2->prev->pt, op2->pt, j))
+                AddToEdge(edges_[j * 2], op2);
+              else
+                AddToEdge(edges_[j * 2 + 1], op2);
+            }
+          }
+        }
+        edgeSet1 = edgeSet2;
+        op2 = op2->next;
+      } while (op2 != op);
+    }
+  }
+
+  void RectClip64::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw)
+  {
+    if (ccw.empty()) return;
+    bool isHorz = ((idx == 1) || (idx == 3));
+    bool cwIsTowardLarger = ((idx == 1) || (idx == 2));
+    size_t i = 0, j = 0;
+    OutPt2* p1, * p2, * p1a, * p2a, * op, * op2;
+
+    while (i < cw.size())
+    {
+      p1 = cw[i];
+      if (!p1 || p1->next == p1->prev)
+      {
+        cw[i++] = nullptr;
+        j = 0;
+        continue;
+      }
+
+      size_t jLim = ccw.size();
+      while (j < jLim &&
+        (!ccw[j] || ccw[j]->next == ccw[j]->prev)) ++j;
+
+      if (j == jLim)
+      {
+        ++i;
+        j = 0;
+        continue;
+      }
+
+      if (cwIsTowardLarger)
+      {
+        // p1 >>>> p1a;
+        // p2 <<<< p2a;
+        p1 = cw[i]->prev;
+        p1a = cw[i];
+        p2 = ccw[j];
+        p2a = ccw[j]->prev;
+      }
+      else
+      {
+        // p1 <<<< p1a;
+        // p2 >>>> p2a;
+        p1 = cw[i];
+        p1a = cw[i]->prev;
+        p2 = ccw[j]->prev;
+        p2a = ccw[j];
+      }
+
+      if ((isHorz && !HasHorzOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)) ||
+        (!isHorz && !HasVertOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)))
+      {
+        ++j;
+        continue;
+      }
+
+      // to get here we're either splitting or rejoining
+      bool isRejoining = cw[i]->owner_idx != ccw[j]->owner_idx;
+
+      if (isRejoining)
+      {
+        results_[p2->owner_idx] = nullptr;
+        SetNewOwner(p2, p1->owner_idx);
+      }
+
+      // do the split or re-join
+      if (cwIsTowardLarger)
+      {
+        // p1 >> | >> p1a;
+        // p2 << | << p2a;
+        p1->next = p2;
+        p2->prev = p1;
+        p1a->prev = p2a;
+        p2a->next = p1a;
+      }
+      else
+      {
+        // p1 << | << p1a;
+        // p2 >> | >> p2a;
+        p1->prev = p2;
+        p2->next = p1;
+        p1a->next = p2a;
+        p2a->prev = p1a;
+      }
+
+      if (!isRejoining)
+      {
+        size_t new_idx = results_.size();
+        results_.push_back(p1a);
+        SetNewOwner(p1a, new_idx);
+      }
+
+      if (cwIsTowardLarger)
+      {
+        op = p2;
+        op2 = p1a;
+      }
+      else
+      {
+        op = p1;
+        op2 = p2a;
+      }
+      results_[op->owner_idx] = op;
+      results_[op2->owner_idx] = op2;
+
+      // and now lots of work to get ready for the next loop
+
+      bool opIsLarger, op2IsLarger;
+      if (isHorz) // X
+      {
+        opIsLarger = op->pt.x > op->prev->pt.x;
+        op2IsLarger = op2->pt.x > op2->prev->pt.x;
+      }
+      else       // Y
+      {
+        opIsLarger = op->pt.y > op->prev->pt.y;
+        op2IsLarger = op2->pt.y > op2->prev->pt.y;
+      }
+
+      if ((op->next == op->prev) ||
+        (op->pt == op->prev->pt))
+      {
+        if (op2IsLarger == cwIsTowardLarger)
+        {
+          cw[i] = op2;
+          ccw[j++] = nullptr;
+        }
+        else
+        {
+          ccw[j] = op2;
+          cw[i++] = nullptr;
+        }
+      }
+      else if ((op2->next == op2->prev) ||
+        (op2->pt == op2->prev->pt))
+      {
+        if (opIsLarger == cwIsTowardLarger)
+        {
+          cw[i] = op;
+          ccw[j++] = nullptr;
+        }
+        else
+        {
+          ccw[j] = op;
+          cw[i++] = nullptr;
+        }
+      }
+      else if (opIsLarger == op2IsLarger)
+      {
+        if (opIsLarger == cwIsTowardLarger)
+        {
+          cw[i] = op;
+          UncoupleEdge(op2);
+          AddToEdge(cw, op2);
+          ccw[j++] = nullptr;
+        }
+        else
+        {
+          cw[i++] = nullptr;
+          ccw[j] = op2;
+          UncoupleEdge(op);
+          AddToEdge(ccw, op);
+          j = 0;
+        }
+      }
+      else
+      {
+        if (opIsLarger == cwIsTowardLarger)
+          cw[i] = op;
+        else
+          ccw[j] = op;
+        if (op2IsLarger == cwIsTowardLarger)
+          cw[i] = op2;
+        else
+          ccw[j] = op2;
+      }
+    }
+  }
 
-    if (result_.size() < 3) return Path64();
+  Path64 RectClip64::GetPath(OutPt2*& op)
+  {
+    if (!op || op->next == op->prev) return Path64();
 
-    // tidy up duplicates and collinear segments
-    Path64 res;
-    res.reserve(result_.size());
-    size_t k = 0; highI = static_cast<int>(result_.size()) - 1;
-    Point64 prev_pt = result_[highI];
-    res.push_back(result_[0]);
-    Path64::const_iterator cit;
-    for (cit = result_.cbegin() + 1; cit != result_.cend(); ++cit)
+    OutPt2* op2 = op->next;
+    while (op2 && op2 != op)
     {
-      if (CrossProduct(prev_pt, res[k], *cit))
+      if (CrossProduct(op2->prev->pt,
+        op2->pt, op2->next->pt) == 0)
       {
-        prev_pt = res[k++];
-        res.push_back(*cit);
+        op = op2->prev;
+        op2 = UnlinkOp(op2);
       }
       else
-        res[k] = *cit;
+        op2 = op2->next;
     }
+    op = op2; // needed for op cleanup
+    if (!op2) return Path64();
+
+    Path64 result;
+    result.push_back(op->pt);
+    op2 = op->next;
+    while (op2 != op)
+    {
+      result.push_back(op2->pt);
+      op2 = op2->next;
+    }
+    return result;
+  }
+
+  Paths64 RectClip64::Execute(const Paths64& paths)
+  {
+    Paths64 result;
+    if (rect_.IsEmpty()) return result;
+
+    for (const Path64& path : paths)
+    {
+      if (path.size() < 3) continue;
+      path_bounds_ = GetBounds(path);
+      if (!rect_.Intersects(path_bounds_))
+        continue; // the path must be completely outside rect_
+      else if (rect_.Contains(path_bounds_))
+      {
+        // the path must be completely inside rect_
+        result.push_back(path);
+        continue;
+      }
+
+      ExecuteInternal(path);
+      CheckEdges();
+      for (int i = 0; i < 4; ++i)
+        TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]);
+
+      for (OutPt2*& op :  results_)
+      {
+        Path64 tmp = GetPath(op);
+        if (!tmp.empty())
+          result.emplace_back(tmp);
+      }
 
-    if (k < 2) return Path64();
-    // and a final check for collinearity
-    else if (!CrossProduct(res[0], res[k - 1], res[k])) res.pop_back();
-    return res;
+      //clean up after every loop
+      op_container_ = std::deque<OutPt2>();
+      results_.clear();
+      for (OutPt2List &edge : edges_) edge.clear();
+      start_locs_.clear();
+    }
+    return result;
   }
 
-  Paths64 RectClipLines::Execute(const Path64& path)
+  //------------------------------------------------------------------------------
+  // RectClipLines64
+  //------------------------------------------------------------------------------
+
+  Paths64 RectClipLines64::Execute(const Paths64& paths)
   {
-    result_.clear();
     Paths64 result;
-    if (rect_.IsEmpty() || path.size() == 0) return result;
+    if (rect_.IsEmpty()) return result;
+
+    for (const auto& path : paths)
+    {
+      Rect64 pathrec = GetBounds(path);
+      if (!rect_.Intersects(pathrec)) continue;
+
+      ExecuteInternal(path);
+
+      for (OutPt2*& op : results_)
+      {
+        Path64 tmp = GetPath(op);
+        if (!tmp.empty())
+          result.emplace_back(tmp);
+      }
+      results_.clear();
+
+      op_container_ = std::deque<OutPt2>();
+      start_locs_.clear();
+    }
+    return result;
+  }
+
+  void RectClipLines64::ExecuteInternal(const Path64& path)
+  {
+    if (rect_.IsEmpty() || path.size() < 2) return;
+
+    results_.clear();
+    op_container_ = std::deque<OutPt2>();
+    start_locs_.clear();
 
     int i = 1, highI = static_cast<int>(path.size()) - 1;
 
     Location prev = Location::Inside, loc;
-    Location crossing_loc = Location::Inside;
+    Location crossing_loc;
     if (!GetLocation(rect_, path[0], loc))
     {
       while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i;
-      if (i > highI) {
-        result.push_back(path);
-        return result;
+      if (i > highI)
+      {
+        // all of path must be inside fRect
+        for (const auto& pt : path) Add(pt);
+        return;
       }
       if (prev == Location::Inside) loc = Location::Inside;
       i = 1;
     }
-    if (loc == Location::Inside) result_.push_back(path[0]);
+    if (loc == Location::Inside) Add(path[0]);
 
     ///////////////////////////////////////////////////
     while (i <= highI)
@@ -470,7 +953,8 @@ namespace Clipper2Lib {
       Point64 prev_pt = path[static_cast<size_t>(i - 1)];
 
       crossing_loc = loc;
-      if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip))
+      if (!GetIntersection(rect_as_path_,
+        path[i], prev_pt, crossing_loc, ip))
       {
         // ie remaining outside
         ++i;
@@ -483,30 +967,38 @@ namespace Clipper2Lib {
 
       if (loc == Location::Inside) // path must be entering rect
       {
-        result_.push_back(ip);
+        Add(ip, true);
       }
       else if (prev != Location::Inside)
       {
-        // passing right through rect. 'ip' here will be the second 
+        // passing right through rect. 'ip' here will be the second
         // intersect pt but we'll also need the first intersect pt (ip2)
         crossing_loc = prev;
-        GetIntersection(rectPath_, prev_pt, path[i], crossing_loc, ip2);
-        result_.push_back(ip2);
-        result_.push_back(ip);
-        result.push_back(result_);
-        result_.clear();
+        GetIntersection(rect_as_path_,
+          prev_pt, path[i], crossing_loc, ip2);
+        Add(ip2, true);
+        Add(ip);
       }
       else // path must be exiting rect
       {
-        result_.push_back(ip);
-        result.push_back(result_);
-        result_.clear();
+        Add(ip);
       }
     } //while i <= highI
     ///////////////////////////////////////////////////
+  }
 
-    if (result_.size() > 1)
-      result.push_back(result_);
+  Path64 RectClipLines64::GetPath(OutPt2*& op)
+  {
+    Path64 result;
+    if (!op || op == op->next) return result;
+    op = op->next; // starting at path beginning
+    result.push_back(op->pt);
+    OutPt2 *op2 = op->next;
+    while (op2 != op)
+    {
+      result.push_back(op2->pt);
+      op2 = op2->next;
+    }
     return result;
   }
 

+ 133 - 0
polygon.mod/clipper2/CPP/Examples/Benchmarks/Benchmarks.cpp

@@ -0,0 +1,133 @@
+#include <cstdlib>
+#include <string>
+#include <chrono> 
+ 
+#include "clipper2/clipper.h"
+#include "../../Utils/clipper.svg.utils.h"
+#include "../../Utils/ClipFileLoad.h"
+#include "../../Utils/ClipFileSave.h"
+#include "../../Utils/Timer.h"
+
+using namespace Clipper2Lib;
+
+const int display_width = 800, display_height = 600;
+
+void RecheckLastBenchmark(bool use_polytree);
+void DoBenchmark(int edge_cnt_start, int edge_cnt_end, 
+  int increment, bool test_polytree = false);
+Path64 MakeRandomPoly(int width, int height, unsigned vertCnt);
+void System(const std::string &filename);
+
+int main()
+{  
+  bool test_polytree = false;//true;// 
+  srand((unsigned)time(0));
+  DoBenchmark(1000, 7000, 1000, test_polytree);
+  //RecheckLastBenchmark(test_polytree);
+
+  std::cout << std::endl;
+//#ifdef _DEBUG
+  std::string s;
+  std::cout << "Press Enter to continue" << std::endl;
+  std::getline(std::cin, s);
+//#endif
+  return 0;
+}
+
+void RecheckLastBenchmark(bool use_polytree)
+{
+  ClipType ct;
+  FillRule fr;
+
+  Paths64 subject, subj_open, clip, solution;
+  int64_t area, count;
+  PolyTree64 polytree;
+
+  std::ifstream test("benchmark_test.txt");
+  if (!test.good())
+  {
+    std::cout << "Unable to find or open test file." << std::endl;
+    return;
+  }
+
+  bool success = false;
+  std::cout << "Checking last benchmark ..." << std::endl;
+  LoadTestNum(test, 1, subject, subj_open, clip, area, count, ct, fr);
+  if (use_polytree)
+  {
+    BooleanOp(ct, fr, subject, clip, polytree);
+    success = polytree.Count();
+  }
+  else
+  {
+    solution = BooleanOp(ct, fr, subject, clip);
+    success = solution.size();
+  }
+  if (success)
+    std::cout << "It's OK." << std::endl;
+  else
+    std::cout << "It failed (again)." << std::endl;
+}
+
+inline Path64 MakeRandomPoly(int width, int height, unsigned vertCnt)
+{
+  Path64 result;
+  result.reserve(vertCnt);
+  for (unsigned i = 0; i < vertCnt; ++i)
+    result.push_back(Point64(rand() % width, rand() % height));
+  return result;
+}
+
+void DoBenchmark(int edge_cnt_start, int edge_cnt_end, 
+  int increment, bool test_polytree)
+{
+  ClipType ct = ClipType::Intersection;
+  FillRule fr = FillRule::NonZero;//EvenOdd;//Positive;//
+
+  Paths64 subject, clip, solution;
+  PolyTree64 polytree;
+  std::cout << std::endl << "Complex Polygons Benchmark:  " << std::endl;
+  for (int i = edge_cnt_start; i <= edge_cnt_end; i += increment)
+  {
+    subject.clear();
+    clip.clear();
+    subject.push_back(MakeRandomPoly(800, 600, i));
+    clip.push_back(MakeRandomPoly(800, 600, i));
+    SaveTest( "benchmark_test.txt", false, &subject, nullptr, &clip, 0, 0, ct, fr);
+
+    std::cout << "Edge Count: " << i << " = ";
+    {
+      if (test_polytree)
+      {
+        polytree.Clear();
+        Timer t;
+        BooleanOp(ct, fr, subject, clip, polytree);
+        if (!polytree.Count()) break;
+      }
+      else
+      {
+        Timer t;
+        solution = BooleanOp(ct, fr, subject, clip);
+        if (solution.empty()) break;
+      }
+    }
+  }
+
+  if (test_polytree) solution = PolyTreeToPaths64(polytree);
+    
+  SvgWriter svg;
+  SvgAddSubject(svg, subject, fr);
+  SvgAddClip(svg, clip, fr);
+  SvgAddSolution(svg, solution, fr, false);
+  SvgSaveToFile(svg, "solution3.svg", display_width, display_height, 20);
+  System("solution3.svg");
+}
+
+void System(const std::string &filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}

+ 0 - 210
polygon.mod/clipper2/CPP/Examples/ConsoleDemo1/ConsoleDemo1.cpp

@@ -1,210 +0,0 @@
-#include <cstdlib>
-#include <string>
-#include <chrono> 
-
-#include "clipper2/clipper.h"
-#include "../../Utils/clipper.svg.utils.h"
-#include "../../Utils/ClipFileLoad.h"
-#include "../../Utils/ClipFileSave.h"
-#include "../../Utils/Timer.h"
-
-using namespace Clipper2Lib;
-
-const int display_width = 800, display_height = 600;
-enum class TestType { All, Simple, Benchmark, MemoryLeak};
-
-Path64 MakeRandomPoly(int width, int height, unsigned vertCnt);
-void DoSimpleTest(bool show_solution_coords = false);
-void DoBenchmark(int edge_cnt_start, int edge_cnt_end, int increment);
-void DoMemoryLeakTest();
-void System(const std::string &filename);
-
-int main()
-{
-  std::cout.imbue(std::locale(""));
-  srand((unsigned)time(0));
-
-  //////////////////////////////////////////////////////////////////////////
-  //test_type options:  Simple; Benchmark; All; MemoryLeak; 
-  TestType test_type = TestType::Simple;
-  //////////////////////////////////////////////////////////////////////////
-
-  switch (test_type)
-  {
-  case TestType::All:
-
-  case TestType::Simple:
-    DoSimpleTest();
-    if (test_type == TestType::Simple) break;
-
-  case TestType::Benchmark:
-    std::cout << std::endl << "==========" << std::endl;
-    std::cout << "Benchmarks" << std::endl;
-    std::cout << "==========" << std::endl;
-    DoBenchmark(1000, 5000, 1000);
-    if (test_type == TestType::Benchmark) break;
-
-  case TestType::MemoryLeak:
-    DoMemoryLeakTest();
-    break;
-
-  }
-
-  std::cout << std::endl;
-#ifdef _DEBUG
-  std::string s;
-  std::cout << "Press Enter to continue" << std::endl;
-  std::getline(std::cin, s);
-#endif
-  return 0;
-}
-
-
-inline Path64 MakeRandomPoly(int width, int height, unsigned vertCnt)
-{
-  Path64 result;
-  result.reserve(vertCnt);
-  for (unsigned i = 0; i < vertCnt; ++i)
-    result.push_back(Point64(rand() % width, rand() % height));
-  return result;
-}
-
-inline Path64 MakeStar(const Point64& center, int radius, int points)
-{
-  if (!(points % 2)) --points;
-  if (points < 5) points = 5;
-  Path64 tmp = Ellipse<int64_t>(center, radius, radius, points);
-  Path64 result; 
-  result.reserve(points);
-  result.push_back(tmp[0]);
-  for (int i = points -1, j = i / 2; j;)
-  {
-    result.push_back(tmp[j--]);
-    result.push_back(tmp[i--]);
-  }
-  return result;
-}
-
-void DoSimpleTest(bool show_solution_coords)
-{
-  Paths64 tmp, solution;
-  FillRule fr = FillRule::NonZero;
-
-  //SVG IMAGE #1
-  //Use Minkowski to draw a stylised "C2"
-  
-  SvgWriter svg;
-  //create a circular shape to use as the 'paint brush' shape
-  Path64 pattern = Ellipse<int64_t>(Point64(), 25, 25);
-  //or alternatively create a diagonal brush shape
-  //Path64 pattern = MakePath("-13,20,  -11,26, 13,-20  11,-26");
-
-  //design "C2" as the drawing path
-  //first design "C" 
-  Path64 path = Ellipse<int64_t>(Point64(240,225), 200, 200);
-  path.erase(path.begin(), path.begin() + 4);
-  path.erase(path.end() - 4, path.end());
-  //use MinkowskiSum to 'draw' the "C" using the pattern
-  solution = MinkowskiSum(pattern, path, false);
-  SvgAddSolution(svg, solution, fr, false);
-  //and design "2"
-  path = Ellipse<int64_t>(Point64(240, 180), 75, 65);
-  std::rotate(path.begin(), path.begin() + 6, path.end());
-  path.erase(path.begin(), path.begin() +9);
-  path.push_back(Point64(190, 249));
-  path.push_back(Point64(190, 325));
-  path.push_back(Point64(315, 325));
-  //use MinkowskiSum to 'draw' the "2" using the pattern
-  solution = MinkowskiSum(pattern, path, false);
-  //save and display
-  SvgAddSolution(svg, solution, fr, false);
-  SvgSaveToFile(svg, "solution1.svg", 450, 450, 10);
-  System("solution1.svg");
-
-  //SVG IMAGE #2
-  //intersect a 9 point star with a circle
-
-  Paths64 subject, clip;
-  subject.push_back(MakeStar(Point64(225, 225), 220, 9));
-  clip.push_back(Ellipse<int64_t>(Point64(225,225), 150, 150));  
-  //SaveTest("debug.txt", false, &subject, NULL, &clip, 0, 0, ClipType::Intersection, fr);
-  
-  //Intersect both shapes and then 'inflate' result -10 (ie deflate)
-  solution = Intersect(subject, clip, fr);
-  solution = InflatePaths(solution, -10, JoinType::Round, EndType::Polygon);
-
-  SvgWriter svg2;
-  SvgAddSubject(svg2, subject, fr);
-  SvgAddClip(svg2, clip, fr);
-  SvgAddSolution(svg2, solution, fr, false);
-  SvgSaveToFile(svg2, "solution2.svg", 450, 450, 10);
-  System("solution2.svg");
-}
-
-void DoBenchmark(int edge_cnt_start, int edge_cnt_end, int increment)
-{
-  ClipType ct = ClipType::Intersection;
-  FillRule fr = FillRule::NonZero;//EvenOdd;//Positive;//
-
-  Paths64 subject, clip, solution;
-
-  std::cout << std::endl << "Complex Polygons Benchmark:  " << std::endl;
-  for (int i = edge_cnt_start; i <= edge_cnt_end; i += increment)
-  {
-    subject.clear();
-    clip.clear();
-    subject.push_back(MakeRandomPoly(800, 600, i));
-    clip.push_back(MakeRandomPoly(800, 600, i));
-    //SaveToFile("benchmark_test.txt", subject, clip, ct_benchmark, fr_benchmark);
-
-    std::cout << "Edge Count: " << i << " = ";
-    {
-      Timer t;
-      solution = BooleanOp(ct, fr, subject, clip);
-      if (solution.empty()) break;
-    }
-  }
-
-  SvgWriter svg;
-  SvgAddSubject(svg, subject, fr);
-  SvgAddClip(svg, clip, fr);
-  SvgAddSolution(svg, solution, fr, false);
-  SvgSaveToFile(svg, "solution3.svg", display_width, display_height, 20);
-  System("solution3.svg");
-}
-
-void DoMemoryLeakTest()
-{
-#ifdef _WIN32
-  int edge_cnt = 1000;
-
-  Paths64 subject, clip;
-  subject.push_back(MakeRandomPoly(800, 600, edge_cnt));
-  clip.push_back(MakeRandomPoly(800, 600, edge_cnt));
-
-  _CrtMemState sOld {}, sNew {}, sDiff {};
-  _CrtMemCheckpoint(&sOld); //take a snapshot
-  {
-    Paths64 solution = Intersect(subject, clip, FillRule::NonZero);
-  }
-  _CrtMemCheckpoint(&sNew); //take another snapshot (outside code block)
-  if (_CrtMemDifference(&sDiff, &sOld, &sNew)) // check for a difference
-  {
-    std::cout << std::endl << "Memory leaks!" << std::endl;
-    //_CrtMemDumpStatistics(&sDiff);
-  }
-  else
-  {
-    std::cout << std::endl << "No memory leaks detected :)" << std::endl << std::endl;
-  }
-#endif
-}
-
-void System(const std::string &filename)
-{
-#ifdef _WIN32
-  system(filename.c_str());
-#else
-  system(("firefox " + filename).c_str());
-#endif
-}

+ 0 - 92
polygon.mod/clipper2/CPP/Examples/ConsoleDemo2/ConsoleDemo2.cpp

@@ -1,92 +0,0 @@
-#include <cstdlib>
-#include <string>
-#include <chrono> 
-
-#include "clipper2/clipper.h"
-#include "../../Utils/clipper.svg.utils.h"
-#include "../../Utils/ClipFileLoad.h"
-#include "../../Utils/ClipFileSave.h"
-#include "../../Utils/Timer.h"
-
-using namespace Clipper2Lib;
-
-// forward declarations
-void DoDiamonds();
-void DoCircles();
-void System(const std::string &filename);
-
-int main()
-{
-  std::cout.imbue(std::locale(""));
-  srand((unsigned)time(0));
-
-  std::cout << std::endl << "This should take less than a minute ... " << std::endl;
-  DoDiamonds();
-  std::cout << std::endl << "And now the next test ... " << std::endl;
-  DoCircles();
-#ifdef _DEBUG
-  std::string s;
-  std::cout  << std::endl << "All done. Press Enter to continue" << std::endl;
-  std::getline(std::cin, s);
-#endif
-  return 0;
-}
-
-
-void DoDiamonds()
-{
-  Path64 shape = MakePath("5,0, 10,5, 5,10, 0,5");
-  Paths64 subjects, solution;
-  int w = 900, h = 600;
-  for (int i = 0; i < h; i += 2)
-  {
-    for (int j = 0; j < w; ++j)
-    {
-      // move right 5 & up or down 5 ...
-      shape = TranslatePath(shape, 5, (j & 1) == 0 ? 5 : -5);
-      // skip random diamonds ...
-      if (rand() % 4 != 1)
-        subjects.push_back(shape);
-    }
-    shape = TranslatePath(shape, -w * 5, 10);
-  }
-  solution = Union(subjects, FillRule::NonZero);
-  // tidy up the solution
-  //solution = Union(solution, FillRule::NonZero);
-
-  SvgWriter svg;
-  SvgAddSolution(svg, solution, FillRule::NonZero, false);
-  SvgSaveToFile(svg, "solution1.svg", 900, 600, 10);
-  System("solution1.svg");
-}
-
-void DoCircles()
-{
-  // create a small pseudo circle that has 11 edges
-  PathD shape = Ellipse(RectD(0, 0, 10, 10), 11);
-
-  PathsD subjects, solution;
-  int w = 900, h = 600;
-
-  for (int j = 0; j < 25000; ++j)
-  {
-    // add a copy of shape that's been moved right and down a random amount
-    subjects.push_back(TranslatePath(shape,
-      rand() % (w - 10), rand() % (h - 10)));
-  }
-  solution = Union(subjects, FillRule::NonZero);
-
-  SvgWriter svg;
-  SvgAddSolution(svg, solution, FillRule::NonZero, false);
-  SvgSaveToFile(svg, "solution2.svg", 900, 600, 10);
-  System("solution2.svg");
-}
-
-void System(const std::string &filename)
-{
-#ifdef _WIN32
-  system(filename.c_str());
-#else
-  system(("firefox " + filename).c_str());
-#endif
-}

+ 47 - 34
polygon.mod/clipper2/CPP/Examples/InflateDemo/InflateDemo1.cpp → polygon.mod/clipper2/CPP/Examples/Inflate/Inflate.cpp

@@ -3,49 +3,59 @@
 #include "clipper2/clipper.h"
 #include "../../Utils/clipper.svg.h"
 #include "../../Utils/clipper.svg.utils.h"
-#include "../../Utils/Timer.h"
 
 using namespace std;
 using namespace Clipper2Lib;
 
-void System(const std::string& filename)
+void DoRabbit();
+void DoSimpleShapes();
+void System(const std::string& filename);
+
+
+int main(int argc, char* argv[])
 {
-#ifdef _WIN32
-  system(filename.c_str());
-#else
-  system(("firefox " + filename).c_str());
-#endif
+
+  DoSimpleShapes();
+  DoRabbit();
+  //std::getchar();
 }
 
 void DoSimpleShapes() 
 {
-
-  //open path offsets 
   Paths64 op1, op2;
-
   FillRule fr2 = FillRule::EvenOdd;
   SvgWriter svg2;
-  op1.push_back(MakePath("100,100, 20,20 180,20 180,180, 20,180"));
-  op2 = InflatePaths(op1, 20, JoinType::Square, EndType::Square);
+
+  op1.push_back(MakePath({ 80,60, 20,20, 180,20, 180,70, 25,150, 20,180, 180,180 }));
+  op2 = InflatePaths(op1, 15, JoinType::Miter, EndType::Square, 3);
+  SvgAddOpenSubject(svg2, op1, fr2, false);
+  SvgAddSolution(svg2, TransformPaths<double, int64_t>(op2), fr2, false);
+  SvgAddCaption(svg2, "Miter Joins; Square Ends", 20, 210);
+
+  op1 = TranslatePaths<int64_t>(op1, 210, 0);
+  op2 = InflatePaths(op1, 15, JoinType::Square, EndType::Square);
   SvgAddOpenSubject(svg2, op1, fr2, false);
-  SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false);
+  SvgAddSolution(svg2, TransformPaths<double, int64_t>(op2), fr2, false);
+  SvgAddCaption(svg2, "Square Joins; Square Ends", 230, 210);
 
-  op1 = TranslatePaths(op1, 250, 0);
-  op2 = InflatePaths(op1, 20, JoinType::Miter, EndType::Butt, 5);
+  op1 = TranslatePaths<int64_t>(op1, 210, 0);
+  op2 = InflatePaths(op1, 15, JoinType::Bevel, EndType::Butt, 3);
   SvgAddOpenSubject(svg2, op1, fr2, false);
-  SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false);
+  SvgAddSolution(svg2, TransformPaths<double, int64_t>(op2), fr2, false);
+  SvgAddCaption(svg2, "Bevel Joins; Butt Ends", 440, 210);
 
-  op1 = TranslatePaths(op1, 250, 0);
-  op2 = InflatePaths(op1, 20, JoinType::Round, EndType::Round);
+  op1 = TranslatePaths<int64_t>(op1, 210, 0);
+  op2 = InflatePaths(op1, 15, JoinType::Round, EndType::Round);
   SvgAddOpenSubject(svg2, op1, fr2, false);
-  SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false);
+  SvgAddSolution(svg2, TransformPaths<double, int64_t>(op2), fr2, false);
+  SvgAddCaption(svg2, "Round Joins; Round Ends", 650, 210);
 
   SvgSaveToFile(svg2, "open_paths.svg", 800, 600, 20);
   System("open_paths.svg");
 
   //triangle offset - with large miter
   Paths64 p, pp;
-  p.push_back(MakePath("30, 150, 60, 350, 0, 350"));
+  p.push_back(MakePath({ 30, 150, 60, 350, 0, 350 }));
   pp.insert(pp.end(), p.begin(), p.end());
 
   for (int i = 0; i < 5; ++i)
@@ -58,21 +68,21 @@ void DoSimpleShapes()
 
   //rectangle offset - both squared and rounded
   p.clear();
-  p.push_back(MakePath("100,30, 340,30, 340,230, 100,230"));
+  p.push_back(MakePath({ 100,30, 340,30, 340,230, 100,230 }));
   pp.insert(pp.end(), p.begin(), p.end());
   //nb: using the ClipperOffest class directly here to control 
   //different join types within the same offset operation
   ClipperOffset co;
   co.AddPaths(p, JoinType::Miter, EndType::Joined);
-  p = TranslatePaths(p, 120, 100);
+  p = TranslatePaths<int64_t>(p, 120, 100);
   pp.insert(pp.end(), p.begin(), p.end());
   co.AddPaths(p, JoinType::Round, EndType::Joined);
-  p = co.Execute(20);
+  co.Execute(10, p);
   pp.insert(pp.end(), p.begin(), p.end());
 
-  FillRule fr = FillRule::EvenOdd;
+  FillRule fr3 = FillRule::EvenOdd;
   SvgWriter svg;
-  SvgAddSolution(svg, Paths64ToPathsD(pp), fr, false);
+  SvgAddSolution(svg, TransformPaths<double, int64_t>(pp), fr3, false);
   SvgSaveToFile(svg, "solution_off.svg", 800, 600, 20);
   System("solution_off.svg");
 }
@@ -88,25 +98,28 @@ void DoRabbit()
 
   while (p.size())
   {
-    //nb: don't forget to scale the delta offset too!
+    // nb: don't forget to scale the delta offset too!
     p = InflatePaths(p, -2.5, jt, EndType::Polygon);
-    //RamerDouglasPeucker - not essential but
-    //speeds up the loop and also tidies up the result
-    p = RamerDouglasPeucker(p, 0.025);
+    // SimplifyPaths (or RamerDouglasPeucker) is not 
+    // essential but is highly recommended because it 
+    // speeds up the loop and also tidies up the result
+    p = SimplifyPaths(p, 0.25); // preferred over RDP()
     solution.reserve(solution.size() + p.size());
     copy(p.begin(), p.end(), back_inserter(solution));
   }
 
   FillRule fr = FillRule::EvenOdd;
   SvgWriter svg;
-  SvgAddSolution(svg, solution, fr, false); 
+  SvgAddSolution(svg, solution, fr, false);
   SvgSaveToFile(svg, "solution_off2.svg", 450, 720, 0);
   System("solution_off2.svg");
 }
 
-int main(int argc, char* argv[])
+void System(const std::string& filename)
 {
-  DoSimpleShapes();
-  DoRabbit();
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
 }
-//---------------------------------------------------------------------------

+ 0 - 0
polygon.mod/clipper2/CPP/Examples/InflateDemo/rabbit.svg → polygon.mod/clipper2/CPP/Examples/Inflate/rabbit.svg


+ 74 - 0
polygon.mod/clipper2/CPP/Examples/MemLeakTest/MemLeakTest.cpp

@@ -0,0 +1,74 @@
+#include <cstdlib>
+#include <string>
+#include <chrono> 
+ 
+#include "clipper2/clipper.h"
+
+using namespace Clipper2Lib;
+
+const int display_width = 800, display_height = 600;
+
+void DoMemoryLeakTest();
+Path64 MakeRandomPoly(int width, int height, unsigned vertCnt);
+void System(const std::string &filename);
+
+int main()
+{  
+  std::cout.imbue(std::locale(""));
+  srand((unsigned)time(0));
+  DoMemoryLeakTest();
+
+  std::cout << std::endl;
+//#ifdef _DEBUG
+  std::string s;
+  std::cout << "Press Enter to continue" << std::endl;
+  std::getline(std::cin, s);
+//#endif
+  return 0;
+}
+
+
+inline Path64 MakeRandomPoly(int width, int height, unsigned vertCnt)
+{
+  Path64 result;
+  result.reserve(vertCnt);
+  for (unsigned i = 0; i < vertCnt; ++i)
+    result.push_back(Point64(rand() % width, rand() % height));
+  return result;
+}
+
+void DoMemoryLeakTest()
+{
+#ifdef _WIN32
+  int edge_cnt = 1000;
+
+  Paths64 subject, clip;
+  subject.push_back(MakeRandomPoly(800, 600, edge_cnt));
+  clip.push_back(MakeRandomPoly(800, 600, edge_cnt));
+
+  _CrtMemState sOld {}, sNew {}, sDiff {};
+  _CrtMemCheckpoint(&sOld); //take a snapshot
+  {
+    Paths64 solution = Intersect(subject, clip, FillRule::NonZero);
+  }
+  _CrtMemCheckpoint(&sNew); //take another snapshot (outside code block)
+  if (_CrtMemDifference(&sDiff, &sOld, &sNew)) // check for a difference
+  {
+    std::cout << std::endl << "Memory leaks!" << std::endl;
+    //_CrtMemDumpStatistics(&sDiff);
+  }
+  else
+  {
+    std::cout << std::endl << "No memory leaks detected :)" << std::endl << std::endl;
+  }
+#endif
+}
+
+void System(const std::string &filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}

+ 102 - 0
polygon.mod/clipper2/CPP/Examples/PolygonSamples/PolygonSamples.cpp

@@ -0,0 +1,102 @@
+#include <cstdlib>
+#include <string>
+#include <chrono> 
+#include <random>
+ 
+#include "clipper2/clipper.h"
+#include "../../Utils/clipper.svg.utils.h"
+#include "../../Utils/ClipFileLoad.h"
+#include "../../Utils/ClipFileSave.h"
+
+using namespace Clipper2Lib;
+
+const int display_width = 800, display_height = 600;
+
+void DoLoopThruPolygons(int start = 0, int end = 0);
+void System(const std::string &filename);
+
+int main()
+{  
+  std::cout.imbue(std::locale(""));
+  srand((unsigned)time(0));
+
+  //DoLoopThruPolygons();         // check all
+  //DoLoopThruPolygons(46);       // display one
+  DoLoopThruPolygons(110,113);    // display a range
+
+  return 0;
+}
+
+void DoLoopThruPolygons(int start, int end)
+{
+  Paths64 subject, subject_open, clip, solution, solution_open;
+  int64_t stored_area, stored_count;
+  ClipType ct;
+  FillRule fr;
+  bool first_fail = true;
+  bool do_all = (start == 0 && end == 0);
+  if (do_all) { start = 1; end = 0xFFFF; }
+  else if (end == 0) end = start;
+
+  std::ifstream ifs("Polygons.txt");
+  for (int test_number = start; test_number <= end; ++test_number)
+  {
+    if (!LoadTestNum(ifs, test_number,
+      subject, subject_open, clip, stored_area, stored_count, ct, fr)) break;
+    Clipper64 c64;
+    c64.AddSubject(subject);
+    c64.AddOpenSubject(subject_open);
+    c64.AddClip(clip);
+    if (!c64.Execute(ct, fr, solution, solution_open)) return;
+
+    if (do_all)
+    {
+      double area = (double)Area(solution);
+      double area_diff = (double)stored_area <= 0 ?
+        0 :
+        std::fabs((area / (double)stored_area) - 1.0);
+      int count = (int)(solution.size());
+      double count_diff = stored_count <= 0 ? 
+        0 : 
+        std::abs(count - stored_count)/(double)stored_count;
+      if (count_diff > 0.02 || (area_diff > 0.1))
+      {
+        if (first_fail)
+        {
+          std::cout << "\nCount and area differences (expected vs measured):\n" << std::endl;
+          first_fail = false;
+        }
+        if (count_diff > 0.02)
+          std::cout << test_number << ": counts " << stored_count << " vs " << count << std::endl;
+        if (area_diff > 0.1)
+          std::cout << test_number << ": areas  " << stored_area << " vs " << area << std::endl;
+      }
+      continue;
+    }
+    SvgWriter svg;
+    SvgAddSubject(svg, subject, fr);
+    SvgAddClip(svg, clip, fr);
+    if (fr == FillRule::Negative) 
+      for (auto& path: solution) std::reverse(path.begin(), path.end());
+    SvgAddSolution(svg, solution, fr, false);
+    SvgAddCaption(svg, std::to_string(test_number), 20, 20);
+    std::string filename = "poly" + std::to_string(test_number - 1) + ".svg";
+    SvgSaveToFile(svg, filename, 800, 600, 10);
+    System(filename);
+  }
+
+  if (!do_all) return;
+  std::cout << std::endl;
+  std::string s;
+  std::cout << "Press Enter to continue" << std::endl;
+  std::getline(std::cin, s);
+}
+
+void System(const std::string &filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}

+ 153 - 0
polygon.mod/clipper2/CPP/Examples/RandomClipping/RandomClipping.cpp

@@ -0,0 +1,153 @@
+#include <cstdlib>
+#include <string>
+#include <chrono> 
+#include <random>
+
+#include "clipper2/clipper.h"
+#include "../../Utils/clipper.svg.utils.h"
+#include "../../Utils/ClipFileLoad.h"
+#include "../../Utils/ClipFileSave.h"
+#include "../../Utils/Timer.h"
+
+using namespace Clipper2Lib;
+
+const int display_width = 800, display_height = 600, max_paths = 10;
+
+void DoRandomTest();
+void System(const std::string &filename);
+
+int main()
+{  
+  srand((unsigned)time(0));
+  DoRandomTest();
+  return 0;
+}
+
+int GenerateRandomNumber(std::default_random_engine& rng, int min_value, int max_value)
+{
+  if (min_value >= max_value) return min_value;
+  std::uniform_int_distribution<int> distribution(min_value, max_value);
+  return distribution(rng);
+}
+
+Paths64 GenerateRandomPaths(std::default_random_engine& rng,  int path_count, int edge_count)
+{
+  if (!path_count) return Paths64();
+  // with fewer paths, keep them closer to the center of the display ...
+  double center_x = display_width / 2.0; 
+  double center_y = display_height / 2.0;
+  double dx = center_x / 3.0 * (static_cast<double>(path_count) / max_paths);
+  double dy = center_y / 3.0 * (static_cast<double>(path_count) / max_paths);
+  std::normal_distribution<double> first_point_coordinate_x(0, dx);
+  std::normal_distribution<double> first_point_coordinate_y(0, dy);
+  std::uniform_int_distribution<int> orthogonal_dist_next_point(-100, 100);
+
+  Clipper2Lib::Paths64 result(path_count);
+
+  for (int path = 0; path < path_count; ++path)
+  {
+    const int path_length = edge_count;
+    auto& result_path = result[path];
+    result_path.reserve(path_length);
+
+    for (int point = 0; point < path_length; ++point) {
+      if (result_path.empty()) {
+        result_path.emplace_back(
+          center_x + first_point_coordinate_x(rng),
+          center_y + first_point_coordinate_y(rng));
+      }
+      else {
+        const auto& previous_point = result_path.back();
+        result_path.emplace_back(
+          previous_point.x + orthogonal_dist_next_point(rng),
+          previous_point.y + orthogonal_dist_next_point(rng));
+      }
+    }
+  }
+  return result;
+}
+
+std::string ClipTypeToString(ClipType ct)
+{
+  switch (ct)
+  {
+  case ClipType::Union: return "Union";
+  case ClipType::Difference: return "Difference";
+  case ClipType::Xor: return "Xor";
+  default: return "Intersection";
+  }
+}
+
+std::string FillRuleToString(FillRule fr)
+{
+  switch (fr)
+  {
+  case FillRule::NonZero: return "NonZero";
+  case FillRule::Positive: return "Positive";
+  case FillRule::Negative: return "Negative";
+  default: return "EvenOdd";
+  }
+}
+
+void RandomTest(int path_count)
+{
+  unsigned seed = (unsigned)std::chrono::system_clock::now().time_since_epoch().count();
+  std::default_random_engine rng(seed);
+  
+  ClipType cliptype =
+    static_cast<Clipper2Lib::ClipType>(GenerateRandomNumber(rng, 0, 3) + 1);
+  FillRule fillrule =
+    static_cast<Clipper2Lib::FillRule>(GenerateRandomNumber(rng, 0, 1));
+
+  int max_edges = path_count * 3;
+  path_count = std::min(max_paths, path_count);
+
+  Paths64 subj, subj_open, clip, sol, sol_open;
+  // generate exactly path_count subjects, between 0 & path_count open subjects, 
+  // and between 1 & path_count clips 
+  subj = GenerateRandomPaths(rng, path_count, GenerateRandomNumber(rng, 3, max_edges));
+  subj_open = GenerateRandomPaths(rng, 
+    GenerateRandomNumber(rng, 0, path_count), GenerateRandomNumber(rng, 3, max_edges));
+  clip = GenerateRandomPaths(rng, 
+    GenerateRandomNumber(rng, 1, path_count), GenerateRandomNumber(rng, 3, max_edges));
+
+  //SaveTest("random.txt", false, &subj, &subj_open, &clip, 0, 0, cliptype, fillrule);
+  //int64_t area, cnt;
+  //std::ifstream ifs("random2.txt");
+  //LoadTestNum(ifs, 1, subj, subj_open, clip, area, cnt, cliptype, fillrule);
+
+  Clipper64 c64;
+  c64.AddSubject(subj);
+  c64.AddOpenSubject(subj_open);
+  c64.AddClip(clip);
+  if (!c64.Execute(cliptype, fillrule, sol, sol_open)) return;
+
+  SvgWriter svg;
+  SvgAddSubject(svg, subj, fillrule);
+  SvgAddOpenSubject(svg, subj_open, fillrule);
+  SvgAddClip(svg, clip, fillrule);
+  //if (fillrule == FillRule::Negative)
+  //  for (auto& path : sol) std::reverse(path.begin(), path.end());
+  SvgAddSolution(svg, sol, fillrule, false);
+  SvgAddOpenSolution(svg, sol_open, fillrule, false);
+  SvgAddCaption(svg, 
+    ClipTypeToString(cliptype) + ", " + FillRuleToString(fillrule), 20, 20);
+  std::string filename = "random_" + std::to_string(path_count) + ".svg";
+  SvgSaveToFile(svg, filename, 800, 600, 10);
+  System(filename);
+}
+
+void DoRandomTest()
+{
+  for (int path_cnt = 1; path_cnt <= 10; ++path_cnt)
+    RandomTest(path_cnt);
+}
+
+void System(const std::string& filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}

+ 46 - 72
polygon.mod/clipper2/CPP/Examples/RectClipDemo/RectClipDemo1.cpp → polygon.mod/clipper2/CPP/Examples/RectClipping/RectClipping.cpp

@@ -1,14 +1,15 @@
 
 
 #include <cstdint>
-
 #include <cstdlib>
 #include <sstream>
 #include <fstream>
 #include <string>
 #include "clipper2/clipper.h"
 #include "../../Utils/clipper.svg.h"
+#include "../../Utils/ClipFileLoad.h"
 #include "../../Utils/clipper.svg.utils.h"
+#include "../../Utils/Colors.h"
 #include "../../Utils/Timer.h"
 
 using namespace std;
@@ -17,22 +18,23 @@ using namespace Clipper2Lib;
 void System(const std::string& filename);
 void PressEnterToExit();
 
-void DoEllipses();
-void DoRectangles();
+void DoEllipses(int cnt);
+void DoRectangles(int cnt);
 void DoRandomPoly(int count);
 void MeasurePerformance(int min, int max, int step);
-void MeasureLineClippingPerformance(int line_length);
 
-const int width = 1600, height = 1200, margin = 200;
+const int width = 800, height = 600, margin = 120;
+
 
 int main(int argc, char* argv[])
 {
   srand((unsigned)time(0));
-  DoEllipses();
-  DoRectangles();
-  DoRandomPoly(31);
-  //MeasurePerformance(500, 2500, 500);
-  //MeasureLineClippingPerformance(250);
+  
+  DoEllipses(500);      
+  DoRectangles(500);    
+  //DoRandomPoly(21);     
+  //MeasurePerformance(1000, 5000, 1000);  
+  PressEnterToExit();
 }
 
 Path64 MakeRandomEllipse(int minWidth, int minHeight, int maxWidth, int maxHeight,
@@ -45,11 +47,10 @@ Path64 MakeRandomEllipse(int minWidth, int minHeight, int maxWidth, int maxHeigh
   return Ellipse(Rect64(l, t, l + w, t + h));
 }
 
-void DoEllipses()
+void DoEllipses(int cnt)
 {
   Paths64 sub, clp, sol, store;
 
-  const int cnt = 1000;
   Rect64 rect = Rect64(margin, margin, width - margin, height - margin);
   clp.push_back(rect.AsPath());
   for (int i = 0; i < cnt; ++i)
@@ -84,10 +85,9 @@ Path64 MakeRandomRectangle(int minWidth, int minHeight, int maxWidth, int maxHei
   return result;
 }
 
-void DoRectangles()
+void DoRectangles(int cnt)
 {
   Paths64 sub, clp, sol, store;
-  const int cnt = 1000;
   Rect64 rect = Rect64(margin, margin, width - margin, height - margin);
   clp.push_back(rect.AsPath());
   for (int i = 0; i < cnt; ++i)
@@ -124,24 +124,32 @@ PathD MakeRandomPolyD(int width, int height, unsigned vertCnt)
 
 void DoRandomPoly(int count)
 {
-  PathsD sub_open, clp, sol_open, store;
-  RectD rect;
+  PathsD sub, clp, sol;
 
   // generate random poly
-  rect = RectD(margin, margin, width - margin, height - margin);
+  RectD rect = RectD(margin, margin, width - margin, height - margin);
   clp.push_back(rect.AsPath());
-  sub_open.push_back(MakeRandomPolyD(width, height, count));
+  sub.push_back(MakeRandomPolyD(width, height, count));
 
   //////////////////////////////////
-  sol_open = RectClipLines(rect, sub_open);
+  sol = RectClip(rect, sub, false);
   //////////////////////////////////
 
   FillRule fr = FillRule::EvenOdd;
+  double frac = sol.size() ? 1.0 / sol.size() : 1.0;
+  double cum_frac = 0;
   SvgWriter svg;
-  svg.AddPaths(sub_open, true, fr, 0x0, 0x400066FF, 1, false);
-  svg.AddPaths(clp, false, fr, 0x10FFAA00, 0xFFFF0000, 1, false);
-  svg.AddPaths(sol_open, true, fr, 0x0, 0xFF006600, 2.2, false);
-  svg.SaveToFile("rectclip3.svg", 800, 600, 0);
+  svg.AddPaths(sub, false, fr, 0x100066FF, 0x800066FF, 1, false);
+  svg.AddPaths(clp, false, fr, 0x10FFAA00, 0x80FF0000, 1, false);
+  //svg.AddPaths(sol, false, fr, 0x30AAFF00, 0xFF00FF00, 1, false);
+  for (const PathD& sol_path : sol)
+  {
+    uint32_t c = RainbowColor(cum_frac, 64);
+    cum_frac += frac;
+    uint32_t c2 = (c & 0xFFFFFF) | 0x20000000;
+    svg.AddPath(sol_path, false, fr, c2, c, 1.2, false);
+  }
+  svg.SaveToFile("rectclip3.svg", width, height, 0);
   System("rectclip3.svg");
 }
 
@@ -149,19 +157,15 @@ void MeasurePerformance(int min, int max, int step)
 {
   FillRule fr = FillRule::EvenOdd;
   Paths64 sub, clp, sol, store;
-  Rect64 rect;
+  Rect64 rect = Rect64(margin, margin, width - margin, height - margin);
+  clp.push_back(rect.AsPath());
 
   for (int cnt = min; cnt <= max; cnt += step)
   {
-    // generate random poly
-    Rect64 rect = Rect64(margin, margin, width - margin, height - margin);
-    clp.push_back(rect.AsPath());
     sub.clear();
+    sub.push_back(MakeRandomPoly(width, height, cnt));
 
-    for (int i = 0; i < cnt; ++i)
-      sub.push_back(MakeRandomEllipse(100, 100, 100, 100, width, height));
-
-    std::cout << std::endl << cnt << " ellipses" << std::endl;
+    std::cout << std::endl << cnt << " random poly" << std::endl;
     {
       Timer t("Clipper64: ");
       sol = Intersect(sub, clp, fr);
@@ -171,54 +175,24 @@ void MeasurePerformance(int min, int max, int step)
       Timer t("RectClip: ");
       sol = RectClip(rect, sub);
     }
+
   }
 
   SvgWriter svg;
   svg.AddPaths(sub, false, fr, 0x200066FF, 0x400066FF, 1, false);
   svg.AddPaths(clp, false, fr, 0x10FFAA00, 0xFFFF0000, 1, false);
-  svg.AddPaths(sol, false, fr, 0x8066FF66, 0xFF006600, 1, false);
-  svg.SaveToFile("RectClipQ.svg", 800, 600, 0);
-  System("RectClipQ.svg");
-
-  PressEnterToExit();
-}
-
-void MeasureLineClippingPerformance(int line_length)
-{
-  FillRule fr = FillRule::EvenOdd;
-  Paths64 sub_open, clp, sol_open, store;
-
-  // generate random poly
-  Rect64 rect = Rect64(margin, margin, width - margin, height - margin);
-  clp.push_back(rect.AsPath());
-
-  sub_open.push_back(MakeRandomPoly(width, height, line_length));
-
-  std::cout << std::endl << "Measuring line clipping performance" << std::endl;
-  std::cout << "line length: " << line_length << std::endl;
+  //svg.AddPaths(sol, false, fr, 0x8066FF66, 0xFF006600, 1, false);
+  double frac = sol.size() ? 1.0 / sol.size() : 1.0;
+  double cum_frac = 0;
+  for (const Path64& sol_path : sol)
   {
-    Timer t("Clipper64: ");
-    Paths64 dummy;
-    Clipper64 c;
-    c.AddOpenSubject(sub_open);
-    c.AddClip(clp);
-    c.Execute(ClipType::Intersection, fr, dummy, sol_open);
-    sol_open = Intersect(sub_open, clp, fr);
+    uint32_t c = RainbowColor(cum_frac, 64);
+    cum_frac += frac;
+    uint32_t c2 = (c & 0xFFFFFF) | 0x20000000;
+    svg.AddPath(sol_path, false, fr, c2, c, 1.2, false);
   }
-
-  {
-    Timer t("RectClip: ");
-    sol_open = RectClipLines(rect, sub_open);
-  }
-
-  SvgWriter svg;
-  svg.AddPaths(sub_open, true, fr, 0x0, 0x400066FF, 1, false);
-  svg.AddPaths(clp, false, fr, 0x10FFAA00, 0xFFFF0000, 1, false);
-  svg.AddPaths(sol_open, true, fr, 0x0, 0xFF006600, 2.0, false);
-  svg.SaveToFile("RectClipL.svg", 800, 600, 0);
-  System("RectClipL.svg");
-
-  PressEnterToExit();
+  svg.SaveToFile("RectClipQ2.svg", 800, 600, 0);
+  System("RectClipQ2.svg");
 }
 
 void System(const std::string& filename)

+ 63 - 0
polygon.mod/clipper2/CPP/Examples/SimpleClipping/SimpleClipping.cpp

@@ -0,0 +1,63 @@
+#include <cstdlib>
+#include <string>
+
+#include "clipper2/clipper.h"
+#include "../../Utils/clipper.svg.utils.h"
+
+using namespace Clipper2Lib;
+
+void DoSimpleTest(bool show_solution_coords = false);
+Path64 MakeRandomPoly(int width, int height, unsigned vertCnt);
+void System(const std::string &filename);
+
+int main()
+{  
+  DoSimpleTest();    
+}
+
+inline Path64 MakeStar(const Point64& center, int radius, int points)
+{
+  if (!(points % 2)) --points;
+  if (points < 5) points = 5;
+  Path64 tmp = Ellipse<int64_t>(center, radius, radius, points);
+  Path64 result;
+  result.reserve(points);
+  result.push_back(tmp[0]);
+  for (int i = points - 1, j = i / 2; j;)
+  {
+    result.push_back(tmp[j--]);
+    result.push_back(tmp[i--]);
+  }
+  return result;
+}
+
+
+void DoSimpleTest(bool show_solution_coords)
+{
+  Paths64 tmp, solution;
+  FillRule fr = FillRule::NonZero;
+
+  Paths64 subject, clip;
+  subject.push_back(MakeStar(Point64(225, 225), 220, 9));
+  clip.push_back(Ellipse<int64_t>(Point64(225,225), 150, 150));  
+  
+  //Intersect both shapes and then 'inflate' result -10 (ie deflate)
+  solution = Intersect(subject, clip, fr);
+  solution = InflatePaths(solution, -10, JoinType::Round, EndType::Polygon);
+
+  SvgWriter svg;
+  SvgAddSubject(svg, subject, fr);
+  SvgAddClip(svg, clip, fr);
+  SvgAddSolution(svg, solution, fr, false);
+  SvgSaveToFile(svg, "solution.svg", 450, 450, 10);
+  System("solution.svg");
+}
+
+void System(const std::string &filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}

+ 168 - 0
polygon.mod/clipper2/CPP/Examples/UnionClipping/UnionClipping.cpp

@@ -0,0 +1,168 @@
+#include <cstdlib>
+#include <string>
+#include <chrono> 
+
+#include "clipper2/clipper.h"
+#include "../../Utils/clipper.svg.utils.h"
+#include "../../Utils/ClipFileLoad.h"
+#include "../../Utils/ClipFileSave.h"
+#include "../../Utils/Timer.h"
+
+using namespace Clipper2Lib;
+
+void DoDiamonds();
+void DoSquares();
+void DoCircles();
+void DoTriangles();
+
+int main()
+{
+  std::cout.imbue(std::locale(""));
+  srand((unsigned)time(0));
+
+  DoDiamonds();
+  DoSquares();
+  DoCircles();
+  DoTriangles();
+
+#ifdef _DEBUG
+  //std::string s;
+  //std::cout  << std::endl << "All done. Press Enter to continue" << std::endl;
+  //std::getline(std::cin, s);
+#endif
+  return 0;
+}
+
+void System(const std::string& filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}
+
+void DoDiamonds()
+{
+  static const int size = 10, size2 = size * 2;;
+  static const int w = 800, h = 600;
+  Path64 shape;
+  shape.push_back(Point64(size, 0));
+  shape.push_back(Point64(size2, size));
+  shape.push_back(Point64(size, size2));
+  shape.push_back(Point64(0, size));
+
+  Paths64 subjects, solution;
+  
+  for (int i = 0; i < h / size; i += 2)
+  {
+    for (int j = 0; j < w / size; ++j)
+    {
+      shape = TranslatePath(shape, size, (j & 1) == 0 ? size : -size);
+      if (rand() % 7) subjects.push_back(shape);
+    }
+    shape = TranslatePath(shape, static_cast<int64_t>(-w / size) * size, size*2);
+  }
+
+  solution = Union(subjects, FillRule::NonZero);
+
+  SvgWriter svg;
+  SvgAddSubject(svg, subjects, FillRule::NonZero);
+  SvgAddSolution(svg, solution, FillRule::NonZero, false);
+  SvgSaveToFile(svg, "solution1.svg", w, h, 10);
+  System("solution1.svg");
+}
+
+void DoSquares()
+{
+  static const int w = 800, h = 600;
+  static const int size = 10;
+  Path64 shape;
+  shape.push_back(Point64(0, 0));
+  shape.push_back(Point64(size, 0));
+  shape.push_back(Point64(size, size));
+  shape.push_back(Point64(0, size));
+  Paths64 subjects, solution;
+  ClipType cliptype = ClipType::Union;
+  FillRule fillrule = FillRule::NonZero;
+
+  for (int i = 0; i < h / size; ++i)
+  {
+    for (int j = 0; j < w / size; ++j)
+    {
+      if (rand() % 4 != 1) subjects.push_back(shape);
+      shape = TranslatePath(shape, size, 0);
+    }
+    shape = TranslatePath(shape, static_cast<int64_t>(-w / size) * size, size);
+  }
+  //SaveTest("squares.txt", false, &subjects, nullptr, nullptr, 0, 0, ClipType::Union, FillRule::NonZero);
+
+  solution = Union(subjects, fillrule);
+  
+  SvgWriter svg;
+  SvgAddSubject(svg, subjects, fillrule);
+  SvgAddSolution(svg, solution, fillrule, false);
+  SvgSaveToFile(svg, "solution2.svg", 800, 600, 10);
+  System("solution2.svg");
+}
+
+void DoCircles()
+{
+  // create a small circle with 31 vertices
+  PathD shape = Ellipse(RectD(0, 0, 35, 35), 31);
+
+  PathsD subjects, solution;
+  int w = 800, h = 600;
+
+  for (int j = 0; j < 550; ++j)
+  {
+    subjects.push_back(TranslatePath(shape,
+      rand() % (w - 50), rand() % (h - 50)));
+  }
+
+  solution = Union(subjects, FillRule::NonZero);
+
+  SvgWriter svg;
+  SvgAddSolution(svg, solution, FillRule::NonZero, false);
+  SvgSaveToFile(svg, "solution3.svg", 800, 600, 10);
+  System("solution3.svg");
+}
+
+void DoTriangles()
+{
+  static const int w = 800, h = 600;
+  static const int size = 10;
+  Path64 tri1;
+  tri1.push_back(Point64(0, 0));
+  tri1.push_back(Point64(size * 2, 0));
+  tri1.push_back(Point64(size, size * 2));
+  Path64 tri2;
+  tri2.push_back(Point64(size * 2, 0));
+  tri2.push_back(Point64(size, size * 2));
+  tri2.push_back(Point64(size * 3, size * 2));
+
+  Paths64 subjects, solution;
+  ClipType cliptype = ClipType::Union;
+  FillRule fillrule = FillRule::NonZero;
+
+  for (int i = 0; i < h / size / 2; ++i)
+  {
+    for (int j = 0; j < w / size / 2; ++j)
+    {
+      if (rand() % 5 != 1) subjects.push_back(tri1);
+      if (rand() % 5 != 1) subjects.push_back(tri2);
+      tri1 = TranslatePath(tri1, size * 2, 0);
+      tri2 = TranslatePath(tri2, size * 2, 0);
+    }
+    tri1 = TranslatePath(tri1, static_cast<int64_t>(-w / size) * size, size * 2);
+    tri2 = TranslatePath(tri2, static_cast<int64_t>(-w / size) * size, size * 2);
+  }
+
+  solution = Union(subjects, fillrule);
+
+  SvgWriter svg;
+  SvgAddSubject(svg, subjects, fillrule);
+  SvgAddSolution(svg, solution, fillrule, false);
+  SvgSaveToFile(svg, "solution4.svg", 800, 600, 10);
+  System("solution4.svg");
+}

+ 26 - 28
polygon.mod/clipper2/CPP/Examples/UsingZ/UsingZ1.cpp → polygon.mod/clipper2/CPP/Examples/UsingZ/UsingZ.cpp

@@ -9,19 +9,22 @@ using namespace Clipper2Lib;
 
 
 void System(const std::string &filename);
-void TestInt64();
-void TestDouble();
+void TestingZ_Int64();
+void TestingZ_Double();
+
+// use the Z callback to flag intersections by setting z = 1;
 
 class MyClass {
 public:
-  // Point64 callback - see TestInt64()
+  
+  // Point64 callback - see TestingZ_Int64()
   void myZCB(const Point64& e1bot, const Point64& e1top,
     const Point64& e2bot, const Point64& e2top, Point64& pt)
   {
     pt.z = 1;
   }
 
-  // PointD callback - see TestDouble()
+  // PointD callback - see TestingZ_Double()
   void myZCBD(const PointD& e1bot, const PointD& e1top,
     const PointD& e2bot, const PointD& e2top, PointD& pt)
   {
@@ -31,28 +34,25 @@ public:
 
 int main(int argc, char* argv[])
 {
-  TestInt64();
-  TestDouble();
+  //TestingZ_Int64();
+  TestingZ_Double();
 }
-//---------------------------------------------------------------------------
 
-void TestInt64()
+void TestingZ_Int64()
 {
 
   Paths64 subject, solution;
   MyClass mc;
   Clipper64 c64;
 
-  subject.push_back(MakePath("100, 50, 10, 79, 65, 2, 65, 98, 10, 21 "));
+  subject.push_back(MakePath({ 100, 50, 10, 79, 65, 2, 65, 98, 10, 21 }));
   c64.AddSubject(subject);
   c64.SetZCallback(
     std::bind(&MyClass::myZCB, mc, std::placeholders::_1,
       std::placeholders::_2, std::placeholders::_3,
       std::placeholders::_4, std::placeholders::_5));
-
   c64.Execute(ClipType::Union, FillRule::NonZero, solution);
 
-  /*
   SvgWriter svg;
   SvgAddSolution(svg, solution, FillRule::NonZero, false);
   if (solution.size() > 0) {
@@ -66,42 +66,40 @@ void TestInt64()
       }
     SvgAddClip(svg, ellipses, FillRule::NonZero);
   }
-  SvgSaveToFile(svg, "using_z_64.svg", 800, 600, 20);
-  System("using_z_64.svg");
-  */
+  SvgSaveToFile(svg, "usingz_int64.svg", 800, 600, 20);
+  System("usingz_int64.svg");
 }
 
-void TestDouble()
+void TestingZ_Double()
 {
-
   PathsD subject, solution;
   MyClass mc;
   ClipperD c;
 
-  subject.push_back(MakePathD("100, 50, 10, 79, 65, 2, 65, 98, 10, 21 "));
+  subject.push_back(MakePathD({ 100, 50, 10, 79, 65, 2, 65, 98, 10, 21 }));
   c.AddSubject(subject);
   c.SetZCallback(
     std::bind(&MyClass::myZCBD, mc, std::placeholders::_1,
       std::placeholders::_2, std::placeholders::_3,
       std::placeholders::_4, std::placeholders::_5));
-
   c.Execute(ClipType::Union, FillRule::NonZero, solution);
 
   SvgWriter svg;
-  SvgAddSolution(svg, solution, FillRule::NonZero, false);
-  if (solution.size() > 0) {
-    // draw circles around intersection points - flagged by z == 1
+  SvgAddSubject(svg, subject, FillRule::NonZero);
+  if (solution.size() > 0) 
+  {
+    // draw circles around intersection points
     PathsD ellipses;
-    double r = 5.0;
+    double r = 3.0;
     for (const PointD& pt : solution[0])
       if (pt.z == 1)
-      {
-        ellipses.push_back(Ellipse(RectD(pt.x - r, pt.y - r, pt.x + r, pt.y + r), 11));
-      }
-    SvgAddClip(svg, ellipses, FillRule::NonZero);
+        ellipses.push_back(Ellipse(RectD(pt.x - r, pt.y - r, 
+          pt.x + r, pt.y + r), 11));
+
+    SvgAddSolution(svg, ellipses, FillRule::NonZero, false);
   }
-  SvgSaveToFile(svg, "using_z_d.svg", 800, 600, 20);
-  System("using_z_d.svg");
+  SvgSaveToFile(svg, "usingz_double.svg", 320, 320, 0);
+  System("usingz_double.svg");
 }
 
 void System(const std::string &filename)

+ 161 - 0
polygon.mod/clipper2/CPP/Examples/VariableOffset/VariableOffset.cpp

@@ -0,0 +1,161 @@
+#include <iostream>
+
+#include "clipper2/clipper.h"
+#include "clipper2/clipper.core.h"
+#include "../../Utils/clipper.svg.utils.h"
+#include "../../Utils/CommonUtils.h"
+
+using namespace Clipper2Lib;
+
+void System(const std::string& filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}
+
+void test1() {
+
+  int64_t const scale = 10;
+  double delta = 10 * scale;
+
+  ClipperOffset co;
+  co.SetDeltaCallback([delta](const Path64& path,
+    const PathD& path_norms, size_t curr_idx, size_t prev_idx)
+    {
+      // gradually scale down the offset to a minimum of 25% of delta
+      double high = static_cast<double>(path.size() - 1) * 1.25;
+      return (high - curr_idx) / high * delta;
+    });
+
+  Paths64 subject{ Ellipse(Rect64(0, 0, 200 * scale, 180 * scale)) };
+  subject[0].resize(subject[0].size() * 0.9);
+
+  co.AddPaths(subject, JoinType::Miter, EndType::Round);
+  Paths64 solution;
+  co.Execute(1.0, solution);
+
+  std::string filename = "test1.svg";
+  SvgWriter svg;
+  SvgAddOpenSubject(svg, subject, FillRule::NonZero);
+  SvgAddSolution(svg, solution, FillRule::NonZero, false);
+  SvgSaveToFile(svg, filename, 400, 400);
+  System(filename);
+}
+
+void test2() {
+
+  int64_t const scale = 10;
+  double delta = 10 * scale;
+
+  ClipperOffset co;
+  co.SetDeltaCallback([delta](const Path64& path,
+    const PathD& path_norms, size_t curr_idx, size_t prev_idx) {
+      // calculate offset based on distance from the middle of the path
+      double mid_idx = static_cast<double>(path.size()) / 2.0;
+      return delta * (1.0 - 0.70 * (std::fabs(curr_idx - mid_idx) / mid_idx));
+    });
+
+  Paths64 subject{ Ellipse(Rect64(0, 0, 200 * scale, 180 * scale)) };
+  subject[0].resize(subject[0].size() * 0.9);
+
+  co.AddPaths(subject, JoinType::Miter, EndType::Round);
+  Paths64 solution;
+  co.Execute(1.0, solution);
+
+  std::string filename = "test2.svg";
+  SvgWriter svg;
+  SvgAddOpenSubject(svg, subject, FillRule::NonZero);
+  SvgAddSolution(svg, solution, FillRule::NonZero, false);
+  SvgSaveToFile(svg, filename, 400, 400);
+  System(filename);
+}
+
+void test3() {
+
+  double radius = 5000.0;
+  Paths64 subject = { Ellipse(Rect64(-radius, -radius, radius, radius), 200) };
+
+  ClipperOffset co;
+  co.AddPaths(subject, JoinType::Miter, EndType::Polygon);
+
+  co.SetDeltaCallback([radius](const Path64& path,
+    const PathD& path_norms, size_t curr_idx, size_t prev_idx) {
+      // when multiplying the x & y of edge unit normal vectors, the value will be 
+      // largest (0.5) when edges are at 45 deg. and least (-0.5) at negative 45 deg.
+      double delta = path_norms[curr_idx].y * path_norms[curr_idx].x;
+      return radius * 0.5 + radius * delta;
+    });
+
+  //  solution
+  Paths64 solution;
+  co.Execute(1.0, solution);
+
+  std::string filename = "test3.svg";
+  SvgWriter svg;
+  SvgAddSubject(svg, subject, FillRule::NonZero);
+  SvgAddSolution(svg, solution, FillRule::NonZero, false);
+  SvgSaveToFile(svg, filename, 400, 400);
+  System(filename);
+}
+
+void test4() {
+
+  int64_t const scale = 100;
+  Paths64 solution;
+  Paths64 subject = { Ellipse(ScaleRect<int64_t,int64_t>(Rect64(10, 10, 50, 50), scale)) };
+
+  ClipperOffset co;
+  co.AddPaths(subject, JoinType::Round, EndType::Round);
+  co.Execute(
+    [scale](const Path64& path,
+      const PathD& path_norms, size_t curr_idx, size_t prev_idx) {
+        //double vertex_sin_a = CrossProduct(path_norms[curr_idx], path_norms[prev_idx]);
+        //double vertex_cos_a = DotProduct(path_norms[curr_idx], path_norms[prev_idx]);
+        //double vertex_angle = std::atan2(vertex_sin_a, vertex_cos_a);
+        //double edge_angle = std::atan2(path_norms[curr_idx].y, path_norms[curr_idx].x);
+        double sin_edge = path_norms[curr_idx].y;
+        return Sqr(sin_edge) * 3 * scale; }
+  , solution);
+
+  std::string filename = "test4.svg";
+  SvgWriter svg;
+  SvgAddOpenSubject(svg, subject, FillRule::NonZero);
+  SvgAddSolution(svg, solution, FillRule::NonZero, false);
+  SvgSaveToFile(svg, filename, 400, 400);
+  System(filename);
+}
+
+void test5() {
+
+  Paths64 solution;
+  Paths64 subject = { MakePath({0,0, 20,0, 40,0, 60,0, 80,0, 100,0}) };
+
+  ClipperOffset co;
+  co.AddPaths(subject, JoinType::Round, EndType::Butt);
+  co.Execute(
+    [](const Path64& path,
+      const PathD& path_norms, size_t curr_idx, size_t prev_idx) {
+        return double(curr_idx * curr_idx + 10); }
+  , solution);
+
+  SvgWriter svg;
+  std::string filename = "test5.svg";
+  SvgAddOpenSubject(svg, subject, FillRule::NonZero);
+  SvgAddSolution(svg, solution, FillRule::NonZero, false);
+  SvgSaveToFile(svg, filename, 400, 400);
+  System(filename);
+}
+
+
+int main() {
+
+  test1();
+  test2();
+  test3();
+  test4();
+  test5();
+  return 0;
+}

+ 6 - 5
polygon.mod/clipper2/CPP/GoogleTest in Visual Studio.txt

@@ -1,12 +1,13 @@
 Installing GoogleTest in Visual Studio
 
-1. Goto https://github.com/google/googletest
-2. Click on the bright green "Code" button and then click "Download ZIP".
+1. In Clipper2's CPP/Tests folder create a subfolder named googletest.
+2. Goto https://github.com/google/googletest
+3. Click on the bright green "Code" button and then click "Download ZIP".
 3. Open the downloaded Zip package and locate the following:
    a. CMakeLists.txt 
    b. googlemock folder 
    c. googletest folder
-4. Copy these into Clipper2's empty CPP/Tests/googletest folder.
-5. In Visual Studio, open Clipper2's CPP folder and wait to see
+5. Copy these into the empty googletest folder created in step 1.
+6. In Visual Studio, open Clipper2's CPP folder and wait to see
    "CMake generation finished" in Visual Studio's statusbar.
-6. Rebuild all files (Ctrl+Shift+B).
+7. Rebuild all files (Ctrl+Shift+B).

+ 178 - 0
polygon.mod/clipper2/CPP/Tests/TestExportHeaders.cpp

@@ -0,0 +1,178 @@
+#include <gtest/gtest.h>
+#include "clipper2/clipper.h"
+#include "clipper2/clipper.core.h"
+#include "clipper2/clipper.export.h"
+using namespace Clipper2Lib;
+static bool CreatePolyPath64FromCPolyPath(CPolyPath64& v, PolyPath64& owner)
+{
+  int64_t poly_len = *v++, child_count = *v++;
+  if (!poly_len) return false;
+  Path64 path;
+  path.reserve(poly_len);
+  for (size_t i = 0; i < poly_len; ++i)
+  {
+    int64_t x = *v++, y = *v++;
+    path.push_back(Point64(x,y));
+  }
+  PolyPath64* new_owner = owner.AddChild(path);
+  for (size_t i = 0; i < child_count; ++i)
+    CreatePolyPath64FromCPolyPath(v, *new_owner);
+  return true;
+}
+static bool BuildPolyTree64FromCPolyTree(CPolyTree64 tree, PolyTree64& result)
+{
+  result.Clear();
+  int64_t* v = tree;
+  int64_t array_len = *v++, child_count = *v++;
+  for (size_t i = 0; i < child_count; ++i)
+    if (!CreatePolyPath64FromCPolyPath(v, result)) return false;
+  return true;
+}
+static bool CreatePolyPathDFromCPolyPath(CPolyPathD& v, PolyPathD& owner)
+{
+  int64_t poly_len = *v++, child_count = *v++;
+  if (!poly_len) return false;
+  PathD path;
+  path.reserve(poly_len);
+  for (size_t i = 0; i < poly_len; ++i)
+  {
+    int64_t x = *v++, y = *v++;
+    path.push_back(PointD(x, y));
+  }
+  PolyPathD* new_owner = owner.AddChild(path);
+  for (size_t i = 0; i < child_count; ++i)
+    CreatePolyPathDFromCPolyPath(v, *new_owner);
+  return true;
+}
+static bool BuildPolyTreeDFromCPolyTree(CPolyTreeD tree, PolyTreeD& result)
+{
+  result.Clear();
+  double* v = tree;
+  int64_t array_len = *v++, child_count = *v++;
+  for (size_t i = 0; i < child_count; ++i)
+    if (!CreatePolyPathDFromCPolyPath(v, result)) return false;
+  return true;
+}
+TEST(Clipper2Tests, ExportHeader64)
+{
+  uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4;
+  uint8_t EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3;
+  Paths64 subj, clip, solution;
+  //subj.push_back(MakeRandomPoly(600, 400, 25));
+  //clip.push_back(MakeRandomPoly(600, 400, 25));
+  for (int i = 1; i < 6; ++i)
+    subj.push_back(MakePath({ -i*20,-i * 20, i * 20,-i * 20, i * 20,i * 20, -i * 20,i * 20 }));
+  clip.push_back(MakePath({ -90,-120,90,-120, 90,120, -90,120 }));
+  CPaths64 c_subj_open = nullptr, c_sol = nullptr, c_sol_open = nullptr;
+  // Note: while CreateCPaths64 isn't exported in clipper.export.h, it can still
+  // be used here because we're simply statically compiling clipper.export.h.
+  // Normally clipper.export.h will be compiled into a DLL/so so it can be called
+  // by non C++ applications. If CreateCPaths64 was an exported function and it
+  // was called by a non C++ application, it would crash that application.
+  CPaths64 c_subj = CreateCPaths(subj);
+  CPaths64 c_clip = CreateCPaths(clip);
+  BooleanOp64(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol, c_sol_open);
+  solution = ConvertCPaths(c_sol);
+  //clean up !!!
+  delete[] c_subj;
+  delete[] c_clip;
+  DisposeArray64(c_sol);
+  DisposeArray64(c_sol_open);
+  EXPECT_EQ(solution.size(), 5);
+}
+TEST(Clipper2Tests, ExportHeaderD)
+{
+  uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4;
+  uint8_t EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3;
+  PathsD subj, clip, solution;
+  //subj.push_back(MakeRandomPolyD(600, 400, 25));
+  //clip.push_back(MakeRandomPolyD(600, 400, 25));
+  for (int i = 1; i < 6; ++i)
+    subj.push_back(MakePathD({ -i * 20,-i * 20, i * 20,-i * 20, i * 20,i * 20, -i * 20,i * 20 }));
+  clip.push_back(MakePathD({ -90,-120,90,-120, 90,120, -90,120 }));
+  CPathsD c_subj_open = nullptr, c_sol = nullptr, c_sol_open = nullptr;
+  // Note: while CreateCPathsD isn't exported in clipper.export.h, it can still
+  // be used here because we're simply statically compiling clipper.export.h.
+  // Normally clipper.export.h will be compiled into a DLL/so so it can be called
+  // by non C++ applications. If CreateCPathsD was an exported function and it
+  // was called by a non C++ application, it would crash that application.
+  CPathsD c_subj = CreateCPaths(subj);
+  CPathsD c_clip = CreateCPaths(clip);
+  BooleanOpD(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol, c_sol_open);
+  solution = ConvertCPaths(c_sol);
+  //clean up !!!
+  delete[] c_subj;
+  delete[] c_clip;
+  DisposeArrayD(c_sol);
+  DisposeArrayD(c_sol_open);
+  EXPECT_EQ(solution.size(), 5);
+}
+TEST(Clipper2Tests, ExportHeaderTree64)
+{
+  uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4;
+  uint8_t EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3;
+  Paths64 subj, clip, solution;
+  for (int i = 1; i < 6; ++i)
+    subj.push_back(MakePath({ -i * 20,-i * 20, i * 20,-i * 20, i * 20,i * 20, -i * 20,i * 20 }));
+  clip.push_back(MakePath({ -90,-120,90,-120, 90,120, -90,120 }));
+  CPaths64 c_subj_open = nullptr, c_sol = nullptr, c_sol_open = nullptr;
+  // Note: while CreateCPaths64 isn't exported in clipper.export.h, it can still
+  // be used here because we're statically compiling clipper.export.h.
+  // More likely, clipper.export.h will be compiled into a DLL/so so it can be
+  // called by non C++ applications. If CreateCPaths64 was an exported function
+  // and it was called by a non C++ application, it would crash that application.
+  CPaths64 c_subj = CreateCPaths(subj);
+  CPaths64 c_clip = CreateCPaths(clip);
+  int64_t* c_sol_tree = nullptr;
+  BooleanOp_PolyTree64(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol_tree, c_sol_open);
+  PolyTree64 sol_tree;
+  // convert CPolyTree64 to PolyTree64
+  BuildPolyTree64FromCPolyTree(c_sol_tree, sol_tree);
+  // convert PolyTree64 to Paths64
+  solution = PolyTreeToPaths64(sol_tree);
+  //clean up !!!
+  delete[] c_subj;
+  delete[] c_clip;
+  DisposeArray64(c_sol_tree);
+  DisposeArray64(c_sol_open);
+  PolyPath64* pp = &sol_tree;
+  for (int i = 0; i < 4; ++i)
+  {
+    EXPECT_TRUE(pp->Count() == 1); pp = pp->Child(0);
+  }
+}
+TEST(Clipper2Tests, ExportHeaderTreeD)
+{
+  uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4;
+  uint8_t EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3;
+  PathsD subj, clip, solution;
+  for (int i = 1; i < 6; ++i)
+    subj.push_back(MakePathD({ -i * 20,-i * 20, i * 20,-i * 20, i * 20,i * 20, -i * 20,i * 20 }));
+  clip.push_back(MakePathD({ -90,-120,90,-120, 90,120, -90,120 }));
+  CPathsD c_subj_open = nullptr, c_sol = nullptr, c_sol_open = nullptr;
+  // Note: while CreateCPathsD isn't exported in clipper.export.h, it can still
+  // be used here because we're statically compiling clipper.export.h.
+  // More likely, clipper.export.h will be compiled into a DLL/so so it can be
+  // called by non C++ applications. If CreateCPathsD was an exported function
+  // and it was called by a non C++ application, it would crash that application.
+  CPathsD c_subj = CreateCPaths(subj);
+  CPathsD c_clip = CreateCPaths(clip);
+  static const int precision = 4;
+  CPolyPathD c_sol_tree = nullptr;
+  BooleanOp_PolyTreeD(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol_tree, c_sol_open, precision);
+  PolyTreeD sol_tree;
+  // convert CPolyTreeD to PolyTreeD
+  BuildPolyTreeDFromCPolyTree(c_sol_tree, sol_tree);
+  // convert PolyTreeD to PathsD
+  solution = PolyTreeToPathsD(sol_tree);
+  //clean up !!!
+  delete[] c_subj;
+  delete[] c_clip;
+  DisposeArrayD(c_sol_tree);
+  DisposeArrayD(c_sol_open);
+  PolyPathD* pp = &sol_tree;
+  for (int i = 0; i < 4; ++i)
+  {
+    EXPECT_TRUE(pp->Count() == 1); pp = pp->Child(0);
+  }
+}

+ 4 - 14
polygon.mod/clipper2/CPP/Tests/TestLines.cpp

@@ -1,15 +1,9 @@
 #include <gtest/gtest.h>
 #include "clipper2/clipper.h"
 #include "ClipFileLoad.h"
-
 TEST(Clipper2Tests, TestMultipleLines) {
-
   std::ifstream ifs("Lines.txt");
-
-
-  ASSERT_TRUE(ifs);
   ASSERT_TRUE(ifs.good());
-
   int test_number = 1;
   while (true)
   {
@@ -18,31 +12,26 @@ TEST(Clipper2Tests, TestMultipleLines) {
     Clipper2Lib::ClipType ct;
     Clipper2Lib::FillRule fr;
     int64_t area, count;
-
     if (!LoadTestNum(ifs, test_number,
       subject, subject_open, clip, area, count, ct, fr)) break;
-
     Clipper2Lib::Clipper64 c;
     c.AddSubject(subject);
     c.AddOpenSubject(subject_open);
     c.AddClip(clip);
     EXPECT_TRUE(c.Execute(ct, fr, solution, solution_open));
-
     const int64_t count2 = solution.size() + solution_open.size();
     const int64_t count_diff = std::abs(count2 - count);
-    const double relative_count_diff = count ? 
-      count_diff / static_cast<double>(count) : 
+    const double relative_count_diff = count ?
+      count_diff / static_cast<double>(count) :
       0;
-
     if (test_number == 1)
     {
       EXPECT_EQ(solution.size(), 1);
       if (solution.size() > 0)
       {
-        EXPECT_EQ(solution[0].size(), 6);        
+        EXPECT_EQ(solution[0].size(), 6);
         EXPECT_TRUE(IsPositive(solution[0]));
       }
-
       EXPECT_EQ(solution_open.size(), 1);
       if (solution_open.size() > 0)
       {
@@ -61,5 +50,6 @@ TEST(Clipper2Tests, TestMultipleLines) {
     }
     ++test_number;
   }
+  ifs.close();
   EXPECT_GE(test_number, 17);
 }

+ 26 - 17
polygon.mod/clipper2/CPP/Tests/TestOffsetOrientation.cpp

@@ -1,20 +1,29 @@
 #include <gtest/gtest.h>
 #include "clipper2/clipper.offset.h"
-
-TEST(Clipper2Tests, TestOffsettingOrientation) {
-    Clipper2Lib::ClipperOffset co;
-
-    const Clipper2Lib::Path64 input = {
-        Clipper2Lib::Point64(0, 0),
-        Clipper2Lib::Point64(0, 5),
-        Clipper2Lib::Point64(5, 5),
-        Clipper2Lib::Point64(5, 0)
-    };
-
-    co.AddPath(input, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon);
-    const auto outputs = co.Execute(1);
-
-    ASSERT_EQ(outputs.size(), 1);
-    //when offsetting, output orientation should match input
-    EXPECT_TRUE(Clipper2Lib::IsPositive(input) == Clipper2Lib::IsPositive(outputs[0]));
+#include "ClipFileLoad.h"
+TEST(Clipper2Tests, TestOffsettingOrientation1) {
+  const Clipper2Lib::Paths64 subject = { Clipper2Lib::MakePath({ 0,0, 0,5, 5,5, 5,0 }) };
+  Clipper2Lib::Paths64 solution = Clipper2Lib::InflatePaths(subject, 1,
+    Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon);
+  ASSERT_EQ(solution.size(), 1);
+  //when offsetting, output orientation should match input
+  EXPECT_TRUE(Clipper2Lib::IsPositive(subject[0]) == Clipper2Lib::IsPositive(solution[0]));
+}
+TEST(Clipper2Tests, TestOffsettingOrientation2) {
+  const Clipper2Lib::Paths64 subject = {
+    Clipper2Lib::MakePath({20, 220, 280, 220, 280, 280, 20, 280}),
+    Clipper2Lib::MakePath({0, 200, 0, 300, 300, 300, 300, 200})
+  };
+  Clipper2Lib::ClipperOffset co;
+  co.ReverseSolution(true); // could also assign using a parameter in ClipperOffset's constructor
+  co.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon);
+  Clipper2Lib::Paths64 solution;
+  co.Execute(5, solution);
+  ASSERT_EQ(solution.size(), 2);
+  // When offsetting, output orientation should match input EXCEPT when ReverseSolution == true
+  // However, input path ORDER may not match output path order. For example, order will change
+  // whenever inner paths (holes) are defined before their container outer paths (as above).
+  // And when offsetting multiple outer paths, their order will likely change too. Due to the
+  // sweep-line algorithm used, paths with larger Y coordinates will likely be listed first.
+  EXPECT_TRUE(Clipper2Lib::IsPositive(subject[1]) != Clipper2Lib::IsPositive(solution[0]));
 }

+ 605 - 0
polygon.mod/clipper2/CPP/Tests/TestOffsets.cpp

@@ -0,0 +1,605 @@
+#include <gtest/gtest.h>
+#include "clipper2/clipper.offset.h"
+#include "ClipFileLoad.h"
+//#include "clipper.svg.utils.h"
+using namespace Clipper2Lib;
+TEST(Clipper2Tests, TestOffsets) {
+  std::ifstream ifs("Offsets.txt");
+  ASSERT_TRUE(ifs.good());
+  for (int test_number = 1; test_number <= 2; ++test_number)
+  {
+    ClipperOffset co;
+    Paths64 subject, subject_open, clip;
+    Paths64 solution, solution_open;
+    ClipType ct = ClipType::None;
+    FillRule fr = FillRule::NonZero;
+    int64_t stored_area = 0, stored_count = 0;
+    ASSERT_TRUE(LoadTestNum(ifs, test_number, subject, subject_open, clip, stored_area, stored_count, ct, fr));
+    co.AddPaths(subject, JoinType::Round, EndType::Polygon);
+    Paths64 outputs;
+    co.Execute(1, outputs);
+    // is the sum total area of the solution is positive
+    const auto outer_is_positive = Area(outputs) > 0;
+    // there should be exactly one exterior path
+    const auto is_positive_func = IsPositive<int64_t>;
+    const auto is_positive_count = std::count_if(
+      outputs.begin(), outputs.end(), is_positive_func);
+    const auto is_negative_count =
+      outputs.size() - is_positive_count;
+    if (outer_is_positive)
+      EXPECT_EQ(is_positive_count, 1);
+    else
+      EXPECT_EQ(is_negative_count, 1);
+  }
+  ifs.close();
+}
+static Point64 MidPoint(const Point64& p1, const Point64& p2)
+{
+  Point64 result;
+  result.x = (p1.x + p2.x) / 2;
+  result.y = (p1.y + p2.y) / 2;
+  return result;
+}
+TEST(Clipper2Tests, TestOffsets2) { // see #448 & #456
+  double scale = 10, delta = 10 * scale, arc_tol = 0.25 * scale;
+  Paths64 subject, solution;
+  ClipperOffset c;
+  subject.push_back(MakePath({ 50,50, 100,50, 100,150, 50,150, 0,100 }));
+  int err;
+  subject = ScalePaths<int64_t, int64_t>(subject, scale, err);
+  c.AddPaths(subject, JoinType::Round, EndType::Polygon);
+  c.ArcTolerance(arc_tol);
+  c.Execute(delta, solution);
+  double min_dist = delta * 2, max_dist = 0;
+  for (const Point64& subjPt : subject[0])
+  {
+    Point64 prevPt = solution[0][solution[0].size() - 1];
+    for (const Point64& pt : solution[0])
+    {
+      Point64 mp = MidPoint(prevPt, pt);
+      double d = Distance(mp, subjPt);
+      if (d < delta * 2)
+      {
+        if (d < min_dist) min_dist = d;
+        if (d> max_dist) max_dist = d;
+      }
+      prevPt = pt;
+    }
+  }
+  EXPECT_GE(min_dist + 1, delta - arc_tol); // +1 for rounding errors
+  EXPECT_LE(solution[0].size(), 21);
+}
+TEST(Clipper2Tests, TestOffsets3) // see #424
+{
+  Paths64 subjects = {{
+   {1525311078, 1352369439}, {1526632284, 1366692987}, {1519397110, 1367437476},
+   {1520246456, 1380177674}, {1520613458, 1385913385}, {1517383844, 1386238444},
+   {1517771817, 1392099983}, {1518233190, 1398758441}, {1518421934, 1401883197},
+   {1518694564, 1406612275}, {1520267428, 1430289121}, {1520770744, 1438027612},
+   {1521148232, 1443438264}, {1521441833, 1448964260}, {1521683005, 1452518932},
+   {1521819320, 1454374912}, {1527943004, 1454154711}, {1527649403, 1448523858},
+   {1535901696, 1447989084}, {1535524209, 1442788147}, {1538953052, 1442463089},
+   {1541553521, 1442242888}, {1541459149, 1438855987}, {1538764308, 1439076188},
+   {1538575565, 1436832236}, {1538764308, 1436832236}, {1536509870, 1405374956},
+   {1550497874, 1404347351}, {1550214758, 1402428457}, {1543818445, 1402868859},
+   {1543734559, 1402124370}, {1540672717, 1402344571}, {1540473487, 1399995761},
+   {1524996506, 1400981422}, {1524807762, 1398223667}, {1530092585, 1397898609},
+   {1531675935, 1397783265}, {1531392819, 1394920653}, {1529809469, 1395025510},
+   {1529348096, 1388880855}, {1531099218, 1388660654}, {1530826588, 1385158410},
+   {1532955197, 1384938209}, {1532661596, 1379003269}, {1532472852, 1376235028},
+   {1531277476, 1376350372}, {1530050642, 1361806623}, {1599487345, 1352704983},
+   {1602758902, 1378489467}, {1618990858, 1376350372}, {1615058698, 1344085688},
+   {1603230761, 1345700495}, {1598648484, 1346329641}, {1598931599, 1348667965},
+   {1596698132, 1348993024}, {1595775386, 1342722540} }};
+  Paths64 solution = InflatePaths(subjects, -209715, JoinType::Miter, EndType::Polygon);
+  EXPECT_LE(solution[0].size() - subjects[0].size(), 1);
+}
+TEST(Clipper2Tests, TestOffsets4) // see #482
+{
+  Paths64 paths = { { {0, 0}, {20000, 200},
+    {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} };
+  Paths64 solution = InflatePaths(paths, -5000,
+    JoinType::Square, EndType::Polygon);
+  //std::cout << solution[0].size() << std::endl;
+  EXPECT_EQ(solution[0].size(), 5);
+  paths = { { {0, 0}, {20000, 400},
+    {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} };
+  solution = InflatePaths(paths, -5000,
+    JoinType::Square, EndType::Polygon);
+  //std::cout << solution[0].size() << std::endl;
+  EXPECT_EQ(solution[0].size(), 5);
+  paths = { { {0, 0}, {20000, 400},
+    {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} };
+  solution = InflatePaths(paths, -5000,
+    JoinType::Round, EndType::Polygon, 2, 100);
+  //std::cout << solution[0].size() << std::endl;
+  EXPECT_GT(solution[0].size(), 5);
+  paths = { { {0, 0}, {20000, 1500},
+    {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} };
+  solution = InflatePaths(paths, -5000,
+    JoinType::Round, EndType::Polygon, 2, 100);
+  //std::cout << solution[0].size() << std::endl;
+  EXPECT_GT(solution[0].size(), 5);
+}
+TEST(Clipper2Tests, TestOffsets5) // modified from #593 (tests offset clean up)
+{
+  Paths64 subject = {
+    MakePath({
+      524,1483, 524,2711, 610,2744, 693,2782, 773,2825, 852,2872,
+      927,2924, 999,2980, 1068,3040, 1133,3103, 1195,3171, 1252,3242,
+      1305,3316, 1354,3394, 1398,3473, 1437,3556, 1472,3640, 1502,3727,
+      1526,3815, 1546,3904, 1560,3994, 1569,4085, 1573,4176, 1571,4267,
+      1564,4358, 1552,4449, 1535,4539, 1512,4627, 1485,4714, 1452,4799,
+      1414,4883, 1372,4964, 1325,5042, 1274,5117, 1218,5190, 1158,5259,
+      1094,5324, 1027,5386, 956,5443, 882,5497, 805,5546, 725,5590,
+      643,5630, 559,5665, 524,5677, 524,6906, 610,6939, 693,6977,
+      773,7019, 852,7066, 927,7118, 999,7174, 1068,7234, 1133,7298,
+      1195,7365, 1252,7436, 1305,7511, 1354,7588, 1398,7668, 1437,7750,
+      1472,7835, 1502,7921, 1526,8009, 1546,8098, 1560,8188, 1569,8279,
+      1573,8370, 1571,8462, 1564,8553, 1552,8643, 1535,8733, 1512,8821,
+      1485,8908, 1452,8994, 1414,9077, 1372,9158, 1325,9236, 1274,9312,
+      1218,9384, 1158,9453, 1094,9518, 1027,9580, 956,9638, 882,9691,
+      805,9740, 725,9784, 643,9824, 559,9859, 524,9872, 524,11100,
+      610,11133, 693,11171, 773,11213, 852,11261, 927,11312, 999,11368,
+      1068,11428, 1133,11492, 1195,11560, 1252,11631, 1305,11705, 1354,11782,
+      1398,11862, 1437,11945, 1472,12029, 1502,12115, 1526,12203, 1546,12293,
+      1560,12383, 1569,12474, 1573,12565, 1571,12656, 1564,12747, 1552,12838,
+      1535,12927, 1512,13016, 1485,13103, 1452,13188, 1414,13271, 1372,13352,
+      1325,13431, 1274,13506, 1218,13578, 1158,13647, 1094,13713, 1027,13774,
+      956,13832, 882,13885, 805,13934, 725,13979, 643,14019, 559,14053,
+      524,14066, 524,15295, 610,15327, 693,15365, 773,15408, 852,15455,
+      927,15507, 999,15563, 1068,15623, 1133,15687, 1195,15754, 1252,15825,
+      1305,15899, 1354,15977, 1398,16057, 1437,16139, 1472,16223, 1502,16310,
+      1526,16398, 1546,16487, 1560,16577, 1569,16668, 1573,16759, 1571,16850,
+      1564,16942, 1552,17032, 1535,17122, 1512,17210, 1485,17297, 1452,17382,
+      1414,17466, 1372,17547, 1325,17625, 1274,17700, 1218,17773, 1158,17842,
+      1094,17907, 1027,17969, 956,18026, 882,18080, 805,18129, 725,18173,
+      643,18213, 559,18248, 524,18260, 524,19489, 610,19522, 693,19560,
+      773,19602, 852,19649, 927,19701, 999,19757, 1068,19817, 1133,19881,
+      1195,19948, 1252,20019, 1305,20094, 1354,20171, 1398,20251, 1437,20333,
+      1472,20418, 1502,20504, 1526,20592, 1546,20681, 1560,20771, 1569,20862,
+      1573,20954, 1571,21045, 1564,21136, 1552,21226, 1535,21316, 1512,21404,
+      1485,21492, 1452,21577, 1414,21660, 1372,21741, 1325,21819, 1274,21895,
+      1218,21967, 1158,22036, 1094,22101, 1027,22163, 956,22221, 882,22274,
+      805,22323, 725,22368, 643,22407, 559,22442, 524,22455, 524,23683,
+      610,23716, 693,23754, 773,23797, 852,23844, 927,23895, 999,23951,
+      1068,24011, 1133,24075, 1195,24143, 1252,24214, 1305,24288, 1354,24365,
+      1398,24445, 1437,24528, 1472,24612, 1502,24698, 1526,24786, 1546,24876,
+      1560,24966, 1569,25057, 1573,25148, 1571,25239, 1564,25330, 1552,25421,
+      1535,25510, 1512,25599, 1485,25686, 1452,25771, 1414,25854, 1372,25935,
+      1325,26014, 1274,26089, 1218,26161, 1158,26230, 1094,26296, 1027,26357,
+      956,26415, 882,26468, 805,26517, 725,26562, 643,26602, 559,26636,
+      524,26649, 524,27878, 610,27910, 693,27948, 773,27991, 852,28038,
+      927,28090, 999,28146, 1068,28206, 1133,28270, 1195,28337, 1252,28408,
+      1305,28482, 1354,28560, 1398,28640, 1437,28722, 1472,28806, 1502,28893,
+      1526,28981, 1546,29070, 1560,29160, 1569,29251, 1573,29342, 1571,29434,
+      1564,29525, 1552,29615, 1535,29705, 1512,29793, 1485,29880, 1452,29965,
+      1414,30049, 1372,30130, 1325,30208, 1274,30283, 1218,30356, 1158,30425,
+      1094,30490, 1027,30552, 956,30609, 882,30663, 805,30712, 725,30756,
+      643,30796, 559,30831, 524,30843, 524,32072, 609,32105, 692,32142,
+      773,32185, 851,32232, 926,32283, 998,32339, 1066,32398, 1131,32462,
+      1193,32529, 1250,32600, 1303,32674, 1352,32751, 1396,32830, 1436,32912,
+      1470,32996, 1483,33031, 3131,33031, 3164,32945, 3202,32862, 3244,32781,
+      3291,32703, 3343,32628, 3399,32556, 3459,32487, 3523,32422, 3591,32360,
+      3662,32303, 3736,32250, 3813,32201, 3893,32157, 3975,32117, 4060,32083,
+      4146,32053, 4234,32028, 4323,32009, 4413,31995, 4504,31986, 4596,31982,
+      4687,31984, 4778,31991, 4868,32003, 4958,32020, 5047,32043, 5134,32070,
+      5219,32103, 5302,32141, 5383,32183, 5461,32230, 5537,32281, 5609,32337,
+      5678,32397, 5744,32460, 5805,32528, 5863,32599, 5916,32673, 5965,32750,
+      6010,32830, 6049,32912, 6084,32996, 6097,33031, 7745,33031, 7778,32945,
+      7815,32862, 7858,32781, 7905,32703, 7957,32628, 8013,32556, 8073,32487,
+      8137,32422, 8204,32360, 8275,32303, 8350,32250, 8427,32201, 8507,32157,
+      8589,32117, 8674,32083, 8760,32053, 8848,32028, 8937,32009, 9027,31995,
+      9118,31986, 9209,31982, 9301,31984, 9392,31991, 9482,32003, 9572,32020,
+      9660,32043, 9747,32070, 9833,32103, 9916,32141, 9997,32183, 10075,32230,
+      10151,32281, 10223,32337, 10292,32397, 10357,32460, 10419,32528, 10477,32599,
+      10530,32673, 10579,32750, 10623,32830, 10663,32912, 10698,32996, 10711,33031,
+      12358,33031, 12391,32945, 12429,32862, 12472,32781, 12519,32703, 12571,32628,
+      12627,32556, 12687,32487, 12750,32422, 12818,32360, 12889,32303, 12963,32250,
+      13041,32201, 13120,32157, 13203,32117, 13287,32083, 13374,32053, 13462,32028,
+      13551,32009, 13641,31995, 13732,31986, 13823,31982, 13914,31984, 14005,31991,
+      14096,32003, 14186,32020, 14274,32043, 14361,32070, 14446,32103, 14530,32141,
+      14611,32183, 14689,32230, 14764,32281, 14837,32337, 14906,32397, 14971,32460,
+      15033,32528, 15090,32599, 15144,32673, 15193,32750, 15237,32830, 15277,32912,
+      15312,32996, 15324,33031, 16972,33031, 17005,32945, 17043,32862, 17086,32781,
+      17133,32703, 17184,32628, 17240,32556, 17300,32487, 17364,32422, 17432,32360,
+      17503,32303, 17577,32250, 17654,32201, 17734,32157, 17817,32117, 17901,32083,
+      17987,32053, 18075,32028, 18165,32009, 18255,31995, 18346,31986, 18437,31982,
+      18528,31984, 18619,31991, 18710,32003, 18799,32020, 18888,32043, 18975,32070,
+      19060,32103, 19143,32141, 19224,32183, 19303,32230, 19378,32281, 19450,32337,
+      19519,32397, 19585,32460, 19646,32528, 19704,32599, 19757,32673, 19806,32750,
+      19851,32830, 19891,32912, 19926,32996, 19938,33031, 21586,33031, 21619,32945,
+      21657,32862, 21699,32781, 21747,32703, 21798,32628, 21854,32556, 21914,32487,
+      21978,32422, 22046,32360, 22117,32303, 22191,32250, 22268,32201, 22348,32157,
+      22430,32117, 22515,32083, 22601,32053, 22689,32028, 22778,32009, 22869,31995,
+      22959,31986, 23051,31982, 23142,31984, 23233,31991, 23324,32003, 23413,32020,
+      23502,32043, 23589,32070, 23674,32103, 23757,32141, 23838,32183, 23916,32230,
+      23992,32281, 24064,32337, 24133,32397, 24199,32460, 24260,32528, 24318,32599,
+      24371,32673, 24420,32750, 24465,32830, 24504,32912, 24539,32996, 24552,33031,
+      26200,33031, 26233,32945, 26271,32862, 26313,32781, 26360,32703, 26412,32628,
+      26468,32556, 26528,32487, 26592,32422, 26659,32360, 26730,32303, 26805,32250,
+      26882,32201, 26962,32157, 27044,32117, 27129,32083, 27215,32053, 27303,32028,
+      27392,32009, 27482,31995, 27573,31986, 27664,31982, 27756,31984, 27847,31991,
+      27937,32003, 28027,32020, 28115,32043, 28202,32070, 28288,32103, 28371,32141,
+      28452,32183, 28530,32230, 28606,32281, 28678,32337, 28747,32397, 28812,32460,
+      28874,32528, 28932,32599, 28985,32673, 29034,32750, 29078,32830, 29118,32912,
+      29153,32996, 29166,33031, 30814,33031, 30847,32945, 30884,32862, 30927,32781,
+      30974,32703, 31026,32628, 31082,32556, 31142,32487, 31206,32422, 31273,32360,
+      31344,32303, 31418,32250, 31496,32201, 31576,32157, 31658,32117, 31742,32083,
+      31829,32053, 31917,32028, 32006,32009, 32096,31995, 32187,31986, 32278,31982,
+      32370,31984, 32461,31991, 32551,32003, 32641,32020, 32729,32043, 32816,32070,
+      32902,32103, 32985,32141, 33066,32183, 33144,32230, 33219,32281, 33292,32337,
+      33361,32397, 33426,32460, 33488,32528, 33545,32599, 33599,32673, 33648,32750,
+      33692,32830, 33732,32912, 33767,32996, 33779,33031, 35427,33031, 35460,32946,
+      35498,32863, 35540,32782, 35587,32704, 35639,32629, 35694,32557, 35754,32489,
+      35818,32423, 35885,32362, 35956,32305, 36029,32252, 36106,32203, 36186,32159,
+      36268,32119, 36352,32084, 36386,32072, 36386,30843, 36301,30810, 36218,30773,
+      36137,30730, 36059,30683, 35983,30631, 35911,30575, 35842,30515, 35777,30451,
+      35716,30384, 35658,30313, 35605,30239, 35557,30161, 35512,30081, 35473,29999,
+      35438,29915, 35409,29828, 35384,29740, 35364,29651, 35350,29561, 35341,29470,
+      35338,29379, 35339,29287, 35346,29196, 35358,29106, 35376,29016, 35398,28928,
+      35426,28841, 35458,28755, 35496,28672, 35538,28591, 35585,28513, 35637,28438,
+      35692,28365, 35752,28296, 35816,28231, 35883,28169, 35954,28112, 36028,28058,
+      36105,28009, 36185,27965, 36267,27925, 36352,27890, 36386,27878, 36386,26649,
+      36301,26616, 36218,26578, 36137,26536, 36059,26489, 35983,26437, 35911,26381,
+      35842,26321, 35777,26257, 35716,26189, 35658,26118, 35605,26044, 35557,25967,
+      35512,25887, 35473,25805, 35438,25720, 35409,25634, 35384,25546, 35364,25457,
+      35350,25366, 35341,25276, 35338,25184, 35339,25093, 35346,25002, 35358,24912,
+      35376,24822, 35398,24733, 35426,24646, 35458,24561, 35496,24478, 35538,24397,
+      35585,24319, 35637,24243, 35692,24171, 35752,24102, 35816,24036, 35883,23975,
+      35954,23917, 36028,23864, 36105,23815, 36185,23770, 36267,23731, 36352,23696,
+      36386,23683, 36386,22455, 36301,22422, 36218,22384, 36137,22341, 36059,22294,
+      35983,22243, 35911,22187, 35842,22127, 35777,22063, 35716,21995, 35658,21924,
+      35605,21850, 35557,21773, 35512,21693, 35473,21610, 35438,21526, 35409,21440,
+      35384,21352, 35364,21262, 35350,21172, 35341,21081, 35338,20990, 35339,20899,
+      35346,20808, 35358,20717, 35376,20628, 35398,20539, 35426,20452, 35458,20367,
+      35496,20284, 35538,20203, 35585,20124, 35637,20049, 35692,19976, 35752,19907,
+      35816,19842, 35883,19780, 35954,19723, 36028,19669, 36105,19620, 36185,19576,
+      36267,19536, 36352,19501, 36386,19489, 36386,18260, 36301,18227, 36218,18190,
+      36137,18147, 36059,18100, 35983,18048, 35911,17992, 35842,17932, 35777,17868,
+      35716,17801, 35658,17730, 35605,17655, 35557,17578, 35512,17498, 35473,17416,
+      35438,17332, 35409,17245, 35384,17157, 35364,17068, 35350,16978, 35341,16887,
+      35338,16796, 35339,16704, 35346,16613, 35358,16523, 35376,16433, 35398,16345,
+      35426,16258, 35458,16172, 35496,16089, 35538,16008, 35585,15930, 35637,15854,
+      35692,15782, 35752,15713, 35816,15648, 35883,15586, 35954,15529, 36028,15475,
+      36105,15426, 36185,15382, 36267,15342, 36352,15307, 36386,15295, 36386,14066,
+      36301,14033, 36218,13995, 36137,13953, 36059,13906, 35983,13854, 35911,13798,
+      35842,13738, 35777,13674, 35716,13606, 35658,13535, 35605,13461, 35557,13384,
+      35512,13304, 35473,13222, 35438,13137, 35409,13051, 35384,12963, 35364,12874,
+      35350,12783, 35341,12693, 35338,12601, 35339,12510, 35346,12419, 35358,12328,
+      35376,12239, 35398,12150, 35426,12063, 35458,11978, 35496,11895, 35538,11814,
+      35585,11736, 35637,11660, 35692,11588, 35752,11519, 35816,11453, 35883,11392,
+      35954,11334, 36028,11281, 36105,11232, 36185,11187, 36267,11148, 36352,11113,
+      36386,11100, 36386,9872, 36301,9839, 36218,9801, 36137,9758, 36059,9711,
+      35983,9660, 35911,9604, 35842,9544, 35777,9480, 35716,9412, 35658,9341,
+      35605,9267, 35557,9190, 35512,9110, 35473,9027, 35438,8943, 35409,8856,
+      35384,8769, 35364,8679, 35350,8589, 35341,8498, 35338,8407, 35339,8316,
+      35346,8225, 35358,8134, 35376,8045, 35398,7956, 35426,7869, 35458,7784,
+      35496,7700, 35538,7620, 35585,7541, 35637,7466, 35692,7393, 35752,7324,
+      35816,7259, 35883,7197, 35954,7140, 36028,7086, 36105,7037, 36185,6993,
+      36267,6953, 36352,6918, 36386,6906, 36386,5677, 36301,5644, 36218,5607,
+      36137,5564, 36059,5517, 35983,5465, 35911,5409, 35842,5349, 35777,5285,
+      35716,5218, 35658,5147, 35605,5072, 35557,4995, 35512,4915, 35473,4833,
+      35438,4748, 35409,4662, 35384,4574, 35364,4485, 35350,4395, 35341,4304,
+      35338,4213, 35339,4121, 35346,4030, 35358,3940, 35376,3850, 35398,3762,
+      35426,3675, 35458,3589, 35496,3506, 35538,3425, 35585,3347, 35637,3271,
+      35692,3199, 35752,3130, 35816,3065, 35883,3003, 35954,2945, 36028,2892,
+      36105,2843, 36185,2799, 36267,2759, 36352,2724, 36386,2711, 36386,1483,
+      36301,1450, 36218,1413, 36138,1370, 36060,1323, 35985,1272, 35913,1216,
+      35844,1156, 35779,1093, 35718,1026, 35660,955, 35607,881, 35558,804,
+      35514,725, 35475,643, 35440,559, 35427,524, 33779,524, 33747,610,
+      33709,693, 33666,773, 33619,852, 33567,927, 33511,999, 33451,1068,
+      33387,1133, 33320,1195, 33249,1252, 33175,1305, 33097,1354, 33017,1398,
+      32935,1437, 32851,1472, 32764,1502, 32676,1526, 32587,1546, 32497,1560,
+      32406,1569, 32315,1573, 32223,1571, 32132,1564, 32042,1552, 31952,1535,
+      31864,1512, 31777,1485, 31691,1452, 31608,1414, 31527,1372, 31449,1325,
+      31374,1274, 31301,1218, 31232,1158, 31167,1094, 31105,1027, 31048,956,
+      30994,882, 30945,805, 30901,725, 30861,643, 30826,559, 30814,524,
+      29166,524, 29133,610, 29095,693, 29052,773, 29005,852, 28954,927,
+      28898,999, 28838,1068, 28774,1133, 28706,1195, 28635,1252, 28561,1305,
+      28484,1354, 28404,1398, 28321,1437, 28237,1472, 28150,1502, 28063,1526,
+      27973,1546, 27883,1560, 27792,1569, 27701,1573, 27610,1571, 27519,1564,
+      27428,1552, 27338,1535, 27250,1512, 27163,1485, 27078,1452, 26994,1414,
+      26914,1372, 26835,1325, 26760,1274, 26687,1218, 26618,1158, 26553,1094,
+      26491,1027, 26434,956, 26380,882, 26331,805, 26287,725, 26247,643,
+      26212,559, 26200,524, 24552,524, 24519,610, 24481,693, 24439,773,
+      24391,852, 24340,927, 24284,999, 24224,1068, 24160,1133, 24092,1195,
+      24021,1252, 23947,1305, 23870,1354, 23790,1398, 23708,1437, 23623,1472,
+      23537,1502, 23449,1526, 23360,1546, 23269,1560, 23178,1569, 23087,1573,
+      22996,1571, 22905,1564, 22814,1552, 22725,1535, 22636,1512, 22549,1485,
+      22464,1452, 22381,1414, 22300,1372, 22221,1325, 22146,1274, 22074,1218,
+      22005,1158, 21939,1094, 21878,1027, 21820,956, 21767,882, 21718,805,
+      21673,725, 21633,643, 21599,559, 21586,524, 19938,524, 19905,610,
+      19867,693, 19825,773, 19778,852, 19726,927, 19670,999, 19610,1068,
+      19546,1133, 19478,1195, 19407,1252, 19333,1305, 19256,1354, 19176,1398,
+      19094,1437, 19009,1472, 18923,1502, 18835,1526, 18746,1546, 18656,1560,
+      18565,1569, 18473,1573, 18382,1571, 18291,1564, 18201,1552, 18111,1535,
+      18022,1512, 17935,1485, 17850,1452, 17767,1414, 17686,1372, 17608,1325,
+      17532,1274, 17460,1218, 17391,1158, 17325,1094, 17264,1027, 17206,956,
+      17153,882, 17104,805, 17059,725, 17020,643, 16985,559, 16972,524,
+      15324,524, 15291,610, 15254,693, 15211,773, 15164,852, 15112,927,
+      15056,999, 14996,1068, 14932,1133, 14865,1195, 14794,1252, 14719,1305,
+      14642,1354, 14562,1398, 14480,1437, 14395,1472, 14309,1502, 14221,1526,
+      14132,1546, 14042,1560, 13951,1569, 13860,1573, 13768,1571, 13677,1564,
+      13587,1552, 13497,1535, 13409,1512, 13322,1485, 13236,1452, 13153,1414,
+      13072,1372, 12994,1325, 12918,1274, 12846,1218, 12777,1158, 12712,1094,
+      12650,1027, 12592,956, 12539,882, 12490,805, 12446,725, 12406,643,
+      12371,559, 12358,524, 10711,524, 10678,610, 10640,693, 10597,773,
+      10550,852, 10498,927, 10442,999, 10382,1068, 10319,1133, 10251,1195,
+      10180,1252, 10106,1305, 10028,1354, 9949,1398, 9866,1437, 9782,1472,
+      9695,1502, 9607,1526, 9518,1546, 9428,1560, 9337,1569, 9246,1573,
+      9155,1571, 9064,1564, 8973,1552, 8883,1535, 8795,1512, 8708,1485,
+      8623,1452, 8539,1414, 8458,1372, 8380,1325, 8305,1274, 8232,1218,
+      8163,1158, 8098,1094, 8036,1027, 7979,956, 7925,882, 7876,805,
+      7832,725, 7792,643, 7757,559, 7745,524, 6097,524, 6064,610,
+      6026,693, 5983,773, 5936,852, 5885,927, 5829,999, 5769,1068,
+      5705,1133, 5637,1195, 5566,1252, 5492,1305, 5415,1354, 5335,1398,
+      5252,1437, 5168,1472, 5082,1502, 4994,1526, 4904,1546, 4814,1560,
+      4723,1569, 4632,1573, 4541,1571, 4450,1564, 4359,1552, 4270,1535,
+      4181,1512, 4094,1485, 4009,1452, 3926,1414, 3845,1372, 3766,1325,
+      3691,1274, 3619,1218, 3550,1158, 3484,1094, 3423,1027, 3365,956,
+      3312,882, 3263,805, 3218,725, 3178,643, 3143,559, 3131,524,
+      1483,524, 1450,609, 1413,692, 1370,773, 1323,851, 1272,926,
+      1216,998, 1156,1066, 1093,1131, 1026,1193, 955,1250, 881,1303,
+      804,1352, 725,1396, 643,1436, 559,1470
+      }),
+    MakePath({ -47877,-47877, 84788,-47877, 84788,81432, -47877,81432 })
+  };
+  Paths64 solution = InflatePaths(subject, -10000, JoinType::Round, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 2);
+}
+TEST(Clipper2Tests, TestOffsets6) // also modified from #593 (tests rounded ends)
+{
+  Paths64 subjects = {
+    {{620,620}, {-620,620}, {-620,-620}, {620,-620}},
+    {{20,-277}, {42,-275}, {59,-272}, {80,-266}, {97,-261}, {114,-254},
+    {135,-243}, {149,-235}, {167,-222}, {182,-211}, {197,-197},
+    {212,-181}, {223,-167}, {234,-150}, {244,-133}, {253,-116},
+    {260,-99}, {267,-78}, {272,-61}, {275,-40}, {278,-18}, {276,-39},
+    {272,-61}, {267,-79}, {260,-99}, {253,-116}, {245,-133}, {235,-150},
+    {223,-167}, {212,-181}, {197,-197}, {182,-211}, {168,-222}, {152,-233},
+    {135,-243}, {114,-254}, {97,-261}, {80,-267}, {59,-272}, {42,-275}, {20,-278}}
+  };
+  const double offset = -50;
+  Clipper2Lib::ClipperOffset offseter;
+  Clipper2Lib::Paths64 tmpSubject;
+  offseter.AddPaths(subjects, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon);
+  Clipper2Lib::Paths64 solution;
+  offseter.Execute(offset, solution);
+  EXPECT_EQ(solution.size(), 2);
+  double area = Area<int64_t>(solution[1]);
+  EXPECT_LT(area, -47500);
+}
+TEST(Clipper2Tests, TestOffsets7) // (#593 & #715)
+{
+  Paths64 solution;
+  Paths64 subject = { MakePath({0,0, 100,0, 100,100, 0,100}) };
+  solution = InflatePaths(subject, -50, JoinType::Miter, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 0);
+  subject.push_back(MakePath({ 40,60, 60,60, 60,40, 40,40 }));
+  solution = InflatePaths(subject, 10, JoinType::Miter, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 1);
+  reverse(subject[0].begin(), subject[0].end());
+  reverse(subject[1].begin(), subject[1].end());
+  solution = InflatePaths(subject, 10, JoinType::Miter, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 1);
+  subject.resize(1);
+  solution = InflatePaths(subject, -50, JoinType::Miter, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 0);
+}
+struct OffsetQual
+{
+  PointD smallestInSub;   // smallestInSub & smallestInSol are the points in subject and solution
+  PointD smallestInSol;   // that define the place that most falls short of the expected offset
+  PointD largestInSub;    // largestInSub & largestInSol are the points in subject and solution
+  PointD largestInSol;    // that define the place that most exceeds the expected offset
+};
+template<typename T>
+inline PointD GetClosestPointOnSegment(const PointD& offPt,
+  const Point<T>& seg1, const Point<T>& seg2)
+{
+  if (seg1.x == seg2.x && seg1.y == seg2.y) return PointD(seg1);
+  double dx = static_cast<double>(seg2.x - seg1.x);
+  double dy = static_cast<double>(seg2.y - seg1.y);
+  double q = (
+    (offPt.x - static_cast<double>(seg1.x)) * dx +
+    (offPt.y - static_cast<double>(seg1.y)) * dy) /
+    (Sqr(dx) + Sqr(dy));
+  q = (q < 0) ? 0 : (q > 1) ? 1 : q;
+  return PointD(
+    static_cast<double>(seg1.x) + (q * dx),
+    static_cast<double>(seg1.y) + (q * dy));
+}
+template<typename T>
+static OffsetQual GetOffsetQuality(const Path<T>& subject, const Path<T>& solution, const double delta)
+{
+  if (!subject.size() || !solution.size()) return OffsetQual();
+  double desiredDistSqr = delta * delta;
+  double smallestSqr = desiredDistSqr, largestSqr = desiredDistSqr;
+  double deviationsSqr = 0;
+  OffsetQual oq;
+  const size_t subVertexCount = 4; // 1 .. 100 :)
+  const double subVertexFrac = 1.0 / subVertexCount;
+  Point<T> solPrev = solution[solution.size() - 1];
+  for (const Point<T>& solPt0 : solution)
+  {
+    for (size_t i = 0; i < subVertexCount; ++i)
+    {
+      // divide each edge in solution into series of sub-vertices (solPt),
+      PointD solPt = PointD(
+        static_cast<double>(solPrev.x) + static_cast<double>(solPt0.x - solPrev.x) * subVertexFrac * i,
+        static_cast<double>(solPrev.y) + static_cast<double>(solPt0.y - solPrev.y) * subVertexFrac * i);
+      // now find the closest point in subject to each of these solPt.
+      PointD closestToSolPt;
+      double closestDistSqr = std::numeric_limits<double>::infinity();
+      Point<T> subPrev = subject[subject.size() - 1];
+      for (size_t i = 0; i < subject.size(); ++i)
+      {
+        PointD closestPt = ::GetClosestPointOnSegment(solPt, subject[i], subPrev);
+        subPrev = subject[i];
+        const double sqrDist = DistanceSqr(closestPt, solPt);
+        if (sqrDist < closestDistSqr) {
+          closestDistSqr = sqrDist;
+          closestToSolPt = closestPt;
+        };
+      }
+      // we've now found solPt's closest pt in subject (closestToSolPt).
+      // but how does the distance between these 2 points compare with delta
+      // ideally - Distance(closestToSolPt, solPt) == delta;
+      // see how this distance compares with every other solPt
+      if (closestDistSqr < smallestSqr) {
+        smallestSqr = closestDistSqr;
+        oq.smallestInSub = closestToSolPt;
+        oq.smallestInSol = solPt;
+      }
+      if (closestDistSqr > largestSqr) {
+        largestSqr = closestDistSqr;
+        oq.largestInSub = closestToSolPt;
+        oq.largestInSol = solPt;
+      }
+    }
+    solPrev = solPt0;
+  }
+  return oq;
+}
+TEST(Clipper2Tests, TestOffsets8) // (#724)
+{
+  Paths64 subject = { MakePath({
+       91759700, -49711991,    83886095, -50331657,
+       -872415388, -50331657,  -880288993, -49711991,  -887968725, -47868251,
+       -895265482, -44845834,  -901999593, -40719165,  -908005244, -35589856,
+       -913134553, -29584205,  -917261224, -22850094,  -920283639, -15553337,
+       -922127379, -7873605,   -922747045, 0,          -922747045, 1434498600,
+       -922160557, 1442159790, -920414763, 1449642437, -917550346, 1456772156,
+       -913634061, 1463382794, -908757180, 1469320287, -903033355, 1474446264,
+       -896595982, 1478641262, -889595081, 1481807519, -882193810, 1483871245,
+       -876133965, 1484596521, -876145751, 1484713389, -875781839, 1485061090,
+       -874690056, 1485191762, -874447580, 1485237014, -874341490, 1485264094,
+       -874171960, 1485309394, -873612294, 1485570372, -873201878, 1485980788,
+       -872941042, 1486540152, -872893274, 1486720070, -872835064, 1487162210,
+       -872834788, 1487185500, -872769052, 1487406000, -872297948, 1487583168,
+       -871995958, 1487180514, -871995958, 1486914040, -871908872, 1486364208,
+       -871671308, 1485897962, -871301302, 1485527956, -870835066, 1485290396,
+       -870285226, 1485203310, -868659019, 1485203310, -868548443, 1485188472,
+       -868239649, 1484791011, -868239527, 1484783879, -838860950, 1484783879,
+       -830987345, 1484164215, -823307613, 1482320475, -816010856, 1479298059,
+       -809276745, 1475171390, -803271094, 1470042081, -752939437, 1419710424,
+       -747810128, 1413704773, -743683459, 1406970662, -740661042, 1399673904,
+       -738817302, 1391994173, -738197636, 1384120567, -738197636, 1244148246,
+       -738622462, 1237622613, -739889768, 1231207140, -802710260, 995094494,
+       -802599822, 995052810,  -802411513, 994586048,  -802820028, 993050638,
+       -802879992, 992592029,  -802827240, 992175479,  -802662144, 991759637,
+       -802578556, 991608039,  -802511951, 991496499,  -801973473, 990661435,
+       -801899365, 990554757,  -801842657, 990478841,  -801770997, 990326371,
+       -801946911, 989917545,  -801636397, 989501855,  -801546099, 989389271,
+       -800888669, 988625013,  -800790843, 988518907,  -800082405, 987801675,
+       -799977513, 987702547,  -799221423, 987035738,  -799109961, 986944060,
+       -798309801, 986330832,  -798192297, 986247036,  -797351857, 985690294,
+       -797228867, 985614778,  -796352124, 985117160,  -796224232, 985050280,
+       -795315342, 984614140,  -795183152, 984556216,  -794246418, 984183618,
+       -794110558, 984134924,  -793150414, 983827634,  -793011528, 983788398,
+       -792032522, 983547874,  -791891266, 983518284,  -790898035, 983345662,
+       -790755079, 983325856,  -789752329, 983221956,  -789608349, 983212030,
+       -787698545, 983146276,  -787626385, 983145034,  -536871008, 983145034,
+       -528997403, 982525368,  -521317671, 980681627,  -514020914, 977659211,
+       -507286803, 973532542,  -501281152, 968403233,  -496151843, 962397582,
+       -492025174, 955663471,  -489002757, 948366714,  -487159017, 940686982,
+       -486539351, 932813377,  -486539351, 667455555,  -486537885, 667377141,
+       -486460249, 665302309,  -486448529, 665145917,  -486325921, 664057737,
+       -486302547, 663902657,  -486098961, 662826683,  -486064063, 662673784,
+       -485780639, 661616030,  -485734413, 661466168,  -485372735, 660432552,
+       -485315439, 660286564,  -484877531, 659282866,  -484809485, 659141568,
+       -484297795, 658173402,  -484219379, 658037584,  -483636768, 657110363,
+       -483548422, 656980785,  -482898150, 656099697,  -482800368, 655977081,
+       -482086070, 655147053,  -481979398, 655032087,  -481205068, 654257759,
+       -481090104, 654151087,  -480260074, 653436789,  -480137460, 653339007,
+       -479256372, 652688735,  -479126794, 652600389,  -478199574, 652017779,
+       -478063753, 651939363,  -477095589, 651427672,  -476954289, 651359626,
+       -475950593, 650921718,  -475804605, 650864422,  -474770989, 650502744,
+       -474621127, 650456518,  -473563373, 650173094,  -473410475, 650138196,
+       -472334498, 649934610,  -472179420, 649911236,  -471091240, 649788626,
+       -470934848, 649776906,  -468860016, 649699272,  -468781602, 649697806,
+       -385876037, 649697806,  -378002432, 649078140,  -370322700, 647234400,
+       -363025943, 644211983,  -356291832, 640085314,  -350286181, 634956006,
+       -345156872, 628950354,  -341030203, 622216243,  -338007786, 614919486,
+       -336164046, 607239755,  -335544380, 599366149,  -335544380, 571247184,
+       -335426942, 571236100,  -335124952, 570833446,  -335124952, 569200164,
+       -335037864, 568650330,  -334800300, 568184084,  -334430294, 567814078,
+       -333964058, 567576517,  -333414218, 567489431,  -331787995, 567489431,
+       -331677419, 567474593,  -331368625, 567077133,  -331368503, 567070001,
+       -142068459, 567070001,  -136247086, 566711605,  -136220070, 566848475,
+       -135783414, 567098791,  -135024220, 567004957,  -134451560, 566929159,
+       -134217752, 566913755,  -133983942, 566929159,  -133411282, 567004957,
+       -132665482, 567097135,  -132530294, 567091859,  -132196038, 566715561,
+       -132195672, 566711157,  -126367045, 567070001,  -33554438, 567070001,
+       -27048611, 566647761,   -20651940, 565388127,   -14471751, 563312231,
+       -8611738, 560454902,    36793963, 534548454,    43059832, 530319881,
+       48621743, 525200596,    53354240, 519306071,    57150572, 512769270,
+       59925109, 505737634,    61615265, 498369779,    62182919, 490831896,
+       62182919, 474237629,    62300359, 474226543,    62602349, 473823889,
+       62602349, 472190590,    62689435, 471640752,    62926995, 471174516,
+       63297005, 470804506,    63763241, 470566946,    64313081, 470479860,
+       65939308, 470479860,    66049884, 470465022,    66358678, 470067562,
+       66358800, 470060430,    134217752, 470060430,   134217752, 0,
+       133598086, -7873605,    131754346, -15553337,   128731929, -22850094,
+       124605260, -29584205,   119475951, -35589856,   113470300, -40719165,
+       106736189, -44845834,   99439432, -47868251,    91759700, -49711991
+    }) };
+  double offset = -50329979.277800001, arc_tol = 5000;
+  Paths64 solution = InflatePaths(subject, offset, JoinType::Round, EndType::Polygon, 2, arc_tol);
+  OffsetQual oq = GetOffsetQuality(subject[0], solution[0], offset);
+  double smallestDist = Distance(oq.smallestInSub, oq.smallestInSol);
+  double largestDist = Distance(oq.largestInSub, oq.largestInSol);
+  const double rounding_tolerance = 1.0;
+  offset = std::abs(offset);
+  //std::cout << std::setprecision(0) << std::fixed;
+  //std::cout << "Expected delta           : " << offset << std::endl;
+  //std::cout << "Smallest delta           : " << smallestDist << " (" << smallestDist - offset << ")" << std::endl;
+  //std::cout << "Largest delta            : " << largestDist << " (" << largestDist - offset << ")" << std::endl;
+  //std::cout << "Coords of smallest delta : " << oq.smallestInSub << " and " << oq.smallestInSol << std::endl;
+  //std::cout << "Coords of largest delta  : " << oq.largestInSub << " and " << oq.largestInSol << std::endl;
+  //std::cout << std::endl;
+  //SvgWriter svg;
+  //SvgAddSubject(svg, subject, FillRule::NonZero);
+  //SvgAddSolution(svg, solution, FillRule::NonZero, false);
+  //std::string filename = "offset_test.svg";
+  //SvgSaveToFile(svg, filename, 800, 600, 10);
+  EXPECT_LE(offset - smallestDist - rounding_tolerance, arc_tol);
+  EXPECT_LE(largestDist - offset  - rounding_tolerance, arc_tol);
+}
+TEST(Clipper2Tests, TestOffsets9) // (#733)
+{
+  // solution orientations should match subject orientations UNLESS
+  // reverse_solution is set true in ClipperOffset's constructor
+  // start subject's orientation positive ...
+  Paths64 subject{ MakePath({100,100, 200,100, 200, 400, 100, 400}) };
+  Paths64 solution = InflatePaths(subject, 50, JoinType::Miter, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 1);
+  EXPECT_TRUE(IsPositive(solution[0]));
+  // reversing subject's orientation should not affect delta direction
+  // (ie where positive deltas inflate).
+  std::reverse(subject[0].begin(), subject[0].end());
+  solution = InflatePaths(subject, 50, JoinType::Miter, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 1);
+  EXPECT_TRUE(std::fabs(Area(solution[0])) > std::fabs(Area(subject[0])));
+  EXPECT_FALSE(IsPositive(solution[0]));
+  ClipperOffset co(2, 0, false, true); // last param. reverses solution
+  co.AddPaths(subject, JoinType::Miter, EndType::Polygon);
+  co.Execute(50, solution);
+  EXPECT_EQ(solution.size(), 1);
+  EXPECT_TRUE(std::fabs(Area(solution[0])) > std::fabs(Area(subject[0])));
+  EXPECT_TRUE(IsPositive(solution[0]));
+  // add a hole (ie has reverse orientation to outer path)
+  subject.push_back( MakePath({130,130, 170,130, 170,370, 130,370}) );
+  solution = InflatePaths(subject, 30, JoinType::Miter, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 1);
+  EXPECT_FALSE(IsPositive(solution[0]));
+  co.Clear(); // should still reverse solution orientation
+  co.AddPaths(subject, JoinType::Miter, EndType::Polygon);
+  co.Execute(30, solution);
+  EXPECT_EQ(solution.size(), 1);
+  EXPECT_TRUE(std::fabs(Area(solution[0])) > std::fabs(Area(subject[0])));
+  EXPECT_TRUE(IsPositive(solution[0]));
+  solution = InflatePaths(subject, -15, JoinType::Miter, EndType::Polygon);
+  EXPECT_EQ(solution.size(), 0);
+}

+ 3 - 7
polygon.mod/clipper2/CPP/Tests/TestOrientation.cpp

@@ -1,19 +1,15 @@
 #include <gtest/gtest.h>
 #include "clipper2/clipper.h"
-
 using namespace Clipper2Lib;
-
 TEST(Clipper2Tests, TestNegativeOrientation) {
-
   Paths64 subjects, clips, solution;
   //also test MakePath using specified skip_chars (useful when pasting coords)
-  subjects.push_back(MakePath("(0,0), (0,100) (100,100) (100,0)"));
-  subjects.push_back(MakePath("(10,10) (10,110) (110,110) (110,10)"));
+  subjects.push_back(MakePath({ 0,0, 0,100, 100,100, 100,0 }));
+  subjects.push_back(MakePath({ 10,10, 10,110, 110,110, 110,10 }));
   EXPECT_FALSE(IsPositive(subjects[0]));
   EXPECT_FALSE(IsPositive(subjects[1]));
-  clips.push_back(MakePath("50,50 50,150 150,150 150,50"));
+  clips.push_back(MakePath({ 50,50, 50,150, 150,150, 150,50 }));
   EXPECT_FALSE(IsPositive(clips[0]));
-
   solution = Union(subjects, clips, FillRule::Negative);
   ASSERT_EQ(solution.size(), 1);
   EXPECT_EQ(solution[0].size(), 12);

+ 52 - 49
polygon.mod/clipper2/CPP/Tests/TestPolygons.cpp

@@ -1,7 +1,6 @@
 #include <gtest/gtest.h>
 #include "clipper2/clipper.h"
 #include "ClipFileLoad.h"
-
 inline Clipper2Lib::PathD MakeRandomPath(int width, int height, unsigned vertCnt)
 {
   Clipper2Lib::PathD result;
@@ -10,49 +9,38 @@ inline Clipper2Lib::PathD MakeRandomPath(int width, int height, unsigned vertCnt
     result.push_back(Clipper2Lib::PointD(double(rand()) / RAND_MAX * width, double(rand()) / RAND_MAX * height));
   return result;
 }
-
 template <size_t N>
 inline bool IsInList(int num, const int (&intArray)[N])
-{  
+{
   const int* list = &intArray[0];
   for (int cnt = N; cnt; --cnt)
     if (num == *list++) return true;
   return false;
 }
-
 TEST(Clipper2Tests, TestMultiplePolygons)
 {
   std::ifstream ifs("Polygons.txt");
-
-
-  ASSERT_TRUE(ifs);
   ASSERT_TRUE(ifs.good());
-
-  int test_number = 1;
-  while (true)
+  const int start_num = 1;
+  const int end_num = 1000;
+  int test_number = start_num;
+  while (test_number <= end_num)
   {
     Clipper2Lib::Paths64 subject, subject_open, clip;
     Clipper2Lib::Paths64 solution, solution_open;
     Clipper2Lib::ClipType ct;
     Clipper2Lib::FillRule fr;
     int64_t stored_area, stored_count;
-
-    if (!LoadTestNum(ifs, test_number, 
+    if (!LoadTestNum(ifs, test_number,
       subject, subject_open, clip, stored_area, stored_count, ct, fr)) break;
-
     // check Paths64 solutions
     Clipper2Lib::Clipper64 c;
     c.AddSubject(subject);
     c.AddOpenSubject(subject_open);
     c.AddClip(clip);
     c.Execute(ct, fr, solution, solution_open);
-
     const int64_t measured_area = static_cast<int64_t>(Area(solution));
-    const int64_t measured_count = static_cast<int64_t>(solution.size() + solution_open.size());    
-    const int64_t count_diff = stored_count <= 0 ? 0 : std::abs(measured_count - stored_count);
-    const int64_t area_diff = stored_area <= 0 ? 0 : std::abs(measured_area - stored_area);
-    double area_diff_ratio = (area_diff == 0) ? 0 : std::fabs((double)(area_diff) / measured_area);
-    
+    const int64_t measured_count = static_cast<int64_t>(solution.size() + solution_open.size());
     // check the polytree variant too
     Clipper2Lib::PolyTree64 solution_polytree;
     Clipper2Lib::Paths64 solution_polytree_open;
@@ -61,45 +49,60 @@ TEST(Clipper2Tests, TestMultiplePolygons)
     clipper_polytree.AddOpenSubject(subject_open);
     clipper_polytree.AddClip(clip);
     clipper_polytree.Execute(ct, fr, solution_polytree, solution_polytree_open);
-    
-    const int64_t measured_area_pt = 
+    const int64_t measured_area_polytree =
       static_cast<int64_t>(solution_polytree.Area());
     const auto solution_polytree_paths = PolyTreeToPaths64(solution_polytree);
-    const int64_t measured_count_pt = static_cast<int64_t>(solution_polytree_paths.size());
-
+    const int64_t measured_count_polytree =
+      static_cast<int64_t>(solution_polytree_paths.size());
     // check polygon counts
-    if (stored_count <= 0) 
+    if (stored_count <= 0)
       ; // skip count
-    else if (IsInList(test_number, { 23, 27 }))
-      EXPECT_LE(count_diff, 2);
-    else if (IsInList(test_number, { 37, 43, 87, 102, 111, 118, 183, 185 }))
-      EXPECT_LE(count_diff, 1);
-    else if (test_number >= 120)
-      EXPECT_LE((double)count_diff / stored_count, 0.05);
-    else if (count_diff > 0)
-      EXPECT_EQ(count_diff, 0);
-
+    else if (IsInList(test_number, { 120, 121, 130, 138,
+      140, 148, 163, 165, 166, 167, 168, 172, 175, 178, 180 }))
+      EXPECT_NEAR(measured_count, stored_count, 5) << " in test " << test_number;
+    else if (IsInList(test_number, { 27, 181 }))
+      EXPECT_NEAR(measured_count, stored_count, 2) << " in test " << test_number;
+    else if (test_number >= 120 && test_number <= 184)
+      EXPECT_NEAR(measured_count, stored_count, 2) << " in test " << test_number;
+    else if (IsInList(test_number, { 23, 45, 87, 102, 111, 113, 191 }))
+      EXPECT_NEAR(measured_count, stored_count, 1) << " in test " << test_number;
+    else
+      EXPECT_EQ(measured_count, stored_count) << " in test " << test_number;
     // check polygon areas
     if (stored_area <= 0)
       ; // skip area
-    else if (IsInList(test_number, { 22, 23, 24 }))
-      EXPECT_LE(area_diff, 8);
-    else if (area_diff > 100)
-      EXPECT_LE((double)area_diff/stored_area, 0.005);
-
-    EXPECT_EQ(measured_area, measured_area_pt);
-    EXPECT_EQ(measured_count, measured_count_pt);
-
+    else if (IsInList(test_number, { 19, 22, 23, 24 }))
+      EXPECT_NEAR(measured_area, stored_area, 0.5 * measured_area) << " in test " << test_number;
+    else if (test_number == 193)
+      EXPECT_NEAR(measured_area, stored_area, 0.2 * measured_area) << " in test " << test_number;
+    else if (test_number == 63)
+      EXPECT_NEAR(measured_area, stored_area, 0.1 * measured_area) << " in test " << test_number;
+    else if (test_number == 16)
+      EXPECT_NEAR(measured_area, stored_area, 0.075 * measured_area) << " in test " << test_number;
+    else if (test_number == 26)
+      EXPECT_NEAR(measured_area, stored_area, 0.05 * measured_area) << " in test " << test_number;
+    else if (IsInList(test_number, { 15, 52, 53, 54, 59, 60, 64, 117, 119, 184 }))
+      EXPECT_NEAR(measured_area, stored_area, 0.02 * measured_area) << " in test " << test_number;
+    else
+      EXPECT_NEAR(measured_area, stored_area, 0.01 * measured_area) << " in test " << test_number;
+    EXPECT_EQ(measured_count, measured_count_polytree)
+      << " in test " << test_number;
+    EXPECT_EQ(measured_area, measured_area_polytree)
+      << " in test " << test_number;
     ++test_number;
   }
-  EXPECT_GE(test_number, 188);
-
+  //EXPECT_GE(test_number, 188);
   Clipper2Lib::PathsD subjd, clipd, solutiond;
   Clipper2Lib::FillRule frd = Clipper2Lib::FillRule::NonZero;
-
-  subjd.push_back(MakeRandomPath(800, 600, 100));
-  clipd.push_back(MakeRandomPath(800, 600, 100));
-  solutiond = Clipper2Lib::Intersect(subjd, clipd, Clipper2Lib::FillRule::NonZero);
-  EXPECT_GE(solutiond.size(), 1);
-
+}
+TEST(Clipper2Tests, TestHorzSpikes) //#720
+{
+  Clipper2Lib::Paths64 paths = {
+    Clipper2Lib::MakePath({1600,0, 1600,100, 2050,100, 2050,300, 450,300, 450, 0}),
+    Clipper2Lib::MakePath({1800,200, 1800,100, 1600,100, 2000,100, 2000,200}) };
+  std::cout << paths << std::endl;
+  Clipper2Lib::Clipper64 c;
+  c.AddSubject(paths);
+  c.Execute(Clipper2Lib::ClipType::Union, Clipper2Lib::FillRule::NonZero, paths);
+  EXPECT_GE(paths.size(), 1);
 }

+ 216 - 0
polygon.mod/clipper2/CPP/Tests/TestPolytreeHoles.cpp

@@ -0,0 +1,216 @@
+#include <gtest/gtest.h>
+#include "clipper2/clipper.h"
+#include "ClipFileLoad.h"
+using namespace Clipper2Lib;
+TEST(Clipper2Tests, TestPolytreeHoles1)
+{
+  std::ifstream ifs("PolytreeHoleOwner.txt");
+  ASSERT_TRUE(ifs.good());
+  Paths64 subject, subject_open, clip;
+  PolyTree64 solution;
+  Paths64 solution_open;
+  ClipType ct = ClipType::None;
+  FillRule fr = FillRule::EvenOdd;
+  int64_t area = 0, count = 0;
+  bool success = false;
+  ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr));
+  Clipper64 c;
+  c.AddSubject(subject);
+  c.AddOpenSubject(subject_open);
+  c.AddClip(clip);
+  c.Execute(ct, fr, solution, solution_open);
+  EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution));
+}
+void PolyPathContainsPoint(const PolyPath64& pp, const Point64 pt, int& counter)
+{
+  if (pp.Polygon().size() > 0)
+  {
+    if (PointInPolygon(pt, pp.Polygon()) != PointInPolygonResult::IsOutside)
+    {
+      if (pp.IsHole()) --counter;
+      else  ++counter;
+    }
+  }
+  for (const auto& child : pp)
+    PolyPathContainsPoint(*child, pt, counter);
+}
+bool PolytreeContainsPoint(const PolyPath64& pp, const Point64 pt)
+{
+  int counter = 0;
+  for (const auto& child : pp)
+    PolyPathContainsPoint(*child, pt, counter);
+  EXPECT_GE(counter, 0); //ie 'pt' can't be inside more holes than outers
+  return counter != 0;
+}
+void GetPolyPathArea(const PolyPath64& pp, double& area)
+{
+  area += Area(pp.Polygon());
+  for (const auto& child : pp)
+    GetPolyPathArea(*child, area);
+}
+double GetPolytreeArea(const PolyPath64& pp)
+{
+  double result = 0;
+  for (const auto& child : pp)
+    GetPolyPathArea(*child, result);
+  return result;
+}
+TEST(Clipper2Tests, TestPolytreeHoles2)
+{
+  std::ifstream ifs("PolytreeHoleOwner2.txt");
+  ASSERT_TRUE(ifs);
+  ASSERT_TRUE(ifs.good());
+  Paths64 subject, subject_open, clip;
+  ClipType ct = ClipType::None;
+  FillRule fr = FillRule::EvenOdd;
+  int64_t area = 0, count = 0;
+  ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr));
+  const std::vector<Point64> points_of_interest_outside = {
+     Point64(21887, 10420),
+     Point64(21726, 10825),
+     Point64(21662, 10845),
+     Point64(21617, 10890)
+  };
+  // confirm that each 'points_of_interest_outside' is outside every subject,
+  for (const auto& poi_outside : points_of_interest_outside)
+  {
+    int outside_subject_count = 0;
+    for (const auto& path : subject)
+      if (PointInPolygon(poi_outside, path) != PointInPolygonResult::IsOutside)
+        ++outside_subject_count;
+    EXPECT_EQ(outside_subject_count, 0);
+  }
+  const std::vector<Point64> points_of_interest_inside = {
+     Point64(21887, 10430),
+     Point64(21843, 10520),
+     Point64(21810, 10686),
+     Point64(21900, 10461)
+  };
+  // confirm that each 'points_of_interest_inside' is inside a subject,
+  // and inside only one subject (to exclude possible subject holes)
+  for (const auto& poi_inside : points_of_interest_inside)
+  {
+    int inside_subject_count = 0;
+    for (const auto& path : subject)
+    {
+      if (PointInPolygon(poi_inside, path) != PointInPolygonResult::IsOutside)
+        ++inside_subject_count;
+    }
+    EXPECT_EQ(inside_subject_count, 1);
+  }
+  PolyTree64 solution_tree;
+  Paths64 solution_open;
+  Clipper64 c;
+  c.AddSubject(subject);
+  c.AddOpenSubject(subject_open);
+  c.AddClip(clip);
+  c.Execute(ct, FillRule::Negative, solution_tree, solution_open);
+  const auto solution_paths = PolyTreeToPaths64(solution_tree);
+  ASSERT_FALSE(solution_paths.empty());
+  const double subject_area = -Area(subject); //negate (see fillrule)
+  const double solution_tree_area = GetPolytreeArea(solution_tree);
+  const double solution_paths_area = Area(solution_paths);
+  // 1a. check solution_paths_area  is smaller than subject_area
+  EXPECT_LT(solution_paths_area, subject_area);
+  // 1b. but not too much smaller
+  EXPECT_GT(solution_paths_area, (subject_area * 0.92));
+  // 2. check solution_tree's area matches solution_paths' area
+  EXPECT_NEAR(solution_tree_area, solution_paths_area, 0.0001);
+  // 3. check that all children are inside their parents
+  EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution_tree));
+  // 4. confirm all 'point_of_interest_outside' are outside polytree
+  for (const auto& poi_outside : points_of_interest_outside)
+    EXPECT_FALSE(PolytreeContainsPoint(solution_tree, poi_outside));
+  // 5. confirm all 'point_of_interest_inside' are inside polytree
+  for (const auto& poi_inside : points_of_interest_inside)
+    EXPECT_TRUE(PolytreeContainsPoint(solution_tree, poi_inside));
+}
+TEST(Clipper2Tests, TestPolytreeHoles3)
+{
+  Paths64 subject, clip, sol;
+  PolyTree64 solution;
+  Clipper64 c;
+  subject.push_back(MakePath({ 1072,501, 1072,501, 1072,539, 1072,539, 1072,539, 870,539,
+    870,539, 870,539, 870,520, 894,520, 898,524, 911,524, 915,520, 915,520, 936,520,
+    940,524, 953,524, 957,520, 957,520, 978,520, 983,524, 995,524, 1000,520, 1021,520,
+    1025,524, 1038,524, 1042,520, 1038,516, 1025,516, 1021,520, 1000,520, 995,516,
+    983,516, 978,520, 957,520, 953,516, 940,516, 936,520, 915,520, 911,516, 898,516,
+    894,520, 870,520, 870,516, 870,501, 870,501, 870,501, 1072,501 }));
+  clip.push_back(MakePath({ 870,501, 971,501, 971,539, 870,539 }));
+  c.AddSubject(subject);
+  c.AddClip(clip);
+  c.Execute(ClipType::Intersection, FillRule::NonZero, solution);
+  EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 2);
+}
+TEST(Clipper2Tests, TestPolytreeHoles4) //#618
+{
+  Paths64 subject;
+  PolyTree64 solution;
+  Clipper64 c;
+  subject.push_back(MakePath({ 50,500, 50,300, 100,300, 100,350, 150,350,
+    150,250, 200,250, 200,450, 350,450, 350,200, 400,200, 400,225, 450,225,
+    450,175, 400,175, 400,200, 350,200, 350,175, 200,175, 200,250, 150,250,
+    150,200, 100,200, 100,300, 50,300, 50,125, 500,125, 500,500 }));
+  subject.push_back(MakePath({ 250,425, 250,375,  300,375, 300,425 }));
+  c.AddSubject(subject);
+  c.Execute(ClipType::Union, FillRule::NonZero, solution);
+  // Polytree root
+  //   +- Polygon with 3 holes.
+  //      +- Hole with 1 nested polygon.
+  //         +-Polygon
+  //      +- Hole
+  //      +- Hole
+  EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 3);
+}
+TEST(Clipper2Tests, TestPolytreeHoles5)
+{
+  Paths64 subject, clip;
+  subject.push_back(MakePath({ 0,30, 400,30, 400,100, 0,100 }));
+  clip.push_back(MakePath({ 20,30, 30,30, 30,150, 20,150 }));
+  clip.push_back(MakePath({ 200,0, 300,0, 300,30, 280,30, 280,20, 220,20, 220,30, 200,30 }));
+  clip.push_back(MakePath({ 200,50, 300,50, 300,80, 200,80 }));
+  Clipper64 c;
+  c.AddSubject(subject);
+  c.AddClip(clip);
+  PolyTree64 tree;
+  c.Execute(ClipType::Xor, FillRule::NonZero, tree);
+  ////std::cout << tree << std::endl;
+  //Polytree with 3 polygons.
+  //  + -Polygon (2) contains 2 holes.
+  EXPECT_TRUE(tree.Count() == 3 && tree[2]->Count() == 2);
+}
+TEST(Clipper2Tests, TestPolytreeHoles6) //#618
+{
+  Paths64 subject, clip;
+  subject.push_back(MakePath({ 150,50, 200,50, 200,100, 150,100 }));
+  subject.push_back(MakePath({ 125,100, 150,100, 150,150, 125,150 }));
+  subject.push_back(MakePath({ 225,50, 300,50, 300,80, 225,80 }));
+  subject.push_back(MakePath({ 225,100, 300,100, 300,150, 275,150, 275,175, 260,175,
+    260,250, 235,250, 235,300, 275,300, 275,275, 300,275, 300,350, 225,350 }));
+  subject.push_back(MakePath({ 300,150, 350,150, 350,175, 300,175 }));
+  clip.push_back(MakePath({ 0,0, 400,0, 400,50, 0,50 }));
+  clip.push_back(MakePath({ 0,100, 400,100, 400,150, 0,150 }));
+  clip.push_back(MakePath({ 260,175, 325,175, 325,275, 260,275 }));
+  Clipper64 c;
+  c.AddSubject(subject);
+  c.AddClip(clip);
+  PolyTree64 tree;
+  c.Execute(ClipType::Xor, FillRule::NonZero, tree);
+  ////std::cout << tree << std::endl;
+  //Polytree with 3 polygons.
+  //  + -Polygon (2) contains 1 holes.
+  EXPECT_TRUE(tree.Count() == 3 && tree[2]->Count() == 1);
+}
+TEST(Clipper2Tests, TestPolytreeHoles7) //#618
+{
+  Paths64 subject;
+  subject.push_back(MakePath({ 0, 0, 100000, 0, 100000, 100000, 200000, 100000,
+    200000, 0, 300000, 0, 300000, 200000, 0, 200000 }));
+  subject.push_back(MakePath({ 0, 0, 0, -100000, 250000, -100000, 250000, 0 }));
+  PolyTree64 polytree;
+  Clipper64 c;
+  c.AddSubject(subject);
+  c.Execute(ClipType::Union, FillRule::NonZero, polytree);
+  //std::cout << polytree << std::endl;
+  EXPECT_TRUE(polytree.Count() == 1 && polytree[0]->Count() == 1);
+}

+ 0 - 31
polygon.mod/clipper2/CPP/Tests/TestPolytreeHoles1.cpp

@@ -1,31 +0,0 @@
-#include <gtest/gtest.h>
-#include "clipper2/clipper.h"
-#include "ClipFileLoad.h"
-
-using namespace Clipper2Lib;
-
-TEST(Clipper2Tests, TestPolytreeHoles1)
-{
-  std::ifstream ifs("PolytreeHoleOwner.txt");
-  ASSERT_TRUE(ifs);
-  ASSERT_TRUE(ifs.good());
-
-  Paths64 subject, subject_open, clip;
-  PolyTree64 solution;
-  Paths64 solution_open;
-  ClipType ct;
-  FillRule fr;
-  int64_t area, count;
-
-  bool success = false;
-  ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr));
-
-  Clipper64 c;
-  c.AddSubject(subject);
-  c.AddOpenSubject(subject_open);
-  c.AddClip(clip);
-  c.Execute(ct, fr, solution, solution_open);
-
-  EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution));
-
-}

+ 0 - 132
polygon.mod/clipper2/CPP/Tests/TestPolytreeHoles2.cpp

@@ -1,132 +0,0 @@
-#include <gtest/gtest.h>
-#include "clipper2/clipper.h"
-#include "ClipFileLoad.h"
-
-using namespace Clipper2Lib;
-
-
-void PolyPathContainsPoint(const PolyPath64& pp, const Point64 pt, int& counter)
-{
-  if (pp.Polygon().size() > 0)
-  {
-    if (PointInPolygon(pt, pp.Polygon()) != PointInPolygonResult::IsOutside)
-    {
-      if (pp.IsHole()) --counter;
-      else  ++counter;
-    }
-  }
-  for (const auto& child : pp)
-    PolyPathContainsPoint(*child, pt, counter);
-}
-
-bool PolytreeContainsPoint(const PolyPath64& pp, const Point64 pt)
-{
-  int counter = 0;
-  for (const auto& child : pp)
-    PolyPathContainsPoint(*child, pt, counter);
-  EXPECT_GE(counter, 0); //ie 'pt' can't be inside more holes than outers
-  return counter != 0;
-}
-
-void GetPolyPathArea(const PolyPath64& pp, double& area)
-{
-  area += Area(pp.Polygon());
-  for (const auto& child : pp)
-    GetPolyPathArea(*child, area);
-}
-
-double GetPolytreeArea(const PolyPath64& pp)
-{
-  double result = 0;
-  for (const auto& child : pp)
-    GetPolyPathArea(*child, result);
-  return result;
-}
-
-TEST(Clipper2Tests, TestPolytreeHoles2)
-{
-  std::ifstream ifs("PolytreeHoleOwner2.txt");
-
-  ASSERT_TRUE(ifs);
-  ASSERT_TRUE(ifs.good());
-
-  Paths64 subject, subject_open, clip;
-  ClipType ct;
-  FillRule fr;
-  int64_t area, count;
-
-  ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr));
-
-  const std::vector<Point64> points_of_interest_outside = {
-     Point64(21887, 10420),
-     Point64(21726, 10825),
-     Point64(21662, 10845),
-     Point64(21617, 10890)
-  };
-
-  // confirm that each 'points_of_interest_outside' is outside every subject,
-  for (const auto& poi_outside : points_of_interest_outside)
-  {
-    int outside_subject_count = 0;
-    for (const auto& path : subject)
-      if (PointInPolygon(poi_outside, path) != PointInPolygonResult::IsOutside)
-        ++outside_subject_count;
-    EXPECT_EQ(outside_subject_count, 0);
-  }
-
-  const std::vector<Point64> points_of_interest_inside = {
-     Point64(21887, 10430),
-     Point64(21843, 10520),
-     Point64(21810, 10686),
-     Point64(21900, 10461)
-  };
-
-  // confirm that each 'points_of_interest_inside' is inside a subject,
-  // and inside only one subject (to exclude possible subject holes)
-  for (const auto& poi_inside : points_of_interest_inside)
-  {
-    int inside_subject_count = 0;
-    for (const auto& path : subject)
-    {
-      if (PointInPolygon(poi_inside, path) != PointInPolygonResult::IsOutside)
-        ++inside_subject_count;
-    }
-    EXPECT_EQ(inside_subject_count, 1);
-  }
-
-  PolyTree64 solution_tree;
-  Paths64 solution_open;
-  Clipper64 c;
-  c.AddSubject(subject);
-  c.AddOpenSubject(subject_open);
-  c.AddClip(clip);
-  c.Execute(ct, FillRule::Negative, solution_tree, solution_open);
-
-  const auto solution_paths = PolyTreeToPaths64(solution_tree);
-
-  ASSERT_FALSE(solution_paths.empty());
-  
-  const double subject_area         = -Area(subject); //negate (see fillrule)
-  const double solution_tree_area   = GetPolytreeArea(solution_tree);
-  const double solution_paths_area  = Area(solution_paths);
-
-  // 1a. check solution_paths_area  is smaller than subject_area
-  EXPECT_LT(solution_paths_area, subject_area);
-  // 1b. but not too much smaller
-  EXPECT_GT(solution_paths_area, (subject_area * 0.95)); 
-
-  // 2. check solution_tree's area matches solution_paths' area
-  EXPECT_NEAR(solution_tree_area, solution_paths_area, 0.0001);
-
-  // 3. check that all children are inside their parents
-  EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution_tree));
-
-  // 4. confirm all 'point_of_interest_outside' are outside polytree
-  for (const auto& poi_outside : points_of_interest_outside)
-    EXPECT_FALSE(PolytreeContainsPoint(solution_tree, poi_outside));
-
-  // 5. confirm all 'point_of_interest_inside' are inside polytree
-  for (const auto& poi_inside : points_of_interest_inside)
-    EXPECT_TRUE(PolytreeContainsPoint(solution_tree, poi_inside));
-
-}

+ 4 - 10
polygon.mod/clipper2/CPP/Tests/TestPolytreeIntersection.cpp

@@ -1,30 +1,24 @@
 #include <gtest/gtest.h>
 #include "clipper2/clipper.h"
-
 using namespace Clipper2Lib;
-
 TEST(Clipper2Tests, TestPolyTreeIntersection)
 {
     Clipper64 clipper;
-
-    Paths64 subject; 
-    subject.push_back( MakePath("0,0  0,5  5,5  5,0") );
+    Paths64 subject;
+    subject.push_back(MakePath({ 0,0, 0,5, 5,5, 5,0 }));
     clipper.AddSubject(subject);
     Paths64 clip;
-    clip.push_back( MakePath("1,1  1,6  6,6  6,1") );
+    clip.push_back(MakePath({ 1,1,  1,6,  6,6,  6,1 }));
     clipper.AddClip (clip);
-
     PolyTree64 solution;
     Paths64 open_paths;
-
     if (IsPositive(subject[0]))
       clipper.Execute(ClipType::Intersection,
         FillRule::Positive, solution, open_paths);
     else
       clipper.Execute(ClipType::Intersection,
         FillRule::Negative, solution, open_paths);
-
     EXPECT_EQ(open_paths.size(), 0);
     ASSERT_EQ(solution.Count(), 1);
     EXPECT_EQ(solution[0]->Polygon().size(), 4);
-}
+}

+ 5 - 11
polygon.mod/clipper2/CPP/Tests/TestPolytreeUnion.cpp

@@ -1,32 +1,26 @@
 #include <gtest/gtest.h>
 #include "clipper2/clipper.h"
-
 using namespace Clipper2Lib;
-
 TEST(Clipper2Tests, TestPolytreeUnion) {
-
     Paths64 subject;
-    subject.push_back(MakePath("0,0  0,5  5,5  5,0"));
-    subject.push_back(MakePath("1,1  1,6  6,6  6,1"));
-
+    subject.push_back(MakePath({ 0,0, 0,5, 5,5, 5,0 }));
+    subject.push_back(MakePath({ 1,1, 1,6, 6,6, 6,1 }));
     Clipper64 clipper;
     clipper.AddSubject(subject);
-
     PolyTree64 solution;
     Paths64 open_paths;
     if (IsPositive(subject[0]))
-      clipper.Execute(ClipType::Union, 
+      clipper.Execute(ClipType::Union,
         FillRule::Positive, solution, open_paths);
     else
     {
       //because clipping ops normally return Positive solutions
-      clipper.ReverseSolution = true;
+      clipper.ReverseSolution(true);
       clipper.Execute(ClipType::Union,
         FillRule::Negative, solution, open_paths);
     }
-
     EXPECT_EQ(open_paths.size(), 0);
     ASSERT_EQ(solution.Count(), 1);
     EXPECT_EQ(solution[0]->Polygon().size(), 8);
     EXPECT_EQ(IsPositive(subject[0]), IsPositive(solution[0]->Polygon()));
-}
+}

+ 16 - 91
polygon.mod/clipper2/CPP/Tests/TestRandomPaths.cpp

@@ -2,31 +2,25 @@
 #include "clipper2/clipper.h"
 #include <fstream>
 #include <random>
-
-int GenerateRandomInteger(std::default_random_engine& rng, int min_value, int max_value)
+int GenerateRandomInt(std::default_random_engine& rng, int min_value, int max_value)
 {
   if (min_value == max_value)
     return min_value;
-
   std::uniform_int_distribution<int> distribution(min_value, max_value);
   return distribution(rng);
 }
-
 Clipper2Lib::Paths64 GenerateRandomPaths(std::default_random_engine& rng, int min_path_count, int max_complexity)
 {
   std::uniform_int_distribution<int> first_point_coordinate_distribution(-max_complexity, max_complexity * 2);
   std::uniform_int_distribution<int> difference_to_previous_point_distribution(-5, 5);
-
-  const int path_count = GenerateRandomInteger(rng, min_path_count, max_complexity);
+  const int path_count = GenerateRandomInt(rng, min_path_count, max_complexity);
   Clipper2Lib::Paths64 result(path_count);
-
   for (int path = 0; path < path_count; ++path)
   {
-    const int min_point_count = 3;
-    const int path_length = GenerateRandomInteger(rng, min_point_count, std::max(min_point_count, max_complexity));
+    const int min_point_count = 0;
+    const int path_length = GenerateRandomInt(rng, min_point_count, std::max(min_point_count, max_complexity));
     auto& result_path = result[path];
     result_path.reserve(path_length);
-
     for (int point = 0; point < path_length; ++point) {
       if (result_path.empty()) {
         result_path.emplace_back(
@@ -45,74 +39,9 @@ Clipper2Lib::Paths64 GenerateRandomPaths(std::default_random_engine& rng, int mi
   }
   return result;
 }
-
-std::string ToString(Clipper2Lib::ClipType ct)
-{
-  switch (ct)
-  {
-  case Clipper2Lib::ClipType::None:         return "NONE";
-  case Clipper2Lib::ClipType::Intersection: return "INTERSECTION";
-  case Clipper2Lib::ClipType::Union:        return "UNION";
-  case Clipper2Lib::ClipType::Difference:   return "DIFFERENCE";
-  case Clipper2Lib::ClipType::Xor:          return "XOR";
-  default: throw std::runtime_error("Unexpected clip type: " + std::to_string(static_cast<int>(ct)));
-  }
-}
-
-std::string ToString(Clipper2Lib::FillRule fr)
-{
-  switch (fr)
-  {
-  case Clipper2Lib::FillRule::EvenOdd:  return "EVENODD";
-  case Clipper2Lib::FillRule::NonZero:  return "NONZERO";
-  case Clipper2Lib::FillRule::Positive: return "POSITIVE";
-  case Clipper2Lib::FillRule::Negative: return "NEGATIVE";
-  default: throw std::runtime_error("Unexpected fill rule: " + std::to_string(static_cast<int>(fr)));
-  }
-}
-
-void SaveInputToFile(
-  const Clipper2Lib::Paths64& subject,
-  const Clipper2Lib::Paths64& subject_open,
-  const Clipper2Lib::Paths64& clip,
-  Clipper2Lib::ClipType ct,
-  Clipper2Lib::FillRule fr
-)
-{
-  std::ofstream out("RandomPolygons.txt");
-  out << "CAPTION: 1." << std::endl;
-  out << "CLIPTYPE: " << ToString(ct) << std::endl;
-  out << "FILLRULE: " << ToString(fr) << std::endl;
-
-  const auto writePaths = [&out](const Clipper2Lib::Paths64& paths) {
-    for (const auto& path : paths) {
-      for (const auto& point : path) {
-        out << point.x << "," << point.y << " ";
-      }
-      out << std::endl;
-    }
-  };
-
-  out << "SUBJECTS" << std::endl;
-  writePaths(subject);
-
-  if (!subject_open.empty())
-  {
-    out << "SUBJECTS_OPEN" << std::endl;
-    writePaths(subject_open);
-  }
-
-  if (!clip.empty())
-  {
-    out << "CLIPS" << std::endl;
-    writePaths(clip);
-  }
-}
-
 TEST(Clipper2Tests, TestRandomPaths)
 {
   std::default_random_engine rng(42);
-
 #if DEBUG
   for (int i = 0; i < 10; ++i)
 #else
@@ -120,26 +49,20 @@ TEST(Clipper2Tests, TestRandomPaths)
 #endif
   {
     const auto max_complexity = std::max(1, i / 10);
-
     const auto subject      = GenerateRandomPaths(rng, 1, max_complexity);
     const auto subject_open = GenerateRandomPaths(rng, 0, max_complexity);
     const auto clip         = GenerateRandomPaths(rng, 0, max_complexity);
-
-    const Clipper2Lib::ClipType ct = static_cast<Clipper2Lib::ClipType>(GenerateRandomInteger(rng, 0, 4));
-    const Clipper2Lib::FillRule fr = static_cast<Clipper2Lib::FillRule>(GenerateRandomInteger(rng, 0, 3));
-
-    SaveInputToFile(subject, subject_open, clip, ct, fr);
-
+    const Clipper2Lib::ClipType ct = static_cast<Clipper2Lib::ClipType>(GenerateRandomInt(rng, 0, 4));
+    const Clipper2Lib::FillRule fr = static_cast<Clipper2Lib::FillRule>(GenerateRandomInt(rng, 0, 3));
+    //SaveInputToFile(subject, subject_open, clip, ct, fr);
     Clipper2Lib::Paths64 solution, solution_open;
     Clipper2Lib::Clipper64 c;
     c.AddSubject(subject);
     c.AddOpenSubject(subject_open);
     c.AddClip(clip);
     c.Execute(ct, fr, solution, solution_open);
-
-    const auto area_paths = static_cast<int64_t>(Area(solution));
-    const auto count_paths = solution.size() + solution_open.size();
-
+    const int64_t area_paths = static_cast<int64_t>(Area(solution));
+    const int64_t count_paths = solution.size() + solution_open.size();
     Clipper2Lib::PolyTree64 solution_polytree;
     Clipper2Lib::Paths64 solution_polytree_open;
     Clipper2Lib::Clipper64 clipper_polytree;
@@ -147,12 +70,14 @@ TEST(Clipper2Tests, TestRandomPaths)
     clipper_polytree.AddOpenSubject(subject_open);
     clipper_polytree.AddClip(clip);
     clipper_polytree.Execute(ct, fr, solution_polytree, solution_polytree_open);
-
     const auto solution_polytree_paths = PolyTreeToPaths64(solution_polytree);
-    const auto area_polytree = static_cast<int64_t>(Area(solution_polytree_paths));
-    const auto count_polytree = solution_polytree_paths.size() + solution_polytree_open.size();
-
+    const int64_t area_polytree = static_cast<int64_t>(Area(solution_polytree_paths));
+    const int64_t count_polytree = solution_polytree_paths.size() + solution_polytree_open.size();
     EXPECT_EQ(area_paths, area_polytree);
-    EXPECT_EQ(count_paths, count_polytree);
+    // polytree does an additional bounds check on each path
+    // and discards paths with empty bounds, so count_polytree
+    // may on occasions be slightly less than count_paths even
+    // though areas match
+    //EXPECT_LE(count_paths - count_polytree, 2);
   }
 }

+ 30 - 27
polygon.mod/clipper2/CPP/Tests/TestRectClip.cpp

@@ -1,63 +1,66 @@
 #include <gtest/gtest.h>
 #include "clipper2/clipper.h"
-#include "ClipFileLoad.h"
-
 using namespace Clipper2Lib;
-
 TEST(Clipper2Tests, TestRectClip)
 {
   Paths64 sub, clp, sol;
-  Rect64 rect = Rect64(100,100, 700, 500);
+  Rect64 rect = Rect64(100, 100, 700, 500);
   clp.push_back(rect.AsPath());
-
-  sub.push_back(MakePath("100,100, 700,100, 700,500, 100,500"));
+  sub.push_back(MakePath({ 100,100, 700,100, 700,500, 100,500 }));
   sol = RectClip(rect, sub);
   EXPECT_TRUE(Area(sol) == Area(sub));
-
   sub.clear();
-  sub.push_back(MakePath("110,110, 700,100, 700,500, 100,500"));
+  sub.push_back(MakePath({ 110,110, 700,100, 700,500, 100,500 }));
   sol = RectClip(rect, sub);
   EXPECT_TRUE(Area(sol) == Area(sub));
-
   sub.clear();
-  sub.push_back(MakePath("90,90, 700,100, 700,500, 100,500"));
+  sub.push_back(MakePath({ 90,90, 700,100, 700,500, 100,500 }));
   sol = RectClip(rect, sub);
   EXPECT_TRUE(Area(sol) == Area(clp));
-
   sub.clear();
-  sub.push_back(MakePath("90,90, 710,90, 710,510, 90,510"));
-  sol = RectClip(rect, sub);
-  EXPECT_TRUE(Area(sol) == Area(clp));
-
-  sub.clear();
-  sub.push_back(MakePath("110,110, 690,110, 690,490, 110,490"));
+  sub.push_back(MakePath({ 110,110, 690,110, 690,490, 110,490 }));
   sol = RectClip(rect, sub);
   EXPECT_TRUE(Area(sol) == Area(sub));
-
   sub.clear();
   clp.clear();
   rect = Rect64(390, 290, 410, 310);
   clp.push_back(rect.AsPath());
-  sub.push_back(MakePath("410,290 500,290 500,310 410,310"));
+  sub.push_back(MakePath({ 410,290, 500,290, 500,310, 410,310 }));
   sol = RectClip(rect, sub);
   EXPECT_TRUE(sol.empty());
-
   sub.clear();
-  sub.push_back(MakePath("430,290 470,330 390,330"));
+  sub.push_back(MakePath({ 430,290, 470,330, 390,330 }));
   sol = RectClip(rect, sub);
   EXPECT_TRUE(sol.empty());
-
   sub.clear();
-  sub.push_back(MakePath("450,290 480,330 450,330"));
+  sub.push_back(MakePath({ 450,290, 480,330, 450,330 }));
   sol = RectClip(rect, sub);
   EXPECT_TRUE(sol.empty());
-
   sub.clear();
-  sub.push_back(MakePath("208,66 366,112 402,303 234,332 233,262 243,140 215,126 40,172"));
+  sub.push_back(MakePath({ 208,66, 366,112, 402,303,
+    234,332, 233,262, 243,140, 215,126, 40,172 }));
   rect = Rect64(237, 164, 322, 248);
   sol = RectClip(rect, sub);
-  const auto solBounds = Bounds(sol);
+  const auto solBounds = GetBounds(sol);
   EXPECT_EQ(solBounds.Width(), rect.Width());
   EXPECT_EQ(solBounds.Height(), rect.Height());
-
+}
+TEST(Clipper2Tests, TestRectClip2) //#597
+{
+  Clipper2Lib::Rect64 rect(54690, 0, 65628, 6000);
+  Clipper2Lib::Paths64 subject {{{700000, 6000}, { 0, 6000 }, { 0, 5925 }, { 700000, 5925 }}};
+  Clipper2Lib::Paths64 solution = Clipper2Lib::RectClip(rect, subject);
+  //std::cout << solution << std::endl;
+  EXPECT_TRUE(solution.size() == 1 && solution[0].size() == 4);
+}
+TEST(Clipper2Tests, TestRectClip3) //#637
+{
+  Rect64 r(-1800000000LL, -137573171LL, -1741475021LL, 3355443LL);
+  Paths64 subject, clip, solution;
+  subject.push_back(MakePath({ -1800000000LL, 10005000LL,
+    -1800000000LL, -5000LL, -1789994999LL, -5000LL, -1789994999LL, 10005000LL }));
+  clip.push_back(r.AsPath());
+  solution = RectClip(r, subject);
+  //std::cout << solution << std::endl;
+  EXPECT_TRUE(solution.size() == 1);
 }

+ 11 - 0
polygon.mod/clipper2/CPP/Tests/TestSimplifyPath.cpp

@@ -0,0 +1,11 @@
+#include "clipper2/clipper.h"
+#include <gtest/gtest.h>
+using namespace Clipper2Lib;
+TEST(Clipper2Tests, TestSimplifyPath) {
+  Path64 input1 = MakePath({
+      0,0, 1,1, 0,20, 0,21, 1,40, 0,41, 0,60, 0,61, 0,80, 1,81, 0,100
+  });
+  Path64 output1 = SimplifyPath(input1, 2, false);
+  EXPECT_EQ(Length(output1), 100);
+  EXPECT_EQ(output1.size(), 2);
+}

+ 9 - 17
polygon.mod/clipper2/CPP/Tests/TestTrimCollinear.cpp

@@ -1,35 +1,27 @@
 #include <gtest/gtest.h>
 #include "clipper2/clipper.h"
-
 using namespace Clipper2Lib;
-
 TEST(Clipper2Tests, TestTrimCollinear) {
-      
-  Path64 input1 = 
-    MakePath("10,10, 10,10, 50,10, 100,10, 100,100, 10,100, 10,10, 20,10");
+  Path64 input1 =
+    MakePath({ 10,10, 10,10, 50,10, 100,10, 100,100, 10,100, 10,10, 20,10 });
   Path64 output1 = TrimCollinear(input1, false);
   EXPECT_EQ(output1.size(), 4);
-
-  Path64 input2 = 
-    MakePath("10,10, 10,10, 100,10, 100,100, 10,100, 10,10, 10,10");
+  Path64 input2 =
+    MakePath({ 10,10, 10,10, 100,10, 100,100, 10,100, 10,10, 10,10 });
   Path64 output2 = TrimCollinear(input2, true);
   EXPECT_EQ(output2.size(), 5);
-
-  Path64 input3 = MakePath("10,10, 10,50, 10,10, 50,10,50,50, \
-    50,10, 70,10, 70,50, 70,10, 50,10, 100,10, 100,50, 100,10");
+  Path64 input3 = MakePath({ 10,10, 10,50, 10,10, 50,10,50,50,
+    50,10, 70,10, 70,50, 70,10, 50,10, 100,10, 100,50, 100,10 });
   Path64 output3 = TrimCollinear(input3);
   EXPECT_EQ(output3.size(), 0);
-
-  Path64 input4 = MakePath("2,3, 3,4, 4,4, 4,5, 7,5, \
-    8,4, 8,3, 9,3, 8,3, 7,3, 6,3, 5,3, 4,3, 3,3, 2,3");
+  Path64 input4 = MakePath({ 2,3, 3,4, 4,4, 4,5, 7,5,
+    8,4, 8,3, 9,3, 8,3, 7,3, 6,3, 5,3, 4,3, 3,3, 2,3 });
   Path64 output4a = TrimCollinear(input4);
   Path64 output4b = TrimCollinear(output4a);
-
-  int area4a = static_cast<int>(Area(output4a)); 
+  int area4a = static_cast<int>(Area(output4a));
   int area4b = static_cast<int>(Area(output4b));
   EXPECT_EQ(output4a.size(), 7);
   EXPECT_EQ(area4a, -9);
   EXPECT_EQ(output4a.size(), output4b.size());
   EXPECT_EQ(area4a, area4b);
-
 }

+ 5 - 0
polygon.mod/clipper2/CPP/Tests/TestWindows.cpp

@@ -0,0 +1,5 @@
+// see: https://github.com/AngusJohnson/Clipper2/issues/601
+#ifdef WIN32
+#include <Windows.h>
+#include "clipper2/clipper.h"
+#endif // WIN32

+ 22 - 21
polygon.mod/clipper2/CPP/Utils/ClipFileLoad.cpp

@@ -42,7 +42,7 @@ bool GetPath(const string& line, Paths64& paths)
   return true;
 }
 
-void GetPaths(ifstream& source, Paths64& paths) 
+void GetPaths(ifstream& source, Paths64& paths)
 {
   while (true)
   {
@@ -50,13 +50,14 @@ void GetPaths(ifstream& source, Paths64& paths)
     stringstream::pos_type last_read_line_pos = source.tellg();
     if (getline(source, line) && GetPath(line, paths))
       continue;
+    last_read_line_pos -= 1; // workaround for LF vs LFCR (#764)
     source.seekg(last_read_line_pos, ios_base::beg);
     break;
   }
 }
 
 bool LoadTestNum(ifstream &source, int test_num,
-  Paths64 &subj, Paths64 &subj_open, Paths64 &clip, 
+  Paths64 &subj, Paths64 &subj_open, Paths64 &clip,
   int64_t& area, int64_t& count, ClipType &ct, FillRule &fr)
 {
   string line;
@@ -65,37 +66,37 @@ bool LoadTestNum(ifstream &source, int test_num,
   source.seekg(0, ios_base::beg);
   subj.clear(); subj_open.clear(); clip.clear();
 
-  while (std::getline(source, line))
-  {    
+  while (getline(source, line))
+  {
     if (test_num)
     {
       if (line.find("CAPTION:") != string::npos) --test_num;
       continue;
     }
 
-    if (line.find("CAPTION:") != string::npos) break;
+    if (line.find("CAPTION:") != string::npos) break; // ie don't go beyond current test
 
-    else if (line.find("INTERSECTION") != string::npos) 
-      ct = ClipType::Intersection; 
-    else if (line.find("UNION") != string::npos) 
-      ct = ClipType::Union; 
-    else if (line.find("DIFFERENCE") != string::npos) 
-      ct = ClipType::Difference; 
-    else if (line.find("XOR") != string::npos) 
-      ct = ClipType::Xor; 
-    else if (line.find("EVENODD") != string::npos) 
-      fr = FillRule::EvenOdd; 
-    else if (line.find("NONZERO") != string::npos) 
-      fr = FillRule::NonZero ; 
-    else if (line.find("POSITIVE") != string::npos) 
-      fr = FillRule::Positive; 
+    else if (line.find("INTERSECTION") != string::npos)
+      ct = ClipType::Intersection;
+    else if (line.find("UNION") != string::npos)
+      ct = ClipType::Union;
+    else if (line.find("DIFFERENCE") != string::npos)
+      ct = ClipType::Difference;
+    else if (line.find("XOR") != string::npos)
+      ct = ClipType::Xor;
+    else if (line.find("EVENODD") != string::npos)
+      fr = FillRule::EvenOdd;
+    else if (line.find("NONZERO") != string::npos)
+      fr = FillRule::NonZero ;
+    else if (line.find("POSITIVE") != string::npos)
+      fr = FillRule::Positive;
     else if (line.find("NEGATIVE") != string::npos)
-      fr = FillRule::Negative; 
+      fr = FillRule::Negative;
     else if (line.find("SOL_AREA") != string::npos)
     {
       string::const_iterator s_it, s_end = line.cend();
       s_it = (line.cbegin() + 10);
-      GetInt(s_it, s_end, area); 
+      GetInt(s_it, s_end, area);
     }
     else if (line.find("SOL_COUNT") != string::npos)
     {

+ 8 - 6
polygon.mod/clipper2/CPP/Utils/ClipFileSave.cpp

@@ -2,13 +2,13 @@
 // Functions load clipping operations from text files
 //------------------------------------------------------------------------------
 
-#include "ClipFileLoad.h"
 #include "ClipFileSave.h"
 #include <sstream>
 #include <cstring>
 
-using namespace std;
-using namespace Clipper2Lib;
+namespace Clipper2Lib {
+
+ using namespace std;
 
 //------------------------------------------------------------------------------
 // Boyer Moore Horspool Search
@@ -247,8 +247,8 @@ static bool GetInt(string::const_iterator& s_it,
 }
 
 bool SaveTest(const std::string& filename, bool append,
-  Clipper2Lib::Paths64* subj, Clipper2Lib::Paths64* subj_open, Clipper2Lib::Paths64* clip,
-  int64_t area, int64_t count, Clipper2Lib::ClipType ct, Clipper2Lib::FillRule fr)
+  const Paths64* subj, const Paths64* subj_open, const Paths64* clip, 
+  int64_t area, int64_t count, ClipType ct, FillRule fr)
 {
   string line;
   bool found = false;
@@ -258,7 +258,7 @@ bool SaveTest(const std::string& filename, bool append,
   {
     ifstream file;
     file.open(filename, std::ios::binary);
-    if (!file) return false;
+    if (!file || !file.good()) return false;
     BMH_Search bmh = BMH_Search(file, "CAPTION:");
     while (bmh.FindNext()) ;
     if (bmh.LastFound())
@@ -322,3 +322,5 @@ bool SaveTest(const std::string& filename, bool append,
   source.close();
   return true;
 }
+
+} //end namespace

+ 8 - 4
polygon.mod/clipper2/CPP/Utils/ClipFileSave.h

@@ -5,10 +5,14 @@
 #ifndef CLIPPER_TEST_SAVE_H
 #define CLIPPER_TEST_SAVE_H
 
-#include "clipper2/clipper.h"
+#include "ClipFileLoad.h"
 
-bool SaveTest(const std::string& filename, bool append,
-  Clipper2Lib::Paths64* subj, Clipper2Lib::Paths64* subj_open, Clipper2Lib::Paths64* clip,
-  int64_t area, int64_t count, Clipper2Lib::ClipType ct, Clipper2Lib::FillRule fr);
+namespace Clipper2Lib {
+
+  bool SaveTest(const std::string& filename, bool append,
+    const Paths64* subj, const Paths64* subj_open, const Paths64* clip, 
+    int64_t area, int64_t count, ClipType ct, FillRule fr);
+
+} //end namespace
 
 #endif //CLIPPER_TEST_SAVE_H

+ 56 - 0
polygon.mod/clipper2/CPP/Utils/Colors.h

@@ -0,0 +1,56 @@
+#ifndef CLIPPER_COLORS_H
+#define CLIPPER_COLORS_H
+
+#include <cstdlib>
+
+struct Hsl {
+  uint8_t alpha = 0;
+  uint8_t hue = 0;
+  uint8_t sat = 0;
+  uint8_t lum = 0;
+  Hsl() {};
+  Hsl(uint8_t a, uint8_t h, uint8_t s, uint8_t l) :
+    alpha(a), hue(h), sat(s), lum(l) {};
+};
+
+union Color32 {
+  struct {
+    uint8_t b : 8;
+    uint8_t g : 8;
+    uint8_t r : 8;
+    uint8_t a : 8;
+  } argb;
+  uint32_t color;
+};
+
+Color32 HslToRgb(Hsl hsl)
+{
+  int c, x, m, a;
+  c = ((255 - std::abs(2 * hsl.lum - 255)) * hsl.sat) >> 8;
+  a = 252 - (hsl.hue % 85) * 6;
+  x = (c * (255 - std::abs(a))) >> 8;
+  m = hsl.lum - c / 2;
+  Color32 result{};
+  result.argb.a = hsl.alpha;
+  switch ((hsl.hue * 6) >> 8)
+  {
+  case 0: { result.argb.r = c + m; result.argb.g = x + m; result.argb.b = 0 + m; break; };
+    case 1: { result.argb.r = x + m; result.argb.g = c + m; result.argb.b = 0 + m; break; };
+    case 2: { result.argb.r = 0 + m; result.argb.g = c + m; result.argb.b = x + m; break; };
+    case 3: { result.argb.r = 0 + m; result.argb.g = x + m; result.argb.b = c + m; break; };
+    case 4: { result.argb.r = x + m; result.argb.g = 0 + m; result.argb.b = c + m; break; };
+    case 5: { result.argb.r = c + m; result.argb.g = 0 + m; result.argb.b = x + m; break; };
+  }
+  return result;
+}
+
+uint32_t RainbowColor(double frac, 
+  uint8_t luminance = 128, uint8_t alpha = 255)
+{
+  frac = static_cast<double>(frac - static_cast<int>(frac));
+  Hsl hsl(alpha, static_cast<uint8_t>(frac * 255), 255, luminance);
+  Color32 result = HslToRgb(hsl);
+  return result.color;
+}
+
+#endif // CLIPPER_COLORS_H

+ 38 - 0
polygon.mod/clipper2/CPP/Utils/CommonUtils.h

@@ -0,0 +1,38 @@
+#ifndef __COMMONUTILS_H__
+#define __COMMONUTILS_H__
+
+#include <cstdlib>
+#include <random>
+#include "clipper2/clipper.h"
+
+Clipper2Lib::Path64 MakeRandomPoly(int width, int height, unsigned vertCnt)
+{
+  std::random_device rd;
+  std::mt19937 gen(rd());
+  std::uniform_int_distribution<> w(0, width);
+  std::uniform_int_distribution<> h(0, height);
+
+  using namespace Clipper2Lib;
+  Path64 result;
+  result.reserve(vertCnt);
+  for (unsigned i = 0; i < vertCnt; ++i)
+    result.push_back(Point64(w(gen), h(gen)));
+  return result;
+}
+
+Clipper2Lib::PathD MakeRandomPolyD(int width, int height, unsigned vertCnt)
+{
+  std::random_device rd;
+  std::mt19937 gen(rd());
+  std::uniform_int_distribution<> w(0, width);
+  std::uniform_int_distribution<> h(0, height);
+
+  using namespace Clipper2Lib;
+  PathD result;
+  result.reserve(vertCnt);
+  for (unsigned i = 0; i < vertCnt; ++i)
+    result.push_back(PointD(w(gen), h(gen)));
+  return result;
+}
+
+#endif

+ 38 - 45
polygon.mod/clipper2/CPP/Utils/Timer.h

@@ -5,31 +5,25 @@
 #include <string>
 #include <chrono> 
 #include <iomanip>
+#include <sstream>
 
 /*
 
 Timer Usage:
 The Timer object will start immediately following its construction 
-(unless "start_paused" is true). It will stop either when its 
-destructor is called on leaving scope, or when pause() is called.
-The timer's pause() and resume() can be called an number of times and 
-the total interval while unpaused will be reported in standard output.
-
-The Timer's constructor takes two optional string parameters:
-  caption   : test sent to standard output just as timing commences.
-  time_text : text to be output to the left of the recorded time interval.
+(unless "start_paused" is true). The timer's pause() and resume() can 
+be called an number of times and the total interval while unpaused 
+will be returned when elapsed() is called.
 
 Example:
 
   #include "timer.h"
-  #include "windows.h" //for Sleep() :)
 
   void main()
   {
-    {
-      Timer timer("Starting timer now.", "This operation took ");
-      Sleep(1000);
-    }
+    Timer timer;
+    Sleep(1000);
+    std::cout << timer.elapsed_str();
   }
 
 */
@@ -38,31 +32,23 @@ struct Timer {
 private:
   std::streamsize old_precision = std::cout.precision(0);
   std::ios_base::fmtflags old_flags = std::cout.flags();
-  std::chrono::high_resolution_clock::time_point time_started_ = 
-    std::chrono::high_resolution_clock::now();
+  std::chrono::high_resolution_clock::time_point time_started_;
   std::chrono::high_resolution_clock::duration duration_ = {};
   bool paused_ = false;
-  std::string time_text_ = "";
 
 public:
 
-  Timer(bool start_paused = false): paused_(start_paused) {}
-
-  explicit Timer(const char time_text[], bool start_paused) :
-    paused_(start_paused), time_text_(time_text) {}
-
-  explicit Timer(const char caption[], const char time_text[] = "",
-    bool start_paused = false) :
-    paused_(start_paused), time_text_(time_text)
+  Timer(bool start_paused = false): paused_(start_paused) 
   {
-    std::cout << caption << std::endl;
+    if (!paused_) time_started_ =
+      std::chrono::high_resolution_clock::now();
   }
 
-  explicit Timer(const std::string& caption, const std::string& time_text = "",
-    bool start_paused = false) :
-    paused_(start_paused), time_text_(time_text)
+  void restart()
   {
-    if (caption != "") std::cout << caption << std::endl;
+    paused_ = false;
+    duration_ = {};
+    time_started_ = std::chrono::high_resolution_clock::now();
   }
 
   void resume()
@@ -81,24 +67,31 @@ public:
     paused_ = true;
   }
 
-  ~Timer()
+  int64_t elapsed_nano() // result in nano-seconds
+  {
+    if (!paused_)
+    {
+      std::chrono::high_resolution_clock::time_point now =
+        std::chrono::high_resolution_clock::now();
+      duration_ += (now - time_started_);
+    }
+    return std::chrono::duration_cast<std::chrono::nanoseconds>(duration_).count();
+  }
+
+  std::string elapsed_str() // result as string
   {
-    pause();
-    int nsecs_log10 = static_cast<int>(std::log10(
-      std::chrono::duration_cast<std::chrono::nanoseconds>(duration_).count()));
-    std::cout << std::fixed << 
-      std::setprecision(static_cast<uint8_t>(2.0 - (nsecs_log10 % 3))) << time_text_;
-    if (nsecs_log10 < 6) 
-      std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>
-      (duration_).count() * 1.0e-3 << " microsecs" << std::endl;
+    int64_t nano_secs = elapsed_nano();
+    int nsecs_log10 = static_cast<int>(std::log10(nano_secs));
+    std::ostringstream os{};
+    os.precision(static_cast<uint8_t>(2.0 - (nsecs_log10 % 3)));
+    os << std::fixed;
+    if (nsecs_log10 < 6)
+      os << nano_secs * 1.0e-3 << " microsecs";
     else if (nsecs_log10 < 9)
-      std::cout << std::chrono::duration_cast<std::chrono::microseconds>
-        (duration_).count() * 1.0e-3 << " millisecs" << std::endl;
-    else 
-      std::cout << std::chrono::duration_cast<std::chrono::milliseconds>
-      (duration_).count() * 1.0e-3 << " secs" << std::endl;
-    std::cout.precision(old_precision);
-    std::cout.flags(old_flags);
+      os << nano_secs * 1.0e-6 << " millisecs";
+    else
+      os << nano_secs * 1.0e-9 << " secs";
+    return os.str();
   }
 };
 

+ 36 - 17
polygon.mod/clipper2/CPP/Utils/clipper.svg.cpp

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  23 July 2022                                                    *
+* Date      :  28 January 2023                                                 *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
 
@@ -43,7 +43,7 @@ namespace Clipper2Lib {
 
   inline float GetAlphaAsFrac(unsigned int clr)
   {
-    return ((clr >> 24) / 255.0f);
+    return ((float)(clr >> 24) / 255.0f);
   }
   //------------------------------------------------------------------------------
 
@@ -71,6 +71,20 @@ namespace Clipper2Lib {
   }
   //------------------------------------------------------------------------------
 
+  void SvgWriter::AddPath(const Path64& path, bool is_open, FillRule fillrule,
+    unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords)
+  {
+    int error_code = 0;
+    if (path.size() == 0) return;
+    PathsD tmp;
+    tmp.push_back(ScalePath<double, int64_t>(path, scale_, error_code));
+    if (error_code) return;
+    PathInfo* pi = new PathInfo(tmp, is_open, fillrule,
+      brush_color, pen_color, pen_width, show_coords);
+    path_infos.push_back(pi);
+  }
+  //------------------------------------------------------------------------------
+
   void SvgWriter::AddPath(const PathD &path, bool is_open, FillRule fillrule,
     unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords)
   {
@@ -85,8 +99,10 @@ namespace Clipper2Lib {
   void SvgWriter::AddPaths(const Paths64& paths, bool is_open, FillRule fillrule,
     unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords)
   {
+    int error_code = 0;
     if (paths.size() == 0) return;
-    PathsD tmp = ScalePaths<double, int64_t>(paths, scale_);
+    PathsD tmp = ScalePaths<double, int64_t>(paths, scale_, error_code);
+    if (error_code) return;
     PathInfo* pi = new PathInfo(tmp, is_open, fillrule,
       brush_color, pen_color, pen_width, show_coords);
     path_infos.push_back(pi);
@@ -124,7 +140,7 @@ namespace Clipper2Lib {
   bool SvgWriter::SaveToFile(const std::string &filename,
     int max_width, int max_height, int margin)
   {
-    RectD rec = MaxInvalidRectD;
+    RectD rec = InvalidRectD;
     for (const PathInfo* pi : path_infos)
       for (const PathD& path : pi->paths_)
         for (const PointD& pt : path){
@@ -165,8 +181,9 @@ namespace Clipper2Lib {
         (pi->fillrule_ != FillRule::Positive && pi->fillrule_ != FillRule::Negative))
           continue;
 
-      PathsD ppp = pi->fillrule_ == 
-        FillRule::Positive ? SimulatePositiveFill(pi->paths_) : SimulateNegativeFill(pi->paths_);
+      PathsD ppp = pi->fillrule_ == FillRule::Positive ? 
+        SimulatePositiveFill(pi->paths_) : 
+        SimulateNegativeFill(pi->paths_);
 
       file << "  <path d=\"";
       for (PathD& path : ppp)
@@ -207,15 +224,15 @@ namespace Clipper2Lib {
       }
 
       file << svg_xml_0 << ColorToHtml(brushColor) <<
-        svg_xml_1 << GetAlphaAsFrac(brushColor) <<
+        svg_xml_1 << std::setprecision(2) << GetAlphaAsFrac(brushColor) <<
         svg_xml_2 << (pi->fillrule_ == FillRule::NonZero ? "nonzero" : "evenodd") <<
         svg_xml_3 << ColorToHtml(pi->pen_color_) <<
         svg_xml_4 << GetAlphaAsFrac(pi->pen_color_) <<
         svg_xml_5 << pi->pen_width_ << svg_xml_6;
 
       if (pi->show_coords_) {
-        file << std::setprecision(0) 
-          << "  <g font-family=\"" << coords_style.font_name << "\" font-size=\"" <<
+        file << std::setprecision(0)  << 
+          "  <g font-family=\"" << coords_style.font_name << "\" font-size=\"" <<
           coords_style.font_size  << "\" fill=\""<< ColorToHtml(coords_style.font_color) << 
           "\" fill-opacity=\"" << GetAlphaAsFrac(coords_style.font_color) << "\">\n";
         for (PathD& path : pi->paths_)
@@ -231,18 +248,19 @@ namespace Clipper2Lib {
       }
     }
 
-    //draw red dots at all vertices - useful for debugging
+    ////draw red dots at all solution vertices - useful for debugging
     //for (PathInfo* pi : path_infos)
-    //  for (PathD& path : pi->paths_)
-    //    for (PointD& pt : path)
-    //      DrawCircle(file, pt.x * scale + offsetX, pt.y * scale + offsetY, 1);
+    //  if (!(pi->pen_color_ & 0x00FF00FF)) // ie any shade of green only
+    //    for (PathD& path : pi->paths_)
+    //      for (PointD& pt : path)
+    //        DrawCircle(file, pt.x * scale + offsetX, pt.y * scale + offsetY, 1.6);
 
     for (TextInfo* ti : text_infos) 
     {
       file << "  <g font-family=\"" << ti->font_name << "\" font-size=\"" <<
         ti->font_size << "\" fill=\"" << ColorToHtml(ti->font_color) <<
         "\" fill-opacity=\"" << GetAlphaAsFrac(ti->font_color) << "\">\n";
-      file << "    <text x=\"" << (ti->x + margin) << "\" y=\"" << (ti->y+margin) << "\">" <<
+      file << "    <text x=\"" << (ti->x * scale + offsetX) << "\" y=\"" << (ti->y * scale + offsetY) << "\">" <<
         ti->text << "</text>\n  </g>\n\n";
     }
 
@@ -393,8 +411,9 @@ namespace Clipper2Lib {
   bool SvgReader::LoadFromFile(const std::string &filename)
   {
       Clear();
-      std::ifstream file;
-      file.open(filename);      
+      std::ifstream file(filename);
+      if (!file.good()) return false;
+
       std::stringstream xml_buff;
       xml_buff << file.rdbuf();
       file.close();

+ 5 - 3
polygon.mod/clipper2/CPP/Utils/clipper.svg.h

@@ -46,7 +46,7 @@ namespace Clipper2Lib {
     public:
       std::string font_name;
       unsigned font_color = 0xFF000000;
-      unsigned font_size = 11;
+      unsigned font_size = 7;
     };
 
     class TextInfo {
@@ -91,9 +91,11 @@ namespace Clipper2Lib {
     FillRule Fill_Rule() { return fill_rule_; }
     void SetCoordsStyle(const std::string &font_name, unsigned font_color, unsigned font_size);
     void AddText(const std::string &text, unsigned font_color, unsigned font_size, int x, int y);
-    void AddPath(const PathD& path, bool is_open, FillRule fillrule, 
+    void AddPath(const Path64& path, bool is_open, FillRule fillrule,
       unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords);
-    void AddPaths(const PathsD& paths, bool is_open, FillRule fillrule, 
+    void AddPath(const PathD& path, bool is_open, FillRule fillrule,
+      unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords);
+    void AddPaths(const PathsD& paths, bool is_open, FillRule fillrule,
       unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords);
     void AddPaths(const Paths64& paths, bool is_open, FillRule fillrule, 
       unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords);

+ 3 - 3
polygon.mod/clipper2/CPP/Utils/clipper.svg.utils.h

@@ -96,13 +96,13 @@ namespace Clipper2Lib {
   }
 
 
-  inline void SvgAddSolution(SvgWriter& svg, const PathsD& path, FillRule fillrule, bool show_coords)
+  inline void SvgAddSolution(SvgWriter& svg, const PathsD &path, FillRule fillrule, bool show_coords)
   {
     svg.AddPaths(path, false, fillrule, solution_brush_clr, 0xFF003300, 1.2, show_coords);
   }
 
 
-  inline void SvgAddSolution(SvgWriter& svg, const Paths64& path, FillRule fillrule, bool show_coords)
+  inline void SvgAddSolution(SvgWriter& svg, const Paths64 &path, FillRule fillrule, bool show_coords)
   {
     svg.AddPaths(TransformPaths<double, int64_t>(path),
       false, fillrule, solution_brush_clr, 0xFF003300, 1.2, show_coords);
@@ -129,7 +129,7 @@ namespace Clipper2Lib {
     int max_width = 0, int max_height = 0, int margin = 0)
   {
     if (FileExists(filename)) remove(filename.c_str());
-    svg.SetCoordsStyle("Verdana", 0xFF0000AA, 9);
+    svg.SetCoordsStyle("Verdana", 0xFF0000AA, 7);
     svg.SaveToFile(filename, max_width, max_height, margin);
   }
 }

+ 6 - 0
polygon.mod/clipper2/CPP/clipper.version.in

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

+ 0 - 1
polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using BenchmarkDotNet.Attributes;
 using BenchmarkDotNet.Configs;
 using BenchmarkDotNet.Jobs;

+ 1 - 1
polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Clipper2Lib.Benchmark.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
     <OutputType>Exe</OutputType>
   </PropertyGroup>
   <PropertyGroup>

+ 1 - 1
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/ConsoleDemo.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
     <EnableNETAnalyzers>false</EnableNETAnalyzers>
     <ApplicationIcon>Clipper2.ico</ApplicationIcon>
   </PropertyGroup>

+ 192 - 36
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs

@@ -1,69 +1,209 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  17 July 2022                                                    *
+* Date      :  1 January 2023                                                  *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
 
-using System.Collections.Generic;
 using System.Reflection;
 using System.IO;
 using Clipper2Lib;
+using System;
 
 namespace ClipperDemo1
 {
   public class Application
   {
-
     public static void Main()
     {
-      ClipSimpleShapes();
-      ClipTestPolys();
+      //ClipTestPolys();
+
+      //LoopThruTestPolygons();             // summary test all
+      //LoopThruTestPolygons(110, 113);     // display test range
+      //LoopThruTestPolygons(46);           // display one
+
+      SquaresTest(true);
+      TrianglesTest(true);
+      DiamondsTest(true);
+    }
+
+    public static Paths64 Polytree_Union(Paths64 subjects, FillRule fillrule)
+    {
+      // of course this function is inefficient, 
+      // but it's purpose is simply to test polytrees.
+      PolyTree64 polytree = new PolyTree64();
+      Clipper.BooleanOp(ClipType.Union, subjects, null, polytree, fillrule);
+      return Clipper.PolyTreeToPaths64(polytree);
     }
-    public static void ClipSimpleShapes()
+
+    public static void SquaresTest(bool test_polytree = false)
     {
-      Paths64 subject = new ();
-      Paths64 clip = new ();
+      const int size = 10;
+      const int w = 800, h = 600;
       FillRule fillrule = FillRule.NonZero;
 
-      subject.Add(Clipper.MakePath(new int[] { 100, 50, 10, 79, 65, 2, 65, 98, 10, 21 }));
-      clip.Add(Clipper.MakePath(new int[] { 98, 63, 4, 68, 77, 8, 52, 100, 19, 12 }));
-      Paths64 solution = Clipper.Intersect(subject, clip, fillrule);
+      Path64 shape = Clipper.MakePath(new int[] { 0, 0, size, 0, size, size, 0, size });
+      Paths64 subjects = new(), solution;
+      Random rand = new();
+      for (int i = 0; i < h / size; ++i)
+      {
+        for (int j = 0; j < w / size; ++j)
+        {
+          shape = Clipper.TranslatePath(shape, size, 0);
+          if (rand.Next(5) != 0) subjects.Add(shape);
+        }
+        shape = Clipper.TranslatePath(shape, (-w / size) * size, size);
+      }
 
-      SimpleSvgWriter svg = new ();
-      SvgUtils.AddSubject(svg, subject);
-      SvgUtils.AddClip(svg, clip);
-      SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperA.svg", fillrule, 400, 300, 20);
-      ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperA.svg");
+      if (test_polytree)
+        solution = Polytree_Union(subjects, fillrule);
+      else
+        solution = Clipper.Union(subjects, fillrule);
 
-      svg.ClearAll();
-      SvgUtils.AddSubject(svg, subject);
-      SvgUtils.AddClip(svg, clip);
+      SvgWriter svg = new SvgWriter();
+      SvgUtils.AddSubject(svg, subjects);
       SvgUtils.AddSolution(svg, solution, false);
-      SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperB.svg", fillrule, 400, 300, 20);
-      ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperB.svg");
+      string filename = @"..\..\..\squares.svg";
+      SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10);
+      ClipperFileIO.OpenFileWithDefaultApp(filename);
     }
 
-    public static void ClipTestPolys()
+    public static void TrianglesTest(bool test_polytree = false)
     {
+      const int size = 10;
+      const int w = 800, h = 600;
       FillRule fillrule = FillRule.NonZero;
-      Paths64 subject = LoadPathsFromResource("ConsoleDemo.subj.bin");
-      Paths64 clip = LoadPathsFromResource("ConsoleDemo.clip.bin");
-      Paths64 solution = Clipper.Intersect(subject, clip, fillrule);
 
-      SimpleSvgWriter svg = new ();
-      SvgUtils.AddSubject(svg, subject);
-      SvgUtils.AddClip(svg, clip);
-      SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperC.svg", fillrule, 800, 600, 20);
-      ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperC.svg");
+      Path64 tri1 = Clipper.MakePath(new int[] { 0,0, size * 2,0, size,size * 2 });
+      Path64 tri2 = Clipper.MakePath(new int[] { size * 2, 0, size, size * 2, size*3, size*2 });
 
-      svg.ClearAll();
-      SvgUtils.AddSubject(svg, subject);
-      SvgUtils.AddClip(svg, clip);
+      Paths64 subjects = new(), solution;
+      Random rand = new();
+      for (int i = 0; i < h / size / 2; ++i)
+      {
+        for (int j = 0; j < w / size / 2; ++j)
+        {
+          if (rand.Next(4) != 0) subjects.Add(tri1);
+          if (rand.Next(4) != 0) subjects.Add(tri2);
+          tri1 = Clipper.TranslatePath(tri1, size * 2, 0);
+          tri2 = Clipper.TranslatePath(tri2, size * 2, 0);
+        }
+        tri1 = Clipper.TranslatePath(tri1, (-w / size) * size, size * 2);
+        tri2 = Clipper.TranslatePath(tri2, (-w / size) * size, size * 2);
+      }
+
+      if (test_polytree)
+        solution = Polytree_Union(subjects, fillrule);
+      else
+        solution = Clipper.Union(subjects, fillrule);
+
+      SvgWriter svg = new SvgWriter();
+      SvgUtils.AddSubject(svg, subjects);
       SvgUtils.AddSolution(svg, solution, false);
-      SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperD.svg", fillrule, 800, 600, 20);
-      ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperD.svg");
+      string filename = @"..\..\..\triangles.svg";
+      SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10);
+      ClipperFileIO.OpenFileWithDefaultApp(filename);
+    }
+
+    public static void DiamondsTest(bool test_polytree = false)
+    {
+      const int size = 10;
+      const int w = 800, h = 600;
+      FillRule fillrule = FillRule.NonZero;
+
+      Path64 shape = Clipper.MakePath(new int[] { size, 0, size * 2, size, size, size * 2, 0, size });
+      Paths64 subjects = new(), solution;
+      Random rand = new();
+      for (int i = 0; i < h / size / 2; ++i)
+      {
+        for (int j = 0; j < w / size; ++j)
+        {
+          if (rand.Next(7) != 0) subjects.Add(shape);
+          if ((j & 1) == 0)
+            shape = Clipper.TranslatePath(shape, size, size);
+          else
+            shape = Clipper.TranslatePath(shape, size, -size);
+        }
+        shape = Clipper.TranslatePath(shape, (-w / size) * size, size * 2);
+      }
+
+      if (test_polytree)
+        solution = Polytree_Union(subjects, fillrule);
+      else
+        solution = Clipper.Union(subjects, fillrule);
+
+      SvgWriter svg = new SvgWriter();
+      SvgUtils.AddSubject(svg, subjects);
+      SvgUtils.AddSolution(svg, solution, false);
+      string filename = @"..\..\..\diamonds.svg";
+      SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10);
+      ClipperFileIO.OpenFileWithDefaultApp(filename);
+    }
+
+    public static void LoopThruTestPolygons(int start = 0, int end = 0)
+    {
+      Paths64 subject = new Paths64();
+      Paths64 subject_open = new Paths64();
+      Paths64 clip = new Paths64();
+      Paths64 solution = new Paths64();
+      Paths64 solution_open = new Paths64();
+      ClipType ct;
+      FillRule fr;
+      bool do_all = (start == 0 && end == 0);
+      if (do_all) { start = 1; end = 0xFFFF; }
+      else if (end == 0) end = start;
+
+      if (do_all)
+        Console.WriteLine("\nCount and area differences (expected vs measured):\n");
+        int test_number = start;
+      for (; test_number <= end; ++test_number)
+      {
+        if (!ClipperFileIO.LoadTestNum(@"..\..\..\..\..\..\Tests\Polygons.txt", 
+          test_number, subject, subject_open, clip, 
+          out ct, out fr, out long area, out int cnt, out _)) break;
+        Clipper64 c64 = new Clipper64();
+        c64.AddSubject(subject);
+        c64.AddOpenSubject(subject_open);
+        c64.AddClip(clip);
+        if (!c64.Execute(ct, fr, solution, solution_open)) return;
+
+        if (do_all)
+        {
+          int measuredCnt = solution.Count;
+          double measuredArea = Clipper.Area(solution);
+
+          double count_diff = (cnt <= 0) ? 0 :
+            Math.Abs( measuredCnt / (double)cnt - 1.0);
+
+          double area_diff = area <= 0 ? 0 : Math.Abs(measuredArea / area -1.0);
+
+          if (count_diff > 0.05)
+            Console.WriteLine(string.Format("{0}: count {1} vs {2}", test_number, cnt, measuredCnt));
+          if (area_diff > 0.1)
+            Console.WriteLine(string.Format("{0}: area {1} vs {2}", test_number, area, measuredArea));
+
+          // don't display when looping through every test
+          continue; 
+        }
+
+        SvgWriter svg = new SvgWriter();
+        SvgUtils.AddSubject(svg, subject);
+        SvgUtils.AddClip(svg, clip);
+        if (fr == FillRule.Negative)
+          solution = Clipper.ReversePaths(solution);
+        SvgUtils.AddSolution(svg, solution, false);
+        SvgUtils.AddCaption(svg, test_number.ToString(), 20, 20);
+        string filename = @"..\..\..\poly" + (test_number - 1).ToString() + ".svg";
+        SvgUtils.SaveToFile(svg, filename, fr, 800, 600, 10);
+        ClipperFileIO.OpenFileWithDefaultApp(filename);
+      }
+
+      if (do_all)
+      {
+        Console.WriteLine(string.Format("\ntest ended at polygon {0}.\n", test_number));
+        Console.ReadKey();
+      }
     }
 
     public static Paths64 LoadPathsFromResource(string resourceName)
@@ -89,5 +229,21 @@ namespace ClipperDemo1
       return result;
     }
 
+    public static void ClipTestPolys()
+    {
+      FillRule fillrule = FillRule.NonZero;
+      Paths64 subject = LoadPathsFromResource("ConsoleDemo.subj.bin");
+      Paths64 clip = LoadPathsFromResource("ConsoleDemo.clip.bin");
+      Paths64 solution = Clipper.Intersect(subject, clip, fillrule);
+
+      SvgWriter svg = new();
+      SvgUtils.AddSubject(svg, subject);
+      SvgUtils.AddClip(svg, clip);
+      SvgUtils.AddSolution(svg, solution, false);
+      SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperD.svg", fillrule, 800, 600, 20);
+      ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperD.svg");
+    }
+
+
   } //end Application
 } //namespace

+ 1 - 1
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/InflateDemo.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
     <EnableNETAnalyzers>false</EnableNETAnalyzers>
     <ApplicationIcon>Clipper2.ico</ApplicationIcon>
   </PropertyGroup>

+ 59 - 31
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs

@@ -1,12 +1,12 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  16 September 2022                                               *
+* Date      :  24 September 2023                                               *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
 
-using System.Collections.Generic;
+using System;
 using System.IO;
 using System.Reflection;
 using Clipper2Lib;
@@ -21,40 +21,49 @@ namespace ClipperDemo1
     {
       DoSimpleShapes();
       DoRabbit();
+      DoVariableOffset();
     }
 
     public static void DoSimpleShapes()
     {
-      //triangle offset - with large miter
-      Paths64 p = new() { Clipper.MakePath(new int[] { 30, 150, 60, 350, 0, 350 }) };
-      Paths64 pp = new ();
-      pp.AddRange(p);
+      SvgWriter svg = new();
+      ClipperOffset co = new();
 
+      //triangle offset - with large miter
+      Paths64 p0 = new() { Clipper.MakePath(new int[] { 30,150, 60,350, 0,350 }) };
+      Paths64 p = new();
       for (int i = 0; i < 5; ++i)
       {
-        //nb: the following '10' parameter greatly increases miter limit
-        p = Clipper.InflatePaths(p, 5, JoinType.Miter, EndType.Polygon, 10);
-        pp.AddRange(p);
+        //nb: the last parameter here (10) greatly increases miter limit
+        p0 = Clipper.InflatePaths(p0, 5, JoinType.Miter, EndType.Polygon, 10);
+        p.AddRange(p0);
       }
+      SvgUtils.AddSolution(svg, p, false);
+      p.Clear();  
 
       //rectangle offset - both squared and rounded
-      p.Clear();
-      p.Add(Clipper.MakePath(new int[] { 100, 0, 340, 0, 340, 200, 100, 200 }));
-      pp.AddRange(p);
       //nb: using the ClipperOffest class directly here to control 
       //different join types within the same offset operation
-      ClipperOffset co = new ();
-      co.AddPaths(p, JoinType.Miter, EndType.Joined);
-      p = Clipper.TranslatePaths(p, 120, 100);
-      pp.AddRange(p);
+      p.Add(Clipper.MakePath(new int[] { 100,0, 340,0, 340,200, 100,200, 100, 0 }));
+      SvgUtils.AddOpenSubject(svg, p);
+      co.AddPaths(p, JoinType.Bevel, EndType.Joined);
+
+      p = Clipper.TranslatePaths(p, 60, 50);
+      SvgUtils.AddOpenSubject(svg, p);
+      co.AddPaths(p, JoinType.Square, EndType.Joined);
+      p = Clipper.TranslatePaths(p, 60, 50);
+      SvgUtils.AddOpenSubject(svg, p);
       co.AddPaths(p, JoinType.Round, EndType.Joined);
-      p = co.Execute(20);
-      pp.AddRange(p);
 
-      SimpleSvgWriter svg = new ();
-      SvgUtils.AddSolution(svg, pp, false);
-      SvgUtils.SaveToFile(svg, "../../../inflate.svg", FillRule.EvenOdd, 800, 600, 20);
-      ClipperFileIO.OpenFileWithDefaultApp("../../../inflate.svg");
+      co.Execute(10, p);
+
+      string filename = "../../../inflate.svg";
+      SvgUtils.AddSolution(svg, p, false);
+      SvgUtils.AddCaption(svg, "Beveled join", 100, -27);
+      SvgUtils.AddCaption(svg, "Squared join", 160, 23);
+      SvgUtils.AddCaption(svg, "Rounded join", 220, 73);
+      SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 800, 600, 20);
+      ClipperFileIO.OpenFileWithDefaultApp(filename);
     }
 
     public static void DoRabbit()
@@ -64,18 +73,19 @@ namespace ClipperDemo1
       PathsD solution = new (pd);
       while (pd.Count > 0)
       {
-        //don't forget to scale the delta offset
+        // and don't forget to scale the delta offset
         pd = Clipper.InflatePaths(pd, -2.5, JoinType.Round, EndType.Polygon);
-        //RamerDouglasPeucker - not essential but not only 
-        //speeds up the loop but also tidies the result
-        pd = Clipper.RamerDouglasPeucker(pd, 0.025);
+        // SimplifyPaths - is not essential but it not only 
+        // speeds up the loop but it also tidies the result
+        pd = Clipper.SimplifyPaths(pd, 0.25);
         solution.AddRange(pd);
       }
 
-      SimpleSvgWriter svg = new ();
+      string filename = "../../../rabbit.svg";
+      SvgWriter svg = new ();
       SvgUtils.AddSolution(svg, solution, false);
-      SvgUtils.SaveToFile(svg, "../../../rabbit2.svg", FillRule.EvenOdd, 450, 720, 10);
-      ClipperFileIO.OpenFileWithDefaultApp("../../../rabbit2.svg");
+      SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 450, 720, 10);
+      ClipperFileIO.OpenFileWithDefaultApp(filename);
     }
 
     public static PathsD LoadPathsFromResource(string resourceName)
@@ -100,7 +110,25 @@ namespace ClipperDemo1
       }
       return result;
     }
-  
+
+    public static void DoVariableOffset()
+    {
+      Paths64 p = new() { Clipper.MakePath(new int[] { 0,50, 20,50, 40,50, 60,50, 80,50, 100,50 }) };
+      Paths64 solution = new();
+      ClipperOffset co = new();
+      co.AddPaths(p, JoinType.Square, EndType.Butt);
+      co.Execute(
+        delegate (Path64 path, PathD path_norms, int currPt, int prevPt) 
+        { return currPt* currPt + 10; } , solution);
+
+      string filename = "../../../variable_offset.svg";
+      SvgWriter svg = new();
+      SvgUtils.AddOpenSubject(svg, p);
+      SvgUtils.AddSolution(svg, solution, true);
+      SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 500, 500, 60);
+      ClipperFileIO.OpenFileWithDefaultApp(filename);
+    }
+
   } //end Application
 
 } //namespace

+ 77 - 60
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/Main.cs

@@ -1,78 +1,110 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  16 September 2022                                               *
+* Date      :  11 February 2023                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
 
 using System;
 using System.IO;
+using System.Reflection.Metadata.Ecma335;
 using Clipper2Lib;
 
 namespace ClipperDemo1
 {
   public class Application
   {
-
+    ////////////////////
+    public const int edgeCount      = 19;
+    public const int displayWidth   = 500;
+    public const int displayHeight  = 500;
+    public const int rectInsetDist  = 200;
+    ////////////////////
     public static void Main()
     {
-      DoRandomPoly(true);
+      // although RectClip isn't really designed for 
+      // complex  self-intersecting polygons, it still 
+      // handles them pretty well.      
+      DoRandomPoly(/* true == repeat last random */);
     }
 
-    public static void DoRandomPoly(bool makeNewPoly)
+    public static bool SaveBackup(string filename, Paths64 sub, Paths64 clp)
     {
-      Random rand = new();
-      Paths64 subjOpen, clip, solOpen;
-      Rect64 rec;
+      StreamWriter writer;
+      try
+      { writer = new StreamWriter(filename, false); }
+      catch { return false; }
+
+      foreach (Point64 pt in sub[0])
+        writer.Write("{0},{1} ", pt.X, pt.Y);
+      writer.Write("\r\n");
+      foreach (Point64 pt in clp[0])
+        writer.Write("{0},{1} ", pt.X, pt.Y);
+      writer.Write("\r\n");
+      writer.Close();
+      return true; 
+    }
 
-      if (makeNewPoly)
+    public static bool RestoreBackup(string filename, out Paths64 sub, out Paths64 clp)
+    {
+      sub = null;
+      clp = null;
+      if (!File.Exists(filename)) return false;
+      try
       {
-        ////////////////////
-        const int count = 75;
-        ////////////////////
+        StreamReader reader = new StreamReader(filename);
+        string s = reader.ReadLine();
+        sub = ClipperFileIO.PathFromStr(s);
+        s = reader.ReadLine();
+        clp = ClipperFileIO.PathFromStr(s);
+      }
+      catch { return false; }
+      return true;
+    }
 
-        rec = new(100, 100, 700, 500);
-        clip = new() { rec.AsPath() };
-        subjOpen = new() { MakeRandomPath(800, 600, count, rand) };
+      public static void DoRandomPoly(bool loadPrevious = false)
+    {
+      Random rand = new();
+      Paths64 sub = new Paths64(), clp = new Paths64(), sol;
+      Rect64 rec = new(rectInsetDist, rectInsetDist,
+        displayWidth - rectInsetDist, displayHeight - rectInsetDist);
 
-        // save - useful when debugging 
-        StreamWriter writer;
-        try
-        { writer = new StreamWriter(".\\store.txt", false); }
-        catch { return; }
-        foreach (Point64 pt in subjOpen[0])
-          writer.Write("{0},{1} ", pt.X, pt.Y);
-        writer.Write("\r\n");
-        foreach (Point64 pt in clip[0])
-          writer.Write("{0},{1} ", pt.X, pt.Y);
-        writer.Write("\r\n");
-        writer.Close();
+      if (loadPrevious)
+      {
+        if (!RestoreBackup(".\\backup.txt", out sub, out clp)) return;
+        rec = Clipper.GetBounds(clp);
       }
-      else
+      else      
       {
-        if (!File.Exists(".\\store.txt")) return;
-
-        StreamReader reader;
-        try
-        { reader = new StreamReader(".\\store.txt"); }
-        catch { return; }
-        string s = reader.ReadLine();
-        subjOpen = ClipperFileIO.PathFromStr(s);
-        s = reader.ReadLine();
-        clip = ClipperFileIO.PathFromStr(s);
-        rec = Clipper.GetBounds(clip);
+        clp.Add(rec.AsPath());
+        sub.Add(MakeRandomPath(displayWidth, displayHeight, edgeCount, rand));
+        if (!SaveBackup(".\\backup.txt", sub, clp)) return;
       }
 
       /////////////////////////////////////////////////
-      solOpen = Clipper.RectClipLines(rec, subjOpen);
+      sol = Clipper.RectClip(rec, sub);
       /////////////////////////////////////////////////
 
-      SimpleSvgWriter svg = new ();
-      SvgUtils.AddSubject(svg, subjOpen, false);
-      SvgUtils.AddClip(svg, clip);
-      SvgUtils.AddSolution(svg, solOpen, false, false, false);
-      SvgUtils.SaveToFile(svg, "../../../rectclip.svg", FillRule.EvenOdd, 800, 600, 20);
+      SvgWriter svg = new (FillRule.NonZero);
+      SvgUtils.AddSubject(svg, sub);
+      SvgUtils.AddClip(svg, clp);
+
+      //SvgUtils.AddSolution(svg, sol, false);
+      if (sol.Count > 0)
+      {
+        double frac = 1.0 / sol.Count;
+        double cumFrac = frac;
+        foreach (Path64 path in sol)
+        {
+          Colors.Color32 c = Colors.Rainbow(cumFrac, 64);
+          cumFrac += frac;
+          Colors.Color32 c2 = new Colors.Color32(
+            (c.color & 0xFFFFFF) | 0x20000000);
+          svg.AddClosedPath(path, c2.color, c.color, 1.2);
+        }
+      }
+      svg.SaveToFile("../../../rectclip.svg", 800, 600, 20);
       ClipperFileIO.OpenFileWithDefaultApp("../../../rectclip.svg");
     }
 
@@ -82,12 +114,6 @@ namespace ClipperDemo1
       long y = rand.Next(maxHeight);
       return new Point64(x, y);
     }
-    private static PointD MakeRandomPtD(int maxWidth, int maxHeight, Random rand)
-    {
-      double x = rand.Next(maxWidth);
-      double y = rand.Next(maxHeight);
-      return new PointD(x, y);
-    }
 
     public static Path64 MakeRandomPath(int width, int height, int count, Random rand)
     {
@@ -97,15 +123,6 @@ namespace ClipperDemo1
       return result;
     }
 
-    public static PathD MakeRandomPathD(int width, int height, int count, Random rand)
-    {
-      PathD result = new(count);
-      for (int i = 0; i < count; ++i)
-        result.Add(MakeRandomPtD(width, height, rand));
-      return result;
-    }
-
-
   } //end Application
 
 } //namespace

+ 2 - 1
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/RectClipDemo.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
     <EnableNETAnalyzers>false</EnableNETAnalyzers>
     <ApplicationIcon>Clipper2.ico</ApplicationIcon>
     <Platforms>AnyCPU;x86</Platforms>
@@ -33,6 +33,7 @@
   <ItemGroup>
     <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
     <ProjectReference Include="..\..\Utils\ClipFileIO\Clipper.FileIO.csproj" />
+    <ProjectReference Include="..\..\Utils\Colors\Clipper.Colors.csproj" />
     <ProjectReference Include="..\..\Utils\SVG\Clipper2.SVG.csproj" />
   </ItemGroup>
 

+ 1 - 1
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestLines.cs

@@ -29,7 +29,7 @@ namespace Clipper2Lib.UnitTests
         if (area > 0)
         {
           double area2 = Clipper.Area(solution);
-          double a = area2 / area2;
+          double a = area / area2;
           Assert.IsTrue(a > 0.995 && a < 1.005,
             string.Format("Incorrect area in test {0}", i));
         }

+ 19 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs

@@ -0,0 +1,19 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Clipper2Lib.UnitTests
+{
+
+  [TestClass]
+  public class TestOffsets
+  {
+
+    [TestMethod]
+    public void TestOffsetEmpty()
+    {
+      Paths64 solution = new();
+
+      ClipperOffset offset = new ClipperOffset();
+      offset.Execute(10, solution);
+    }
+  }
+}

+ 34 - 24
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolygons.cs

@@ -40,36 +40,46 @@ namespace Clipper2Lib.UnitTests
         long measuredArea = (long)Clipper.Area(solution);
         int countDiff = storedCount > 0 ? Math.Abs(storedCount - measuredCount) : 0;
         long areaDiff = storedArea > 0 ? Math.Abs(storedArea - measuredArea) : 0;
+        double areaDiffRatio = storedArea <= 0 ? 0 : (double) areaDiff / storedArea;
 
-        if (testNum == 23)
+        // check polygon counts
+        if (storedCount > 0)
         {
-          Assert.IsTrue(countDiff <= 4);
+          if (IsInList(testNum, new int[] { 140, 150, 165, 166, 172, 173, 176, 177, 179 }))
+          {
+            Assert.IsTrue(countDiff <= 9);
+          }
+          else if (testNum >= 120)
+          {
+            Assert.IsTrue(countDiff <= 6);
+          }
+          else if (IsInList(testNum, new int[] { 27, 121, 126 }))
+            Assert.IsTrue(countDiff <= 2);
+          else if (IsInList(testNum, new int[] { 23, 37, 43, 45, 87, 102, 111, 118, 119 }))
+            Assert.IsTrue(countDiff <= 1);
+          else
+            Assert.IsTrue(countDiff == 0);
         }
-        else if (testNum == 27)
-        {
-          Assert.IsTrue(countDiff <= 2);
-        }
-        else if (IsInList(testNum,
-          new int[] {18, 22, 32, 37, 42, 43, 45, 87, 102, 103, 111, 118, 183 }))
-        {
-          Assert.IsTrue(countDiff <= 1);
-        }
-        else if (testNum >= 120)
-        {
-          if (storedCount > 0)
-            Assert.IsTrue(countDiff / storedCount <= 0.02);
-        }
-        else if (storedCount > 0)
-          Assert.IsTrue(countDiff == 0);
 
-        if (IsInList(testNum, new int[] { 22,23,24 }))
-        {
-          Assert.IsTrue(areaDiff <= 8);
-        }
-        else if (storedArea > 0)
+        // check polygon areas
+        if (storedArea > 0)
         {
-          Assert.IsTrue(areaDiff / storedArea <= 0.02);
+          if (IsInList(testNum, new int[] { 19, 22, 23, 24 }))
+            Assert.IsTrue(areaDiffRatio <= 0.5);
+          else if (testNum == 193)
+            Assert.IsTrue(areaDiffRatio <= 0.25);
+          else if (testNum == 63)
+            Assert.IsTrue(areaDiffRatio <= 0.1);
+          else if (testNum == 16)
+            Assert.IsTrue(areaDiffRatio <= 0.075);
+          else if (IsInList(testNum, new int[] { 15, 26 }))
+            Assert.IsTrue(areaDiffRatio <= 0.05);
+          else if (IsInList(testNum, new int[] { 52, 53, 54, 59, 60, 64, 117, 118, 119, 184 }))
+            Assert.IsTrue(areaDiffRatio <= 0.02);
+          else
+            Assert.IsTrue(areaDiffRatio <= 0.01);
         }
+
       } //bottom of num loop
 
     }

+ 154 - 104
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Core.cs

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  19 November 2022                                                *
+* Date      :  14 February 2024                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2024                                         *
 * Purpose   :  Core structures and functions for the Clipper Library           *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
@@ -11,8 +11,7 @@
 using System;
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
-using System.Security.Cryptography;
-using System.Threading;
+using System.Runtime.InteropServices;
 
 namespace Clipper2Lib
 {
@@ -33,9 +32,9 @@ namespace Clipper2Lib
 
     public Point64(Point64 pt, double scale)
     {
-      X = (long) Math.Round(pt.X * scale);
-      Y = (long) Math.Round(pt.Y * scale);
-      Z = (long) Math.Round(pt.Z * scale);
+      X = (long) Math.Round(pt.X * scale, MidpointRounding.AwayFromZero);
+      Y = (long) Math.Round(pt.Y * scale, MidpointRounding.AwayFromZero);
+      Z = (long) Math.Round(pt.Z * scale, MidpointRounding.AwayFromZero);
     }
     
     public Point64(long x, long y, long z = 0)
@@ -47,22 +46,22 @@ namespace Clipper2Lib
 
     public Point64(double x, double y, double z = 0.0)
     {
-      X = (long) Math.Round(x);
-      Y = (long) Math.Round(y);
-      Z = (long) Math.Round(z);
+      X = (long) Math.Round(x, MidpointRounding.AwayFromZero);
+      Y = (long) Math.Round(y, MidpointRounding.AwayFromZero);
+      Z = (long) Math.Round(z, MidpointRounding.AwayFromZero);
     }
 
     public Point64(PointD pt)
     {
-      X = (long) Math.Round(pt.x);
-      Y = (long) Math.Round(pt.y);
+      X = (long) Math.Round(pt.x, MidpointRounding.AwayFromZero);
+      Y = (long) Math.Round(pt.y, MidpointRounding.AwayFromZero);
       Z = pt.z;
     }
 
     public Point64(PointD pt, double scale)
     {
-      X = (long) Math.Round(pt.x * scale);
-      Y = (long) Math.Round(pt.y * scale);
+      X = (long) Math.Round(pt.x * scale, MidpointRounding.AwayFromZero);
+      Y = (long) Math.Round(pt.y * scale, MidpointRounding.AwayFromZero);
       Z = pt.z;
     }
 
@@ -106,26 +105,26 @@ namespace Clipper2Lib
 
     public Point64(double x, double y)
     {
-      X = (long) Math.Round(x);
-      Y = (long) Math.Round(y);
+      X = (long) Math.Round(x, MidpointRounding.AwayFromZero);
+      Y = (long) Math.Round(y, MidpointRounding.AwayFromZero);
     }
 
     public Point64(PointD pt)
     {
-      X = (long) Math.Round(pt.x);
-      Y = (long) Math.Round(pt.y);
+      X = (long) Math.Round(pt.x, MidpointRounding.AwayFromZero);
+      Y = (long) Math.Round(pt.y, MidpointRounding.AwayFromZero);
     }
 
     public Point64(Point64 pt, double scale)
     {
-      X = (long) Math.Round(pt.X * scale);
-      Y = (long) Math.Round(pt.Y * scale);
+      X = (long) Math.Round(pt.X * scale, MidpointRounding.AwayFromZero);
+      Y = (long) Math.Round(pt.Y * scale, MidpointRounding.AwayFromZero);
     }
 
     public Point64(PointD pt, double scale)
     {
-      X = (long) Math.Round(pt.x * scale);
-      Y = (long) Math.Round(pt.y * scale);
+      X = (long) Math.Round(pt.x * scale, MidpointRounding.AwayFromZero);
+      Y = (long) Math.Round(pt.y * scale, MidpointRounding.AwayFromZero);
     }
 
     public static bool operator ==(Point64 lhs, Point64 rhs)
@@ -147,20 +146,24 @@ namespace Clipper2Lib
     {
       return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y);
     }
-    public override string ToString()
+    public readonly override string ToString()
     {
       return $"{X},{Y} "; // nb: trailing space
     }
 
 #endif
-    public override bool Equals(object? obj)
+    public readonly override bool Equals(object? obj)
     {
       if (obj != null && obj is Point64 p)
         return this == p;
       return false;
     }
 
-    public override int GetHashCode() { return 0; }
+    public readonly override int GetHashCode()
+    {
+      return HashCode.Combine(X, Y); //#599
+    }
+
   }
 
   public struct PointD
@@ -213,9 +216,9 @@ namespace Clipper2Lib
       this.z = z;
     }
 
-    public override string ToString()
+    public string ToString(int precision = 2)
     {
-      return $"{x:F},{y:F},{z} ";
+      return string.Format($"{{0:F{precision}}},{{1:F{precision}}},{{2:D}}", x,y,z);
     }
 
 #else
@@ -255,9 +258,9 @@ namespace Clipper2Lib
       this.y = y;
     }
 
-    public override string ToString()
+    public readonly string ToString(int precision = 2)
     {
-      return $"{x:F},{y:F} ";
+      return string.Format($"{{0:F{precision}}},{{1:F{precision}}}", x,y);
     }
 
 #endif
@@ -273,7 +276,7 @@ namespace Clipper2Lib
         !InternalClipper.IsAlmostZero(lhs.y - rhs.y);
     }
 
-    public override bool Equals(object? obj)
+    public readonly override bool Equals(object? obj)
     {
       if (obj != null && obj is PointD p)
         return this == p;
@@ -282,7 +285,11 @@ namespace Clipper2Lib
 
     public void Negate() { x = -x; y = -y; }
 
-    public override int GetHashCode() { return 0; }
+    public readonly override int GetHashCode()
+    {
+      return HashCode.Combine(x, y); //#599
+    }
+
   }
 
   public struct Rect64
@@ -300,6 +307,19 @@ namespace Clipper2Lib
       bottom = b;
     }
 
+    public Rect64(bool isValid)
+    {
+      if (isValid)
+      {
+        left = 0; top = 0; right = 0; bottom = 0;
+      }
+      else
+      {
+        left = long.MaxValue; top = long.MaxValue; 
+        right = long.MinValue; bottom = long.MinValue;
+      }
+    }
+
     public Rect64(Rect64 rec)
     {
       left = rec.left;
@@ -309,46 +329,49 @@ namespace Clipper2Lib
     }
 
     public long Width
-    {
-      get => right - left;
+    { readonly get => right - left;
       set => right = left + value;
     }
 
     public long Height
-    {
-      get => bottom - top;
+    { readonly get => bottom - top;
       set => bottom = top + value;
     }
 
-    public bool IsEmpty()
+    public readonly bool IsEmpty()
     {
       return bottom <= top || right <= left;
     }
 
-    public Point64 MidPoint()
+    public readonly bool IsValid()
+    {
+      return left < long.MaxValue;
+    }
+
+    public readonly Point64 MidPoint()
     {
       return new Point64((left + right) /2, (top + bottom)/2);
     }
 
-    public bool Contains(Point64 pt)
+    public readonly bool Contains(Point64 pt)
     {
       return pt.X > left && pt.X < right &&
         pt.Y > top && pt.Y < bottom;
     }
 
-    public bool Contains(Rect64 rec)
+    public readonly bool Contains(Rect64 rec)
     {
       return rec.left >= left && rec.right <= right &&
         rec.top >= top && rec.bottom <= bottom;
     }
 
-    public bool Intersects(Rect64 rec)
+    public readonly bool Intersects(Rect64 rec)
     {
-      return (Math.Max(left, rec.left) < Math.Min(right, rec.right)) &&
-        (Math.Max(top, rec.top) < Math.Min(bottom, rec.bottom));
+      return (Math.Max(left, rec.left) <= Math.Min(right, rec.right)) &&
+        (Math.Max(top, rec.top) <= Math.Min(bottom, rec.bottom));
     }
 
-    public Path64 AsPath()
+    public readonly Path64 AsPath()
     {
       Path64 result = new Path64(4)
       {
@@ -385,47 +408,57 @@ namespace Clipper2Lib
       bottom = rec.bottom;
     }
 
-    public double Width
+    public RectD(bool isValid)
     {
-      get => right - left;
+      if (isValid)
+      {
+        left = 0; top = 0; right = 0; bottom = 0;
+      }
+      else
+      {
+        left = double.MaxValue; top = double.MaxValue;
+        right = -double.MaxValue; bottom = -double.MaxValue;
+      }
+    }
+    public double Width
+    { readonly get => right - left;
       set => right = left + value;
     }
 
     public double Height
-    {
-      get => bottom - top;
+    { readonly get => bottom - top;
       set => bottom = top + value;
     }
 
-    public bool IsEmpty()
+    public readonly bool IsEmpty()
     {
       return bottom <= top || right <= left;
     }
 
-    public PointD MidPoint()
+    public readonly PointD MidPoint()
     {
       return new PointD((left + right) / 2, (top + bottom) / 2);
     }
 
-    public bool Contains(PointD pt)
+    public readonly bool Contains(PointD pt)
     {
       return pt.x > left && pt.x < right &&
         pt.y > top && pt.y < bottom;
     }
 
-    public bool Contains(RectD rec)
+    public readonly bool Contains(RectD rec)
     {
       return rec.left >= left && rec.right <= right &&
         rec.top >= top && rec.bottom <= bottom;
     }
 
-    public bool Intersects(RectD rec)
+    public readonly bool Intersects(RectD rec)
     {
       return (Math.Max(left, rec.left) < Math.Min(right, rec.right)) &&
         (Math.Max(top, rec.top) < Math.Min(bottom, rec.bottom));
     }
 
-    public PathD AsPath()
+    public readonly PathD AsPath()
     {
       PathD result = new PathD(4)
       {
@@ -452,6 +485,7 @@ namespace Clipper2Lib
       return s;
     }
   }
+
   public class Paths64 : List<Path64>
   {
     private Paths64() : base() { }
@@ -471,11 +505,11 @@ namespace Clipper2Lib
     private PathD() : base() { }
     public PathD(int capacity = 0) : base(capacity) { }
     public PathD(IEnumerable<PointD> path) : base(path) { }
-    public override string ToString()
+    public string ToString(int precision = 2)
     {
       string s = "";
       foreach (PointD p in this)
-        s = s + p.ToString() + " ";
+        s = s + p.ToString(precision) + ", ";
       return s;
     }
   }
@@ -485,11 +519,11 @@ namespace Clipper2Lib
     private PathsD() : base() { }
     public PathsD(int capacity = 0) : base(capacity) { }
     public PathsD(IEnumerable<PathD> paths) : base(paths) { }
-    public override string ToString()
+    public string ToString(int precision = 2)
     {
       string s = "";
       foreach (PathD p in this)
-        s = s + p.ToString() + "\n";
+        s = s + p.ToString(precision) + "\n";
       return s;
     }
   }
@@ -537,12 +571,22 @@ namespace Clipper2Lib
     internal const double min_coord = -MaxCoord;
     internal const long Invalid64 = MaxInt64;
 
+    internal const double defaultArcTolerance = 0.25;
     internal const double floatingPointTolerance = 1E-12;
     internal const double defaultMinimumEdgeLength = 0.1;
 
     private static readonly string
       precision_range_error = "Error: Precision is out of range.";
 
+#if USINGZ
+    public static Path64 SetZ(Path64 path, long Z)
+    {
+      Path64 result = new Path64(path.Count);
+      foreach (Point64 pt in path) result.Add(new Point64(pt.X, pt.Y, Z));
+      return result;
+    }
+#endif
+
     internal static void CheckPrecision(int precision)
     {
       if (precision < -8 || precision > 8)
@@ -586,52 +630,39 @@ namespace Clipper2Lib
     internal static long CheckCastInt64(double val)
     {
       if ((val >= max_coord) || (val <= min_coord)) return Invalid64;
-      return (long)Math.Round(val);
+      return (long)Math.Round(val, MidpointRounding.AwayFromZero);
     }
 
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    internal static bool GetIntersectPt(Point64 ln1a,
+    public static bool GetSegmentIntersectPt(Point64 ln1a,
       Point64 ln1b, Point64 ln2a, Point64 ln2b, out Point64 ip)
     {
       double dy1 = (ln1b.Y - ln1a.Y);
       double dx1 = (ln1b.X - ln1a.X);
       double dy2 = (ln2b.Y - ln2a.Y);
       double dx2 = (ln2b.X - ln2a.X);
-      double cp = dy1 * dx2 - dy2 * dx1;
-      if (cp == 0.0)
+      double det = dy1 * dx2 - dy2 * dx1;
+      if (det == 0.0)
       {
         ip = new Point64();
         return false;
       }
-      double qx = dx1 * ln1a.Y - dy1 * ln1a.X;
-      double qy = dx2 * ln2a.Y - dy2 * ln2a.X;
-      ip = new Point64(
-        CheckCastInt64((dx1 * qy - dx2 * qx) / cp),
-        CheckCastInt64((dy1 * qy - dy2 * qx) / cp));
-      return (ip.X != Invalid64 && ip.Y != Invalid64);
-    }
 
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    internal static bool GetIntersectPoint(Point64 ln1a,
-      Point64 ln1b, Point64 ln2a, Point64 ln2b, out PointD ip)
-    {
-      double dy1 = (ln1b.Y - ln1a.Y);
-      double dx1 = (ln1b.X - ln1a.X);
-      double dy2 = (ln2b.Y - ln2a.Y);
-      double dx2 = (ln2b.X - ln2a.X);
-      double q1 = dy1 * ln1a.X - dx1 * ln1a.Y;
-      double q2 = dy2 * ln2a.X - dx2 * ln2a.Y;
-      double cross_prod = dy1 * dx2 - dy2 * dx1;
-      if (cross_prod == 0.0)
-      {
-        ip = new PointD();
-        return false;
+      double t = ((ln1a.X - ln2a.X) * dy2 - (ln1a.Y - ln2a.Y) * dx2) / det;
+      if (t <= 0.0) ip = ln1a;
+      else if (t >= 1.0) ip = ln1b;
+      else {
+        // avoid using constructor (and rounding too) as they affect performance //664
+        ip.X = (long) (ln1a.X + t * dx1);
+        ip.Y = (long) (ln1a.Y + t * dy1);
+#if USINGZ
+        ip.Z = 0;
+#endif
       }
-      ip = new PointD(
-        (dx2 * q1 - dx1 * q2) / cross_prod,
-        (dy2 * q1 - dy1 * q2) / cross_prod);
       return true;
     }
+
     internal static bool SegsIntersect(Point64 seg1a, 
       Point64 seg1b, Point64 seg2a, Point64 seg2b, bool inclusive = false)
     {
@@ -664,38 +695,44 @@ namespace Clipper2Lib
         (offPt.Y - seg1.Y) * dy) / ((dx*dx) + (dy*dy));
       if (q < 0) q = 0; else if (q > 1) q = 1;
       return new Point64(
-        seg1.X + Math.Round(q * dx), seg1.Y + Math.Round(q* dy));
+        // use MidpointRounding.ToEven in order to explicitly match the nearbyint behaviour on the C++ side
+        seg1.X + Math.Round(q * dx, MidpointRounding.ToEven),
+        seg1.Y + Math.Round(q * dy, MidpointRounding.ToEven)
+      );
     }
 
     public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon)
     {
-      int len = polygon.Count, i = len - 1;
-
+      int len = polygon.Count, start = 0;
       if (len < 3) return PointInPolygonResult.IsOutside;
 
-      while (i >= 0 && polygon[i].Y == pt.Y) --i;
-      if (i < 0) return PointInPolygonResult.IsOutside;
-
-      int val = 0;
-      bool isAbove = polygon[i].Y < pt.Y;
-      i = 0;
+      while (start < len && polygon[start].Y == pt.Y) start++;
+      if (start == len) return PointInPolygonResult.IsOutside;
 
-      while (i < len)
+      double d;
+      bool isAbove = polygon[start].Y < pt.Y, startingAbove = isAbove;
+      int val = 0, i = start + 1, end = len;
+      while (true)
       {
+        if (i == end)
+        {
+          if (end == 0 || start == 0) break;  
+          end = start;
+          i = 0;
+        }
+        
         if (isAbove)
         {
-          while (i < len && polygon[i].Y < pt.Y) i++;
-          if (i == len) break;
+          while (i < end && polygon[i].Y < pt.Y) i++;
+          if (i == end) continue;
         }
         else
         {
-          while (i < len && polygon[i].Y > pt.Y) i++;
-          if (i == len) break;
+          while (i < end && polygon[i].Y > pt.Y) i++;
+          if (i == end) continue;
         }
 
-        Point64 prev;
-
-        Point64 curr = polygon[i];
+        Point64 curr = polygon[i], prev;
         if (i > 0) prev = polygon[i - 1];
         else prev = polygon[len - 1];
 
@@ -705,6 +742,7 @@ namespace Clipper2Lib
             ((pt.X < prev.X) != (pt.X < curr.X))))
             return PointInPolygonResult.IsOn;
           i++;
+          if (i == start) break;
           continue;
         }
 
@@ -718,13 +756,25 @@ namespace Clipper2Lib
         }
         else
         {
-          double d = CrossProduct(prev, curr, pt);
+          d = CrossProduct(prev, curr, pt);
           if (d == 0) return PointInPolygonResult.IsOn;
           if ((d < 0) == isAbove) val = 1 - val;
         }
         isAbove = !isAbove;
         i++;
       }
+
+      if (isAbove != startingAbove)
+      {
+        if (i == len) i = 0;  
+        if (i == 0)
+          d = CrossProduct(polygon[len - 1], polygon[0], pt);
+        else
+          d = CrossProduct(polygon[i - 1], polygon[i], pt);
+        if (d == 0) return PointInPolygonResult.IsOn;
+        if ((d < 0) == isAbove) val = 1 - val;
+      }
+
       if (val == 0)
         return PointInPolygonResult.IsOutside;
       return PointInPolygonResult.IsInside;

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 365 - 334
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Engine.cs


+ 0 - 1
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Minkowski.cs

@@ -9,7 +9,6 @@
 
 #nullable enable
 using System;
-using System.Collections.Generic;
 
 namespace Clipper2Lib
 {

+ 465 - 220
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Offset.cs

@@ -1,23 +1,25 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  15 October 2022                                                 *
+* Date      :  14 February 2024                                                *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2024                                         *
 * Purpose   :  Path Offset (Inflate/Shrink)                                    *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
 
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Runtime.CompilerServices;
 
 namespace Clipper2Lib
 {
   public enum JoinType
   {
+    Miter,
     Square,
-    Round,
-    Miter
+    Bevel,
+    Round
   };
 
   public enum EndType
@@ -34,39 +36,93 @@ namespace Clipper2Lib
 
     private class Group
     {
-      internal Paths64 _inPaths;
-      internal Path64 _outPath;
-      internal Paths64 _outPaths;
-      internal JoinType _joinType;
-      internal EndType _endType;
-      internal bool _pathsReversed;
+      internal Paths64 inPaths;
+      internal List<double> areasList;
+      internal List<bool> isHoleList;
+      internal JoinType joinType;
+      internal EndType endType;
+      internal bool pathsReversed;
+      internal int lowestPathIdx;
 
       public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon)
       {
-        _inPaths = new Paths64(paths);
-        _joinType = joinType;
-        _endType = endType;
-        _outPath = new Path64();
-        _outPaths = new Paths64();
-        _pathsReversed = false;
+        this.joinType = joinType;
+        this.endType = endType;
+
+        bool isJoined = ((endType == EndType.Polygon) || (endType == EndType.Joined));
+        inPaths = new Paths64(paths.Count);
+        foreach(Path64 path in paths)
+          inPaths.Add(Clipper.StripDuplicates(path, isJoined));
+
+        if (endType == EndType.Polygon)
+        {
+          isHoleList = new List<bool>(inPaths.Count);
+          areasList = new List<double>(inPaths.Count);
+
+          foreach (Path64 path in inPaths)
+          {
+            double a = Clipper.Area(path);
+            areasList.Add(a);
+            isHoleList.Add(a < 0);
+          }
+
+          lowestPathIdx = GetLowestPathIdx(inPaths);
+          // the lowermost path must be an outer path, so if its orientation is negative,
+          // then flag that the whole group is 'reversed' (will negate delta etc.)
+          // as this is much more efficient than reversing every path.
+          pathsReversed = (lowestPathIdx >= 0) && isHoleList[lowestPathIdx];
+          if (pathsReversed)
+            for (int i = 0; i < isHoleList.Count; i++) isHoleList[i] = !isHoleList[i];
+        }
+        else
+        {
+          lowestPathIdx = -1;
+          isHoleList = new List<bool>(new bool[inPaths.Count]);
+          areasList = new List<double>(new double[inPaths.Count]);
+          pathsReversed = false;
+        }
       }
     }
 
-    private readonly List<Group> _pathGroups = new List<Group>();
+    private static readonly double Tolerance = 1.0E-12;
+    private static readonly Rect64 InvalidRect64 =
+      new Rect64(long.MaxValue, long.MaxValue, long.MinValue, long.MinValue);
+    private static readonly RectD InvalidRectD =
+      new RectD(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue);
+    private static readonly long MAX_COORD = long.MaxValue >> 2;
+    private static readonly long MIN_COORD = -MAX_COORD;
+
+    private static readonly string
+      coord_range_error = "Error: Coordinate range.";
+
+
+    private readonly List<Group> _groupList = new List<Group>();
+    private Path64 pathOut = new Path64();
     private readonly PathD _normals = new PathD();
-    private readonly Paths64 solution = new Paths64();
-    private double _group_delta, _abs_group_delta, _tmpLimit, _stepsPerRad;
+    private readonly Paths64 _solution = new Paths64();
+    private double _groupDelta; //*0.5 for open paths; *-1.0 for negative areas
+    private double _delta;
+    private double _mitLimSqr;
+    private double _stepsPerRad;
+    private double _stepSin;
+    private double _stepCos;
     private JoinType _joinType;
+    private EndType _endType;
     public double ArcTolerance { get; set; }
     public bool MergeGroups { get; set; }
     public double MiterLimit { get; set; }
     public bool PreserveCollinear { get; set; }
     public bool ReverseSolution { get; set; }
 
-    private const double TwoPi = Math.PI * 2;
+    public delegate double DeltaCallback64(Path64 path,
+      PathD path_norms, int currPt, int prevPt);
+    public ClipperOffset.DeltaCallback64? DeltaCallback { get; set; }
 
-    public ClipperOffset(double miterLimit = 2.0, 
-      double arcTolerance = 0.0, bool 
+#if USINGZ
+    public ClipperBase.ZCallback64? ZCallback { get; set; }
+#endif
+    public ClipperOffset(double miterLimit = 2.0,
+      double arcTolerance = 0.0, bool
       preserveCollinear = false, bool reverseSolution = false)
     {
       MiterLimit = miterLimit;
@@ -74,11 +130,13 @@ namespace Clipper2Lib
       MergeGroups = true;
       PreserveCollinear = preserveCollinear;
       ReverseSolution = reverseSolution;
+#if USINGZ
+      ZCallback = null;
+#endif
     }
-
     public void Clear()
     {
-      _pathGroups.Clear();
+      _groupList.Clear();
     }
 
     public void AddPath(Path64 path, JoinType joinType, EndType endType)
@@ -93,43 +151,94 @@ namespace Clipper2Lib
     {
       int cnt = paths.Count;
       if (cnt == 0) return;
-      _pathGroups.Add(new Group(paths, joinType, endType));
+      _groupList.Add(new Group(paths, joinType, endType));
     }
 
-    public Paths64 Execute(double delta)
+    private int CalcSolutionCapacity()
     {
-      solution.Clear();
+      int result = 0;
+      foreach (Group g in _groupList)
+        result += (g.endType == EndType.Joined) ? g.inPaths.Count * 2 : g.inPaths.Count;
+      return result;
+    }
+
+    private void ExecuteInternal(double delta)
+    {
+      _solution.Clear();
+      if (_groupList.Count == 0) return;
+      _solution.EnsureCapacity(CalcSolutionCapacity());
+
+      // make sure the offset delta is significant
       if (Math.Abs(delta) < 0.5)
       {
-        foreach (Group group in _pathGroups)
-          foreach (Path64 path in group._inPaths)
-            solution.Add(path);
-        return solution;
+        foreach (Group group in _groupList)
+          foreach (Path64 path in group.inPaths)
+            _solution.Add(path);
+        return;
       }
 
-      _tmpLimit = (MiterLimit <= 1 ? 2.0 : 2.0 / Clipper.Sqr(MiterLimit));
+      _delta = delta;
+      _mitLimSqr = (MiterLimit <= 1 ?
+        2.0 : 2.0 / Clipper.Sqr(MiterLimit));
 
-      foreach (Group group in _pathGroups)
-        DoGroupOffset(group, delta);
+      foreach (Group group in _groupList)
+        DoGroupOffset(group);
+    }
 
-      if (MergeGroups && _pathGroups.Count > 0)
-      {
-        // clean up self-intersections ...
-        Clipper64 c = new Clipper64()
+    internal bool CheckPathsReversed()
+    {
+      bool result = false;
+      foreach (Group g in _groupList)
+        if (g.endType == EndType.Polygon)
         {
-          PreserveCollinear = PreserveCollinear,
-          // the solution should retain the orientation of the input
-          ReverseSolution = ReverseSolution != _pathGroups[0]._pathsReversed
-        };
-        c.AddSubject(solution);
-        if (_pathGroups[0]._pathsReversed)
-          c.Execute(ClipType.Union, FillRule.Negative, solution);
-        else
-          c.Execute(ClipType.Union, FillRule.Positive, solution);
-      }
-      return solution;
+          result = g.pathsReversed;
+          break;
+        }
+      return result;
+    }
+
+    public void Execute(double delta, Paths64 solution)
+    {
+      solution.Clear();
+      ExecuteInternal(delta);
+      if (_groupList.Count == 0) return;
+
+      bool pathsReversed = CheckPathsReversed();
+      FillRule fillRule = pathsReversed ? FillRule.Negative : FillRule.Positive;
+
+      // clean up self-intersections ...
+      Clipper64 c = new Clipper64();
+      c.PreserveCollinear = PreserveCollinear;
+      // the solution should retain the orientation of the input
+      c.ReverseSolution = ReverseSolution != pathsReversed;
+#if USINGZ
+      c.ZCallback = ZCallback;
+#endif
+      c.AddSubject(_solution);
+      c.Execute(ClipType.Union, fillRule, solution);
+    }
+
+    public void Execute(double delta, PolyTree64 solutionTree)
+    {
+      solutionTree.Clear();
+      ExecuteInternal(delta);
+      if (_groupList.Count == 0) return;
+
+      bool pathsReversed = CheckPathsReversed();
+      FillRule fillRule = pathsReversed ? FillRule.Negative : FillRule.Positive;
+      // clean up self-intersections ...
+      Clipper64 c = new Clipper64();
+      c.PreserveCollinear = PreserveCollinear;
+      // the solution should normally retain the orientation of the input
+      c.ReverseSolution = ReverseSolution != pathsReversed;
+#if USINGZ
+      c.ZCallback = ZCallback;
+#endif
+      c.AddSubject(_solution);
+      c.Execute(ClipType.Union, fillRule, solutionTree);
     }
 
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     internal static PointD GetUnitNormal(Point64 pt1, Point64 pt2)
     {
@@ -144,31 +253,48 @@ namespace Clipper2Lib
       return new PointD(dy, -dx);
     }
 
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static int GetLowestPolygonIdx(Paths64 paths)
+    public void Execute(DeltaCallback64 deltaCallback, Paths64 solution)
+    {
+      DeltaCallback = deltaCallback;
+      Execute(1.0, solution);
+    }    
+    
+    internal static int GetLowestPathIdx(Paths64 paths)
     {
-      Point64 lp = new Point64(0, long.MinValue);
       int result = -1;
-      for (int i = 0; i < paths.Count; i++)
+      Point64 botPt = new Point64(Int64.MaxValue, Int64.MinValue);
+      for (int i = 0; i < paths.Count; ++i)
+      {
         foreach (Point64 pt in paths[i])
-        {
-          if (pt.Y < lp.Y || (pt.Y == lp.Y && pt.X >= lp.X)) continue;
+		    {
+          if ((pt.Y < botPt.Y) ||
+            ((pt.Y == botPt.Y) && (pt.X >= botPt.X))) continue;
           result = i;
-          lp = pt;
+          botPt.X = pt.X;
+          botPt.Y = pt.Y;
         }
-      return result;
+      }
+	    return result;
     }
 
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+  [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private static PointD TranslatePoint(PointD pt, double dx, double dy)
     {
-	    return new PointD(pt.x + dx, pt.y + dy);
+#if USINGZ
+      return new PointD(pt.x + dx, pt.y + dy, pt.z);
+#else
+      return new PointD(pt.x + dx, pt.y + dy);
+#endif
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private static PointD ReflectPoint(PointD pt, PointD pivot)
     {
+#if USINGZ
+      return new PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z);
+#else
       return new PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
+#endif
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -231,24 +357,79 @@ namespace Clipper2Lib
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private Point64 GetPerpendic(Point64 pt, PointD norm)
     {
-      return new Point64(pt.X + norm.x * _group_delta,
-        pt.Y + norm.y * _group_delta);
+#if USINGZ
+      return new Point64(pt.X + norm.x * _groupDelta,
+        pt.Y + norm.y * _groupDelta, pt.Z);
+#else
+      return new Point64(pt.X + norm.x * _groupDelta,
+        pt.Y + norm.y * _groupDelta);
+#endif
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private PointD GetPerpendicD(Point64 pt, PointD norm)
     {
-      return new PointD(pt.X + norm.x * _group_delta,
-        pt.Y + norm.y * _group_delta);
+#if USINGZ
+      return new PointD(pt.X + norm.x * _groupDelta,
+        pt.Y + norm.y * _groupDelta, pt.Z);
+#else
+      return new PointD(pt.X + norm.x * _groupDelta,
+        pt.Y + norm.y * _groupDelta);
+#endif
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DoBevel(Path64 path, int j, int k)
+    {
+      Point64 pt1, pt2;
+      if (j == k)
+      {
+        double absDelta = Math.Abs(_groupDelta);
+#if USINGZ
+        pt1 = new Point64(
+          path[j].X - absDelta * _normals[j].x, 
+          path[j].Y - absDelta * _normals[j].y, path[j].Z);
+        pt2 = new Point64(
+          path[j].X + absDelta * _normals[j].x, 
+          path[j].Y + absDelta * _normals[j].y, path[j].Z);
+#else
+        pt1 = new Point64(
+          path[j].X - absDelta * _normals[j].x,
+          path[j].Y - absDelta * _normals[j].y);
+        pt2 = new Point64(
+          path[j].X + absDelta * _normals[j].x,
+          path[j].Y + absDelta * _normals[j].y);
+#endif
+      }
+      else
+      {
+#if USINGZ
+        pt1 = new Point64(
+          path[j].X + _groupDelta * _normals[k].x,
+          path[j].Y + _groupDelta * _normals[k].y, path[j].Z);
+        pt2 = new Point64(
+          path[j].X + _groupDelta * _normals[j].x,
+          path[j].Y + _groupDelta * _normals[j].y, path[j].Z);
+#else
+        pt1 = new Point64(
+          path[j].X + _groupDelta * _normals[k].x,
+          path[j].Y + _groupDelta * _normals[k].y);
+        pt2 = new Point64(
+          path[j].X + _groupDelta * _normals[j].x,
+          path[j].Y + _groupDelta * _normals[j].y);
+#endif
+      }
+      pathOut.Add(pt1);
+      pathOut.Add(pt2);
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private void DoSquare(Group group, Path64 path, int j, int k)
+    private void DoSquare(Path64 path, int j, int k)
     {
       PointD vec;
       if (j == k)
       {
-        vec = new PointD(_normals[0].y, -_normals[0].x);
+        vec = new PointD(_normals[j].y, -_normals[j].x);
       }
       else
       {
@@ -256,66 +437,98 @@ namespace Clipper2Lib
           new PointD(-_normals[k].y, _normals[k].x),
           new PointD(_normals[j].y, -_normals[j].x));
       }
-      
+
+      double absDelta = Math.Abs(_groupDelta);
       // now offset the original vertex delta units along unit vector
       PointD ptQ = new PointD(path[j]);
-      ptQ = TranslatePoint(ptQ, _abs_group_delta * vec.x, _abs_group_delta * vec.y);
+      ptQ = TranslatePoint(ptQ, absDelta * vec.x, absDelta * 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);
+      PointD pt1 = TranslatePoint(ptQ, _groupDelta * vec.y, _groupDelta * -vec.x);
+      PointD pt2 = TranslatePoint(ptQ, _groupDelta * -vec.y, _groupDelta * vec.x);
       // get 2 vertices along one edge offset
       PointD pt3 = GetPerpendicD(path[k], _normals[k]);
 
       if (j == k)
       {
         PointD pt4 = new PointD(
-          pt3.x + vec.x * _group_delta,
-          pt3.y + vec.y * _group_delta);
+          pt3.x + vec.x * _groupDelta,
+          pt3.y + vec.y * _groupDelta);
         PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
+#if USINGZ
+        pt.z = ptQ.z;
+#endif    
         //get the second intersect point through reflecion
-        group._outPath.Add(new Point64(ReflectPoint(pt, ptQ)));
-        group._outPath.Add(new Point64(pt));
+        pathOut.Add(new Point64(ReflectPoint(pt, ptQ)));
+        pathOut.Add(new Point64(pt));
       }
       else
       {
         PointD pt4 = GetPerpendicD(path[j], _normals[k]);
         PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
-        group._outPath.Add(new Point64(pt));
+#if USINGZ
+        pt.z = ptQ.z;
+#endif
+        pathOut.Add(new Point64(pt));
         //get the second intersect point through reflecion
-        group._outPath.Add(new Point64(ReflectPoint(pt, ptQ)));
+        pathOut.Add(new Point64(ReflectPoint(pt, ptQ)));
       }
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private void DoMiter(Group group, Path64 path, int j, int k, double cosA)
     {
-      double q = _group_delta / (cosA + 1);
-      group._outPath.Add(new Point64(
+      double q = _groupDelta / (cosA + 1);
+#if USINGZ
+      pathOut.Add(new Point64(
+          path[j].X + (_normals[k].x + _normals[j].x) * q,
+          path[j].Y + (_normals[k].y + _normals[j].y) * q,
+          path[j].Z));
+#else
+      pathOut.Add(new Point64(
           path[j].X + (_normals[k].x + _normals[j].x) * q,
           path[j].Y + (_normals[k].y + _normals[j].y) * q));
+#endif
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private void DoRound(Group group, Path64 path, int j, int k, double angle)
+    private void DoRound(Path64 path, int j, int k, double angle)
     {
-      // even though angle may be negative this is a convex join
-      Point64 pt = path[j];
-      PointD pt2 = new PointD(_normals[k].x * _group_delta, _normals[k].y * _group_delta);
-      if (j == k) pt2.Negate();
+      if (DeltaCallback != null)
+      {
+        // when DeltaCallback is assigned, _groupDelta won't be constant,
+        // so we'll need to do the following calculations for *every* vertex.
+        double absDelta = Math.Abs(_groupDelta);
+        double arcTol = ArcTolerance > 0.01 ?
+          ArcTolerance :
+          Math.Log10(2 + absDelta) * InternalClipper.defaultArcTolerance;
+        double stepsPer360 = Math.PI / Math.Acos(1 - arcTol / absDelta);
+        _stepSin = Math.Sin((2 * Math.PI) / stepsPer360);
+        _stepCos = Math.Cos((2 * Math.PI) / stepsPer360);
+        if (_groupDelta < 0.0) _stepSin = -_stepSin;
+        _stepsPerRad = stepsPer360 / (2 * Math.PI);
+      }
 
+      Point64 pt = path[j];
+      PointD offsetVec = new PointD(_normals[k].x * _groupDelta, _normals[k].y * _groupDelta);
+      if (j == k) offsetVec.Negate();
+#if USINGZ
+      pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z));
+#else
+      pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y));
+#endif
       int steps = (int) Math.Ceiling(_stepsPerRad * Math.Abs(angle));
-      double stepSin = Math.Sin(angle / steps);
-      double stepCos = Math.Cos(angle / steps);
-
-      group._outPath.Add(new Point64(pt.X + pt2.x, pt.Y + pt2.y));
-      for (int i = 0; i < steps; i++)
+      for (int i = 1; i < steps; i++) // ie 1 less than steps
       {
-        pt2 = new PointD(pt2.x * stepCos - stepSin * pt2.y,
-            pt2.x * stepSin + pt2.y * stepCos);
-        group._outPath.Add(new Point64(pt.X + pt2.x, pt.Y + pt2.y));
+        offsetVec = new PointD(offsetVec.x * _stepCos - _stepSin * offsetVec.y,
+            offsetVec.x * _stepSin + offsetVec.y * _stepCos);
+#if USINGZ
+        pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z));
+#else
+        pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y));
+#endif
       }
-      group._outPath.Add(GetPerpendic(pt, _normals[j]));
+      pathOut.Add(GetPerpendic(pt, _normals[j]));
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -323,7 +536,7 @@ namespace Clipper2Lib
     {
       int cnt = path.Count;
       _normals.Clear();
-      _normals.Capacity = cnt;
+      _normals.EnsureCapacity(cnt);
 
       for (int i = 0; i < cnt - 1; i++)
         _normals.Add(GetUnitNormal(path[i], path[i + 1]));
@@ -332,6 +545,8 @@ namespace Clipper2Lib
 
     private void OffsetPoint(Group group, Path64 path, int j, ref int k)
     {
+      if (path[j] == path[k]) { k = j; return; }
+
       // Let A = change in angle where edges join
       // A == 0: ie no change in angle (flat join)
       // A == PI: edges 'spike'
@@ -341,74 +556,98 @@ namespace Clipper2Lib
       double cosA = InternalClipper.DotProduct(_normals[j], _normals[k]);
       if (sinA > 1.0) sinA = 1.0;
       else if (sinA < -1.0) sinA = -1.0;
-      bool almostNoAngle = (AlmostZero(sinA) && cosA > 0); 
-      if (almostNoAngle || (sinA * _group_delta < 0))
+
+      if (DeltaCallback != null)
+      { 
+        _groupDelta = DeltaCallback(path, _normals, j, k);
+        if (group.pathsReversed) _groupDelta = -_groupDelta;
+      }
+      if (Math.Abs(_groupDelta) < Tolerance)
       {
-        group._outPath.Add(GetPerpendic(path[j], _normals[k]));
-        if (!almostNoAngle) group._outPath.Add(path[j]);
-        group._outPath.Add(GetPerpendic(path[j], _normals[j]));
+        pathOut.Add(path[j]);
+        return;
       }
-      else //convex
+
+      if (cosA > -0.99 && (sinA * _groupDelta < 0)) // test for concavity first (#593)
       {
-        if (_joinType == JoinType.Round)
-          DoRound(group, path, j, k, Math.Atan2(sinA, cosA));
-        else if (_joinType == JoinType.Miter)
-        {
-          // miter unless the angle is so acute the miter would exceeds ML
-          if (cosA > _tmpLimit - 1) DoMiter(group, path, j, k, cosA);
-          else DoSquare(group, path, j, k);
-        }
-        // don't bother squaring angles that deviate < ~20 degrees because
-        // squaring will be indistinguishable from mitering and just be a lot slower
-        else if (cosA > 0.9)
-          DoMiter(group, path, j, k, cosA); 
-        else
-          DoSquare(group, path, j, k);
+        // is concave
+        pathOut.Add(GetPerpendic(path[j], _normals[k]));
+        // this extra point is the only (simple) way to ensure that
+        // path reversals are fully cleaned with the trailing clipper
+        pathOut.Add(path[j]); // (#405)
+        pathOut.Add(GetPerpendic(path[j], _normals[j]));
       }
+      else if ((cosA > 0.999) && (_joinType != JoinType.Round))
+      {
+        // almost straight - less than 2.5 degree (#424, #482, #526 & #724) 
+        DoMiter(group, path, j, k, cosA);
+      }
+      else if (_joinType == JoinType.Miter)
+      {
+        // miter unless the angle is sufficiently acute to exceed ML
+        if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA);
+        else DoSquare(path, j, k);
+      }
+      else if (_joinType == JoinType.Round)
+        DoRound(path, j, k, Math.Atan2(sinA, cosA));
+      else if (_joinType == JoinType.Bevel)
+        DoBevel(path, j, k);
+      else
+        DoSquare(path, j, k);
 
       k = j;
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private void OffsetPolygon(Group group, Path64 path)
+    private void OffsetPolygon(Group group, Path64 path, bool is_shrinking, double area)
     {
-      group._outPath = new Path64();
+      pathOut = new Path64();
       int cnt = path.Count, prev = cnt - 1;
       for (int i = 0; i < cnt; i++)
         OffsetPoint(group, path, i, ref prev);
-      group._outPaths.Add(group._outPath);
+
+      // make sure that polygon areas aren't reversing which would indicate
+      // that the polygon has shrunk too far and that it should be discarded.
+      // See also - #593 & #715
+      if (is_shrinking && area != 0 && // area == 0.0 when JoinType.Joined
+        ((area < 0) != (Clipper.Area(pathOut) < 0))) return;
+
+      _solution.Add(pathOut);
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private void OffsetOpenJoined(Group group, Path64 path)
     {
-      OffsetPolygon(group, path);
+      OffsetPolygon(group, path, false, 0);
       path = Clipper.ReversePath(path);
       BuildNormals(path);
-      OffsetPolygon(group, path);
+      OffsetPolygon(group, path, true, 0);
     }
 
-    private void OffsetOpenPath(Group group, Path64 path, EndType endType)
+    private void OffsetOpenPath(Group group, Path64 path)
     {
-      group._outPath = new Path64();
+      pathOut = new Path64();
       int highI = path.Count - 1;
 
+      if (DeltaCallback != null) 
+        _groupDelta = DeltaCallback(path, _normals, 0, 0);
+
       // do the line start cap
-      switch (endType)
-      {
-        case EndType.Butt:
-          group._outPath.Add(new Point64(
-              path[0].X - _normals[0].x * _group_delta,
-              path[0].Y - _normals[0].y * _group_delta));
-          group._outPath.Add(GetPerpendic(path[0], _normals[0]));
-          break;
-        case EndType.Round:
-          DoRound(group, path, 0, 0, Math.PI);
-          break;
-        default:
-          DoSquare(group, path, 0, 0);
-          break;
-      }
+      if (Math.Abs(_groupDelta) < Tolerance)
+        pathOut.Add(path[0]);
+      else
+        switch (_endType)
+        {
+          case EndType.Butt:
+            DoBevel(path, 0, 0);
+            break;
+          case EndType.Round:
+            DoRound(path, 0, 0, Math.PI);
+            break;
+          default:
+            DoSquare(path, 0, 0);
+            break;
+        }
 
       // offset the left side going forward
       for (int i = 1, k = 0; i < highI; i++)
@@ -419,122 +658,128 @@ namespace Clipper2Lib
         _normals[i] = new PointD(-_normals[i - 1].x, -_normals[i - 1].y);
       _normals[0] = _normals[highI];
 
+      if (DeltaCallback != null)
+        _groupDelta = DeltaCallback(path, _normals, highI, highI);
       // do the line end cap
-      switch (endType)
-      {
-        case EndType.Butt:
-          group._outPath.Add(new Point64(
-              path[highI].X - _normals[highI].x * _group_delta,
-              path[highI].Y - _normals[highI].y * _group_delta));
-          group._outPath.Add(GetPerpendic(path[highI], _normals[highI]));
-          break;
-        case EndType.Round:
-          DoRound(group, path, highI, highI, Math.PI);
-          break;
-        default:
-          DoSquare(group, path, highI, highI);
-          break;
-      }
+      if (Math.Abs(_groupDelta) < Tolerance)
+        pathOut.Add(path[highI]);
+      else
+        switch (_endType)
+        {
+          case EndType.Butt:
+            DoBevel(path, highI, highI);
+            break;
+          case EndType.Round:
+            DoRound(path, highI, highI, Math.PI);
+            break;
+          default:
+            DoSquare(path, highI, highI);
+            break;
+        }
 
       // offset the left side going back
       for (int i = highI, k = 0; i > 0; i--)
         OffsetPoint(group, path, i, ref k);
 
-      group._outPaths.Add(group._outPath);
+      _solution.Add(pathOut);
     }
 
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static bool IsFullyOpenEndType(EndType et)
+    private void DoGroupOffset(Group group)
     {
-      return (et != EndType.Polygon) && (et != EndType.Joined);
-    }
-
-    private void DoGroupOffset(Group group, double delta)
-    {
-      if (group._endType != EndType.Polygon) delta = Math.Abs(delta) / 2;
-      bool isClosedPaths = !IsFullyOpenEndType(group._endType);
-
-      if (isClosedPaths)
+      if (group.endType == EndType.Polygon)
       {
-        // the lowermost polygon must be an outer polygon. So we can use that as the
-        // designated orientation for outer polygons (needed for tidy-up clipping)
-        int lowestIdx = GetLowestPolygonIdx(group._inPaths);
-        if (lowestIdx < 0) return;
-        // nb: don't use the default orientation here ...
-        double area = Clipper.Area(group._inPaths[lowestIdx]);
-        if (area == 0) return;
-        group._pathsReversed = (area < 0);
-        if (group._pathsReversed)
-          delta = -delta;
+        // a straight path (2 points) can now also be 'polygon' offset 
+        // where the ends will be treated as (180 deg.) joins
+        if (group.lowestPathIdx < 0) _delta = Math.Abs(_delta);
+        _groupDelta = (group.pathsReversed) ? -_delta : _delta;
       }
       else
-        group._pathsReversed = false;
+        _groupDelta = Math.Abs(_delta);
 
-      _group_delta = delta;
-      _abs_group_delta = Math.Abs(_group_delta);
-      _joinType = group._joinType;
+      double absDelta = Math.Abs(_groupDelta);
 
-      // calculate a sensible number of steps (for 360 deg for the given offset
-      if (group._joinType == JoinType.Round || group._endType == EndType.Round)
+      _joinType = group.joinType;
+      _endType = group.endType;
+
+      if (group.joinType == JoinType.Round || group.endType == EndType.Round)
       {
+        // calculate a sensible number of steps (for 360 deg for the given offset
+        // arcTol - when fArcTolerance 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 = ArcTolerance > 0.01 ?
-              ArcTolerance :
-              Math.Log10(2 + _abs_group_delta) * 0.25; // empirically derived
-        // get steps per 180 degrees (see offset_triginometry2.svg)
-        _stepsPerRad = Math.PI / Math.Acos(1 - arcTol / _abs_group_delta) / TwoPi;
+          ArcTolerance :              
+          Math.Log10(2 + absDelta) * InternalClipper.defaultArcTolerance; 
+        double stepsPer360 = Math.PI / Math.Acos(1 - arcTol / absDelta);
+        _stepSin = Math.Sin((2 * Math.PI) / stepsPer360);
+        _stepCos = Math.Cos((2 * Math.PI) / stepsPer360);
+        if (_groupDelta < 0.0) _stepSin = -_stepSin;
+        _stepsPerRad = stepsPer360 / (2 * Math.PI);
       }
 
-      foreach (Path64 p in group._inPaths)
+      double min_area = Math.PI * Clipper.Sqr(_groupDelta);
+      using List<Path64>.Enumerator pathIt = group.inPaths.GetEnumerator();
+      using List<bool>.Enumerator isHoleIt = group.isHoleList.GetEnumerator();
+      using List<double>.Enumerator areaIt = group.areasList.GetEnumerator();
+      while (pathIt.MoveNext() && isHoleIt.MoveNext() && areaIt.MoveNext())
       {
-        Path64 path = Clipper.StripDuplicates(p, isClosedPaths);
-        int cnt = path.Count;
-        if (cnt == 0 || (cnt < 3 && !IsFullyOpenEndType(group._endType))) continue;
+        bool isShrinking =
+          (group.endType == EndType.Polygon) &&
+          (group.pathsReversed == ((_groupDelta < 0) == isHoleIt.Current));
+        if (isShrinking && (Math.Abs(areaIt.Current) < min_area)) continue;
+
+        Path64 p = pathIt.Current;
+        bool isHole = isHoleIt.Current;
+
+        pathOut = new Path64();
+        int cnt = p.Count;
 
         if (cnt == 1)
         {
-          group._outPath = new Path64();
+          Point64 pt = p[0];
+
+          if (DeltaCallback != null)
+          {
+            _groupDelta = DeltaCallback(p, _normals, 0, 0);
+            if (group.pathsReversed) _groupDelta = -_groupDelta;
+            absDelta = Math.Abs(_groupDelta);
+          }
+
           // single vertex so build a circle or square ...
-          if (group._endType == EndType.Round)
+          if (group.endType == EndType.Round)
           {
-            double r = _abs_group_delta;
-            if (group._endType == EndType.Polygon) r *= 0.5;
-            group._outPath = Clipper.Ellipse(path[0], r, r);
+            double r = absDelta;
+            int steps = (int) Math.Ceiling(_stepsPerRad * 2 * Math.PI);
+            pathOut = Clipper.Ellipse(pt, r, r, steps);
+#if USINGZ
+            pathOut = InternalClipper.SetZ(pathOut, pt.Z);
+#endif
           }
           else
           {
-            int d = (int) Math.Ceiling(_group_delta);
-            Rect64 r = new Rect64(path[0].X - d, path[0].Y - d, 
-              path[0].X - d, path[0].Y - d);
-            group._outPath = r.AsPath();
+            int d = (int) Math.Ceiling(_groupDelta);
+            Rect64 r = new Rect64(pt.X - d, pt.Y - d, pt.X + d, pt.Y + d);
+            pathOut = r.AsPath();
+#if USINGZ
+            pathOut = InternalClipper.SetZ(pathOut, pt.Z);
+#endif
           }
-          group._outPaths.Add(group._outPath);
-        }
-        else
-        {
-          BuildNormals(path);
-          if (group._endType == EndType.Polygon) OffsetPolygon(group, path);
-          else if (group._endType == EndType.Joined) OffsetOpenJoined(group, path);
-          else OffsetOpenPath(group, path, group._endType);
-        }
-      }
+          _solution.Add(pathOut);
+          continue;
+        } // end of offsetting a single point 
 
-      if (!MergeGroups)
-      {
-        // clean up self-intersections
-        Clipper64 c = new Clipper64()
-        {
-          PreserveCollinear = PreserveCollinear,
-          // the solution should retain the orientation of the input
-          ReverseSolution = ReverseSolution != group._pathsReversed
-        };
-        c.AddSubject(group._outPaths);
-        if (group._pathsReversed)
-          c.Execute(ClipType.Union, FillRule.Negative, group._outPaths);
-        else
-          c.Execute(ClipType.Union, FillRule.Positive, group._outPaths);
+
+        if (cnt == 2 && group.endType == EndType.Joined)
+          _endType = (group.joinType == JoinType.Round) ?
+            EndType.Round :
+            EndType.Square;
+
+        BuildNormals(p);
+        if (_endType == EndType.Polygon) OffsetPolygon(group, p, isShrinking, areaIt.Current);
+        else if (_endType == EndType.Joined) OffsetOpenJoined(group, p);
+        else OffsetOpenPath(group, p);
       }
-      solution.AddRange(group._outPaths);
-      group._outPaths.Clear();
     }
   }
 

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 666 - 152
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.RectClip.cs


+ 311 - 128
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.cs

@@ -1,8 +1,8 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  19 November 2022                                                *
+* Date      :  18 October 2023                                                 *
 * Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
+* Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  This module contains simple functions that will likely cover    *
 *              most polygon boolean and offsetting needs, while also avoiding  *
 *              the inherent complexities of the other modules.                 *
@@ -14,7 +14,6 @@
 #nullable enable
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Runtime.CompilerServices;
 
 namespace Clipper2Lib
@@ -25,14 +24,11 @@ namespace Clipper2Lib
 
   public static class Clipper
   {
-    private static Rect64 maxInvalidRect64 = new Rect64(
-      long.MaxValue, long.MaxValue, long.MinValue, long.MinValue);
+    private static Rect64 invalidRect64 = new Rect64(false);
+    public static Rect64 InvalidRect64 => invalidRect64;
 
-    private static RectD maxInvalidRectD = new RectD(
-      double.MaxValue, double.MaxValue, -double.MaxValue, -double.MaxValue);
-
-    public static Rect64 MaxInvalidRect64 => maxInvalidRect64;
-    public static RectD MaxInvalidRectD => maxInvalidRectD;
+    private static RectD invalidRectD = new RectD(false);
+    public static RectD InvalidRectD => invalidRectD;
 
     public static Paths64 Intersect(Paths64 subject, Paths64 clip, FillRule fillRule)
     {
@@ -92,7 +88,7 @@ namespace Clipper2Lib
         subject, clip, fillRule, precision);
     }
 
-    public static Paths64 BooleanOp(ClipType clipType, 
+    public static Paths64 BooleanOp(ClipType clipType,
       Paths64? subject, Paths64? clip, FillRule fillRule)
     {
       Paths64 solution = new Paths64();
@@ -105,6 +101,18 @@ namespace Clipper2Lib
       return solution;
     }
 
+    public static void BooleanOp(ClipType clipType,
+      Paths64? subject, Paths64? clip, 
+      PolyTree64 polytree, FillRule fillRule)
+    {
+      if (subject == null) return;
+      Clipper64 c = new Clipper64();
+      c.AddPaths(subject, PathType.Subject);
+      if (clip != null)
+        c.AddPaths(clip, PathType.Clip);
+      c.Execute(clipType, fillRule, polytree);
+    }
+
     public static PathsD BooleanOp(ClipType clipType, PathsD subject, PathsD? clip, 
       FillRule fillRule, int precision = 2)
     {
@@ -117,12 +125,26 @@ namespace Clipper2Lib
       return solution;
     }
 
+    public static void BooleanOp(ClipType clipType,
+      PathsD? subject, PathsD? clip,
+      PolyTreeD polytree, FillRule fillRule, int precision = 2)
+    {
+      if (subject == null) return;
+      ClipperD c = new ClipperD(precision);
+      c.AddPaths(subject, PathType.Subject);
+      if (clip != null)
+        c.AddPaths(clip, PathType.Clip);
+      c.Execute(clipType, fillRule, polytree);
+    }
+
     public static Paths64 InflatePaths(Paths64 paths, double delta, JoinType joinType,
       EndType endType, double miterLimit = 2.0)
     {
       ClipperOffset co = new ClipperOffset(miterLimit);
       co.AddPaths(paths, joinType, endType);
-      return co.Execute(delta);
+      Paths64 solution = new Paths64();
+      co.Execute(delta, solution);
+      return solution;
     }
 
     public static PathsD InflatePaths(PathsD paths, double delta, JoinType joinType,
@@ -133,151 +155,94 @@ namespace Clipper2Lib
       Paths64 tmp = ScalePaths64(paths, scale);
       ClipperOffset co = new ClipperOffset(miterLimit);
       co.AddPaths(tmp, joinType, endType);
-      tmp = co.Execute(delta * scale);
+      co.Execute(delta * scale, tmp); // reuse 'tmp' to receive (scaled) solution
       return ScalePathsD(tmp, 1 / scale);
     }
 
-    public static Path64 RectClip(Rect64 rect, Path64 path)
-    {
-      if (rect.IsEmpty() || path.Count == 0) return new Path64();
-      RectClip rc = new RectClip(rect);
-      return rc.ExecuteInternal(path);
-    }
-
     public static Paths64 RectClip(Rect64 rect, Paths64 paths)
     {
       if (rect.IsEmpty() || paths.Count == 0) return new Paths64();
-
-      Paths64 result = new Paths64(paths.Count);
-      RectClip rc = new RectClip(rect);
-      foreach(Path64 path in paths)
-      {
-        Rect64 pathRec = Clipper.GetBounds(path);
-        if (!rect.Intersects(pathRec))
-          continue;
-        else if (rect.Contains(pathRec))
-          result.Add(path);
-        else
-        {
-          Path64 p = rc.ExecuteInternal(path);
-          if (p.Count > 0) result.Add(p);
-        }
-      }
-      return result;
+      RectClip64 rc = new RectClip64(rect);
+      return rc.Execute(paths);
     }
 
-    public static PathD RectClip(RectD rect, PathD path, int precision = 2)
+    public static Paths64 RectClip(Rect64 rect, Path64 path)
     {
-      InternalClipper.CheckPrecision(precision);
-      if (rect.IsEmpty() || path.Count == 0) return new PathD();
-      double scale = Math.Pow(10, precision);
-      Rect64 r = ScaleRect(rect, scale);
-      Path64 tmpPath = ScalePath64(path, scale);
-      RectClip rc = new RectClip(r);
-      tmpPath = rc.ExecuteInternal(tmpPath);
-      return ScalePathD(tmpPath, 1 / scale);
+      if (rect.IsEmpty() || path.Count == 0) return new Paths64();
+      Paths64 tmp = new Paths64 { path };
+      return RectClip(rect, tmp);
     }
-
+    
     public static PathsD RectClip(RectD rect, PathsD paths, int precision = 2)
     {
       InternalClipper.CheckPrecision(precision);
       if (rect.IsEmpty() || paths.Count == 0) return new PathsD();
       double scale = Math.Pow(10, precision);
       Rect64 r = ScaleRect(rect, scale);
-      RectClip rc = new RectClip(r);
-      PathsD result = new PathsD(paths.Count);
-      foreach (PathD p in paths)
-      {
-        RectD pathRec = Clipper.GetBounds(p);
-        if (!rect.Intersects(pathRec))
-          continue;
-        else if (rect.Contains(pathRec))
-          result.Add(p);
-        else
-        {
-          Path64 p64 = ScalePath64(p, scale);
-          p64 = rc.ExecuteInternal(p64);
-          if (p64.Count > 0)
-            result.Add(ScalePathD(p64, 1 / scale));
-        }
-      }
-      return result;
+      Paths64 tmpPath = ScalePaths64(paths, scale);
+      RectClip64 rc = new RectClip64(r);
+      tmpPath = rc.Execute(tmpPath);
+      return ScalePathsD(tmpPath, 1 / scale);
     }
-    public static Paths64 RectClipLines(Rect64 rect, Path64 path)
+
+    public static PathsD RectClip(RectD rect, PathD path, int precision = 2)
     {
-      if (rect.IsEmpty() || path.Count == 0) return new Paths64();
-      RectClipLines rco = new RectClipLines(rect);
-      return rco.ExecuteInternal(path);
+      if (rect.IsEmpty() || path.Count == 0) return new PathsD();
+      PathsD tmp = new PathsD { path };
+      return RectClip(rect, tmp, precision);
     }
-
     public static Paths64 RectClipLines(Rect64 rect, Paths64 paths)
     {
-      Paths64 result = new Paths64(paths.Count);
-      if (rect.IsEmpty() || paths.Count == 0) return result;
-      RectClipLines rco = new RectClipLines(rect);
-      foreach (Path64 path in paths)
-      {
-        Rect64 pathRec = Clipper.GetBounds(path);
-        if (!rect.Intersects(pathRec))
-          continue;
-        else if (rect.Contains(pathRec))
-          result.Add(path);
-        else
-        {
-          Paths64 pp = rco.ExecuteInternal(path);
-          if (pp.Count > 0) result.AddRange(pp);
-        }
-      }
-      return result;
+      if (rect.IsEmpty() || paths.Count == 0) return new Paths64();
+      RectClipLines64 rc = new RectClipLines64(rect);
+      return rc.Execute(paths);
     }
 
-    public static PathsD RectClipLines(RectD rect, PathD path, int precision = 2)
+    public static Paths64 RectClipLines(Rect64 rect, Path64 path)
     {
-      InternalClipper.CheckPrecision(precision);
-      if (rect.IsEmpty() || path.Count == 0) return new PathsD();
-      double scale = Math.Pow(10, precision);
-      Rect64 r = ScaleRect(rect, scale);
-      Path64 tmpPath = ScalePath64(path, scale);
-      RectClipLines rco = new RectClipLines(r);
-      Paths64 tmpPaths = rco.ExecuteInternal(tmpPath);
-      return ScalePathsD(tmpPaths, 1 / scale);
+      if (rect.IsEmpty() || path.Count == 0) return new Paths64();
+      Paths64 tmp = new Paths64 { path };
+      return RectClipLines(rect, tmp);
     }
-    public static PathsD RectClipLines(RectD rect, PathsD paths, int precision = 2)
+
+    public static PathsD RectClipLines(RectD rect, 
+      PathsD paths, int precision = 2)
     {
       InternalClipper.CheckPrecision(precision);
-      PathsD result = new PathsD(paths.Count);
-      if (rect.IsEmpty() || paths.Count == 0) return result;
+      if (rect.IsEmpty() || paths.Count == 0) return new PathsD();
       double scale = Math.Pow(10, precision);
       Rect64 r = ScaleRect(rect, scale);
-      RectClipLines rco = new RectClipLines(r);
-      foreach (PathD p in paths)
-      {
-        RectD pathRec = Clipper.GetBounds(p);
-        if (!rect.Intersects(pathRec))
-          continue;
-        else if (rect.Contains(pathRec))
-          result.Add(p);
-        else
-        {
-          Path64 p64 = ScalePath64(p, scale);
-          Paths64 pp64 = rco.ExecuteInternal(p64);
-          if (pp64.Count == 0) continue;
-          PathsD ppd = ScalePathsD(pp64, 1 / scale);
-          result.AddRange(ppd);
-        }
-      }
-      return result;
+      Paths64 tmpPath = ScalePaths64(paths, scale);
+      RectClipLines64 rc = new RectClipLines64(r);
+      tmpPath = rc.Execute(tmpPath);
+      return ScalePathsD(tmpPath, 1 / scale);
+    }
+    public static PathsD RectClipLines(RectD rect, PathD path, int precision = 2)
+    {
+      if (rect.IsEmpty() || path.Count == 0) return new PathsD();
+      PathsD tmp = new PathsD { path };
+      return RectClipLines(rect, tmp, precision);
     }
     public static Paths64 MinkowskiSum(Path64 pattern, Path64 path, bool isClosed)
     {
       return Minkowski.Sum(pattern, path, isClosed);
     }
 
+    public static PathsD MinkowskiSum(PathD pattern, PathD path, bool isClosed)
+    {
+      return Minkowski.Sum(pattern, path, isClosed);
+    }
+
     public static Paths64 MinkowskiDiff(Path64 pattern, Path64 path, bool isClosed)
     {
       return Minkowski.Diff(pattern, path, isClosed);
     }
 
+    public static PathsD MinkowskiDiff(PathD pattern, PathD path, bool isClosed)
+    {
+      return Minkowski.Diff(pattern, path, isClosed);
+    }
+
     public static double Area(Path64 path)
     {
       // https://en.wikipedia.org/wiki/Shoelace_formula
@@ -376,10 +341,10 @@ namespace Clipper2Lib
     {
       Point64 result = new Point64()
       {
-        X = (long) (pt.X * scale),
-        Y = (long) (pt.Y * scale),
+        X = (long) Math.Round(pt.X * scale, MidpointRounding.AwayFromZero),
+        Y = (long) Math.Round(pt.Y * scale, MidpointRounding.AwayFromZero),
 #if USINGZ
-        Z = (long) (pt.Z),
+        Z = pt.Z
 #endif
       };
       return result;
@@ -589,7 +554,7 @@ namespace Clipper2Lib
 
     public static Rect64 GetBounds(Path64 path)
     {
-      Rect64 result = MaxInvalidRect64;
+      Rect64 result = InvalidRect64;
       foreach (Point64 pt in path)
       {
         if (pt.X < result.left) result.left = pt.X;
@@ -597,12 +562,12 @@ namespace Clipper2Lib
         if (pt.Y < result.top) result.top = pt.Y;
         if (pt.Y > result.bottom) result.bottom = pt.Y;
       }
-      return result.IsEmpty() ? new Rect64() : result;
+      return result.left == long.MaxValue ? new Rect64() : result;
     }
 
     public static Rect64 GetBounds(Paths64 paths)
     {
-      Rect64 result = MaxInvalidRect64;
+      Rect64 result = InvalidRect64;
       foreach (Path64 path in paths)
         foreach (Point64 pt in path)
         {
@@ -611,12 +576,12 @@ namespace Clipper2Lib
           if (pt.Y < result.top) result.top = pt.Y;
           if (pt.Y > result.bottom) result.bottom = pt.Y;
         }
-      return result.IsEmpty() ? new Rect64() : result;
+      return result.left == long.MaxValue ? new Rect64() : result;
     }
 
     public static RectD GetBounds(PathD path)
     {
-      RectD result = MaxInvalidRectD;
+      RectD result = InvalidRectD;
       foreach (PointD pt in path)
       {
         if (pt.x < result.left) result.left = pt.x;
@@ -624,12 +589,12 @@ namespace Clipper2Lib
         if (pt.y < result.top) result.top = pt.y;
         if (pt.y > result.bottom) result.bottom = pt.y;
       }
-      return result.IsEmpty() ? new RectD() : result;
+      return result.left == double.MaxValue ? new RectD() : result;
     }
 
     public static RectD GetBounds(PathsD paths)
     {
-      RectD result = MaxInvalidRectD;
+      RectD result = InvalidRectD;
       foreach (PathD path in paths)
         foreach (PointD pt in path)
         {
@@ -638,7 +603,7 @@ namespace Clipper2Lib
           if (pt.y < result.top) result.top = pt.y;
           if (pt.y > result.bottom) result.bottom = pt.y;
         }
-      return result.IsEmpty() ? new RectD() : result;
+      return result.left == double.MaxValue ? new RectD() : result;
     }
 
     public static Path64 MakePath(int[] arr)
@@ -721,6 +686,7 @@ namespace Clipper2Lib
       return result;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private static void AddPolyNodeToPaths(PolyPath64 polyPath, Paths64 paths)
     {
       if (polyPath.Polygon!.Count > 0)
@@ -729,6 +695,7 @@ namespace Clipper2Lib
         AddPolyNodeToPaths((PolyPath64) polyPath._childs[i], paths);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static Paths64 PolyTreeToPaths64(PolyTree64 polyTree)
     {
       Paths64 result = new Paths64();
@@ -737,6 +704,7 @@ namespace Clipper2Lib
       return result;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static void AddPolyNodeToPathsD(PolyPathD polyPath, PathsD paths)
     {
       if (polyPath.Polygon!.Count > 0)
@@ -745,6 +713,7 @@ namespace Clipper2Lib
         AddPolyNodeToPathsD((PolyPathD) polyPath._childs[i], paths);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static PathsD PolyTreeToPathsD(PolyTreeD polyTree)
     {
       PathsD result = new PathsD();
@@ -757,6 +726,8 @@ namespace Clipper2Lib
       return result;
     }
 
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static double PerpendicDistFromLineSqrd(PointD pt, PointD line1, PointD line2)
     {
       double a = pt.x - line1.x;
@@ -767,6 +738,7 @@ namespace Clipper2Lib
       return Sqr(a * d - c * b) / (c * c + d * d);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static double PerpendicDistFromLineSqrd(Point64 pt, Point64 line1, Point64 line2)
     {
       double a = (double) pt.X - line1.X;
@@ -855,6 +827,175 @@ namespace Clipper2Lib
       return result;
     }
 
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static int GetNext(int current, int high, ref bool[] flags)
+    {
+      ++current;
+      while (current <= high && flags[current]) ++current;
+      if (current <= high) return current;
+      current = 0;
+      while (flags[current]) ++current;
+      return current;
+    }
+
+    private static int GetPrior(int current, int high, ref bool[] flags)
+    {
+      if (current == 0) current = high;
+      else --current;
+      while (current > 0 && flags[current]) --current;
+      if (!flags[current]) return current;
+      current = high;
+      while (flags[current]) --current;
+      return current;
+    }
+
+      public static Path64 SimplifyPath(Path64 path,
+      double epsilon, bool isClosedPath = true)
+    {
+      int len = path.Count, high = len - 1;
+      double epsSqr = Sqr(epsilon);
+      if (len < 4) return path;
+
+      bool[] flags = new bool[len];
+      double[] dsq = new double[len];
+      int curr = 0, prev, start, next, prior2;
+
+      if (isClosedPath)
+      {
+        dsq[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
+        dsq[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
+      }
+      else
+      {
+        dsq[0] = double.MaxValue;
+        dsq[high] = double.MaxValue;
+      }
+
+      for (int i = 1; i < high; ++i)
+        dsq[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
+
+      for (; ; )
+      {
+        if (dsq[curr] > epsSqr)
+        {
+          start = curr;
+          do
+          {
+            curr = GetNext(curr, high, ref flags);
+          } while (curr != start && dsq[curr] > epsSqr);
+          if (curr == start) break;
+        }
+
+        prev = GetPrior(curr, high, ref flags);
+        next = GetNext(curr, high, ref flags);
+        if (next == prev) break;
+
+        if (dsq[next] < dsq[curr])
+        {
+          prior2 = prev;
+          prev = curr;
+          curr = next;
+          next = GetNext(next, high, ref flags);
+        }
+        else
+          prior2 = GetPrior(prev, high, ref flags);
+
+        flags[curr] = true;
+        curr = next;
+        next = GetNext(next, high, ref flags);
+        if (isClosedPath || ((curr != high) && (curr != 0)))
+          dsq[curr] = PerpendicDistFromLineSqrd(path[curr], path[prev], path[next]);
+        if (isClosedPath || ((prev != 0) && (prev != high)))
+          dsq[prev] = PerpendicDistFromLineSqrd(path[prev], path[prior2], path[curr]);
+      }
+      Path64 result = new Path64(len);
+      for (int i = 0; i < len; i++)
+        if (!flags[i]) result.Add(path[i]);
+      return result;
+    }
+
+    public static Paths64 SimplifyPaths(Paths64 paths,
+      double epsilon, bool isClosedPaths = true)
+    {
+      Paths64 result = new Paths64(paths.Count);
+      foreach (Path64 path in paths)
+        result.Add(SimplifyPath(path, epsilon, isClosedPaths));
+      return result;
+    }
+
+    public static PathD SimplifyPath(PathD path,
+      double epsilon, bool isClosedPath = true)
+    {
+      int len = path.Count, high = len - 1;
+      double epsSqr = Sqr(epsilon);
+      if (len < 4) return path;
+
+      bool[] flags = new bool[len];
+      double[] dsq = new double[len];
+      int curr = 0, prev, start, next, prior2;
+      if (isClosedPath)
+      {
+        dsq[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
+        dsq[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
+      }
+      else
+      {
+        dsq[0] = double.MaxValue;
+        dsq[high] = double.MaxValue;
+      }
+      for (int i = 1; i < high; ++i)
+        dsq[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
+
+      for (; ; )
+      {
+        if (dsq[curr] > epsSqr)
+        {
+          start = curr;
+          do
+          {
+            curr = GetNext(curr, high, ref flags);
+          } while (curr != start && dsq[curr] > epsSqr);
+          if (curr == start) break;
+        }
+
+        prev = GetPrior(curr, high, ref flags);
+        next = GetNext(curr, high, ref flags);
+        if (next == prev) break;
+
+        if (dsq[next] < dsq[curr])
+        {
+          prior2 = prev;
+          prev = curr;
+          curr = next;
+          next = GetNext(next, high, ref flags);
+        }
+        else 
+          prior2 = GetPrior(prev, high, ref flags);
+
+        flags[curr] = true;
+        curr = next;
+        next = GetNext(next, high, ref flags);
+        if (isClosedPath || ((curr != high) && (curr != 0)))
+          dsq[curr] = PerpendicDistFromLineSqrd(path[curr], path[prev], path[next]);
+        if (isClosedPath || ((prev != 0) && (prev != high)))
+          dsq[prev] = PerpendicDistFromLineSqrd(path[prev], path[prior2], path[curr]);
+      }
+      PathD result = new PathD(len);
+      for (int i = 0; i < len; i++)
+        if (!flags[i]) result.Add(path[i]);
+      return result;
+    }
+
+    public static PathsD SimplifyPaths(PathsD paths,
+      double epsilon, bool isClosedPath = true)
+    {
+      PathsD result = new PathsD(paths.Count);
+      foreach (PathD path in paths)
+        result.Add(SimplifyPath(path, epsilon, isClosedPath));
+      return result;
+    }
+
     public static Path64 TrimCollinear(Path64 path, bool isOpen = false)
     {
       int len = path.Count;
@@ -969,5 +1110,47 @@ namespace Clipper2Lib
       return result;
     }
 
+    private static void ShowPolyPathStructure(PolyPath64 pp, int level)
+    {
+      string spaces = new string(' ', level * 2);
+      string caption = (pp.IsHole ? "Hole " : "Outer ");
+      if (pp.Count == 0)
+      {
+        Console.WriteLine(spaces + caption);
+      }
+      else
+      {
+        Console.WriteLine(spaces + caption + string.Format("({0})", pp.Count));
+        foreach (PolyPath64 child in pp) { ShowPolyPathStructure(child, level + 1); }
+      }
+    }
+
+    public static void ShowPolyTreeStructure(PolyTree64 polytree)
+    {
+      Console.WriteLine("Polytree Root");
+      foreach (PolyPath64 child in polytree) { ShowPolyPathStructure(child, 1); }
+    }
+
+    private static void ShowPolyPathStructure(PolyPathD pp, int level)
+    {
+      string spaces = new string(' ', level * 2);
+      string caption = (pp.IsHole ? "Hole " : "Outer ");
+      if (pp.Count == 0)
+      {
+        Console.WriteLine(spaces + caption);
+      }
+      else
+      {
+        Console.WriteLine(spaces + caption + string.Format("({0})", pp.Count));
+        foreach (PolyPathD child in pp) { ShowPolyPathStructure(child, level + 1); }
+      }
+    }
+
+    public static void ShowPolyTreeStructure(PolyTreeD polytree)
+    {
+      Console.WriteLine("Polytree Root");
+      foreach (PolyPathD child in polytree) { ShowPolyPathStructure(child, 1); }
+    }
+
   } // Clipper
 } // namespace

BIN
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper2.snk


+ 31 - 33
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper2Lib.csproj

@@ -1,33 +1,31 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
-    <Nullable>enable</Nullable>
-    <LangVersion>8</LangVersion>
-    <Version>1.0.5</Version>
-    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
-    <Authors>Angus Johnson</Authors>
-    <Description>Polygon Clipping and Offsetting Library</Description>
-    <Title>Clipper2</Title>
-    <PackageId>Clipper2</PackageId>
-    <PackageProjectUrl>http://www.angusj.com/clipper2/Docs/Overview.htm</PackageProjectUrl>
-    <Copyright>Copyright © 2010-2022</Copyright>
-    <RepositoryType>git</RepositoryType>
-    <RepositoryUrl>https://github.com/AngusJohnson/Clipper2</RepositoryUrl>
-    <PackageReleaseNotes>This is a major update of my original Clipper library, first released over 10yrs ago.</PackageReleaseNotes>
-    <Platforms>AnyCPU;x86</Platforms>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
-    <DefineConstants>TRACE</DefineConstants>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
-    <DefineConstants>TRACE</DefineConstants>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <None Remove="*.ico" />
-  </ItemGroup>
-
-</Project>
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>8</LangVersion>
+    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
+    <Authors>Angus Johnson</Authors>
+    <Description>Polygon Clipping and Offsetting Library</Description>
+    <Title>Clipper2</Title>
+    <PackageId>Clipper2</PackageId>
+    <PackageProjectUrl>http://www.angusj.com/clipper2/Docs/Overview.htm</PackageProjectUrl>
+    <Copyright>Copyright © 2010-2023</Copyright>
+    <RepositoryType>git</RepositoryType>
+    <RepositoryUrl>https://github.com/AngusJohnson/Clipper2</RepositoryUrl>
+    <PackageReleaseNotes>Major revision to merging polygons in clipping solutions</PackageReleaseNotes>
+    <Platforms>AnyCPU;x86</Platforms>
+    <SignAssembly>true</SignAssembly>
+    <AssemblyOriginatorKeyFile>Clipper2.snk</AssemblyOriginatorKeyFile>
+    <DelaySign>false</DelaySign>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+</Project>

+ 126 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib/HashCode.cs

@@ -0,0 +1,126 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+
+namespace Clipper2Lib
+{
+  /*
+
+  Licensed to the .NET Foundation under one or more agreements.
+  // The .NET Foundation licenses this file to you under the MIT license.
+
+  The xxHash32 implementation is based on the code published by Yann Collet:
+  https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c
+
+    xxHash - Fast Hash algorithm
+    Copyright (C) 2012-2016, Yann Collet
+
+    BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+
+    * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following disclaimer
+    in the documentation and/or other materials provided with the
+    distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+    You can contact the author at :
+    - xxHash homepage: http://www.xxhash.com
+    - xxHash source repository : https://github.com/Cyan4973/xxHash
+  */
+
+  public struct HashCode
+  {
+    private static readonly uint s_seed = GenerateGlobalSeed();
+
+    private const uint Prime1 = 2654435761U;
+    private const uint Prime2 = 2246822519U;
+    private const uint Prime3 = 3266489917U;
+    private const uint Prime4 = 668265263U;
+    private const uint Prime5 = 374761393U;
+
+    private static uint GenerateGlobalSeed()
+    {
+      using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create();
+      byte[] data = new byte[sizeof(uint)];
+      randomNumberGenerator.GetBytes(data);
+      return BitConverter.ToUInt32(data, 0);
+    }
+
+    public static int Combine<T1, T2>(T1 value1, T2 value2)
+    {
+      uint hc1 = (uint) (value1?.GetHashCode() ?? 0);
+      uint hc2 = (uint) (value2?.GetHashCode() ?? 0);
+
+      uint hash = MixEmptyState();
+      hash += 8;
+
+      hash = QueueRound(hash, hc1);
+      hash = QueueRound(hash, hc2);
+
+      hash = MixFinal(hash);
+      return (int) hash;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static uint QueueRound(uint hash, uint queuedValue)
+    {
+      return RotateLeft(hash + (queuedValue * Prime3), 17) * Prime4;
+    }
+
+    private static uint MixEmptyState()
+    {
+      return s_seed + Prime5;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static uint MixFinal(uint hash)
+    {
+      hash ^= hash >> 15;
+      hash *= Prime2;
+      hash ^= hash >> 13;
+      hash *= Prime3;
+      hash ^= hash >> 16;
+      return hash;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static uint RotateLeft(uint value, int offset)
+    {
+      return (value << offset) | (value >> (32 - offset));
+    }
+
+#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
+    [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)]
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public override int GetHashCode()
+    {
+      throw new NotSupportedException($"{nameof(HashCode)}.{nameof(GetHashCode)}() is not supported");
+    }
+
+    [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)]
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public override bool Equals(object? obj)
+    {
+      throw new NotSupportedException($"{nameof(HashCode)}.{nameof(Equals)}() is not supported");
+    }
+#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member
+  }
+}

+ 79 - 0
polygon.mod/clipper2/CSharp/USINGZ.TestApp/Program.cs

@@ -0,0 +1,79 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  24 January 2023                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System.Collections.Generic;
+using System.Reflection;
+using System.IO;
+using System;
+using System.Security.Cryptography;
+using System.Xml.Linq;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+
+using Clipper2Lib;
+
+public class Application
+{
+  public class MyCallbacks
+  {
+    public void MyCallback64(Point64 bot1, Point64 top1,
+      Point64 bot2, Point64 top2, ref Point64 intersectPt)
+    {
+      intersectPt.Z = 1;
+    }
+
+    public void MyCallbackD(PointD bot1, PointD top1,
+        PointD bot2, PointD top2, ref PointD intersectPt)
+    {
+      intersectPt.z = 1;
+    }
+  }
+
+  public static void Main()
+  {
+    PathsD solution = new PathsD();
+    PathsD subject = new PathsD();
+    subject.Add(Clipper.MakePath(new double[] { 100, 50, 10, 79, 65, 2, 65, 98, 10, 21 }));
+
+    ClipperD clipperD = new ClipperD();
+    MyCallbacks cb = new MyCallbacks();
+    clipperD.ZCallback = cb.MyCallbackD;
+    clipperD.AddSubject(subject);
+    clipperD.Execute(ClipType.Union, FillRule.NonZero, solution);
+
+
+    Console.WriteLine(solution.ToString(0)); 
+
+    SvgWriter svg= new SvgWriter(FillRule.NonZero);
+    SvgUtils.AddSubject(svg, subject);
+    SvgUtils.AddSolution(svg, solution, false);
+
+    PathsD ellipses = new PathsD();
+    for (int i = 0; i < solution[0].Count; i++)
+    {
+      if (solution[0][i].z == 1)
+        ellipses.Add(Clipper.Ellipse(
+          new PointD(solution[0][i].x, solution[0][i].y), 4));
+    }
+    svg.AddClosedPaths(ellipses, 0x20FF0000, 0xFFFF0000, 1);
+    svg.SaveToFile("usingz.svg", 300, 300);
+    OpenFileWithDefaultApp("usingz.svg");
+  }
+
+  public static void OpenFileWithDefaultApp(string filename)
+  {
+    string path = Path.GetFullPath(filename);
+    if (!File.Exists(path)) return;
+    Process p = new Process() 
+    { 
+      StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } 
+    };
+    p.Start();
+  }
+
+}

+ 23 - 0
polygon.mod/clipper2/CSharp/USINGZ.TestApp/UsingZTestApp.csproj

@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>$(DefineConstants);USINGZ</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\USINGZ\Clipper2LibZ.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="..\Utils\SVG\Clipper.SVG.cs" Link="Clipper.SVG.cs" />
+    <Compile Include="..\Utils\SVG\Clipper.SVG.Utils.cs" Link="Clipper.SVG.Utils.cs" />
+  </ItemGroup>
+  
+</Project>

+ 31 - 0
polygon.mod/clipper2/CSharp/USINGZ.TestApp/UsingZ_TestApp.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33205.214
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UsingZTestApp", "UsingZTestApp.csproj", "{34A93225-8048-4A5A-8741-BC619C13C7B0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2LibZ", "..\USINGZ\Clipper2LibZ.csproj", "{C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{34A93225-8048-4A5A-8741-BC619C13C7B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{34A93225-8048-4A5A-8741-BC619C13C7B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{34A93225-8048-4A5A-8741-BC619C13C7B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{34A93225-8048-4A5A-8741-BC619C13C7B0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {0E1221D6-32D4-4974-9205-DE97EC5F4937}
+	EndGlobalSection
+EndGlobal

+ 8 - 3
polygon.mod/clipper2/CSharp/USINGZ/Clipper2LibZ.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>netstandard2.0</TargetFramework>
     <Nullable>enable</Nullable>
     <LangVersion>8</LangVersion>
   </PropertyGroup>
@@ -15,7 +15,12 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <None Remove="*.ico" />
+    <Compile Include="..\Clipper2Lib\HashCode.cs" Link="HashCode.cs" />
+    <Compile Include="..\Clipper2Lib\Clipper.cs" Link="Clipper.cs" />
+    <Compile Include="..\Clipper2Lib\Clipper.Engine.cs" Link="Clipper.Engine.cs" />
+    <Compile Include="..\Clipper2Lib\Clipper.Offset.cs" Link="Clipper.Offset.cs" />
+    <Compile Include="..\Clipper2Lib\Clipper.Core.cs" Link="Clipper.Core.cs" />
+    <Compile Include="..\Clipper2Lib\Clipper.RectClip.cs" Link="Clipper.RectClip.cs" />
+    <Compile Include="..\Clipper2Lib\Clipper.Minkowski.cs" Link="Clipper.Minkowski.cs" />
   </ItemGroup>
-
 </Project>

+ 13 - 13
polygon.mod/clipper2/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs

@@ -17,9 +17,9 @@ namespace Clipper2Lib
   {
     public static Paths64 PathFromStr(string s)
     {
-      if (s == null) return null;
-      Path64 p = new ();
-      Paths64 pp = new ();
+      if (s == null) return new Paths64();
+      Path64 p = new Path64();
+      Paths64 pp = new Paths64();
       int len = s.Length, i = 0, j;
       while (i < len)
       {
@@ -32,7 +32,7 @@ namespace Clipper2Lib
         if (i >= len || s[i] < 48 || s[i] > 57) break;
         j = i + 1;
         while (j < len && s[j] > 47 && s[j] < 58) j++;
-        if (!long.TryParse(s[i..j], out long x)) break;
+        if (!long.TryParse(s.Substring(i, j - i), out long x)) break;
         if (isNeg) x = -x;
         //skip space or comma between X & Y ...
         i = j;
@@ -44,7 +44,7 @@ namespace Clipper2Lib
         if (i >= len || s[i] < 48 || s[i] > 57) break;
         j = i + 1;
         while (j < len && s[j] > 47 && s[j] < 58) j++;
-        if (!long.TryParse(s[i..j], out long y)) break;
+        if (!long.TryParse(s.Substring(i,j-i), out long y)) break;
         if (isNeg) y = -y;
         p.Add(new Point64(x, y));
         //skip trailing space, comma ...
@@ -58,14 +58,14 @@ namespace Clipper2Lib
             nlCnt++;
             if (nlCnt == 2)
             {
-              if (p.Count > 2) pp.Add(p);
+              if (p.Count > 0) pp.Add(p);
               p = new Path64();
             }
           }
           i++;
         }
       }
-      if (p.Count > 2) pp.Add(p);
+      if (p.Count > 0) pp.Add(p);
       return pp;
     }
     //------------------------------------------------------------------------------
@@ -103,7 +103,7 @@ namespace Clipper2Lib
         {
           num--;
           if (num != 0) continue;
-          caption = s[9..]; 
+          caption = s.Substring(9);
           result = true;
           continue;
         }
@@ -131,13 +131,13 @@ namespace Clipper2Lib
 
         if (s.IndexOf("SOL_AREA: ", StringComparison.Ordinal) == 0)
         {
-          area = long.Parse(s[10..]);
+          area = long.Parse(s.Substring(10));
           continue;
         }
 
         if (s.IndexOf("SOL_COUNT: ", StringComparison.Ordinal) == 0)
         {
-          count = int.Parse(s[11..]);
+          count = int.Parse(s.Substring(11));
           continue;
         }
 
@@ -252,10 +252,10 @@ namespace Clipper2Lib
 
     public static Paths64 AffineTranslatePaths(Paths64 paths, long dx, long dy)
     {
-      Paths64 result = new (paths.Count);
+      Paths64 result = new Paths64(paths.Count);
       foreach (Path64 path in paths)
       {
-        Path64 p = new (path.Count);
+        Path64 p = new Path64(path.Count);
         foreach (Point64 pt in path)
           p.Add(new Point64(pt.X + dx, pt.Y + dy));
         result.Add(p);
@@ -267,7 +267,7 @@ namespace Clipper2Lib
     {
       string path = Path.GetFullPath(filename);
       if (!File.Exists(path)) return;
-      Process p = new() { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } };
+      Process p = new Process() { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } };
       p.Start();
     }
 

+ 15 - 12
polygon.mod/clipper2/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj

@@ -1,12 +1,15 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>net5.0</TargetFramework>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
-    <ProjectReference Include="..\SVG\Clipper2.SVG.csproj" />
-  </ItemGroup>
-
-</Project>
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>8</LangVersion>
+    <Platforms>AnyCPU;x86</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
+    <ProjectReference Include="..\SVG\Clipper2.SVG.csproj" />
+  </ItemGroup>
+
+</Project>

+ 75 - 0
polygon.mod/clipper2/CSharp/Utils/Colors/Clipper.Colors.cs

@@ -0,0 +1,75 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  11 February 2023                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Clipper2Lib
+{
+
+  public static class Colors
+  {
+    public struct Hsl
+    {
+      public byte alpha = 0;
+      public byte hue = 0;
+      public byte sat = 0;
+      public byte lum = 0;
+      public Hsl(byte a, byte h, byte s, byte l)
+      { alpha = a; hue = h; sat = s; lum = l; }
+    }
+
+    [StructLayout(LayoutKind.Explicit)]
+    public struct Color32
+    {
+      [FieldOffset(0)]
+      public byte b = 0;
+      [FieldOffset(1)]
+      public byte g = 0;
+      [FieldOffset(2)]
+      public byte r = 0;
+      [FieldOffset(3)]
+      public byte a = 0;
+      [FieldOffset(0)]
+      public UInt32 color;
+      public Color32(uint val = 0) { color = val; }
+    }
+
+    public static Color32 HslToRgb(Hsl hsl)
+    {
+      long c, x, m, a;
+      c = ((255 - Math.Abs(2 * hsl.lum - 255)) * hsl.sat) >> 8;
+      a = 252 - (hsl.hue % 85) * 6;
+      x = (c * (255 - Math.Abs(a))) >> 8;
+      m = hsl.lum - c / 2;
+      Color32 result = new Color32();
+      result.a = hsl.alpha;
+      switch ((hsl.hue * 6) >> 8)
+      {
+        case 0: { result.r = (byte) (c + m); result.g = (byte) (x + m); result.b = (byte) (0 + m); break; };
+        case 1: { result.r = (byte) (x + m); result.g = (byte) (c + m); result.b = (byte) (0 + m); break; };
+        case 2: { result.r = (byte) (0 + m); result.g = (byte) (c + m); result.b = (byte) (x + m); break; };
+        case 3: { result.r = (byte) (0 + m); result.g = (byte) (x + m); result.b = (byte) (c + m); break; };
+        case 4: { result.r = (byte) (x + m); result.g = (byte) (0 + m); result.b = (byte) (c + m); break; };
+        case 5: { result.r = (byte) (c + m); result.g = (byte) (0 + m); result.b = (byte) (x + m); break; };
+      }
+      return result;
+    }
+
+    public static Color32 Rainbow(
+      double frac, byte luminance = 128, byte alpha = 255)
+    {
+      frac = (double) (frac - (int) (frac));
+      Hsl hsl = new Hsl(alpha, (byte) (frac * 255), 255, luminance);
+      Color32 result = HslToRgb(hsl);
+      return result;
+    }
+  }
+  
+}

+ 14 - 0
polygon.mod/clipper2/CSharp/Utils/Colors/Clipper.Colors.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>8</LangVersion>
+    <Platforms>AnyCPU;x86</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
+  </ItemGroup>
+
+</Project>

+ 60 - 31
polygon.mod/clipper2/CSharp/Utils/SVG/Clipper.SVG.Utils.cs

@@ -10,62 +10,91 @@ using System.IO;
 
 namespace Clipper2Lib
 {
-
   public static class SvgUtils
   {
-
-    public static void AddCaption(SimpleSvgWriter svg, string caption, int x, int y)
+    public static void AddCaption(SvgWriter svg, string caption, int x, int y)
     {
       svg.AddText(caption, x, y, 14);
     }
 
-    public static void AddSubject(SimpleSvgWriter svg, Paths64 paths,
-      bool is_closed = true, bool is_joined = true)
+    public static void AddSubject(SvgWriter svg, Path64 path)
+    {
+      Paths64 paths = new Paths64();
+      paths.Add(path);
+      svg.AddClosedPaths(paths, 0x1800009C, 0xAAB3B3DA, 0.8);
+    }
+    public static void AddSubject(SvgWriter svg, PathD path)
     {
-      if (!is_closed)
-        svg.AddPaths(paths, !is_joined, 0x0, 0xCCB3B3DA, 0.8);
-      else
-        svg.AddPaths(paths, false, 0x1800009C, 0xCCB3B3DA, 0.8);
+      PathsD paths = new PathsD();
+      paths.Add(path);
+      svg.AddClosedPaths(paths, 0x1800009C, 0xAAB3B3DA, 0.8);
     }
 
-    public static void AddSubject(SimpleSvgWriter svg, PathsD paths,
-      bool is_closed = true, bool is_joined = true)
+    public static void AddSubject(SvgWriter svg, Paths64 paths)
     {
-      if (!is_closed)
-        svg.AddPaths(paths, !is_joined, 0x0, 0xCCB3B3DA, 0.8);
-      else
-        svg.AddPaths(paths, false, 0x1800009C, 0xCCB3B3DA, 0.8);
+      svg.AddClosedPaths(paths, 0x1800009C, 0xAAB3B3DA, 0.8);
+    }
+    public static void AddOpenSubject(SvgWriter svg, Paths64 paths)
+    {
+        svg.AddOpenPaths(paths, 0xAAB3B3DA, 0.8);
     }
 
-    public static void AddClip(SimpleSvgWriter svg, Paths64 paths)
+    public static void AddSubject(SvgWriter svg, PathsD paths)
     {
-      svg.AddPaths(paths, false, 0x129C0000, 0xCCFFA07A, 0.8);
+        svg.AddClosedPaths(paths, 0x1800009C, 0xAAB3B3DA, 0.8);
     }
 
-    public static void AddClip(SimpleSvgWriter svg, PathsD paths)
+    public static void AddOpenSubject(SvgWriter svg, PathsD paths)
     {
-      svg.AddPaths(paths, false, 0x129C0000, 0xCCFFA07A, 0.8);
+        svg.AddOpenPaths(paths, 0xAAB3B3DA, 1.2);
     }
 
-    public static void AddSolution(SimpleSvgWriter svg, Paths64 paths,
-      bool show_coords, bool is_closed = true, bool is_joined = true)
+    public static void AddClip(SvgWriter svg, Path64 path)
+    {
+      Paths64 paths = new Paths64();
+      paths.Add(path);
+      svg.AddClosedPaths(paths, 0x129C0000, 0xCCFFA07A, 0.8);
+    }
+
+    public static void AddClip(SvgWriter svg, PathD path)
+    {
+      PathsD paths = new PathsD();
+      paths.Add(path);
+      svg.AddClosedPaths(paths, 0x129C0000, 0xCCFFA07A, 0.8);
+    }
+
+    public static void AddClip(SvgWriter svg, Paths64 paths)
     {
-      if (!is_closed)
-        svg.AddPaths(paths, !is_joined, 0x0, 0xFF003300, 0.8, show_coords);
-      else
-        svg.AddPaths(paths, false, 0x4080ff9C, 0xFF003300, 0.8, show_coords);
+      svg.AddClosedPaths(paths, 0x129C0000, 0xCCFFA07A, 0.8);
     }
 
-    public static void AddSolution(SimpleSvgWriter svg, PathsD paths,
+    public static void AddClip(SvgWriter svg, PathsD paths)
+    {
+      svg.AddClosedPaths(paths, 0x129C0000, 0xCCFFA07A, 0.8);
+    }
+
+    public static void AddSolution(SvgWriter svg, Paths64 paths,
       bool show_coords, bool is_closed = true, bool is_joined = true)
     {
-      if (!is_closed)
-        svg.AddPaths(paths, !is_joined, 0x0, 0xFF003300, 0.8, show_coords);
-      else
-        svg.AddPaths(paths, false, 0x8080ff9C, 0xFF003300, 0.8, show_coords);
+        svg.AddClosedPaths(paths, 0x4080ff9C, 0xFF003300, 1.5, show_coords);
+    }
+
+    public static void AddOpenSolution(SvgWriter svg, Paths64 paths, bool show_coords)
+    {
+        svg.AddOpenPaths(paths, 0xFF003300, 2.2, show_coords);
+    }
+
+    public static void AddSolution(SvgWriter svg, PathsD paths, bool show_coords)
+    {
+        svg.AddClosedPaths(paths, 0x4080ff9C, 0xFF003300, 1.5, show_coords);
+    }
+
+    public static void AddOpenSolution(SvgWriter svg, PathsD paths, bool show_coords)
+    {
+        svg.AddOpenPaths(paths, 0xFF003300, 2.2, show_coords);
     }
 
-    public static void SaveToFile(SimpleSvgWriter svg,
+    public static void SaveToFile(SvgWriter svg,
       string filename, FillRule fill_rule,
       int max_width = 0, int max_height = 0, int margin = 0)
     {

+ 59 - 15
polygon.mod/clipper2/CSharp/Utils/SVG/Clipper.SVG.cs

@@ -13,9 +13,8 @@ using System.IO;
 
 namespace Clipper2Lib
 {
-  public class SimpleSvgWriter
+  public class SvgWriter
   {
-
     public const uint black = 0xFF000000;
     public const uint white = 0xFFFFFFFF;
     public const uint maroon = 0xFF800000;
@@ -29,11 +28,10 @@ namespace Clipper2Lib
     public const uint fuscia = 0xFFFF00FF;
     public const uint aqua = 0xFF00FFFF;
 
-    private static RectD rectMax =
-      new (double.MaxValue, double.MaxValue, -double.MaxValue, -double.MaxValue);
+    private static RectD rectMax = Clipper.InvalidRectD;
     public static RectD RectMax => rectMax;
 
-    private static RectD rectEmpty = new (0, 0, 0, 0);
+    private static RectD rectEmpty = new RectD(true);
     public static RectD RectEmpty => rectEmpty;
     internal static bool IsValidRect(RectD rec)
     {
@@ -92,8 +90,8 @@ namespace Clipper2Lib
     }
 
     public FillRule FillRule { get; set; }
-    private readonly List<PolyInfo> PolyInfoList = new ();
-    private readonly List<TextInfo> textInfos = new ();
+    private readonly List<PolyInfo> PolyInfoList = new List<PolyInfo>();
+    private readonly List<TextInfo> textInfos = new List<TextInfo>();
     private readonly CoordStyle coordStyle;
 
     private const string svg_header = "<?xml version=\"1.0\" standalone=\"no\"?>\n" +
@@ -105,7 +103,7 @@ namespace Clipper2Lib
     private const string svg_path_format2 = "\"\n style=\"fill:none; stroke:{0};" +
         "stroke-opacity:{1:f2}; stroke-width:{2:f2};\"/>\n\n";
 
-    public SimpleSvgWriter(FillRule fillrule = FillRule.EvenOdd,
+    public SvgWriter(FillRule fillrule = FillRule.EvenOdd,
       string coordFontName = "Verdana", int coordFontsize = 9, uint coordFontColor = black)
     {
       coordStyle = new CoordStyle(coordFontName, coordFontsize, coordFontColor);
@@ -127,25 +125,71 @@ namespace Clipper2Lib
       PolyInfoList.Clear();
       textInfos.Clear();
     }
+    public void AddClosedPath(Path64 path, uint brushColor,
+      uint penColor, double penWidth, bool showCoords = false)
+    {
+      Paths64 tmp = new Paths64();
+      tmp.Add(path);
+      AddClosedPaths(tmp, brushColor, penColor, penWidth, showCoords);
+    }
 
+    public void AddClosedPath(PathD path, uint brushColor,
+      uint penColor, double penWidth, bool showCoords = false)
+    {
+      PathsD tmp = new PathsD();
+      tmp.Add(path);
+      AddClosedPaths(tmp, brushColor, penColor, penWidth, showCoords);
+    }
 
-    public void AddPaths(Paths64 paths, bool IsOpen, uint brushColor,
+    public void AddClosedPaths(Paths64 paths, uint brushColor,
       uint penColor, double penWidth, bool showCoords = false)
     {
       if (paths.Count == 0) return;
       PolyInfoList.Add(new PolyInfo(Clipper.PathsD(paths),
-        brushColor, penColor, penWidth, showCoords, IsOpen));
+        brushColor, penColor, penWidth, showCoords, false));
     }
-    //------------------------------------------------------------------------------
 
-    public void AddPaths(PathsD paths, bool IsOpen, uint brushColor,
+    public void AddClosedPaths(PathsD paths, uint brushColor,
       uint penColor, double penWidth, bool showCoords = false)
     {
       if (paths.Count == 0) return;
       PolyInfoList.Add(new PolyInfo(paths,
-        brushColor, penColor, penWidth, showCoords, IsOpen));
+        brushColor, penColor, penWidth, showCoords, false));
+    }
+
+    public void AddOpenPath(Path64 path,  uint penColor, 
+      double penWidth, bool showCoords = false)
+    {
+      Paths64 tmp = new Paths64();
+      tmp.Add(path);
+      AddOpenPaths(tmp, penColor, penWidth, showCoords);
+    }
+
+    public void AddOpenPath(PathD path, uint penColor, 
+      double penWidth, bool showCoords = false)
+    {
+      PathsD tmp = new PathsD();
+      tmp.Add(path);
+      AddOpenPaths(tmp, penColor, penWidth, showCoords);
     }
 
+    public void AddOpenPaths(Paths64 paths,
+      uint penColor, double penWidth, bool showCoords = false)
+    {
+      if (paths.Count == 0) return;
+      PolyInfoList.Add(new PolyInfo(Clipper.PathsD(paths),
+        0x0, penColor, penWidth, showCoords, true));
+    }
+
+    public void AddOpenPaths(PathsD paths, uint penColor, 
+      double penWidth, bool showCoords = false)
+    {
+      if (paths.Count == 0) return;
+      PolyInfoList.Add(new PolyInfo(paths,
+        0x0, penColor, penWidth, showCoords, true));
+    }
+
+
     public void AddText(string cap, int posX, int posY, int fontSize, uint fontClr = black)
     {
       textInfos.Add(new TextInfo(cap, posX, posY, fontSize, fontClr));
@@ -153,7 +197,7 @@ namespace Clipper2Lib
 
     private RectD GetBounds()
     {
-      RectD bounds = new (RectMax);
+      RectD bounds = new RectD(RectMax);
       foreach (PolyInfo pi in PolyInfoList)
         foreach (PathD path in pi.paths)
           foreach (PointD pt in path)
@@ -261,7 +305,7 @@ namespace Clipper2Lib
       {
         writer.Write("<g font-family=\"Verdana\" font-style=\"normal\" " +
                      "font-weight=\"normal\" font-size=\"{0}\" fill=\"{1}\">\n", captionInfo.fontSize, ColorToHtml(captionInfo.fontColor));
-        writer.Write("<text x=\"{0}\" y=\"{1}\">{2}</text>\n</g>\n", captionInfo.posX + margin, captionInfo.posY + margin, captionInfo.text);
+        writer.Write("<text x=\"{0}\" y=\"{1}\">{2}</text>\n</g>\n", captionInfo.posX * scale + offsetX, captionInfo.posY * scale + offsetX, captionInfo.text);
       }
 
       writer.Write("</svg>\n");

+ 14 - 11
polygon.mod/clipper2/CSharp/Utils/SVG/Clipper2.SVG.csproj

@@ -1,11 +1,14 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>net5.0</TargetFramework>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
-  </ItemGroup>
-
-</Project>
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>8</LangVersion>
+    <Platforms>AnyCPU;x86</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
+  </ItemGroup>
+
+</Project>

+ 0 - 0
polygon.mod/clipper2/DLL/CSharp_TestApp/COMPILE AND ADD Clipper2_64.dll HERE


+ 11 - 0
polygon.mod/clipper2/DLL/CSharp_TestApp/CSharp_TestApp.csproj

@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <Platforms>x64</Platforms>
+  </PropertyGroup>
+
+</Project>

+ 25 - 0
polygon.mod/clipper2/DLL/CSharp_TestApp/CSharp_TestApp.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33801.468
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharp_TestApp", "CSharp_TestApp.csproj", "{CFC62F44-8150-4BD9-A65F-2FC3876F561C}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x64 = Debug|x64
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Debug|x64.ActiveCfg = Debug|x64
+		{CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Debug|x64.Build.0 = Debug|x64
+		{CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Release|x64.ActiveCfg = Release|x64
+		{CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Release|x64.Build.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {F0327373-C3AF-400F-B7EB-33118D1EEE68}
+	EndGlobalSection
+EndGlobal

+ 291 - 0
polygon.mod/clipper2/DLL/CSharp_TestApp/Program.cs

@@ -0,0 +1,291 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  29 October 2023                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace ClipperDllDemo
+{
+
+  public class Application
+  {
+
+    // CreateCPaths: The CPaths<T> structure is defined in
+    // clipper.export.h and is a simple array of long[] or
+    // double[] that represents a number of path contours.
+
+    static T[]? CreateCPath<T>(T[] coords)
+    {      
+      int pathLen = coords.Length / 2;
+      if (pathLen == 0) return null;
+      int arrayLen = pathLen * 2 + 2;
+      T[] result = new T[arrayLen];
+      result[0] = (T)Convert.ChangeType(pathLen, typeof(T));
+      result[1] = (T)Convert.ChangeType(0, typeof(T));
+      coords.CopyTo(result, 2);
+      return result;  
+    }
+
+    static T[] CreateCPaths<T>(List<T[]> listOfCPath)
+    {
+      int pathCount = listOfCPath.Count();
+      int arrayLen = 2;
+      foreach (T[] path in listOfCPath)
+        arrayLen += path.Length;
+      T[] result = new T[arrayLen];
+
+      result[0] = (T)Convert.ChangeType(arrayLen, typeof(T));
+      result[1] = (T)Convert.ChangeType(pathCount, typeof(T));
+
+      int idx = 2;
+      foreach (T[] cpath in listOfCPath)
+      {
+        cpath.CopyTo(result, idx);
+        idx += cpath.Length;
+      }
+      return result;
+    }
+
+    // or create a cpaths array that contains just 1 path
+    static T[] CreateCPaths<T>(T[] coords)
+    {
+      int pathLen = coords.Length / 2;
+      int arrayLen = pathLen *2 + 2 + 2;
+      T[] result = new T[arrayLen];
+
+      result[0] = (T)Convert.ChangeType(arrayLen, typeof(T));
+      result[1] = (T)Convert.ChangeType(1, typeof(T)); // 1 path
+
+      result[2] = (T)Convert.ChangeType(pathLen, typeof(T));
+      result[3] = (T)Convert.ChangeType(0, typeof(T));
+
+      coords.CopyTo(result, 4);
+      return result;
+    }
+
+    public static string XyCoordsAsString<T>(T X, T Y, int precision = 0)
+    {
+      if (typeof(T) == typeof(long)) // ignore precision
+        return $"{X},{Y} ";
+      else
+        return string.Format($"{{0:F{precision}}},{{1:F{precision}}} ", X, Y);
+    }
+
+    public static void DisplayCPath<T>(T[] cpaths, ref int idx, string spaceIndent)
+    {
+      int vertexCnt = Convert.ToInt32(cpaths[idx]);
+      idx += 2;
+      for (int i = 0; i < vertexCnt; i++)
+        Console.Write(spaceIndent + 
+          XyCoordsAsString<T>(cpaths[idx++], cpaths[idx++], 2));
+      Console.Write("\n");
+    }
+
+    public static void DisplayCPaths<T>(T[]? cpaths, string spaceIndent)
+    {
+      if (cpaths == null) return;
+      int pathCnt = Convert.ToInt32(cpaths[1]);
+      int idx = 2;
+      for (int i = 0; i < pathCnt; i++)
+        DisplayCPath<T>(cpaths, ref idx, spaceIndent);
+    }
+
+    // Note: The CPolyTree<T> structure defined in clipper.export.h is 
+    // a simple array of T that contains any number of nested path contours.
+
+    public static void DisplayPolyPath<T>(T[] polypath, 
+      ref int idx, bool isHole, string spaceIndent, int precision)
+    {
+      int polyCnt = Convert.ToInt32(polypath[idx++]);
+      int childCnt = Convert.ToInt32(polypath[idx++]);
+      string preamble = isHole ? "Hole: " : (spaceIndent == "") ? 
+        "Polygon: " : "Nested Polygon: ";
+      Console.Write(spaceIndent + preamble);
+      spaceIndent += "  ";
+      for (int i = 0; i < polyCnt; i++)
+        Console.Write(XyCoordsAsString<T>(polypath[idx++], polypath[idx++], precision));
+      Console.Write("\n");
+      for (int i = 0; i < childCnt; i++)
+        DisplayPolyPath<T>(polypath, ref idx, !isHole, spaceIndent, precision);
+    }
+
+    public static void DisplayPolytree<T>(T[] polytree, int precision)
+    {
+      int cnt = Convert.ToInt32(polytree[1]);
+      int idx = 2;
+      for (int i = 0; i < cnt; i++) 
+        DisplayPolyPath<T>(polytree, ref idx, false, "  ", precision);
+    }
+
+    public static T[]? GetArrayFromIntPtr<T>(IntPtr paths)
+    {
+      if (paths == IntPtr.Zero) return null;
+      if (typeof(T) == typeof(long))
+      {
+        long[] len = new long[1];
+        Marshal.Copy(paths, len, 0, 1);
+        long[] res = new long[(int)len[0]];
+        Marshal.Copy(paths, res, 0, (int)len[0]);
+        return res as T[];
+      }
+      else if (typeof(T) == typeof(double))
+      {
+        double[] len = new double[1];
+        Marshal.Copy(paths, len, 0, 1);
+        double[] res = new double[(int)len[0]];
+        Marshal.Copy(paths, res, 0, (int)len[0]);
+        return res as T[];
+      }
+      else return null;
+    }
+  
+    // DLL exported function definitions /////////////////////
+
+    const string clipperDll = @"..\..\..\..\Clipper2_64.dll";
+
+    [DllImport(clipperDll, EntryPoint = 
+      "Version", CallingConvention = CallingConvention.Cdecl)]
+    static extern IntPtr Version();
+
+    [DllImport(clipperDll, EntryPoint = 
+      "BooleanOp64", CallingConvention = CallingConvention.Cdecl)]
+    static extern Int32 BooleanOp64(byte clipType, byte fillRule,
+      long[] subjects, long[]? openSubs, long[]? clips,
+      out IntPtr solution, out IntPtr openSol, bool preserveCollinear, bool reverseSolution);
+
+    [DllImport(clipperDll, EntryPoint = 
+      "BooleanOpD", CallingConvention = CallingConvention.Cdecl)]
+    static extern Int32 BooleanOpD(byte clipType, byte fillRule,
+      double[] subjects, double[]? openSubs, double[]? clips,
+      out IntPtr solution, out IntPtr openSol, Int32 precision, bool preserveCollinear, bool reverseSolution);
+
+    [DllImport(clipperDll, EntryPoint =
+      "DisposeArray64", CallingConvention = CallingConvention.Cdecl)]
+    static extern void DisposeArray64(ref IntPtr intptr);
+
+    // DisposeExported(): since all these functions behave identically ...
+    [DllImport(clipperDll, EntryPoint =
+      "DisposeArrayD", CallingConvention = CallingConvention.Cdecl)]
+    static extern void DisposeArrayD(ref IntPtr intptr);
+
+    [DllImport(clipperDll, EntryPoint = 
+      "BooleanOp_PolyTree64", CallingConvention = CallingConvention.Cdecl)]
+    static extern Int32 BooleanOp_PolyTree64(byte cliptype,
+      byte fillrule, long[] subjects, long[]? openSubs, long[]? clips,
+      out IntPtr solTree, out IntPtr openSol,
+      bool preserve_collinear, bool reverse_solution);
+
+    [DllImport(clipperDll, EntryPoint = 
+      "BooleanOp_PolyTreeD", CallingConvention = CallingConvention.Cdecl)]
+    static extern Int32 BooleanOp_PolyTreeD(byte cliptype,
+      byte fillrule, double[] subjects, double[]? openSubs, double[]? clips,
+      out IntPtr solTree, out IntPtr openSol, Int32 precision,
+      bool preserve_collinear, bool reverse_solution);
+    
+
+    public static readonly byte None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4;
+    public static readonly byte EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3;
+
+
+    /// Main Entry ////////////////////////////////////////////////////////////
+    public static void Main()
+    {
+
+      //string? ver = Marshal.PtrToStringAnsi(Version());
+      //Console.WriteLine(ver +"\n");
+
+      // test BooleanOp64() ///////////////////////////////////////////////////
+      Console.WriteLine("BooleanOp64:");
+      long[] cSubject = CreateCPaths(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 });
+      long[] cClip = CreateCPaths(new long[] { 20, 20, 120, 20, 120, 120, 20, 120 });
+      
+      if (BooleanOp64(Intersection, NonZero, cSubject,
+        null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false) != 0) return;
+
+      long[]? cSolution = GetArrayFromIntPtr<long>(cSol);
+      // clean up unmanaged memory
+      DisposeArray64(ref cSol);
+      DisposeArray64(ref cSolOpen);
+
+      DisplayCPaths(cSolution, "  ");
+      /////////////////////////////////////////////////////////////////////////
+
+      // test BooleanOpD() ////////////////////////////////////////////////////
+      Console.WriteLine("BooleanOpD:");
+      double[] cSubjectD = CreateCPaths(new double[] { 0, 0, 100, 0, 100, 100, 0, 100 });
+      double[] cClipD = CreateCPaths(new double[] { 20, 20, 120, 20, 120, 120, 20, 120 });
+      int resultD = BooleanOpD(Intersection, NonZero, cSubjectD,
+        null, cClipD, out IntPtr cSolD, out IntPtr cSolOpenD, 2, false, false);
+      if (resultD != 0) return;
+      double[]? cSolutionD = GetArrayFromIntPtr<double>(cSolD);
+      // clean up unmanaged memory
+      DisposeArrayD(ref cSolD);
+      DisposeArrayD(ref cSolOpenD);
+
+      DisplayCPaths(cSolutionD, "  ");
+      /////////////////////////////////////////////////////////////////////////
+
+
+
+      // test BooleanOp_PolyTree64() //////////////////////////////////////////
+      Console.WriteLine("BooleanOp_PolyTree64:");
+
+      List<long[]> subList = new(5);
+      for (int i = 1; i < 6; ++i)
+        subList.Add(CreateCPath(new long[] { 
+          -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })!);
+
+      long[] cSubject3 = CreateCPaths(subList);
+      long[] cClip3 = CreateCPaths(new long[] { -90, -120, 90, -120, 90, 120, -90, 120 });
+
+      int result3 = BooleanOp_PolyTree64(Intersection, EvenOdd, cSubject3, null, cClip3,
+        out IntPtr cSol_pt64, out IntPtr cSolOpen_pt64, false, false);
+      if (result3 != 0) return;
+
+      long[]? cPolyTree64 = GetArrayFromIntPtr<long>(cSol_pt64);
+      // clean up unmanaged memory
+      DisposeArray64(ref cSol_pt64);
+      DisposeArray64(ref cSolOpen_pt64);
+
+      if (cPolyTree64 == null) return;
+      DisplayPolytree<long>(cPolyTree64, 2);
+      /////////////////////////////////////////////////////////////////////////
+
+
+      // test BooleanOp_PolyTreeD() ///////////////////////////////////////////
+      Console.WriteLine("BooleanOp_PolyTreeD:");
+
+      List<double[]> subList2 = new(5);
+      for (int i = 1; i < 6; ++i)
+        subList2.Add(CreateCPath(new double[] { 
+          -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })!);
+
+      double[] cSubject4 = CreateCPaths(subList2);
+      double[] cClip4 = CreateCPaths(new double[] { -90, -120, 90, -120, 90, 120, -90, 120 });
+
+      int result4 = BooleanOp_PolyTreeD(Intersection, EvenOdd, cSubject4, null, cClip4,
+        out IntPtr cSol_ptD, out IntPtr cSolOpen_ptD, 2, false, false);
+      if (result4 != 0) return;
+
+      double[]? cPolyTreeD = GetArrayFromIntPtr<double>(cSol_ptD);
+
+      // clean up unmanaged memory
+      DisposeArrayD(ref cSol_ptD);
+      DisposeArrayD(ref cSolOpen_ptD);
+
+      if (cPolyTreeD == null) return;
+      DisplayPolytree<double>(cPolyTreeD, 2);
+      /////////////////////////////////////////////////////////////////////////
+
+
+      Console.WriteLine("\nPress any key to exit ... ");
+      Console.ReadKey();
+    }
+
+  } //end Application
+} //namespace

BIN
polygon.mod/clipper2/DLL/CSharp_TestApp/polytree_sample.png


+ 0 - 0
polygon.mod/clipper2/DLL/CSharp_TestApp2/COMPILE AND ADD Clipper2_64.dll HERE


+ 16 - 0
polygon.mod/clipper2/DLL/CSharp_TestApp2/CSharp_TestApp2.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <Platforms>x64</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\CSharp\Clipper2Lib\Clipper2Lib.csproj" />
+    <ProjectReference Include="..\..\CSharp\Utils\SVG\Clipper2.SVG.csproj" />
+  </ItemGroup>
+
+</Project>

+ 63 - 0
polygon.mod/clipper2/DLL/CSharp_TestApp2/CSharp_TestApp2.sln

@@ -0,0 +1,63 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33801.468
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharp_TestApp2", "CSharp_TestApp2.csproj", "{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib", "..\..\CSharp\Clipper2Lib\Clipper2Lib.csproj", "{401DBA71-BB90-495E-9F91-CC495FEE264D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2.SVG", "..\..\CSharp\Utils\SVG\Clipper2.SVG.csproj", "{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|Any CPU.ActiveCfg = Debug|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x64.ActiveCfg = Debug|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x64.Build.0 = Debug|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x86.ActiveCfg = Debug|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x86.Build.0 = Debug|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|Any CPU.ActiveCfg = Release|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x64.ActiveCfg = Release|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x64.Build.0 = Release|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x86.ActiveCfg = Release|x64
+		{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x86.Build.0 = Release|x64
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|x64.Build.0 = Debug|Any CPU
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|x86.ActiveCfg = Debug|x86
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|x86.Build.0 = Debug|x86
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|x64.ActiveCfg = Release|Any CPU
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|x64.Build.0 = Release|Any CPU
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|x86.ActiveCfg = Release|x86
+		{401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|x86.Build.0 = Release|x86
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|x64.Build.0 = Debug|Any CPU
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|x86.ActiveCfg = Debug|x86
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|x86.Build.0 = Debug|x86
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|Any CPU.Build.0 = Release|Any CPU
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|x64.ActiveCfg = Release|Any CPU
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|x64.Build.0 = Release|Any CPU
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|x86.ActiveCfg = Release|x86
+		{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {DAE344E0-A107-49C8-B269-1E1896665E6B}
+	EndGlobalSection
+EndGlobal

+ 184 - 0
polygon.mod/clipper2/DLL/CSharp_TestApp2/Program.cs

@@ -0,0 +1,184 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  26 October 2023                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Clipper2Lib;
+
+namespace ClipperDllDemo
+{
+  public class Application
+  {
+
+    // Define miscellaneous functions ////////////////////////////
+    public static void OpenFileWithDefaultApp(string filename)
+    {
+      string path = Path.GetFullPath(filename);
+      if (!File.Exists(path)) return;
+      Process p = new() { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } };
+      p.Start();
+    }
+
+    public static Path64 MakePath(int[] arr)
+    {
+      int len = arr.Length / 2;
+      Path64 p = new(len);
+      for (int i = 0; i < len; i++)
+        p.Add(new Point64(arr[i * 2], arr[i * 2 + 1]));
+      return p;
+    }
+
+    private static Point64 MakeRandomPt(int maxWidth, int maxHeight, Random rand)
+    {
+      long x = rand.Next(maxWidth);
+      long y = rand.Next(maxHeight);
+      return new Point64(x, y);
+    }
+
+    public static Path64 MakeRandomPath(int width, int height, int count, Random rand)
+    {
+      Path64 result = new(count);
+      for (int i = 0; i < count; ++i)
+        result.Add(MakeRandomPt(width, height, rand));
+      return result;
+    }
+
+    static Path64 GetPath64FromCPath(long[] cpaths, ref int idx)
+    {
+      int cnt = (int)cpaths[idx]; idx += 2;
+      Path64 result = new(cnt);
+      for (int i = 0; i < cnt; i++)
+      {
+        long x = cpaths[idx++];
+        long y = cpaths[idx++];
+        result.Add(new Point64(x, y));
+      }
+      return result;
+    }
+
+    static Paths64 GetPaths64FromCPaths(long[] cpaths)
+    {
+      int cnt = (int)cpaths[1], idx = 2;
+      Paths64 result = new(cnt);
+      for (int i = 0; i < cnt; i++)
+        result.Add(GetPath64FromCPath(cpaths, ref idx));
+      return result;
+    }
+
+    static long[] CreateCPaths64(Paths64 pp)
+    {
+      int len = pp.Count, len2 = 2;
+      for (int i = 0; i < len; i++)
+        if (pp[i].Count > 0)
+          len2 += pp[i].Count * 2 + 2;
+      long[] result = new long[len2];
+      result[0] = 0;
+      result[1] = len;
+      int rPos = 2;
+      for (int i = 0; i < len; i++)
+      {
+        len2 = pp[i].Count;
+        if (len2 == 0) continue;
+        result[rPos++] = len2;
+        result[rPos++] = 0;
+        for (int j = 0; j < len2; j++)
+        {
+          result[rPos++] = pp[i][j].X;
+          result[rPos++] = pp[i][j].Y;
+        }
+      }
+      return result;
+    }
+
+    public static long[]? GetPathsFromIntPtr(IntPtr paths)
+    {
+      if (paths == IntPtr.Zero) return null;
+      long[] len = new long[1];
+      Marshal.Copy(paths, len, 0, 1);
+      long[] result = new long[len[0]];
+      Marshal.Copy(paths, result, 0, (int)len[0]);
+      return result; 
+    }
+
+    // Define DLL exported functions /////////////////////
+
+    public const string clipperDll = @"..\..\..\..\Clipper2_64.dll";
+
+    [DllImport(clipperDll, EntryPoint = "Version", CallingConvention = CallingConvention.Cdecl)]
+    static extern IntPtr Version();
+
+    [DllImport(clipperDll, EntryPoint = "BooleanOp64", CallingConvention = CallingConvention.Cdecl)]
+    static extern int BooleanOp64(byte clipType, byte fillRule,
+      long[] subject, long[]? subOpen, long[]? clip,
+      out IntPtr solution, out IntPtr solOpen, bool preserveCollinear, bool reverseSolution);
+
+    [DllImport(clipperDll, EntryPoint = "DisposeArray64", CallingConvention = CallingConvention.Cdecl)]
+    static extern void DisposeArray64(ref IntPtr paths);
+
+    static readonly byte None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4;
+    static readonly byte EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3;
+
+    public static void Main()
+    {
+
+      //string? ver = Marshal.PtrToStringAnsi(Version());
+      //Console.WriteLine(ver + "\n");
+
+      long timeMsec;
+      Random rand = new();
+
+      ////////////////////////////////////////////////////////////////////////
+      int edgeCount = 2500;
+      ////////////////////////////////////////////////////////////////////////
+
+      Paths64 subject = new() { MakeRandomPath(600,400, edgeCount, rand)};
+      Paths64 clip = new() { MakeRandomPath(600, 400, edgeCount, rand) };
+
+      //////////////////////////////////////////////////////////////////////
+      // Use Dynamically Linked C++ compiled library (ie use the DLL)
+      // NB: time will include ALL the overhead of swapping path structures 
+      Stopwatch sw1 = Stopwatch.StartNew();
+      long[] cSubject = CreateCPaths64(subject);
+      long[] cClip = CreateCPaths64(clip);
+      if (BooleanOp64(Intersection, NonZero, cSubject,
+        null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false) != 0)
+          return;
+
+      long[]? cSolution = GetPathsFromIntPtr(cSol);
+      if (cSolution == null) return;
+      DisposeArray64(ref cSol);
+      DisposeArray64(ref cSolOpen);
+      Paths64 solution = GetPaths64FromCPaths(cSolution);
+      sw1.Stop();
+      timeMsec = sw1.ElapsedMilliseconds;
+      Console.WriteLine($"Time using DLL (C++ code): {timeMsec} ms");
+      //////////////////////////////////////////////////////////////////////
+
+      //////////////////////////////////////////////////////////////////////
+      // Use Clipper2's statically linked C# compiled library
+      Stopwatch sw2 = Stopwatch.StartNew();
+      Clipper.Intersect(subject, clip, FillRule.NonZero);
+      sw2.Stop();
+      timeMsec = sw2.ElapsedMilliseconds;
+      Console.WriteLine($"Time using C# code       : {timeMsec} ms");
+      //////////////////////////////////////////////////////////////////////
+
+      string fileName = "../../../clipper2_dll.svg";
+      SvgWriter svg = new(FillRule.NonZero);
+      SvgUtils.AddSubject(svg, subject);
+      SvgUtils.AddClip(svg, clip);
+      SvgUtils.AddSolution(svg, solution, false);
+      svg.SaveToFile(fileName, 800, 600, 20);
+      OpenFileWithDefaultApp(fileName);
+
+      Console.WriteLine("Press any key to exit ... ");
+      Console.ReadKey();
+    }
+
+  } //end Application
+} //namespace

+ 0 - 0
polygon.mod/clipper2/DLL/Delphi_TestApp/COMPILE AND ADD Clipper2_64.dll HERE


+ 0 - 663
polygon.mod/clipper2/DLL/Delphi_TestApp/SvgWriter.pas

@@ -1,663 +0,0 @@
- unit SvgWriter;
-
-(*******************************************************************************
-* Author    :  Angus Johnson                                                   *
-* Date      :  26 October 2022                                                 *
-* Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2022                                         *
-* Purpose   :  This module provides a very simple SVG Writer                   *
-* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
-*******************************************************************************)
-
-interface
-
-{$I ..\..\Delphi\Clipper2Lib\Clipper.inc}
-
-uses
-  Classes,
-  {$IFDEF USING_CLIPPER2_SRC}
-  Clipper,
-  {$ENDIF}
-  SysUtils, Math;
-
-const
-  black   = $FF000000;
-  white   = $FFFFFFFF;
-  maroon  = $FF800000;
-  navy    = $FF000080;
-  blue    = $FF0000FF;
-  red     = $FFFF0000;
-  green   = $FF008000;
-  yellow  = $FFFFFF00;
-  lime    = $FF00FF00;
-  fuscia  = $FFFF00FF;
-  aqua    = $FF00FFFF;
-
-type
-
-{$IFNDEF USING_CLIPPER2_SRC}
-  TFillRule = (frEvenOdd, frNonZero, frPositive, frNegative);
-  TPointD = record X,Y: double; end;
-  TPathD = array of TPointD;
-  TPathsD = array of TPathD;
-  TRectD = record left,top,right,bottom: double; end;
-  TPoint64 = record X,Y: Int64; end;
-  TPath64 = array of TPoint64;
-  TPaths64 = array of TPath64;
-  TRect64 = record left,top,right,bottom: Int64; end;
-{$ENDIF}
-
-  PPointD = ^TPointD;
-  TArrayOfInteger = array of Integer;
-
-{$IFDEF RECORD_METHODS}
-  TCoordStyle = record
-{$ELSE}
-  TCoordStyle = object
-{$ENDIF}
-    FontName: string;
-    FontSize: integer;
-    FontColor: cardinal;
-    constructor Create(const afontname: string;
-      afontsize: integer; afontcolor: Cardinal);
-  end;
-
-  PTextInfo = ^TTextInfo;
-{$IFDEF RECORD_METHODS}
-  TTextInfo = record
-{$ELSE}
-  TTextInfo = object
-{$ENDIF}
-    x,y : integer;
-    text: string;
-    fontSize: integer;
-    fontColor: Cardinal;
-    Bold: Boolean;
-    constructor Create(atext: string; _x, _y: integer;
-      afontsize: integer = 12;
-      afontcolor: Cardinal = black;
-      aBold: Boolean = false);
-  end;
-
-  PPolyInfo = ^TPolyInfo;
-  TPolyInfo = record
-    paths     : TPathsD;
-    BrushClr  : Cardinal;
-    PenClr    : Cardinal;
-    PenWidth  : double;
-    ShowCoords: Boolean;
-    IsOpen    : Boolean;
-    dashes    : TArrayOfInteger;
-  end;
-
-  PCircleInfo = ^TCircleInfo;
-  TCircleInfo = record
-    center    : TPointD;
-    radius    : double;
-    BrushClr  : Cardinal;
-    PenClr    : Cardinal;
-    PenWidth  : double;
-  end;
-
-  TSvgWriter = class
-  private
-    fFillRule   : TFillRule;
-    fCoordStyle : TCoordStyle;
-    fPolyInfos  : TList;
-    fCircleInfos: TList;
-    fTextInfos  : TList;
-    function GetBounds: TRectD;
-  public
-    constructor Create(fillRule: TFillRule;
-      const coordFontName: string = 'Verdana';
-      coordFontSize: integer = 9;
-      coordFontColor: Cardinal = black);
-    destructor Destroy; override;
-    procedure AddPaths(const paths: TPaths64; isOpen: Boolean;
-      brushColor, penColor: Cardinal;
-      penWidth: double; showCoords: Boolean = false); overload;
-    procedure AddPaths(const paths: TPathsD; isOpen: Boolean;
-      brushColor, penColor: Cardinal;
-      penWidth: double; showCoords: Boolean = false); overload;
-    procedure AddDashedPath(const paths: TPathsD; penColor: Cardinal;
-      penWidth: double; const dashes: TArrayOfInteger);
-
-    procedure AddArrow(const center: TPointD;
-      radius: double; angleRad: double;
-      brushColor, penColor: Cardinal;
-      penWidth: double); overload;
-    procedure AddCircle(const center: TPointD; radius: double;
-      brushColor, penColor: Cardinal; penWidth: double); overload;
-    procedure AddText(text: string; x,y: integer;
-      fontSize: integer = 14; fontClr:
-      Cardinal = black; bold: Boolean = false);
-    function SaveToFile(const filename: string;
-      maxWidth: integer = 0; maxHeight: integer = 0;
-      margin: integer = 20): Boolean;
-    procedure ClearPaths;
-    procedure ClearText;
-    procedure ClearAll;
-  end;
-
-  procedure AddSubject(svg: TSvgWriter; const paths: TPaths64); overload;
-  procedure AddOpenSubject(svg: TSvgWriter; const paths: TPaths64); overload;
-  procedure AddClip(svg: TSvgWriter; const paths: TPaths64); overload;
-  procedure AddSolution(svg: TSvgWriter; const paths: TPaths64); overload;
-  procedure AddOpenSolution(svg: TSvgWriter; const paths: TPaths64); overload;
-  procedure AddSubject(svg: TSvgWriter; const paths: TPathsD); overload;
-  procedure AddOpenSubject(svg: TSvgWriter; const paths: TPathsD); overload;
-  procedure AddClip(svg: TSvgWriter; const paths: TPathsD); overload;
-  procedure AddSolution(svg: TSvgWriter; const paths: TPathsD); overload;
-  procedure AddOpenSolution(svg: TSvgWriter; const paths: TPathsD); overload;
-
-  procedure SaveSvg(svg: TSvgWriter; const filename: string;
-    width: integer = 0; height: integer = 0; margin: integer = 0);
-
-
-implementation
-
-const
-  MaxRect: TRectD  = (left: MaxDouble;
-    Top: MaxDouble; Right: -MaxDouble; Bottom: -MaxDouble);
-  NullRectD   : TRectD = (left: 0; top: 0; right: 0; Bottom: 0);
-
-  svg_header: string =
-      '<svg width="%dpx" height="%dpx" viewBox="0 0 %0:d %1:d" ' +
-      'version="1.1" xmlns="http://www.w3.org/2000/svg">';
-  svg_path_format: string = '"'+#10+'    style="fill:%s;' +
-        ' fill-opacity:%1.2f; fill-rule:%s; stroke:%s;' +
-        ' stroke-opacity:%1.2f; stroke-width:%1.2f;"/>'#10;
-  svg_path_format2: string = '"'+#10+'    style="fill:none; stroke:%s; ' +
-        'stroke-opacity:%1.2f; stroke-width:%1.2f; %s"/>'#10;
-
-function GetBounds(const paths: TPathsD): TRectD;
-var
-  i, j: Integer;
-  p: PPointD;
-begin
-  Result.Left :=infinity;
-  Result.Top := infinity;
-  Result.Right := -infinity;
-  Result.Bottom := -infinity;
-
-  for i := 0 to High(paths) do
-    if Assigned(paths[i]) then
-    begin
-      p := @paths[i][0];
-      for j := 0 to High(paths[i]) do
-      begin
-        if p.X < Result.Left then Result.Left := p.X;
-        if p.X > Result.Right then Result.Right := p.X;
-        if p.Y < Result.Top then Result.Top := p.Y;
-        if p.Y > Result.Bottom then Result.Bottom := p.Y;
-        inc(p);
-      end;
-    end;
-  if Result.Left >= Result.Right then Result := nullRectD;
-end;
-
-function PathD(const path: TPath64): TPathD;
-var
-  i, len: integer;
-begin
-  len := Length(path);
-  setLength(Result, len);
-  for i := 0 to len -1 do
-  begin
-    Result[i].X := path[i].X;
-    Result[i].Y := path[i].Y;
-  end;
-end;
-
-function PathsD(const paths: TPaths64): TPathsD;
-var
-  i, len: integer;
-begin
-  len := Length(paths);
-  setLength(Result, len);
-  for i := 0 to len -1 do
-    Result[i] := PathD(paths[i]);
-end;
-//------------------------------------------------------------------------------
-
-function Ellipse(l,t,r,b: double; steps: integer): TPathD;
-var
-  i: Integer;
-  x, sinA, cosA: double;
-  centre, radius, delta: TPointD;
-begin
-  result := nil;
-  if (r <= l) or (b <= t) then Exit;
-  centre.X := (r + l) * 0.5;
-  centre.Y := (b + t) * 0.5;
-  radius.X := (r - l) * 0.5;
-  radius.Y := (b - l) * 0.5;
-  if (steps < 3) then
-    steps := Ceil(PI * sqrt((radius.X + radius.Y) * 2));
-  SinCos(2 * Pi / Steps, sinA, cosA);
-  delta.x := cosA; delta.y := sinA;
-  SetLength(Result, Steps);
-  Result[0].X := centre.X + radius.X;
-  Result[0].y := centre.Y;
-  for i := 1 to steps -1 do
-  begin
-
-    Result[i].X := centre.X + radius.X * delta.x;
-    Result[i].Y := centre.Y + radius.y * delta.y;
-    x := delta.X * cosA - delta.Y * sinA;
-    delta.X :=  x;
-    delta.Y := delta.Y * cosA + x * sinA;
-  end; // rotates clockwise
-end;
-
-procedure RotatePt(var pt: TPointD; const center: TPointD; sinA, cosA: double);
-var
-  tmpX, tmpY: double;
-begin
-  tmpX := pt.X-center.X;
-  tmpY := pt.Y-center.Y;
-  pt.X := tmpX * cosA - tmpY * sinA + center.X;
-  pt.Y := tmpX * sinA + tmpY * cosA + center.Y;
-end;
-
-procedure RotatePath(var path: TPathD;
-  const center: TPointD; sinA, cosA: double);
-var
-  i: integer;
-begin
-  for i := 0 to High(path) do
-    RotatePt(path[i], center, sinA, cosA);
-end;
-//------------------------------------------------------------------------------
-
-
-function ColorToHtml(color: Cardinal): string;
-begin
-  Result := Format('#%6.6x', [color and $FFFFFF]);;
-end;
-
-function GetAlpha(clr: Cardinal): double;
-begin
-  Result := (clr shr 24) / 255;
-end;
-
-constructor TCoordStyle.Create(const afontname: string;
-  afontsize: integer; afontcolor: Cardinal);
-begin
-  Self.FontName   := afontname;
-  Self.FontSize   := afontsize;
-  Self.FontColor  := afontcolor;
-end;
-
-constructor TTextInfo.Create(atext: string; _x, _y: integer;
-  afontsize: integer = 12; afontcolor: Cardinal = black;
-  aBold: Boolean = false);
-begin
-  self.x := _x;
-  self.y := _y;
-  self.text := text;
-  self.fontSize := afontsize;
-  self.fontColor := afontcolor;
-  Self.Bold       := aBold;
-end;
-
-constructor TSvgWriter.Create(fillRule: TFillRule;
-  const coordFontName: string = 'Verdana';
-  coordFontSize: integer = 9;
-  coordFontColor: Cardinal = black);
-begin
-  fFillRule := fillRule;
-  fCoordStyle.FontName := coordFontName;
-  fCoordStyle.FontSize := coordFontSize;
-  fCoordStyle.FontColor := coordFontColor;
-  fPolyInfos := TList.Create;
-  fCircleInfos := TList.Create;
-  fTextInfos := TList.Create;
-end;
-
-destructor TSvgWriter.Destroy;
-begin
-  ClearAll;
-  fPolyInfos.Free;
-  fCircleInfos.Free;
-  fTextInfos.Free;
-  inherited;
-end;
-
-procedure TSvgWriter.AddPaths(const paths: TPaths64; isOpen: Boolean;
-  brushColor, penColor: Cardinal;
-  penWidth: double; showCoords: Boolean = false);
-var
-  pi: PPolyInfo;
-begin
-  if Length(paths) = 0 then Exit;
-  new(pi);
-  pi.paths := PathsD(paths);
-  pi.BrushClr := brushColor;
-  pi.PenClr   := penColor;
-  pi.PenWidth := penWidth;
-  pi.ShowCoords := showCoords;
-  pi.IsOpen := isOpen;
-  fPolyInfos.Add(pi);
-end;
-
-procedure TSvgWriter.AddPaths(const paths: TPathsD; isOpen: Boolean;
-  brushColor, penColor: Cardinal;
-  penWidth: double; showCoords: Boolean = false);
-var
-  pi: PPolyInfo;
-begin
-  new(pi);
-  pi.paths := Copy(paths, 0, Length(paths));
-  pi.BrushClr := brushColor;
-  pi.PenClr   := penColor;
-  pi.PenWidth := penWidth;
-  pi.ShowCoords := showCoords;
-  pi.IsOpen := isOpen;
-  fPolyInfos.Add(pi);
-end;
-
-procedure TSvgWriter.AddDashedPath(const paths: TPathsD;
-  penColor: Cardinal; penWidth: double; const dashes: TArrayOfInteger);
-var
-  pi: PPolyInfo;
-begin
-  new(pi);
-  pi.paths := Copy(paths, 0, Length(paths));
-  pi.BrushClr := 0;
-  pi.PenClr   := penColor;
-  pi.PenWidth := penWidth;
-  pi.ShowCoords := false;
-  pi.IsOpen := true;
-  pi.dashes := Copy(dashes, 0, Length(dashes));
-  fPolyInfos.Add(pi);
-end;
-
-procedure TSvgWriter.AddArrow(const center: TPointD;
-  radius: double; angleRad: double; brushColor, penColor: Cardinal;
-  penWidth: double);
-var
-  pp: TPathsD;
-  s,c: double;
-begin
-  SetLength(pp, 1);
-  with center do
-    pp[0] := Ellipse(X - radius * 1.2, Y - radius * 0.9,
-      X + radius * 1.2, Y + radius * 0.9, 3);
-  if angleRad <> 0 then
-  begin
-    SinCos(angleRad, s,c);
-    RotatePath(pp[0], center, s, c);
-  end;
-  AddPaths(pp, false, brushColor, penColor, penWidth);
-end;
-
-procedure TSvgWriter.AddCircle(const center: TPointD;
-  radius: double; brushColor, penColor: Cardinal; penWidth: double);
-var
-  ci: PCircleInfo;
-begin
-  new(ci);
-  ci.center := center;
-  ci.radius := radius;
-  ci.BrushClr := brushColor;
-  ci.PenClr   := penColor;
-  ci.PenWidth := penWidth;
-  fCircleInfos.Add(ci);
-end;
-
-procedure TSvgWriter.AddText(text: string; x,y: integer;
-  fontSize: integer; fontClr: Cardinal; bold: Boolean);
-var
-  ti: PTextInfo;
-begin
-  new(ti);
-  ti.x := x;
-  ti.y := y;
-  ti.text := text;
-  ti.fontSize := fontSize;
-  ti.fontColor := fontClr;
-  ti.Bold := bold;
-  fTextInfos.Add(ti);
-end;
-
-function TSvgWriter.GetBounds: TRectD;
-var
-  i: integer;
-  bounds: TRectD;
-begin
-  Result := MaxRect;
-  for i := 0 to fPolyInfos.Count -1 do
-    with PPolyInfo(fPolyInfos[i])^ do
-    begin
-      bounds := SvgWriter.GetBounds(paths);
-      if (bounds.left < Result.Left) then Result.Left := bounds.Left;
-      if (bounds.right> Result.Right) then Result.Right := bounds.Right;
-      if (bounds.top < Result.Top) then Result.Top := bounds.Top;
-      if (bounds.bottom > Result.Bottom) then Result.Bottom := bounds.Bottom;
-    end;
-end;
-
-procedure TSvgWriter.ClearPaths;
-var
-  i: integer;
-begin
-  for i := 0 to fPolyInfos.Count -1 do
-    Dispose(PPolyInfo(fPolyInfos[i]));
-  fPolyInfos.Clear;
-
-  for i := 0 to fCircleInfos.Count -1 do
-    Dispose(PCircleInfo(fCircleInfos[i]));
-  fCircleInfos.Clear;
-end;
-
-procedure TSvgWriter.ClearText;
-var
-  i: integer;
-begin
-  for i := 0 to fTextInfos.Count -1 do
-    Dispose(PTextInfo(fTextInfos[i]));
-  fTextInfos.Clear;
-end;
-
-procedure TSvgWriter.ClearAll;
-begin
-  ClearText;
-  ClearPaths;
-end;
-
-function TSvgWriter.SaveToFile(const filename: string;
-  maxWidth: integer = 0; maxHeight: integer = 0; margin: integer = 20): Boolean;
-var
-  i,j,k: integer;
-  bounds: TRectD;
-  scale: double;
-  offsetX, offsetY: integer;
-  s, sInline, dashStr: string;
-  sl: TStringList;
-  formatSettings: TFormatSettings;
-const
-  fillRuleStr: array[boolean] of string = ('evenodd', 'nonzero');
-  boldFont: array[boolean] of string = ('normal', '700');
-
-  procedure Add(const s: string);
-  begin
-    sl.Add( sInline + s);
-    sInline := '';
-  end;
-
-  procedure AddInline(const s: string);
-  begin
-    sInline := sInline + s;
-  end;
-
-begin
-  formatSettings := TFormatSettings.Create;
-  formatSettings.DecimalSeparator := '.';
-
-  Result := false;
-  if (margin < 20) then margin := 20;
-  bounds := GetBounds;
-  if (bounds.Right <= bounds.left) or
-    (bounds.Bottom <= bounds.Top) then Exit;
-
-  scale := 1.0;
-  if (maxWidth > 0) and (maxHeight > 0) then
-    scale := 1.0 / Max((bounds.right - bounds.left) /
-      (maxWidth - margin * 2), (bounds.bottom - bounds.top) /
-      (maxHeight - margin * 2));
-
-  offsetX := margin - Round(bounds.left * scale);
-  offsetY := margin - Round(bounds.top * scale);
-
-  sl := TStringList.Create;
-  try
-    if (maxWidth <= 0) or (maxHeight <= 0) then
-       Add(Format(svg_header,
-       [Round(bounds.right - bounds.left) + margin * 2,
-        Round(bounds.bottom - bounds.top) + margin * 2], formatSettings))
-    else
-      Add(Format(svg_header, [maxWidth, maxHeight], formatSettings));
-
-    for i := 0 to fPolyInfos.Count -1 do
-      with PPolyInfo(fPolyInfos[i])^ do
-      begin
-        AddInline('  <path d="');
-        for j := 0 to High(paths) do
-        begin
-          if Length(paths[j]) < 2 then Continue;
-          if not IsOpen and (Length(paths[j]) < 3) then Continue;
-          AddInline(Format('M %1.2f %1.2f L ',
-            [paths[j][0].x * scale + offsetX,
-            paths[j][0].y * scale + offsetY], formatSettings));
-          for k := 1 to High(paths[j]) do
-            AddInline(Format('%1.2f %1.2f ',
-              [paths[j][k].x * scale + offsetX,
-              paths[j][k].y * scale + offsetY], formatSettings));
-          if not IsOpen then AddInline('Z');
-        end;
-
-        if not IsOpen then
-          Add(Format(svg_path_format,
-            [ColorToHtml(BrushClr), GetAlpha(BrushClr),
-            fillRuleStr[fFillRule = frNonZero],
-            ColorToHtml(PenClr), GetAlpha(PenClr), PenWidth], formatSettings))
-        else
-        begin
-          dashStr := '';;
-          if Length(dashes) > 0 then
-          begin
-            s := '';
-            for k := 0 to High(dashes) do
-              s := s + IntToStr(dashes[k]) + ' ';
-            dashStr := 'stroke-dasharray: ' + s + ';';
-          end;
-          Add(Format(svg_path_format2,
-              [ColorToHtml(PenClr), GetAlpha(PenClr),
-              PenWidth, dashStr], formatSettings));
-        end;
-
-        if (ShowCoords) then
-        begin
-          with fCoordStyle do
-            Add(Format('<g font-family="%s" font-size="%d" fill="%s">',
-              [FontName, FontSize, ColorToHtml(FontColor)], formatSettings));
-          for j := 0 to High(paths) do
-            for k := 0 to High(paths[j]) do
-              with paths[j][k] do
-                Add(Format('  <text x="%1.2f" y="%1.2f">%1.0f,%1.0f</text>',
-                  [x * scale + offsetX, y * scale + offsetY, x, y],
-                  formatSettings));
-          Add('</g>'#10);
-        end;
-      end;
-
-      for i := 0 to fCircleInfos.Count -1 do
-        with PCircleInfo(fCircleInfos[i])^ do
-        begin
-          Add(Format('  <circle cx="%1.2f" cy="%1.2f" r="%1.2f" '+
-            'stroke="%s" stroke-width="%1.2f" fill="%s" />',
-            [center.X * scale + offsetX, center.Y * scale + offsetY,
-            radius, ColorToHtml(PenClr),
-            PenWidth, ColorToHtml(BrushClr)], formatSettings));
-        end;
-
-    for i := 0 to fTextInfos.Count -1 do
-      with PTextInfo(fTextInfos[i])^ do
-      begin
-            Add(Format(
-            '  <g font-family="Verdana" font-style="normal" ' +
-            'font-weight="%s" font-size="%d" fill="%s">' +
-            '<text x="%1.2f" y="%1.2f">%s</text></g>',
-            [boldFont[Bold], Round(fontSize * scale),
-            ColorToHtml(fontColor),
-            (x) * scale + offsetX,
-            (y) * scale + offsetY, text], formatSettings));
-      end;
-    Add('</svg>'#10);
-    sl.SaveToFile(filename);
-    Result := true;
-  finally
-    sl.free;
-  end;
-end;
-
-procedure AddSubject(svg: TSvgWriter; const paths: TPaths64);
-begin
-  svg.AddPaths(paths, false, $200099FF, $800066FF, 1.0);
-end;
-
-procedure AddOpenSubject(svg: TSvgWriter; const paths: TPaths64);
-begin
-  svg.AddPaths(paths, true, $0, $800066FF, 1.8);
-end;
-
-procedure AddClip(svg: TSvgWriter; const paths: TPaths64);
-begin
-  svg.AddPaths(paths, false, $10FF9900, $80FF6600, 1.0);
-end;
-
-procedure AddSolution(svg: TSvgWriter; const paths: TPaths64);
-begin
-  svg.AddPaths(paths, false, $8066FF66, $FF006600, 1.5);
-end;
-
-procedure AddOpenSolution(svg: TSvgWriter; const paths: TPaths64);
-begin
-  svg.AddPaths(paths, true, $0, $FF006600, 2.2);
-end;
-
-
-procedure AddSubject(svg: TSvgWriter; const paths: TPathsD); overload;
-begin
-  svg.AddPaths(paths, false, $200099FF, $800066FF, 1.0);
-end;
-
-procedure AddOpenSubject(svg: TSvgWriter; const paths: TPathsD); overload;
-begin
-  svg.AddPaths(paths, true, $0, $800066FF, 1.8);
-end;
-
-procedure AddClip(svg: TSvgWriter; const paths: TPathsD); overload;
-begin
-  svg.AddPaths(paths, false, $10FF9900, $80FF6600, 1.0);
-end;
-
-procedure AddSolution(svg: TSvgWriter; const paths: TPathsD); overload;
-begin
-  svg.AddPaths(paths, false, $8066FF66, $FF006600, 1.5);
-end;
-
-procedure AddOpenSolution(svg: TSvgWriter; const paths: TPathsD); overload;
-begin
-  svg.AddPaths(paths, true, $0, $FF006600, 2.2);
-end;
-
-procedure SaveSvg(svg: TSvgWriter; const filename: string;
-  width: integer = 0; height: integer = 0; margin: integer = 0);
-begin
-  svg.SaveToFile(filename, width, height, margin);
-end;
-
-end.
-

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 354 - 540
polygon.mod/clipper2/DLL/Delphi_TestApp/Test_DLL.dpr


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác