瀏覽代碼

Moved from brl.mod.

Brucey 2 年之前
父節點
當前提交
b374079543
共有 100 個文件被更改,包括 32877 次插入0 次删除
  1. 6 0
      .gitignore
  2. 795 0
      matrix.mod/doc/intro.bbdoc
  3. 2964 0
      matrix.mod/matrix.bmx
  4. 1 0
      polygon.mod/clipper2/CPP/.gitignore
  5. 203 0
      polygon.mod/clipper2/CPP/CMakeLists.txt
  6. 12 0
      polygon.mod/clipper2/CPP/Clipper2.pc.cmakein
  7. 701 0
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.core.h
  8. 588 0
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.engine.h
  9. 830 0
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.export.h
  10. 761 0
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.h
  11. 118 0
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h
  12. 107 0
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.offset.h
  13. 50 0
      polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h
  14. 3500 0
      polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.engine.cpp
  15. 485 0
      polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.offset.cpp
  16. 513 0
      polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.rectclip.cpp
  17. 210 0
      polygon.mod/clipper2/CPP/Examples/ConsoleDemo1/ConsoleDemo1.cpp
  18. 92 0
      polygon.mod/clipper2/CPP/Examples/ConsoleDemo2/ConsoleDemo2.cpp
  19. 112 0
      polygon.mod/clipper2/CPP/Examples/InflateDemo/InflateDemo1.cpp
  20. 12 0
      polygon.mod/clipper2/CPP/Examples/InflateDemo/rabbit.svg
  21. 241 0
      polygon.mod/clipper2/CPP/Examples/RectClipDemo/RectClipDemo1.cpp
  22. 114 0
      polygon.mod/clipper2/CPP/Examples/UsingZ/UsingZ1.cpp
  23. 12 0
      polygon.mod/clipper2/CPP/GoogleTest in Visual Studio.txt
  24. 65 0
      polygon.mod/clipper2/CPP/Tests/TestLines.cpp
  25. 20 0
      polygon.mod/clipper2/CPP/Tests/TestOffsetOrientation.cpp
  26. 20 0
      polygon.mod/clipper2/CPP/Tests/TestOrientation.cpp
  27. 105 0
      polygon.mod/clipper2/CPP/Tests/TestPolygons.cpp
  28. 31 0
      polygon.mod/clipper2/CPP/Tests/TestPolytreeHoles1.cpp
  29. 132 0
      polygon.mod/clipper2/CPP/Tests/TestPolytreeHoles2.cpp
  30. 30 0
      polygon.mod/clipper2/CPP/Tests/TestPolytreeIntersection.cpp
  31. 32 0
      polygon.mod/clipper2/CPP/Tests/TestPolytreeUnion.cpp
  32. 158 0
      polygon.mod/clipper2/CPP/Tests/TestRandomPaths.cpp
  33. 63 0
      polygon.mod/clipper2/CPP/Tests/TestRectClip.cpp
  34. 35 0
      polygon.mod/clipper2/CPP/Tests/TestTrimCollinear.cpp
  35. 120 0
      polygon.mod/clipper2/CPP/Utils/ClipFileLoad.cpp
  36. 34 0
      polygon.mod/clipper2/CPP/Utils/ClipFileLoad.h
  37. 324 0
      polygon.mod/clipper2/CPP/Utils/ClipFileSave.cpp
  38. 14 0
      polygon.mod/clipper2/CPP/Utils/ClipFileSave.h
  39. 105 0
      polygon.mod/clipper2/CPP/Utils/Timer.h
  40. 424 0
      polygon.mod/clipper2/CPP/Utils/clipper.svg.cpp
  41. 122 0
      polygon.mod/clipper2/CPP/Utils/clipper.svg.h
  42. 137 0
      polygon.mod/clipper2/CPP/Utils/clipper.svg.utils.h
  43. 120 0
      polygon.mod/clipper2/CSharp/.editorconfig
  44. 260 0
      polygon.mod/clipper2/CSharp/.gitignore
  45. 100 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs
  46. 22 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Clipper2Lib.Benchmark.csproj
  47. 12 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Program.cs
  48. 二進制
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/Clipper2.ico
  49. 41 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/ConsoleDemo.csproj
  50. 48 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/ConsoleDemo.sln
  51. 93 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs
  52. 二進制
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/clip.bin
  53. 二進制
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/subj.bin
  54. 二進制
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/Clipper2.ico
  55. 41 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/InflateDemo.csproj
  56. 42 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/InflateDemo.sln
  57. 106 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs
  58. 二進制
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/rabbit.bin
  59. 二進制
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/Clipper2.ico
  60. 111 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/Main.cs
  61. 49 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/RectClipDemo.csproj
  62. 42 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/RectClipDemo.sln
  63. 47 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestLines.cs
  64. 77 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolygons.cs
  65. 136 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolytree.cs
  66. 31 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests1.csproj
  67. 41 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests1.sln
  68. 158 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests2/Tests/TestZCallback1.cs
  69. 30 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests2/TestsZ.csproj
  70. 31 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests2/TestsZ.sln
  71. 60 0
      polygon.mod/clipper2/CSharp/Clipper2Lib.sln
  72. 735 0
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Core.cs
  73. 3999 0
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Engine.cs
  74. 91 0
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Minkowski.cs
  75. 541 0
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Offset.cs
  76. 564 0
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.RectClip.cs
  77. 973 0
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.cs
  78. 33 0
      polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper2Lib.csproj
  79. 21 0
      polygon.mod/clipper2/CSharp/USINGZ/Clipper2LibZ.csproj
  80. 25 0
      polygon.mod/clipper2/CSharp/USINGZ/Clipper2LibZ.sln
  81. 275 0
      polygon.mod/clipper2/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs
  82. 12 0
      polygon.mod/clipper2/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj
  83. 79 0
      polygon.mod/clipper2/CSharp/Utils/SVG/Clipper.SVG.Utils.cs
  84. 273 0
      polygon.mod/clipper2/CSharp/Utils/SVG/Clipper.SVG.cs
  85. 11 0
      polygon.mod/clipper2/CSharp/Utils/SVG/Clipper2.SVG.csproj
  86. 31 0
      polygon.mod/clipper2/DLL/CPP_DLL/Clipper2_DLL.sln
  87. 185 0
      polygon.mod/clipper2/DLL/CPP_DLL/Clipper2_Export.vcxproj
  88. 21 0
      polygon.mod/clipper2/DLL/CPP_DLL/dllmain.cpp
  89. 5 0
      polygon.mod/clipper2/DLL/CPP_DLL/framework.h
  90. 5 0
      polygon.mod/clipper2/DLL/CPP_DLL/pch.cpp
  91. 13 0
      polygon.mod/clipper2/DLL/CPP_DLL/pch.h
  92. 663 0
      polygon.mod/clipper2/DLL/Delphi_TestApp/SvgWriter.pas
  93. 1003 0
      polygon.mod/clipper2/DLL/Delphi_TestApp/Test_DLL.dpr
  94. 134 0
      polygon.mod/clipper2/DLL/Delphi_TestApp/Test_DLL.dproj
  95. 112 0
      polygon.mod/clipper2/DLL/Delphi_TestApp/Timer.pas
  96. 78 0
      polygon.mod/clipper2/Delphi/BenchMark/DelphiBenchmark.dpr
  97. 141 0
      polygon.mod/clipper2/Delphi/BenchMark/DelphiBenchmark.dproj
  98. 2074 0
      polygon.mod/clipper2/Delphi/Clipper2Lib/Clipper.Core.pas
  99. 4546 0
      polygon.mod/clipper2/Delphi/Clipper2Lib/Clipper.Engine.pas
  100. 138 0
      polygon.mod/clipper2/Delphi/Clipper2Lib/Clipper.Minkowski.pas

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+
+.bmx
+*.a
+*.i
+*.i2
+.DS_Store

+ 795 - 0
matrix.mod/doc/intro.bbdoc

@@ -0,0 +1,795 @@
+Matrices are fundamental mathematical constructs used in various fields, such as computer graphics,
+physics simulations, and linear algebra. They are especially useful for representing linear transformations,
+including rotations, translations, and scaling operations. In the context of graphics and 3D geometry,
+matrices are commonly used to manipulate vertices and objects in a scene, transforming their coordinates
+and orientations.
+
+A matrix is a rectangular array of numbers, organized into rows and columns. In BlitzMax,
+`math.matrix` provides several structs to work with matrices of different
+sizes and types, such as #SMat2D, #SMat3D, #SMat4D, #SMat2F, #SMat3F, #SMat4F, #SMat2I, #SMat3I, and #SMat4I.
+The name of each struct indicates its size and the primitive type it uses: #Double, #Float, or #Int.
+In our examples, we'll focus primarily on the #Double structs, but the same concepts apply to the other
+structs as well.
+
+The following is a brief introduction to the three main types of matrices used in graphics and 3D geometry, which
+BlitzMax provides structs for, along with a high-level overview of their applications:
+
+## Types of Matrices
+
+### 2x2 Matrices
+A 2x2 matrix, represented by the #SMat2D, #SMat2F and #SMat2I structs, is primarily used for 2D linear transformations,
+such as rotation and scaling. These matrices can be applied to 2D vectors, transforming their
+coordinates accordingly. For example, a 2x2 rotation matrix can be used to rotate a 2D vector
+around the origin by a specified angle.
+
+### 3x3 Matrices
+3x3 matrices, represented by the #SMat3D, #SMat3F and #SMat3I structs, are commonly used for 3D linear transformations that do
+not involve translation, such as rotation and scaling. These matrices can be applied to 3D vectors,
+transforming their coordinates without changing their position in the scene. For example, a 3x3 rotation
+matrix can be used to rotate a 3D vector around a specific axis by a given angle.
+
+The following example demonstrates the use of 3x3 matrices, by creating a simple solar system. The sun
+is represented by a yellow circle, and the planets are represented by blue circles. The planets are
+rotating around the sun, and their positions are calculated using 3x3 matrices.
+
+```blitzmax
+SuperStrict
+
+Framework SDL.SDLRenderMax2D
+Import Math.Matrix
+
+Graphics 800, 600, 0
+
+Local sunX:Float = GraphicsWidth() / 2
+Local sunY:Float = GraphicsHeight() / 2
+Local sunRadius:Float = 50
+
+Local planets:TPlanet[] = [New TPlanet(150, 20, 0.7), New TPlanet(200, 15, 0.5), New TPlanet(250, 10, 0.3)]
+
+While Not KeyHit(KEY_ESCAPE)
+    Cls
+
+    ' Draw the sun
+    SetColor(255, 255, 0)
+    DrawOval(sunX - sunRadius, sunY - sunRadius, sunRadius * 2, sunRadius * 2)
+
+    ' Draw the planets
+    For Local planet:TPlanet = EachIn planets
+        planet.Draw(sunX, sunY)
+    Next
+
+    Flip
+Wend
+
+Type TPlanet
+    Field distance:Float
+    Field radius:Float
+    Field angle:Float
+    Field speed:Float
+
+    Method New(distance:Float, radius:Float, speed:Float)
+        Self.distance = distance
+        Self.radius = radius
+        Self.speed = speed
+    End Method
+
+	Method Draw(centerX:Float, centerY:Float)
+		' Calculate the planet's position
+		Local planetMatrix:SMat3D = SMat3D.Identity()
+		planetMatrix = planetMatrix.Rotate(angle)
+		planetMatrix = planetMatrix.Translate(New SVec3D(distance, 0, 0))
+		Local planetPos:SVec3D = planetMatrix.Apply(New SVec3D(0, 0, 1))
+	
+		' Draw the planet
+		SetColor(0, 0, 255)
+		DrawOval(Float(centerX + planetPos.x - radius), Float(centerY + planetPos.y - radius), radius * 2, radius * 2)
+	
+		' Update the angle for the next frame
+		angle :+ speed
+	End Method
+End Type
+```
+
+### 4x4 Matrices
+4x4 matrices, represented by the #SMat4D, #SMat4F and #SMat4I structs, are the most versatile and widely used matrices in 3D
+graphics. They can represent any combination of linear transformations, such as rotation, scaling,
+and translation. A 4x4 matrix can be applied to a 3D vector, transforming its position, orientation,
+and scale in the scene. For example, a 4x4 transformation matrix can be used to move an object in 3D space,
+rotate it around a specific axis, and scale it uniformly or non-uniformly.
+
+Creating a 3D-like effect in 2D can be achieved using simple orthogonal projection. In this example,
+we will create a rotating cube using a 4x4 transformation matrix to apply rotation, scaling, and translation. 
+
+```blitzmax
+SuperStrict
+
+Framework SDL.SDLRenderMax2D
+Import Math.Matrix
+
+Graphics 800, 600, 0
+
+Local cube:TCube = New TCube
+Local angle:Float = 0
+
+Local centerX:Int = GraphicsWidth() / 2
+Local centerY:Int = GraphicsHeight() / 2
+
+While Not KeyHit(KEY_ESCAPE)
+    Cls
+
+    ' Create the transformation matrix..
+    ' Start with an identity matrix as the initial transformation.
+    Local matrix:SMat4D = SMat4D.Identity()
+    ' Move the object away from the camera along the Z-axis by a distance of 6 units.
+    ' This translation ensures that the object is visible and centered in the view.
+    matrix = matrix.Translate(New SVec3D(0, 0, -6))
+    ' Rotate the object around the Z-axis (blue axis) by an angle scaled by 1.2.
+    ' This rotation is applied first to achieve a "rolling" effect.
+    matrix = matrix.Rotate(New SVec3D(0, 0, 1), angle * 1.2)
+    ' Rotate the object around the Y-axis (green axis) by an angle scaled by 0.7.
+    ' This rotation is applied after the first rotation, creating a "pitching" effect.
+    matrix = matrix.Rotate(New SVec3D(0, 1, 0), angle * 0.7)
+    ' Finally, rotate the object around the X-axis (red axis) by the base angle.
+    ' This rotation is applied last, creating a "yawing" effect.
+    matrix = matrix.Rotate(New SVec3D(1, 0, 0), angle)
+
+    ' Draw cube
+    cube.Draw(matrix, 100, centerX, centerY)
+
+    Flip
+    angle :+ 0.5
+Wend
+
+Type TCube
+    Field vertices:Float[24] ' 8 vertices, each with 3 coordinates
+    Field edges:Int[24] ' 12 edges, each with 2 vertex indices
+
+    Method New()
+        vertices = [ -1:Float, -1:Float, -1:Float, 1:Float, -1:Float, -1:Float, 1:Float, 1:Float, -1:Float, -1:Float, ..
+					1:Float, -1:Float, -1:Float, -1:Float, 1:Float, 1:Float, -1:Float, 1:Float, 1:Float, 1:Float, ..
+					1:Float, -1:Float, 1:Float, 1:Float ]
+        edges = [ 0, 1, 1, 2, 2, 3, 3, 0, ..
+                  4, 5, 5, 6, 6, 7, 7, 4, ..
+                  0, 4, 1, 5, 2, 6, 3, 7 ]
+    End Method
+
+    Method Draw(matrix:SMat4D, scale:Float = 1, centerX:Int, centerY:Int)
+        Local transformedVertices:Float[24]
+
+		' Apply transformation and orthographic projection
+		For Local i:Int = 0 Until 24 Step 3
+			Local vertex:SVec3D = New SVec3D(vertices[i], vertices[i + 1], vertices[i + 2])
+			vertex = matrix.Apply(vertex)
+			transformedVertices[i] = centerX + (scale * vertex.x)
+			transformedVertices[i + 1] = centerY - (scale * vertex.y)
+		Next
+
+		' Draw edges
+		For Local i:Int = 0 Until 24 Step 2
+			DrawLine(transformedVertices[edges[i] * 3], transformedVertices[edges[i] * 3 + 1],
+					transformedVertices[edges[i + 1] * 3], transformedVertices[edges[i + 1] * 3 + 1])
+		Next
+
+    End Method
+End Type
+```
+
+## Column-Major vs. Row-Major Matrices
+
+In `math.matrix`, we use column-major ordering for matrices. Column-major ordering is a convention
+in which elements are stored column by column in memory. It is the opposite of row-major ordering, where
+elements are stored row by row. This distinction is important when performing operations like matrix
+multiplication or when applying transformations to vectors or other matrices.
+
+Column-major ordering is commonly used in graphics programming and some mathematical libraries. In
+this convention, when applying transformations, the order in which they are applied is reversed
+compared to row-major ordering. This means that when you want to apply multiple transformations,
+you should apply them in the reverse order that you would with row-major ordering.
+
+For example, in column-major ordering, to rotate an object around the X, Y, and Z axes and then
+translate it, you would first apply the translation, then the rotation around the Z axis, followed
+by the rotation around the Y axis, and finally the rotation around the X axis.
+
+## Determinant, Inverse, and Transpose in Matrices
+
+### Determinant
+
+The determinant is a scalar value associated with square matrices that carries essential
+information about the matrix's properties. It has several important geometric interpretations
+and applications in linear algebra.
+
+In the context of 2D transformations, the determinant represents the area scaling factor when a
+matrix is applied to a vector. If the determinant is positive, the transformation preserves the
+orientation; if it's negative, the orientation is reversed. A determinant equal to zero indicates
+that the transformation collapses the vector onto a lower-dimensional subspace (e.g., a line or a point),
+making the matrix singular and non-invertible.
+
+Mathematically, the determinant of a 2x2 matrix is calculated as follows:
+
+```
+|A| = |a  b|
+      |c  d|
+
+|A| = ad - bc
+```
+
+Here's an example illustrating how to calculate the determinant of a 2x2 matrix using the #SMat2D struct:
+
+```blitzmax
+SuperStrict
+
+Framework BRL.StandardIO
+Import Math.Matrix
+
+' Define matrix elements
+Local a:Double = 3.0
+Local b:Double = 2.0
+Local c:Double = 5.0
+Local d:Double = 4.0
+
+' Create a 2x2 matrix
+Local mat:SMat2D = New SMat2D(a, b, c, d)
+
+' Calculate the determinant
+Local det:Double = mat.Determinant()
+
+' Print the determinant
+Print "Determinant: " + det
+```
+When you run this example, it will create a 2x2 matrix with the specified elements,
+calculate the determinant using the `Determinant()` method provided by #SMat2D, and print the resulting
+determinant value. In this case, the output will be:
+```
+Determinant: -2.0
+```
+
+For a 3x3 matrix, the determinant can be calculated using the following formula:
+```
+|A| = |a  b  c|
+      |d  e  f|
+      |g  h  i|
+
+|A| = a(ei - fh) - b(di - fg) + c(dh - eg)
+```
+
+Here's an example illustrating how to calculate the determinant of a 3x3 matrix using the #SMat3D struct:
+```blitzmax
+SuperStrict
+
+Framework BRL.StandardIO
+Import Math.Matrix
+
+' Define matrix elements
+Local a:Double = 1.0
+Local b:Double = 2.0
+Local c:Double = 3.0
+Local d:Double = 4.0
+Local e:Double = 5.0
+Local f:Double = 6.0
+Local g:Double = 7.0
+Local h:Double = 8.0
+Local i:Double = 9.0
+
+' Create a 3x3 matrix
+Local mat:SMat3D = New SMat3D(a, b, c, d, e, f, g, h, i)
+
+' Calculate the determinant
+Local det:Double = mat.Determinant()
+
+' Print the determinant
+Print "Determinant: " + det
+```
+
+When you run this example, it will create a 3x3 matrix with the specified elements,
+calculate the determinant using the `Determinant()` method provided by #SMat3D, and print the
+resulting determinant value. In this case, the output will be:
+```
+Determinant: 0.0
+```
+
+### Inverse
+
+The inverse of a matrix is another matrix that, when multiplied with the original matrix,
+results in the identity matrix. In other words, for a matrix A, its inverse A⁻¹ exists if
+and only if A * A⁻¹ = A⁻¹ * A = I, where I is the identity matrix. Not all matrices have
+inverses; only square matrices (i.e., matrices with the same number of rows and columns) may
+have inverses, and even then, not all square matrices are invertible.
+
+For a 2x2 matrix:
+```
+| a  b |
+| c  d |
+```
+The inverse can be calculated using the following formula:
+```
+1 / (ad - bc) * |  d  -b |
+                | -c   a |
+```
+
+The term (ad - bc) is the determinant of the matrix. If the determinant is zero, the matrix
+is singular and does not have an inverse.
+
+For a 3x3 matrix, the process is more complicated and involves finding the matrix of minors,
+the matrix of cofactors, and finally, the adjugate matrix. The inverse can then be computed
+by dividing the adjugate matrix by the determinant of the original matrix.
+
+Here is an example for finding the inverse of a 2x2 matrix using the #SMat2D struct:
+```blitzmax
+SuperStrict
+
+Framework brl.standardio
+Import Math.Matrix
+
+' Create a 2x2 matrix
+Local a:Double = 3
+Local b:Double = 2
+Local c:Double = 5
+Local d:Double = 3
+
+Local mat:SMat2D = New SMat2D(a, b, c, d)
+
+' Calculate the inverse
+Local matInverse:SMat2D = mat.Invert()
+
+' Print the original and inverse matrices
+Print "Original matrix:~n" + mat.ToString()
+Print "Inverse matrix:~n" + matInverse.ToString()
+```
+When you run this example, it will output the original 2x2 matrix and its inverse:
+```
+Original matrix:
+3.000 2.000
+5.000 3.000
+Inverse matrix:
+-1.000 0.667
+ 1.667 -1.000
+```
+
+
+And here is an example for finding the inverse of a 3x3 matrix using the #SMat3D struct:
+```blitzmax
+SuperStrict
+
+Framework brl.standardio
+Import Math.Matrix
+
+' Create a 3x3 matrix
+Local a:Double = 1
+Local b:Double = 2
+Local c:Double = 3
+Local d:Double = 0
+Local e:Double = 1
+Local f:Double = 4
+Local g:Double = 5
+Local h:Double = 6
+Local i:Double = 0
+
+Local mat:SMat3D = New SMat3D(a, b, c, d, e, f, g, h, i)
+
+' Calculate the inverse
+Local matInverse:SMat3D = mat.Invert()
+
+' Print the original and inverse matrices
+Print "Original matrix: " + mat.ToString()
+Print "Inverse matrix: " + matInverse.ToString()
+```
+When you run this example, it will output the original 3x3 matrix and its inverse:
+```
+Original matrix:
+ 1.000 2.000 3.000
+ 0.000 1.000 4.000
+ 5.000 6.000 0.000
+Inverse matrix:
+ -24.000 18.000 5.000
+ 20.000 -15.000 -4.000
+ -5.000 4.000 1.000
+```
+
+### Transpose
+
+The transpose of a matrix is a new matrix obtained by interchanging its rows and columns.
+In other words, the transpose of a matrix A is a new matrix Aᵀ, where the element Aᵀ[i, j] = A[j, i].
+The transpose operation has several important properties and applications in linear algebra, including
+simplifying matrix equations and working with symmetric matrices.
+
+For a 2x2 matrix A:
+```
+A = |a  b|       Aᵀ = |a  c|
+    |c  d|            |b  d|
+```
+
+For a 3x3 matrix A:
+```
+A = |a  b  c|       Aᵀ = |a  d  g|
+    |d  e  f|            |b  e  h|
+    |g  h  i|            |c  f  i|
+```
+
+Here's an example illustrating how to calculate the transpose of a 2x2 matrix using the #SMat2D struct.
+```blitzmax
+SuperStrict
+
+Framework brl.standardIO
+Import Math.Matrix
+
+' Define matrix elements
+Local a:Double = 1.0
+Local b:Double = 2.0
+Local c:Double = 3.0
+Local d:Double = 4.0
+
+' Create a 2x2 matrix
+Local mat:SMat2D = New SMat2D(a, b, c, d)
+
+' Calculate the transpose
+Local matTranspose:SMat2D = mat.Transpose()
+
+' Print the original and transposed matrices
+Print "Original matrix:~n" + mat.ToString()
+Print "Transposed matrix:~n" + matTranspose.ToString()
+```
+When you run this example, it will create a 2x2 matrix with the specified elements, calculate
+the transpose using the Transpose() method provided by the #SMat2D struct, and print the original
+and transposed matrices. In this case, the output will be:
+```
+Original matrix:
+1.0,  2.0
+3.0,  4.0
+Transposed matrix:
+1.0  3.0
+2.0  4.0
+```
+
+And an example for calculating the transpose of a 3x3 matrix using the #SMat3D struct.
+```blitzmax
+SuperStrict
+
+Framework brl.standardIO
+Import Math.Matrix
+
+' Define matrix elements
+Local a:Double = 1.0
+Local b:Double = 2.0
+Local c:Double = 3.0
+Local d:Double = 4.0
+Local e:Double = 5.0
+Local f:Double = 6.0
+Local g:Double = 7.0
+Local h:Double = 8.0
+Local i:Double = 9.0
+
+' Create a 3x3 matrix
+Local mat:SMat3D = New SMat3D(a, b, c, d, e, f, g, h, i)
+
+' Calculate the transpose
+Local matTranspose:SMat3D = mat.Transpose()
+
+' Print the original and transposed matrices
+Print "Original matrix: " + mat.ToString()
+Print "Transposed matrix: " + matTranspose.ToString()
+```
+
+When you run this example, it will create a 3x3 matrix with the specified elements,
+calculate the transpose using the Transpose() method provided by the #SMat3D struct, and print the
+original and transposed matrices. In this case, the output will be:
+```
+Original matrix:
+1.0  2.0  3.0
+4.0  5.0  6.0
+7.0  8.0  9.0
+Transposed matrix:
+1.0  4.0  7.0
+2.0  5.0  8.0
+3.0  6.0  9.0
+```
+
+
+Understanding the transpose concept and its properties is important when working with matrices
+in your BlitzMax projects. The transpose operation can help simplify matrix equations and work with
+symmetric matrices, among other applications. By transposing a matrix, you can manipulate its elements
+and apply various transformations as required in your project.
+
+## Scaling, rotation, and shearing matrices
+
+Scaling, rotation, and shearing matrices are fundamental linear transformations used in various fields, such as
+computer graphics, physics simulations, and geometric modeling. These transformations can be applied to vectors or points
+to change their position, orientation, or shape. 
+
+### Scaling matrices
+
+Scaling is the process of resizing an object uniformly or non-uniformly along its axes. Scaling matrices are diagonal matrices
+that have scaling factors along the diagonal elements, and zeros elsewhere.
+
+For 2D scaling:
+
+```
+| sx  0  |
+| 0   sy |
+```
+
+For 3D scaling:
+```
+| sx  0   0  |
+| 0   sy  0  |
+| 0   0   sz |
+```
+
+Here, sx, sy, and sz are the scaling factors along the x, y, and z axes, respectively. To apply the scaling transformation, you multiply
+the scaling matrix by the vector or point you want to scale.
+
+### Rotation matrices
+
+Rotation is the process of rotating an object around a point or an axis. Rotation matrices are used to represent the rotation
+transformations in both 2D and 3D spaces.
+
+For 2D rotation around the origin by angle θ:
+
+```
+| cos(θ)  -sin(θ) |
+| sin(θ)   cos(θ) |
+```
+
+For 3D rotation around an arbitrary axis (x, y, z) by angle θ, the rotation matrix is more complex. One common method is using
+the axis-angle representation, which involves the Rodrigues' rotation formula. The formula for the 3D rotation matrix is as follows:
+
+```
+R = I + sin(θ) * K + (1 - cos(θ)) * K^2
+```
+
+Where `I` is the identity matrix, `θ` is the angle of rotation, and `K` is the skew-symmetric matrix representation of the axis vector.
+
+### Shearing matrices
+
+Shearing is the process of transforming an object by displacing its points along one axis based on the displacement of points along a
+parallel axis. Shearing matrices are used to represent these transformations.
+
+For 2D shearing:
+
+```
+| 1  shx |
+| shy  1 |
+```
+
+For 3D shearing along the x-axis:
+
+```
+| 1  shy  shz |
+| 0   1    0  |
+| 0   0    1  |
+```
+
+And similar matrices can be defined for shearing along the y-axis and z-axis. Here, `shx`, `shy`, and `shz` represent the shearing factors.
+
+When applying these transformations, it's important to consider the order of operations, as matrix multiplication is not commutative.
+Typically, scaling and shearing are applied first, followed by rotation, and finally translation. Also, remember that these matrices should
+be applied to homogeneous coordinates for translation to work correctly. In 3D graphics, this often involves using 4x4 matrices with an
+additional row and column for the homogeneous coordinate.
+
+## Matrix multiplication
+
+Matrix multiplication is an essential operation in linear algebra and has numerous applications in computer graphics, physics, and other fields.
+Multiplying two matrices involves specific rules, and it's important to understand the dimensions of the matrices and how to perform the
+multiplication element-wise.
+
+### Rules for Matrix Multiplication
+
+Given two matrices A and B, the product AB can only be computed if the number of columns in A is equal to the number of rows in B.
+If matrix A has dimensions m × n and matrix B has dimensions n × p, then the product AB will have dimensions m × p.
+
+`(AB)_ij = A_i1 * B_1j + A_i2 * B_2j + ... + A_in * B_nj`
+
+where (AB)_ij represents the element in the i-th row and j-th column of the resulting matrix AB.
+
+### Element-wise matrix multiplication
+
+To multiply two matrices element-wise, follow these steps:
+
+1. Verify that the dimensions of the matrices are compatible for multiplication. The number of columns in matrix A must be equal
+to the number of rows in matrix B.
+
+2. Create a new matrix C with dimensions m × p, where m is the number of rows in matrix A and p is the number of columns in matrix B.
+
+3. For each element (i, j) in the resulting matrix C, calculate the value as the sum of the products of the corresponding row elements in matrix A
+and the column elements in matrix B:
+`C_ij = A_i1 * B_1j + A_i2 * B_2j + ... + A_in * B_nj`
+Iterate through all elements in matrix C, computing their values using the above formula.
+
+4. The resulting matrix C represents the product of matrices A and B.
+
+It's important to note that matrix multiplication is not commutative, meaning that AB ≠ BA in general. However, it is
+associative (A(BC) = (AB)C) and distributive over addition (A(B+C) = AB + AC).
+
+The `CompMul()` method can be used to perform element-wise matrix multiplication.
+
+### Matrix multiplication using the dot product
+
+Matrix multiplication is a fundamental operation in linear algebra, and it is performed using the dot product.
+It is important to understand that matrix multiplication is not the same as element-wise multiplication. In matrix
+multiplication, the dot product of the rows of the first matrix and the columns of the second matrix is used to calculate the resulting matrix elements.
+
+Consider two matrices, A and B, where A has dimensions m x n (m rows and n columns), and B has dimensions p x q (p rows and q columns).
+For matrix multiplication to be possible, the number of columns in A (n) must be equal to the number of rows in B (p). The resulting
+matrix, C, will have dimensions m x q (m rows and q columns).
+
+Here is the formula for matrix multiplication using the dot product:
+
+C[i][j] = Σ (A[i][k] * B[k][j]) for k = 0 to n-1
+
+where i ranges from 0 to m-1 and j ranges from 0 to q-1.
+
+In other words, each element in the resulting matrix C is the dot product of the i-th row of matrix A and the j-th column of matrix B.
+
+Let's see an example with two 3x3 matrices:
+
+```
+A = | a b c |     B = | p q r |
+    | d e f |         | s t u |
+    | g h i |         | v w x |
+
+C = | (a*p + b*s + c*v) (a*q + b*t + c*w) (a*r + b*u + c*x) |
+    | (d*p + e*s + f*v) (d*q + e*t + f*w) (d*r + e*u + f*x) |
+    | (g*p + h*s + i*v) (g*q + h*t + i*w) (g*r + h*u + i*x) |
+```
+
+The `Operator *()` method can be used to perform matrix multiplication using the dot product.
+
+### Vector Multiplication
+
+Matrix-vector multiplication is a fundamental operation in linear algebra, computer graphics, and many other fields. This operation
+is used to apply transformations, such as scaling, rotation, and translation, to points and vectors in 2D and 3D space. In this section,
+we will discuss how to multiply a matrix by a vector and how this operation can be applied to 2D and 3D vectors in BlitzMax.
+
+#### 2D Vectors ( #SVec2D, #SVec2F, #SVec2I )
+
+To multiply a 2x2 matrix by a 2D vector, perform the following steps:
+
+1. Multiply each element of the first row of the matrix by the corresponding element of the vector.
+2. Sum the results from step 1 to obtain the first element of the resulting vector.
+3. Multiply each element of the second row of the matrix by the corresponding element of the vector.
+4. Sum the results from step 3 to obtain the second element of the resulting vector.
+
+Mathematically, this can be represented as:
+
+```
+| a c | | x |   | a * x + c * y |
+| b d | | y | = | b * x + d * y |
+```
+
+#### 3D Vectors ( #SVec3D, #SVec3F, #SVec3I )
+
+To multiply a 3x3 matrix by a 3D vector, follow these steps:
+
+1. Multiply each element of the first row of the matrix by the corresponding element of the vector.
+2. Sum the results from step 1 to obtain the first element of the resulting vector.
+3. Multiply each element of the second row of the matrix by the corresponding element of the vector.
+4. Sum the results from step 3 to obtain the second element of the resulting vector.
+5. Multiply each element of the third row of the matrix by the corresponding element of the vector.
+6. Sum the results from step 5 to obtain the third element of the resulting vector.
+
+Mathematically, this can be represented as:
+
+```
+| a d g | | x |   | a * x + d * y + g * z |
+| b e h | | y | = | b * x + e * y + h * z |
+| c f i | | z |   | c * x + f * y + i * z |
+```
+
+You can use the `Apply()` method to multiply a matrix by a vector.
+
+Here's an example:
+
+```blitzmax
+Local mat2D:SMat2D = SMat2D.Identity()
+Local vec2D:SVec2D = New SVec2D(2, 3)
+Local result2D:SVec2D = mat2D.Apply(vec2D)
+
+Local mat3D:SMat3D = SMat3D.Identity()
+Local vec3D:SVec3D = New SVec3D(2, 3, 4)
+Local result3D:SVec3D = mat3D.Apply(vec3D)
+```
+
+## The Identity Matrix
+
+The identity matrix is a special square matrix that has ones on the diagonal and zeros elsewhere. In mathematical notation,
+an identity matrix of size n x n is denoted as Iₙ. Here's an example of a 3x3 identity matrix:
+
+```
+I₃ = | 1 0 0 |
+     | 0 1 0 |
+     | 0 0 1 |
+```
+
+The identity matrix plays a crucial role in matrix algebra, as it serves as the "neutral element" for matrix multiplication. When you
+multiply any matrix by the identity matrix, the result is the original matrix unchanged. Mathematically, this property can be written as:
+
+A * Iₙ = A
+
+Iₙ * A = A
+
+where A is any n x n matrix.
+
+This property is analogous to the number 1 in scalar multiplication. Just as multiplying any number by 1 leaves the number unchanged
+(e.g., 7 * 1 = 7), multiplying any matrix by the identity matrix leaves the matrix unchanged.
+
+The identity matrix is also important for other matrix operations. For example, when calculating the inverse of a matrix, the goal is to
+find a matrix B such that A * B = Iₙ. When this condition is met, matrix B is the inverse of matrix A.
+
+The `Identity()` method allows you create an identity matrix.
+
+## Linear Transformations
+
+Matrices are often used to represent linear transformations in various fields, including computer graphics and linear algebra.
+A linear transformation is a function that maps vectors from one vector space to another while preserving the operations of vector
+addition and scalar multiplication. In this section, we will discuss some of the key properties of linear transformations and how
+matrices can be used to represent them.
+
+### Linearity
+
+A linear transformation, T, has the property of linearity, which means it satisfies the following conditions:
+
+1. T(u + v) = T(u) + T(v) for all vectors u and v.
+2. T(c * u) = c * T(u) for all vectors u and any scalar c.
+
+This property ensures that the transformation maintains the structure of the vector space, preserving
+parallelism and proportions between the vectors.
+
+### Invertibility
+
+A linear transformation is invertible if there exists another transformation, called its inverse, that can undo the effect of the original
+transformation. In terms of matrices, a matrix A is invertible if there exists a matrix B such that AB = BA = I, where I is the identity matrix.
+Invertible transformations are essential in many applications, as they allow us to reverse the effects of a transformation and recover the original data.
+
+### Effect on Geometry
+
+Linear transformations can have various effects on the geometry of shapes, including:
+
+1. Scaling: A transformation that changes the size of a shape, either uniformly or non-uniformly along different axes.
+2. Rotation: A transformation that rotates a shape around a specified point or axis.
+3. Shearing: A transformation that distorts a shape by shifting its points along one axis relative to the other axis.
+
+## Orthogonal and Orthonormal Matrices
+
+Orthogonal and orthonormal matrices are special types of square matrices that have unique properties, making them particularly useful in
+various applications such as computer graphics, physics, and linear algebra.
+
+### Orthogonal Matrices
+
+A matrix A is called an orthogonal matrix if its transpose, A^T, is also its inverse, i.e., A^T * A = A * A^T = I, where I is the identity
+matrix. In other words, the columns (and rows) of an orthogonal matrix are orthogonal to each other, meaning they form a 90-degree angle
+and their dot product is zero.
+
+The geometric interpretation of orthogonal matrices is that they represent transformations that preserve lengths and angles, such as
+rotations and reflections.
+
+### Orthonormal Matrices
+
+An orthonormal matrix is an orthogonal matrix where the columns (and rows) are not only orthogonal but also have unit length (i.e., they
+are normalized). Orthonormal matrices simplify calculations in various applications, as they do not change the length of the vectors they transform.
+
+### Applications
+
+Orthogonal and orthonormal matrices play a significant role in computer graphics, as they are used to represent transformations that maintain
+the geometry of 3D objects. For instance, they can be employed to create rotation matrices, which are essential for rotating objects in 3D space.
+
+Here's a BlitzMax example demonstrating the use of an orthonormal matrix to represent a 3D rotation:
+
+```blitzmax
+Local angleX:Float = 30 ' in degrees
+Local angleY:Float = 45 ' in degrees
+Local angleZ:Float = 60 ' in degrees
+
+' Create orthonormal rotation matrices for each axis
+Local rotationMatrixX:SMat3D = SMat3D.RotationX(angleX)
+Local rotationMatrixY:SMat3D = SMat3D.RotationY(angleY)
+Local rotationMatrixZ:SMat3D = SMat3D.RotationZ(angleZ)
+
+' Combine the rotations
+Local rotationMatrix:SMat3D = rotationMatrixX * rotationMatrixY * rotationMatrixZ
+
+' Apply the rotation to a 3D vector
+Local vec:SVec3D = New SVec3D(1, 2, 3)
+Local rotatedVec:SVec3D = rotationMatrix.Apply(vec)
+```
+
+When the rotation matrix is applied to a 3D vector, it will rotate the vector according to the specified angles.
+The order in which the rotations are combined (in this example, X, then Y, then Z) will determine the final rotation.

+ 2964 - 0
matrix.mod/matrix.bmx

@@ -0,0 +1,2964 @@
+' Copyright (c) 2019-2023 Bruce A Henderson
+'
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+'    1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgment in the product documentation would be
+'    appreciated but is not required.
+' 
+'    2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 
+'    3. This notice may not be removed or altered from any source
+'    distribution.
+' 
+SuperStrict
+
+Rem
+bbdoc: Math/Matrix
+End Rem
+Module Math.Matrix
+
+ModuleInfo "Version: 1.01"
+ModuleInfo "Author: Bruce A Henderson"
+ModuleInfo "License: zlib"
+ModuleInfo "Copyright: 2019-2023 Bruce A Henderson"
+
+ModuleInfo "History: 1.01"
+ModuleInfo "History: Moved to Math.Matrix"
+ModuleInfo "History: 1.00"
+ModuleInfo "History: Initial Release"
+
+Import BRL.Math
+Import Math.Vector
+Import BRL.StringBuilder
+
+Rem
+bbdoc: A 2x2 Matrix
+End Rem
+Struct SMat2D
+	Field ReadOnly a:Double
+	Field ReadOnly b:Double
+	Field ReadOnly c:Double
+	Field ReadOnly d:Double
+	
+	Rem
+	bbdoc: Creates a new #SMat2D from the supplied arguments.
+	End Rem
+	Method New(a:Double, b:Double, c:Double, d:Double)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+	End Method
+	
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2D(v:SVec2D)
+		Return New SVec2D(a * v.x + c * v.y, b * v.x + d * v.y)
+	End Method
+
+	Rem
+	bbdoc: Returns the identity matrix.
+	End Rem
+	Function Identity:SMat2D()
+		Return New SMat2D(1, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat2D(z:SMat2D)
+		Return New SMat2D(a + z.a, b + z.b, c + z.c, d + z.d)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat2D(z:SMat2D)
+		Return New SMat2D(a - z.a, b - z.b, c - z.c, d - z.d)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix.
+	End Rem
+	Method Operator*:SMat2D(z:SMat2D)
+		Return New SMat2D(a * z.a + c * z.b, b * z.a + d * z.b, a * z.c + c * z.d, b * z.c + d * z.d)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat2D()
+		Return New SMat2D(d, -b, -c, a)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, or element-wise matrix multiplication, return a new matrix.
+	End Rem
+	Method CompMul:SMat2D(z:SMat2D)
+		Return New SMat2D(a * z.a, b * z.b, c * z.c, d * z.d)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Double()
+		Return a * d - c * b
+	End Method
+	
+	Rem
+	bbdoc: Returns the inverse of the matrix.
+	End Rem
+	Method Invert:SMat2D()
+		Local det:Double = a * d - c * b
+		If det = 0 Then
+			Return New SMat2D(0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat2D(d * det, -b * det, -c * det, a * det)
+	End Method
+	
+	Rem
+	bbdoc: Rotates the matrix by @angle degrees, returning the rotated matrix.
+	End Rem
+	Method Rotate:SMat2D(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat2D(a * ca + c * sa, b * ca + d * sa, a * -sa + c * ca, b * -sa + d * ca)
+	End Method
+	
+	Rem
+	bbdoc: Creates a rotated matrix of @angle degrees.
+	End Rem
+	Function Rotation:SMat2D(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat2D(ca, sa, -sa, ca)
+	End Function
+	
+	Rem
+	bbdoc: Returns the scale of this matrix.
+	End Rem
+	Method Scale:SMat2D(s:SVec2D)
+		Return New SMat2D(a * s.x, b * s.x, c * s.y, d * s.y)
+	End Method
+	
+	Rem
+	bbdoc: Creates a scaled matrix of the scale @s.
+	End Rem
+	Function Scaling:SMat2D(s:SVec2D)
+		Return New SMat2D(s.x, 0, 0, s.y)
+	End Function
+	
+	Rem
+	bbdoc: Returns the transpose of this matrix.
+	End Rem
+	Method Transpose:SMat2D()
+		Return New SMat2D(a, c, b, d)
+	End Method
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(c).Append(",~n")
+		sb.Append(b).Append(", ").Append(d)
+		
+		Return sb.ToString()
+	End Method
+
+End Struct
+
+Rem
+bbdoc: A 3x3 matrix.
+End Rem
+Struct SMat3D
+	Field ReadOnly a:Double
+	Field ReadOnly b:Double
+	Field ReadOnly c:Double
+	Field ReadOnly d:Double
+	Field ReadOnly e:Double
+	Field ReadOnly f:Double
+	Field ReadOnly g:Double
+	Field ReadOnly h:Double
+	Field ReadOnly i:Double
+
+	Rem
+	bbdoc: Creates a new #SMat3D from the supplied arguments.
+	End Rem
+	Method New(a:Double, b:Double, c:Double, d:Double, e:Double, f:Double, g:Double, h:Double, i:Double)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+		Self.e = e
+		Self.f = f
+		Self.g = g
+		Self.h = h
+		Self.i = i
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2D(v:SVec2D)
+		Return New SVec2D(a * v.x + d * v.y + g, b * v.x + e * v.y + h)
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec3D(v:SVec3D)
+		Return New SVec3D(v.x * a + v.y * d + v.z * g, v.x * b + v.y * e + v.z * h, v.x * c + v.y * f + v.z * i)
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec4D(v:SVec4D)
+		Return New SVec4D(v.x * a + v.y * d + v.z * g, v.x * b + v.y * e + v.z * h, v.x * c + v.y * f + v.z * i, 0)
+	End Method
+
+	Rem
+	bbdoc: Return the 3x3 identity matrix.
+	End Rem
+	Function Identity:SMat3D()
+		Return New SMat3D(1, 0, 0, 0, 1, 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat3D(z:SMat3D Var)
+		Return New SMat3D(a + z.a, b + z.b, c + z.c, d + z.d, e + z.e, f + z.f, g + z.g, h + z.h, i + z.i)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat3D(z:SMat3D Var)
+		Return New SMat3D(a - z.a, b - z.b, c - z.c, d - z.d, e - z.e, f - z.f, g - z.g, h - z.h, i - z.i)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix.
+	End Rem
+	Method Operator*:SMat3D(z:SMat3D Var)
+		Local a00:Double = a
+		Local a01:Double = b
+		Local a02:Double = c
+		Local a10:Double = d
+		Local a11:Double = e
+		Local a12:Double = f
+		Local a20:Double = g
+		Local a21:Double = h
+		Local a22:Double = i
+		Local b00:Double = z.a
+		Local b01:Double = z.b
+		Local b02:Double = z.c
+		Local b10:Double = z.d
+		Local b11:Double = z.e
+		Local b12:Double = z.f
+		Local b20:Double = z.g
+		Local b21:Double = z.h
+		Local b22:Double = z.i
+		Return New SMat3D(b00 * a00 + b01 * a10 + b02 * a20, ..
+			b00 * a01 + b01 * a11 + b02 * a21, ..
+			b00 * a02 + b01 * a12 + b02 * a22, ..
+			b10 * a00 + b11 * a10 + b12 * a20, ..
+			b10 * a01 + b11 * a11 + b12 * a21, ..
+			b10 * a02 + b11 * a12 + b12 * a22, ..
+			b20 * a00 + b21 * a10 + b22 * a20, ..
+			b20 * a01 + b21 * a11 + b22 * a21, ..
+			b20 * a02 + b21 * a12 + b22 * a22)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat3D()
+		Return New SMat3D(e * i - f * h, ..
+			c * h - b * i, ..
+			b * f - c * e, ..
+			f * g - d * i, ..
+			a * i - c * g, ..
+			c * d - a * f, ..
+			d * h - e * g, ..
+			b * g - a * h, ..
+			a * e - b * d)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, or element-wise matrix multiplication, return a new matrix.
+	End Rem
+	Method CompMul:SMat3D(z:SMat3D Var)
+		Return New SMat3D(a * z.a, b * z.b, c * z.c, d * z.d, e * z.e, f * z.f, g * z.g, h * z.h, i * z.i)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Double()
+		Local a00:Double = a
+		Local a01:Double = b
+		Local a02:Double = c
+		Local a10:Double = d
+		Local a11:Double = e
+		Local a12:Double = f
+		Local a20:Double = g
+		Local a21:Double = h
+		Local a22:Double = i
+		Return a00 * ( a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * ( a21 * a10 - a11 * a20)
+	End Method
+	
+	Rem
+	bbdoc: Returns the inverse of the matrix.
+	End Rem
+	Method Invert:SMat3D()
+		Local a00:Double = a
+		Local a01:Double = b
+		Local a02:Double = c
+		Local a10:Double = d
+		Local a11:Double = e
+		Local a12:Double = f
+		Local a20:Double = g
+		Local a21:Double = h
+		Local a22:Double = i
+		Local b01:Double =  a22 * a11 - a12 * a21
+		Local b11:Double = -a22 * a10 + a12 * a20
+		Local b21:Double =  a21 * a10 - a11 * a20
+		Local det:Double = a00 * b01 + a01 * b11 + a02 * b21
+		If det = 0 Then
+			Return New SMat3D(0, 0, 0, 0, 0, 0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat3D(b01 * det, ..
+			(-a22 * a01 + a02 * a21) * det, ..
+			( a12 * a01 - a02 * a11) * det,
+			b11 * det, ..
+			( a22 * a00 - a02 * a20) * det, ..
+			(-a12 * a00 + a02 * a10) * det, ..
+			b21 * det, ..
+			(-a21 * a00 + a01 * a20) * det, ..
+			( a11 * a00 - a01 * a10) * det)
+	End Method
+	
+	Rem
+	bbdoc: Rotates the matrix on a 2D rotation in the XY plane by @angle degrees, returning a new matrix.
+	End Rem
+	Method Rotate:SMat3D(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat3D(ca * a + sa * d, ..
+			ca * b + sa * e, ..
+			ca * c + sa * f, ..
+			ca * d - sa * a, ..
+			ca * e - sa * b, ..
+			ca * f - sa * c, ..
+			g, h, i)
+	End Method
+
+	Rem
+	bbdoc: Rotates the matrix around the Z axis by @angle degrees, returning a new matrix.
+	End Rem
+	Method RotateZ:SMat3D(angle:Double)
+		Local ca:Double = Cos(angle)
+		Local sa:Double = Sin(angle)
+	
+		Return New SMat3D( ..
+			a * ca - c * sa, ..
+			b * ca - d * sa, ..
+			0, ..
+			a * sa + c * ca, ..
+			b * sa + d * ca, ..
+			0, ..
+			g, h, i)
+	End Method
+
+	Rem
+	bbdoc: Returns a rotation matrix of @angle degrees.
+	End Rem
+	Function Rotation:SMat3D(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat3D(ca, sa, 0, -sa, ca, 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Scales the matrix by @s, returning a new matrix.
+	End Rem
+	Method Scale:SMat3D(s:SVec2D)
+		Local bx:Double = s.x
+		Local by:Double = s.y
+		Return New SMat3D(a * bx, b * bx, c * bx, d * by, e * by, f * by, g, h, i)
+	End Method
+	
+	Rem
+	bbdoc: Returns a scaling matrix of @s.
+	End Rem
+	Function Scaling:SMat3D(s:SVec2D)
+		Return New SMat3D(s.x, 0, 0, 0, s.y, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns a translation with the specified @x, @y, and @z displacements.
+	End Rem
+	Method Translate:SMat3D(x:Double, y:Double, z:Double)
+		Return New SMat3D( ..
+			a, b, c, ..
+			d, e, f, ..
+			g + a * x + b * y + c * z, ..
+			h + d * x + e * y + f * z, ..
+			i + g * x + h * y + i * z)
+	End Method
+
+	Rem
+	bbdoc: Returns a translation with displacement vector @s.
+	End Rem
+	Method Translate:SMat3D(t:SVec3D)
+		Return New SMat3D( ..
+			a, b, c, ..
+			d, e, f, ..
+			g + a * t.x + b * t.y + c * t.z, ..
+			h + d * t.x + e * t.y + f * t.z, ..
+			i + g * t.x + h * t.y + i * t.z)
+	End Method
+	
+	Rem
+	bbdoc: Returns a transposition of the matrix.
+	End Rem
+	Method Transpose:SMat3D()
+		Return New SMat3D(a, d, g, b, e, h, c, f, i)
+	End Method
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(d).Append(", ").Append(g).Append(",~n")
+		sb.Append(b).Append(", ").Append(e).Append(", ").Append(h).Append(",~n")
+		sb.Append(c).Append(", ").Append(f).Append(", ").Append(i)
+		
+		Return sb.ToString()
+	End Method
+	
+End Struct
+
+Rem
+bbdoc: A standard 4x4 transformation matrix.
+End Rem
+Struct SMat4D
+	Field ReadOnly a:Double
+	Field ReadOnly b:Double
+	Field ReadOnly c:Double
+	Field ReadOnly d:Double
+	Field ReadOnly e:Double
+	Field ReadOnly f:Double
+	Field ReadOnly g:Double
+	Field ReadOnly h:Double
+	Field ReadOnly i:Double
+	Field ReadOnly j:Double
+	Field ReadOnly k:Double
+	Field ReadOnly l:Double
+	Field ReadOnly m:Double
+	Field ReadOnly n:Double
+	Field ReadOnly o:Double
+	Field ReadOnly p:Double
+
+	Rem
+	bbdoc: Creates a new #SMat4D from the supplied arguments.
+	End Rem
+	Method New(a:Double, b:Double, c:Double, d:Double, e:Double, f:Double, g:Double, h:Double, i:Double, j:Double, k:Double, l:Double, m:Double, n:Double, o:Double, p:Double)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+		Self.e = e
+		Self.f = f
+		Self.g = g
+		Self.h = h
+		Self.i = i
+		Self.j = j
+		Self.k = k
+		Self.l = l
+		Self.m = m
+		Self.n = n
+		Self.o = o
+		Self.p = p
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2D(v:SVec2D)
+		Return New SVec2D(a * v.x + e * v.y + m, b * v.x + f * v.y + n)
+	End Method
+
+	Rem
+	bbdoc: Applies the 4x4 matrix @b to the vector, returning a new vector.
+	End Rem
+	Method Apply:SVec3D(v:SVec3D)
+		Local w:Double = d * v.x + h * v.y + l * v.z + p
+		If w = 0 Then
+			w = 1
+		Else
+			w = 1 / w
+		End If
+		Return New SVec3D((a * v.x + e * v.y + i * v.z + m) * w, ..
+			(b * v.x + f * v.y + j * v.z + n) * w, ..
+			(c * v.x + g * v.y + k * v.z + o) * w)
+	End Method
+
+	Rem
+	bbdoc: Applies the 4x4 matrix @b to the vector, returning a new vector.
+	End Rem
+	Method Apply:SVec4D(v:SVec4D)
+		Return New SVec4D(a * v.x + e * v.y + i * v.z + m * v.w, ..
+			b * v.x + f * v.y + j * v.z + n * v.w, ..
+			c * v.x + g * v.y + k * v.z + o * v.w, ..
+			d * v.x + h * v.y + l * v.z + p * v.w)
+	End Method
+
+	Rem
+	bbdoc: Returns the identity matrix.
+	End Rem
+	Function Identity:SMat4D()
+		Return New SMat4D(1, 0, 0, 0, ..
+				0, 1, 0, 0, ..
+				0, 0, 1, 0, ..
+				0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat4D(z:SMat4D Var)
+		Return New SMat4D(a + z.a, b + z.b, c + z.c, d + z.d, ..
+			e + z.e, f + z.f, g + z.g, h + z.h, ..
+			i + z.i, j + z.j, k + z.k, l + z.l, ..
+			m + z.m, n + z.n, o + z.o, p + z.p)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat4D(z:SMat4D Var)
+		Return New SMat4D(a - z.a, b - z.b, c - z.c, d - z.d, ..
+			e - z.e, f - z.f, g - z.g, h - z.h, ..
+			i - z.i, j - z.j, k - z.k, l - z.l, ..
+			m - z.m, n - z.n, o - z.o, p - z.p)
+	End Method
+
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix. 
+	End Rem
+	Method Operator*:SMat4D(z:SMat4D Var)
+		Local a00:Double = a
+		Local a01:Double = b
+		Local a02:Double = c
+		Local a03:Double = d
+		Local a10:Double = e
+		Local a11:Double = f
+		Local a12:Double = g
+		Local a13:Double = h
+		Local a20:Double = i
+		Local a21:Double = j
+		Local a22:Double = k
+		Local a23:Double = l
+		Local a30:Double = m
+		Local a31:Double = n
+		Local a32:Double = o
+		Local a33:Double = p
+		Local b00:Double = z.a
+		Local b01:Double = z.b
+		Local b02:Double = z.c
+		Local b03:Double = z.d
+		Local b10:Double = z.e
+		Local b11:Double = z.f
+		Local b12:Double = z.g
+		Local b13:Double = z.h
+		Local b20:Double = z.i
+		Local b21:Double = z.j
+		Local b22:Double = z.k
+		Local b23:Double = z.l
+		Local b30:Double = z.m
+		Local b31:Double = z.n
+		Local b32:Double = z.o
+		Local b33:Double = z.p
+		Return New SMat4D(b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, ..
+			b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, ..
+			b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, ..
+			b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, ..
+			b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, ..
+			b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, ..
+			b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, ..
+			b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, ..
+			b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, ..
+			b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, ..
+			b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, ..
+			b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, ..
+			b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, ..
+			b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, ..
+			b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, ..
+			b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat4D()
+		Local a00:Double = a
+		Local a01:Double = b
+		Local a02:Double = c
+		Local a03:Double = d
+		Local a10:Double = e
+		Local a11:Double = f
+		Local a12:Double = g
+		Local a13:Double = h
+		Local a20:Double = i
+		Local a21:Double = j
+		Local a22:Double = k
+		Local a23:Double = l
+		Local a30:Double = m
+		Local a31:Double = n
+		Local a32:Double = o
+		Local a33:Double = p
+		Return New SMat4D(a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22), ..
+			-(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22)), ..
+			a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12), ..
+			-(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12)), ..
+			-(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22)), ..
+			a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22), ..
+			-(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12)), ..
+			a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12), ..
+			a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21), ..
+			-(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21)), ..
+			a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11), ..
+			-(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11)), ..
+			-(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21)), ..
+			a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21), ..
+			-(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11)), ..
+			a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11))
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, or element-wise matrix multiplication, returning a new matrix.
+	End Rem
+	Method CompMul:SMat4D(z:SMat4D Var)
+		Return New SMat4D(a * z.a, b * z.b, c * z.c, d * z.d, ..
+			e * z.e, f * z.f, g * z.g, h * z.h, ..
+			i * z.i, j * z.j, k * z.k, l * z.l, ..
+			m * z.m, n * z.n, o * z.o, p * z.p)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Double()
+		Local a00:Double = a
+		Local a01:Double = b
+		Local a02:Double = c
+		Local a03:Double = d
+		Local a10:Double = e
+		Local a11:Double = f
+		Local a12:Double = g
+		Local a13:Double = h
+		Local a20:Double = i
+		Local a21:Double = j
+		Local a22:Double = k
+		Local a23:Double = l
+		Local a30:Double = m
+		Local a31:Double = n
+		Local a32:Double = o
+		Local a33:Double = p
+		Local b00:Double = a00 * a11 - a01 * a10
+		Local b01:Double = a00 * a12 - a02 * a10
+		Local b02:Double = a00 * a13 - a03 * a10
+		Local b03:Double = a01 * a12 - a02 * a11
+		Local b04:Double = a01 * a13 - a03 * a11
+		Local b05:Double = a02 * a13 - a03 * a12
+		Local b06:Double = a20 * a31 - a21 * a30
+		Local b07:Double = a20 * a32 - a22 * a30
+		Local b08:Double = a20 * a33 - a23 * a30
+		Local b09:Double = a21 * a32 - a22 * a31
+		Local b10:Double = a21 * a33 - a23 * a31
+		Local b11:Double = a22 * a33 - a23 * a32
+		Return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
+	End Method
+
+	Rem
+	bbdoc: Returns a projection matrix with a viewing frustum defined by the plane coordinates passed in.
+	End Rem
+	Function Frustum:SMat4D(l:Double, r:Double, b:Double, t:Double, n:Double, f:Double)
+		Local rl:Double = 1.0 / (r - l)
+		Local tb:Double = 1.0 / (t - b)
+		Local nf:Double = 1.0 / (n - f)
+		Return New SMat4D((2.0 * n) * rl, 0, 0, 0, ..
+			0, (2.0 * n) * tb, 0, 0, ..
+			(r + l) * rl, (t + b) * tb, (f + n) * nf, -1, ..
+			0, 0, (2.0 * n * f) * nf, 0)
+	End Function
+	
+	Rem
+	bbdoc: The inverse of this matrix.
+	about: An inverted matrix is such that if multiplied by the original would result in identity matrix.
+	If some matrix transforms vectors in a particular way, then the inverse matrix can transform them back.
+	End Rem
+	Method Invert:SMat4D()
+		Local a00:Double = a
+		Local a01:Double = b
+		Local a02:Double = c
+		Local a03:Double = d
+		Local a10:Double = e
+		Local a11:Double = f
+		Local a12:Double = g
+		Local a13:Double = h
+		Local a20:Double = i
+		Local a21:Double = j
+		Local a22:Double = k
+		Local a23:Double = l
+		Local a30:Double = m
+		Local a31:Double = n
+		Local a32:Double = o
+		Local a33:Double = p
+		Local b00:Double = a00 * a11 - a01 * a10
+		Local b01:Double = a00 * a12 - a02 * a10
+		Local b02:Double = a00 * a13 - a03 * a10
+		Local b03:Double = a01 * a12 - a02 * a11
+		Local b04:Double = a01 * a13 - a03 * a11
+		Local b05:Double = a02 * a13 - a03 * a12
+		Local b06:Double = a20 * a31 - a21 * a30
+		Local b07:Double = a20 * a32 - a22 * a30
+		Local b08:Double = a20 * a33 - a23 * a30
+		Local b09:Double = a21 * a32 - a22 * a31
+		Local b10:Double = a21 * a33 - a23 * a31
+		Local b11:Double = a22 * a33 - a23 * a32
+		Local det:Double = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
+		If det = 0 Then
+			Return New SMat4D(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat4D((a11 * b11 - a12 * b10 + a13 * b09) * det, ..
+			(a02 * b10 - a01 * b11 - a03 * b09) * det, ..
+			(a31 * b05 - a32 * b04 + a33 * b03) * det, ..
+			(a22 * b04 - a21 * b05 - a23 * b03) * det, ..
+			(a12 * b08 - a10 * b11 - a13 * b07) * det, ..
+			(a00 * b11 - a02 * b08 + a03 * b07) * det, ..
+			(a32 * b02 - a30 * b05 - a33 * b01) * det, ..
+			(a20 * b05 - a22 * b02 + a23 * b01) * det, ..
+			(a10 * b10 - a11 * b08 + a13 * b06) * det, ..
+			(a01 * b08 - a00 * b10 - a03 * b06) * det, ..
+			(a30 * b04 - a31 * b02 + a33 * b00) * det, ..
+			(a21 * b02 - a20 * b04 - a23 * b00) * det, ..
+			(a11 * b07 - a10 * b09 - a12 * b06) * det, ..
+			(a00 * b09 - a01 * b07 + a02 * b06) * det, ..
+			(a31 * b01 - a30 * b03 - a32 * b00) * det, ..
+			(a20 * b03 - a21 * b01 + a22 * b00) * det)
+	End Method
+	
+	Rem
+	bbdoc: Computes a transformation matrix that corresponds to a camera viewing the @eye from the @pos.
+	about: The right-hand vector is perpendicular to the up vector.
+	End Rem
+	Function LookAt:SMat4D(eye:SVec3D, pos:SVec3D, upDir:SVec3D)
+		Local forward:SVec3D = (eye - pos).Normal()
+		Local lft:SVec3D = upDir.Cross(forward).Normal()
+		
+		Local up:SVec3D = forward.Cross(lft)
+		
+		Local mat:SMat4D = SMat4D.Identity()
+		
+		Local a00:Double = lft.x
+		Local a01:Double = up.x
+		Local a02:Double = forward.x
+		Local a03:Double = mat.d
+		Local a10:Double = lft.y
+		Local a11:Double = up.y
+		Local a12:Double = forward.y
+		Local a13:Double = mat.h
+		Local a20:Double = lft.z
+		Local a21:Double = up.z
+		Local a22:Double = forward.z
+		Local a23:Double = mat.l
+		Local a30:Double = -lft.x * eye.x - lft.y * eye.y - lft.z * eye.z
+		Local a31:Double = -up.x * eye.x - up.y * eye.y - up.z * eye.z
+		Local a32:Double = -forward.x * eye.x - forward.y * eye.y - forward.z * eye.z
+		Local a33:Double = mat.p
+
+		Return New SMat4D(a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33)
+	End Function
+	
+	Rem
+	bbdoc: Creates an orthogonal projection matrix.
+	about: The returned matrix, when used as a Camera's projection matrix, creates a view showing the area between @width and @height, with @zNear and @zFar as the near and far depth clipping planes.
+	End Rem
+	Function Orthogonal:SMat4D(width:Double, height:Double, zNear:Double, zFar:Double)
+		Local nf:Double = 1.0 / (zNear - zFar)
+		Return New SMat4D(2.0 / width, 0, 0, 0, ..
+			0, 2.0 / height, 0, 0, ..
+			0, 0, 2.0 * nf, 0, ..
+			0, 0, (zNear + zFar) * nf, 1)
+	End Function
+	
+	Rem
+	bbdoc: Creates a perspective projection matrix.
+	End Rem
+	Function Perspective:SMat4D(fov:Double, w:Double, h:Double, n:Double, f:Double)
+		Local tf:Double = Tan(fov / 2)
+		Return New SMat4D(1 / ((w / h) * tf), 0, 0, 0, ..
+			0, 1 / tf, 0, 0, ..
+			0, 0, - (f + n) / (f - n), -1, ..
+			0, 0, - (2 * f * n) / (f - n), 0)
+	End Function
+	
+	Rem
+	bbdoc: Creates a rotation matrix, rotated @angle degrees around the point @axis.
+	End Rem
+	Method Rotate:SMat4D(axis:SVec3D, angle:Double)
+		Local c:Double = Cos(angle)
+		Local ic:Double = 1 - c
+		Local s:Double = Sin(angle)
+
+		Local norm:SVec3D = axis.Normal()
+
+		Local x:Double = ic * norm.x
+		Local y:Double = ic * norm.y
+		Local z:Double = ic * norm.z
+		Local mat:SMat4D = New SMat4D(c + x * norm.x, x * norm.y + s * norm.z, x * norm.z - s * norm.y, 0, ..
+				y * norm.x - s * norm.z, c + y * norm.y, y * norm.z + s * norm.x, 0, ..
+				z * norm.x + s * norm.y, z * norm.y - s * norm.x, c + z * norm.z, 0, ..
+				0, 0, 0, 1)
+		
+		Return Self * mat
+	End Method
+	
+	Rem
+	bbdoc: Returns a rotation matrix on the given @axis and @angle degrees.
+	End Rem
+	Function Rotation:SMat4D(axis:SVec3D, angle:Double)
+		Local x:Double = axis.x
+		Local y:Double = axis.y
+		Local z:Double = axis.z
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Local t:Double = 1 - ca
+		Return New SMat4D(x * x * t + ca, ..
+			y * x * t + z * sa, ..
+			z * x * t - y * sa, ..
+			0, ..
+			x * y * t - z * sa, ..
+			y * y * t + ca, ..
+			z * y * t + x * sa, ..
+			0, ..
+			x * z * t + y * sa, ..
+			y * z * t - x * sa, ..
+			z * z * t + ca, ..
+			0, 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Scales the matrix, return the new scaled matrix.
+	End Rem
+	Method Scale:SMat4D(s:SVec3D)
+		Local bx:Double = s.x
+		Local by:Double = s.y
+		Local bz:Double = s.z
+		Return New SMat4D(a * bx, b * bx, c * bx, d * bx, ..
+			e * by, f * by, g * by, h * by, ..
+			i * bz, j * bz, k * bz, l * bz, ..
+			m, n, o, p)
+	End Method
+	
+	Rem
+	bbdoc: Creates a scaling matrix.
+	End Rem
+	Function Scaling:SMat4D(s:SVec3D)
+		Return New SMat4D(s.x, 0, 0, 0, 0, s.y, 0, 0, 0, 0, s.z, 0, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns the transpose of this matrix.
+	about: The transposed matrix is the one that has the columns exchanged with its rows.
+	End Rem
+	Method Transpose:SMat4D()
+		Return New SMat4D(a, e, i, m, b, f, j, n, c, g, k, o, d, h, l, p)
+	End Method
+	
+	Rem
+	bbdoc: Translates the matrix to @s.
+	End Rem
+	Method Translate:SMat4D(s:SVec3D)
+		Local bx:Double = s.x
+		Local by:Double = s.y
+		Local bz:Double = s.z
+		Return New SMat4D(a, b, c, d, e, f, g, h, i, j, k, l, ..
+			a * bx + e * by + i * bz + m, ..
+			b * bx + f * by + j * bz + n, ..
+			c * bx + g * by + k * bz + o, ..
+			d * bx + h * by + l * bz + p)
+	End Method
+
+	Rem
+	bbdoc: Creates a translation matrix.
+	End Rem
+	Function Translation:SMat4D(s:SVec3D)
+		Return New SMat4D(1, 0, 0, 0, ..
+			0, 1, 0, 0, ..
+			0, 0, 1, 0, ..
+			s.x, s.y, s.z, 1)
+	End Function
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(e).Append(", ").Append(i).Append(", ").Append(m).Append(",~n")
+		sb.Append(b).Append(", ").Append(f).Append(", ").Append(j).Append(", ").Append(n).Append(",~n")
+		sb.Append(c).Append(", ").Append(g).Append(", ").Append(k).Append(", ").Append(o).Append(",~n")
+		sb.Append(d).Append(", ").Append(h).Append(", ").Append(l).Append(", ").Append(p)
+		
+		Return sb.ToString()
+	End Method
+	
+End Struct
+
+Rem
+bbdoc: A #Float backed 2x2 Matrix.
+End Rem
+Struct SMat2F
+	Field ReadOnly a:Float
+	Field ReadOnly b:Float
+	Field ReadOnly c:Float
+	Field ReadOnly d:Float
+	
+	Rem
+	bbdoc: Creates a new #SMat2F from the supplied arguments.
+	End Rem
+	Method New(a:Float, b:Float, c:Float, d:Float)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+	End Method
+	
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2F(v:SVec2F)
+		Return New SVec2F(a * v.x + c * v.y, b * v.x + d * v.y)
+	End Method
+
+	Rem
+	bbdoc: Returns the identity matrix.
+	End Rem
+	Function Identity:SMat2F()
+		Return New SMat2F(1, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat2F(z:SMat2F)
+		Return New SMat2F(a + z.a, b + z.b, c + z.c, d + z.d)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat2F(z:SMat2F)
+		Return New SMat2F(a - z.a, b - z.b, c - z.c, d - z.d)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix.
+	End Rem
+	Method Operator*:SMat2F(z:SMat2F)
+		Return New SMat2F(a * z.a + c * z.b, b * z.a + d * z.b, a * z.c + c * z.d, b * z.c + d * z.d)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat2F()
+		Return New SMat2F(d, -b, -c, a)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, return a new matrix.
+	End Rem
+	Method CompMul:SMat2F(z:SMat2F)
+		Return New SMat2F(a * z.a, b * z.b, c * z.c, d * z.d)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Float()
+		Return a * d - c * b
+	End Method
+	
+	Rem
+	bbdoc: Returns the inverse of the matrix.
+	End Rem
+	Method Invert:SMat2F()
+		Local det:Float = a * d - c * b
+		If det = 0 Then
+			Return New SMat2F(0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat2F(d * det, -b * det, -c * det, a * det)
+	End Method
+	
+	Rem
+	bbdoc: Rotates the matrix by @angle degrees, returning the rotated matrix.
+	End Rem
+	Method Rotate:SMat2F(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat2F(Float(a * ca + c * sa), Float(b * ca + d * sa), Float(a * -sa + c * ca), Float(b * -sa + d * ca))
+	End Method
+	
+	Rem
+	bbdoc: Creates a rotated matrix of @angle degrees.
+	End Rem
+	Function Rotation:SMat2F(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat2F(Float(ca), Float(sa), Float(-sa), Float(ca))
+	End Function
+	
+	Rem
+	bbdoc: Returns the scale of this matrix.
+	End Rem
+	Method Scale:SMat2F(s:SVec2F)
+		Return New SMat2F(a * s.x, b * s.x, c * s.y, d * s.y)
+	End Method
+
+	Rem
+	bbdoc: Returns the scale of this matrix.
+	End Rem
+	Method Scale:SMat2F(s:SVec2D)
+		Return New SMat2F(Float(a * s.x), Float(b * s.x), Float(c * s.y), Float(d * s.y))
+	End Method
+	
+	Rem
+	bbdoc: Creates a scaled matrix of the scale @s.
+	End Rem
+	Function Scaling:SMat2F(s:SVec2F)
+		Return New SMat2F(s.x, 0, 0, s.y)
+	End Function
+
+	Rem
+	bbdoc: Creates a scaled matrix of the scale @s.
+	End Rem
+	Function Scaling:SMat2F(s:SVec2D)
+		Return New SMat2F(Float(s.x), 0, 0, Float(s.y))
+	End Function
+
+	Rem
+	bbdoc: Returns the transpose of this matrix.
+	End Rem
+	Method Transpose:SMat2F()
+		Return New SMat2F(a, c, b, d)
+	End Method
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(c).Append(",~n")
+		sb.Append(b).Append(", ").Append(d)
+		
+		Return sb.ToString()
+	End Method
+
+End Struct
+
+Rem
+bbdoc: A #Float backed 3x3 matrix.
+End Rem
+Struct SMat3F
+	Field ReadOnly a:Float
+	Field ReadOnly b:Float
+	Field ReadOnly c:Float
+	Field ReadOnly d:Float
+	Field ReadOnly e:Float
+	Field ReadOnly f:Float
+	Field ReadOnly g:Float
+	Field ReadOnly h:Float
+	Field ReadOnly i:Float
+
+	Rem
+	bbdoc: Creates a new #SMat3F from the supplied arguments.
+	End Rem
+	Method New(a:Float, b:Float, c:Float, d:Float, e:Float, f:Float, g:Float, h:Float, i:Float)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+		Self.e = e
+		Self.f = f
+		Self.g = g
+		Self.h = h
+		Self.i = i
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2F(v:SVec2F)
+		Return New SVec2F(a * v.x + d * v.y + g, b * v.x + e * v.y + h)
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec3F(v:SVec3F)
+		Return New SVec3F(v.x * a + v.y * d + v.z * g, v.x * b + v.y * e + v.z * h, v.x * c + v.y * f + v.z * i)
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec4F(v:SVec4F)
+		Return New SVec4F(v.x * a + v.y * d + v.z * g, v.x * b + v.y * e + v.z * h, v.x * c + v.y * f + v.z * i, 0)
+	End Method
+
+	Rem
+	bbdoc: Return the 3x3 identity matrix.
+	End Rem
+	Function Identity:SMat3F()
+		Return New SMat3F(1, 0, 0, 0, 1, 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat3F(z:SMat3F Var)
+		Return New SMat3F(a + z.a, b + z.b, c + z.c, d + z.d, e + z.e, f + z.f, g + z.g, h + z.h, i + z.i)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat3F(z:SMat3F Var)
+		Return New SMat3F(a - z.a, b - z.b, c - z.c, d - z.d, e - z.e, f - z.f, g - z.g, h - z.h, i - z.i)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix.
+	End Rem
+	Method Operator*:SMat3F(z:SMat3F Var)
+		Local a00:Float = a
+		Local a01:Float = b
+		Local a02:Float = c
+		Local a10:Float = d
+		Local a11:Float = e
+		Local a12:Float = f
+		Local a20:Float = g
+		Local a21:Float = h
+		Local a22:Float = i
+		Local b00:Float = z.a
+		Local b01:Float = z.b
+		Local b02:Float = z.c
+		Local b10:Float = z.d
+		Local b11:Float = z.e
+		Local b12:Float = z.f
+		Local b20:Float = z.g
+		Local b21:Float = z.h
+		Local b22:Float = z.i
+		Return New SMat3F(b00 * a00 + b01 * a10 + b02 * a20, ..
+			b00 * a01 + b01 * a11 + b02 * a21, ..
+			b00 * a02 + b01 * a12 + b02 * a22, ..
+			b10 * a00 + b11 * a10 + b12 * a20, ..
+			b10 * a01 + b11 * a11 + b12 * a21, ..
+			b10 * a02 + b11 * a12 + b12 * a22, ..
+			b20 * a00 + b21 * a10 + b22 * a20, ..
+			b20 * a01 + b21 * a11 + b22 * a21, ..
+			b20 * a02 + b21 * a12 + b22 * a22)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat3F()
+		Return New SMat3F(e * i - f * h, ..
+			c * h - b * i, ..
+			b * f - c * e, ..
+			f * g - d * i, ..
+			a * i - c * g, ..
+			c * d - a * f, ..
+			d * h - e * g, ..
+			b * g - a * h, ..
+			a * e - b * d)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, return a new matrix.
+	End Rem
+	Method CompMul:SMat3F(z:SMat3F Var)
+		Return New SMat3F(a * z.a, b * z.b, c * z.c, d * z.d, e * z.e, f * z.f, g * z.g, h * z.h, i * z.i)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Float()
+		Local a00:Float = a
+		Local a01:Float = b
+		Local a02:Float = c
+		Local a10:Float = d
+		Local a11:Float = e
+		Local a12:Float = f
+		Local a20:Float = g
+		Local a21:Float = h
+		Local a22:Float = i
+		Return a00 * ( a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * ( a21 * a10 - a11 * a20)
+	End Method
+	
+	Rem
+	bbdoc: Returns the inverse of the matrix.
+	End Rem
+	Method Invert:SMat3F()
+		Local a00:Float = a
+		Local a01:Float = b
+		Local a02:Float = c
+		Local a10:Float = d
+		Local a11:Float = e
+		Local a12:Float = f
+		Local a20:Float = g
+		Local a21:Float = h
+		Local a22:Float = i
+		Local b01:Float =  a22 * a11 - a12 * a21
+		Local b11:Float = -a22 * a10 + a12 * a20
+		Local b21:Float =  a21 * a10 - a11 * a20
+		Local det:Float = a00 * b01 + a01 * b11 + a02 * b21
+		If det = 0 Then
+			Return New SMat3F(0, 0, 0, 0, 0, 0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat3F(b01 * det, ..
+			(-a22 * a01 + a02 * a21) * det, ..
+			( a12 * a01 - a02 * a11) * det,
+			b11 * det, ..
+			( a22 * a00 - a02 * a20) * det, ..
+			(-a12 * a00 + a02 * a10) * det, ..
+			b21 * det, ..
+			(-a21 * a00 + a01 * a20) * det, ..
+			( a11 * a00 - a01 * a10) * det)
+	End Method
+	
+	Rem
+	bbdoc: Rotates the matrix by @angle degrees, returning a new matrix.
+	End Rem
+	Method Rotate:SMat3F(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat3F(Float(ca * a + sa * d), ..
+			Float(ca * b + sa * e), ..
+			Float(ca * c + sa * f), ..
+			Float(ca * d - sa * a), ..
+			Float(ca * e - sa * b), ..
+			Float(ca * f - sa * c), ..
+			g, h, i)
+	End Method
+	
+	Rem
+	bbdoc: Retrns a rotation matrix of @angle degrees.
+	End Rem
+	Function Rotation:SMat3F(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat3F(Float(ca), Float(sa), 0, Float(-sa), Float(ca), 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Scales the matrix by @s, returning a new matrix.
+	End Rem
+	Method Scale:SMat3F(s:SVec2F)
+		Local bx:Float = s.x
+		Local by:Float = s.y
+		Return New SMat3F(a * bx, b * bx, c * bx, d * by, e * by, f * by, g, h, i)
+	End Method
+
+	Rem
+	bbdoc: Scales the matrix by @s, returning a new matrix.
+	End Rem
+	Method Scale:SMat3F(s:SVec2D)
+		Local bx:Float = s.x
+		Local by:Float = s.y
+		Return New SMat3F(Float(a * bx), Float(b * bx), Float(c * bx), Float(d * by), Float(e * by), Float(f * by), g, h, i)
+	End Method
+	
+	Rem
+	bbdoc: Returns a scaling matrix of @s.
+	End Rem
+	Function Scaling:SMat3F(s:SVec2F)
+		Return New SMat3F(s.x, 0, 0, 0, s.y, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns a scaling matrix of @s.
+	End Rem
+	Function Scaling:SMat3F(s:SVec2D)
+		Return New SMat3F(Float(s.x), 0, 0, 0, Float(s.y), 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns a translation with the specified @x, @y, and @z displacements.
+	End Rem
+	Method Translate:SMat3F(x:Float, y:Float, z:Float)
+		Return New SMat3F( ..
+			a, b, c, ..
+			d, e, f, ..
+			g + a * x + b * y + c * z, ..
+			h + d * x + e * y + f * z, ..
+			i + g * x + h * y + i * z)
+	End Method
+
+	Rem
+	bbdoc: Returns a translation with displacement vector @s.
+	End Rem
+	Method Translate:SMat3F(t:SVec3F)
+		Return New SMat3F( ..
+			a, b, c, ..
+			d, e, f, ..
+			g + a * t.x + b * t.y + c * t.z, ..
+			h + d * t.x + e * t.y + f * t.z, ..
+			i + g * t.x + h * t.y + i * t.z)
+	End Method
+
+	Rem
+	bbdoc: Returns a transposition of the matrix.
+	End Rem
+	Method Transpose:SMat3F()
+		Return New SMat3F(a, d, g, b, e, h, c, f, i)
+	End Method
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(d).Append(", ").Append(g).Append(",~n")
+		sb.Append(b).Append(", ").Append(e).Append(", ").Append(h).Append(",~n")
+		sb.Append(c).Append(", ").Append(f).Append(", ").Append(i)
+		
+		Return sb.ToString()
+	End Method
+	
+End Struct
+
+Rem
+bbdoc: A standard #Float backed 4x4 transformation matrix.
+End Rem
+Struct SMat4F
+	Field ReadOnly a:Float
+	Field ReadOnly b:Float
+	Field ReadOnly c:Float
+	Field ReadOnly d:Float
+	Field ReadOnly e:Float
+	Field ReadOnly f:Float
+	Field ReadOnly g:Float
+	Field ReadOnly h:Float
+	Field ReadOnly i:Float
+	Field ReadOnly j:Float
+	Field ReadOnly k:Float
+	Field ReadOnly l:Float
+	Field ReadOnly m:Float
+	Field ReadOnly n:Float
+	Field ReadOnly o:Float
+	Field ReadOnly p:Float
+
+	Rem
+	bbdoc: Creates a new #SMat4F from the supplied arguments.
+	End Rem
+	Method New(a:Float, b:Float, c:Float, d:Float, e:Float, f:Float, g:Float, h:Float, i:Float, j:Float, k:Float, l:Float, m:Float, n:Float, o:Float, p:Float)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+		Self.e = e
+		Self.f = f
+		Self.g = g
+		Self.h = h
+		Self.i = i
+		Self.j = j
+		Self.k = k
+		Self.l = l
+		Self.m = m
+		Self.n = n
+		Self.o = o
+		Self.p = p
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2F(v:SVec2F)
+		Return New SVec2F(a * v.x + e * v.y + m, b * v.x + f * v.y + n)
+	End Method
+
+	Rem
+	bbdoc: Applies the 4x4 matrix @b to the vector, returning a new vector.
+	End Rem
+	Method Apply:SVec3F(v:SVec3F)
+		Local w:Float = d * v.x + h * v.y + l * v.z + p
+		If w = 0 Then
+			w = 1
+		Else
+			w = 1 / w
+		End If
+		Return New SVec3F((a * v.x + e * v.y + i * v.z + m) * w, ..
+			(b * v.x + f * v.y + j * v.z + n) * w, ..
+			(c * v.x + g * v.y + k * v.z + o) * w)
+	End Method
+
+	Rem
+	bbdoc: Applies the 4x4 matrix @b to the vector, returning a new vector.
+	End Rem
+	Method Apply:SVec4F(v:SVec4F)
+		Return New SVec4F(a * v.x + e * v.y + i * v.z + m * v.w, ..
+			b * v.x + f * v.y + j * v.z + n * v.w, ..
+			c * v.x + g * v.y + k * v.z + o * v.w, ..
+			d * v.x + h * v.y + l * v.z + p * v.w)
+	End Method
+
+	Rem
+	bbdoc: Returns the identity matrix.
+	End Rem
+	Function Identity:SMat4F()
+		Return New SMat4F(1, 0, 0, 0, ..
+				0, 1, 0, 0, ..
+				0, 0, 1, 0, ..
+				0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat4F(z:SMat4F Var)
+		Return New SMat4F(a + z.a, b + z.b, c + z.c, d + z.d, ..
+			e + z.e, f + z.f, g + z.g, h + z.h, ..
+			i + z.i, j + z.j, k + z.k, l + z.l, ..
+			m + z.m, n + z.n, o + z.o, p + z.p)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat4F(z:SMat4F Var)
+		Return New SMat4F(a - z.a, b - z.b, c - z.c, d - z.d, ..
+			e - z.e, f - z.f, g - z.g, h - z.h, ..
+			i - z.i, j - z.j, k - z.k, l - z.l, ..
+			m - z.m, n - z.n, o - z.o, p - z.p)
+	End Method
+
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix. 
+	End Rem
+	Method Operator*:SMat4F(z:SMat4F Var)
+		Local a00:Float = a
+		Local a01:Float = b
+		Local a02:Float = c
+		Local a03:Float = d
+		Local a10:Float = e
+		Local a11:Float = f
+		Local a12:Float = g
+		Local a13:Float = h
+		Local a20:Float = i
+		Local a21:Float = j
+		Local a22:Float = k
+		Local a23:Float = l
+		Local a30:Float = m
+		Local a31:Float = n
+		Local a32:Float = o
+		Local a33:Float = p
+		Local b00:Float = z.a
+		Local b01:Float = z.b
+		Local b02:Float = z.c
+		Local b03:Float = z.d
+		Local b10:Float = z.e
+		Local b11:Float = z.f
+		Local b12:Float = z.g
+		Local b13:Float = z.h
+		Local b20:Float = z.i
+		Local b21:Float = z.j
+		Local b22:Float = z.k
+		Local b23:Float = z.l
+		Local b30:Float = z.m
+		Local b31:Float = z.n
+		Local b32:Float = z.o
+		Local b33:Float = z.p
+		Return New SMat4F(b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, ..
+			b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, ..
+			b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, ..
+			b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, ..
+			b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, ..
+			b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, ..
+			b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, ..
+			b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, ..
+			b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, ..
+			b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, ..
+			b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, ..
+			b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, ..
+			b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, ..
+			b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, ..
+			b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, ..
+			b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat4F()
+		Local a00:Float = a
+		Local a01:Float = b
+		Local a02:Float = c
+		Local a03:Float = d
+		Local a10:Float = e
+		Local a11:Float = f
+		Local a12:Float = g
+		Local a13:Float = h
+		Local a20:Float = i
+		Local a21:Float = j
+		Local a22:Float = k
+		Local a23:Float = l
+		Local a30:Float = m
+		Local a31:Float = n
+		Local a32:Float = o
+		Local a33:Float = p
+		Return New SMat4F(a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22), ..
+			-(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22)), ..
+			a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12), ..
+			-(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12)), ..
+			-(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22)), ..
+			a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22), ..
+			-(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12)), ..
+			a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12), ..
+			a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21), ..
+			-(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21)), ..
+			a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11), ..
+			-(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11)), ..
+			-(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21)), ..
+			a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21), ..
+			-(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11)), ..
+			a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11))
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, returning a new matrix.
+	End Rem
+	Method CompMul:SMat4F(z:SMat4F Var)
+		Return New SMat4F(a * z.a, b * z.b, c * z.c, d * z.d, ..
+			e * z.e, f * z.f, g * z.g, h * z.h, ..
+			i * z.i, j * z.j, k * z.k, l * z.l, ..
+			m * z.m, n * z.n, o * z.o, p * z.p)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Float()
+		Local a00:Float = a
+		Local a01:Float = b
+		Local a02:Float = c
+		Local a03:Float = d
+		Local a10:Float = e
+		Local a11:Float = f
+		Local a12:Float = g
+		Local a13:Float = h
+		Local a20:Float = i
+		Local a21:Float = j
+		Local a22:Float = k
+		Local a23:Float = l
+		Local a30:Float = m
+		Local a31:Float = n
+		Local a32:Float = o
+		Local a33:Float = p
+		Local b00:Float = a00 * a11 - a01 * a10
+		Local b01:Float = a00 * a12 - a02 * a10
+		Local b02:Float = a00 * a13 - a03 * a10
+		Local b03:Float = a01 * a12 - a02 * a11
+		Local b04:Float = a01 * a13 - a03 * a11
+		Local b05:Float = a02 * a13 - a03 * a12
+		Local b06:Float = a20 * a31 - a21 * a30
+		Local b07:Float = a20 * a32 - a22 * a30
+		Local b08:Float = a20 * a33 - a23 * a30
+		Local b09:Float = a21 * a32 - a22 * a31
+		Local b10:Float = a21 * a33 - a23 * a31
+		Local b11:Float = a22 * a33 - a23 * a32
+		Return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
+	End Method
+
+	Rem
+	bbdoc: Returns a projection matrix with a viewing frustum defined by the plane coordinates passed in.
+	End Rem
+	Function Frustum:SMat4F(l:Float, r:Float, b:Float, t:Float, n:Float, f:Float)
+		Local rl:Float = 1.0 / (r - l)
+		Local tb:Float = 1.0 / (t - b)
+		Local nf:Float = 1.0 / (n - f)
+		Return New SMat4F((2.0 * n) * rl, 0, 0, 0, ..
+			0, (2.0 * n) * tb, 0, 0, ..
+			(r + l) * rl, (t + b) * tb, (f + n) * nf, -1, ..
+			0, 0, (2.0 * n * f) * nf, 0)
+	End Function
+	
+	Rem
+	bbdoc: The inverse of this matrix.
+	about: An inverted matrix is such that if multiplied by the original would result in identity matrix.
+	If some matrix transforms vectors in a particular way, then the inverse matrix can transform them back.
+	End Rem
+	Method Invert:SMat4F()
+		Local a00:Float = a
+		Local a01:Float = b
+		Local a02:Float = c
+		Local a03:Float = d
+		Local a10:Float = e
+		Local a11:Float = f
+		Local a12:Float = g
+		Local a13:Float = h
+		Local a20:Float = i
+		Local a21:Float = j
+		Local a22:Float = k
+		Local a23:Float = l
+		Local a30:Float = m
+		Local a31:Float = n
+		Local a32:Float = o
+		Local a33:Float = p
+		Local b00:Float = a00 * a11 - a01 * a10
+		Local b01:Float = a00 * a12 - a02 * a10
+		Local b02:Float = a00 * a13 - a03 * a10
+		Local b03:Float = a01 * a12 - a02 * a11
+		Local b04:Float = a01 * a13 - a03 * a11
+		Local b05:Float = a02 * a13 - a03 * a12
+		Local b06:Float = a20 * a31 - a21 * a30
+		Local b07:Float = a20 * a32 - a22 * a30
+		Local b08:Float = a20 * a33 - a23 * a30
+		Local b09:Float = a21 * a32 - a22 * a31
+		Local b10:Float = a21 * a33 - a23 * a31
+		Local b11:Float = a22 * a33 - a23 * a32
+		Local det:Float = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
+		If det = 0 Then
+			Return New SMat4F(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat4F((a11 * b11 - a12 * b10 + a13 * b09) * det, ..
+			(a02 * b10 - a01 * b11 - a03 * b09) * det, ..
+			(a31 * b05 - a32 * b04 + a33 * b03) * det, ..
+			(a22 * b04 - a21 * b05 - a23 * b03) * det, ..
+			(a12 * b08 - a10 * b11 - a13 * b07) * det, ..
+			(a00 * b11 - a02 * b08 + a03 * b07) * det, ..
+			(a32 * b02 - a30 * b05 - a33 * b01) * det, ..
+			(a20 * b05 - a22 * b02 + a23 * b01) * det, ..
+			(a10 * b10 - a11 * b08 + a13 * b06) * det, ..
+			(a01 * b08 - a00 * b10 - a03 * b06) * det, ..
+			(a30 * b04 - a31 * b02 + a33 * b00) * det, ..
+			(a21 * b02 - a20 * b04 - a23 * b00) * det, ..
+			(a11 * b07 - a10 * b09 - a12 * b06) * det, ..
+			(a00 * b09 - a01 * b07 + a02 * b06) * det, ..
+			(a31 * b01 - a30 * b03 - a32 * b00) * det, ..
+			(a20 * b03 - a21 * b01 + a22 * b00) * det)
+	End Method
+	
+	Rem
+	bbdoc: Computes a transformation matrix that corresponds to a camera viewing the @eye from the @pos.
+	about: The right-hand vector is perpendicular to the up vector.
+	End Rem
+	Function LookAt:SMat4F(eye:SVec3F, pos:SVec3F, upDir:SVec3F)
+		Local forward:SVec3F = (eye - pos).Normal()
+		Local lft:SVec3F = upDir.Cross(forward).Normal()
+		
+		Local up:SVec3F = forward.Cross(lft)
+		
+		Local mat:SMat4F = SMat4F.Identity()
+		
+		Local a00:Float = lft.x
+		Local a01:Float = up.x
+		Local a02:Float = forward.x
+		Local a03:Float = mat.d
+		Local a10:Float = lft.y
+		Local a11:Float = up.y
+		Local a12:Float = forward.y
+		Local a13:Float = mat.h
+		Local a20:Float = lft.z
+		Local a21:Float = up.z
+		Local a22:Float = forward.z
+		Local a23:Float = mat.l
+		Local a30:Float = -lft.x * eye.x - lft.y * eye.y - lft.z * eye.z
+		Local a31:Float = -up.x * eye.x - up.y * eye.y - up.z * eye.z
+		Local a32:Float = -forward.x * eye.x - forward.y * eye.y - forward.z * eye.z
+		Local a33:Float = mat.p
+
+		Return New SMat4F(a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33)
+	End Function
+	
+	Rem
+	bbdoc: Creates an orthogonal projection matrix.
+	about: The returned matrix, when used as a Camera's projection matrix, creates a view showing the area between @width and @height, with @zNear and @zFar as the near and far depth clipping planes.
+	End Rem
+	Function Orthogonal:SMat4F(width:Float, height:Float, zNear:Float, zFar:Float)
+		Local nf:Float = 1.0 / (zNear - zFar)
+		Return New SMat4F(2.0 / width, 0, 0, 0, ..
+			0, 2.0 / height, 0, 0, ..
+			0, 0, 2.0 * nf, 0, ..
+			0, 0, (zNear + zFar) * nf, 1)
+	End Function
+	
+	Rem
+	bbdoc: Creates a perspective projection matrix.
+	End Rem
+	Function Perspective:SMat4F(fov:Float, w:Float, h:Float, n:Float, f:Float)
+		Local tf:Float = Tan(fov / 2)
+		Return New SMat4F(1 / ((w / h) * tf), 0, 0, 0, ..
+			0, 1 / tf, 0, 0, ..
+			0, 0, - (f + n) / (f - n), -1, ..
+			0, 0, - (2 * f * n) / (f - n), 0)
+	End Function
+	
+	Rem
+	bbdoc: Creates a rotation matrix, rotated @angle degrees around the point @axis.
+	End Rem
+	Method Rotate:SMat4F(axis:SVec3F, angle:Double)
+		Local c:Float = Cos(angle)
+		Local ic:Float = 1 - c
+		Local s:Float = Sin(angle)
+
+		Local norm:SVec3F = axis.Normal()
+
+		Local x:Float = ic * norm.x
+		Local y:Float = ic * norm.y
+		Local z:Float = ic * norm.z
+		Local mat:SMat4F = New SMat4F(c + x * norm.x, x * norm.y + s * norm.z, x * norm.z - s * norm.y, 0, ..
+				y * norm.x - s * norm.z, c + y * norm.y, y * norm.z + s * norm.x, 0, ..
+				z * norm.x + s * norm.y, z * norm.y - s * norm.x, c + z * norm.z, 0, ..
+				0, 0, 0, 1)
+		
+		Return Self * mat
+	End Method
+	
+	Rem
+	bbdoc: Returns a rotation matrix on the given @axis and @angle degrees.
+	End Rem
+	Function Rotation:SMat4F(axis:SVec3F, angle:Double)
+		Local x:Float = axis.x
+		Local y:Float = axis.y
+		Local z:Float = axis.z
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Local t:Float = 1 - ca
+		Return New SMat4F(Float(x * x * t + ca), ..
+			Float(y * x * t + z * sa), ..
+			Float(z * x * t - y * sa), ..
+			0, ..
+			Float(x * y * t - z * sa), ..
+			Float(y * y * t + ca), ..
+			Float(z * y * t + x * sa), ..
+			0, ..
+			Float(x * z * t + y * sa), ..
+			Float(y * z * t - x * sa), ..
+			Float(z * z * t + ca), ..
+			0, 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Scales the matrix, return the new scaled matrix.
+	End Rem
+	Method Scale:SMat4F(s:SVec3F)
+		Local bx:Float = s.x
+		Local by:Float = s.y
+		Local bz:Float = s.z
+		Return New SMat4F(a * bx, b * bx, c * bx, d * bx, ..
+			e * by, f * by, g * by, h * by, ..
+			i * bz, j * bz, k * bz, l * bz, ..
+			m, n, o, p)
+	End Method
+
+	Rem
+	bbdoc: Scales the matrix, return the new scaled matrix.
+	End Rem
+	Method Scale:SMat4F(s:SVec3D)
+		Local bx:Double = s.x
+		Local by:Double = s.y
+		Local bz:Double = s.z
+		Return New SMat4F(Float(a * bx), Float(b * bx), Float(c * bx), Float(d * bx), ..
+			Float(e * by), Float(f * by), Float(g * by), Float(h * by), ..
+			Float(i * bz), Float(j * bz), Float(k * bz), Float(l * bz), ..
+			m, n, o, p)
+	End Method
+	
+	Rem
+	bbdoc: Creates a scaling matrix.
+	End Rem
+	Function Scaling:SMat4F(s:SVec3F)
+		Return New SMat4F(s.x, 0, 0, 0, 0, s.y, 0, 0, 0, 0, s.z, 0, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Creates a Scaling matrix.
+	End Rem
+	Function Scaling:SMat4F(s:SVec3D)
+		Return New SMat4F(Float(s.x), 0, 0, 0, 0, Float(s.y), 0, 0, 0, 0, Float(s.z), 0, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns the transpose of this matrix.
+	about: The transposed matrix is the one that has the columns exchanged with its rows.
+	End Rem
+	Method Transpose:SMat4F()
+		Return New SMat4F(a, e, i, m, b, f, j, n, c, g, k, o, d, h, l, p)
+	End Method
+	
+	Rem
+	bbdoc: Translates the matrix to @s.
+	End Rem
+	Method Translate:SMat4F(s:SVec3F)
+		Local bx:Float = s.x
+		Local by:Float = s.y
+		Local bz:Float = s.z
+		Return New SMat4F(a, b, c, d, e, f, g, h, i, j, k, l, ..
+			a * bx + e * by + i * bz + m, ..
+			b * bx + f * by + j * bz + n, ..
+			c * bx + g * by + k * bz + o, ..
+			d * bx + h * by + l * bz + p)
+	End Method
+
+	Rem
+	bbdoc: Translates the matrix To @s.
+	End Rem
+	Method Translate:SMat4F(s:SVec3D)
+		Local bx:Float = s.x
+		Local by:Float = s.y
+		Local bz:Float = s.z
+		Return New SMat4F(a, b, c, d, e, f, g, h, i, j, k, l, ..
+			a * bx + e * by + i * bz + m, ..
+			b * bx + f * by + j * bz + n, ..
+			c * bx + g * by + k * bz + o, ..
+			d * bx + h * by + l * bz + p)
+	End Method
+
+	Rem
+	bbdoc: Creates a translation matrix.
+	End Rem
+	Function Translation:SMat4F(s:SVec3F)
+		Return New SMat4F(1, 0, 0, 0, ..
+			0, 1, 0, 0, ..
+			0, 0, 1, 0, ..
+			s.x, s.y, s.z, 1)
+	End Function
+
+	Rem
+	bbdoc: Creates a translation matrix.
+	End Rem
+	Function Translation:SMat4F(s:SVec3D)
+		Return New SMat4F(1, 0, 0, 0, ..
+			0, 1, 0, 0, ..
+			0, 0, 1, 0, ..
+			Float(s.x), Float(s.y), Float(s.z), 1)
+	End Function
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(e).Append(", ").Append(i).Append(", ").Append(m).Append(",~n")
+		sb.Append(b).Append(", ").Append(f).Append(", ").Append(j).Append(", ").Append(n).Append(",~n")
+		sb.Append(c).Append(", ").Append(g).Append(", ").Append(k).Append(", ").Append(o).Append(",~n")
+		sb.Append(d).Append(", ").Append(h).Append(", ").Append(l).Append(", ").Append(p)
+		
+		Return sb.ToString()
+	End Method
+	
+End Struct
+
+Rem
+bbdoc: An #Int backed 2x2 Matrix.
+End Rem
+Struct SMat2I
+	Field ReadOnly a:Int
+	Field ReadOnly b:Int
+	Field ReadOnly c:Int
+	Field ReadOnly d:Int
+	
+	Rem
+	bbdoc: Creates a new #SMat2I from the supplied arguments.
+	End Rem
+	Method New(a:Int, b:Int, c:Int, d:Int)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+	End Method
+	
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2I(v:SVec2I)
+		Return New SVec2I(a * v.x + c * v.y, b * v.x + d * v.y)
+	End Method
+
+	Rem
+	bbdoc: Returns the identity matrix.
+	End Rem
+	Function Identity:SMat2I()
+		Return New SMat2I(1, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat2I(z:SMat2I)
+		Return New SMat2I(a + z.a, b + z.b, c + z.c, d + z.d)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat2I(z:SMat2I)
+		Return New SMat2I(a - z.a, b - z.b, c - z.c, d - z.d)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix.
+	End Rem
+	Method Operator*:SMat2I(z:SMat2I)
+		Return New SMat2I(a * z.a + c * z.b, b * z.a + d * z.b, a * z.c + c * z.d, b * z.c + d * z.d)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat2I()
+		Return New SMat2I(d, -b, -c, a)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, return a new matrix.
+	End Rem
+	Method CompMul:SMat2I(z:SMat2I)
+		Return New SMat2I(a * z.a, b * z.b, c * z.c, d * z.d)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Int()
+		Return a * d - c * b
+	End Method
+	
+	Rem
+	bbdoc: Returns the inverse of the matrix.
+	End Rem
+	Method Invert:SMat2I()
+		Local det:Double = a * d - c * b
+		If det = 0 Then
+			Return New SMat2I(0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat2I(Int(d * det), Int(-b * det), Int(-c * det), Int(a * det))
+	End Method
+	
+	Rem
+	bbdoc: Rotates the matrix by @angle degrees, returning the rotated matrix.
+	End Rem
+	Method Rotate:SMat2I(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat2I(Int(a * ca + c * sa), Int(b * ca + d * sa), Int(a * -sa + c * ca), Int(b * -sa + d * ca))
+	End Method
+	
+	Rem
+	bbdoc: Creates a rotated matrix of @angle degrees.
+	End Rem
+	Function Rotation:SMat2I(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat2I(Int(ca), Int(sa), Int(-sa), Int(ca))
+	End Function
+	
+	Rem
+	bbdoc: Returns the scale of this matrix.
+	End Rem
+	Method Scale:SMat2I(s:SVec2I)
+		Return New SMat2I(a * s.x, b * s.x, c * s.y, d * s.y)
+	End Method
+
+	Rem
+	bbdoc: Returns the scale of this matrix.
+	End Rem
+	Method Scale:SMat2I(s:SVec2D)
+		Return New SMat2I(Int(a * s.x), Int(b * s.x), Int(c * s.y), Int(d * s.y))
+	End Method
+
+	Rem
+	bbdoc: Returns the scale of this matrix.
+	End Rem
+	Method Scale:SMat2I(s:SVec2F)
+		Return New SMat2I(Int(a * s.x), Int(b * s.x), Int(c * s.y), Int(d * s.y))
+	End Method
+	
+	Rem
+	bbdoc: Creates a scaled matrix of the scale @s.
+	End Rem
+	Function Scaling:SMat2I(s:SVec2I)
+		Return New SMat2I(s.x, 0, 0, s.y)
+	End Function
+	
+	Rem
+	bbdoc: Returns the transpose of this matrix.
+	End Rem
+	Method Transpose:SMat2I()
+		Return New SMat2I(a, c, b, d)
+	End Method
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(c).Append(",~n")
+		sb.Append(b).Append(", ").Append(d)
+		
+		Return sb.ToString()
+	End Method
+
+End Struct
+
+Rem
+bbdoc: An #Int backed 3x3 matrix.
+End Rem
+Struct SMat3I
+	Field ReadOnly a:Int
+	Field ReadOnly b:Int
+	Field ReadOnly c:Int
+	Field ReadOnly d:Int
+	Field ReadOnly e:Int
+	Field ReadOnly f:Int
+	Field ReadOnly g:Int
+	Field ReadOnly h:Int
+	Field ReadOnly i:Int
+
+	Rem
+	bbdoc: Creates a new #SMat3I from the supplied arguments.
+	End Rem
+	Method New(a:Int, b:Int, c:Int, d:Int, e:Int, f:Int, g:Int, h:Int, i:Int)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+		Self.e = e
+		Self.f = f
+		Self.g = g
+		Self.h = h
+		Self.i = i
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2I(v:SVec2I)
+		Return New SVec2I(a * v.x + d * v.y + g, b * v.x + e * v.y + h)
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec3I(v:SVec3I)
+		Return New SVec3I(v.x * a + v.y * d + v.z * g, v.x * b + v.y * e + v.z * h, v.x * c + v.y * f + v.z * i)
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec4I(v:SVec4I)
+		Return New SVec4I(v.x * a + v.y * d + v.z * g, v.x * b + v.y * e + v.z * h, v.x * c + v.y * f + v.z * i, 0)
+	End Method
+
+	Rem
+	bbdoc: Return the 3x3 identity matrix.
+	End Rem
+	Function Identity:SMat3I()
+		Return New SMat3I(1, 0, 0, 0, 1, 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat3I(z:SMat3I Var)
+		Return New SMat3I(a + z.a, b + z.b, c + z.c, d + z.d, e + z.e, f + z.f, g + z.g, h + z.h, i + z.i)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat3I(z:SMat3I Var)
+		Return New SMat3I(a - z.a, b - z.b, c - z.c, d - z.d, e - z.e, f - z.f, g - z.g, h - z.h, i - z.i)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix.
+	End Rem
+	Method Operator*:SMat3I(z:SMat3I Var)
+		Local a00:Int = a
+		Local a01:Int = b
+		Local a02:Int = c
+		Local a10:Int = d
+		Local a11:Int = e
+		Local a12:Int = f
+		Local a20:Int = g
+		Local a21:Int = h
+		Local a22:Int = i
+		Local b00:Int = z.a
+		Local b01:Int = z.b
+		Local b02:Int = z.c
+		Local b10:Int = z.d
+		Local b11:Int = z.e
+		Local b12:Int = z.f
+		Local b20:Int = z.g
+		Local b21:Int = z.h
+		Local b22:Int = z.i
+		Return New SMat3I(b00 * a00 + b01 * a10 + b02 * a20, ..
+			b00 * a01 + b01 * a11 + b02 * a21, ..
+			b00 * a02 + b01 * a12 + b02 * a22, ..
+			b10 * a00 + b11 * a10 + b12 * a20, ..
+			b10 * a01 + b11 * a11 + b12 * a21, ..
+			b10 * a02 + b11 * a12 + b12 * a22, ..
+			b20 * a00 + b21 * a10 + b22 * a20, ..
+			b20 * a01 + b21 * a11 + b22 * a21, ..
+			b20 * a02 + b21 * a12 + b22 * a22)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat3I()
+		Return New SMat3I(e * i - f * h, ..
+			c * h - b * i, ..
+			b * f - c * e, ..
+			f * g - d * i, ..
+			a * i - c * g, ..
+			c * d - a * f, ..
+			d * h - e * g, ..
+			b * g - a * h, ..
+			a * e - b * d)
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, return a new matrix.
+	End Rem
+	Method CompMul:SMat3I(z:SMat3I Var)
+		Return New SMat3I(a * z.a, b * z.b, c * z.c, d * z.d, e * z.e, f * z.f, g * z.g, h * z.h, i * z.i)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Int()
+		Local a00:Int = a
+		Local a01:Int = b
+		Local a02:Int = c
+		Local a10:Int = d
+		Local a11:Int = e
+		Local a12:Int = f
+		Local a20:Int = g
+		Local a21:Int = h
+		Local a22:Int = i
+		Return a00 * ( a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * ( a21 * a10 - a11 * a20)
+	End Method
+	
+	Rem
+	bbdoc: Returns the inverse of the matrix.
+	End Rem
+	Method Invert:SMat3I()
+		Local a00:Int = a
+		Local a01:Int = b
+		Local a02:Int = c
+		Local a10:Int = d
+		Local a11:Int = e
+		Local a12:Int = f
+		Local a20:Int = g
+		Local a21:Int = h
+		Local a22:Int = i
+		Local b01:Int =  a22 * a11 - a12 * a21
+		Local b11:Int = -a22 * a10 + a12 * a20
+		Local b21:Int =  a21 * a10 - a11 * a20
+		Local det:Double = a00 * b01 + a01 * b11 + a02 * b21
+		If det = 0 Then
+			Return New SMat3I(0, 0, 0, 0, 0, 0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat3I(Int(b01 * det), ..
+			Int((-a22 * a01 + a02 * a21) * det), ..
+			Int(( a12 * a01 - a02 * a11) * det),
+			Int(b11 * det), ..
+			Int(( a22 * a00 - a02 * a20) * det), ..
+			Int((-a12 * a00 + a02 * a10) * det), ..
+			Int(b21 * det), ..
+			Int((-a21 * a00 + a01 * a20) * det), ..
+			Int(( a11 * a00 - a01 * a10) * det))
+	End Method
+	
+	Rem
+	bbdoc: Rotates the matrix by @angle degrees, returning a new matrix.
+	End Rem
+	Method Rotate:SMat3I(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat3I(Int(ca * a + sa * d), ..
+			Int(ca * b + sa * e), ..
+			Int(ca * c + sa * f), ..
+			Int(ca * d - sa * a), ..
+			Int(ca * e - sa * b), ..
+			Int(ca * f - sa * c), ..
+			g, h, i)
+	End Method
+	
+	Rem
+	bbdoc: Retrns a rotation matrix of @angle degrees.
+	End Rem
+	Function Rotation:SMat3I(angle:Double)
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Return New SMat3I(Int(ca), Int(sa), 0, Int(-sa), Int(ca), 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Scales the matrix by @s, returning a new matrix.
+	End Rem
+	Method Scale:SMat3I(s:SVec2I)
+		Local bx:Int = s.x
+		Local by:Int = s.y
+		Return New SMat3I(a * bx, b * bx, c * bx, d * by, e * by, f * by, g, h, i)
+	End Method
+
+	Rem
+	bbdoc: Scales the matrix by @s, returning a new matrix.
+	End Rem
+	Method Scale:SMat3I(s:SVec2D)
+		Local bx:Int = s.x
+		Local by:Int = s.y
+		Return New SMat3I(Int(a * bx), Int(b * bx), Int(c * bx), Int(d * by), Int(e * by), Int(f * by), g, h, i)
+	End Method
+
+	Rem
+	bbdoc: Scales the matrix by @s, returning a new matrix.
+	End Rem
+	Method Scale:SMat3I(s:SVec2F)
+		Local bx:Int = s.x
+		Local by:Int = s.y
+		Return New SMat3I(Int(a * bx), Int(b * bx), Int(c * bx), Int(d * by), Int(e * by), Int(f * by), g, h, i)
+	End Method
+	
+	Rem
+	bbdoc: Returns a scaling matrix of @s.
+	End Rem
+	Function Scaling:SMat3I(s:SVec2I)
+		Return New SMat3I(s.x, 0, 0, 0, s.y, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns a scaling matrix of @s.
+	End Rem
+	Function Scaling:SMat3I(s:SVec2D)
+		Return New SMat3I(Int(s.x), 0, 0, 0, Int(s.y), 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns a scaling matrix of @s.
+	End Rem
+	Function Scaling:SMat3I(s:SVec2F)
+		Return New SMat3I(Int(s.x), 0, 0, 0, Int(s.y), 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns a translation with the specified @x, @y, and @z displacements.
+	End Rem
+	Method Translate:SMat3I(x:Int, y:Int, z:Int)
+		Return New SMat3I( ..
+			a, b, c, ..
+			d, e, f, ..
+			g + a * x + b * y + c * z, ..
+			h + d * x + e * y + f * z, ..
+			i + g * x + h * y + i * z)
+	End Method
+
+	Rem
+	bbdoc: Returns a translation with displacement vector @s.
+	End Rem
+	Method Translate:SMat3I(t:SVec3I)
+		Return New SMat3I( ..
+			a, b, c, ..
+			d, e, f, ..
+			g + a * t.x + b * t.y + c * t.z, ..
+			h + d * t.x + e * t.y + f * t.z, ..
+			i + g * t.x + h * t.y + i * t.z)
+	End Method
+
+	Rem
+	bbdoc: Returns a transposition of the matrix.
+	End Rem
+	Method Transpose:SMat3I()
+		Return New SMat3I(a, d, g, b, e, h, c, f, i)
+	End Method
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(d).Append(", ").Append(g).Append(",~n")
+		sb.Append(b).Append(", ").Append(e).Append(", ").Append(h).Append(",~n")
+		sb.Append(c).Append(", ").Append(f).Append(", ").Append(i)
+		
+		Return sb.ToString()
+	End Method
+	
+End Struct
+
+Rem
+bbdoc: A standard #Int backed 4x4 transformation matrix.
+End Rem
+Struct SMat4I
+	Field ReadOnly a:Int
+	Field ReadOnly b:Int
+	Field ReadOnly c:Int
+	Field ReadOnly d:Int
+	Field ReadOnly e:Int
+	Field ReadOnly f:Int
+	Field ReadOnly g:Int
+	Field ReadOnly h:Int
+	Field ReadOnly i:Int
+	Field ReadOnly j:Int
+	Field ReadOnly k:Int
+	Field ReadOnly l:Int
+	Field ReadOnly m:Int
+	Field ReadOnly n:Int
+	Field ReadOnly o:Int
+	Field ReadOnly p:Int
+
+	Rem
+	bbdoc: Creates a new #SMat4I from the supplied arguments.
+	End Rem
+	Method New(a:Int, b:Int, c:Int, d:Int, e:Int, f:Int, g:Int, h:Int, i:Int, j:Int, k:Int, l:Int, m:Int, n:Int, o:Int, p:Int)
+		Self.a = a
+		Self.b = b
+		Self.c = c
+		Self.d = d
+		Self.e = e
+		Self.f = f
+		Self.g = g
+		Self.h = h
+		Self.i = i
+		Self.j = j
+		Self.k = k
+		Self.l = l
+		Self.m = m
+		Self.n = n
+		Self.o = o
+		Self.p = p
+	End Method
+
+	Rem
+	bbdoc: Applies the matrix to the vector @v, returning a new vector.
+	End Rem
+	Method Apply:SVec2I(v:SVec2I)
+		Return New SVec2I(a * v.x + e * v.y + m, b * v.x + f * v.y + n)
+	End Method
+
+	Rem
+	bbdoc: Applies the 4x4 matrix @b to the vector, returning a new vector.
+	End Rem
+	Method Apply:SVec3I(v:SVec3I)
+		Local w:Double = d * v.x + h * v.y + l * v.z + p
+		If w = 0 Then
+			w = 1
+		Else
+			w = 1 / w
+		End If
+		Return New SVec3I(Int((a * v.x + e * v.y + i * v.z + m) * w), ..
+			Int((b * v.x + f * v.y + j * v.z + n) * w), ..
+			Int((c * v.x + g * v.y + k * v.z + o) * w))
+	End Method
+
+	Rem
+	bbdoc: Applies the 4x4 matrix @b to the vector, returning a new vector.
+	End Rem
+	Method Apply:SVec4I(v:SVec4I)
+		Return New SVec4I(a * v.x + e * v.y + i * v.z + m * v.w, ..
+			b * v.x + f * v.y + j * v.z + n * v.w, ..
+			c * v.x + g * v.y + k * v.z + o * v.w, ..
+			d * v.x + h * v.y + l * v.z + p * v.w)
+	End Method
+
+	Rem
+	bbdoc: Returns the identity matrix.
+	End Rem
+	Function Identity:SMat4I()
+		Return New SMat4I(1, 0, 0, 0, ..
+				0, 1, 0, 0, ..
+				0, 0, 1, 0, ..
+				0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Adds @z to the matrix, returning a new matrix.
+	End Rem
+	Method Operator+:SMat4I(z:SMat4I Var)
+		Return New SMat4I(a + z.a, b + z.b, c + z.c, d + z.d, ..
+			e + z.e, f + z.f, g + z.g, h + z.h, ..
+			i + z.i, j + z.j, k + z.k, l + z.l, ..
+			m + z.m, n + z.n, o + z.o, p + z.p)
+	End Method
+	
+	Rem
+	bbdoc: Subtracts @z from the matrix, returning a new matrix.
+	End Rem
+	Method Operator-:SMat4I(z:SMat4I Var)
+		Return New SMat4I(a - z.a, b - z.b, c - z.c, d - z.d, ..
+			e - z.e, f - z.f, g - z.g, h - z.h, ..
+			i - z.i, j - z.j, k - z.k, l - z.l, ..
+			m - z.m, n - z.n, o - z.o, p - z.p)
+	End Method
+
+	Rem
+	bbdoc: Multiplies the matrix by @z, the dot product, returning a new matrix. 
+	End Rem
+	Method Operator*:SMat4I(z:SMat4I Var)
+		Local a00:Int = a
+		Local a01:Int = b
+		Local a02:Int = c
+		Local a03:Int = d
+		Local a10:Int = e
+		Local a11:Int = f
+		Local a12:Int = g
+		Local a13:Int = h
+		Local a20:Int = i
+		Local a21:Int = j
+		Local a22:Int = k
+		Local a23:Int = l
+		Local a30:Int = m
+		Local a31:Int = n
+		Local a32:Int = o
+		Local a33:Int = p
+		Local b00:Int = z.a
+		Local b01:Int = z.b
+		Local b02:Int = z.c
+		Local b03:Int = z.d
+		Local b10:Int = z.e
+		Local b11:Int = z.f
+		Local b12:Int = z.g
+		Local b13:Int = z.h
+		Local b20:Int = z.i
+		Local b21:Int = z.j
+		Local b22:Int = z.k
+		Local b23:Int = z.l
+		Local b30:Int = z.m
+		Local b31:Int = z.n
+		Local b32:Int = z.o
+		Local b33:Int = z.p
+		Return New SMat4I(b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, ..
+			b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, ..
+			b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, ..
+			b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, ..
+			b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, ..
+			b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, ..
+			b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, ..
+			b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, ..
+			b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, ..
+			b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, ..
+			b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, ..
+			b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, ..
+			b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, ..
+			b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, ..
+			b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, ..
+			b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33)
+	End Method
+	
+	Rem
+	bbdoc: Returns the transposition of the cofactor matrix.
+	End Rem
+	Method Adjoint:SMat4I()
+		Local a00:Int = a
+		Local a01:Int = b
+		Local a02:Int = c
+		Local a03:Int = d
+		Local a10:Int = e
+		Local a11:Int = f
+		Local a12:Int = g
+		Local a13:Int = h
+		Local a20:Int = i
+		Local a21:Int = j
+		Local a22:Int = k
+		Local a23:Int = l
+		Local a30:Int = m
+		Local a31:Int = n
+		Local a32:Int = o
+		Local a33:Int = p
+		Return New SMat4I(a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22), ..
+			-(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22)), ..
+			a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12), ..
+			-(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12)), ..
+			-(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22)), ..
+			a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22), ..
+			-(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12)), ..
+			a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12), ..
+			a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21), ..
+			-(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21)), ..
+			a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11), ..
+			-(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11)), ..
+			-(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21)), ..
+			a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21), ..
+			-(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11)), ..
+			a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11))
+	End Method
+	
+	Rem
+	bbdoc: Multiplies the matrix by @z by its components, returning a new matrix.
+	End Rem
+	Method CompMul:SMat4I(z:SMat4I Var)
+		Return New SMat4I(a * z.a, b * z.b, c * z.c, d * z.d, ..
+			e * z.e, f * z.f, g * z.g, h * z.h, ..
+			i * z.i, j * z.j, k * z.k, l * z.l, ..
+			m * z.m, n * z.n, o * z.o, p * z.p)
+	End Method
+	
+	Rem
+	bbdoc: Returns the determinant of the matrix.
+	End Rem
+	Method Determinant:Int()
+		Local a00:Int = a
+		Local a01:Int = b
+		Local a02:Int = c
+		Local a03:Int = d
+		Local a10:Int = e
+		Local a11:Int = f
+		Local a12:Int = g
+		Local a13:Int = h
+		Local a20:Int = i
+		Local a21:Int = j
+		Local a22:Int = k
+		Local a23:Int = l
+		Local a30:Int = m
+		Local a31:Int = n
+		Local a32:Int = o
+		Local a33:Int = p
+		Local b00:Int = a00 * a11 - a01 * a10
+		Local b01:Int = a00 * a12 - a02 * a10
+		Local b02:Int = a00 * a13 - a03 * a10
+		Local b03:Int = a01 * a12 - a02 * a11
+		Local b04:Int = a01 * a13 - a03 * a11
+		Local b05:Int = a02 * a13 - a03 * a12
+		Local b06:Int = a20 * a31 - a21 * a30
+		Local b07:Int = a20 * a32 - a22 * a30
+		Local b08:Int = a20 * a33 - a23 * a30
+		Local b09:Int = a21 * a32 - a22 * a31
+		Local b10:Int = a21 * a33 - a23 * a31
+		Local b11:Int = a22 * a33 - a23 * a32
+		Return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
+	End Method
+
+	Rem
+	bbdoc: Returns a projection matrix with a viewing frustum defined by the plane coordinates passed in.
+	End Rem
+	Function Frustum:SMat4I(l:Double, r:Double, b:Double, t:Double, n:Double, f:Double)
+		Local rl:Double = 1.0 / (r - l)
+		Local tb:Double = 1.0 / (t - b)
+		Local nf:Double = 1.0 / (n - f)
+		Return New SMat4I(Int((2.0 * n) * rl), 0, 0, 0, ..
+			0, Int((2.0 * n) * tb), 0, 0, ..
+			Int((r + l) * rl), Int((t + b) * tb), Int((f + n) * nf), -1, ..
+			0, 0, Int((2.0 * n * f) * nf), 0)
+	End Function
+	
+	Rem
+	bbdoc: The inverse of this matrix.
+	about: An inverted matrix is such that if multiplied by the original would result in identity matrix.
+	If some matrix transforms vectors in a particular way, then the inverse matrix can transform them back.
+	End Rem
+	Method Invert:SMat4I()
+		Local a00:Int = a
+		Local a01:Int = b
+		Local a02:Int = c
+		Local a03:Int = d
+		Local a10:Int = e
+		Local a11:Int = f
+		Local a12:Int = g
+		Local a13:Int = h
+		Local a20:Int = i
+		Local a21:Int = j
+		Local a22:Int = k
+		Local a23:Int = l
+		Local a30:Int = m
+		Local a31:Int = n
+		Local a32:Int = o
+		Local a33:Int = p
+		Local b00:Int = a00 * a11 - a01 * a10
+		Local b01:Int = a00 * a12 - a02 * a10
+		Local b02:Int = a00 * a13 - a03 * a10
+		Local b03:Int = a01 * a12 - a02 * a11
+		Local b04:Int = a01 * a13 - a03 * a11
+		Local b05:Int = a02 * a13 - a03 * a12
+		Local b06:Int = a20 * a31 - a21 * a30
+		Local b07:Int = a20 * a32 - a22 * a30
+		Local b08:Int = a20 * a33 - a23 * a30
+		Local b09:Int = a21 * a32 - a22 * a31
+		Local b10:Int = a21 * a33 - a23 * a31
+		Local b11:Int = a22 * a33 - a23 * a32
+		Local det:Int = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
+		If det = 0 Then
+			Return New SMat4I(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+		End If
+		det = 1 / det
+		Return New SMat4I((a11 * b11 - a12 * b10 + a13 * b09) * det, ..
+			(a02 * b10 - a01 * b11 - a03 * b09) * det, ..
+			(a31 * b05 - a32 * b04 + a33 * b03) * det, ..
+			(a22 * b04 - a21 * b05 - a23 * b03) * det, ..
+			(a12 * b08 - a10 * b11 - a13 * b07) * det, ..
+			(a00 * b11 - a02 * b08 + a03 * b07) * det, ..
+			(a32 * b02 - a30 * b05 - a33 * b01) * det, ..
+			(a20 * b05 - a22 * b02 + a23 * b01) * det, ..
+			(a10 * b10 - a11 * b08 + a13 * b06) * det, ..
+			(a01 * b08 - a00 * b10 - a03 * b06) * det, ..
+			(a30 * b04 - a31 * b02 + a33 * b00) * det, ..
+			(a21 * b02 - a20 * b04 - a23 * b00) * det, ..
+			(a11 * b07 - a10 * b09 - a12 * b06) * det, ..
+			(a00 * b09 - a01 * b07 + a02 * b06) * det, ..
+			(a31 * b01 - a30 * b03 - a32 * b00) * det, ..
+			(a20 * b03 - a21 * b01 + a22 * b00) * det)
+	End Method
+	
+	Rem
+	bbdoc: Computes a transformation matrix that corresponds to a camera viewing the @eye from the @pos.
+	about: The right-hand vector is perpendicular to the up vector.
+	End Rem
+	Function LookAt:SMat4I(eye:SVec3I, pos:SVec3I, up:SVec3I)
+		Local ex:Int = eye.x
+		Local ey:Int = eye.y
+		Local ez:Int = eye.z
+		Local px:Int = pos.x
+		Local py:Int = pos.y
+		Local pz:Int = pos.z
+		Local ux:Int = up.x
+		Local uy:Int = up.y
+		Local uz:Int = up.z
+		Local z0:Int = ex - px
+		Local z1:Int = ey - py
+		Local z2:Int = ez - pz
+		
+		If z0 = 0 Or z1 = 0 Or z2 = 0 Then
+			Return Identity()
+		End If
+		
+		Local length:Int = Sqr(z0 * z0 + z1 * z1 + z2 * z2)
+		z0 :* length
+		z1 :* length
+		z2 :* length
+		
+		Local x0:Int = uy * z2 - uz * z1
+		Local x1:Int = uz * z0 - ux * z2
+		Local x2:Int = ux * z1 - uy * z0
+		
+		length = Sqr(x0 * x0 + x1 * x1 + x2 * x2)
+		
+		If length = 0 Then
+			x0 = 0
+			x1 = 0
+			x2 = 0
+		Else
+			length = 1 / length
+			x0 :* length
+			x1 :* length
+			x2 :* length
+		End If
+		
+		Local y0:Int = z1 * x2 - z2 * x1
+		Local y1:Int = z2 * x0 - z0 * x2
+		Local y2:Int = z0 * x1 - z1 * x0
+		
+		length = Sqr(y0 * y0 + y1 * y1 + y2 * y2)
+		If length = 0 Then
+			y0 = 0
+			y1 = 0
+			y2 = 0
+		Else
+			length = 1 / length
+			y0 :* length
+			y1 :* length
+			y2 :* length
+		End If
+		
+		Return New SMat4I(x0, y0, z0, 0, x1, y1, z1, 0, x2, y2, z2, 0, ..
+			-(x0 * ex + x1 * ey + x2 * ez), -(y0 * ex + y1 * ey + y2 * ez), -(z0 * ex + z1 * ey + z2 * ez), 1)
+	End Function
+	
+	Rem
+	bbdoc: Creates an orthogonal projection matrix.
+	about: The returned matrix, when used as a Camera's projection matrix, creates a view showing the area between @width and @height, with @zNear and @zFar as the near and far depth clipping planes.
+	End Rem
+	Function Orthogonal:SMat4I(width:Double, height:Double, zNear:Double, zFar:Double)
+		Local nf:Double = 1.0 / (zNear - zFar)
+		Return New SMat4I(Int(2.0 / width), 0, 0, 0, ..
+			0, Int(2.0 / height), 0, 0, ..
+			0, 0, Int(2.0 * nf), 0, ..
+			0, 0, Int((zNear + zFar) * nf), 1)
+	End Function
+	
+	Rem
+	bbdoc: Creates a Perspective projection matrix.
+	End Rem
+	Function Perspective:SMat4I(fov:Double, w:Double, h:Double, n:Double, f:Double)
+		Local ft:Double = 1.0 / Tan(fov * 0.5)
+		Local nf:Double = 1.0 / (n - f)
+		Return New SMat4I(Int(ft), 0, 0, 0, ..
+			0, Int(ft * w / h), 0, 0, ..
+			0, 0, Int((f + n) * nf), -1, ..
+			0, 0, Int((2.0 * f * n) * nf), 0) 
+	End Function
+	
+	Rem
+	bbdoc: Creates a rotation matrix, rotated @angle degrees around the point @axis.
+	End Rem
+	Method Rotate:SMat4I(axis:SVec3I, angle:Double)
+		Local x:Int = axis.x
+		Local y:Int = axis.y
+		Local z:Int = axis.z
+		Local a00:Int = a
+		Local a01:Int = b
+		Local a02:Int = c
+		Local a03:Int = d
+		Local a10:Int = e
+		Local a11:Int = f
+		Local a12:Int = g
+		Local a13:Int = h
+		Local a20:Int = i
+		Local a21:Int = j
+		Local a22:Int = k
+		Local a23:Int = l
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Local t:Double = 1 - ca
+		Local b00:Double = x * x * t + ca
+		Local b01:Double = y * x * t + z * sa
+		Local b02:Double = z * x * t - y * sa
+		Local b10:Double = x * y * t - z * sa
+		Local b11:Double = y * y * t + ca
+		Local b12:Double = z * y * t + x * sa
+		Local b20:Double = x * z * t + y * sa
+		Local b21:Double = y * z * t - x * sa
+		Local b22:Double = z * z * t + ca
+		Return New SMat4I(Int(a00 * b00 + a10 * b01 + a20 * b02), ..
+			Int(a01 * b00 + a11 * b01 + a21 * b02), ..
+			Int(a02 * b00 + a12 * b01 + a22 * b02), ..
+			Int(a03 * b00 + a13 * b01 + a23 * b02), ..
+			Int(a00 * b10 + a10 * b11 + a20 * b12), ..
+			Int(a01 * b10 + a11 * b11 + a21 * b12), ..
+			Int(a02 * b10 + a12 * b11 + a22 * b12), ..
+			Int(a03 * b10 + a13 * b11 + a23 * b12), ..
+			Int(a00 * b20 + a10 * b21 + a20 * b22), ..
+			Int(a01 * b20 + a11 * b21 + a21 * b22), ..
+			Int(a02 * b20 + a12 * b21 + a22 * b22), ..
+			Int(a03 * b20 + a13 * b21 + a23 * b22), ..
+			m, n, o, p)
+	End Method
+	
+	Rem
+	bbdoc: Returns a rotation matrix on the given @axis and @angle degrees.
+	End Rem
+	Function Rotation:SMat4I(axis:SVec3I, angle:Double)
+		Local x:Int = axis.x
+		Local y:Int = axis.y
+		Local z:Int = axis.z
+		Local sa:Double = Sin(angle)
+		Local ca:Double = Cos(angle)
+		Local t:Double = 1 - ca
+		Return New SMat4I(Int(x * x * t + ca), ..
+			Int(y * x * t + z * sa), ..
+			Int(z * x * t - y * sa), ..
+			0, ..
+			Int(x * y * t - z * sa), ..
+			Int(y * y * t + ca), ..
+			Int(z * y * t + x * sa), ..
+			0, ..
+			Int(x * z * t + y * sa), ..
+			Int(y * z * t - x * sa), ..
+			Int(z * z * t + ca), ..
+			0, 0, 0, 0, 1)
+	End Function
+	
+	Rem
+	bbdoc: Scales the matrix, return the new scaled matrix.
+	End Rem
+	Method Scale:SMat4I(s:SVec3I)
+		Local bx:Int = s.x
+		Local by:Int = s.y
+		Local bz:Int = s.z
+		Return New SMat4I(a * bx, b * bx, c * bx, d * bx, ..
+			e * by, f * by, g * by, h * by, ..
+			i * bz, j * bz, k * bz, l * bz, ..
+			m, n, o, p)
+	End Method
+
+	Rem
+	bbdoc: Scales the matrix, return the new scaled matrix.
+	End Rem
+	Method Scale:SMat4I(s:SVec3D)
+		Local bx:Double = s.x
+		Local by:Double = s.y
+		Local bz:Double = s.z
+		Return New SMat4I(Int(a * bx), Int(b * bx), Int(c * bx), Int(d * bx), ..
+			Int(e * by), Int(f * by), Int(g * by), Int(h * by), ..
+			Int(i * bz), Int(j * bz), Int(k * bz), Int(l * bz), ..
+			m, n, o, p)
+	End Method
+
+	Rem
+	bbdoc: Scales the matrix, return the new scaled matrix.
+	End Rem
+	Method Scale:SMat4I(s:SVec3F)
+		Local bx:Float = s.x
+		Local by:Float = s.y
+		Local bz:Float = s.z
+		Return New SMat4I(Int(a * bx), Int(b * bx), Int(c * bx), Int(d * bx), ..
+			Int(e * by), Int(f * by), Int(g * by), Int(h * by), ..
+			Int(i * bz), Int(j * bz), Int(k * bz), Int(l * bz), ..
+			m, n, o, p)
+	End Method
+	
+	Rem
+	bbdoc: Creates a scaling matrix.
+	End Rem
+	Function Scaling:SMat4I(s:SVec3I)
+		Return New SMat4I(s.x, 0, 0, 0, 0, s.y, 0, 0, 0, 0, s.z, 0, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Creates a scaling matrix.
+	End Rem
+	Function Scaling:SMat4I(s:SVec3D)
+		Return New SMat4I(Int(s.x), 0, 0, 0, 0, Int(s.y), 0, 0, 0, 0, Int(s.z), 0, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Creates a scaling matrix.
+	End Rem
+	Function Scaling:SMat4I(s:SVec3F)
+		Return New SMat4I(Int(s.x), 0, 0, 0, 0, Int(s.y), 0, 0, 0, 0, Int(s.z), 0, 0, 0, 0, 1)
+	End Function
+
+	Rem
+	bbdoc: Returns the transpose of this matrix.
+	about: The transposed matrix is the one that has the columns exchanged with its rows.
+	End Rem
+	Method Transpose:SMat4I()
+		Return New SMat4I(a, e, i, m, b, f, j, n, c, g, k, o, d, h, l, p)
+	End Method
+	
+	Rem
+	bbdoc: Translates the matrix to @s.
+	End Rem
+	Method Translate:SMat4I(s:SVec3I)
+		Local bx:Int = s.x
+		Local by:Int = s.y
+		Local bz:Int = s.z
+		Return New SMat4I(a, b, c, d, e, f, g, h, i, j, k, l, ..
+			a * bx + e * by + i * bz + m, ..
+			b * bx + f * by + j * bz + n, ..
+			c * bx + g * by + k * bz + o, ..
+			d * bx + h * by + l * bz + p)
+	End Method
+
+	Rem
+	bbdoc: Translates the matrix to @s.
+	End Rem
+	Method Translate:SMat4I(s:SVec3D)
+		Local bx:Double = s.x
+		Local by:Double = s.y
+		Local bz:Double = s.z
+		Return New SMat4I(a, b, c, d, e, f, g, h, i, j, k, l, ..
+			Int(a * bx + e * by + i * bz + m), ..
+			Int(b * bx + f * by + j * bz + n), ..
+			Int(c * bx + g * by + k * bz + o), ..
+			Int(d * bx + h * by + l * bz + p))
+	End Method
+
+	Rem
+	bbdoc: Translates the matrix To @s.
+	End Rem
+	Method Translate:SMat4I(s:SVec3F)
+		Local bx:Float = s.x
+		Local by:Float = s.y
+		Local bz:Float = s.z
+		Return New SMat4I(a, b, c, d, e, f, g, h, i, j, k, l, ..
+			Int(a * bx + e * by + i * bz + m), ..
+			Int(b * bx + f * by + j * bz + n), ..
+			Int(c * bx + g * by + k * bz + o), ..
+			Int(d * bx + h * by + l * bz + p))
+	End Method
+
+	Rem
+	bbdoc: Creates a translation matrix.
+	End Rem
+	Function Translation:SMat4I(s:SVec3I)
+		Return New SMat4I(1, 0, 0, 0, ..
+			0, 1, 0, 0, ..
+			0, 0, 1, 0, ..
+			s.x, s.y, s.z, 1)
+	End Function
+
+	Rem
+	bbdoc: Creates a translation matrix.
+	End Rem
+	Function Translation:SMat4I(s:SVec3D)
+		Return New SMat4I(1, 0, 0, 0, ..
+			0, 1, 0, 0, ..
+			0, 0, 1, 0, ..
+			Int(s.x), Int(s.y), Int(s.z), 1)
+	End Function
+
+	Rem
+	bbdoc: Creates a translation matrix.
+	End Rem
+	Function Translation:SMat4I(s:SVec3F)
+		Return New SMat4I(1, 0, 0, 0, ..
+			0, 1, 0, 0, ..
+			0, 0, 1, 0, ..
+			Int(s.x), Int(s.y), Int(s.z), 1)
+	End Function
+	
+	Rem
+	bbdoc: Returns a #String representation of the matrix.
+	End Rem
+	Method ToString:String() Override
+		Local sb:TStringBuilder = New TStringBuilder
+		
+		sb.Append(a).Append(", ").Append(e).Append(", ").Append(i).Append(", ").Append(m).Append(",~n")
+		sb.Append(b).Append(", ").Append(f).Append(", ").Append(j).Append(", ").Append(n).Append(",~n")
+		sb.Append(c).Append(", ").Append(g).Append(", ").Append(k).Append(", ").Append(o).Append(",~n")
+		sb.Append(d).Append(", ").Append(h).Append(", ").Append(l).Append(", ").Append(p)
+		
+		Return sb.ToString()
+	End Method
+	
+End Struct

+ 1 - 0
polygon.mod/clipper2/CPP/.gitignore

@@ -0,0 +1 @@
+build*

+ 203 - 0
polygon.mod/clipper2/CPP/CMakeLists.txt

@@ -0,0 +1,203 @@
+cmake_minimum_required(VERSION 3.10)
+project(Clipper2 VERSION 1.0.6 LANGUAGES C CXX)
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+
+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(BUILD_SHARED_LIBS "Build shared libs" OFF)
+
+include(GNUInstallDirs)
+
+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
+)
+
+set(CLIPPER2_SRC
+  Clipper2Lib/src/clipper.engine.cpp
+  Clipper2Lib/src/clipper.offset.cpp
+  Clipper2Lib/src/clipper.rectclip.cpp
+)
+
+set(PCFILE "${CMAKE_CURRENT_BINARY_DIR}/Clipper2.pc")
+set(PCFILEZ "${CMAKE_CURRENT_BINARY_DIR}/Clipper2Z.pc")
+
+# 2d version of Clipper2
+add_library(Clipper2 ${CLIPPER2_INC} ${CLIPPER2_SRC})
+
+target_include_directories(Clipper2
+  PUBLIC Clipper2Lib/include
+)
+
+# Clipper2 but with USINGZ defined
+add_library(Clipper2Z ${CLIPPER2_INC} ${CLIPPER2_SRC})
+
+target_compile_definitions(Clipper2Z PUBLIC USINGZ)
+
+target_include_directories(Clipper2Z
+  PUBLIC Clipper2Lib/include
+)
+
+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)
+
+  target_compile_options(Clipper2Z PRIVATE -Wall -Wextra -Wpedantic -Werror)
+  target_link_libraries(Clipper2Z PUBLIC -lm)
+endif()
+
+set_target_properties(Clipper2 Clipper2Z PROPERTIES FOLDER Libraries
+                                         VERSION ${PROJECT_VERSION}
+                                         SOVERSION ${PROJECT_VERSION_MAJOR}
+                                         PUBLIC_HEADER "${CLIPPER2_INC}"
+)
+
+if(CLIPPER2_UTILS OR CLIPPER2_TESTS OR CLIPPER2_EXAMPLES)
+  set(CLIPPER2_UTILS_INC
+    Utils/clipper.svg.h
+    Utils/ClipFileLoad.h
+    Utils/ClipFileSave.h
+  )
+  set(CLIPPER2_UTILS_SRC
+    Utils/clipper.svg.cpp
+    Utils/ClipFileLoad.cpp
+    Utils/ClipFileSave.cpp
+  )
+
+  add_library(Clipper2utils STATIC ${CLIPPER2_UTILS_INC} ${CLIPPER2_UTILS_SRC})
+
+  target_link_libraries(Clipper2utils PUBLIC Clipper2)
+  target_include_directories(Clipper2utils
+    PUBLIC Utils
+  )
+
+  add_library(Clipper2Zutils STATIC ${CLIPPER2_UTILS_INC} ${CLIPPER2_UTILS_SRC})
+
+  target_link_libraries(Clipper2Zutils PUBLIC Clipper2Z)
+  target_include_directories(Clipper2Zutils
+    PUBLIC Utils
+  )
+
+  set_target_properties(Clipper2utils Clipper2Zutils 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)
+  endif()
+endif()
+
+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)
+
+  add_executable(RectClipDemo1 Examples/RectClipDemo/RectClipDemo1.cpp)
+  target_link_libraries(RectClipDemo1 PRIVATE Clipper2 Clipper2utils)
+
+  add_executable(UsingZ1 Examples/UsingZ/UsingZ1.cpp)
+  target_link_libraries(UsingZ1 PRIVATE Clipper2Z Clipper2Zutils)
+
+  set_target_properties(ConsoleDemo1 ConsoleDemo2 InflateDemo1 RectClipDemo1 UsingZ1 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)
+  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)
+  endif()
+  set(BUILD_GMOCK OFF)
+if(USE_EXTERNAL_GTEST)
+  find_package(GTest REQUIRED)
+else()
+  include(GoogleTest)
+
+  add_subdirectory("${PROJECT_SOURCE_DIR}/Tests/googletest/")
+  set_target_properties(gtest gtest_main PROPERTIES FOLDER GTest)
+endif()
+  set(ClipperTests_SRC
+    Tests/TestLines.cpp
+    Tests/TestOffsetOrientation.cpp
+    Tests/TestOrientation.cpp
+    Tests/TestPolygons.cpp
+    Tests/TestPolytreeHoles1.cpp
+    Tests/TestPolytreeHoles2.cpp
+    Tests/TestPolytreeIntersection.cpp
+    Tests/TestPolytreeUnion.cpp
+    Tests/TestRandomPaths.cpp
+    Tests/TestRectClip.cpp
+    Tests/TestTrimCollinear.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)
+
+  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)
+  endif()
+
+  set_target_properties(ClipperTests ClipperTestsZ PROPERTIES FOLDER Tests)
+
+  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}"
+  )
+
+  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"
+  )
+
+  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 )
+endif()
+
+CONFIGURE_FILE(Clipper2.pc.cmakein "${PCFILE}" @ONLY)
+set(PCFILE_LIB_SUFFIX "Z")
+CONFIGURE_FILE(Clipper2.pc.cmakein "${PCFILEZ}" @ONLY)
+
+install(TARGETS Clipper2 Clipper2Z
+        PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/clipper2
+)
+install(FILES ${PCFILE} ${PCFILEZ} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

+ 12 - 0
polygon.mod/clipper2/CPP/Clipper2.pc.cmakein

@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+
+Name: Clipper2@PCFILE_LIB_SUFFIX@
+Description: A Polygon Clipping and Offsetting library in C++
+Version: @PROJECT_VERSION@
+URL: https://github.com/AngusJohnson/Clipper2
+Requires:
+Libs: -L${libdir} -lClipper2@PCFILE_LIB_SUFFIX@
+Cflags: -I${includedir}

+ 701 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.core.h

@@ -0,0 +1,701 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  21 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Core Clipper Library structures and functions                   *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef CLIPPER_CORE_H
+#define CLIPPER_CORE_H
+
+#include <cstdlib>
+#include <cmath>
+#include <vector>
+#include <string>
+#include <iostream>
+#include <algorithm>
+#include <climits>
+
+namespace Clipper2Lib
+{
+#ifdef __cpp_exceptions
+  static const char* precision_error =
+    "Precision exceeds the permitted range";
+#endif
+
+  static const double PI = 3.141592653589793238;
+  static const int64_t MAX_COORD = INT64_MAX >> 2;
+  static const int64_t MIN_COORD = -MAX_COORD;
+  static const int64_t INVALID = INT64_MAX;
+
+  //By far the most widely used filling rules for polygons are EvenOdd
+  //and NonZero, sometimes called Alternate and Winding respectively.
+  //https://en.wikipedia.org/wiki/Nonzero-rule
+  enum class FillRule { EvenOdd, NonZero, Positive, Negative };
+
+  // Point ------------------------------------------------------------------------
+
+  template <typename T>
+  struct Point {
+    T x;
+    T y;
+#ifdef USINGZ
+    int64_t z;
+
+    template <typename T2>
+    inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0)
+    {
+      if constexpr (std::numeric_limits<T>::is_integer &&
+        !std::numeric_limits<T2>::is_integer)
+      {
+        x = static_cast<T>(std::round(x_));
+        y = static_cast<T>(std::round(y_));
+        z = z_;
+      }
+      else
+      {
+        x = static_cast<T>(x_);
+        y = static_cast<T>(y_);
+        z = z_;
+      }
+    }
+
+    explicit Point() : x(0), y(0), z(0) {};
+
+    template <typename T2>
+    Point(const T2 x_, const T2 y_, const int64_t z_ = 0)
+    {
+      Init(x_, y_);
+      z = z_;
+    }
+
+    template <typename T2>
+    explicit Point<T>(const Point<T2>& p)
+    {
+      Init(p.x, p.y, p.z);
+    }
+
+    Point operator * (const double scale) const
+    {
+      return Point(x * scale, y * scale, z);
+    }
+
+
+    friend std::ostream& operator<<(std::ostream& os, const Point& point)
+    {
+      os << point.x << " " << point.y << " " << point.z;
+      return os;
+    }
+
+#else
+
+    template <typename T2>
+    inline void Init(const T2 x_ = 0, const T2 y_ = 0)
+    {
+      if constexpr (std::numeric_limits<T>::is_integer &&
+        !std::numeric_limits<T2>::is_integer)
+      {
+        x = static_cast<T>(std::round(x_));
+        y = static_cast<T>(std::round(y_));
+      }
+      else
+      {
+        x = static_cast<T>(x_);
+        y = static_cast<T>(y_);
+      }
+    }
+
+    explicit Point() : x(0), y(0) {};
+
+    template <typename T2>
+    Point(const T2 x_, const T2 y_) { Init(x_, y_); }
+
+    template <typename T2>
+    explicit Point<T>(const Point<T2>& p) { Init(p.x, p.y); }
+
+    Point operator * (const double scale) const
+    {
+      return Point(x * scale, y * scale);
+    }
+
+    friend std::ostream& operator<<(std::ostream& os, const Point& point)
+    {
+      os << point.x << " " << point.y;
+      return os;
+    }
+#endif
+
+    friend bool operator==(const Point& a, const Point& b)
+    {
+      return a.x == b.x && a.y == b.y;
+    }
+
+    friend bool operator!=(const Point& a, const Point& b)
+    {
+      return !(a == b);
+    }
+
+    inline Point<T> operator-() const
+    {
+      return Point<T>(-x, -y);
+    }
+
+    inline Point operator+(const Point& b) const
+    {
+      return Point(x + b.x, y + b.y);
+    }
+
+    inline Point operator-(const Point& b) const
+    {
+      return Point(x - b.x, y - b.y);
+    }
+
+    inline void Negate() { x = -x; y = -y; }
+
+  };
+
+  //nb: using 'using' here (instead of typedef) as they can be used in templates
+  using Point64 = Point<int64_t>;
+  using PointD = Point<double>;
+
+  template <typename T>
+  using Path = std::vector<Point<T>>;
+  template <typename T>
+  using Paths = std::vector<Path<T>>;
+
+  using Path64 = Path<int64_t>;
+  using PathD = Path<double>;
+  using Paths64 = std::vector< Path64>;
+  using PathsD = std::vector< PathD>;
+
+  template <typename T>
+  std::ostream& operator << (std::ostream& outstream, const Path<T>& path)
+  {
+    if (!path.empty())
+    {
+      auto pt = path.cbegin(), last = path.cend() - 1;
+      while (pt != last)
+        outstream << *pt++ << ", ";
+      outstream << *last << std::endl;
+    }
+    return outstream;
+  }
+
+  template <typename T>
+  std::ostream& operator << (std::ostream& outstream, const Paths<T>& paths)
+  {
+    for (auto p : paths)
+      outstream << p;
+    return outstream;
+  }
+
+  template <typename T1, typename T2>
+  inline Path<T1> ScalePath(const Path<T2>& path, double scale_x, double scale_y)
+  {
+    Path<T1> result;
+    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));
+#else
+    for (const Point<T2>& pt : path)
+      result.push_back(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)
+  {
+    return ScalePath<T1, T2>(path, scale, scale);
+  }
+
+  template <typename T1, typename T2>
+  inline Paths<T1> ScalePaths(const Paths<T2>& paths, double scale_x, double scale_y)
+  {
+    Paths<T1> result;
+    result.reserve(paths.size());
+    for (const Path<T2>& path : paths)
+      result.push_back(ScalePath<T1, T2>(path, scale_x, scale_y));
+    return result;
+  }
+
+  template <typename T1, typename T2>
+  inline Paths<T1> ScalePaths(const Paths<T2>& paths, double scale)
+  {
+    return ScalePaths<T1, T2>(paths, scale, scale);
+  }
+
+  template <typename T1, typename T2>
+  inline Path<T1> TransformPath(const Path<T2>& path)
+  {
+    Path<T1> result;
+    result.reserve(path.size());
+    std::transform(path.cbegin(), path.cend(), std::back_inserter(result),
+      [](const Point<T2>& pt) {return Point<T1>(pt); });
+    return result;
+  }
+
+  template <typename T1, typename T2>
+  inline Paths<T1> TransformPaths(const Paths<T2>& paths)
+  {
+    Paths<T1> result;
+    std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result),
+      [](const Path<T2>& path) {return TransformPath<T1, T2>(path); });
+    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)
+  {
+    return static_cast<double>(val) * static_cast<double>(val);
+  }
+
+  template<typename T>
+  inline bool NearEqual(const Point<T>& p1,
+    const Point<T>& p2, double max_dist_sqrd)
+  {
+    return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd;
+  }
+
+  template<typename T>
+  inline Path<T> StripNearEqual(const Path<T>& path,
+    double max_dist_sqrd, 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 (!NearEqual(*path_iter, last_pt, max_dist_sqrd))
+      {
+        last_pt = *path_iter;
+        result.push_back(last_pt);
+      }
+    }
+    if (!is_closed_path) return result;
+    while (result.size() > 1 &&
+      NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back();
+    return result;
+  }
+
+  template<typename T>
+  inline Paths<T> StripNearEqual(const Paths<T>& paths,
+    double max_dist_sqrd, 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)
+    {
+      result.push_back(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path));
+    }
+    return result;
+  }
+
+  template<typename T>
+  inline Path<T> StripDuplicates(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;
+  }
+
+  template<typename T>
+  inline Paths<T> StripDuplicates(const 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)
+    {
+      result.push_back(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;
+    }
+  };
+
+  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;
+  }
+
+  // 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)
+  {
+    if (precision >= -8 && precision <= 8) return;
+#ifdef __cpp_exceptions
+    throw Clipper2Exception(precision_error);
+#else
+    precision = precision > 8 ? 8 : -8;
+#endif
+  }
+
+  template <typename T>
+  inline double CrossProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
+    return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.y -
+      pt2.y) - static_cast<double>(pt2.y - pt1.y) * static_cast<double>(pt3.x - pt2.x));
+  }
+
+  template <typename T>
+  inline double CrossProduct(const Point<T>& vec1, const Point<T>& vec2)
+  {
+    return static_cast<double>(vec1.y * vec2.x) - static_cast<double>(vec2.y * vec1.x);
+  }
+
+  template <typename T>
+  inline double DotProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
+    return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.x - pt2.x) +
+      static_cast<double>(pt2.y - pt1.y) * static_cast<double>(pt3.y - pt2.y));
+  }
+
+  template <typename T>
+  inline double DotProduct(const Point<T>& vec1, const Point<T>& vec2)
+  {
+    return static_cast<double>(vec1.x * vec2.x) + static_cast<double>(vec1.y * vec2.y);
+  }
+
+  template <typename T>
+  inline double DistanceSqr(const Point<T> pt1, const Point<T> pt2)
+  {
+    return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y);
+  }
+
+  template <typename T>
+  inline double DistanceFromLineSqrd(const Point<T>& pt, const Point<T>& ln1, const Point<T>& ln2)
+  {
+    //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
+    //see http://en.wikipedia.org/wiki/Perpendicular_distance
+    double A = static_cast<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);
+  }
+
+  template <typename T>
+  inline double Area(const Path<T>& path)
+  {
+    size_t cnt = path.size();
+    if (cnt < 3) return 0.0;
+    double a = 0.0;
+    typename Path<T>::const_iterator it1, it2 = path.cend() - 1, stop = it2;
+    if (!(cnt & 1)) ++stop;
+    for (it1 = path.cbegin(); it1 != stop;)
+    {
+      a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
+      it2 = it1 + 1;
+      a += static_cast<double>(it1->y + it2->y) * (it1->x - it2->x);
+      it1 += 2;
+    }
+    if (cnt & 1)
+      a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
+    return a * 0.5;
+  }
+
+  template <typename T>
+  inline double Area(const Paths<T>& paths)
+  {
+    double a = 0.0;
+    for (typename Paths<T>::const_iterator paths_iter = paths.cbegin();
+      paths_iter != paths.cend(); ++paths_iter)
+    {
+      a += Area<T>(*paths_iter);
+    }
+    return a;
+  }
+
+  template <typename T>
+  inline bool IsPositive(const Path<T>& poly)
+  {
+    // A curve has positive orientation [and area] if a region 'R' 
+    // is on the left when traveling around the outside of 'R'.
+    //https://mathworld.wolfram.com/CurveOrientation.html
+    //nb: This statement is premised on using Cartesian coordinates
+    return Area<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 ((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);
+  }
+
+  inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
+    const Point64& seg2a, const Point64& seg2b, bool inclusive = false)
+  {
+    if (inclusive)
+    {
+      double res1 = CrossProduct(seg1a, seg2a, seg2b);
+      double res2 = CrossProduct(seg1b, seg2a, seg2b);
+      if (res1 * res2 > 0) return false;
+      double res3 = CrossProduct(seg2a, seg1a, seg1b);
+      double res4 = CrossProduct(seg2b, seg1a, seg1b);
+      if (res3 * res4 > 0) return false;
+      return (res1 || res2 || res3 || res4); // ensures not collinear
+    }
+    else {
+      return (CrossProduct(seg1a, seg2a, seg2b) *
+        CrossProduct(seg1b, seg2a, seg2b) < 0) &&
+        (CrossProduct(seg2a, seg1a, seg1b) *
+          CrossProduct(seg2b, seg1a, seg1b) < 0);
+    }
+  }
+
+  inline Point64 GetClosestPointOnSegment(const Point64& offPt,
+    const Point64& seg1, const Point64& seg2)
+  {
+    if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1;
+    double dx = static_cast<double>(seg2.x - seg1.x);
+    double dy = static_cast<double>(seg2.y - seg1.y);
+    double q =
+      (static_cast<double>(offPt.x - seg1.x) * dx +
+        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)));
+  }
+
+  enum class PointInPolygonResult { IsOn, IsInside, IsOutside };
+
+  template <typename T>
+  inline PointInPolygonResult PointInPolygon(const Point<T>& pt, const Path<T>& polygon)
+  {
+    if (polygon.size() < 3)
+      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;
+
+    while (pit->y == pt.y)
+    {
+      if (pit == start) return PointInPolygonResult::IsOutside;
+      --pit;
+    }
+    bool is_above = pit->y < pt.y;
+
+    while (cit != cend)
+    {
+      if (is_above)
+      {
+        while (cit != cend && cit->y < pt.y) ++cit;
+        if (cit == cend) break;
+      }
+      else
+      {
+        while (cit != cend && cit->y > pt.y) ++cit;
+        if (cit == cend) break;
+      }
+
+      if (cit == start) pit = cend - 1;
+      else  pit = cit - 1;
+
+      if (cit->y == pt.y)
+      {
+        if (cit->x == pt.x || (cit->y == pit->y &&
+          ((pt.x < pit->x) != (pt.x < cit->x))))
+          return PointInPolygonResult::IsOn;
+        ++cit;
+        continue;
+      }
+
+      if (pt.x < cit->x && pt.x < pit->x)
+      {
+        // we're only interested in edges crossing on the left
+      }
+      else if (pt.x > pit->x && pt.x > cit->x)
+        val = 1 - val; // toggle val
+      else
+      {
+        double d = CrossProduct(*pit, *cit, pt);
+        if (d == 0) return PointInPolygonResult::IsOn;
+        if ((d < 0) == is_above) val = 1 - val;
+      }
+      is_above = !is_above;
+      ++cit;
+    }
+    return (val == 0) ?
+      PointInPolygonResult::IsOutside :
+      PointInPolygonResult::IsInside;
+  }
+
+}  // namespace
+
+#endif  // CLIPPER_CORE_H

+ 588 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.engine.h

@@ -0,0 +1,588 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  19 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  This is the main polygon clipping module                        *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef CLIPPER_ENGINE_H
+#define CLIPPER_ENGINE_H
+
+constexpr auto CLIPPER2_VERSION = "1.0.6";
+
+#include <cstdlib>
+#include <queue>
+#include <stdexcept>
+#include <vector>
+#include <functional>
+#include <memory>
+#include "clipper.core.h"
+
+namespace Clipper2Lib {
+
+	struct Scanline;
+	struct IntersectNode;
+	struct Active;
+	struct Vertex;
+	struct LocalMinima;
+	struct OutRec;
+	struct Joiner;
+
+	//Note: all clipping operations except for Difference are commutative.
+	enum class ClipType { None, Intersection, Union, Difference, Xor };
+	
+	enum class PathType { Subject, Clip };
+
+	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) 
+	{
+		return (enum VertexFlags)(uint32_t(a) & uint32_t(b));
+	}
+
+	constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b) 
+	{
+		return (enum VertexFlags)(uint32_t(a) | uint32_t(b));
+	}
+
+	struct Vertex {
+		Point64 pt;
+		Vertex* next = nullptr;
+		Vertex* prev = nullptr;
+		VertexFlags flags = VertexFlags::None;
+	};
+
+	struct OutPt {
+		Point64 pt;
+		OutPt*	next = nullptr;
+		OutPt*	prev = nullptr;
+		OutRec* outrec;
+		Joiner* joiner = nullptr;
+
+		OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
+			next = this;
+			prev = this;
+		}
+	};
+
+	class PolyPath;
+	class PolyPath64;
+	class PolyPathD;
+	using PolyTree64 = PolyPath64;
+	using PolyTreeD = PolyPathD;
+
+	struct OutRec;
+	typedef std::vector<OutRec*> OutRecList;
+
+	//OutRec: contains a path in the clipping solution. Edges in the AEL will
+	//have OutRec pointers assigned when they form part of the clipping solution.
+	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;
+		Rect64 bounds = {};
+		Path64 path;
+		bool is_open = false;
+		~OutRec() { if (splits) delete splits; };
+	};
+
+	///////////////////////////////////////////////////////////////////
+	//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;
+		int64_t curr_x = 0;		//current (updated at every new scanline)
+		double dx = 0.0;
+		int wind_dx = 1;			//1 or -1 depending on winding direction
+		int wind_cnt = 0;
+		int wind_cnt2 = 0;		//winding count of the opposite polytype
+		OutRec* outrec = nullptr;
+		//AEL: 'active edge list' (Vatti's AET - active edge table)
+		//     a linked list of all edges (from left to right) that are present
+		//     (or 'active') within the current scanbeam (a horizontal 'beam' that
+		//     sweeps from bottom to top over the paths in the clipping operation).
+		Active* prev_in_ael = nullptr;
+		Active* next_in_ael = nullptr;
+		//SEL: 'sorted edge list' (Vatti's ST - sorted table)
+		//     linked list used when sorting edges into their new positions at the
+		//     top of scanbeams, but also (re)used to process horizontals.
+		Active* prev_in_sel = nullptr;
+		Active* next_in_sel = nullptr;
+		Active* jump = nullptr;
+		Vertex* vertex_top = nullptr;
+		LocalMinima* local_min = nullptr;  // the bottom of an edge 'bound' (also Vatti)
+		bool is_left_bound = false;
+	};
+
+	struct LocalMinima {
+		Vertex* vertex;
+		PathType polytype;
+		bool is_open;
+		LocalMinima(Vertex* v, PathType pt, bool open) :
+			vertex(v), polytype(pt), is_open(open){}
+	};
+
+	struct IntersectNode {
+		Point64 pt;
+		Active* edge1;
+		Active* edge2;
+		IntersectNode() : pt(Point64(0, 0)), edge1(NULL), edge2(NULL) {}
+			IntersectNode(Active* e1, Active* e2, Point64& pt_) :
+			pt(pt_), edge1(e1), edge2(e2)
+		{
+		}
+	};
+
+#ifdef USINGZ
+		typedef std::function<void(const Point64& e1bot, const Point64& e1top,
+		const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64;
+
+	typedef std::function<void(const PointD& e1bot, const PointD& e1top,
+		const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
+#endif
+
+	// ClipperBase -------------------------------------------------------------
+
+	class ClipperBase {
+	private:
+		ClipType cliptype_ = ClipType::None;
+		FillRule fillrule_ = FillRule::EvenOdd;
+		FillRule fillpos = FillRule::Positive;
+		int64_t bot_y_ = 0;
+		bool minima_list_sorted_ = false;
+		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_;
+		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
+		void Reset();
+		void InsertScanline(int64_t y);
+		bool PopScanline(int64_t &y);
+		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);
+		bool IsContributingClosed(const Active &e) const;
+		inline bool IsContributingOpen(const Active &e) const;
+		void SetWindCountForClosedPathEdge(Active &edge);
+		void SetWindCountForOpenPathEdge(Active &e);
+		void InsertLocalMinimaIntoAEL(int64_t bot_y);
+		void InsertLeftEdge(Active &e);
+		inline void PushHorz(Active &e);
+		inline bool PopHorz(Active *&e);
+		inline OutPt* StartOpenPath(Active &e, const Point64& pt);
+		inline void UpdateEdgeIntoAEL(Active *e);
+		OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt);
+		inline void DeleteFromAEL(Active &e);
+		inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
+		void DoIntersections(const int64_t top_y);
+		void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y);
+		bool BuildIntersectList(const int64_t top_y);
+		void ProcessIntersectList();
+		void SwapPositionsInAEL(Active& edge1, Active& edge2);
+		OutPt* AddOutPt(const Active &e, const Point64& pt);
+		OutPt* AddLocalMinPoly(Active &e1, Active &e2, 
+			const Point64& pt, bool is_new = false);
+		OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
+		void DoHorizontal(Active &horz);
+		bool ResetHorzDirection(const Active &horz, const Active *max_pair,
+			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);
+	protected:
+		bool has_open_paths_ = false;
+		bool succeeded_ = true;
+		std::vector<OutRec*> outrec_list_; //pointers in case list memory reallocated
+		bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
+		bool DeepCheckOwner(OutRec* outrec, OutRec* owner);
+#ifdef USINGZ
+		ZCallback64 zCallback_ = nullptr;
+		void SetZ(const Active& e1, const Active& e2, Point64& pt);
+#endif
+		void CleanUp();  // unlike Clear, CleanUp preserves added paths
+		void AddPath(const Path64& path, PathType polytype, bool is_open);
+		void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
+	public:
+		virtual ~ClipperBase();
+		bool PreserveCollinear = true;
+		bool ReverseSolution = false;
+		void Clear();
+#ifdef USINGZ
+		int64_t DefaultZ = 0;
+#endif
+	};
+
+	// PolyPath / PolyTree --------------------------------------------------------
+
+	//PolyTree: is intended as a READ-ONLY data structure for CLOSED paths returned
+	//by clipping operations. While this structure is more complex than the
+	//alternative Paths structure, it does preserve path 'ownership' - ie those
+	//paths that contain (or own) other paths. This will be useful to some users.
+
+	class PolyPath {
+	protected:
+		PolyPath* parent_;
+	public:
+		PolyPath(PolyPath* parent = nullptr): parent_(parent){}
+		virtual ~PolyPath() { Clear(); };		
+		//https://en.cppreference.com/w/cpp/language/rule_of_three
+		PolyPath(const PolyPath&) = delete;
+		PolyPath& operator=(const PolyPath&) = delete;
+
+		unsigned Level() const
+		{
+			unsigned result = 0;
+			const PolyPath* p = parent_;
+			while (p) { ++result; p = p->parent_; }
+			return result;
+		}
+
+		virtual PolyPath* AddChild(const Path64& path) = 0;
+
+		virtual void Clear() {};
+		virtual size_t Count() const { return 0; }
+
+		const PolyPath* Parent() const { return parent_; }
+
+		bool IsHole() const 
+		{
+			const PolyPath* pp = parent_;
+			bool is_hole = pp;
+			while (pp) {
+				is_hole = !is_hole;
+				pp = pp->parent_;
+			}
+			return is_hole;
+		}
+	};
+
+	class PolyPath64 : public PolyPath {
+	private:
+		std::vector<std::unique_ptr<PolyPath64>> 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(); }
+
+		PolyPath64* AddChild(const Path64& path) override
+		{
+			auto p = std::make_unique<PolyPath64>(this);
+			auto* result = childs_.emplace_back(std::move(p)).get();
+			result->polygon_ = path;
+			return result;
+		}
+
+		void Clear() override
+		{
+			childs_.resize(0);
+		}
+
+		size_t Count() const  override
+		{
+			return childs_.size();
+		}
+
+		const Path64& Polygon() const { return polygon_; };
+
+		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;
+		}
+
+	};
+
+	class PolyPathD : public PolyPath {
+	private:
+		std::vector<std::unique_ptr<PolyPathD>> childs_;
+		double inv_scale_;
+		PathD polygon_;
+		typedef typename std::vector<std::unique_ptr<PolyPathD>>::const_iterator ppD_itor;
+	public:
+		PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) 
+		{
+			inv_scale_ = parent ? parent->inv_scale_ : 1.0;
+		}
+		PolyPathD* operator [] (size_t index) 
+		{ 
+			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_; }
+		PolyPathD* AddChild(const Path64& path) override
+		{
+			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_);
+			return result;
+		}
+
+		void Clear() override
+		{
+			childs_.resize(0);
+		}
+
+		size_t Count() const  override
+		{
+			return childs_.size();
+		}
+
+		const PathD& Polygon() const { return polygon_; };
+
+		double Area() const
+		{
+			double result = Clipper2Lib::Area<double>(polygon_);
+			for (const auto& child : childs_)
+				result += child->Area();
+			return result;
+		}
+	};
+
+	class Clipper64 : public ClipperBase
+	{
+	private:
+		void BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen);
+		void BuildTree64(PolyPath64& polytree, Paths64& open_paths);
+	public:
+#ifdef USINGZ
+		void SetZCallback(ZCallback64 cb) { zCallback_ = cb; }
+#endif
+
+		void AddSubject(const Paths64& subjects)
+		{
+			AddPaths(subjects, PathType::Subject, false);
+		}
+		void AddOpenSubject(const Paths64& open_subjects)
+		{
+			AddPaths(open_subjects, PathType::Subject, true);
+		}
+		void AddClip(const Paths64& clips)
+		{
+			AddPaths(clips, PathType::Clip, false);
+		}
+
+		bool Execute(ClipType clip_type,
+			FillRule fill_rule, Paths64& closed_paths)
+		{
+			Paths64 dummy;
+			return Execute(clip_type, fill_rule, closed_paths, dummy);
+		}
+
+		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);
+			CleanUp();
+			return succeeded_;
+		}
+
+		bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree)
+		{
+			Paths64 dummy;
+			return Execute(clip_type, fill_rule, polytree, dummy);
+		}
+
+		bool Execute(ClipType clip_type,
+			FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths)
+		{
+			if (ExecuteInternal(clip_type, fill_rule, true))
+			{
+				open_paths.clear();
+				polytree.Clear();
+				BuildTree64(polytree, open_paths);
+			}
+			CleanUp();
+			return succeeded_;
+		}
+	};
+
+	class ClipperD : public ClipperBase {
+	private:
+		double scale_ = 1.0, invScale_ = 1.0;
+#ifdef USINGZ
+		ZCallbackD zCallback_ = nullptr;
+#endif
+		void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
+		void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
+	public:
+		explicit ClipperD(int precision = 2) : ClipperBase()
+		{
+			CheckPrecision(precision);
+			// 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,
+				std::ilogb(std::pow(10, precision)) + 1);
+			invScale_ = 1 / scale_;
+		}
+
+#ifdef USINGZ
+		void SetZCallback(ZCallbackD cb) { zCallback_ = cb; };
+
+		void ZCB(const Point64& e1bot, const Point64& e1top,
+			const Point64& e2bot, const Point64& e2top, Point64& pt)
+		{
+			// de-scale (x & y)
+			// temporarily convert integers to their initial float values
+			// this will slow clipping marginally but will make it much easier
+			// to understand the coordinates passed to the callback function
+			PointD tmp = PointD(pt) * invScale_;
+			PointD e1b = PointD(e1bot) * invScale_;
+			PointD e1t = PointD(e1top) * invScale_;
+			PointD e2b = PointD(e2bot) * invScale_;
+			PointD e2t = PointD(e2top) * invScale_;
+			zCallback_(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 
+				// then assign the proxy callback function
+				ClipperBase::zCallback_ = 
+					std::bind(&ClipperD::ZCB, this, std::placeholders::_1,
+					std::placeholders::_2, std::placeholders::_3,
+					std::placeholders::_4, std::placeholders::_5); 
+			else
+				ClipperBase::zCallback_ = nullptr;
+		}
+
+#endif
+
+		void AddSubject(const PathsD& subjects)
+		{
+			AddPaths(ScalePaths<int64_t, double>(subjects, scale_), PathType::Subject, false);
+		}
+
+		void AddOpenSubject(const PathsD& open_subjects)
+		{
+			AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_), PathType::Subject, true);
+		}
+
+		void AddClip(const PathsD& clips)
+		{
+			AddPaths(ScalePaths<int64_t, double>(clips, scale_), PathType::Clip, false);
+		}
+
+		bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
+		{
+			PathsD dummy;
+			return Execute(clip_type, fill_rule, closed_paths, dummy);
+		}
+
+		bool Execute(ClipType clip_type,
+			FillRule fill_rule, PathsD& closed_paths, PathsD& open_paths)
+		{
+#ifdef USINGZ
+			CheckCallback();
+#endif
+			if (ExecuteInternal(clip_type, fill_rule, false))
+			{
+				BuildPathsD(closed_paths, &open_paths);
+			}
+			CleanUp();
+			return succeeded_;
+		}
+
+		bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree)
+		{
+			PathsD dummy;
+			return Execute(clip_type, fill_rule, polytree, dummy);
+		}
+
+		bool Execute(ClipType clip_type,
+			FillRule fill_rule, PolyTreeD& polytree, PathsD& open_paths)
+		{
+#ifdef USINGZ
+			CheckCallback();
+#endif
+			if (ExecuteInternal(clip_type, fill_rule, true))
+			{
+				polytree.Clear();
+				polytree.SetInvScale(invScale_);
+				open_paths.clear();
+				BuildTreeD(polytree, open_paths);
+			}
+			CleanUp();
+			return succeeded_;
+		}
+
+	};
+
+}  // namespace 
+
+#endif  // CLIPPER_ENGINE_H

+ 830 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.export.h

@@ -0,0 +1,830 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  11 December 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* 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.
+
+#ifndef CLIPPER2_EXPORT_H
+#define CLIPPER2_EXPORT_H
+
+#include <cstdlib>
+#include <vector>
+
+#include "clipper2/clipper.core.h"
+#include "clipper2/clipper.engine.h"
+#include "clipper2/clipper.offset.h"
+#include "clipper2/clipper.rectclip.h"
+
+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 struct CPolyPathD {
+  CPathD        polygon;
+  uint32_t      is_hole;
+  uint32_t      child_count;
+  CPolyPathD*   childs;
+}
+CPolyTreeD;
+
+template <typename T>
+struct CRect {
+  T left;
+  T top;
+  T right;
+  T bottom;
+};
+
+typedef CRect<int64_t> CRect64;
+typedef CRect<double> CRectD;
+
+template <typename T>
+inline bool CRectIsEmpty(const CRect<T>& rect)
+{
+  return (rect.right <= rect.left) || (rect.bottom <= rect.top);
+}
+
+template <typename T>
+inline Rect<T> CRectToRect(const CRect<T>& rect)
+{
+  Rect<T> result;
+  result.left = rect.left;
+  result.top = rect.top;
+  result.right = rect.right;
+  result.bottom = rect.bottom;
+  return result;
+}
+
+#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
+
+//////////////////////////////////////////////////////
+// EXPORTED FUNCTION DEFINITIONS
+//////////////////////////////////////////////////////
+
+EXTERN_DLL_EXPORT const char* Version();
+
+// Some of the functions below will return data in the various CPath
+// and CPolyTree structures which are pointers to heap allocated
+// memory. Eventually this memory will need to be released with one
+// of the following 'DisposeExported' functions.  (This may be the
+// only safe way to release this memory since the executable
+// accessing these exported functions may use a memory manager that
+// allocates and releases heap memory in a different way. Also,
+// CPath structures that have been constructed by the executable
+// should not be destroyed using these 'DisposeExported' functions.)
+EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p);
+EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp);
+EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p);
+EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp);
+EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt);
+EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt);
+
+// Boolean clipping:
+// cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4
+// fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3
+EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
+  uint8_t fillrule, const CPaths64 subjects,
+  const CPaths64 subjects_open, const CPaths64 clips,
+  CPaths64& solution, CPaths64& solution_open,
+  bool preserve_collinear = true, bool reverse_solution = false);
+EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype,
+  uint8_t fillrule, const CPaths64 subjects,
+  const CPaths64 subjects_open, const CPaths64 clips,
+  CPolyTree64*& solution, CPaths64& solution_open,
+  bool preserve_collinear = true, bool reverse_solution = false);
+EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
+  uint8_t fillrule, const CPathsD subjects,
+  const CPathsD subjects_open, const CPathsD clips,
+  CPathsD& solution, CPathsD& solution_open, int precision = 2,
+  bool preserve_collinear = true, bool reverse_solution = false);
+EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype,
+  uint8_t fillrule, const CPathsD subjects,
+  const CPathsD subjects_open, const CPathsD clips,
+  CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2,
+  bool preserve_collinear = true, bool reverse_solution = false);
+
+// Polygon offsetting (inflate/deflate):
+// jointype: Square=0, Round=1, Miter=2
+// endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4
+EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
+  double delta, uint8_t jointype, uint8_t endtype, 
+  double miter_limit = 2.0, double arc_tolerance = 0.0, 
+  bool reverse_solution = false);
+EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
+  double delta, uint8_t jointype, uint8_t endtype,
+  int precision = 2, double miter_limit = 2.0,
+  double arc_tolerance = 0.0, bool reverse_solution = false);
+
+// RectClip & RectClipLines:
+EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
+  const CPaths64 paths);
+EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
+  const CPathsD paths, int precision = 2);
+EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
+  const CPaths64 paths);
+EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
+  const CPathsD paths, int precision = 2);
+
+//////////////////////////////////////////////////////
+// INTERNAL FUNCTIONS
+//////////////////////////////////////////////////////
+
+inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2);
+inline CPath64 CreateCPath64(const Path64& p);
+inline CPaths64 CreateCPaths64(const Paths64& pp);
+inline Path64 ConvertCPath64(const CPath64& p);
+inline Paths64 ConvertCPaths64(const CPaths64& pp);
+
+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);
+
+// 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);
+
+EXTERN_DLL_EXPORT const char* Version()
+{
+  return CLIPPER2_VERSION;
+}
+
+EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p)
+{
+  if (p) delete[] p;
+}
+
+EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp)
+{
+  if (!pp) return;
+  CPaths64 v = pp;
+  CPath64 cnts = *v;
+  const size_t cnt = static_cast<size_t>(cnts[1]);
+  for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1
+    DisposeExportedCPath64(*v++);
+  delete[] pp;
+  pp = nullptr;
+}
+
+EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p)
+{
+  if (p) delete[] p;
+}
+
+EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp)
+{
+  if (!pp) return;
+  CPathsD v = pp;
+  CPathD cnts = *v;
+  size_t cnt = static_cast<size_t>(cnts[1]);
+  for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1
+    DisposeExportedCPathD(*v++);
+  delete[] pp;
+  pp = nullptr;
+}
+
+EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, 
+  uint8_t fillrule, const CPaths64 subjects,
+  const CPaths64 subjects_open, const CPaths64 clips,
+  CPaths64& solution, 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, sol_open;
+  sub       = ConvertCPaths64(subjects);
+  sub_open  = ConvertCPaths64(subjects_open);
+  clp       = ConvertCPaths64(clips);
+
+  Clipper64 clipper;
+  clipper.PreserveCollinear = preserve_collinear;
+  clipper.ReverseSolution = reverse_solution;
+  if (sub.size() > 0) clipper.AddSubject(sub);
+  if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
+  if (clp.size() > 0) clipper.AddClip(clp);
+  if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) 
+    return -1; // clipping bug - should never happen :)
+  solution = CreateCPaths64(sol);
+  solution_open = CreateCPaths64(sol_open);
+  return 0; //success !!
+}
+
+EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype,
+  uint8_t fillrule, const CPaths64 subjects,
+  const CPaths64 subjects_open, const CPaths64 clips,
+  CPolyTree64*& solution, CPaths64& solution_open,
+  bool preserve_collinear, 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);
+
+  PolyTree64 pt;
+  Clipper64 clipper;
+  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))
+    return -1; // clipping bug - should never happen :)
+
+  solution = CreateCPolyTree64(pt);
+  solution_open = CreateCPaths64(sol_open);
+  return 0; //success !!
+}
+
+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,
+  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);
+
+  Paths64 sub, sub_open, clp, sol, sol_open;
+  sub       = ConvertCPathsD(subjects, scale);
+  sub_open  = ConvertCPathsD(subjects_open, scale);
+  clp       = ConvertCPathsD(clips, scale);
+
+  Clipper64 clipper;
+  clipper.PreserveCollinear = preserve_collinear;
+  clipper.ReverseSolution = reverse_solution;
+  if (sub.size() > 0) clipper.AddSubject(sub);
+  if (sub_open.size() > 0)
+    clipper.AddOpenSubject(sub_open);
+  if (clp.size() > 0) clipper.AddClip(clp);
+  if (!clipper.Execute(ClipType(cliptype),
+    FillRule(fillrule), sol, sol_open)) return -1;
+
+  if (sol.size() > 0) solution = CreateCPathsD(sol, 1 / scale);
+  if (sol_open.size() > 0)
+    solution_open = CreateCPathsD(sol_open, 1 / scale);
+  return 0;
+}
+
+EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype,
+  uint8_t fillrule, const CPathsD subjects,
+  const CPathsD subjects_open, const CPathsD clips,
+  CPolyTreeD*& solution, CPathsD& solution_open, int precision,
+  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);
+  Paths64 sub, sub_open, clp, sol_open;
+  sub       = ConvertCPathsD(subjects, scale);
+  sub_open  = ConvertCPathsD(subjects_open, scale);
+  clp       = ConvertCPathsD(clips, scale);
+
+  PolyTree64 sol;
+  Clipper64 clipper;
+  clipper.PreserveCollinear = preserve_collinear;
+  clipper.ReverseSolution = reverse_solution;
+  if (sub.size() > 0) clipper.AddSubject(sub);
+  if (sub_open.size() > 0)
+    clipper.AddOpenSubject(sub_open);
+  if (clp.size() > 0) clipper.AddClip(clp);
+  if (!clipper.Execute(ClipType(cliptype),
+    FillRule(fillrule), sol, sol_open)) return -1;
+
+  solution = CreateCPolyTreeD(sol, 1 / scale);
+  if (sol_open.size() > 0)
+    solution_open = CreateCPathsD(sol_open, 1 / scale);
+  return 0;
+}
+
+EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
+  double delta, uint8_t jointype, uint8_t endtype, double miter_limit,
+  double arc_tolerance, bool reverse_solution)
+{
+  Paths64 pp;
+  pp = ConvertCPaths64(paths);
+
+  ClipperOffset clip_offset( miter_limit, 
+    arc_tolerance, reverse_solution);
+  clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
+  Paths64 result = clip_offset.Execute(delta);
+  return CreateCPaths64(result);
+}
+
+EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
+  double delta, uint8_t jointype, uint8_t endtype,
+  int precision, double miter_limit,
+  double arc_tolerance, bool reverse_solution)
+{
+  if (precision < -8 || precision > 8 || !paths) return nullptr;
+  const double scale = std::pow(10, precision);
+  ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
+  Paths64 pp = ConvertCPathsD(paths, scale);
+  clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
+  Paths64 result = clip_offset.Execute(delta * scale);
+  return CreateCPathsD(result, 1/scale);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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;
+}
+
+}  // end Clipper2Lib namespace
+  
+#endif  // CLIPPER2_EXPORT_H

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

@@ -0,0 +1,761 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  29 October 2022                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  This module provides a simple interface to the Clipper Library  *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef CLIPPER_H
+#define CLIPPER_H
+
+#include <cstdlib>
+#include <vector>
+
+#include "clipper.core.h"
+#include "clipper.engine.h"
+#include "clipper.offset.h"
+#include "clipper.minkowski.h"
+#include "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);
+    clipper.AddClip(clips);
+    clipper.Execute(cliptype, fillrule, result);
+    return result;
+  }
+
+  inline void BooleanOp(ClipType cliptype, FillRule fillrule,
+    const Paths64& subjects, const Paths64& clips, PolyTree64& solution)
+  {
+    Paths64 sol_open;
+    Clipper64 clipper;
+    clipper.AddSubject(subjects);
+    clipper.AddClip(clips);
+    clipper.Execute(cliptype, fillrule, solution, sol_open);
+  }
+
+  inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule,
+    const PathsD& subjects, const PathsD& clips, int decimal_prec = 2)
+  {
+    CheckPrecision(decimal_prec);
+    PathsD result;
+    ClipperD clipper(decimal_prec);
+    clipper.AddSubject(subjects);
+    clipper.AddClip(clips);
+    clipper.Execute(cliptype, fillrule, result);
+    return result;
+  }
+
+  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);
+    clipper.AddSubject(subjects);
+    clipper.AddClip(clips);
+    clipper.Execute(cliptype, fillrule, polytree);
+  }
+
+  inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
+  {
+    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);
+  }
+
+  inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
+  {
+    return BooleanOp(ClipType::Union, fillrule, subjects, clips);
+  }
+
+  inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
+  {
+    return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec);
+  }
+
+  inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
+  {
+    Paths64 result;
+    Clipper64 clipper;
+    clipper.AddSubject(subjects);
+    clipper.Execute(ClipType::Union, fillrule, result);
+    return result;
+  }
+
+  inline PathsD Union(const PathsD& subjects, FillRule fillrule, int decimal_prec = 2)
+  {
+    CheckPrecision(decimal_prec);
+    PathsD result;
+    ClipperD clipper(decimal_prec);
+    clipper.AddSubject(subjects);
+    clipper.Execute(ClipType::Union, fillrule, result);
+    return result;
+  }
+
+  inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
+  {
+    return BooleanOp(ClipType::Difference, fillrule, subjects, clips);
+  }
+
+  inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
+  {
+    return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec);
+  }
+
+  inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
+  {
+    return BooleanOp(ClipType::Xor, fillrule, subjects, clips);
+  }
+
+  inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
+  {
+    return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec);
+  }
+
+  inline Paths64 InflatePaths(const Paths64& paths, double delta,
+    JoinType jt, EndType et, double miter_limit = 2.0)
+  {
+    ClipperOffset clip_offset(miter_limit);
+    clip_offset.AddPaths(paths, jt, et);
+    return clip_offset.Execute(delta);
+  }
+
+  inline PathsD InflatePaths(const PathsD& paths, double delta,
+    JoinType jt, EndType et, double miter_limit = 2.0, int precision = 2)
+  {
+    CheckPrecision(precision);
+    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);
+  }
+
+  inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
+  {
+    Path64 result;
+    result.reserve(path.size());
+    for (const Point64& pt : path)
+      result.push_back(Point64(pt.x + dx, pt.y + dy));
+    return result;
+  }
+
+  inline PathD TranslatePath(const PathD& path, double dx, double dy)
+  {
+    PathD result;
+    result.reserve(path.size());
+    for (const PointD& pt : path)
+      result.push_back(PointD(pt.x + dx, pt.y + dy));
+    return result;
+  }
+
+  inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
+  {
+    Paths64 result;
+    result.reserve(paths.size());
+    for (const Path64& path : paths)
+      result.push_back(TranslatePath(path, dx, dy));
+    return result;
+  }
+
+  inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
+  {
+    PathsD result;
+    result.reserve(paths.size());
+    for (const PathD& path : paths)
+      result.push_back(TranslatePath(path, dx, dy));
+    return result;
+  }
+
+  inline Rect64 Bounds(const Path64& path)
+  {
+    Rect64 rec = MaxInvalidRect64;
+    for (const Point64& pt : path)
+    {
+      if (pt.x < rec.left) rec.left = pt.x;
+      if (pt.x > rec.right) rec.right = pt.x;
+      if (pt.y < rec.top) rec.top = pt.y;
+      if (pt.y > rec.bottom) rec.bottom = pt.y;
+    }
+    if (rec.IsEmpty()) return Rect64();
+    return rec;
+  }
+  
+  inline Rect64 Bounds(const Paths64& paths)
+  {
+    Rect64 rec = MaxInvalidRect64;
+    for (const Path64& path : paths)
+      for (const Point64& pt : path)
+      {
+        if (pt.x < rec.left) rec.left = pt.x;
+        if (pt.x > rec.right) rec.right = pt.x;
+        if (pt.y < rec.top) rec.top = pt.y;
+        if (pt.y > rec.bottom) rec.bottom = pt.y;
+      }
+    if (rec.IsEmpty()) return Rect64();
+    return rec;
+  }
+
+  inline RectD Bounds(const PathD& path)
+  {
+    RectD rec = MaxInvalidRectD;
+    for (const PointD& pt : path)
+    {
+      if (pt.x < rec.left) rec.left = pt.x;
+      if (pt.x > rec.right) rec.right = pt.x;
+      if (pt.y < rec.top) rec.top = pt.y;
+      if (pt.y > rec.bottom) rec.bottom = pt.y;
+    }
+    if (rec.IsEmpty()) return RectD();
+    return rec;
+  }
+
+  inline RectD Bounds(const PathsD& paths)
+  {
+    RectD rec = MaxInvalidRectD;
+    for (const PathD& path : paths)
+      for (const PointD& pt : path)
+      {
+        if (pt.x < rec.left) rec.left = pt.x;
+        if (pt.x > rec.right) rec.right = pt.x;
+        if (pt.y < rec.top) rec.top = pt.y;
+        if (pt.y > rec.bottom) rec.bottom = pt.y;
+      }
+    if (rec.IsEmpty()) return RectD();
+    return rec;
+  }
+
+  inline Path64 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;
+  }
+
+  inline PathD RectClip(const RectD& rect, const PathD& path, int precision = 2)
+  {
+    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);
+  }
+
+  inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
+  {
+    if (rect.IsEmpty() || paths.empty()) return PathsD();
+    CheckPrecision(precision);
+    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;
+  }
+
+  inline Paths64 RectClipLines(const Rect64& rect, const Path64& path)
+  {
+    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);
+  }
+
+  inline Paths64 RectClipLines(const Rect64& rect, const Paths64& paths)
+  {
+    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;
+  }
+
+  inline PathsD RectClipLines(const RectD& rect, const PathD& path, int precision = 2)
+  {
+    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);
+  }
+
+  inline PathsD RectClipLines(const RectD& rect, const PathsD& paths, int precision = 2)
+  {
+    PathsD result;
+    if (rect.IsEmpty() || paths.empty()) return result;
+    CheckPrecision(precision);
+    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;
+  }
+
+  namespace details
+  {
+
+    inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths)
+    {
+      paths.push_back(polypath.Polygon());
+      for (const auto& child : polypath)
+        PolyPathToPaths64(*child, paths);
+    }
+
+    inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths)
+    {
+      paths.push_back(polypath.Polygon());
+      for (const auto& child : polypath)
+        PolyPathToPathsD(*child, paths);
+    }
+
+    inline bool PolyPath64ContainsChildren(const PolyPath64& pp)
+    {
+      for (const auto& child : pp)
+      {
+        for (const Point64& pt : child->Polygon())
+          if (PointInPolygon(pt, pp.Polygon()) == PointInPolygonResult::IsOutside)
+            return false;
+        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)
+    {
+      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;
+    }
+
+    inline void SkipWhiteSpace(std::string::const_iterator& iter, 
+      const std::string::const_iterator& end_iter)
+    {
+      while (iter != end_iter && *iter <= ' ') ++iter;
+    }
+
+    inline void SkipSpacesWithOptionalComma(std::string::const_iterator& iter, 
+      const std::string::const_iterator& end_iter)
+    {
+      bool comma_seen = false;
+      while (iter != end_iter)
+      {
+        if (*iter == ' ') ++iter;
+        else if (*iter == ',')
+        {
+          if (comma_seen) return; // don't skip 2 commas!
+          comma_seen = true;
+          ++iter;
+        }
+        else return;                
+      }
+    }
+
+    inline bool has_one_match(const char c, char* chrs)
+    {
+      while (*chrs > 0 && c != *chrs) ++chrs;
+      if (!*chrs) return false;
+      *chrs = ' '; // only match once per char
+      return true;
+    }
+
+
+    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;
+    }
+
+  } // end details namespace 
+
+  inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
+  {
+    Paths64 result;
+    for (const auto& child : polytree)
+      details::PolyPathToPaths64(*child, result);
+    return result;
+  }
+
+  inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree)
+  {
+    PathsD result;
+    for (const auto& child : polytree)
+      details::PolyPathToPathsD(*child, result);
+    return result;
+  }
+
+  inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
+  {
+    for (const auto& child : polytree)
+      if (child->Count() > 0 && 
+        !details::PolyPath64ContainsChildren(*child))
+          return false;
+    return true;
+  }
+
+  inline Path64 MakePath(const std::string& s)
+  {
+    const std::string skip_chars = " ,(){}[]";
+    Path64 result;
+    std::string::const_iterator s_iter = s.cbegin();
+    details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
+    while (s_iter != s.cend())
+    {
+      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);
+    }
+    return result;
+  }
+  
+  inline PathD MakePathD(const std::string& s)
+  {
+    const std::string skip_chars = " ,(){}[]";
+    PathD result;
+    std::string::const_iterator s_iter = s.cbegin();
+    details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
+    while (s_iter != s.cend())
+    {
+      double y = 0, x = 0;
+      if (!details::GetFloat(s_iter, s.cend(), x)) break;
+      details::SkipSpacesWithOptionalComma(s_iter, s.cend());
+      if (!details::GetFloat(s_iter, s.cend(), y)) break;
+      result.push_back(PointD(x, y));
+      details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
+    }
+    return result;
+  }
+
+  inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
+  {
+    size_t len = p.size();
+    if (len < 3)
+    {
+      if (!is_open_path || len < 2 || p[0] == p[1]) return Path64();
+      else return p;
+    }
+
+    Path64 dst;
+    dst.reserve(len);
+    Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1;
+
+    if (!is_open_path)
+    {
+      while (srcIt != stop && !CrossProduct(*stop, *srcIt, *(srcIt + 1)))
+        ++srcIt;
+      while (srcIt != stop && !CrossProduct(*(stop - 1), *stop, *srcIt))
+        --stop;
+      if (srcIt == stop) return Path64();
+    }
+
+    prevIt = srcIt++;
+    dst.push_back(*prevIt);
+    for (; srcIt != stop; ++srcIt)
+    {
+      if (CrossProduct(*prevIt, *srcIt, *(srcIt + 1)))
+      {
+        prevIt = srcIt;
+        dst.push_back(*prevIt);
+      }
+    }
+
+    if (is_open_path)
+      dst.push_back(*srcIt);
+    else if (CrossProduct(*prevIt, *stop, dst[0]))
+      dst.push_back(*stop);
+    else
+    {
+      while (dst.size() > 2 &&
+        !CrossProduct(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
+          dst.pop_back();
+      if (dst.size() < 3) return Path64();
+    }
+    return dst;
+  }
+
+  inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
+  {
+    CheckPrecision(precision);
+    const double scale = std::pow(10, precision);
+    Path64 p = ScalePath<int64_t, double>(path, scale);
+    p = TrimCollinear(p, is_open_path);
+    return ScalePath<double, int64_t>(p, 1/scale);
+  }
+
+  template <typename T>
+  inline double Distance(const Point<T> pt1, const Point<T> pt2)
+  {
+    return std::sqrt(DistanceSqr(pt1, pt2));
+  }
+
+  template <typename T>
+  inline double Length(const Path<T>& path, bool is_closed_path = false)
+  {
+    double result = 0.0;
+    if (path.size() < 2) return result;
+    auto it = path.cbegin(), stop = path.end() - 1;
+    for (; it != stop; ++it)
+      result += Distance(*it, *(it + 1));
+    if (is_closed_path)
+      result += Distance(*stop, *path.cbegin());
+    return result;
+  }
+
+
+  template <typename T>
+  inline bool NearCollinear(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3, double sin_sqrd_min_angle_rads)
+  {
+    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, 
+      static_cast<double>(rect.Height()) * 0.5, steps);
+  }
+
+  template <typename T>
+  inline Path<T> Ellipse(const Point<T>& center,
+    double radiusX, double radiusY = 0, int steps = 0)
+  {
+    if (radiusX <= 0) return Path<T>();
+    if (radiusY <= 0) radiusY = radiusX;
+    if (steps <= 2)
+      steps = static_cast<int>(PI * sqrt((radiusX + radiusY) / 2));
+
+    double si = std::sin(2 * PI / steps);
+    double co = std::cos(2 * PI / steps);
+    double dx = co, dy = si;
+    Path<T> result;
+    result.reserve(steps);
+    result.push_back(Point<T>(center.x + radiusX, static_cast<double>(center.y)));
+    for (int i = 1; i < steps; ++i)
+    {
+      result.push_back(Point<T>(center.x + radiusX * dx, center.y + radiusY * dy));
+      double x = dx * co - dy * si;
+      dy = dy * co + dx * si;
+      dx = x;
+    }
+    return result;
+  }
+
+  template <typename T>
+  inline double PerpendicDistFromLineSqrd(const Point<T>& pt,
+    const Point<T>& line1, const Point<T>& line2)
+  {
+    double a = static_cast<double>(pt.x - line1.x);
+    double b = static_cast<double>(pt.y - line1.y);
+    double c = static_cast<double>(line2.x - line1.x);
+    double d = static_cast<double>(line2.y - line1.y);
+    if (c == 0 && d == 0) return 0;
+    return Sqr(a * d - c * b) / (c * c + d * d);
+  }
+
+  template <typename T>
+  inline void RDP(const Path<T> path, std::size_t begin,
+    std::size_t end, double epsSqrd, std::vector<bool>& flags)
+  {
+    typename Path<T>::size_type idx = 0;
+    double max_d = 0;
+    while (end > begin && path[begin] == path[end]) flags[end--] = false;
+    for (typename Path<T>::size_type i = begin + 1; i < end; ++i)
+    {
+      // PerpendicDistFromLineSqrd - avoids expensive Sqrt()
+      double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
+      if (d <= max_d) continue;
+      max_d = d;
+      idx = i;
+    }
+    if (max_d <= epsSqrd) return;
+    flags[idx] = true;
+    if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
+    if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
+  }
+
+  template <typename T>
+  inline Path<T> RamerDouglasPeucker(const Path<T>& path, double epsilon)
+  {
+    const typename Path<T>::size_type len = path.size();
+    if (len < 5) return Path<T>(path);
+    std::vector<bool> flags(len);
+    flags[0] = true;
+    flags[len - 1] = true;
+    RDP(path, 0, len - 1, Sqr(epsilon), flags);
+    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> RamerDouglasPeucker(const Paths<T>& paths, double epsilon)
+  {
+    Paths<T> result;
+    result.reserve(paths.size());
+    for (const Path<T>& path : paths)
+      result.push_back(RamerDouglasPeucker<T>(path, epsilon));
+    return result;
+  }
+
+}  // end Clipper2Lib namespace
+
+#endif  // CLIPPER_H

+ 118 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h

@@ -0,0 +1,118 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  15 October 2022                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Minkowski Sum and Difference                                    *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef CLIPPER_MINKOWSKI_H
+#define CLIPPER_MINKOWSKI_H
+
+#include <cstdlib>
+#include <vector>
+#include <string>
+#include "clipper.core.h"
+
+namespace Clipper2Lib 
+{
+
+  namespace detail
+  {
+    inline Paths64 Minkowski(const Path64& pattern, const Path64& path, bool isSum, bool isClosed)
+    {
+      size_t delta = isClosed ? 0 : 1;
+      size_t patLen = pattern.size(), pathLen = path.size();
+      if (patLen == 0 || pathLen == 0) return Paths64();
+      Paths64 tmp;
+      tmp.reserve(pathLen);
+
+      if (isSum)
+      {
+        for (const Point64& p : path)
+        {
+          Path64 path2(pattern.size());
+          std::transform(pattern.cbegin(), pattern.cend(),
+            path2.begin(), [p](const Point64& pt2) {return p + pt2; });
+          tmp.push_back(path2);
+        }
+      }
+      else
+      {
+        for (const Point64& p : path)
+        {
+          Path64 path2(pattern.size());
+          std::transform(pattern.cbegin(), pattern.cend(),
+            path2.begin(), [p](const Point64& pt2) {return p - pt2; });
+          tmp.push_back(path2);
+        }
+      }
+
+      Paths64 result;
+      result.reserve((pathLen - delta) * patLen);
+      size_t g = isClosed ? pathLen - 1 : 0;
+      for (size_t h = patLen - 1, i = delta; i < pathLen; ++i)
+      {
+        for (size_t j = 0; j < patLen; j++)
+        {
+          Path64 quad;
+          quad.reserve(4);
+          {
+            quad.push_back(tmp[g][h]);
+            quad.push_back(tmp[i][h]);
+            quad.push_back(tmp[i][j]);
+            quad.push_back(tmp[g][j]);
+          };
+          if (!IsPositive(quad))
+            std::reverse(quad.begin(), quad.end());
+          result.push_back(quad);
+          h = j;
+        }
+        g = i;
+      }
+      return result;
+    }
+
+    inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
+    {
+      Paths64 result;
+      Clipper64 clipper;
+      clipper.AddSubject(subjects);
+      clipper.Execute(ClipType::Union, fillrule, result);
+      return result;
+    }
+
+  } // namespace internal
+
+  inline Paths64 MinkowskiSum(const Path64& pattern, const Path64& path, bool isClosed)
+  {
+    return detail::Union(detail::Minkowski(pattern, path, true, isClosed), FillRule::NonZero);
+  }
+
+  inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
+  {
+    double scale = pow(10, decimalPlaces);
+    Path64 pat64 = ScalePath<int64_t, double>(pattern, scale);
+    Path64 path64 = ScalePath<int64_t, double>(path, scale);
+    Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
+    return ScalePaths<double, int64_t>(tmp, 1 / scale);
+  }
+
+  inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed)
+  {
+    return detail::Union(detail::Minkowski(pattern, path, false, isClosed), FillRule::NonZero);
+  }
+
+  inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
+  {
+    double scale = pow(10, decimalPlaces);
+    Path64 pat64 = ScalePath<int64_t, double>(pattern, scale); 
+    Path64 path64 = ScalePath<int64_t, double>(path, scale);
+    Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
+    return ScalePaths<double, int64_t>(tmp, 1 / scale);
+  }
+
+} // Clipper2Lib namespace
+
+#endif  // CLIPPER_MINKOWSKI_H

+ 107 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.offset.h

@@ -0,0 +1,107 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  15 October 2022                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Path Offset (Inflate/Shrink)                                    *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef CLIPPER_OFFSET_H_
+#define CLIPPER_OFFSET_H_
+
+#include "clipper.core.h"
+
+namespace Clipper2Lib {
+
+enum class JoinType { Square, Round, Miter };
+
+enum class EndType {Polygon, Joined, Butt, Square, Round};
+//Butt   : offsets both sides of a path, with square blunt ends
+//Square : offsets both sides of a path, with square extended ends
+//Round  : offsets both sides of a path, with round extended ends
+//Joined : offsets both sides of a path, with joined ends
+//Polygon: offsets only one side of a closed path
+
+class ClipperOffset {
+private:
+
+	class Group {
+	public:
+		Paths64 paths_in_;
+		Paths64 paths_out_;
+		Path64 path_;
+		bool is_reversed_ = false;
+		JoinType join_type_;
+		EndType end_type_;
+		Group(const Paths64& paths, JoinType join_type, EndType end_type) :
+			paths_in_(paths), join_type_(join_type), end_type_(end_type) {}
+	};
+
+	double group_delta_ = 0.0;
+	double abs_group_delta_ = 0.0;
+	double temp_lim_ = 0.0;
+	double steps_per_rad_ = 0.0;
+	PathD norms;
+	Paths64 solution;
+	std::vector<Group> groups_;
+	JoinType join_type_ = JoinType::Square;
+	
+	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);
+	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);
+public:
+	ClipperOffset(double miter_limit = 2.0,
+		double arc_tolerance = 0.0,
+		bool preserve_collinear = false, 
+		bool reverse_solution = false) :
+		miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
+		preserve_collinear_(preserve_collinear),
+		reverse_solution_(reverse_solution) { };
+
+	~ClipperOffset() { Clear(); };
+
+	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);
+
+	double MiterLimit() const { return miter_limit_; }
+	void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
+
+	//ArcTolerance: needed for rounded offsets (See offset_triginometry2.svg)
+	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;}
+};
+
+}
+#endif /* CLIPPER_OFFSET_H_ */

+ 50 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h

@@ -0,0 +1,50 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  26 October 2022                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  FAST rectangular clipping                                       *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef CLIPPER_RECTCLIP_H
+#define CLIPPER_RECTCLIP_H
+
+#include <cstdlib>
+#include <vector>
+#include "clipper.h"
+#include "clipper.core.h"
+
+namespace Clipper2Lib 
+{
+
+  enum class Location { Left, Top, Right, Bottom, Inside };
+
+  class RectClip {
+  protected:
+    const Rect64 rect_;
+    const Point64 mp_;
+    const Path64 rectPath_;
+    Path64 result_;
+    std::vector<Location> start_locs_;
+
+    void GetNextLocation(const Path64& path,
+      Location& loc, int& i, int highI);
+    void AddCorner(Location prev, Location curr);
+    void AddCorner(Location& loc, bool isClockwise);
+  public:
+    RectClip(const Rect64& rect) :
+      rect_(rect),
+      mp_(rect.MidPoint()),
+      rectPath_(rect.AsPath()) {}
+    Path64 Execute(const Path64& path);
+  };
+
+  class RectClipLines : public RectClip {
+  public:
+    RectClipLines(const Rect64& rect) : RectClip(rect) {};
+    Paths64 Execute(const Path64& path);
+  };
+
+} // Clipper2Lib namespace
+#endif  // CLIPPER_RECTCLIP_H

+ 3500 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.engine.cpp

@@ -0,0 +1,3500 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  19 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  This is the main polygon clipping module                        *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#include <stdlib.h>
+#include <cmath>
+#include <stdexcept>
+#include <vector>
+#include <numeric>
+#include <algorithm>
+#include "clipper2/clipper.engine.h"
+
+
+namespace Clipper2Lib {
+
+  static const Rect64 invalid_rect = Rect64(
+    std::numeric_limits<int64_t>::max(),
+    std::numeric_limits<int64_t>::max(),
+    -std::numeric_limits<int64_t>::max(),
+    -std::numeric_limits<int64_t>::max()
+  );
+
+  //Every closed path (or polygon) is made up of a series of vertices forming
+  //edges that alternate between going up (relative to the Y-axis) and going
+  //down. Edges consecutively going up or consecutively going down are called
+  //'bounds' (or sides if they're simple polygons). 'Local Minima' refer to
+  //vertices where descending bounds become ascending ones.
+
+  struct Scanline {
+    int64_t y = 0;
+    Scanline* next = nullptr;
+
+    explicit Scanline(int64_t y_) : y(y_) {}
+  };
+
+  struct  Joiner {
+    int      idx;
+    OutPt* op1;
+    OutPt* op2;
+    Joiner* next1;
+    Joiner* next2;
+    Joiner* nextH;
+
+    explicit Joiner(OutPt* op1_, OutPt* op2_, Joiner* nexth) :
+      op1(op1_), op2(op2_), nextH(nexth)
+    {
+      idx = -1;
+      next1 = op1->joiner;
+      op1->joiner = this;
+
+      if (op2)
+      {
+        next2 = op2->joiner;
+        op2->joiner = this;
+      }
+      else
+        next2 = nullptr;
+    }
+
+  };
+
+  struct LocMinSorter {
+    inline bool operator()(const LocalMinima* locMin1, const LocalMinima* locMin2)
+    {
+      if (locMin2->vertex->pt.y != locMin1->vertex->pt.y)
+        return locMin2->vertex->pt.y < locMin1->vertex->pt.y;
+      else
+        return locMin2->vertex->pt.x < locMin1->vertex->pt.x;
+    }
+  };
+
+  inline bool IsOdd(int val)
+  {
+    return (val & 1) ? true : false;
+  }
+
+
+  inline bool IsHotEdge(const Active& e)
+  {
+    return (e.outrec);
+  }
+
+
+  inline bool IsOpen(const Active& e)
+  {
+    return (e.local_min->is_open);
+  }
+
+
+  inline bool IsOpenEnd(const Vertex& v)
+  {
+    return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) !=
+      VertexFlags::None;
+  }
+
+
+  inline bool IsOpenEnd(const Active& ae)
+  {
+    return IsOpenEnd(*ae.vertex_top);
+  }
+
+
+  inline Active* GetPrevHotEdge(const Active& e)
+  {
+    Active* prev = e.prev_in_ael;
+    while (prev && (IsOpen(*prev) || !IsHotEdge(*prev)))
+      prev = prev->prev_in_ael;
+    return prev;
+  }
+
+  inline bool IsFront(const Active& e)
+  {
+    return (&e == e.outrec->front_edge);
+  }
+
+  inline bool IsInvalidPath(OutPt* op)
+  {
+    return (!op || op->next == op);
+  }
+
+  /*******************************************************************************
+    *  Dx:                             0(90deg)                                    *
+    *                                  |                                           *
+    *               +inf (180deg) <--- o ---> -inf (0deg)                          *
+    *******************************************************************************/
+
+  inline double GetDx(const Point64& pt1, const Point64& pt2)
+  {
+    double dy = double(pt2.y - pt1.y);
+    if (dy != 0)
+      return double(pt2.x - pt1.x) / dy;
+    else if (pt2.x > pt1.x)
+      return -std::numeric_limits<double>::max();
+    else
+      return std::numeric_limits<double>::max();
+  }
+
+
+  inline int64_t TopX(const Active& ae, const int64_t currentY)
+  {
+    if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x;
+    else if (currentY == ae.bot.y) return ae.bot.x;
+    else return ae.bot.x + static_cast<int64_t>(std::nearbyint(ae.dx * (currentY - ae.bot.y)));
+    // nb: std::nearbyint (or std::round) substantially *improves* performance here
+    // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList().
+  }
+
+
+  inline bool IsHorizontal(const Active& e)
+  {
+    return (e.top.y == e.bot.y);
+  }
+
+
+  inline bool IsHeadingRightHorz(const Active& e)
+  {
+    return e.dx == -std::numeric_limits<double>::max();
+  }
+
+
+  inline bool IsHeadingLeftHorz(const Active& e)
+  {
+    return e.dx == std::numeric_limits<double>::max();
+  }
+
+
+  inline void SwapActives(Active*& e1, Active*& e2)
+  {
+    Active* e = e1;
+    e1 = e2;
+    e2 = e;
+  }
+
+  inline PathType GetPolyType(const Active& e)
+  {
+    return e.local_min->polytype;
+  }
+
+  inline bool IsSamePolyType(const Active& e1, const Active& e2)
+  {
+    return e1.local_min->polytype == e2.local_min->polytype;
+  }
+
+  inline void SetDx(Active& e)
+  {
+    e.dx = GetDx(e.bot, e.top);
+  }
+
+  inline Vertex* NextVertex(const Active& e)
+  {
+    if (e.wind_dx > 0)
+      return e.vertex_top->next;
+    else
+      return e.vertex_top->prev;
+  }
+
+  //PrevPrevVertex: useful to get the (inverted Y-axis) top of the
+  //alternate edge (ie left or right bound) during edge insertion.  
+  inline Vertex* PrevPrevVertex(const Active& ae)
+  {
+    if (ae.wind_dx > 0)
+      return ae.vertex_top->prev->prev;
+    else
+      return ae.vertex_top->next->next;
+  }
+
+
+  inline Active* ExtractFromSEL(Active* ae)
+  {
+    Active* res = ae->next_in_sel;
+    if (res)
+      res->prev_in_sel = ae->prev_in_sel;
+    ae->prev_in_sel->next_in_sel = res;
+    return res;
+  }
+
+
+  inline void Insert1Before2InSEL(Active* ae1, Active* ae2)
+  {
+    ae1->prev_in_sel = ae2->prev_in_sel;
+    if (ae1->prev_in_sel)
+      ae1->prev_in_sel->next_in_sel = ae1;
+    ae1->next_in_sel = ae2;
+    ae2->prev_in_sel = ae1;
+  }
+
+  inline bool IsMaxima(const Vertex& v)
+  {
+    return ((v.flags & VertexFlags::LocalMax) != VertexFlags::None);
+  }
+
+
+  inline bool IsMaxima(const Active& e)
+  {
+    return IsMaxima(*e.vertex_top);
+  }
+
+  Vertex* GetCurrYMaximaVertex(const Active& e)
+  {
+    Vertex* result = e.vertex_top;
+    if (e.wind_dx > 0)
+      while (result->next->pt.y == result->pt.y) result = result->next;
+    else
+      while (result->prev->pt.y == result->pt.y) result = result->prev;
+    if (!IsMaxima(*result)) result = nullptr; // not a maxima   
+    return result;
+  }
+
+  Active* GetMaximaPair(const Active& e)
+  {
+    Active* e2;
+    e2 = e.next_in_ael;
+    while (e2)
+    {
+      if (e2->vertex_top == e.vertex_top) return e2;  // Found!
+      e2 = e2->next_in_ael;
+    }
+    return nullptr;
+  }
+
+  Active* GetHorzMaximaPair(const Active& horz, const Vertex* vert_max)
+  {
+    //we can't be sure whether the MaximaPair is on the left or right, so ...
+    Active* result = horz.prev_in_ael;
+    while (result && result->curr_x >= vert_max->pt.x)
+    {
+      if (result->vertex_top == vert_max) return result;  // Found!
+      result = result->prev_in_ael;
+    }
+    result = horz.next_in_ael;
+    while (result && TopX(*result, horz.top.y) <= vert_max->pt.x)
+    {
+      if (result->vertex_top == vert_max) return result;  // Found!
+      result = result->next_in_ael;
+    }
+    return nullptr;
+  }
+
+  inline int PointCount(OutPt* op)
+  {
+    OutPt* op2 = op;
+    int cnt = 0;
+    do
+    {
+      op2 = op2->next;
+      ++cnt;
+    } while (op2 != op);
+    return cnt;
+  }
+
+
+  inline OutPt* InsertOp(const Point64& pt, OutPt* insertAfter)
+  {
+    OutPt* result = new OutPt(pt, insertAfter->outrec);
+    result->next = insertAfter->next;
+    insertAfter->next->prev = result;
+    insertAfter->next = result;
+    result->prev = insertAfter;
+    return result;
+  }
+
+
+  inline OutPt* DisposeOutPt(OutPt* op)
+  {
+    OutPt* result = op->next;
+    op->prev->next = op->next;
+    op->next->prev = op->prev;
+    delete op;
+    return result;
+  }
+
+
+  inline void DisposeOutPts(OutRec& outrec)
+  {
+    if (!outrec.pts) return;
+    OutPt* op2 = outrec.pts->next;
+    while (op2 != outrec.pts)
+    {
+      OutPt* tmp = op2->next;
+      delete op2;
+      op2 = tmp;
+    }
+    delete outrec.pts;
+    outrec.pts = nullptr;
+  }
+
+
+  bool IntersectListSort(const IntersectNode& a, const IntersectNode& b)
+  {
+    //note different inequality tests ...
+    return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y);
+  }
+
+
+  inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge)
+  {
+    outrec.front_edge = &start_edge;
+    outrec.back_edge = &end_edge;
+  }
+
+
+  void SwapOutrecs(Active& e1, Active& e2)
+  {
+    OutRec* or1 = e1.outrec;
+    OutRec* or2 = e2.outrec;
+    if (or1 == or2)
+    {
+      Active* e = or1->front_edge;
+      or1->front_edge = or1->back_edge;
+      or1->back_edge = e;
+      return;
+    }
+    if (or1)
+    {
+      if (&e1 == or1->front_edge)
+        or1->front_edge = &e2;
+      else
+        or1->back_edge = &e2;
+    }
+    if (or2)
+    {
+      if (&e2 == or2->front_edge)
+        or2->front_edge = &e1;
+      else
+        or2->back_edge = &e1;
+    }
+    e1.outrec = or2;
+    e2.outrec = or1;
+  }
+
+
+  double Area(OutPt* op)
+  {
+    //https://en.wikipedia.org/wiki/Shoelace_formula
+    double result = 0.0;
+    OutPt* op2 = op;
+    do
+    {
+      result += static_cast<double>(op2->prev->pt.y + op2->pt.y) *
+        static_cast<double>(op2->prev->pt.x - op2->pt.x);
+      op2 = op2->next;
+    } while (op2 != op);
+    return result * 0.5;
+  }
+
+  inline double AreaTriangle(const Point64& pt1,
+    const Point64& pt2, const Point64& pt3)
+  {
+    return (static_cast<double>(pt3.y + pt1.y) * static_cast<double>(pt3.x - pt1.x) +
+      static_cast<double>(pt1.y + pt2.y) * static_cast<double>(pt1.x - pt2.x) +
+      static_cast<double>(pt2.y + pt3.y) * static_cast<double>(pt2.x - pt3.x));
+  }
+
+  void ReverseOutPts(OutPt* op)
+  {
+    if (!op) return;
+
+    OutPt* op1 = op;
+    OutPt* op2;
+
+    do
+    {
+      op2 = op1->next;
+      op1->next = op1->prev;
+      op1->prev = op2;
+      op1 = op2;
+    } while (op1 != op);
+  }
+
+
+  inline void SwapSides(OutRec& outrec)
+  {
+    Active* e2 = outrec.front_edge;
+    outrec.front_edge = outrec.back_edge;
+    outrec.back_edge = e2;
+    outrec.pts = outrec.pts->next;
+  }
+
+
+  inline OutRec* GetRealOutRec(OutRec* outrec)
+  {
+    while (outrec && !outrec->pts) outrec = outrec->owner;
+    return outrec;
+  }
+
+
+  inline void UncoupleOutRec(Active ae)
+  {
+    OutRec* outrec = ae.outrec;
+    if (!outrec) return;
+    outrec->front_edge->outrec = nullptr;
+    outrec->back_edge->outrec = nullptr;
+    outrec->front_edge = nullptr;
+    outrec->back_edge = nullptr;
+  }
+
+
+  inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2)
+  {
+    return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2);
+  }
+
+  inline bool IsVerySmallTriangle(const OutPt& op)
+  {
+    return op.next->next == op.prev &&
+      (PtsReallyClose(op.prev->pt, op.next->pt) ||
+        PtsReallyClose(op.pt, op.next->pt) ||
+        PtsReallyClose(op.pt, op.prev->pt));
+  }
+
+  inline bool IsValidClosedPath(const OutPt* op)
+  {
+    return op && (op->next != op) && (op->next != op->prev) &&
+      !IsVerySmallTriangle(*op);
+  }
+
+  inline bool OutrecIsAscending(const Active* hotEdge)
+  {
+    return (hotEdge == hotEdge->outrec->front_edge);
+  }
+
+  inline void SwapFrontBackSides(OutRec& outrec)
+  {
+    Active* tmp = outrec.front_edge;
+    outrec.front_edge = outrec.back_edge;
+    outrec.back_edge = tmp;
+    outrec.pts = outrec.pts->next;
+  }
+
+  inline bool EdgesAdjacentInAEL(const IntersectNode& inode)
+  {
+    return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2);
+  }
+
+  inline bool TestJoinWithPrev1(const Active& e)
+  {
+    //this is marginally quicker than TestJoinWithPrev2
+    //but can only be used when e.PrevInAEL.currX is accurate
+    return IsHotEdge(e) && !IsOpen(e) &&
+      e.prev_in_ael && e.prev_in_ael->curr_x == e.curr_x &&
+      IsHotEdge(*e.prev_in_ael) && !IsOpen(*e.prev_in_ael) &&
+      (CrossProduct(e.prev_in_ael->top, e.bot, e.top) == 0);
+  }
+
+  inline bool TestJoinWithPrev2(const Active& e, const Point64& curr_pt)
+  {
+    return IsHotEdge(e) && !IsOpen(e) &&
+      e.prev_in_ael && !IsOpen(*e.prev_in_ael) &&
+      IsHotEdge(*e.prev_in_ael) && (e.prev_in_ael->top.y < e.bot.y) &&
+      (std::llabs(TopX(*e.prev_in_ael, curr_pt.y) - curr_pt.x) < 2) &&
+      (CrossProduct(e.prev_in_ael->top, curr_pt, e.top) == 0);
+  }
+
+  inline bool TestJoinWithNext1(const Active& e)
+  {
+    //this is marginally quicker than TestJoinWithNext2
+    //but can only be used when e.NextInAEL.currX is accurate
+    return IsHotEdge(e) && !IsOpen(e) &&
+      e.next_in_ael && (e.next_in_ael->curr_x == e.curr_x) &&
+      IsHotEdge(*e.next_in_ael) && !IsOpen(*e.next_in_ael) &&
+      (CrossProduct(e.next_in_ael->top, e.bot, e.top) == 0);
+  }
+
+  inline bool TestJoinWithNext2(const Active& e, const Point64& curr_pt)
+  {
+    return IsHotEdge(e) && !IsOpen(e) &&
+      e.next_in_ael && !IsOpen(*e.next_in_ael) &&
+      IsHotEdge(*e.next_in_ael) && (e.next_in_ael->top.y < e.bot.y) &&
+      (std::llabs(TopX(*e.next_in_ael, curr_pt.y) - curr_pt.x) < 2) &&
+      (CrossProduct(e.next_in_ael->top, curr_pt, e.top) == 0);
+  }
+
+  //------------------------------------------------------------------------------
+  // ClipperBase methods ...
+  //------------------------------------------------------------------------------
+
+  ClipperBase::~ClipperBase()
+  {
+    Clear();
+  }
+
+  void ClipperBase::DeleteEdges(Active*& e)
+  {
+    while (e)
+    {
+      Active* e2 = e;
+      e = e->next_in_ael;
+      delete e2;
+    }
+  }
+
+  void ClipperBase::CleanUp()
+  {
+    DeleteEdges(actives_);
+    scanline_list_ = std::priority_queue<int64_t>();
+    intersect_nodes_.clear();
+    DisposeAllOutRecs();
+  }
+
+
+  void ClipperBase::Clear()
+  {
+    CleanUp();
+    DisposeVerticesAndLocalMinima();
+    current_locmin_iter_ = minima_list_.begin();
+    minima_list_sorted_ = false;
+    has_open_paths_ = false;
+  }
+
+
+  void ClipperBase::Reset()
+  {
+    if (!minima_list_sorted_)
+    {
+      std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter());
+      minima_list_sorted_ = true;
+    }
+    std::vector<LocalMinima*>::const_reverse_iterator i;
+    for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i)
+      InsertScanline((*i)->vertex->pt.y);
+
+    current_locmin_iter_ = minima_list_.begin();
+    actives_ = nullptr;
+    sel_ = nullptr;
+    succeeded_ = true;
+  }
+
+
+#ifdef USINGZ
+  void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip)
+  {
+    if (!zCallback_) return;
+    // prioritize subject over clip vertices by passing 
+    // subject vertices before clip vertices in the callback
+    if (GetPolyType(e1) == PathType::Subject)
+    {
+      if (ip == e1.bot) ip.z = e1.bot.z;
+      else if (ip == e1.top) ip.z = e1.top.z;
+      else if (ip == e2.bot) ip.z = e2.bot.z;
+      else if (ip == e2.top) ip.z = e2.top.z;
+      else ip.z = DefaultZ;
+      zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip);
+    }
+    else
+    {
+      if (ip == e2.bot) ip.z = e2.bot.z;
+      else if (ip == e2.top) ip.z = e2.top.z;
+      else if (ip == e1.bot) ip.z = e1.bot.z;
+      else if (ip == e1.top) ip.z = e1.top.z;
+      else ip.z = DefaultZ;
+      zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip);
+    }
+  }
+#endif
+
+  void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open)
+  {
+    Paths64 tmp;
+    tmp.push_back(path);
+    AddPaths(tmp, polytype, is_open);
+  }
+
+
+  void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open)
+  {
+    if (is_open) has_open_paths_ = true;
+    minima_list_sorted_ = false;
+
+    Path64::size_type total_vertex_count = 0;
+    for (const Path64& path : paths) total_vertex_count += path.size();
+    if (total_vertex_count == 0) return;
+    Vertex* vertices = new Vertex[total_vertex_count], * v = vertices;
+    for (const Path64& path : paths)
+    {
+      //for each path create a circular double linked list of vertices
+      Vertex* v0 = v, * curr_v = v, * prev_v = nullptr;
+
+      v->prev = nullptr;
+      int cnt = 0;
+      for (const Point64& pt : path)
+      {
+        if (prev_v)
+        {
+          if (prev_v->pt == pt) continue; // ie skips duplicates
+          prev_v->next = curr_v;
+        }
+        curr_v->prev = prev_v;
+        curr_v->pt = pt;
+        curr_v->flags = VertexFlags::None;
+        prev_v = curr_v++;
+        cnt++;
+      }
+      if (!prev_v || !prev_v->prev) continue;
+      if (!is_open && prev_v->pt == v0->pt)
+        prev_v = prev_v->prev;
+      prev_v->next = v0;
+      v0->prev = prev_v;
+      v = curr_v; // ie get ready for next path
+      if (cnt < 2 || (cnt == 2 && !is_open)) continue;
+
+      //now find and assign local minima
+      bool going_up, going_up0;
+      if (is_open)
+      {
+        curr_v = v0->next;
+        while (curr_v != v0 && curr_v->pt.y == v0->pt.y)
+          curr_v = curr_v->next;
+        going_up = curr_v->pt.y <= v0->pt.y;
+        if (going_up)
+        {
+          v0->flags = VertexFlags::OpenStart;
+          AddLocMin(*v0, polytype, true);
+        }
+        else
+          v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax;
+      }
+      else // closed path
+      {
+        prev_v = v0->prev;
+        while (prev_v != v0 && prev_v->pt.y == v0->pt.y)
+          prev_v = prev_v->prev;
+        if (prev_v == v0)
+          continue; // only open paths can be completely flat
+        going_up = prev_v->pt.y > v0->pt.y;
+      }
+
+      going_up0 = going_up;
+      prev_v = v0;
+      curr_v = v0->next;
+      while (curr_v != v0)
+      {
+        if (curr_v->pt.y > prev_v->pt.y && going_up)
+        {
+          prev_v->flags = (prev_v->flags | VertexFlags::LocalMax);
+          going_up = false;
+        }
+        else if (curr_v->pt.y < prev_v->pt.y && !going_up)
+        {
+          going_up = true;
+          AddLocMin(*prev_v, polytype, is_open);
+        }
+        prev_v = curr_v;
+        curr_v = curr_v->next;
+      }
+
+      if (is_open)
+      {
+        prev_v->flags = prev_v->flags | VertexFlags::OpenEnd;
+        if (going_up)
+          prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
+        else
+          AddLocMin(*prev_v, polytype, is_open);
+      }
+      else if (going_up != going_up0)
+      {
+        if (going_up0) AddLocMin(*prev_v, polytype, false);
+        else prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
+      }
+    } // end processing current path
+
+    vertex_lists_.emplace_back(vertices);
+  } // end AddPaths
+
+
+  inline void ClipperBase::InsertScanline(int64_t y)
+  {
+    scanline_list_.push(y);
+  }
+
+
+  bool ClipperBase::PopScanline(int64_t& y)
+  {
+    if (scanline_list_.empty()) return false;
+    y = scanline_list_.top();
+    scanline_list_.pop();
+    while (!scanline_list_.empty() && y == scanline_list_.top())
+      scanline_list_.pop();  // Pop duplicates.
+    return true;
+  }
+
+
+  bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima)
+  {
+    if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false;
+    local_minima = (*current_locmin_iter_++);
+    return true;
+  }
+
+
+  void ClipperBase::DisposeAllOutRecs()
+  {
+    for (auto outrec : outrec_list_)
+    {
+      if (outrec->pts) DisposeOutPts(*outrec);
+      delete outrec;
+    }
+    outrec_list_.resize(0);
+  }
+
+
+  void ClipperBase::DisposeVerticesAndLocalMinima()
+  {
+    for (auto lm : minima_list_) delete lm;
+    minima_list_.clear();
+    for (auto v : vertex_lists_) delete[] v;
+    vertex_lists_.clear();
+  }
+
+
+  void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open)
+  {
+    //make sure the vertex is added only once ...
+    if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return;
+
+    vert.flags = (vert.flags | VertexFlags::LocalMin);
+    minima_list_.push_back(new LocalMinima(&vert, polytype, is_open));
+  }
+
+  bool ClipperBase::IsContributingClosed(const Active& e) const
+  {
+    switch (fillrule_)
+    {
+    case FillRule::EvenOdd:
+      break;
+    case FillRule::NonZero:
+      if (abs(e.wind_cnt) != 1) return false;
+      break;
+    case FillRule::Positive:
+      if (e.wind_cnt != 1) return false;
+      break;
+    case FillRule::Negative:
+      if (e.wind_cnt != -1) return false;
+      break;
+    }
+
+    switch (cliptype_)
+    {
+    case ClipType::None:
+      return false;
+    case ClipType::Intersection:
+      switch (fillrule_)
+      {
+      case FillRule::Positive:
+        return (e.wind_cnt2 > 0);
+      case FillRule::Negative:
+        return (e.wind_cnt2 < 0);
+      default:
+        return (e.wind_cnt2 != 0);
+      }
+      break;
+
+    case ClipType::Union:
+      switch (fillrule_)
+      {
+      case FillRule::Positive:
+        return (e.wind_cnt2 <= 0);
+      case FillRule::Negative:
+        return (e.wind_cnt2 >= 0);
+      default:
+        return (e.wind_cnt2 == 0);
+      }
+      break;
+
+    case ClipType::Difference:
+      bool result;
+      switch (fillrule_)
+      {
+      case FillRule::Positive:
+        result = (e.wind_cnt2 <= 0);
+        break;
+      case FillRule::Negative:
+        result = (e.wind_cnt2 >= 0);
+        break;
+      default:
+        result = (e.wind_cnt2 == 0);
+      }
+      if (GetPolyType(e) == PathType::Subject)
+        return result;
+      else
+        return !result;
+      break;
+
+    case ClipType::Xor: return true;  break;
+    }
+    return false;  // we should never get here
+  }
+
+
+  inline bool ClipperBase::IsContributingOpen(const Active& e) const
+  {
+    bool is_in_clip, is_in_subj;
+    switch (fillrule_)
+    {
+    case FillRule::Positive:
+      is_in_clip = e.wind_cnt2 > 0;
+      is_in_subj = e.wind_cnt > 0;
+      break;
+    case FillRule::Negative:
+      is_in_clip = e.wind_cnt2 < 0;
+      is_in_subj = e.wind_cnt < 0;
+      break;
+    default:
+      is_in_clip = e.wind_cnt2 != 0;
+      is_in_subj = e.wind_cnt != 0;
+    }
+
+    switch (cliptype_)
+    {
+    case ClipType::Intersection: return is_in_clip;
+    case ClipType::Union: return (!is_in_subj && !is_in_clip);
+    default: return !is_in_clip;
+    }
+  }
+
+
+  void ClipperBase::SetWindCountForClosedPathEdge(Active& e)
+  {
+    //Wind counts refer to polygon regions not edges, so here an edge's WindCnt
+    //indicates the higher of the wind counts for the two regions touching the
+    //edge. (NB Adjacent regions can only ever have their wind counts differ by
+    //one. Also, open paths have no meaningful wind directions or counts.)
+
+    Active* e2 = e.prev_in_ael;
+    //find the nearest closed path edge of the same PolyType in AEL (heading left)
+    PathType pt = GetPolyType(e);
+    while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael;
+
+    if (!e2)
+    {
+      e.wind_cnt = e.wind_dx;
+      e2 = actives_;
+    }
+    else if (fillrule_ == FillRule::EvenOdd)
+    {
+      e.wind_cnt = e.wind_dx;
+      e.wind_cnt2 = e2->wind_cnt2;
+      e2 = e2->next_in_ael;
+    }
+    else
+    {
+      //NonZero, positive, or negative filling here ...
+      //if e's WindCnt is in the SAME direction as its WindDx, then polygon
+      //filling will be on the right of 'e'.
+      //NB neither e2.WindCnt nor e2.WindDx should ever be 0.
+      if (e2->wind_cnt * e2->wind_dx < 0)
+      {
+        //opposite directions so 'e' is outside 'e2' ...
+        if (abs(e2->wind_cnt) > 1)
+        {
+          //outside prev poly but still inside another.
+          if (e2->wind_dx * e.wind_dx < 0)
+            //reversing direction so use the same WC
+            e.wind_cnt = e2->wind_cnt;
+          else
+            //otherwise keep 'reducing' the WC by 1 (ie towards 0) ...
+            e.wind_cnt = e2->wind_cnt + e.wind_dx;
+        }
+        else
+          //now outside all polys of same polytype so set own WC ...
+          e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx);
+      }
+      else
+      {
+        //'e' must be inside 'e2'
+        if (e2->wind_dx * e.wind_dx < 0)
+          //reversing direction so use the same WC
+          e.wind_cnt = e2->wind_cnt;
+        else
+          //otherwise keep 'increasing' the WC by 1 (ie away from 0) ...
+          e.wind_cnt = e2->wind_cnt + e.wind_dx;
+      }
+      e.wind_cnt2 = e2->wind_cnt2;
+      e2 = e2->next_in_ael;  // ie get ready to calc WindCnt2
+    }
+
+    //update wind_cnt2 ...
+    if (fillrule_ == FillRule::EvenOdd)
+      while (e2 != &e)
+      {
+        if (GetPolyType(*e2) != pt && !IsOpen(*e2))
+          e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0);
+        e2 = e2->next_in_ael;
+      }
+    else
+      while (e2 != &e)
+      {
+        if (GetPolyType(*e2) != pt && !IsOpen(*e2))
+          e.wind_cnt2 += e2->wind_dx;
+        e2 = e2->next_in_ael;
+      }
+  }
+
+
+  void ClipperBase::SetWindCountForOpenPathEdge(Active& e)
+  {
+    Active* e2 = actives_;
+    if (fillrule_ == FillRule::EvenOdd)
+    {
+      int cnt1 = 0, cnt2 = 0;
+      while (e2 != &e)
+      {
+        if (GetPolyType(*e2) == PathType::Clip)
+          cnt2++;
+        else if (!IsOpen(*e2))
+          cnt1++;
+        e2 = e2->next_in_ael;
+      }
+      e.wind_cnt = (IsOdd(cnt1) ? 1 : 0);
+      e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0);
+    }
+    else
+    {
+      while (e2 != &e)
+      {
+        if (GetPolyType(*e2) == PathType::Clip)
+          e.wind_cnt2 += e2->wind_dx;
+        else if (!IsOpen(*e2))
+          e.wind_cnt += e2->wind_dx;
+        e2 = e2->next_in_ael;
+      }
+    }
+  }
+
+
+  bool IsValidAelOrder(const Active& resident, const Active& newcomer)
+  {
+    if (newcomer.curr_x != resident.curr_x)
+      return newcomer.curr_x > resident.curr_x;
+
+    //get the turning direction  a1.top, a2.bot, a2.top
+    double d = CrossProduct(resident.top, newcomer.bot, newcomer.top);
+    if (d != 0) return d < 0;
+
+    //edges must be collinear to get here
+    //for starting open paths, place them according to
+    //the direction they're about to turn
+    if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))
+    {
+      return CrossProduct(newcomer.bot,
+        resident.top, NextVertex(resident)->pt) <= 0;
+    }
+    else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))
+    {
+      return CrossProduct(newcomer.bot,
+        newcomer.top, NextVertex(newcomer)->pt) >= 0;
+    }
+
+    int64_t y = newcomer.bot.y;
+    bool newcomerIsLeft = newcomer.is_left_bound;
+
+    if (resident.bot.y != y || resident.local_min->vertex->pt.y != y)
+      return newcomer.is_left_bound;
+    //resident must also have just been inserted
+    else if (resident.is_left_bound != newcomerIsLeft)
+      return newcomerIsLeft;
+    else if (CrossProduct(PrevPrevVertex(resident)->pt,
+      resident.bot, resident.top) == 0) return true;
+    else
+      //compare turning direction of the alternate bound
+      return (CrossProduct(PrevPrevVertex(resident)->pt,
+        newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;
+  }
+
+
+  void ClipperBase::InsertLeftEdge(Active& e)
+  {
+    Active* e2;
+    if (!actives_)
+    {
+      e.prev_in_ael = nullptr;
+      e.next_in_ael = nullptr;
+      actives_ = &e;
+    }
+    else if (!IsValidAelOrder(*actives_, e))
+    {
+      e.prev_in_ael = nullptr;
+      e.next_in_ael = actives_;
+      actives_->prev_in_ael = &e;
+      actives_ = &e;
+    }
+    else
+    {
+      e2 = actives_;
+      while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e))
+        e2 = e2->next_in_ael;
+      e.next_in_ael = e2->next_in_ael;
+      if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e;
+      e.prev_in_ael = e2;
+      e2->next_in_ael = &e;
+    }
+  }
+
+
+  void InsertRightEdge(Active& e, Active& e2)
+  {
+    e2.next_in_ael = e.next_in_ael;
+    if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2;
+    e2.prev_in_ael = &e;
+    e.next_in_ael = &e2;
+  }
+
+
+  void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y)
+  {
+    LocalMinima* local_minima;
+    Active* left_bound, * right_bound;
+    //Add any local minima (if any) at BotY ...
+    //nb: horizontal local minima edges should contain locMin.vertex.prev
+
+    while (PopLocalMinima(bot_y, local_minima))
+    {
+      if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::None)
+      {
+        left_bound = nullptr;
+      }
+      else
+      {
+        left_bound = new Active();
+        left_bound->bot = local_minima->vertex->pt;
+        left_bound->curr_x = left_bound->bot.x;
+        left_bound->wind_cnt = 0,
+          left_bound->wind_cnt2 = 0,
+          left_bound->wind_dx = -1,
+          left_bound->vertex_top = local_minima->vertex->prev;  // ie descending
+        left_bound->top = left_bound->vertex_top->pt;
+        left_bound->outrec = nullptr;
+        left_bound->local_min = local_minima;
+        SetDx(*left_bound);
+      }
+
+      if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::None)
+      {
+        right_bound = nullptr;
+      }
+      else
+      {
+        right_bound = new Active();
+        right_bound->bot = local_minima->vertex->pt;
+        right_bound->curr_x = right_bound->bot.x;
+        right_bound->wind_cnt = 0,
+          right_bound->wind_cnt2 = 0,
+          right_bound->wind_dx = 1,
+          right_bound->vertex_top = local_minima->vertex->next;  // ie ascending
+        right_bound->top = right_bound->vertex_top->pt;
+        right_bound->outrec = nullptr;
+        right_bound->local_min = local_minima;
+        SetDx(*right_bound);
+      }
+
+      //Currently LeftB is just the descending bound and RightB is the ascending.
+      //Now if the LeftB isn't on the left of RightB then we need swap them.
+      if (left_bound && right_bound)
+      {
+        if (IsHorizontal(*left_bound))
+        {
+          if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound);
+        }
+        else if (IsHorizontal(*right_bound))
+        {
+          if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound);
+        }
+        else if (left_bound->dx < right_bound->dx)
+          SwapActives(left_bound, right_bound);
+      }
+      else if (!left_bound)
+      {
+        left_bound = right_bound;
+        right_bound = nullptr;
+      }
+
+      bool contributing;
+      left_bound->is_left_bound = true;
+      InsertLeftEdge(*left_bound);
+
+      if (IsOpen(*left_bound))
+      {
+        SetWindCountForOpenPathEdge(*left_bound);
+        contributing = IsContributingOpen(*left_bound);
+      }
+      else
+      {
+        SetWindCountForClosedPathEdge(*left_bound);
+        contributing = IsContributingClosed(*left_bound);
+      }
+
+      if (right_bound)
+      {
+        right_bound->is_left_bound = false;
+        right_bound->wind_cnt = left_bound->wind_cnt;
+        right_bound->wind_cnt2 = left_bound->wind_cnt2;
+        InsertRightEdge(*left_bound, *right_bound);  ///////
+        if (contributing)
+        {
+          AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true);
+          if (!IsHorizontal(*left_bound) && TestJoinWithPrev1(*left_bound))
+          {
+            OutPt* op = AddOutPt(*left_bound->prev_in_ael, left_bound->bot);
+            AddJoin(op, left_bound->outrec->pts);
+          }
+        }
+
+        while (right_bound->next_in_ael &&
+          IsValidAelOrder(*right_bound->next_in_ael, *right_bound))
+        {
+          IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot);
+          SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael);
+        }
+
+        if (!IsHorizontal(*right_bound) &&
+          TestJoinWithNext1(*right_bound))
+        {
+          OutPt* op = AddOutPt(*right_bound->next_in_ael, right_bound->bot);
+          AddJoin(right_bound->outrec->pts, op);
+        }
+
+        if (IsHorizontal(*right_bound))
+          PushHorz(*right_bound);
+        else
+          InsertScanline(right_bound->top.y);
+      }
+      else if (contributing)
+      {
+        StartOpenPath(*left_bound, left_bound->bot);
+      }
+
+      if (IsHorizontal(*left_bound))
+        PushHorz(*left_bound);
+      else
+        InsertScanline(left_bound->top.y);
+    }  // while (PopLocalMinima())
+  }
+
+
+  inline void ClipperBase::PushHorz(Active& e)
+  {
+    e.next_in_sel = (sel_ ? sel_ : nullptr);
+    sel_ = &e;
+  }
+
+
+  inline bool ClipperBase::PopHorz(Active*& e)
+  {
+    e = sel_;
+    if (!e) return false;
+    sel_ = sel_->next_in_sel;
+    return true;
+  }
+
+
+  OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2,
+    const Point64& pt, bool is_new)
+  {
+    OutRec* outrec = new OutRec();
+    outrec->idx = (unsigned)outrec_list_.size();
+    outrec_list_.push_back(outrec);
+    outrec->pts = nullptr;
+    outrec->polypath = nullptr;
+    e1.outrec = outrec;
+    e2.outrec = outrec;
+
+    //Setting the owner and inner/outer states (above) is an essential
+    //precursor to setting edge 'sides' (ie left and right sides of output
+    //polygons) and hence the orientation of output paths ...
+
+    if (IsOpen(e1))
+    {
+      outrec->owner = nullptr;
+      outrec->is_open = true;
+      if (e1.wind_dx > 0)
+        SetSides(*outrec, e1, e2);
+      else
+        SetSides(*outrec, e2, e1);
+    }
+    else
+    {
+      Active* prevHotEdge = GetPrevHotEdge(e1);
+      //e.windDx is the winding direction of the **input** paths
+      //and unrelated to the winding direction of output polygons.
+      //Output orientation is determined by e.outrec.frontE which is
+      //the ascending edge (see AddLocalMinPoly).
+      if (prevHotEdge)
+      {
+        outrec->owner = prevHotEdge->outrec;
+        if (OutrecIsAscending(prevHotEdge) == is_new)
+          SetSides(*outrec, e2, e1);
+        else
+          SetSides(*outrec, e1, e2);
+      }
+      else
+      {
+        outrec->owner = nullptr;
+        if (is_new)
+          SetSides(*outrec, e1, e2);
+        else
+          SetSides(*outrec, e2, e1);
+      }
+    }
+
+    OutPt* op = new OutPt(pt, outrec);
+    outrec->pts = op;
+    return op;
+  }
+
+
+  OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt)
+  {
+    if (IsFront(e1) == IsFront(e2))
+    {
+      if (IsOpenEnd(e1))
+        SwapFrontBackSides(*e1.outrec);
+      else if (IsOpenEnd(e2))
+        SwapFrontBackSides(*e2.outrec);
+      else
+      {
+        succeeded_ = false;
+        return nullptr;
+      }
+    }
+
+    OutPt* result = AddOutPt(e1, pt);
+    if (e1.outrec == e2.outrec)
+    {
+      OutRec& outrec = *e1.outrec;
+      outrec.pts = result;
+
+      UncoupleOutRec(e1);
+      if (!IsOpen(e1)) CleanCollinear(&outrec);
+      result = outrec.pts;
+      if (using_polytree_ && outrec.owner && !outrec.owner->front_edge)
+        outrec.owner = GetRealOutRec(outrec.owner->owner);
+    }
+    //and to preserve the winding orientation of outrec ...
+    else if (IsOpen(e1))
+    {
+      if (e1.wind_dx < 0)
+        JoinOutrecPaths(e1, e2);
+      else
+        JoinOutrecPaths(e2, e1);
+    }
+    else if (e1.outrec->idx < e2.outrec->idx)
+      JoinOutrecPaths(e1, e2);
+    else
+      JoinOutrecPaths(e2, e1);
+
+    return result;
+  }
+
+  void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2)
+  {
+    //join e2 outrec path onto e1 outrec path and then delete e2 outrec path
+    //pointers. (NB Only very rarely do the joining ends share the same coords.)
+    OutPt* p1_st = e1.outrec->pts;
+    OutPt* p2_st = e2.outrec->pts;
+    OutPt* p1_end = p1_st->next;
+    OutPt* p2_end = p2_st->next;
+    if (IsFront(e1))
+    {
+      p2_end->prev = p1_st;
+      p1_st->next = p2_end;
+      p2_st->next = p1_end;
+      p1_end->prev = p2_st;
+      e1.outrec->pts = p2_st;
+      e1.outrec->front_edge = e2.outrec->front_edge;
+      if (e1.outrec->front_edge)
+        e1.outrec->front_edge->outrec = e1.outrec;
+    }
+    else
+    {
+      p1_end->prev = p2_st;
+      p2_st->next = p1_end;
+      p1_st->next = p2_end;
+      p2_end->prev = p1_st;
+      e1.outrec->back_edge = e2.outrec->back_edge;
+      if (e1.outrec->back_edge)
+        e1.outrec->back_edge->outrec = e1.outrec;
+    }
+
+    //an owner must have a lower idx otherwise
+    //it can't be a valid owner
+    if (e2.outrec->owner && e2.outrec->owner->idx < e1.outrec->idx)
+    {
+      if (!e1.outrec->owner || e2.outrec->owner->idx < e1.outrec->owner->idx)
+        e1.outrec->owner = e2.outrec->owner;
+    }
+
+    //after joining, the e2.OutRec must contains no vertices ...
+    e2.outrec->front_edge = nullptr;
+    e2.outrec->back_edge = nullptr;
+    e2.outrec->pts = nullptr;
+    e2.outrec->owner = e1.outrec;
+
+    if (IsOpenEnd(e1))
+    {
+      e2.outrec->pts = e1.outrec->pts;
+      e1.outrec->pts = nullptr;
+    }
+
+    //and e1 and e2 are maxima and are about to be dropped from the Actives list.
+    e1.outrec = nullptr;
+    e2.outrec = nullptr;
+  }
+
+
+  OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt)
+  {
+    OutPt* new_op = nullptr;
+
+    //Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
+    //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next
+    OutRec* outrec = e.outrec;
+    bool to_front = IsFront(e);
+    OutPt* op_front = outrec->pts;
+    OutPt* op_back = op_front->next;
+
+    if (to_front)
+    {
+      if (pt == op_front->pt)
+        return op_front;
+    }
+    else if (pt == op_back->pt)
+      return op_back;
+
+    new_op = new OutPt(pt, outrec);
+    op_back->prev = new_op;
+    new_op->prev = op_front;
+    new_op->next = op_back;
+    op_front->next = new_op;
+    if (to_front) outrec->pts = new_op;
+    return new_op;
+  }
+
+
+  bool ClipperBase::ValidateClosedPathEx(OutPt*& outpt)
+  {
+    if (IsValidClosedPath(outpt)) return true;
+    if (outpt) SafeDisposeOutPts(outpt);
+    return false;
+  }
+
+
+  void ClipperBase::CleanCollinear(OutRec* outrec)
+  {
+    outrec = GetRealOutRec(outrec);
+    if (!outrec || outrec->is_open ||
+      outrec->front_edge || !ValidateClosedPathEx(outrec->pts)) return;
+
+    OutPt* startOp = outrec->pts, * op2 = startOp;
+    for (; ; )
+    {
+      if (op2->joiner) return;
+
+      //NB if preserveCollinear == true, then only remove 180 deg. spikes
+      if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) &&
+        (op2->pt == op2->prev->pt ||
+          op2->pt == op2->next->pt || !PreserveCollinear ||
+          DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0))
+      {
+
+        if (op2 == outrec->pts) outrec->pts = op2->prev;
+
+        op2 = DisposeOutPt(op2);
+        if (!ValidateClosedPathEx(op2))
+        {
+          outrec->pts = nullptr;
+          return;
+        }
+        startOp = op2;
+        continue;
+      }
+      op2 = op2->next;
+      if (op2 == startOp) break;
+    }
+    FixSelfIntersects(outrec);
+  }
+
+  void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp)
+  {
+    // splitOp.prev -> splitOp && 
+    // splitOp.next -> splitOp.next.next are intersecting
+    OutPt* prevOp = splitOp->prev;
+    OutPt* nextNextOp = splitOp->next->next;
+    outrec->pts = prevOp;
+
+    Point64 ip;
+    GetIntersectPoint(prevOp->pt, splitOp->pt,
+      splitOp->next->pt, nextNextOp->pt, ip);
+
+#ifdef USINGZ
+    if (zCallback_) zCallback_(prevOp->pt, splitOp->pt,
+      splitOp->next->pt, nextNextOp->pt, ip);
+#endif
+    double area1 = Area(outrec->pts);
+    double absArea1 = std::fabs(area1);
+    if (absArea1 < 2)
+    {
+      SafeDisposeOutPts(outrec->pts);
+      return;
+    }
+
+    // nb: area1 is the path's area *before* splitting, whereas area2 is
+    // the area of the triangle containing splitOp & splitOp.next.
+    // So the only way for these areas to have the same sign is if
+    // the split triangle is larger than the path containing prevOp or
+    // if there's more than one self=intersection.
+    double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt);
+    double absArea2 = std::fabs(area2);
+
+    // de-link splitOp and splitOp.next from the path
+    // while inserting the intersection point
+    if (ip == prevOp->pt || ip == nextNextOp->pt)
+    {
+      nextNextOp->prev = prevOp;
+      prevOp->next = nextNextOp;
+    }
+    else
+    {
+      OutPt* newOp2 = new OutPt(ip, prevOp->outrec);
+      newOp2->prev = prevOp;
+      newOp2->next = nextNextOp;
+      nextNextOp->prev = newOp2;
+      prevOp->next = newOp2;
+    }
+
+    SafeDeleteOutPtJoiners(splitOp->next);
+    SafeDeleteOutPtJoiners(splitOp);
+
+    if (absArea2 >= 1 &&
+      (absArea2 > absArea1 || (area2 > 0) == (area1 > 0)))
+    {
+      OutRec* newOutRec = new OutRec();
+      newOutRec->idx = outrec_list_.size();
+      outrec_list_.push_back(newOutRec);
+      newOutRec->owner = prevOp->outrec->owner;
+      newOutRec->polypath = nullptr;
+      splitOp->outrec = newOutRec;
+      splitOp->next->outrec = newOutRec;
+
+      OutPt* newOp = new OutPt(ip, newOutRec);
+      newOp->prev = splitOp->next;
+      newOp->next = splitOp;
+      newOutRec->pts = newOp;
+      splitOp->prev = newOp;
+      splitOp->next->next = newOp;
+    }
+    else
+    {
+      delete splitOp->next;
+      delete splitOp;
+    }
+  }
+
+  void ClipperBase::FixSelfIntersects(OutRec* outrec)
+  {
+    OutPt* op2 = outrec->pts;
+    for (; ; )
+    {
+      // triangles can't self-intersect
+      if (op2->prev == op2->next->next) break;
+      if (SegmentsIntersect(op2->prev->pt,
+        op2->pt, op2->next->pt, op2->next->next->pt))
+      {
+        if (op2 == outrec->pts || op2->next == outrec->pts)
+          outrec->pts = outrec->pts->prev;
+        DoSplitOp(outrec, op2);
+        if (!outrec->pts) break;
+        op2 = outrec->pts;
+        continue;
+      }
+      else
+        op2 = op2->next;
+
+      if (op2 == outrec->pts) break;
+    }
+  }
+
+
+  inline void UpdateOutrecOwner(OutRec* outrec)
+  {
+    OutPt* opCurr = outrec->pts;
+    for (; ; )
+    {
+      opCurr->outrec = outrec;
+      opCurr = opCurr->next;
+      if (opCurr == outrec->pts) return;
+    }
+  }
+
+
+  void ClipperBase::SafeDisposeOutPts(OutPt*& op)
+  {
+    OutRec* outrec = GetRealOutRec(op->outrec);
+    if (outrec->front_edge)
+      outrec->front_edge->outrec = nullptr;
+    if (outrec->back_edge)
+      outrec->back_edge->outrec = nullptr;
+
+    op->prev->next = nullptr;
+    while (op)
+    {
+      SafeDeleteOutPtJoiners(op);
+      OutPt* tmp = op->next;
+      delete op;
+      op = tmp;
+    }
+    outrec->pts = nullptr;
+  }
+
+
+  void ClipperBase::CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec)
+  {
+    double area1 = Area(op1);
+    double area2 = Area(op2);
+    bool signs_change = (area1 > 0) == (area2 < 0);
+
+    if (area1 == 0 || (signs_change && std::abs(area1) < 2))
+    {
+      SafeDisposeOutPts(op1);
+      outrec.pts = op2;
+    }
+    else if (area2 == 0 || (signs_change && std::abs(area2) < 2))
+    {
+      SafeDisposeOutPts(op2);
+      outrec.pts = op1;
+    }
+    else
+    {
+      OutRec* newOr = new OutRec();
+      newOr->idx = outrec_list_.size();
+      outrec_list_.push_back(newOr);
+      newOr->polypath = nullptr;
+
+      if (using_polytree_)
+      {
+        if (!outrec.splits) outrec.splits = new OutRecList();
+        outrec.splits->push_back(newOr);
+      }
+
+      if (std::abs(area1) >= std::abs(area2))
+      {
+        outrec.pts = op1;
+        newOr->pts = op2;
+      }
+      else
+      {
+        outrec.pts = op2;
+        newOr->pts = op1;
+      }
+
+      if ((area1 > 0) == (area2 > 0))
+        newOr->owner = outrec.owner;
+      else
+        newOr->owner = &outrec;
+
+      UpdateOutrecOwner(newOr);
+      CleanCollinear(newOr);
+    }
+  }
+
+
+  OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt)
+  {
+    OutRec* outrec = new OutRec();
+    outrec->idx = outrec_list_.size();
+    outrec_list_.push_back(outrec);
+    outrec->owner = nullptr;
+    outrec->is_open = true;
+    outrec->pts = nullptr;
+    outrec->polypath = nullptr;
+
+    if (e.wind_dx > 0)
+    {
+      outrec->front_edge = &e;
+      outrec->back_edge = nullptr;
+    }
+    else
+    {
+      outrec->front_edge = nullptr;
+      outrec->back_edge = &e;
+    }
+
+    e.outrec = outrec;
+
+    OutPt* op = new OutPt(pt, outrec);
+    outrec->pts = op;
+    return op;
+  }
+
+
+  inline void ClipperBase::UpdateEdgeIntoAEL(Active* e)
+  {
+    e->bot = e->top;
+    e->vertex_top = NextVertex(*e);
+    e->top = e->vertex_top->pt;
+    e->curr_x = e->bot.x;
+    SetDx(*e);
+    if (IsHorizontal(*e)) return;
+    InsertScanline(e->top.y);
+    if (TestJoinWithPrev1(*e))
+    {
+      OutPt* op1 = AddOutPt(*e->prev_in_ael, e->bot);
+      OutPt* op2 = AddOutPt(*e, e->bot);
+      AddJoin(op1, op2);
+    }
+  }
+
+
+  Active* FindEdgeWithMatchingLocMin(Active* e)
+  {
+    Active* result = e->next_in_ael;
+    while (result)
+    {
+      if (result->local_min == e->local_min) return result;
+      else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr;
+      else result = result->next_in_ael;
+    }
+    result = e->prev_in_ael;
+    while (result)
+    {
+      if (result->local_min == e->local_min) return result;
+      else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr;
+      else result = result->prev_in_ael;
+    }
+    return result;
+  }
+
+
+  OutPt* ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt)
+  {
+    //MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
+    if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2)))
+    {
+      if (IsOpen(e1) && IsOpen(e2)) return nullptr;
+
+      Active* edge_o, * edge_c;
+      if (IsOpen(e1))
+      {
+        edge_o = &e1;
+        edge_c = &e2;
+      }
+      else
+      {
+        edge_o = &e2;
+        edge_c = &e1;
+      }
+
+      if (abs(edge_c->wind_cnt) != 1) return nullptr;
+      switch (cliptype_)
+      {
+      case ClipType::Union:
+        if (!IsHotEdge(*edge_c)) return nullptr;
+        break;
+      default:
+        if (edge_c->local_min->polytype == PathType::Subject)
+          return nullptr;
+      }
+
+      switch (fillrule_)
+      {
+      case FillRule::Positive: if (edge_c->wind_cnt != 1) return nullptr; break;
+      case FillRule::Negative: if (edge_c->wind_cnt != -1) return nullptr; break;
+      default: if (std::abs(edge_c->wind_cnt) != 1) return nullptr; break;
+      }
+
+      //toggle contribution ...
+      if (IsHotEdge(*edge_o))
+      {
+        OutPt* resultOp = AddOutPt(*edge_o, pt);
+#ifdef USINGZ
+        if (zCallback_) SetZ(e1, e2, resultOp->pt);
+#endif
+        if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr;
+        else edge_o->outrec->back_edge = nullptr;
+        edge_o->outrec = nullptr;
+        return resultOp;
+      }
+
+      //horizontal edges can pass under open paths at a LocMins
+      else if (pt == edge_o->local_min->vertex->pt &&
+        !IsOpenEnd(*edge_o->local_min->vertex))
+      {
+        //find the other side of the LocMin and
+        //if it's 'hot' join up with it ...
+        Active* e3 = FindEdgeWithMatchingLocMin(edge_o);
+        if (e3 && IsHotEdge(*e3))
+        {
+          edge_o->outrec = e3->outrec;
+          if (edge_o->wind_dx > 0)
+            SetSides(*e3->outrec, *edge_o, *e3);
+          else
+            SetSides(*e3->outrec, *e3, *edge_o);
+          return e3->outrec->pts;
+        }
+        else
+          return StartOpenPath(*edge_o, pt);
+      }
+      else
+        return StartOpenPath(*edge_o, pt);
+    }
+
+
+    //MANAGING CLOSED PATHS FROM HERE ON
+
+    //UPDATE WINDING COUNTS...
+
+    int old_e1_windcnt, old_e2_windcnt;
+    if (e1.local_min->polytype == e2.local_min->polytype)
+    {
+      if (fillrule_ == FillRule::EvenOdd)
+      {
+        old_e1_windcnt = e1.wind_cnt;
+        e1.wind_cnt = e2.wind_cnt;
+        e2.wind_cnt = old_e1_windcnt;
+      }
+      else
+      {
+        if (e1.wind_cnt + e2.wind_dx == 0)
+          e1.wind_cnt = -e1.wind_cnt;
+        else
+          e1.wind_cnt += e2.wind_dx;
+        if (e2.wind_cnt - e1.wind_dx == 0)
+          e2.wind_cnt = -e2.wind_cnt;
+        else
+          e2.wind_cnt -= e1.wind_dx;
+      }
+    }
+    else
+    {
+      if (fillrule_ != FillRule::EvenOdd)
+      {
+        e1.wind_cnt2 += e2.wind_dx;
+        e2.wind_cnt2 -= e1.wind_dx;
+      }
+      else
+      {
+        e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0);
+        e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0);
+      }
+    }
+
+    switch (fillrule_)
+    {
+    case FillRule::EvenOdd:
+    case FillRule::NonZero:
+      old_e1_windcnt = abs(e1.wind_cnt);
+      old_e2_windcnt = abs(e2.wind_cnt);
+      break;
+    default:
+      if (fillrule_ == fillpos)
+      {
+        old_e1_windcnt = e1.wind_cnt;
+        old_e2_windcnt = e2.wind_cnt;
+      }
+      else
+      {
+        old_e1_windcnt = -e1.wind_cnt;
+        old_e2_windcnt = -e2.wind_cnt;
+      }
+      break;
+    }
+
+    const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1;
+    const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1;
+
+    if ((!IsHotEdge(e1) && !e1_windcnt_in_01) || (!IsHotEdge(e2) && !e2_windcnt_in_01))
+    {
+      return nullptr;
+    }
+
+    //NOW PROCESS THE INTERSECTION ...
+    OutPt* resultOp = nullptr;
+    //if both edges are 'hot' ...
+    if (IsHotEdge(e1) && IsHotEdge(e2))
+    {
+      if ((old_e1_windcnt != 0 && old_e1_windcnt != 1) || (old_e2_windcnt != 0 && old_e2_windcnt != 1) ||
+        (e1.local_min->polytype != e2.local_min->polytype && cliptype_ != ClipType::Xor))
+      {
+        resultOp = AddLocalMaxPoly(e1, e2, pt);
+#ifdef USINGZ
+        if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);
+#endif
+      }
+      else if (IsFront(e1) || (e1.outrec == e2.outrec))
+      {
+        //this 'else if' condition isn't strictly needed but
+        //it's sensible to split polygons that ony touch at
+        //a common vertex (not at common edges).
+
+        resultOp = AddLocalMaxPoly(e1, e2, pt);
+        OutPt* op2 = AddLocalMinPoly(e1, e2, pt);
+#ifdef USINGZ
+        if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);
+        if (zCallback_) SetZ(e1, e2, op2->pt);
+#endif
+        if (resultOp && resultOp->pt == op2->pt &&
+          !IsHorizontal(e1) && !IsHorizontal(e2) &&
+          (CrossProduct(e1.bot, resultOp->pt, e2.bot) == 0))
+          AddJoin(resultOp, op2);
+      }
+      else
+      {
+        resultOp = AddOutPt(e1, pt);
+#ifdef USINGZ
+        OutPt* op2 = AddOutPt(e2, pt);
+        if (zCallback_)
+        {
+          SetZ(e1, e2, resultOp->pt);
+          SetZ(e1, e2, op2->pt);
+        }
+#else
+        AddOutPt(e2, pt);
+#endif
+        SwapOutrecs(e1, e2);
+      }
+    }
+    else if (IsHotEdge(e1))
+    {
+      resultOp = AddOutPt(e1, pt);
+#ifdef USINGZ
+      if (zCallback_) SetZ(e1, e2, resultOp->pt);
+#endif
+      SwapOutrecs(e1, e2);
+    }
+    else if (IsHotEdge(e2))
+    {
+      resultOp = AddOutPt(e2, pt);
+#ifdef USINGZ
+      if (zCallback_) SetZ(e1, e2, resultOp->pt);
+#endif
+      SwapOutrecs(e1, e2);
+    }
+    else
+    {
+      int64_t e1Wc2, e2Wc2;
+      switch (fillrule_)
+      {
+      case FillRule::EvenOdd:
+      case FillRule::NonZero:
+        e1Wc2 = abs(e1.wind_cnt2);
+        e2Wc2 = abs(e2.wind_cnt2);
+        break;
+      default:
+        if (fillrule_ == fillpos)
+        {
+          e1Wc2 = e1.wind_cnt2;
+          e2Wc2 = e2.wind_cnt2;
+        }
+        else
+        {
+          e1Wc2 = -e1.wind_cnt2;
+          e2Wc2 = -e2.wind_cnt2;
+        }
+        break;
+      }
+
+      if (!IsSamePolyType(e1, e2))
+      {
+        resultOp = AddLocalMinPoly(e1, e2, pt, false);
+#ifdef USINGZ
+        if (zCallback_) SetZ(e1, e2, resultOp->pt);
+#endif
+      }
+      else if (old_e1_windcnt == 1 && old_e2_windcnt == 1)
+      {
+        resultOp = nullptr;
+        switch (cliptype_)
+        {
+        case ClipType::Union:
+          if (e1Wc2 <= 0 && e2Wc2 <= 0)
+            resultOp = AddLocalMinPoly(e1, e2, pt, false);
+          break;
+        case ClipType::Difference:
+          if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
+            ((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
+          {
+            resultOp = AddLocalMinPoly(e1, e2, pt, false);
+          }
+          break;
+        case ClipType::Xor:
+          resultOp = AddLocalMinPoly(e1, e2, pt, false);
+          break;
+        default:
+          if (e1Wc2 > 0 && e2Wc2 > 0)
+            resultOp = AddLocalMinPoly(e1, e2, pt, false);
+          break;
+        }
+#ifdef USINGZ
+        if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt);
+#endif
+      }
+    }
+    return resultOp;
+  }
+
+
+  inline void ClipperBase::DeleteFromAEL(Active& e)
+  {
+    Active* prev = e.prev_in_ael;
+    Active* next = e.next_in_ael;
+    if (!prev && !next && (&e != actives_)) return;  // already deleted
+    if (prev)
+      prev->next_in_ael = next;
+    else
+      actives_ = next;
+    if (next) next->prev_in_ael = prev;
+    delete& e;
+  }
+
+
+  inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y)
+  {
+    Active* e = actives_;
+    sel_ = e;
+    while (e)
+    {
+      e->prev_in_sel = e->prev_in_ael;
+      e->next_in_sel = e->next_in_ael;
+      e->jump = e->next_in_sel;
+      e->curr_x = TopX(*e, top_y);
+      e = e->next_in_ael;
+    }
+  }
+
+
+  bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees)
+  {
+    cliptype_ = ct;
+    fillrule_ = fillrule;
+    using_polytree_ = use_polytrees;
+    Reset();
+    int64_t y;
+    if (ct == ClipType::None || !PopScanline(y)) return true;
+
+    while (succeeded_)
+    {
+      InsertLocalMinimaIntoAEL(y);
+      Active* e;
+      while (PopHorz(e)) DoHorizontal(*e);
+      if (horz_joiners_) ConvertHorzTrialsToJoins();
+      bot_y_ = y;  // bot_y_ == bottom of scanbeam
+      if (!PopScanline(y)) break;  // y new top of scanbeam
+      DoIntersections(y);
+      DoTopOfScanbeam(y);
+      while (PopHorz(e)) DoHorizontal(*e);
+    }
+    ProcessJoinerList();
+    return succeeded_;
+  }
+
+  void ClipperBase::DoIntersections(const int64_t top_y)
+  {
+    if (BuildIntersectList(top_y))
+    {
+      ProcessIntersectList();
+      intersect_nodes_.clear();
+    }
+  }
+
+  void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
+  {
+    Point64 ip;
+    if (!GetIntersectPoint(e1.bot, e1.top, e2.bot, e2.top, ip))
+      ip = Point64(e1.curr_x, top_y); //parallel edges
+
+    //rounding errors can occasionally place the calculated intersection
+    //point either below or above the scanbeam, so check and correct ...
+    if (ip.y > bot_y_ || ip.y < top_y)
+    {
+      double abs_dx1 = std::fabs(e1.dx);
+      double abs_dx2 = std::fabs(e2.dx);
+      if (abs_dx1 > 100 && abs_dx2 > 100)
+      {
+        if (abs_dx1 > abs_dx2)
+          ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);
+        else
+          ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);
+      }
+      else if (abs_dx1 > 100)
+        ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);
+      else if (abs_dx2 > 100)
+        ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);
+      else 
+      {
+        if (ip.y < top_y) ip.y = top_y;
+        else ip.y = bot_y_;
+        if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y);
+        else ip.x = TopX(e2, ip.y);
+      }
+    }
+    intersect_nodes_.push_back(IntersectNode(&e1, &e2, ip));
+  }
+
+  bool ClipperBase::BuildIntersectList(const int64_t top_y)
+  {
+    if (!actives_ || !actives_->next_in_ael) return false;
+
+    //Calculate edge positions at the top of the current scanbeam, and from this
+    //we will determine the intersections required to reach these new positions.
+    AdjustCurrXAndCopyToSEL(top_y);
+    //Find all edge intersections in the current scanbeam using a stable merge
+    //sort that ensures only adjacent edges are intersecting. Intersect info is
+    //stored in FIntersectList ready to be processed in ProcessIntersectList.
+    //Re merge sorts see https://stackoverflow.com/a/46319131/359538
+
+    Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp;
+
+    while (left && left->jump)
+    {
+      Active* prev_base = nullptr;
+      while (left && left->jump)
+      {
+        curr_base = left;
+        right = left->jump;
+        l_end = right;
+        r_end = right->jump;
+        left->jump = r_end;
+        while (left != l_end && right != r_end)
+        {
+          if (right->curr_x < left->curr_x)
+          {
+            tmp = right->prev_in_sel;
+            for (; ; )
+            {
+              AddNewIntersectNode(*tmp, *right, top_y);
+              if (tmp == left) break;
+              tmp = tmp->prev_in_sel;
+            }
+
+            tmp = right;
+            right = ExtractFromSEL(tmp);
+            l_end = right;
+            Insert1Before2InSEL(tmp, left);
+            if (left == curr_base)
+            {
+              curr_base = tmp;
+              curr_base->jump = r_end;
+              if (!prev_base) sel_ = curr_base;
+              else prev_base->jump = curr_base;
+            }
+          }
+          else left = left->next_in_sel;
+        }
+        prev_base = curr_base;
+        left = r_end;
+      }
+      left = sel_;
+    }
+    return intersect_nodes_.size() > 0;
+  }
+
+  void ClipperBase::ProcessIntersectList()
+  {
+    //We now have a list of intersections required so that edges will be
+    //correctly positioned at the top of the scanbeam. However, it's important
+    //that edge intersections are processed from the bottom up, but it's also
+    //crucial that intersections only occur between adjacent edges.
+
+    //First we do a quicksort so intersections proceed in a bottom up order ...
+    std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort);
+    //Now as we process these intersections, we must sometimes adjust the order
+    //to ensure that intersecting edges are always adjacent ...
+
+    std::vector<IntersectNode>::iterator node_iter, node_iter2;
+    for (node_iter = intersect_nodes_.begin();
+      node_iter != intersect_nodes_.end();  ++node_iter)
+    {
+      if (!EdgesAdjacentInAEL(*node_iter))
+      {
+        node_iter2 = node_iter + 1;
+        while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2;
+        std::swap(*node_iter, *node_iter2);
+      }
+
+      IntersectNode& node = *node_iter;
+      IntersectEdges(*node.edge1, *node.edge2, node.pt);
+      SwapPositionsInAEL(*node.edge1, *node.edge2);
+
+      if (TestJoinWithPrev2(*node.edge2, node.pt))
+      {
+        OutPt* op1 = AddOutPt(*node.edge2->prev_in_ael, node.pt);
+        OutPt* op2 = AddOutPt(*node.edge2, node.pt);
+        if (op1 != op2) AddJoin(op1, op2);
+      }
+      else if (TestJoinWithNext2(*node.edge1, node.pt))
+      {
+        OutPt* op1 = AddOutPt(*node.edge1, node.pt);
+        OutPt* op2 = AddOutPt(*node.edge1->next_in_ael, node.pt);
+        if (op1 != op2) AddJoin(op1, op2);
+      }
+    }
+  }
+
+
+  void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2)
+  {
+    //preconditon: e1 must be immediately to the left of e2
+    Active* next = e2.next_in_ael;
+    if (next) next->prev_in_ael = &e1;
+    Active* prev = e1.prev_in_ael;
+    if (prev) prev->next_in_ael = &e2;
+    e2.prev_in_ael = prev;
+    e2.next_in_ael = &e1;
+    e1.prev_in_ael = &e2;
+    e1.next_in_ael = next;
+    if (!e2.prev_in_ael) actives_ = &e2;
+  }
+
+
+  bool ClipperBase::ResetHorzDirection(const Active& horz,
+    const Active* max_pair, int64_t& horz_left, int64_t& horz_right)
+  {
+    if (horz.bot.x == horz.top.x)
+    {
+      //the horizontal edge is going nowhere ...
+      horz_left = horz.curr_x;
+      horz_right = horz.curr_x;
+      Active* e = horz.next_in_ael;
+      while (e && e != max_pair) e = e->next_in_ael;
+      return e != nullptr;
+    }
+    else if (horz.curr_x < horz.top.x)
+    {
+      horz_left = horz.curr_x;
+      horz_right = horz.top.x;
+      return true;
+    }
+    else
+    {
+      horz_left = horz.top.x;
+      horz_right = horz.curr_x;
+      return false;  // right to left
+    }
+  }
+
+  inline bool HorzIsSpike(const Active& horzEdge)
+  {
+    Point64 nextPt = NextVertex(horzEdge)->pt;
+    return (nextPt.y == horzEdge.bot.y) &&
+      (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x);
+  }
+
+  inline void TrimHorz(Active& horzEdge, bool preserveCollinear)
+  {
+    bool wasTrimmed = false;
+    Point64 pt = NextVertex(horzEdge)->pt;
+    while (pt.y == horzEdge.top.y)
+    {
+      //always trim 180 deg. spikes (in closed paths)
+      //but otherwise break if preserveCollinear = true
+      if (preserveCollinear &&
+        ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x)))
+        break;
+
+      horzEdge.vertex_top = NextVertex(horzEdge);
+      horzEdge.top = pt;
+      wasTrimmed = true;
+      if (IsMaxima(horzEdge)) break;
+      pt = NextVertex(horzEdge)->pt;
+    }
+
+    if (wasTrimmed) SetDx(horzEdge); // +/-infinity
+  }
+
+
+  void ClipperBase::DoHorizontal(Active& horz)
+    /*******************************************************************************
+        * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or    *
+        * bottom of a scanbeam) are processed as if layered.The order in which HEs     *
+        * are processed doesn't matter. HEs intersect with the bottom vertices of      *
+        * other HEs[#] and with non-horizontal edges [*]. Once these intersections     *
+        * are completed, intermediate HEs are 'promoted' to the next edge in their     *
+        * bounds, and they in turn may be intersected[%] by other HEs.                 *
+        *                                                                              *
+        * eg: 3 horizontals at a scanline:    /   |                     /           /  *
+        *              |                     /    |     (HE3)o ========%========== o   *
+        *              o ======= o(HE2)     /     |         /         /                *
+        *          o ============#=========*======*========#=========o (HE1)           *
+        *         /              |        /       |       /                            *
+        *******************************************************************************/
+  {
+    Point64 pt;
+    bool horzIsOpen = IsOpen(horz);
+    int64_t y = horz.bot.y;
+    Vertex* vertex_max = nullptr;
+    Active* max_pair = nullptr;
+
+    if (!horzIsOpen)
+    {
+      vertex_max = GetCurrYMaximaVertex(horz);
+      if (vertex_max)
+      {
+        max_pair = GetHorzMaximaPair(horz, vertex_max);
+        //remove 180 deg.spikes and also simplify
+        //consecutive horizontals when PreserveCollinear = true
+        if (vertex_max != horz.vertex_top)
+          TrimHorz(horz, PreserveCollinear);
+      }
+    }
+
+    int64_t horz_left, horz_right;
+    bool is_left_to_right =
+      ResetHorzDirection(horz, max_pair, horz_left, horz_right);
+
+    if (IsHotEdge(horz))
+#ifdef USINGZ
+      AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z));
+#else
+      AddOutPt(horz, Point64(horz.curr_x, y));
+#endif
+
+    OutPt* op;
+    while (true) // loop through consec. horizontal edges
+    {
+      if (horzIsOpen && IsMaxima(horz) && !IsOpenEnd(horz))
+      {
+        vertex_max = GetCurrYMaximaVertex(horz);
+        if (vertex_max)
+          max_pair = GetHorzMaximaPair(horz, vertex_max);
+      }
+
+      Active* e;
+      if (is_left_to_right) e = horz.next_in_ael;
+      else e = horz.prev_in_ael;
+
+      while (e)
+      {
+
+        if (e == max_pair)
+        {
+          if (IsHotEdge(horz))
+          {
+            while (horz.vertex_top != e->vertex_top)
+            {
+              AddOutPt(horz, horz.top);
+              UpdateEdgeIntoAEL(&horz);
+            }
+            op = AddLocalMaxPoly(horz, *e, horz.top);
+            if (op && !IsOpen(horz) && op->pt == horz.top)
+              AddTrialHorzJoin(op);
+          }
+          DeleteFromAEL(*e);
+          DeleteFromAEL(horz);
+          return;
+        }
+
+        //if horzEdge is a maxima, keep going until we reach
+        //its maxima pair, otherwise check for break conditions
+        if (vertex_max != horz.vertex_top || IsOpenEnd(horz))
+        {
+          //otherwise stop when 'ae' is beyond the end of the horizontal line
+          if ((is_left_to_right && e->curr_x > horz_right) ||
+            (!is_left_to_right && e->curr_x < horz_left)) break;
+
+          if (e->curr_x == horz.top.x && !IsHorizontal(*e))
+          {
+            pt = NextVertex(horz)->pt;
+            if (is_left_to_right)
+            {
+              //with open paths we'll only break once past horz's end
+              if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e))
+              {
+                if (TopX(*e, pt.y) > pt.x) break;
+              }
+              //otherwise we'll only break when horz's outslope is greater than e's
+              else if (TopX(*e, pt.y) >= pt.x) break;
+            }
+            else
+            {
+              if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e))
+              {
+                if (TopX(*e, pt.y) < pt.x) break;
+              }
+              else if (TopX(*e, pt.y) <= pt.x) break;
+            }
+          }
+        }
+
+        pt = Point64(e->curr_x, horz.bot.y);
+
+        if (is_left_to_right)
+        {
+          op = IntersectEdges(horz, *e, pt);
+          SwapPositionsInAEL(horz, *e);
+          // todo: check if op->pt == pt test is still needed
+          // expect op != pt only after AddLocalMaxPoly when horz.outrec == nullptr
+          if (IsHotEdge(horz) && op && !IsOpen(horz) && op->pt == pt)
+            AddTrialHorzJoin(op);
+
+          if (!IsHorizontal(*e) && TestJoinWithPrev1(*e))
+          {
+            op = AddOutPt(*e->prev_in_ael, pt);
+            OutPt* op2 = AddOutPt(*e, pt);
+            AddJoin(op, op2);
+          }
+
+          horz.curr_x = e->curr_x;
+          e = horz.next_in_ael;
+        }
+        else
+        {
+          op = IntersectEdges(*e, horz, pt);
+          SwapPositionsInAEL(*e, horz);
+
+          if (IsHotEdge(horz) && op &&
+            !IsOpen(horz) && op->pt == pt)
+            AddTrialHorzJoin(op);
+
+          if (!IsHorizontal(*e) && TestJoinWithNext1(*e))
+          {
+            op = AddOutPt(*e, pt);
+            OutPt* op2 = AddOutPt(*e->next_in_ael, pt);
+            AddJoin(op, op2);
+          }
+
+          horz.curr_x = e->curr_x;
+          e = horz.prev_in_ael;
+        }
+      }
+
+      //check if we've finished with (consecutive) horizontals ...
+      if (horzIsOpen && IsOpenEnd(horz)) // ie open at top
+      {
+        if (IsHotEdge(horz))
+        {
+          AddOutPt(horz, horz.top);
+          if (IsFront(horz))
+            horz.outrec->front_edge = nullptr;
+          else
+            horz.outrec->back_edge = nullptr;
+          horz.outrec = nullptr;
+        }
+        DeleteFromAEL(horz);
+        return;
+      }
+      else if (NextVertex(horz)->pt.y != horz.top.y)
+        break;
+
+      //still more horizontals in bound to process ...
+      if (IsHotEdge(horz))
+        AddOutPt(horz, horz.top);
+      UpdateEdgeIntoAEL(&horz);
+
+      if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz))
+        TrimHorz(horz, true);
+
+      is_left_to_right =
+        ResetHorzDirection(horz, max_pair, horz_left, horz_right);
+    }
+
+    if (IsHotEdge(horz))
+    {
+      op = AddOutPt(horz, horz.top);
+      if (!IsOpen(horz))
+        AddTrialHorzJoin(op);
+    }
+    else
+      op = nullptr;
+
+    if ((horzIsOpen && !IsOpenEnd(horz)) ||
+      (!horzIsOpen && vertex_max != horz.vertex_top))
+    {
+      UpdateEdgeIntoAEL(&horz); // this is the end of an intermediate horiz.
+      if (IsOpen(horz)) return;
+
+      if (is_left_to_right && TestJoinWithNext1(horz))
+      {
+        OutPt* op2 = AddOutPt(*horz.next_in_ael, horz.bot);
+        AddJoin(op, op2);
+      }
+      else if (!is_left_to_right && TestJoinWithPrev1(horz))
+      {
+        OutPt* op2 = AddOutPt(*horz.prev_in_ael, horz.bot);
+        AddJoin(op2, op);
+      }
+    }
+    else if (IsHotEdge(horz))
+      AddLocalMaxPoly(horz, *max_pair, horz.top);
+    else
+    {
+      DeleteFromAEL(*max_pair);
+      DeleteFromAEL(horz);
+    }
+  }
+
+
+  void ClipperBase::DoTopOfScanbeam(const int64_t y)
+  {
+    sel_ = nullptr;  // sel_ is reused to flag horizontals (see PushHorz below)
+    Active* e = actives_;
+    while (e)
+    {
+      //nb: 'e' will never be horizontal here
+      if (e->top.y == y)
+      {
+        e->curr_x = e->top.x;
+        if (IsMaxima(*e))
+        {
+          e = DoMaxima(*e);  // TOP OF BOUND (MAXIMA)
+          continue;
+        }
+        else
+        {
+          //INTERMEDIATE VERTEX ...
+          if (IsHotEdge(*e)) AddOutPt(*e, e->top);
+          UpdateEdgeIntoAEL(e);
+          if (IsHorizontal(*e))
+            PushHorz(*e);  // horizontals are processed later
+        }
+      }
+      else // i.e. not the top of the edge
+        e->curr_x = TopX(*e, y);
+
+      e = e->next_in_ael;
+    }
+  }
+
+
+  Active* ClipperBase::DoMaxima(Active& e)
+  {
+    Active* next_e, * prev_e, * max_pair;
+    prev_e = e.prev_in_ael;
+    next_e = e.next_in_ael;
+    if (IsOpenEnd(e))
+    {
+      if (IsHotEdge(e)) AddOutPt(e, e.top);
+      if (!IsHorizontal(e))
+      {
+        if (IsHotEdge(e))
+        {
+          if (IsFront(e))
+            e.outrec->front_edge = nullptr;
+          else
+            e.outrec->back_edge = nullptr;
+          e.outrec = nullptr;
+        }
+        DeleteFromAEL(e);
+      }
+      return next_e;
+    }
+    else
+    {
+      max_pair = GetMaximaPair(e);
+      if (!max_pair) return next_e;  // eMaxPair is horizontal
+    }
+
+    //only non-horizontal maxima here.
+    //process any edges between maxima pair ...
+    while (next_e != max_pair)
+    {
+      IntersectEdges(e, *next_e, e.top);
+      SwapPositionsInAEL(e, *next_e);
+      next_e = e.next_in_ael;
+    }
+
+    if (IsOpen(e))
+    {
+      if (IsHotEdge(e))
+        AddLocalMaxPoly(e, *max_pair, e.top);
+      DeleteFromAEL(*max_pair);
+      DeleteFromAEL(e);
+      return (prev_e ? prev_e->next_in_ael : actives_);
+    }
+
+    //here E.next_in_ael == ENext == EMaxPair ...
+    if (IsHotEdge(e))
+      AddLocalMaxPoly(e, *max_pair, e.top);
+
+    DeleteFromAEL(e);
+    DeleteFromAEL(*max_pair);
+    return (prev_e ? prev_e->next_in_ael : actives_);
+  }
+
+
+  void ClipperBase::SafeDeleteOutPtJoiners(OutPt* op)
+  {
+    Joiner* joiner = op->joiner;
+    if (!joiner) return;
+
+    while (joiner)
+    {
+      if (joiner->idx < 0)
+        DeleteTrialHorzJoin(op);
+      else if (horz_joiners_)
+      {
+        if (OutPtInTrialHorzList(joiner->op1))
+          DeleteTrialHorzJoin(joiner->op1);
+        if (OutPtInTrialHorzList(joiner->op2))
+          DeleteTrialHorzJoin(joiner->op2);
+        DeleteJoin(joiner);
+      }
+      else
+        DeleteJoin(joiner);
+      joiner = op->joiner;
+    }
+  }
+
+
+  Joiner* ClipperBase::GetHorzTrialParent(const OutPt* op)
+  {
+    Joiner* joiner = op->joiner;
+    while (joiner)
+    {
+      if (joiner->op1 == op)
+      {
+        if (joiner->next1 && joiner->next1->idx < 0) return joiner;
+        else joiner = joiner->next1;
+      }
+      else
+      {
+        if (joiner->next2 && joiner->next2->idx < 0) return joiner;
+        else joiner = joiner->next1;
+      }
+    }
+    return joiner;
+  }
+
+
+  bool ClipperBase::OutPtInTrialHorzList(OutPt* op)
+  {
+    return op->joiner && ((op->joiner->idx < 0) || GetHorzTrialParent(op));
+  }
+
+
+  void ClipperBase::AddTrialHorzJoin(OutPt* op)
+  {
+    //make sure 'op' isn't added more than once
+    if (!op->outrec->is_open && !OutPtInTrialHorzList(op))
+      horz_joiners_ = new Joiner(op, nullptr, horz_joiners_);
+  }
+
+
+  Joiner* FindTrialJoinParent(Joiner*& joiner, const OutPt* op)
+  {
+    Joiner* parent = joiner;
+    while (parent)
+    {
+      if (op == parent->op1)
+      {
+        if (parent->next1 && parent->next1->idx < 0)
+        {
+          joiner = parent->next1;
+          return parent;
+        }
+        parent = parent->next1;
+      }
+      else
+      {
+        if (parent->next2 && parent->next2->idx < 0)
+        {
+          joiner = parent->next2;
+          return parent;
+        }
+        parent = parent->next2;
+      }
+    }
+    return nullptr;
+  }
+
+
+  void ClipperBase::DeleteTrialHorzJoin(OutPt* op)
+  {
+    if (!horz_joiners_) return;
+
+    Joiner* joiner = op->joiner;
+    Joiner* parentH, * parentOp = nullptr;
+    while (joiner)
+    {
+      if (joiner->idx < 0)
+      {
+        //first remove joiner from FHorzTrials
+        if (joiner == horz_joiners_)
+          horz_joiners_ = joiner->nextH;
+        else
+        {
+          parentH = horz_joiners_;
+          while (parentH->nextH != joiner)
+            parentH = parentH->nextH;
+          parentH->nextH = joiner->nextH;
+        }
+
+        //now remove joiner from op's joiner list
+        if (!parentOp)
+        {
+          //joiner must be first one in list
+          op->joiner = joiner->next1;
+          delete joiner;
+          joiner = op->joiner;
+        }
+        else
+        {
+          //the trial joiner isn't first
+          if (op == parentOp->op1)
+            parentOp->next1 = joiner->next1;
+          else
+            parentOp->next2 = joiner->next1;
+          delete joiner;
+          joiner = parentOp;
+        }
+      }
+      else
+      {
+        //not a trial join so look further along the linked list
+        parentOp = FindTrialJoinParent(joiner, op);
+        if (!parentOp) break;
+      }
+      //loop in case there's more than one trial join
+    }
+  }
+
+
+  inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2)
+  {
+    OutRec* outrec = GetRealOutRec(op->outrec);
+    op2 = op;
+    if (outrec->front_edge)
+    {
+      while (op->prev != outrec->pts &&
+        op->prev->pt.y == op->pt.y) op = op->prev;
+      while (op2 != outrec->pts &&
+        op2->next->pt.y == op2->pt.y) op2 = op2->next;
+      return op2 != op;
+    }
+    else
+    {
+      while (op->prev != op2 && op->prev->pt.y == op->pt.y)
+        op = op->prev;
+      while (op2->next != op && op2->next->pt.y == op2->pt.y)
+        op2 = op2->next;
+      return op2 != op && op2->next != op;
+    }
+  }
+
+
+  inline bool HorzEdgesOverlap(int64_t x1a, int64_t x1b, int64_t x2a, int64_t x2b)
+  {
+    const int64_t minOverlap = 2;
+    if (x1a > x1b + minOverlap)
+    {
+      if (x2a > x2b + minOverlap)
+        return !((x1a <= x2b) || (x2a <= x1b));
+      else
+        return !((x1a <= x2a) || (x2b <= x1b));
+    }
+    else if (x1b > x1a + minOverlap)
+    {
+      if (x2a > x2b + minOverlap)
+        return !((x1b <= x2b) || (x2a <= x1a));
+      else
+        return !((x1b <= x2a) || (x2b <= x1a));
+    }
+    else
+      return false;
+  }
+
+
+  inline bool ValueBetween(int64_t val, int64_t end1, int64_t end2)
+  {
+    //NB accommodates axis aligned between where end1 == end2
+    return ((val != end1) == (val != end2)) &&
+      ((val > end1) == (val < end2));
+  }
+
+
+  inline bool ValueEqualOrBetween(int64_t val, int64_t end1, int64_t end2)
+  {
+    return (val == end1) || (val == end2) || ((val > end1) == (val < end2));
+  }
+
+
+  inline bool PointBetween(Point64 pt, Point64 corner1, Point64 corner2)
+  {
+    //NB points may not be collinear
+    return ValueBetween(pt.x, corner1.x, corner2.x) &&
+      ValueBetween(pt.y, corner1.y, corner2.y);
+  }
+
+  inline bool PointEqualOrBetween(Point64 pt, Point64 corner1, Point64 corner2)
+  {
+    //NB points may not be collinear
+    return ValueEqualOrBetween(pt.x, corner1.x, corner2.x) &&
+      ValueEqualOrBetween(pt.y, corner1.y, corner2.y);
+  }
+
+
+  Joiner* FindJoinParent(const Joiner* joiner, OutPt* op)
+  {
+    Joiner* result = op->joiner;
+    for (; ; )
+    {
+      if (op == result->op1)
+      {
+        if (result->next1 == joiner) return result;
+        else result = result->next1;
+      }
+      else
+      {
+        if (result->next2 == joiner) return result;
+        else result = result->next2;
+      }
+    }
+  }
+
+
+  void ClipperBase::ConvertHorzTrialsToJoins()
+  {
+    while (horz_joiners_)
+    {
+      Joiner* joiner = horz_joiners_;
+      horz_joiners_ = horz_joiners_->nextH;
+      OutPt* op1a = joiner->op1;
+      if (op1a->joiner == joiner)
+      {
+        op1a->joiner = joiner->next1;
+      }
+      else
+      {
+        Joiner* joinerParent = FindJoinParent(joiner, op1a);
+        if (joinerParent->op1 == op1a)
+          joinerParent->next1 = joiner->next1;
+        else
+          joinerParent->next2 = joiner->next1;
+      }
+      delete joiner;
+
+      OutPt* op1b;
+      if (!GetHorzExtendedHorzSeg(op1a, op1b))
+      {
+        CleanCollinear(op1a->outrec);
+        continue;
+      }
+
+      bool joined = false;
+      joiner = horz_joiners_;
+      while (joiner)
+      {
+        OutPt* op2a = joiner->op1, * op2b;
+        if (GetHorzExtendedHorzSeg(op2a, op2b) &&
+          HorzEdgesOverlap(op1a->pt.x, op1b->pt.x, op2a->pt.x, op2b->pt.x))
+        {
+          //overlap found so promote to a 'real' join
+          joined = true;
+          if (op1a->pt == op2b->pt)
+            AddJoin(op1a, op2b);
+          else if (op1b->pt == op2a->pt)
+            AddJoin(op1b, op2a);
+          else if (op1a->pt == op2a->pt)
+            AddJoin(op1a, op2a);
+          else if (op1b->pt == op2b->pt)
+            AddJoin(op1b, op2b);
+          else if (ValueBetween(op1a->pt.x, op2a->pt.x, op2b->pt.x))
+            AddJoin(op1a, InsertOp(op1a->pt, op2a));
+          else if (ValueBetween(op1b->pt.x, op2a->pt.x, op2b->pt.x))
+            AddJoin(op1b, InsertOp(op1b->pt, op2a));
+          else if (ValueBetween(op2a->pt.x, op1a->pt.x, op1b->pt.x))
+            AddJoin(op2a, InsertOp(op2a->pt, op1a));
+          else if (ValueBetween(op2b->pt.x, op1a->pt.x, op1b->pt.x))
+            AddJoin(op2b, InsertOp(op2b->pt, op1a));
+          break;
+        }
+        joiner = joiner->nextH;
+      }
+      if (!joined)
+        CleanCollinear(op1a->outrec);
+    }
+  }
+
+
+  void ClipperBase::AddJoin(OutPt* op1, OutPt* op2)
+  {
+    if ((op1->outrec == op2->outrec) && ((op1 == op2) ||
+      //unless op1.next or op1.prev crosses the start-end divide
+      //don't waste time trying to join adjacent vertices
+      ((op1->next == op2) && (op1 != op1->outrec->pts)) ||
+      ((op2->next == op1) && (op2 != op1->outrec->pts)))) return;
+
+    Joiner* j = new Joiner(op1, op2, nullptr);
+    j->idx = static_cast<int>(joiner_list_.size());
+    joiner_list_.push_back(j);
+  }
+
+
+  void ClipperBase::DeleteJoin(Joiner* joiner)
+  {
+    //This method deletes a single join, and it doesn't check for or
+    //delete trial horz. joins. For that, use the following method.
+    OutPt* op1 = joiner->op1, * op2 = joiner->op2;
+
+    Joiner* parent_joiner;
+    if (op1->joiner != joiner)
+    {
+      parent_joiner = FindJoinParent(joiner, op1);
+      if (parent_joiner->op1 == op1)
+        parent_joiner->next1 = joiner->next1;
+      else
+        parent_joiner->next2 = joiner->next1;
+    }
+    else
+      op1->joiner = joiner->next1;
+
+    if (op2->joiner != joiner)
+    {
+      parent_joiner = FindJoinParent(joiner, op2);
+      if (parent_joiner->op1 == op2)
+        parent_joiner->next1 = joiner->next2;
+      else
+        parent_joiner->next2 = joiner->next2;
+    }
+    else
+      op2->joiner = joiner->next2;
+
+    joiner_list_[joiner->idx] = nullptr;
+    delete joiner;
+  }
+
+
+  void ClipperBase::ProcessJoinerList()
+  {
+    for (Joiner* j : joiner_list_)
+    {
+      if (!j) continue;
+      if (succeeded_)
+      {
+        OutRec* outrec = ProcessJoin(j);
+        CleanCollinear(outrec);
+      }
+      else
+        delete j;
+    }
+
+    joiner_list_.resize(0);
+  }
+
+
+  bool CheckDisposeAdjacent(OutPt*& op, const OutPt* guard, OutRec& outRec)
+  {
+    bool result = false;
+    while (op->prev != op)
+    {
+      if (op->pt == op->prev->pt && op != guard &&
+        op->prev->joiner && !op->joiner)
+      {
+        if (op == outRec.pts) outRec.pts = op->prev;
+        op = DisposeOutPt(op);
+        op = op->prev;
+      }
+      else
+        break;
+    }
+
+    while (op->next != op)
+    {
+      if (op->pt == op->next->pt && op != guard &&
+        op->next->joiner && !op->joiner)
+      {
+        if (op == outRec.pts) outRec.pts = op->prev;
+        op = DisposeOutPt(op);
+        op = op->prev;
+      }
+      else
+        break;
+    }
+    return result;
+  }
+
+
+  inline bool IsValidPath(OutPt* op)
+  {
+    return (op && op->next != op);
+  }
+
+
+  bool CollinearSegsOverlap(const Point64& seg1a, const Point64& seg1b,
+    const Point64& seg2a, const Point64& seg2b)
+  {
+    //precondition: seg1 and seg2 are collinear      
+    if (seg1a.x == seg1b.x)
+    {
+      if (seg2a.x != seg1a.x || seg2a.x != seg2b.x) return false;
+    }
+    else if (seg1a.x < seg1b.x)
+    {
+      if (seg2a.x < seg2b.x)
+      {
+        if (seg2a.x >= seg1b.x || seg2b.x <= seg1a.x) return false;
+      }
+      else
+      {
+        if (seg2b.x >= seg1b.x || seg2a.x <= seg1a.x) return false;
+      }
+    }
+    else
+    {
+      if (seg2a.x < seg2b.x)
+      {
+        if (seg2a.x >= seg1a.x || seg2b.x <= seg1b.x) return false;
+      }
+      else
+      {
+        if (seg2b.x >= seg1a.x || seg2a.x <= seg1b.x) return false;
+      }
+    }
+
+    if (seg1a.y == seg1b.y)
+    {
+      if (seg2a.y != seg1a.y || seg2a.y != seg2b.y) return false;
+    }
+    else if (seg1a.y < seg1b.y)
+    {
+      if (seg2a.y < seg2b.y)
+      {
+        if (seg2a.y >= seg1b.y || seg2b.y <= seg1a.y) return false;
+      }
+      else
+      {
+        if (seg2b.y >= seg1b.y || seg2a.y <= seg1a.y) return false;
+      }
+    }
+    else
+    {
+      if (seg2a.y < seg2b.y)
+      {
+        if (seg2a.y >= seg1a.y || seg2b.y <= seg1b.y) return false;
+      }
+      else
+      {
+        if (seg2b.y >= seg1a.y || seg2a.y <= seg1b.y) return false;
+      }
+    }
+    return true;
+  }
+
+  OutRec* ClipperBase::ProcessJoin(Joiner* joiner)
+  {
+    OutPt* op1 = joiner->op1, * op2 = joiner->op2;
+    OutRec* or1 = GetRealOutRec(op1->outrec);
+    OutRec* or2 = GetRealOutRec(op2->outrec);
+    DeleteJoin(joiner);
+
+    if (or2->pts == nullptr) return or1;
+    else if (!IsValidClosedPath(op2))
+    {
+      SafeDisposeOutPts(op2);
+      return or1;
+    }
+    else if ((or1->pts == nullptr) || !IsValidClosedPath(op1))
+    {
+      SafeDisposeOutPts(op1);
+      return or2;
+    }
+    else if (or1 == or2 &&
+      ((op1 == op2) || (op1->next == op2) || (op1->prev == op2))) return or1;
+
+    CheckDisposeAdjacent(op1, op2, *or1);
+    CheckDisposeAdjacent(op2, op1, *or2);
+    if (op1->next == op2 || op2->next == op1) return or1;
+    OutRec* result = or1;
+
+    for (; ; )
+    {
+      if (!IsValidPath(op1) || !IsValidPath(op2) ||
+        (or1 == or2 && (op1->prev == op2 || op1->next == op2))) return or1;
+
+      if (op1->prev->pt == op2->next->pt ||
+        ((CrossProduct(op1->prev->pt, op1->pt, op2->next->pt) == 0) &&
+          CollinearSegsOverlap(op1->prev->pt, op1->pt, op2->pt, op2->next->pt)))
+      {
+        if (or1 == or2)
+        {
+          //SPLIT REQUIRED
+          //make sure op1.prev and op2.next match positions
+          //by inserting an extra vertex if needed
+          if (op1->prev->pt != op2->next->pt)
+          {
+            if (PointEqualOrBetween(op1->prev->pt, op2->pt, op2->next->pt))
+              op2->next = InsertOp(op1->prev->pt, op2);
+            else
+              op1->prev = InsertOp(op2->next->pt, op1->prev);
+          }
+
+          //current              to     new
+          //op1.p[opA] >>> op1   ...    opA \   / op1
+          //op2.n[opB] <<< op2   ...    opB /   \ op2
+          OutPt* opA = op1->prev, * opB = op2->next;
+          opA->next = opB;
+          opB->prev = opA;
+          op1->prev = op2;
+          op2->next = op1;
+          CompleteSplit(op1, opA, *or1);
+        }
+        else
+        {
+          //JOIN, NOT SPLIT
+          OutPt* opA = op1->prev, * opB = op2->next;
+          opA->next = opB;
+          opB->prev = opA;
+          op1->prev = op2;
+          op2->next = op1;
+
+          //SafeDeleteOutPtJoiners(op2);
+          //DisposeOutPt(op2);
+
+          if (or1->idx < or2->idx)
+          {
+            or1->pts = op1;
+            or2->pts = nullptr;
+            if (or1->owner && (!or2->owner ||
+              or2->owner->idx < or1->owner->idx))
+              or1->owner = or2->owner;
+            or2->owner = or1;
+          }
+          else
+          {
+            result = or2;
+            or2->pts = op1;
+            or1->pts = nullptr;
+            if (or2->owner && (!or1->owner ||
+              or1->owner->idx < or2->owner->idx))
+              or2->owner = or1->owner;
+            or1->owner = or2;
+          }
+        }
+        break;
+      }
+      else if (op1->next->pt == op2->prev->pt ||
+        ((CrossProduct(op1->next->pt, op2->pt, op2->prev->pt) == 0) &&
+          CollinearSegsOverlap(op1->next->pt, op1->pt, op2->pt, op2->prev->pt)))
+      {
+        if (or1 == or2)
+        {
+          //SPLIT REQUIRED
+          //make sure op2.prev and op1.next match positions
+          //by inserting an extra vertex if needed
+          if (op2->prev->pt != op1->next->pt)
+          {
+            if (PointEqualOrBetween(op2->prev->pt, op1->pt, op1->next->pt))
+              op1->next = InsertOp(op2->prev->pt, op1);
+            else
+              op2->prev = InsertOp(op1->next->pt, op2->prev);
+          }
+
+          //current              to     new
+          //op2.p[opA] >>> op2   ...    opA \   / op2
+          //op1.n[opB] <<< op1   ...    opB /   \ op1
+          OutPt* opA = op2->prev, * opB = op1->next;
+          opA->next = opB;
+          opB->prev = opA;
+          op2->prev = op1;
+          op1->next = op2;
+          CompleteSplit(op1, opA, *or1);
+        }
+        else
+        {
+          //JOIN, NOT SPLIT
+          OutPt* opA = op1->next, * opB = op2->prev;
+          opA->prev = opB;
+          opB->next = opA;
+          op1->next = op2;
+          op2->prev = op1;
+
+          //SafeDeleteOutPtJoiners(op2);
+          //DisposeOutPt(op2);
+
+          if (or1->idx < or2->idx)
+          {
+            or1->pts = op1;
+            or2->pts = nullptr;
+            if (or1->owner && (!or2->owner ||
+              or2->owner->idx < or1->owner->idx))
+              or1->owner = or2->owner;
+            or2->owner = or1;
+          }
+          else
+          {
+            result = or2;
+            or2->pts = op1;
+            or1->pts = nullptr;
+            if (or2->owner && (!or1->owner ||
+              or1->owner->idx < or2->owner->idx))
+              or2->owner = or1->owner;
+            or1->owner = or2;
+          }
+        }
+        break;
+      }
+      else if (PointBetween(op1->next->pt, op2->pt, op2->prev->pt) &&
+        DistanceFromLineSqrd(op1->next->pt, op2->pt, op2->prev->pt) < 2.01)
+      {
+        InsertOp(op1->next->pt, op2->prev);
+        continue;
+      }
+      else if (PointBetween(op2->next->pt, op1->pt, op1->prev->pt) &&
+        DistanceFromLineSqrd(op2->next->pt, op1->pt, op1->prev->pt) < 2.01)
+      {
+        InsertOp(op2->next->pt, op1->prev);
+        continue;
+      }
+      else if (PointBetween(op1->prev->pt, op2->pt, op2->next->pt) &&
+        DistanceFromLineSqrd(op1->prev->pt, op2->pt, op2->next->pt) < 2.01)
+      {
+        InsertOp(op1->prev->pt, op2);
+        continue;
+      }
+      else if (PointBetween(op2->prev->pt, op1->pt, op1->next->pt) &&
+        DistanceFromLineSqrd(op2->prev->pt, op1->pt, op1->next->pt) < 2.01)
+      {
+        InsertOp(op2->prev->pt, op1);
+        continue;
+      }
+
+      //something odd needs tidying up
+      if (CheckDisposeAdjacent(op1, op2, *or1)) continue;
+      else if (CheckDisposeAdjacent(op2, op1, *or1)) continue;
+      else if (op1->prev->pt != op2->next->pt &&
+        (DistanceSqr(op1->prev->pt, op2->next->pt) < 2.01))
+      {
+        op1->prev->pt = op2->next->pt;
+        continue;
+      }
+      else if (op1->next->pt != op2->prev->pt &&
+        (DistanceSqr(op1->next->pt, op2->prev->pt) < 2.01))
+      {
+        op2->prev->pt = op1->next->pt;
+        continue;
+      }
+      else
+      {
+        //OK, there doesn't seem to be a way to join after all
+        //so just tidy up the polygons
+        or1->pts = op1;
+        if (or2 != or1)
+        {
+          or2->pts = op2;
+          CleanCollinear(or2);
+        }
+        break;
+      }
+    }
+    return result;
+
+  }
+
+  inline bool Path1InsidePath2(const OutRec* or1, const OutRec* or2)
+  {
+    PointInPolygonResult result = PointInPolygonResult::IsOn;
+    OutPt* op = or1->pts;
+    do
+    {
+      result = PointInPolygon(op->pt, or2->path);
+      if (result != PointInPolygonResult::IsOn) break;
+      op = op->next;
+    } while (op != or1->pts);
+    if (result == PointInPolygonResult::IsOn)
+      return Area(op) < Area(or2->pts);
+    else
+      return result == PointInPolygonResult::IsInside;
+  }
+
+  inline Rect64 GetBounds(const Path64& path)
+  {
+    if (path.empty()) return Rect64();
+    Rect64 result = invalid_rect;
+    for (const Point64& pt : path)
+    {
+      if (pt.x < result.left) result.left = pt.x;
+      if (pt.x > result.right) result.right = pt.x;
+      if (pt.y < result.top) result.top = pt.y;
+      if (pt.y > result.bottom) result.bottom = pt.y;
+    }
+    return result;
+  }
+
+  bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path)
+  {
+    if (op->next == op || (!isOpen && op->next == op->prev))
+      return false;
+
+    path.resize(0);
+    Point64 lastPt;
+    OutPt* op2;
+    if (reverse)
+    {
+      lastPt = op->pt;
+      op2 = op->prev;
+    }
+    else
+    {
+      op = op->next;
+      lastPt = op->pt;
+      op2 = op->next;
+    }
+    path.push_back(lastPt);
+
+    while (op2 != op)
+    {
+      if (op2->pt != lastPt)
+      {
+        lastPt = op2->pt;
+        path.push_back(lastPt);
+      }
+      if (reverse)
+        op2 = op2->prev;
+      else
+        op2 = op2->next;
+    }
+
+    if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false;
+    else return true;
+  }
+
+  bool ClipperBase::DeepCheckOwner(OutRec* outrec, OutRec* owner)
+  {
+    if (owner->bounds.IsEmpty()) owner->bounds = GetBounds(owner->path);
+    bool is_inside_owner_bounds = owner->bounds.Contains(outrec->bounds);
+
+    // while looking for the correct owner, check the owner's 
+    // splits **before** checking the owner itself because 
+    // splits can occur internally, and checking the owner 
+    // first would miss the inner split's true ownership
+    if (owner->splits)
+    {
+      for (OutRec* split : *owner->splits)
+      {
+        split = GetRealOutRec(split);
+        if (!split || split->idx <= owner->idx || split == outrec) continue;
+
+        if (split->splits && DeepCheckOwner(outrec, split)) return true;
+
+        if (!split->path.size())
+          BuildPath64(split->pts, ReverseSolution, false, split->path);
+        if (split->bounds.IsEmpty()) split->bounds = GetBounds(split->path);
+
+        if (split->bounds.Contains(outrec->bounds) &&
+          Path1InsidePath2(outrec, split))
+        {
+          outrec->owner = split;
+          return true;
+        }
+      }
+    }
+
+    // only continue past here when not inside recursion
+    if (owner != outrec->owner) return false;
+
+    for (;;)
+    {
+      if (is_inside_owner_bounds && Path1InsidePath2(outrec, outrec->owner))
+        return true;
+      // otherwise keep trying with owner's owner 
+      outrec->owner = outrec->owner->owner;
+      if (!outrec->owner) return true; // true or false
+      is_inside_owner_bounds = outrec->owner->bounds.Contains(outrec->bounds);
+    }
+  }
+
+  void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen)
+  {
+    solutionClosed.resize(0);
+    solutionClosed.reserve(outrec_list_.size());
+    if (solutionOpen)
+    {
+      solutionOpen->resize(0);
+      solutionOpen->reserve(outrec_list_.size());
+    }
+
+    for (OutRec* outrec : outrec_list_)
+    {
+      if (outrec->pts == nullptr) continue;
+
+      Path64 path;
+      if (solutionOpen && outrec->is_open)
+      {
+        if (BuildPath64(outrec->pts, ReverseSolution, true, path))
+          solutionOpen->emplace_back(std::move(path));
+      }
+      else
+      {
+        //closed paths should always return a Positive orientation
+        if (BuildPath64(outrec->pts, ReverseSolution, false, path))
+          solutionClosed.emplace_back(std::move(path));
+      }
+    }
+  }
+
+  void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths)
+  {
+    polytree.Clear();
+    open_paths.resize(0);
+    if (has_open_paths_)
+      open_paths.reserve(outrec_list_.size());
+
+    for (OutRec* outrec : outrec_list_)
+    {
+      if (!outrec || !outrec->pts) continue;
+      if (outrec->is_open)
+      {
+        Path64 path;
+        if (BuildPath64(outrec->pts, ReverseSolution, true, path))
+          open_paths.push_back(path);
+        continue;
+      }
+
+      if (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path))
+        continue;
+      if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path);
+      outrec->owner = GetRealOutRec(outrec->owner);
+      if (outrec->owner) DeepCheckOwner(outrec, outrec->owner);
+
+      // swap the order when a child preceeds its owner
+      // (because owners must preceed children in polytrees)
+      if (outrec->owner && outrec->idx < outrec->owner->idx)
+      {
+        OutRec* tmp = outrec->owner;
+        outrec_list_[outrec->owner->idx] = outrec;
+        outrec_list_[outrec->idx] = tmp;
+        size_t tmp_idx = outrec->idx;
+        outrec->idx = tmp->idx;
+        tmp->idx = tmp_idx;
+        outrec = tmp;
+        outrec->owner = GetRealOutRec(outrec->owner);
+        BuildPath64(outrec->pts, ReverseSolution, false, outrec->path);
+        if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path);
+        if (outrec->owner) DeepCheckOwner(outrec, outrec->owner);
+      }
+
+      PolyPath* owner_polypath;
+      if (outrec->owner && outrec->owner->polypath)
+        owner_polypath = outrec->owner->polypath;
+      else
+        owner_polypath = &polytree;
+      outrec->polypath = owner_polypath->AddChild(outrec->path);
+    }
+  }
+
+  bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale)
+  {
+    if (op->next == op || (!isOpen && op->next == op->prev)) return false;
+    path.resize(0);
+    Point64 lastPt;
+    OutPt* op2;
+    if (reverse)
+    {
+      lastPt = op->pt;
+      op2 = op->prev;
+    }
+    else
+    {
+      op = op->next;
+      lastPt = op->pt;
+      op2 = op->next;
+    }
+    path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale
+#ifdef USINGZ
+              , lastPt.z
+#endif
+    ));
+
+    while (op2 != op)
+    {
+      if (op2->pt != lastPt)
+      {
+        lastPt = op2->pt;
+        path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale
+#ifdef USINGZ
+              , lastPt.z
+#endif
+        ));
+      }
+      if (reverse)
+        op2 = op2->prev;
+      else
+        op2 = op2->next;
+    }
+    if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false;
+    return true;
+  }
+
+  void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen)
+  {
+    solutionClosed.resize(0);
+    solutionClosed.reserve(outrec_list_.size());
+    if (solutionOpen)
+    {
+      solutionOpen->resize(0);
+      solutionOpen->reserve(outrec_list_.size());
+    }
+
+    for (OutRec* outrec : outrec_list_)
+    {
+      if (outrec->pts == nullptr) continue;
+
+      PathD path;
+      if (solutionOpen && outrec->is_open)
+      {
+        if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_))
+          solutionOpen->emplace_back(std::move(path));
+      }
+      else
+      {
+        //closed paths should always return a Positive orientation
+        if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_))
+          solutionClosed.emplace_back(std::move(path));
+      }
+    }
+  }
+
+  void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths)
+  {
+    polytree.Clear();
+    open_paths.resize(0);
+    if (has_open_paths_)
+      open_paths.reserve(outrec_list_.size());
+
+    for (OutRec* outrec : outrec_list_)
+    {
+      if (!outrec || !outrec->pts) continue;
+      if (outrec->is_open)
+      {
+        PathD path;
+        if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_))
+          open_paths.push_back(path);
+        continue;
+      }
+
+      if (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path))
+        continue;
+      if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path);
+      outrec->owner = GetRealOutRec(outrec->owner);
+      if (outrec->owner) DeepCheckOwner(outrec, outrec->owner);
+
+      // swap the order when a child preceeds its owner
+      // (because owners must preceed children in polytrees)
+      if (outrec->owner && outrec->idx < outrec->owner->idx)
+      {
+        OutRec* tmp = outrec->owner;
+        outrec_list_[outrec->owner->idx] = outrec;
+        outrec_list_[outrec->idx] = tmp;
+        size_t tmp_idx = outrec->idx;
+        outrec->idx = tmp->idx;
+        tmp->idx = tmp_idx;
+        outrec = tmp;
+        outrec->owner = GetRealOutRec(outrec->owner);
+        BuildPath64(outrec->pts, ReverseSolution, false, outrec->path);
+        if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path);
+        if (outrec->owner) DeepCheckOwner(outrec, outrec->owner);
+      }
+
+      PolyPath* owner_polypath;
+      if (outrec->owner && outrec->owner->polypath)
+        owner_polypath = outrec->owner->polypath;
+      else
+        owner_polypath = &polytree;
+      outrec->polypath = owner_polypath->AddChild(outrec->path);
+    }
+  }
+
+}  // namespace clipper2lib

+ 485 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.offset.cpp

@@ -0,0 +1,485 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  15 October 2022                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Path Offset (Inflate/Shrink)                                    *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#include <cmath>
+#include "clipper2/clipper.h"
+#include "clipper2/clipper.offset.h"
+
+namespace Clipper2Lib {
+
+const double default_arc_tolerance = 0.25;
+const double floating_point_tolerance = 1e-12;
+
+//------------------------------------------------------------------------------
+// Miscellaneous methods
+//------------------------------------------------------------------------------
+
+Paths64::size_type GetLowestPolygonIdx(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;
+		}	
+	return result;
+}
+
+PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
+{
+	double dx, dy, inverse_hypot;
+	if (pt1 == pt2) return PointD(0.0, 0.0);
+	dx = static_cast<double>(pt2.x - pt1.x);
+	dy = static_cast<double>(pt2.y - pt1.y);
+	inverse_hypot = 1.0 / hypot(dx, dy);
+	dx *= inverse_hypot;
+	dy *= inverse_hypot;
+	return PointD(dy, -dx);
+}
+
+inline bool AlmostZero(double value, double epsilon = 0.001)
+{
+	return std::fabs(value) < epsilon;
+}
+
+inline double Hypot(double x, double y) 
+{
+	//see https://stackoverflow.com/a/32436148/359538
+	return std::sqrt(x * x + y * y);
+}
+
+inline PointD NormalizeVector(const PointD& vec)
+{
+	
+	double h = Hypot(vec.x, vec.y);
+	if (AlmostZero(h)) return PointD(0,0);
+	double inverseHypot = 1 / h;
+	return PointD(vec.x * inverseHypot, vec.y * inverseHypot);
+}
+
+inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2)
+{
+	return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y));
+}
+
+inline bool IsClosedPath(EndType et)
+{
+	return et == EndType::Polygon || et == EndType::Joined;
+}
+
+inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
+{
+	return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
+}
+
+inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
+{
+	return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
+}
+
+//------------------------------------------------------------------------------
+// ClipperOffset methods
+//------------------------------------------------------------------------------
+
+void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
+{
+	Paths64 paths;
+	paths.push_back(path);
+	AddPaths(paths, jt_, et_);
+}
+
+void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
+{
+	if (paths.size() == 0) return;
+	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)
+		norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1)));
+	norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin())));
+}
+
+inline PointD TranslatePoint(const PointD& pt, double dx, double dy)
+{
+	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
+	{
+		double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
+		double b1 = pt1a.y - m1 * pt1a.x;
+		return PointD(pt2a.x, m1 * pt2a.x + b1);
+	}
+	else
+	{
+		double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
+		double b1 = pt1a.y - m1 * pt1a.x;
+		double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
+		double b2 = pt2a.y - m2 * pt2a.x;
+		if (m1 == m2) return PointD(0, 0);
+		double x = (b2 - b1) / (m1 - m2);
+		return PointD(x, m1 * x + b1);
+	}
+}
+
+void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k)
+{
+	PointD vec;
+	if (j == k) 
+		vec = PointD(norms[0].y, -norms[0].x);
+	else
+		vec = GetAvgUnitVector(
+			PointD(-norms[k].y, norms[k].x),
+			PointD(norms[j].y, -norms[j].x));
+
+	// 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);
+	// 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);
+	// get 2 vertices along one edge offset
+	PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_);
+	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);
+		//get the second intersect point through reflecion
+		group.path_.push_back(Point64(ReflectPoint(pt, ptQ)));
+		group.path_.push_back(Point64(pt));
+	}
+	else
+	{
+		PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
+		PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
+		group.path_.push_back(Point64(pt));
+		//get the second intersect point through reflecion
+		group.path_.push_back(Point64(ReflectPoint(pt, ptQ)));
+	}
+}
+
+void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a)
+{
+	double q = group_delta_ / (cos_a + 1);
+	group.path_.push_back(Point64(
+		path[j].x + (norms[k].x + norms[j].x) * q,
+		path[j].y + (norms[k].y + norms[j].y) * q));
+}
+
+void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle)
+{
+	//even though angle may be negative this is a convex join
+	Point64 pt = path[j];
+	int steps = static_cast<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();
+
+	group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
+	for (int i = 0; i < steps; i++)
+	{
+		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));
+	}
+	group.path_.push_back(GetPerpendic(path[j], norms[j], group_delta_));
+}
+
+void ClipperOffset::OffsetPoint(Group& group, 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)
+	// A == PI: edges 'spike'
+	// sin(A) < 0: right turning
+	// cos(A) < 0: change in angle is more than 90 degree
+
+	if (path[j] == path[k]) { k = j; 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))
+	{
+		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);
+		}
+	}
+	else // it's convex 
+	{
+		if (join_type_ == JoinType::Round)
+			DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
+		else if (join_type_ == JoinType::Miter)
+		{
+			// miter unless the angle is so acute the miter would exceeds ML
+			if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
+			else DoSquare(group, path, j, k);
+		}
+		// don't bother squaring angles that deviate < ~20 degrees because
+		// squaring will be indistinguishable from mitering and just be a lot slower
+		else if (cos_a > 0.9)
+			DoMiter(group, path, j, k, cos_a);			
+		else
+			DoSquare(group, path, j, k);			
+	}
+	k = j;
+}
+
+void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
+{
+	group.path_.clear();
+	for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i)
+		OffsetPoint(group, path, i, j);
+	group.paths_out_.push_back(group.path_);
+}
+
+void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
+{
+	OffsetPolygon(group, path);
+	std::reverse(path.begin(), path.end());
+	BuildNormals(path);
+	OffsetPolygon(group, path);
+}
+
+void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type)
+{
+	group.path_.clear();
+
+	// do the line start cap
+	switch (end_type)
+	{
+	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;
+	}
+
+	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);
+
+	// 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)
+	{
+	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;
+	}
+
+	for (size_t i = highI, k = 0; i > 0; --i)
+		OffsetPoint(group, path, i, k);
+	group.paths_out_.push_back(group.path_);
+}
+
+void ClipperOffset::DoGroupOffset(Group& group, double delta)
+{
+	if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5;
+	bool isClosedPaths = IsClosedPath(group.end_type_);
+
+	if (isClosedPaths)
+	{
+		//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;
+	} 
+	else
+		group.is_reversed_ = false;
+
+	group_delta_ = delta;
+	abs_group_delta_ = std::abs(group_delta_);
+	join_type_ = group.join_type_;
+
+	double arcTol = (arc_tolerance_ > floating_point_tolerance ? arc_tolerance_
+		: std::log10(2 + abs_group_delta_) * default_arc_tolerance); // empirically derived
+
+//calculate a sensible number of steps (for 360 deg for the given offset
+	if (group.join_type_ == JoinType::Round || group.end_type_ == EndType::Round)
+	{
+		steps_per_rad_ = PI / std::acos(1 - arcTol / abs_group_delta_) / (PI *2);
+	}
+
+	bool is_closed_path = IsClosedPath(group.end_type_);
+	Paths64::const_iterator path_iter;
+	for(path_iter = group.paths_in_.cbegin(); path_iter != group.paths_in_.cend(); ++path_iter)
+	{
+		Path64 path = StripDuplicates(*path_iter, is_closed_path);
+		Path64::size_type cnt = path.size();
+		if (cnt == 0) continue;
+
+		if (cnt == 1) // single point - only valid with open paths
+		{
+			group.path_ = Path64();
+			//single vertex so build a circle or square ...
+			if (group.join_type_ == JoinType::Round)
+			{
+				double radius = abs_group_delta_;
+				group.path_ = Ellipse(path[0], radius, radius);
+			}
+			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();
+			}
+			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.reserve(solution.size() + group.paths_out_.size());
+	copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution));
+	group.paths_out_.clear();
+}
+
+Paths64 ClipperOffset::Execute(double delta)
+{
+	solution.clear();
+	if (std::abs(delta) < default_arc_tolerance)
+	{
+		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;
+	}
+
+	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)
+	{
+		DoGroupOffset(*groups_iter, delta);
+	}
+
+	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);
+	}
+	return solution;
+}
+
+} // namespace

+ 513 - 0
polygon.mod/clipper2/CPP/Clipper2Lib/src/clipper.rectclip.cpp

@@ -0,0 +1,513 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  FAST rectangular clipping                                       *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#include <cmath>
+#include "clipper2/clipper.h"
+#include "clipper2/clipper.rectclip.h"
+
+namespace Clipper2Lib {
+
+  //------------------------------------------------------------------------------
+  // Miscellaneous methods
+  //------------------------------------------------------------------------------
+
+  inline PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2)
+  {
+    PointInPolygonResult result = PointInPolygonResult::IsOn;
+    for(const Point64& pt : path2)
+    {
+      result = PointInPolygon(pt, path1);
+      if (result != PointInPolygonResult::IsOn) break;
+    }
+    return result;
+  }
+
+  inline bool GetLocation(const Rect64& rec,
+    const Point64& pt, Location& loc)
+  {
+    if (pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom)
+    {
+      loc = Location::Left;
+      return false;
+    }
+    else if (pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom)
+    {
+      loc = Location::Right;
+      return false;
+    }
+    else if (pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right)
+    {
+      loc = Location::Top;
+      return false;
+    }
+    else if (pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right)
+    {
+      loc = Location::Bottom;
+      return false;
+    }
+    else if (pt.x < rec.left) loc = Location::Left;
+    else if (pt.x > rec.right) loc = Location::Right;
+    else if (pt.y < rec.top) loc = Location::Top;
+    else if (pt.y > rec.bottom) loc = Location::Bottom;
+    else loc = Location::Inside;
+    return true;
+  }
+
+  inline bool GetIntersection(const Path64& rectPath,
+    const Point64& p, const Point64& p2, Location& loc, Point64& ip)
+  {
+    // gets the intersection closest to 'p'
+    // when Result = false, loc will remain unchanged
+    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))
+      {
+        GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
+        loc = Location::Top;
+      }
+      else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
+      {
+        GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
+        loc = Location::Bottom;
+      }
+      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))
+      {
+        GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
+        loc = Location::Left;
+      }
+      else if (p.x > rectPath[1].x &&
+        SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
+      {
+        GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
+        loc = Location::Right;
+      }
+      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))
+      {
+        GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
+        loc = Location::Top;
+      }
+      else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
+      {
+        GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
+        loc = Location::Bottom;
+      }
+      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))
+      {
+        GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
+        loc = Location::Left;
+      }
+      else if (p.x > rectPath[2].x &&
+        SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
+      {
+        GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
+        loc = Location::Right;
+      }
+      else return false;
+      break;
+
+    default: // loc == rInside
+      if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
+      {
+        GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
+        loc = Location::Left;
+      }
+      else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
+      {
+        GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
+        loc = Location::Top;
+      }
+      else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
+      {
+        GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
+        loc = Location::Right;
+      }
+      else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
+      {
+        GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
+        loc = Location::Bottom;
+      }
+      else return false;
+      break;
+    }
+    return true;
+  }
+
+  inline Location GetAdjacentLocation(Location loc, bool isClockwise)
+  {
+    int delta = (isClockwise) ? 1 : 3;
+    return static_cast<Location>((static_cast<int>(loc) + delta) % 4);
+  }
+
+  inline bool HeadingClockwise(Location prev, Location curr)
+  {
+    return (static_cast<int>(prev) + 1) % 4 == static_cast<int>(curr);
+  }
+
+  inline bool AreOpposites(Location prev, Location curr)
+  {
+    return abs(static_cast<int>(prev) - static_cast<int>(curr)) == 2;
+  }
+
+  inline bool IsClockwise(Location prev, Location curr,
+    Point64 prev_pt, Point64 curr_pt, Point64 rect_mp)
+  {
+    if (AreOpposites(prev, curr))
+      return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
+    else
+      return HeadingClockwise(prev, curr);
+  }
+
+  //----------------------------------------------------------------------------
+  // RectClip64
+  //----------------------------------------------------------------------------
+
+  void RectClip::AddCorner(Location prev, Location curr)
+  {
+    if (HeadingClockwise(prev, curr))
+      result_.push_back(rectPath_[static_cast<int>(prev)]);
+    else
+      result_.push_back(rectPath_[static_cast<int>(curr)]);
+  }
+
+  void RectClip::AddCorner(Location& loc, bool isClockwise)
+  {
+    if (isClockwise)
+    {
+      result_.push_back(rectPath_[static_cast<int>(loc)]);
+      loc = GetAdjacentLocation(loc, true);
+    }
+    else
+    {
+      loc = GetAdjacentLocation(loc, false);
+      result_.push_back(rectPath_[static_cast<int>(loc)]);
+    }
+  }
+
+  void RectClip::GetNextLocation(const Path64& path,
+      Location& loc, int& i, int highI)
+  {
+    switch (loc)
+    {
+    case Location::Left:
+      while (i <= highI && path[i].x <= rect_.left) ++i;
+      if (i > highI) break;
+      else if (path[i].x >= rect_.right) loc = Location::Right;
+      else if (path[i].y <= rect_.top) loc = Location::Top;
+      else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
+      else loc = Location::Inside;
+      break;
+
+    case Location::Top:
+      while (i <= highI && path[i].y <= rect_.top) ++i;
+      if (i > highI) break;
+      else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
+      else if (path[i].x <= rect_.left) loc = Location::Left;
+      else if (path[i].x >= rect_.right) loc = Location::Right;
+      else loc = Location::Inside;
+      break;
+
+    case Location::Right:
+      while (i <= highI && path[i].x >= rect_.right) ++i;
+      if (i > highI) break;
+      else if (path[i].x <= rect_.left) loc = Location::Left;
+      else if (path[i].y <= rect_.top) loc = Location::Top;
+      else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
+      else loc = Location::Inside;
+      break;
+
+    case Location::Bottom:
+      while (i <= highI && path[i].y >= rect_.bottom) ++i;
+      if (i > highI) break;
+      else if (path[i].y <= rect_.top) loc = Location::Top;
+      else if (path[i].x <= rect_.left) loc = Location::Left;
+      else if (path[i].x >= rect_.right) loc = Location::Right;
+      else loc = Location::Inside;
+      break;
+
+    case Location::Inside:
+      while (i <= highI)
+      {
+        if (path[i].x < rect_.left) loc = Location::Left;
+        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; }
+        break; //inner loop
+      }
+      break;
+    } //switch          
+  }
+
+  Path64 RectClip::Execute(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;
+    Location first_cross_ = Location::Inside;
+    if (!GetLocation(rect_, path[highI], loc))
+    {
+      i = highI - 1;
+      while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i;
+      if (i < 0) return path;
+      if (prev == Location::Inside) loc = Location::Inside;
+      i = 0;
+    }
+    Location starting_loc = loc;
+
+    ///////////////////////////////////////////////////
+    while (i <= highI)
+    {
+      prev = loc;
+      Location crossing_prev = crossing_loc;
+
+      GetNextLocation(path, loc, i, highI);
+
+      if (i > highI) break;
+      Point64 ip, ip2;
+      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))
+      {
+        // ie remaining outside
+
+        if (crossing_prev == Location::Inside)
+        {
+          bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
+          do {
+            start_locs_.push_back(prev);
+            prev = GetAdjacentLocation(prev, isClockw);
+          } while (prev != loc);
+          crossing_loc = crossing_prev; // still not crossed 
+        }
+        else if (prev != Location::Inside && prev != loc)
+        {
+          bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
+          do {
+            AddCorner(prev, isClockw);
+          } while (prev != loc);
+        }
+        ++i;
+        continue;
+      }
+
+      ////////////////////////////////////////////////////
+      // we must be crossing the rect boundary to get here
+      ////////////////////////////////////////////////////
+
+      if (loc == Location::Inside) // path must be entering rect
+      {
+        if (first_cross_ == Location::Inside)
+        {
+          first_cross_ = crossing_loc;
+          start_locs_.push_back(prev);
+        }
+        else if (prev != crossing_loc)
+        {
+          bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], mp_);
+          do {
+            AddCorner(prev, isClockw);
+          } while (prev != crossing_loc);
+        }
+      }
+      else if (prev != Location::Inside)
+      {
+        // passing right through rect. 'ip' here will be the second 
+        // intersect pt but we'll also need the first intersect pt (ip2)
+        loc = prev;
+        GetIntersection(rectPath_, prev_pt, path[i], loc, ip2);
+        if (crossing_prev != Location::Inside)
+          AddCorner(crossing_prev, loc);
+
+        if (first_cross_ == Location::Inside)
+        {
+          first_cross_ = loc;
+          start_locs_.push_back(prev);
+        }
+
+        loc = crossing_loc;
+        result_.push_back(ip2);
+        if (ip == ip2)
+        {
+          // it's very likely that path[i] is on rect
+          GetLocation(rect_, path[i], loc);
+          AddCorner(crossing_loc, loc);
+          crossing_loc = loc;
+          continue;
+        }
+      }
+      else // path must be exiting rect
+      {
+        loc = crossing_loc;
+        if (first_cross_ == Location::Inside)
+          first_cross_ = crossing_loc;
+      }
+
+      result_.push_back(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();
+    }
+
+    if (loc != Location::Inside &&
+      (loc != first_cross_ || start_locs_.size() > 2))
+    {
+      if (start_locs_.size() > 0)
+      {
+        prev = loc;
+        for (auto loc2 : start_locs_)
+        {
+          if (prev == loc2) continue;
+          AddCorner(prev, HeadingClockwise(prev, loc2));
+          prev = loc2;
+        }
+        loc = prev;
+      }
+      if (loc != first_cross_)
+        AddCorner(loc, HeadingClockwise(loc, first_cross_));
+    }
+
+    if (result_.size() < 3) return Path64();
+
+    // tidy up duplicates and collinear segments
+    Path64 res;
+    res.reserve(result_.size());
+    size_t k = 0; highI = static_cast<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)
+    {
+      if (CrossProduct(prev_pt, res[k], *cit))
+      {
+        prev_pt = res[k++];
+        res.push_back(*cit);
+      }
+      else
+        res[k] = *cit;
+    }
+
+    if (k < 2) return Path64();
+    // and a final check for collinearity
+    else if (!CrossProduct(res[0], res[k - 1], res[k])) res.pop_back();
+    return res;
+  }
+
+  Paths64 RectClipLines::Execute(const Path64& path)
+  {
+    result_.clear();
+    Paths64 result;
+    if (rect_.IsEmpty() || path.size() == 0) return result;
+
+    int i = 1, highI = static_cast<int>(path.size()) - 1;
+
+    Location prev = Location::Inside, loc;
+    Location crossing_loc = Location::Inside;
+    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 (prev == Location::Inside) loc = Location::Inside;
+      i = 1;
+    }
+    if (loc == Location::Inside) result_.push_back(path[0]);
+
+    ///////////////////////////////////////////////////
+    while (i <= highI)
+    {
+      prev = loc;
+      GetNextLocation(path, loc, i, highI);
+      if (i > highI) break;
+      Point64 ip, ip2;
+      Point64 prev_pt = path[static_cast<size_t>(i - 1)];
+
+      crossing_loc = loc;
+      if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip))
+      {
+        // ie remaining outside
+        ++i;
+        continue;
+      }
+
+      ////////////////////////////////////////////////////
+      // we must be crossing the rect boundary to get here
+      ////////////////////////////////////////////////////
+
+      if (loc == Location::Inside) // path must be entering rect
+      {
+        result_.push_back(ip);
+      }
+      else if (prev != Location::Inside)
+      {
+        // passing right through rect. 'ip' here will be the second 
+        // intersect pt but we'll also need the first intersect pt (ip2)
+        crossing_loc = prev;
+        GetIntersection(rectPath_, prev_pt, path[i], crossing_loc, ip2);
+        result_.push_back(ip2);
+        result_.push_back(ip);
+        result.push_back(result_);
+        result_.clear();
+      }
+      else // path must be exiting rect
+      {
+        result_.push_back(ip);
+        result.push_back(result_);
+        result_.clear();
+      }
+    } //while i <= highI
+    ///////////////////////////////////////////////////
+
+    if (result_.size() > 1)
+      result.push_back(result_);
+    return result;
+  }
+
+} // namespace

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

@@ -0,0 +1,210 @@
+#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
+}

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

@@ -0,0 +1,92 @@
+#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
+}

+ 112 - 0
polygon.mod/clipper2/CPP/Examples/InflateDemo/InflateDemo1.cpp

@@ -0,0 +1,112 @@
+
+#include <cstdlib>
+#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)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}
+
+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);
+  SvgAddOpenSubject(svg2, op1, fr2, false);
+  SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false);
+
+  op1 = TranslatePaths(op1, 250, 0);
+  op2 = InflatePaths(op1, 20, JoinType::Miter, EndType::Butt, 5);
+  SvgAddOpenSubject(svg2, op1, fr2, false);
+  SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false);
+
+  op1 = TranslatePaths(op1, 250, 0);
+  op2 = InflatePaths(op1, 20, JoinType::Round, EndType::Round);
+  SvgAddOpenSubject(svg2, op1, fr2, false);
+  SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false);
+
+  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"));
+  pp.insert(pp.end(), p.begin(), p.end());
+
+  for (int i = 0; i < 5; ++i)
+  {
+    //note the relatively large miter limit set here (10)
+    p = InflatePaths(p, 5, 
+      JoinType::Miter, EndType::Polygon, 10);
+    pp.insert(pp.end(), p.begin(), p.end());
+  }
+
+  //rectangle offset - both squared and rounded
+  p.clear();
+  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);
+  pp.insert(pp.end(), p.begin(), p.end());
+  co.AddPaths(p, JoinType::Round, EndType::Joined);
+  p = co.Execute(20);
+  pp.insert(pp.end(), p.begin(), p.end());
+
+  FillRule fr = FillRule::EvenOdd;
+  SvgWriter svg;
+  SvgAddSolution(svg, Paths64ToPathsD(pp), fr, false);
+  SvgSaveToFile(svg, "solution_off.svg", 800, 600, 20);
+  System("solution_off.svg");
+}
+
+void DoRabbit()
+{
+  SvgReader svg_reader;
+  svg_reader.LoadFromFile("./rabbit.svg");
+  PathsD p = svg_reader.GetPaths();
+
+  JoinType jt = JoinType::Round;
+  PathsD solution(p);
+
+  while (p.size())
+  {
+    //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);
+    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); 
+  SvgSaveToFile(svg, "solution_off2.svg", 450, 720, 0);
+  System("solution_off2.svg");
+}
+
+int main(int argc, char* argv[])
+{
+  DoSimpleShapes();
+  DoRabbit();
+}
+//---------------------------------------------------------------------------

文件差異過大導致無法顯示
+ 12 - 0
polygon.mod/clipper2/CPP/Examples/InflateDemo/rabbit.svg


+ 241 - 0
polygon.mod/clipper2/CPP/Examples/RectClipDemo/RectClipDemo1.cpp

@@ -0,0 +1,241 @@
+
+
+#include <cstdint>
+
+#include <cstdlib>
+#include <sstream>
+#include <fstream>
+#include <string>
+#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 PressEnterToExit();
+
+void DoEllipses();
+void DoRectangles();
+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;
+
+int main(int argc, char* argv[])
+{
+  srand((unsigned)time(0));
+  DoEllipses();
+  DoRectangles();
+  DoRandomPoly(31);
+  //MeasurePerformance(500, 2500, 500);
+  //MeasureLineClippingPerformance(250);
+}
+
+Path64 MakeRandomEllipse(int minWidth, int minHeight, int maxWidth, int maxHeight,
+  int maxRight, int maxBottom)
+{
+  int w = maxWidth > minWidth ? minWidth + rand() % (maxWidth - minWidth) : minWidth;
+  int h = maxHeight > minHeight ? minHeight + rand() % (maxHeight - minHeight) : minHeight;
+  int l = rand() % (maxRight - w);
+  int t = rand() % (maxBottom - h);
+  return Ellipse(Rect64(l, t, l + w, t + h));
+}
+
+void DoEllipses()
+{
+  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)
+    sub.push_back(MakeRandomEllipse(10, 10, 100, 100, width, height));
+      
+  //////////////////////////////////
+  sol = RectClip(rect, sub);
+  //////////////////////////////////
+
+  FillRule fr = FillRule::EvenOdd;
+  SvgWriter svg;
+  svg.AddPaths(sub, false, fr, 0x100066FF, 0x400066FF, 1, false);
+  svg.AddPaths(clp, false, fr, 0x10FFAA00, 0xFFFF0000, 1, false);
+  svg.AddPaths(sol, false, fr, 0x8066FF66, 0xFF006600, 1, false);
+  svg.SaveToFile("rectclip1.svg", 800, 600, 0);
+  System("rectclip1.svg");
+}
+
+Path64 MakeRandomRectangle(int minWidth, int minHeight, int maxWidth, int maxHeight,
+  int maxRight, int maxBottom)
+{
+  int w = maxWidth > minWidth ? minWidth + rand() % (maxWidth - minWidth): minWidth;
+  int h = maxHeight > minHeight ? minHeight + rand() % (maxHeight - minHeight): minHeight;
+  int l = rand() % (maxRight - w);
+  int t = rand() % (maxBottom - h);
+  Path64 result;
+  result.reserve(4);
+  result.push_back(Point64(l, t));
+  result.push_back(Point64(l+w, t));
+  result.push_back(Point64(l+w, t+h));
+  result.push_back(Point64(l, t+h));
+  return result;
+}
+
+void DoRectangles()
+{
+  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)
+    sub.push_back(MakeRandomRectangle(10, 10, 100, 100, width, height));
+
+  sol = RectClip(rect, sub);
+
+  FillRule fr = FillRule::EvenOdd;
+  SvgWriter svg;
+  svg.AddPaths(sub, false, fr, 0x100066FF, 0x400066FF, 1, false);
+  svg.AddPaths(clp, false, fr, 0x10FFAA00, 0xFFFF0000, 1, false);
+  svg.AddPaths(sol, false, fr, 0x8066FF66, 0xFF006600, 1, false);
+  svg.SaveToFile("rectclip2.svg", 800, 600, 0);
+  System("rectclip2.svg");
+}
+
+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;
+}
+
+PathD MakeRandomPolyD(int width, int height, unsigned vertCnt)
+{
+  PathD result;
+  result.reserve(vertCnt);
+  for (unsigned i = 0; i < vertCnt; ++i)
+    result.push_back(PointD(rand() % width, rand() % height));
+  return result;
+}
+
+void DoRandomPoly(int count)
+{
+  PathsD sub_open, clp, sol_open, store;
+  RectD rect;
+
+  // generate random poly
+  rect = RectD(margin, margin, width - margin, height - margin);
+  clp.push_back(rect.AsPath());
+  sub_open.push_back(MakeRandomPolyD(width, height, count));
+
+  //////////////////////////////////
+  sol_open = RectClipLines(rect, sub_open);
+  //////////////////////////////////
+
+  FillRule fr = FillRule::EvenOdd;
+  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);
+  System("rectclip3.svg");
+}
+
+void MeasurePerformance(int min, int max, int step)
+{
+  FillRule fr = FillRule::EvenOdd;
+  Paths64 sub, clp, sol, store;
+  Rect64 rect;
+
+  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();
+
+    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;
+    {
+      Timer t("Clipper64: ");
+      sol = Intersect(sub, clp, fr);
+    }
+
+    {
+      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;
+  {
+    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);
+  }
+
+  {
+    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();
+}
+
+void System(const std::string& filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}
+
+void PressEnterToExit()
+{
+  std::string s;
+  std::cout << std::endl << "Press Enter to exit" << std::endl;
+  std::getline(std::cin, s);
+}
+
+
+

+ 114 - 0
polygon.mod/clipper2/CPP/Examples/UsingZ/UsingZ1.cpp

@@ -0,0 +1,114 @@
+
+#include <cstdlib>
+#include "clipper2/clipper.h"
+#include "../../Utils/clipper.svg.h"
+#include "../../Utils/clipper.svg.utils.h"
+
+using namespace std;
+using namespace Clipper2Lib;
+
+
+void System(const std::string &filename);
+void TestInt64();
+void TestDouble();
+
+class MyClass {
+public:
+  // Point64 callback - see TestInt64()
+  void myZCB(const Point64& e1bot, const Point64& e1top,
+    const Point64& e2bot, const Point64& e2top, Point64& pt)
+  {
+    pt.z = 1;
+  }
+
+  // PointD callback - see TestDouble()
+  void myZCBD(const PointD& e1bot, const PointD& e1top,
+    const PointD& e2bot, const PointD& e2top, PointD& pt)
+  {
+    pt.z = 1;
+  }
+};
+
+int main(int argc, char* argv[])
+{
+  TestInt64();
+  TestDouble();
+}
+//---------------------------------------------------------------------------
+
+void TestInt64()
+{
+
+  Paths64 subject, solution;
+  MyClass mc;
+  Clipper64 c64;
+
+  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) {
+    // draw circles around intersection points - flagged by z == 1
+    PathsD ellipses;
+    double r = 3.0;
+    for (const Point64& 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);
+  }
+  SvgSaveToFile(svg, "using_z_64.svg", 800, 600, 20);
+  System("using_z_64.svg");
+  */
+}
+
+void TestDouble()
+{
+
+  PathsD subject, solution;
+  MyClass mc;
+  ClipperD c;
+
+  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
+    PathsD ellipses;
+    double r = 5.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);
+  }
+  SvgSaveToFile(svg, "using_z_d.svg", 800, 600, 20);
+  System("using_z_d.svg");
+}
+
+void System(const std::string &filename)
+{
+#ifdef _WIN32
+  system(filename.c_str());
+#else
+  system(("firefox " + filename).c_str());
+#endif
+}

+ 12 - 0
polygon.mod/clipper2/CPP/GoogleTest in Visual Studio.txt

@@ -0,0 +1,12 @@
+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".
+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
+   "CMake generation finished" in Visual Studio's statusbar.
+6. Rebuild all files (Ctrl+Shift+B).

+ 65 - 0
polygon.mod/clipper2/CPP/Tests/TestLines.cpp

@@ -0,0 +1,65 @@
+#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)
+  {
+    Clipper2Lib::Paths64 subject, subject_open, clip;
+    Clipper2Lib::Paths64 solution, solution_open;
+    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) : 
+      0;
+
+    if (test_number == 1)
+    {
+      EXPECT_EQ(solution.size(), 1);
+      if (solution.size() > 0)
+      {
+        EXPECT_EQ(solution[0].size(), 6);        
+        EXPECT_TRUE(IsPositive(solution[0]));
+      }
+
+      EXPECT_EQ(solution_open.size(), 1);
+      if (solution_open.size() > 0)
+      {
+        EXPECT_EQ(solution_open[0].size(), 2);
+        if (solution_open[0].size() > 0)
+        {
+          //expect vertex closest to input path's start
+          EXPECT_EQ(solution_open[0][0].y, 6);
+        }
+      }
+    }
+    else
+    {
+      EXPECT_LE(count_diff, 8);
+      EXPECT_LE(relative_count_diff, 0.1);
+    }
+    ++test_number;
+  }
+  EXPECT_GE(test_number, 17);
+}

+ 20 - 0
polygon.mod/clipper2/CPP/Tests/TestOffsetOrientation.cpp

@@ -0,0 +1,20 @@
+#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]));
+}

+ 20 - 0
polygon.mod/clipper2/CPP/Tests/TestOrientation.cpp

@@ -0,0 +1,20 @@
+#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)"));
+  EXPECT_FALSE(IsPositive(subjects[0]));
+  EXPECT_FALSE(IsPositive(subjects[1]));
+  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);
+}

+ 105 - 0
polygon.mod/clipper2/CPP/Tests/TestPolygons.cpp

@@ -0,0 +1,105 @@
+#include <gtest/gtest.h>
+#include "clipper2/clipper.h"
+#include "ClipFileLoad.h"
+
+inline Clipper2Lib::PathD MakeRandomPath(int width, int height, unsigned vertCnt)
+{
+  Clipper2Lib::PathD result;
+  result.reserve(vertCnt);
+  for (unsigned i = 0; i < vertCnt; ++i)
+    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)
+  {
+    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, 
+      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);
+    
+    // check the polytree variant too
+    Clipper2Lib::PolyTree64 solution_polytree;
+    Clipper2Lib::Paths64 solution_polytree_open;
+    Clipper2Lib::Clipper64 clipper_polytree;
+    clipper_polytree.AddSubject(subject);
+    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 = 
+      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());
+
+    // check polygon counts
+    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);
+
+    // 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);
+
+    ++test_number;
+  }
+  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);
+
+}

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

@@ -0,0 +1,31 @@
+#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));
+
+}

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

@@ -0,0 +1,132 @@
+#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));
+
+}

+ 30 - 0
polygon.mod/clipper2/CPP/Tests/TestPolytreeIntersection.cpp

@@ -0,0 +1,30 @@
+#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") );
+    clipper.AddSubject(subject);
+    Paths64 clip;
+    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);
+}

+ 32 - 0
polygon.mod/clipper2/CPP/Tests/TestPolytreeUnion.cpp

@@ -0,0 +1,32 @@
+#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"));
+
+    Clipper64 clipper;
+    clipper.AddSubject(subject);
+
+    PolyTree64 solution;
+    Paths64 open_paths;
+    if (IsPositive(subject[0]))
+      clipper.Execute(ClipType::Union, 
+        FillRule::Positive, solution, open_paths);
+    else
+    {
+      //because clipping ops normally return Positive solutions
+      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()));
+}

+ 158 - 0
polygon.mod/clipper2/CPP/Tests/TestRandomPaths.cpp

@@ -0,0 +1,158 @@
+#include <gtest/gtest.h>
+#include "clipper2/clipper.h"
+#include <fstream>
+#include <random>
+
+int GenerateRandomInteger(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);
+  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));
+    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(
+          first_point_coordinate_distribution(rng),
+          first_point_coordinate_distribution(rng)
+        );
+      }
+      else {
+        const auto& previous_point = result_path.back();
+        result_path.emplace_back(
+          previous_point.x + difference_to_previous_point_distribution(rng),
+          previous_point.y + difference_to_previous_point_distribution(rng)
+        );
+      }
+    }
+  }
+  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
+  for (int i = 0; i < 750; ++i)
+#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);
+
+    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();
+
+    Clipper2Lib::PolyTree64 solution_polytree;
+    Clipper2Lib::Paths64 solution_polytree_open;
+    Clipper2Lib::Clipper64 clipper_polytree;
+    clipper_polytree.AddSubject(subject);
+    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();
+
+    EXPECT_EQ(area_paths, area_polytree);
+    EXPECT_EQ(count_paths, count_polytree);
+  }
+}

+ 63 - 0
polygon.mod/clipper2/CPP/Tests/TestRectClip.cpp

@@ -0,0 +1,63 @@
+#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);
+  clp.push_back(rect.AsPath());
+
+  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"));
+  sol = RectClip(rect, sub);
+  EXPECT_TRUE(Area(sol) == Area(sub));
+
+  sub.clear();
+  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"));
+  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"));
+  sol = RectClip(rect, sub);
+  EXPECT_TRUE(sol.empty());
+
+  sub.clear();
+  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"));
+  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"));
+  rect = Rect64(237, 164, 322, 248);
+  sol = RectClip(rect, sub);
+  const auto solBounds = Bounds(sol);
+  EXPECT_EQ(solBounds.Width(), rect.Width());
+  EXPECT_EQ(solBounds.Height(), rect.Height());
+
+}

+ 35 - 0
polygon.mod/clipper2/CPP/Tests/TestTrimCollinear.cpp

@@ -0,0 +1,35 @@
+#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 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 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 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 output4a = TrimCollinear(input4);
+  Path64 output4b = TrimCollinear(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);
+
+}

+ 120 - 0
polygon.mod/clipper2/CPP/Utils/ClipFileLoad.cpp

@@ -0,0 +1,120 @@
+//------------------------------------------------------------------------------
+// Functions load clipping operations from text files
+//------------------------------------------------------------------------------
+
+#include "ClipFileLoad.h"
+#include <sstream>
+
+using namespace std;
+using namespace Clipper2Lib;
+
+bool GetInt(string::const_iterator& s_it,
+  const string::const_iterator& it_end, int64_t& value)
+{
+  value = 0;
+  while (s_it != it_end && *s_it == ' ') ++s_it;
+  if (s_it == it_end) return false;
+  bool is_neg = (*s_it == '-');
+  if (is_neg) ++s_it;
+  string::const_iterator s_it2 = s_it;
+  while (s_it != it_end && *s_it >= '0' && *s_it <= '9')
+  {
+    value = value * 10 + static_cast<int64_t>(*s_it++) - 48;
+  }
+
+  if (s_it == s_it2) return false; //no value
+  //trim trailing space and a comma if present
+  while (s_it != it_end && *s_it == ' ') ++s_it;
+  if (s_it != it_end && *s_it == ',') ++s_it;
+  if (is_neg) value = -value;
+  return true;
+}
+
+bool GetPath(const string& line, Paths64& paths)
+{
+  Path64 p;
+  int64_t x = 0, y = 0;
+  string::const_iterator s_it = line.cbegin(), s_end = line.cend();
+  while (GetInt(s_it, s_end, x) && GetInt(s_it, s_end, y))
+    p.push_back(Point64(x, y));
+  if (p.empty()) return false;
+  paths.push_back(p);
+  return true;
+}
+
+void GetPaths(ifstream& source, Paths64& paths) 
+{
+  while (true)
+  {
+    string line;
+    stringstream::pos_type last_read_line_pos = source.tellg();
+    if (getline(source, line) && GetPath(line, paths))
+      continue;
+    source.seekg(last_read_line_pos, ios_base::beg);
+    break;
+  }
+}
+
+bool LoadTestNum(ifstream &source, int test_num,
+  Paths64 &subj, Paths64 &subj_open, Paths64 &clip, 
+  int64_t& area, int64_t& count, ClipType &ct, FillRule &fr)
+{
+  string line;
+  area = 0; count = 0;
+  if (test_num <= 0) test_num = 1;
+  source.seekg(0, ios_base::beg);
+  subj.clear(); subj_open.clear(); clip.clear();
+
+  while (std::getline(source, line))
+  {    
+    if (test_num)
+    {
+      if (line.find("CAPTION:") != string::npos) --test_num;
+      continue;
+    }
+
+    if (line.find("CAPTION:") != string::npos) break;
+
+    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; 
+    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); 
+    }
+    else if (line.find("SOL_COUNT") != string::npos)
+    {
+      string::const_iterator s_it, s_end = line.cend();
+      s_it = (line.cbegin() + 11);
+      GetInt(s_it, s_end, count);
+    }
+    else if (line.find("SUBJECTS_OPEN") != string::npos)
+    {
+      GetPaths(source, subj_open);
+    }
+    else if (line.find("SUBJECTS") != string::npos)
+    {
+      GetPaths(source, subj);
+    }
+    else if (line.find("CLIPS") != string::npos)
+    {
+      GetPaths(source, clip);
+    }
+  }
+  return !test_num;
+}

+ 34 - 0
polygon.mod/clipper2/CPP/Utils/ClipFileLoad.h

@@ -0,0 +1,34 @@
+//------------------------------------------------------------------------------
+// Functions load clipping operations from text files
+//------------------------------------------------------------------------------
+
+#ifndef CLIPPER_TEST_LOAD_H
+#define CLIPPER_TEST_LOAD_H
+
+#include <fstream>
+#include <string>
+#include "clipper2/clipper.h"
+#ifndef _WIN32
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+inline bool FileExists(const std::string& name)
+{
+  struct stat buffer;
+  return (stat(name.c_str(), &buffer) == 0);
+}
+
+bool LoadTestNum(std::ifstream& source, int test_num,
+  Clipper2Lib::Paths64& subj, Clipper2Lib::Paths64& subj_open, Clipper2Lib::Paths64& clip,
+  int64_t& area, int64_t& count, Clipper2Lib::ClipType& ct, Clipper2Lib::FillRule& fr);
+
+static bool LoadTest(std::ifstream& source,
+  Clipper2Lib::Paths64& subj, Clipper2Lib::Paths64& subj_open, Clipper2Lib::Paths64& clip,
+  int64_t& area, int64_t& count, Clipper2Lib::ClipType& ct, Clipper2Lib::FillRule& fr)
+{
+  return LoadTestNum(source, 1, subj, subj_open, clip, area, count, ct, fr);
+}
+
+#endif //CLIPPER_TEST_LOAD_H

+ 324 - 0
polygon.mod/clipper2/CPP/Utils/ClipFileSave.cpp

@@ -0,0 +1,324 @@
+//------------------------------------------------------------------------------
+// Functions load clipping operations from text files
+//------------------------------------------------------------------------------
+
+#include "ClipFileLoad.h"
+#include "ClipFileSave.h"
+#include <sstream>
+#include <cstring>
+
+using namespace std;
+using namespace Clipper2Lib;
+
+//------------------------------------------------------------------------------
+// Boyer Moore Horspool Search
+//------------------------------------------------------------------------------
+
+class BMH_Search 
+{
+private:
+  uint8_t case_table[256];
+  unsigned needle_len_, needle_len_less1;
+  size_t haystack_len;
+  uint8_t shift[256];
+  uint8_t jump_;
+  std::vector<uint8_t> needle_;
+  std::vector<uint8_t> needle_ic_;
+  char *haystack_ = nullptr;
+  char *current, *end, *last_found;
+  bool case_sensitive_;
+
+  void SetHayStack(std::ifstream& stream)
+  {
+    ClearHaystack();
+    stream.seekg(0, ios::end);
+    haystack_len = static_cast<size_t>(stream.tellg());
+    stream.seekg(0, ios::beg);
+    haystack_ = new char[haystack_len];
+    stream.read(haystack_, haystack_len);
+    current = haystack_;
+    end = current + haystack_len;
+  }
+
+  void SetHayStack(const char* buffer, size_t buff_len)
+  {
+    ClearHaystack();
+    this->haystack_len = buff_len;
+    haystack_ = new char[buff_len];
+    memcpy(haystack_, buffer, buff_len);
+    current = haystack_;
+    end = current + buff_len;
+  }
+
+  void Init()
+  {    
+    case_sensitive_ = false;
+    current = nullptr; end = nullptr; last_found = nullptr;
+  }
+
+  bool FindNext_CaseSensitive()
+  {
+    while (current < end)
+    {
+      uint8_t i = shift[static_cast<unsigned>(*current)];  //compare last byte first
+      if (!i)                   //last byte matches if i == 0
+      {
+        char* j = current - needle_len_less1;
+        while (i < needle_len_less1 && needle_[i] == *(j + i)) ++i;
+        if (i == needle_len_less1)
+        {
+          ++current;
+          last_found = j;
+          return true;
+        }
+        else
+          current += jump_;
+      }
+      else
+        current += i;
+    }
+    return false;
+  }
+
+  bool FindNext_IgnoreCase()
+  {
+    while (current < end)
+    {
+      uint8_t i = shift[case_table[static_cast<unsigned>(*current)]];
+      if (!i)                          
+      {
+        char* j = current - needle_len_less1;
+        while (i < needle_len_less1 &&
+          needle_ic_[i] == case_table[static_cast<unsigned>(*(j + i))]) ++i;
+        if (i == needle_len_less1)
+        {
+          ++current;
+          last_found = j;
+          return true;
+        }
+        else
+          current += jump_;
+      }
+      else
+        current += i;
+    }
+    return false;
+  }
+
+public:
+
+  explicit BMH_Search(std::ifstream& stream, 
+    const std::string& needle = "")
+  {
+    //case blind table
+    for (int c = 0; c < 0x61; ++c) case_table[c] = c;
+    for (int c = 0x61; c < 0x7B; ++c) case_table[c] = c - 0x20;
+    for (int c = 0x7B; c < 256; ++c) case_table[c] = c;
+
+
+    Init();
+    SetHayStack(stream);
+    if (needle.size() > 0) SetNeedle(needle);
+  }
+
+  BMH_Search(const char* haystack, size_t length, 
+    const std::string& needle = "")
+  {
+    Init();
+    SetHayStack(haystack, length);
+    if (needle.size() > 0) SetNeedle(needle);
+  }
+
+  ~BMH_Search()
+  {
+    ClearHaystack();
+    ClearNeedle();
+  }
+
+  void Reset()
+  {
+    current = haystack_; 
+    last_found = nullptr;
+  }
+
+  void SetNeedle(const std::string& needle)
+  {
+    ClearNeedle();
+    needle_len_ = static_cast<int>(needle.size());
+    if (!needle_len_) return;
+
+    //case sensitive needle
+    needle_len_less1 = needle_len_ - 1;
+    needle_.clear();
+    needle_.reserve(needle_len_);
+    for (const char& c : needle) needle_.push_back(static_cast<uint8_t>(c));
+    
+    //case insensitive needle
+    needle_ic_ = needle_;
+    for (std::vector< uint8_t>::iterator ui = needle_ic_.begin(); ui != needle_ic_.end(); ++ui)
+      *ui = case_table[*ui];
+
+    std::fill(std::begin(shift), std::begin(shift) + 256, needle_len_);
+    for (uint8_t j = 0; j < needle_len_less1; ++j)
+      shift[needle_[j]] = needle_len_less1 - j;
+
+    jump_ = shift[needle_[needle_len_less1]];
+    shift[needle_[needle_len_less1]] = 0;
+  }
+
+  inline void ClearNeedle()
+  {
+    needle_.clear();
+    needle_ic_.clear();
+  }
+
+  inline void ClearHaystack()
+  {
+    if (haystack_) delete[] haystack_;
+    haystack_ = nullptr;
+  }
+
+  void CaseSensitive(bool value) { case_sensitive_ = value; };
+
+  bool FindNext()
+  {
+    if (case_sensitive_)
+      return FindNext_CaseSensitive();
+    else
+      return FindNext_IgnoreCase();
+  }
+
+  bool FindFirst()
+  {
+    Reset();
+    return FindNext();
+  }
+
+  inline char* Base() { return haystack_; }
+  inline char* LastFound() { return last_found; }
+  inline size_t LastFoundOffset() { return last_found - haystack_; }
+
+  inline char* FindNextEndLine()
+  {    
+    current = last_found + needle_len_;
+    while (current < end && 
+      *current != char(13) && *current != char(10)) 
+        ++current;
+    return current;
+  }
+
+}; //BMH_Search class
+
+
+void PathsToStream(const Paths64& paths, std::ostream& stream)
+{
+  for (Paths64::const_iterator paths_it = paths.cbegin(); 
+    paths_it != paths.cend(); ++paths_it)
+  {
+    //watch out for empty paths
+    if (paths_it->cbegin() == paths_it->cend()) continue;
+    Path64::const_iterator path_it, path_it_last;
+    for (path_it = paths_it->cbegin(), path_it_last = --paths_it->cend();
+      path_it != path_it_last; ++path_it)
+      stream << *path_it << ", ";
+    stream << *path_it_last << endl;
+  }
+}
+
+static bool GetInt(string::const_iterator& s_it,
+  const string::const_iterator& it_end, int64_t& value)
+{
+  value = 0;
+  while (s_it != it_end && *s_it == ' ') ++s_it;
+  if (s_it == it_end) return false;
+  bool is_neg = (*s_it == '-');
+  if (is_neg) ++s_it;
+  string::const_iterator s_it2 = s_it;
+  while (s_it != it_end && *s_it >= '0' && *s_it <= '9')
+  {
+    value = value * 10 + static_cast<int64_t>(*s_it++) - 48;
+  }
+  if (s_it == s_it2) return false; //no value
+  //trim trailing space and a comma if present
+  while (s_it != it_end && *s_it == ' ') ++s_it;
+  if (s_it != it_end && *s_it == ',') ++s_it;
+  if (is_neg) value = -value;
+  return true;
+}
+
+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)
+{
+  string line;
+  bool found = false;
+  int last_cap_pos = 0, curr_cap_pos = 0;
+  int64_t last_test_no = 0;
+  if (append && FileExists(filename)) //get the number of the preceeding test
+  {
+    ifstream file;
+    file.open(filename, std::ios::binary);
+    if (!file) return false;
+    BMH_Search bmh = BMH_Search(file, "CAPTION:");
+    while (bmh.FindNext()) ;
+    if (bmh.LastFound())
+    {
+      line.assign(bmh.LastFound()+8, bmh.FindNextEndLine());
+      string::const_iterator s_it = line.cbegin(), s_end = line.cend();
+      GetInt(s_it, s_end, last_test_no);
+    }
+  } 
+  else if (FileExists(filename)) 
+    remove(filename.c_str());
+
+  ++last_test_no;
+
+  std::ofstream source;
+  if (append && FileExists(filename))
+    source.open(filename, ios_base::app | ios_base::ate);
+  else
+    source.open(filename);
+
+  string cliptype_string;
+  switch (ct)
+  {
+  case ClipType::None: cliptype_string = "NONE"; break;
+  case ClipType::Intersection: cliptype_string = "INTERSECTION"; break;
+  case ClipType::Union: cliptype_string = "UNION"; break;
+  case ClipType::Difference: cliptype_string = "DIFFERENCE"; break;
+  case ClipType::Xor: cliptype_string = "XOR"; break;
+  }
+
+  string fillrule_string;
+  switch (fr)
+  {
+    case FillRule::EvenOdd: fillrule_string = "EVENODD"; break;
+    case FillRule::NonZero: fillrule_string = "NONZERO"; break;
+    case FillRule::Positive: fillrule_string = "POSITIVE"; break;
+    case FillRule::Negative: fillrule_string = "NEGATIVE"; break;
+  }
+
+  source << "CAPTION: " << last_test_no <<"." << endl;
+  source << "CLIPTYPE: " << cliptype_string << endl;
+  source << "FILLRULE: " << fillrule_string << endl;
+  source << "SOL_AREA: " << area << endl;
+  source << "SOL_COUNT: " << count << endl;
+  if (subj)
+  {
+    source << "SUBJECTS" << endl;
+    PathsToStream(*subj, source);
+  }
+  if (subj_open)
+  {
+    source << "SUBJECTS_OPEN" << endl;
+    PathsToStream(*subj_open, source);
+  }
+  if (clip && clip->size())
+  {
+    source << "CLIPS" << endl;
+    PathsToStream(*clip, source);
+  }
+  source << endl;
+  source.close();
+  return true;
+}

+ 14 - 0
polygon.mod/clipper2/CPP/Utils/ClipFileSave.h

@@ -0,0 +1,14 @@
+//------------------------------------------------------------------------------
+// Functions load clipping operations from text files
+//------------------------------------------------------------------------------
+
+#ifndef CLIPPER_TEST_SAVE_H
+#define CLIPPER_TEST_SAVE_H
+
+#include "clipper2/clipper.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);
+
+#endif //CLIPPER_TEST_SAVE_H

+ 105 - 0
polygon.mod/clipper2/CPP/Utils/Timer.h

@@ -0,0 +1,105 @@
+#ifndef CLIPPER_TIMER_H
+#define CLIPPER_TIMER_H
+
+#include <cstdlib>
+#include <string>
+#include <chrono> 
+#include <iomanip>
+
+/*
+
+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.
+
+Example:
+
+  #include "timer.h"
+  #include "windows.h" //for Sleep() :)
+
+  void main()
+  {
+    {
+      Timer timer("Starting timer now.", "This operation took ");
+      Sleep(1000);
+    }
+  }
+
+*/
+
+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::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)
+  {
+    std::cout << caption << std::endl;
+  }
+
+  explicit Timer(const std::string& caption, const std::string& time_text = "",
+    bool start_paused = false) :
+    paused_(start_paused), time_text_(time_text)
+  {
+    if (caption != "") std::cout << caption << std::endl;
+  }
+
+  void resume()
+  {
+    if (!paused_) return;
+    paused_ = false;
+    time_started_ = std::chrono::high_resolution_clock::now();
+  }
+
+  void pause()
+  {
+    if (paused_) return;
+    std::chrono::high_resolution_clock::time_point now =
+      std::chrono::high_resolution_clock::now();
+    duration_ += (now - time_started_);
+    paused_ = true;
+  }
+
+  ~Timer()
+  {
+    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;
+    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);
+  }
+};
+
+#endif // CLIPPER_TIMER_H

+ 424 - 0
polygon.mod/clipper2/CPP/Utils/clipper.svg.cpp

@@ -0,0 +1,424 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  23 July 2022                                                    *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#include <cstdlib>
+#include <iomanip>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include "clipper.svg.h"
+
+namespace Clipper2Lib {
+
+  const char svg_xml_header_0[] =
+		  "<?xml version=\"1.0\" standalone=\"no\"?>\n"
+          "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n"
+          "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n\n <svg width=\"";
+  const char svg_xml_header_1[] = "\" height=\"";
+  const char svg_xml_header_2[] = "\" viewBox=\"0 0 ";
+  const char svg_xml_header_3[] = "\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n\n";
+
+
+  const char svg_xml_0[] = "\"\n    style=\"fill:";
+  const char svg_xml_1[] = "; fill-opacity:";
+  const char svg_xml_2[] = "; fill-rule:";
+  const char svg_xml_3[] = "; stroke:";
+  const char svg_xml_4[] = "; stroke-opacity:";
+  const char svg_xml_5[] = "; stroke-width:";
+  const char svg_xml_6[] = ";\"/>\n";
+
+  inline std::string ColorToHtml(unsigned clr)
+  {
+    std::stringstream ss;
+    ss << '#' << std::hex << std::setfill('0') << std::setw(6) << (clr & 0xFFFFFF);
+    return ss.str();
+  }
+  //------------------------------------------------------------------------------
+
+  inline float GetAlphaAsFrac(unsigned int clr)
+  {
+    return ((clr >> 24) / 255.0f);
+  }
+  //------------------------------------------------------------------------------
+
+  void SvgWriter::Clear() 
+  {
+    for (PathInfoList::iterator pi_iter = path_infos.begin(); 
+      pi_iter != path_infos.end(); ++pi_iter) delete (*pi_iter);
+    path_infos.resize(0);    
+  }
+  //------------------------------------------------------------------------------
+
+  void SvgWriter::SetCoordsStyle(const std::string &font_name,
+      unsigned font_color, unsigned font_size)
+  {
+    coords_style.font_name = font_name;
+    coords_style.font_color = font_color;
+    coords_style.font_size = font_size;
+  }
+  //------------------------------------------------------------------------------
+
+  void SvgWriter::AddText(const std::string &text,
+    unsigned font_color, unsigned font_size, int x, int y)
+  {
+      text_infos.push_back(new TextInfo(text, "", font_color, 600, font_size, x, y));
+  }
+  //------------------------------------------------------------------------------
+
+  void SvgWriter::AddPath(const PathD &path, bool is_open, FillRule fillrule,
+    unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords)
+  {
+    if (path.size() == 0) return;
+    PathsD p;
+    p.push_back(path);
+    path_infos.push_back(new PathInfo(p, is_open, fillrule,
+      brush_color, pen_color, pen_width, show_coords));
+  }
+  //------------------------------------------------------------------------------
+
+  void SvgWriter::AddPaths(const Paths64& paths, bool is_open, FillRule fillrule,
+    unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords)
+  {
+    if (paths.size() == 0) return;
+    PathsD tmp = ScalePaths<double, int64_t>(paths, scale_);
+    PathInfo* pi = new PathInfo(tmp, is_open, fillrule,
+      brush_color, pen_color, pen_width, show_coords);
+    path_infos.push_back(pi);
+  }
+  //------------------------------------------------------------------------------
+
+  void SvgWriter::AddPaths(const PathsD &paths, bool is_open, FillRule fillrule,
+    unsigned brush_color, unsigned pen_color, double pen_width, bool show_coords)
+  {
+    if (paths.size() == 0) return;
+    path_infos.push_back(new PathInfo(paths, is_open, fillrule, 
+      brush_color, pen_color, pen_width, show_coords));
+  }
+  //------------------------------------------------------------------------------
+
+  void SvgWriter::DrawCircle(std::ofstream& file, double x, double y, double radius)
+  {
+    file << "  <circle cx = \"" << x << "\" cy = \"" << y << "\" r = \"" << radius
+      << "\" stroke = \"none\" fill = \"red\" />\n";
+  }
+  //------------------------------------------------------------------------------
+
+  PathsD SimulateNegativeFill(const PathsD paths)
+  {
+    return Union(paths, FillRule::Negative);
+  }
+  //------------------------------------------------------------------------------
+
+  PathsD SimulatePositiveFill(const PathsD paths)
+  {
+    return Union(paths, FillRule::Positive);
+  }
+  //------------------------------------------------------------------------------
+
+  bool SvgWriter::SaveToFile(const std::string &filename,
+    int max_width, int max_height, int margin)
+  {
+    RectD rec = MaxInvalidRectD;
+    for (const PathInfo* pi : path_infos)
+      for (const PathD& path : pi->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 false;
+
+    if (margin < 20) margin = 20;
+    if (max_width < 100) max_width = 100;
+    if (max_height < 100) max_height = 100;
+    double  scale = std::min(
+      static_cast<double>(max_width - margin * 2) / rec.Width(),
+      static_cast<double>(max_height - margin * 2) / rec.Height());
+    
+    rec.Scale(scale);
+    double offsetX = margin -rec.left;
+    double offsetY = margin -rec.top;
+
+    std::ofstream file;
+    file.open(filename);
+    if (!file.is_open()) return false;
+    file.setf(std::ios::fixed);
+    file.precision(0);
+    file << svg_xml_header_0 <<
+      max_width << "px" << svg_xml_header_1 <<
+      max_height << "px" << svg_xml_header_2 <<
+      max_width << " " <<
+      max_height << svg_xml_header_3;
+    setlocale(LC_NUMERIC, "C");
+    file.precision(2);
+    
+    for (PathInfo* pi : path_infos)
+    {
+      if (pi->is_open_path || GetAlphaAsFrac(pi->brush_color_) == 0 ||
+        (pi->fillrule_ != FillRule::Positive && pi->fillrule_ != FillRule::Negative))
+          continue;
+
+      PathsD ppp = pi->fillrule_ == 
+        FillRule::Positive ? SimulatePositiveFill(pi->paths_) : SimulateNegativeFill(pi->paths_);
+
+      file << "  <path d=\"";
+      for (PathD& path : ppp)
+      {
+        if (path.size() < 2 || (path.size() == 2 && !pi->is_open_path)) continue;
+        file << " M " << (static_cast<double>(path[0].x) * scale + offsetX) <<
+          " " << (static_cast<double>(path[0].y) * scale + offsetY);
+        for (PointD& pt : path)
+          file << " L " << (pt.x * scale + offsetX) << " "
+          << (pt.y * scale + offsetY);
+        if (!pi->is_open_path)  file << " z";
+      }
+
+      file << svg_xml_0 << ColorToHtml(pi->brush_color_) <<
+        svg_xml_1 << GetAlphaAsFrac(pi->brush_color_) <<
+        svg_xml_2 << "evenodd" <<
+        svg_xml_3 << ColorToHtml(0) <<
+        svg_xml_4 << GetAlphaAsFrac(0) <<
+        svg_xml_5 << pi->pen_width_ << svg_xml_6;
+    }
+
+    for (PathInfo* pi : path_infos) 
+    {
+      unsigned brushColor =
+        (pi->fillrule_ == FillRule::Positive || pi->fillrule_ == FillRule::Negative) ?
+        0 : pi->brush_color_;
+
+      file << "  <path d=\"";      
+      for (PathD& path : pi->paths_)
+      {
+        if (path.size() < 2 || (path.size() == 2 && !pi->is_open_path)) continue;
+        file << " M " << (static_cast<double>(path[0].x) * scale + offsetX) <<
+          " " << (static_cast<double>(path[0].y) * scale + offsetY);
+        for (PointD& pt : path)
+          file << " L " << (pt.x * scale + offsetX) << " " 
+            << (pt.y * scale + offsetY);
+        if(!pi->is_open_path)  file << " z";
+      }
+
+      file << svg_xml_0 << ColorToHtml(brushColor) <<
+        svg_xml_1 << 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=\"" <<
+          coords_style.font_size  << "\" fill=\""<< ColorToHtml(coords_style.font_color) << 
+          "\" fill-opacity=\"" << GetAlphaAsFrac(coords_style.font_color) << "\">\n";
+        for (PathD& path : pi->paths_)
+        {
+          size_t path_len = path.size();
+          if (path_len < 2 || (path_len == 2 && !pi->is_open_path)) continue;
+          for (PointD& pt : path)
+            file << "    <text x=\"" << static_cast<int>(pt.x * scale + offsetX) <<
+              "\" y=\"" << static_cast<int>(pt.y * scale + offsetY) << "\">" <<
+              pt.x << "," << pt.y << "</text>\n";         
+        }
+        file << "  </g>\n\n";
+      }
+    }
+
+    //draw red dots at all 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);
+
+    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) << "\">" <<
+        ti->text << "</text>\n  </g>\n\n";
+    }
+
+    file << "</svg>\n";
+    file.close();
+    setlocale(LC_NUMERIC, "");
+    return true;
+  }
+  
+  //------------------------------------------------------------------------------
+  //------------------------------------------------------------------------------
+
+  bool SkipBlanks(std::string::const_iterator& si,
+    const std::string::const_iterator se)
+  {
+    while (si != se && *si <= ' ') ++si;
+    return si != se;
+  }
+  //------------------------------------------------------------------------------
+
+  bool GetNum(std::string::const_iterator& si,
+    const std::string::const_iterator se, double& value)
+  {
+    while (si != se && *si <= ' ') ++si;
+    if (si != se &&  *si == ',') ++si;
+    while (si != se && *si <= ' ') ++si;
+    if (si == se) return false;
+    std::string::const_iterator sit = si;
+    while ((si != se && (*si <= ' ')) || *si == ',') ++si;
+    bool isneg = *si == '-';
+    if (isneg) ++si;
+    value = 0;
+    int loop_cnt = 0, decpos = -1;
+    while (si != se)
+    {
+      if (*si == '.')
+      {
+        if (loop_cnt == 0 || decpos >= 0) return false; //invalid
+        else decpos = loop_cnt;
+      }
+      else if (*si < '0' || *si > '9') break;
+      else value = value * 10 + (long)(*si) - (long)'0';
+      ++si; loop_cnt++;
+    }
+    if (decpos >= 0)
+    {
+      decpos = loop_cnt - decpos - 1;
+      value *= pow(10, -decpos);
+    }
+    if (isneg) value = -value;
+    return si != sit;
+  }
+  //------------------------------------------------------------------------------
+
+  bool GetCommand(std::string::const_iterator& si,
+    char& command, bool& is_relative)
+  {
+    if (*si >= 'a' && *si <= 'z')
+    {
+      is_relative = true;
+      command = toupper(*si);
+    }
+    else if (*si >= 'A' && *si <= 'Z')
+    {
+      command = *si;
+      is_relative = false;
+    }
+    else return false; //ie leave command and is_relative unchanged!
+    ++si; //only increment the offset with a valid command
+    return true;
+  }
+  //------------------------------------------------------------------------------
+
+  inline bool Find(const std::string& text,
+    std::string::const_iterator& start, const std::string::const_iterator& end)
+  {
+    start = std::search(start, end, text.cbegin(), text.cend());
+    return start != end;
+  }
+  //------------------------------------------------------------------------------
+
+  bool SvgReader::LoadPath(std::string::const_iterator& p,
+    const std::string::const_iterator& pe)
+  {
+    if (!Find("d=\"", p, pe)) return false;
+    p += 3;
+    if (!SkipBlanks(p, pe)) return false;
+    char command;
+    bool is_relative;
+    int vals_needed = 2;
+    //nb: M == absolute move, m == relative move 
+    if (!GetCommand(p, command, is_relative) || command != 'M') return false;
+    double vals[2] { 0, 0 };
+    double x = 0, y = 0;
+    ++p;
+    if (!GetNum(p, pe, x) || !GetNum(p, pe, y)) return false;
+    PathsD ppp;
+    PathD pp;
+    pp.push_back(PointD(x, y));
+    while (SkipBlanks(p, pe)) 
+    {
+      if (GetCommand(p, command, is_relative)) 
+      {
+        switch (command) {
+        case 'L': 
+        case 'M': {vals_needed = 2;  break; }
+        case 'H': 
+        case 'V': {vals_needed = 1;  break; }
+        case 'Z': {
+            if (pp.size() > 2) ppp.push_back(pp);
+            pp.clear();
+            vals_needed = 0;  
+            break;
+        }
+        default: vals_needed = -1;
+        }
+        if (vals_needed < 0) break; //oops!
+      }
+      else
+      {
+        for (int i = 0; i < vals_needed; ++i)
+            if (!GetNum(p, pe, vals[i])) vals_needed = -1;
+        if (vals_needed <= 0) break; //oops!
+        switch (vals_needed) {
+          case 1: 
+          {
+              if (command == 'V') y = (is_relative ? y + vals[0] : vals[0]);
+              else x = (is_relative ? x + vals[0] : vals[0]);
+              break;
+          }
+          case 2: 
+          {
+              x = (is_relative ? x + vals[0] : vals[0]);
+              y = (is_relative ? y + vals[1] : vals[1]);
+              break;
+          }
+          default: break;
+        }
+        pp.push_back(PointD(x, y));
+      }
+    }
+    if (pp.size() > 3) ppp.push_back(pp);
+    //todo - fix fillrule
+    path_infos.push_back(new PathInfo(ppp, false, FillRule::EvenOdd, 0, 0xFF000000, 1, false));
+    return  (ppp.size() > 0);
+  }
+
+  bool SvgReader::LoadFromFile(const std::string &filename)
+  {
+      Clear();
+      std::ifstream file;
+      file.open(filename);      
+      std::stringstream xml_buff;
+      xml_buff << file.rdbuf();
+      file.close();
+      xml = xml_buff.str();
+      std::string::const_iterator p = xml.cbegin(), q, xml_end = xml.cend();
+
+      while (Find("<path", p, xml_end))
+      {      
+        p += 6;
+        q = p;
+        if (!Find("/>", p, xml_end)) break;
+        LoadPath(q, p);
+        p += 2;
+      }
+      return path_infos.size() > 0;
+  }
+
+  PathsD SvgReader::GetPaths() 
+  {
+    PathsD result;
+      for (size_t i = 0; i < path_infos.size(); ++i)
+          for (size_t j = 0; j < path_infos[i]->paths_.size(); ++j)
+              result.push_back(path_infos[i]->paths_[j]);
+      return result;
+  }
+
+} //namespace

+ 122 - 0
polygon.mod/clipper2/CPP/Utils/clipper.svg.h

@@ -0,0 +1,122 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  23 July 2022                                                    *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef svglib_h
+#define svglib_h
+
+#include <cstdlib>
+#include <string>
+#include "clipper2/clipper.h"
+
+namespace Clipper2Lib {
+
+    class PathInfo {
+    private:
+        PathsD paths_;
+        bool is_open_path;
+        FillRule fillrule_;
+        unsigned brush_color_;
+        unsigned pen_color_;
+        double pen_width_;
+        bool show_coords_;
+
+    public:
+        PathInfo(const PathsD& paths, bool is_open, FillRule fillrule,
+            unsigned brush_clr, unsigned pen_clr, double pen_width, bool show_coords) :
+            paths_(paths), is_open_path(is_open), fillrule_(fillrule),
+            brush_color_(brush_clr), pen_color_(pen_clr), 
+            pen_width_(pen_width), show_coords_(show_coords) {};
+        friend class SvgWriter;
+        friend class SvgReader;
+    };
+    typedef std::vector< PathInfo* > PathInfoList;
+
+  //---------------------------------------------------------------------------
+  // SvgWriter
+  //---------------------------------------------------------------------------
+
+  class SvgWriter
+  {
+    class CoordsStyle {
+    public:
+      std::string font_name;
+      unsigned font_color = 0xFF000000;
+      unsigned font_size = 11;
+    };
+
+    class TextInfo {
+    public:
+        std::string text;
+        std::string font_name;
+        unsigned font_color = 0xFF000000;
+        unsigned font_weight = 600;
+        unsigned font_size = 11;
+        int x = 0;
+        int y = 0;
+
+        TextInfo(const std::string &txt, const std::string &fnt_name, unsigned color,
+            unsigned weight, unsigned size, int coord_x, int coord_y) :
+            text(txt), font_name(fnt_name), font_color(color), font_weight(weight), font_size(size),
+            x(coord_x), y(coord_y) {};
+        friend class SvgWriter;
+    };
+
+    typedef std::vector< TextInfo* > TextInfoList;
+
+  private:
+      double scale_;
+      FillRule fill_rule_;
+      CoordsStyle coords_style;
+      TextInfoList text_infos;
+      PathInfoList path_infos;
+      void DrawCircle(std::ofstream& file, double x, double y, double radius);
+  public:
+    explicit SvgWriter(int precision = 0)
+    { 
+      fill_rule_ = FillRule::NonZero;
+      coords_style.font_name = "Verdana";
+      coords_style.font_color = 0xFF000000; 
+      coords_style.font_size = 11; 
+      scale_ = std::pow(10, precision);
+    };
+
+    ~SvgWriter() { Clear(); };
+        
+    void Clear();
+    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, 
+      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);
+    bool SaveToFile(const std::string &filename, int max_width, int max_height, int margin);
+  };
+
+  //---------------------------------------------------------------------------
+  // SvgReader
+  //---------------------------------------------------------------------------
+
+  class SvgReader
+  {
+  private:
+      PathInfoList path_infos;
+      bool LoadPath(std::string::const_iterator& p,
+        const std::string::const_iterator& pe);
+  public:
+      std::string xml;
+      bool LoadFromFile(const std::string &filename);
+      void Clear() { path_infos.clear(); };
+      PathsD GetPaths();
+  };
+
+}
+
+#endif //svglib_h

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

@@ -0,0 +1,137 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 June 2022                                                    *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef svgutillib_h
+#define svgutillib_h
+
+#include <cstdlib>
+#include <string>
+#include "clipper2/clipper.h"
+#include "clipper.svg.h"
+#ifndef _WIN32
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+namespace Clipper2Lib {
+
+  static const unsigned subj_brush_clr = 0x1800009C;
+  static const unsigned subj_stroke_clr = 0xCCB3B3DA;
+  static const unsigned clip_brush_clr = 0x129C0000; 
+  static const unsigned clip_stroke_clr = 0xCCFFA07A;
+  static const unsigned solution_brush_clr = 0x4466FF66;
+
+  inline bool FileExists(const std::string& name)
+  {
+    struct stat buffer;
+    return (stat(name.c_str(), &buffer) == 0);
+  }
+
+  inline void SvgAddCaption(SvgWriter& svg, const std::string& caption, int x, int y)
+  {
+    svg.AddText(caption, 0xFF000000, 14, x, y);
+  }
+
+
+  //nb: SVG only supports fill rules NonZero and EvenOdd
+  //    so while we can clip using Positive and Negative
+  //    we can't displaying these paths accurately in SVG
+  //    without (safely) changing the fill rule
+
+  inline void SvgAddSubject(SvgWriter& svg, const PathsD& path, FillRule fillrule)
+  {
+    if (svg.Fill_Rule() == FillRule::Positive ||
+      svg.Fill_Rule() == FillRule::Negative)
+    {
+      svg.AddPaths(path, false, fillrule, 0x0, subj_stroke_clr, 0.8, false);
+      PathsD tmp = Union(path, svg.Fill_Rule());
+      svg.AddPaths(tmp, false, fillrule, subj_brush_clr, subj_stroke_clr, 0.8, false);
+    } 
+    else
+      svg.AddPaths(path, false, fillrule, subj_brush_clr, subj_stroke_clr, 0.8, false);
+  }
+
+
+  inline void SvgAddSubject(SvgWriter& svg, const Paths64& paths, FillRule fillrule)
+  {
+    PathsD tmp = TransformPaths<double, int64_t>(paths);
+    svg.AddPaths(tmp, false, fillrule, subj_brush_clr, subj_stroke_clr, 0.8, false);
+  }
+
+
+  inline void SvgAddOpenSubject(SvgWriter& svg,
+    const PathsD& path, FillRule fillrule = FillRule::EvenOdd, bool is_joined = false)
+  {
+    if (is_joined)
+      svg.AddPaths(path, false, fillrule, subj_brush_clr, subj_stroke_clr, 1.3, false);
+    else
+      svg.AddPaths(path, true, fillrule, 0x0, subj_stroke_clr, 1.3, false);
+  }
+
+
+  inline void SvgAddOpenSubject(SvgWriter& svg,
+    const Paths64& path, FillRule fillrule = FillRule::EvenOdd, bool is_joined = false)
+  {
+    svg.AddPaths(TransformPaths<double, int64_t>(path),
+      !is_joined, fillrule, 0x0, 0xCCB3B3DA, 1.3, false);
+  }
+
+
+  inline void SvgAddClip(SvgWriter& svg, const PathsD& path, FillRule fillrule)
+  {
+    svg.AddPaths(path, false, fillrule, clip_brush_clr, clip_stroke_clr, 0.8, false);
+  }
+
+
+  inline void SvgAddClip(SvgWriter& svg, const Paths64& paths, FillRule fillrule)
+  {
+    PathsD tmp = TransformPaths<double, int64_t>(paths);
+    svg.AddPaths(tmp, false, fillrule, clip_brush_clr, clip_stroke_clr, 0.8, false);
+  }
+
+
+  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)
+  {
+    svg.AddPaths(TransformPaths<double, int64_t>(path),
+      false, fillrule, solution_brush_clr, 0xFF003300, 1.2, show_coords);
+  }
+
+
+  inline void SvgAddOpenSolution(SvgWriter& svg, const PathsD& path, FillRule fillrule,
+    bool show_coords, bool is_joined = false)
+  {
+    svg.AddPaths(path, !is_joined, fillrule, 0x0, 0xFF006600, 1.8, show_coords);
+  }
+
+
+  inline void SvgAddOpenSolution(SvgWriter& svg, const Paths64& path, 
+    FillRule fillrule, bool show_coords, bool is_joined = false)
+  {
+    svg.AddPaths(TransformPaths<double, int64_t>(path),
+      !is_joined, fillrule, 0x0, 0xFF006600, 1.8, show_coords);
+  }
+
+
+  inline void SvgSaveToFile(SvgWriter& svg,
+    const std::string& filename,
+    int max_width = 0, int max_height = 0, int margin = 0)
+  {
+    if (FileExists(filename)) remove(filename.c_str());
+    svg.SetCoordsStyle("Verdana", 0xFF0000AA, 9);
+    svg.SaveToFile(filename, max_width, max_height, margin);
+  }
+}
+
+#endif //svgutillib_h

+ 120 - 0
polygon.mod/clipper2/CSharp/.editorconfig

@@ -0,0 +1,120 @@
+# Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\mepengadmin\Documents\Projects\Clipper2-DJGosnell\CSharp\Clipper2Lib\ codebase based on best match to current usage at 4/6/2022
+# You can modify the rules from these initially generated values to suit your own policies
+# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
+[*.cs]
+
+
+#Core editorconfig formatting - indentation
+
+#use soft tabs (spaces) for indentation
+indent_style = space
+indent_size = 2
+
+#Formatting - indentation options
+
+#indent switch case contents.
+csharp_indent_case_contents = true
+#indent switch labels
+csharp_indent_switch_labels = true
+
+#Formatting - new line options
+
+#place else statements on a new line
+csharp_new_line_before_else = true
+#require members of object intializers to be on separate lines
+csharp_new_line_before_members_in_object_initializers = true
+#require braces to be on a new line for object_collection_array_initializers, methods, types, and properties (also known as "Allman" style)
+csharp_new_line_before_open_brace = all
+
+#Formatting - organize using options
+
+#sort System.* using directives alphabetically, and place them before other usings
+dotnet_sort_system_directives_first = true
+
+#Formatting - spacing options
+
+#require a space between a cast and the value
+csharp_space_after_cast = true
+#require a space after a keyword in a control flow statement such as a for loop
+csharp_space_after_keywords_in_control_flow_statements = true
+#remove space within empty argument list parentheses
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+#remove space between method call name and opening parenthesis
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
+csharp_space_between_method_call_parameter_list_parentheses = false
+#remove space within empty parameter list parentheses for a method declaration
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+#Formatting - wrapping options
+
+#leave code block on single line
+csharp_preserve_single_line_blocks = true
+#leave statements and member declarations on the same line
+csharp_preserve_single_line_statements = true
+
+#Style - Code block preferences
+
+#prefer no curly braces if allowed
+csharp_prefer_braces = false:suggestion
+
+#Style - expression bodied member options
+
+#prefer expression-bodied members for accessors
+csharp_style_expression_bodied_accessors = true:suggestion
+#prefer block bodies for methods
+csharp_style_expression_bodied_methods = false:suggestion
+#prefer block bodies for operators
+csharp_style_expression_bodied_operators = false:suggestion
+#prefer expression-bodied members for properties
+csharp_style_expression_bodied_properties = true:suggestion
+
+#Style - expression level options
+
+#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+#Style - Expression-level  preferences
+
+#prefer objects to be initialized using object initializers when possible
+dotnet_style_object_initializer = true:suggestion
+
+#Style - implicit and explicit types
+
+#prefer explicit type over var in all cases, unless overridden by another code style rule
+csharp_style_var_elsewhere = false:suggestion
+#prefer explicit type over var to declare variables with built-in system types such as int
+csharp_style_var_for_built_in_types = false:suggestion
+#prefer explicit type over var when the type is already mentioned on the right-hand side of a declaration
+csharp_style_var_when_type_is_apparent = false:suggestion
+
+#Style - language keyword and framework type options
+
+#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+
+#Style - modifier options
+
+#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
+
+#Style - Modifier preferences
+
+#when this rule is set to a list of modifiers, prefer the specified ordering.
+csharp_preferred_modifier_order = public,private,internal,static,readonly,override:suggestion
+
+#Style - Pattern matching
+
+#prefer pattern matching instead of is expression with type casts
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+
+#Style - qualification options
+
+#prefer fields not to be prefaced with this. or Me. in Visual Basic
+dotnet_style_qualification_for_field = false:suggestion
+#prefer methods not to be prefaced with this. or Me. in Visual Basic
+dotnet_style_qualification_for_method = false:suggestion
+#prefer properties not to be prefaced with this. or Me. in Visual Basic
+dotnet_style_qualification_for_property = false:suggestion

+ 260 - 0
polygon.mod/clipper2/CSharp/.gitignore

@@ -0,0 +1,260 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc

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

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Jobs;
+
+namespace Clipper2Lib.Benchmark
+{
+  public class FastConfig : ManualConfig
+    {
+      public FastConfig()
+      {
+        Add(DefaultConfig.Instance); 
+        AddJob(Job.Default
+            .WithLaunchCount(1)
+            .WithWarmupCount(1)
+            .WithIterationCount(1)
+        );
+      }
+    }
+
+    [MemoryDiagnoser]
+    [Config(typeof(FastConfig))] //comment out for marginally more accurate results
+    public class Benchmarks
+    {
+        private Paths64 _subj;
+        private Paths64 _clip;
+        private Paths64 _solution;
+        private const int DisplayWidth = 800;
+        private const int DisplayHeight = 600;
+
+        [Params(1000, 2000, 3000, 4000, 5000/*, 6000, 7000, 8000*/)]
+        public int EdgeCount { get; set; }
+
+        [GlobalSetup]
+        public void GlobalSetup()
+        {
+            Random rand = new ();
+
+            _subj = new ();
+            _clip = new ();
+            _solution = new ();
+
+            _subj.Add(MakeRandomPath(DisplayWidth, DisplayHeight, EdgeCount, rand));
+            _clip.Add(MakeRandomPath(DisplayWidth, DisplayHeight, EdgeCount, rand));
+        }
+
+        [Benchmark]
+        public void Intersection_N()
+        {
+            Clipper64 c = new ();
+            c.AddSubject(_subj);
+            c.AddClip(_clip);
+            c.Execute(ClipType.Intersection, FillRule.NonZero, _solution);
+        }
+
+/*
+        [Benchmark]
+        public void Union_N()
+        {
+            Clipper c = new Clipper();
+            c.AddSubject(_subj);
+            c.AddClip(_clip);
+            c.Execute(ClipType.Union, FillRule.NonZero, _solution);
+        }
+
+        [Benchmark]
+        public void Difference_N()
+        {
+            Clipper c = new ();
+            c.AddSubject(_subj);
+            c.AddClip(_clip);
+            c.Execute(ClipType.Difference, FillRule.NonZero, _solution);
+        }
+
+        [Benchmark]
+        public void Xor_N()
+        {
+            Clipper c = new ();
+            c.AddSubject(_subj);
+            c.AddClip(_clip);
+            c.Execute(ClipType.Xor, FillRule.NonZero, _solution);
+        }
+*/
+        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;
+        }
+    }
+}

+ 22 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Clipper2Lib.Benchmark.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+  <PropertyGroup>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <DebugSymbols>true</DebugSymbols>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Optimize>true</Optimize>
+    <Configuration>Release</Configuration>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
+    <PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.1" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Clipper2Lib\Clipper2Lib.csproj" />
+  </ItemGroup>
+</Project>

+ 12 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Benchmark/Program.cs

@@ -0,0 +1,12 @@
+using BenchmarkDotNet.Running;
+
+namespace Clipper2Lib.Benchmark
+{
+    public class Program
+    {        
+        public static void Main()
+        {
+            BenchmarkRunner.Run<Benchmarks>();
+        }
+    }
+}

二進制
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/Clipper2.ico


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

@@ -0,0 +1,41 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net5.0</TargetFramework>
+    <EnableNETAnalyzers>false</EnableNETAnalyzers>
+    <ApplicationIcon>Clipper2.ico</ApplicationIcon>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
+    <Optimize>false</Optimize>
+    <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Include="clip.bin" />
+    <EmbeddedResource Include="subj.bin" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
+    <ProjectReference Include="..\..\Utils\ClipFileIO\Clipper.FileIO.csproj" />
+    <ProjectReference Include="..\..\Utils\SVG\Clipper2.SVG.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Resource Include="Clipper2.ico" />
+    <Resource Include="Main.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Remove="*.svg" />
+    <None Remove="*.ico" />
+  </ItemGroup>
+  
+</Project>

+ 48 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/ConsoleDemo.sln

@@ -0,0 +1,48 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32126.315
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleDemo", "ConsoleDemo.csproj", "{580C305D-76A9-47AC-809C-1FC474E9EF1B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{82897A5C-39B4-4D45-AEF1-38653D6A07E9}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib", "..\..\Clipper2Lib\Clipper2Lib.csproj", "{624433FB-E378-499C-9A27-CBB702C990C3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper.FileIO", "..\..\Utils\ClipFileIO\Clipper.FileIO.csproj", "{5DFB6B9E-C237-4A9D-B29D-59D5CC4CEEDF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2.SVG", "..\..\Utils\SVG\Clipper2.SVG.csproj", "{0AF70A97-CC9F-4CFF-9204-7570443B84E8}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{580C305D-76A9-47AC-809C-1FC474E9EF1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{580C305D-76A9-47AC-809C-1FC474E9EF1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{580C305D-76A9-47AC-809C-1FC474E9EF1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{580C305D-76A9-47AC-809C-1FC474E9EF1B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{624433FB-E378-499C-9A27-CBB702C990C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{624433FB-E378-499C-9A27-CBB702C990C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{624433FB-E378-499C-9A27-CBB702C990C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{624433FB-E378-499C-9A27-CBB702C990C3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5DFB6B9E-C237-4A9D-B29D-59D5CC4CEEDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5DFB6B9E-C237-4A9D-B29D-59D5CC4CEEDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5DFB6B9E-C237-4A9D-B29D-59D5CC4CEEDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5DFB6B9E-C237-4A9D-B29D-59D5CC4CEEDF}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0AF70A97-CC9F-4CFF-9204-7570443B84E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0AF70A97-CC9F-4CFF-9204-7570443B84E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0AF70A97-CC9F-4CFF-9204-7570443B84E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0AF70A97-CC9F-4CFF-9204-7570443B84E8}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {278478F4-9ED6-4AAD-AA42-9F0BC22D605C}
+	EndGlobalSection
+EndGlobal

+ 93 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs

@@ -0,0 +1,93 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  17 July 2022                                                    *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System.Collections.Generic;
+using System.Reflection;
+using System.IO;
+using Clipper2Lib;
+
+namespace ClipperDemo1
+{
+  public class Application
+  {
+
+    public static void Main()
+    {
+      ClipSimpleShapes();
+      ClipTestPolys();
+    }
+    public static void ClipSimpleShapes()
+    {
+      Paths64 subject = new ();
+      Paths64 clip = new ();
+      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);
+
+      SimpleSvgWriter svg = new ();
+      SvgUtils.AddSubject(svg, subject);
+      SvgUtils.AddClip(svg, clip);
+      SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperA.svg", fillrule, 400, 300, 20);
+      ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperA.svg");
+
+      svg.ClearAll();
+      SvgUtils.AddSubject(svg, subject);
+      SvgUtils.AddClip(svg, clip);
+      SvgUtils.AddSolution(svg, solution, false);
+      SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperB.svg", fillrule, 400, 300, 20);
+      ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperB.svg");
+    }
+
+    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);
+
+      SimpleSvgWriter svg = new ();
+      SvgUtils.AddSubject(svg, subject);
+      SvgUtils.AddClip(svg, clip);
+      SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperC.svg", fillrule, 800, 600, 20);
+      ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperC.svg");
+
+      svg.ClearAll();
+      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");
+    }
+
+    public static Paths64 LoadPathsFromResource(string resourceName)
+    {
+      using Stream stream = Assembly.GetExecutingAssembly().
+        GetManifestResourceStream(resourceName);
+      if (stream == null) return new Paths64();
+      using BinaryReader reader = new (stream);
+      int len = reader.ReadInt32();
+      Paths64 result = new (len);
+      for (int i = 0; i < len; i++)
+      {
+        int len2 = reader.ReadInt32();
+        Path64 p = new (len2);
+        for (int j = 0; j < len2; j++)
+        {
+          long X = reader.ReadInt64();
+          long Y = reader.ReadInt64();
+          p.Add(new Point64(X, Y));
+        }
+        result.Add(p);
+      }
+      return result;
+    }
+
+  } //end Application
+} //namespace

二進制
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/clip.bin


二進制
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/ConsoleDemo/subj.bin


二進制
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/Clipper2.ico


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

@@ -0,0 +1,41 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net5.0</TargetFramework>
+    <EnableNETAnalyzers>false</EnableNETAnalyzers>
+    <ApplicationIcon>Clipper2.ico</ApplicationIcon>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
+    <Optimize>false</Optimize>
+    <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Include="rabbit.bin" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
+    <ProjectReference Include="..\..\Utils\ClipFileIO\Clipper.FileIO.csproj" />
+    <ProjectReference Include="..\..\Utils\SVG\Clipper2.SVG.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Resource Include="Clipper2.ico" />
+    <Resource Include="Main.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Remove="*.svg" />
+    <None Remove="*.ico" />
+  </ItemGroup>
+
+</Project>

+ 42 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/InflateDemo.sln

@@ -0,0 +1,42 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32126.315
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InflateDemo", "InflateDemo.csproj", "{580C305D-76A9-47AC-809C-1FC474E9EF1B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{82897A5C-39B4-4D45-AEF1-38653D6A07E9}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib", "..\..\Clipper2Lib\Clipper2Lib.csproj", "{91DACBAE-8661-4AF3-A13E-2F7D56F10348}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2.SVG", "..\..\Utils\SVG\Clipper2.SVG.csproj", "{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{580C305D-76A9-47AC-809C-1FC474E9EF1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{580C305D-76A9-47AC-809C-1FC474E9EF1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{580C305D-76A9-47AC-809C-1FC474E9EF1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{580C305D-76A9-47AC-809C-1FC474E9EF1B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{91DACBAE-8661-4AF3-A13E-2F7D56F10348}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{91DACBAE-8661-4AF3-A13E-2F7D56F10348}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{91DACBAE-8661-4AF3-A13E-2F7D56F10348}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{91DACBAE-8661-4AF3-A13E-2F7D56F10348}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {278478F4-9ED6-4AAD-AA42-9F0BC22D605C}
+	EndGlobalSection
+EndGlobal

+ 106 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs

@@ -0,0 +1,106 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 September 2022                                               *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using Clipper2Lib;
+
+namespace ClipperDemo1
+{
+
+  public class Application
+  {
+
+    public static void Main()
+    {
+      DoSimpleShapes();
+      DoRabbit();
+    }
+
+    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);
+
+      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);
+      }
+
+      //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);
+      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");
+    }
+
+    public static void DoRabbit()
+    {
+      PathsD pd = LoadPathsFromResource("InflateDemo.rabbit.bin");
+
+      PathsD solution = new (pd);
+      while (pd.Count > 0)
+      {
+        //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);
+        solution.AddRange(pd);
+      }
+
+      SimpleSvgWriter svg = new ();
+      SvgUtils.AddSolution(svg, solution, false);
+      SvgUtils.SaveToFile(svg, "../../../rabbit2.svg", FillRule.EvenOdd, 450, 720, 10);
+      ClipperFileIO.OpenFileWithDefaultApp("../../../rabbit2.svg");
+    }
+
+    public static PathsD LoadPathsFromResource(string resourceName)
+    {
+      using Stream stream = Assembly.GetExecutingAssembly().
+        GetManifestResourceStream(resourceName);
+      if (stream == null) return new PathsD();
+      using BinaryReader reader = new (stream);
+      int len = reader.ReadInt32();
+      PathsD result = new (len);
+      for (int i = 0; i < len; i++)
+      {
+        int len2 = reader.ReadInt32();
+        PathD p = new (len2);
+        for (int j = 0; j < len2; j++)
+        {
+          long X = reader.ReadInt64();
+          long Y = reader.ReadInt64();
+          p.Add(new PointD(X, Y));
+        }
+        result.Add(p);
+      }
+      return result;
+    }
+  
+  } //end Application
+
+} //namespace

二進制
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/InflateDemo/rabbit.bin


二進制
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/Clipper2.ico


+ 111 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/Main.cs

@@ -0,0 +1,111 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 September 2022                                               *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System;
+using System.IO;
+using Clipper2Lib;
+
+namespace ClipperDemo1
+{
+  public class Application
+  {
+
+    public static void Main()
+    {
+      DoRandomPoly(true);
+    }
+
+    public static void DoRandomPoly(bool makeNewPoly)
+    {
+      Random rand = new();
+      Paths64 subjOpen, clip, solOpen;
+      Rect64 rec;
+
+      if (makeNewPoly)
+      {
+        ////////////////////
+        const int count = 75;
+        ////////////////////
+
+        rec = new(100, 100, 700, 500);
+        clip = new() { rec.AsPath() };
+        subjOpen = new() { MakeRandomPath(800, 600, count, rand) };
+
+        // 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();
+      }
+      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);
+      }
+
+      /////////////////////////////////////////////////
+      solOpen = Clipper.RectClipLines(rec, subjOpen);
+      /////////////////////////////////////////////////
+
+      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);
+      ClipperFileIO.OpenFileWithDefaultApp("../../../rectclip.svg");
+    }
+
+    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);
+    }
+    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)
+    {
+      Path64 result = new(count);
+      for (int i = 0; i < count; ++i)
+        result.Add(MakeRandomPt(width, height, rand));
+      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

+ 49 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/RectClipDemo.csproj

@@ -0,0 +1,49 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net5.0</TargetFramework>
+    <EnableNETAnalyzers>false</EnableNETAnalyzers>
+    <ApplicationIcon>Clipper2.ico</ApplicationIcon>
+    <Platforms>AnyCPU;x86</Platforms>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
+    <Optimize>false</Optimize>
+    <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
+    <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
+    <Optimize>false</Optimize>
+    <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
+    <ProjectReference Include="..\..\Utils\ClipFileIO\Clipper.FileIO.csproj" />
+    <ProjectReference Include="..\..\Utils\SVG\Clipper2.SVG.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Resource Include="Clipper2.ico" />
+    <Resource Include="Main.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Remove="*.svg" />
+    <None Remove="*.ico" />
+  </ItemGroup>
+
+</Project>

+ 42 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Examples/RectClip/RectClipDemo.sln

@@ -0,0 +1,42 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32126.315
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RectClipDemo", "RectClipDemo.csproj", "{1D87809D-586C-4202-967E-11EBC8E44746}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{82897A5C-39B4-4D45-AEF1-38653D6A07E9}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib", "..\..\Clipper2Lib\Clipper2Lib.csproj", "{91DACBAE-8661-4AF3-A13E-2F7D56F10348}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2.SVG", "..\..\Utils\SVG\Clipper2.SVG.csproj", "{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{1D87809D-586C-4202-967E-11EBC8E44746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1D87809D-586C-4202-967E-11EBC8E44746}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1D87809D-586C-4202-967E-11EBC8E44746}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1D87809D-586C-4202-967E-11EBC8E44746}.Release|Any CPU.Build.0 = Release|Any CPU
+		{91DACBAE-8661-4AF3-A13E-2F7D56F10348}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{91DACBAE-8661-4AF3-A13E-2F7D56F10348}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{91DACBAE-8661-4AF3-A13E-2F7D56F10348}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{91DACBAE-8661-4AF3-A13E-2F7D56F10348}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9CDE3E1A-58CE-4397-B7F3-9576C64469DB}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {278478F4-9ED6-4AAD-AA42-9F0BC22D605C}
+	EndGlobalSection
+EndGlobal

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

@@ -0,0 +1,47 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Clipper2Lib.UnitTests
+{
+
+  [TestClass]
+  public class TestLines
+  {
+
+    [TestMethod]
+    public void TestOpenPaths()
+    {
+      for (int i = 0; i <= 16; i++)
+      {
+        Clipper64 c64 = new();
+        Paths64 subj = new(), subj_open = new(), clip = new();
+        Paths64 solution = new(), solution_open = new();
+
+        Assert.IsTrue(ClipperFileIO.LoadTestNum("..\\..\\..\\..\\..\\..\\Tests\\Lines.txt",
+          i, subj, subj_open, clip, out ClipType clipType, out FillRule fillrule,  
+          out long area, out int count, out _),
+            string.Format("Loading test {0} failed.", i));
+
+        c64.AddSubject(subj);
+        c64.AddOpenSubject(subj_open);
+        c64.AddClip(clip);
+        c64.Execute(clipType, fillrule, solution, solution_open);
+
+        if (area > 0)
+        {
+          double area2 = Clipper.Area(solution);
+          double a = area2 / area2;
+          Assert.IsTrue(a > 0.995 && a < 1.005,
+            string.Format("Incorrect area in test {0}", i));
+        }
+
+        if (count > 0 && Math.Abs(solution.Count - count) > 0)
+        {
+          Assert.IsTrue(Math.Abs(solution.Count - count) < 2,
+            string.Format("Incorrect count in test {0}", i));
+        }
+
+      } //bottom of num loop
+
+    }
+  }
+}

+ 77 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolygons.cs

@@ -0,0 +1,77 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Clipper2Lib.UnitTests
+{
+
+  [TestClass]
+  public class TestPolygons
+  {
+
+    private static bool IsInList(int num, int[] list)
+    {
+      foreach (int i in list) if (i == num) return true;
+      return false;
+    }
+
+    [TestMethod]
+    public void TestClosedPaths()
+    {
+      int testNum = 0;
+      while (true)
+      {
+        testNum++;
+        Clipper64 c64 = new();
+        Paths64 subj = new(), subj_open = new (), clip = new();
+        Paths64 solution = new(), solution_open = new();
+
+        if (!ClipperFileIO.LoadTestNum("..\\..\\..\\..\\..\\..\\Tests\\Polygons.txt",
+          testNum, subj, subj_open, clip, out ClipType clipType, out FillRule fillrule, 
+          out long storedArea, out int storedCount, out _))
+        {          
+          Assert.IsTrue(testNum > 180, string.Format("Loading test polygon {0} failed.", testNum));
+          break;
+        }
+
+        c64.AddSubject(subj);
+        c64.AddOpenSubject(subj_open);
+        c64.AddClip(clip);
+        c64.Execute(clipType, fillrule, solution, solution_open);
+        int measuredCount = solution.Count;
+        long measuredArea = (long)Clipper.Area(solution);
+        int countDiff = storedCount > 0 ? Math.Abs(storedCount - measuredCount) : 0;
+        long areaDiff = storedArea > 0 ? Math.Abs(storedArea - measuredArea) : 0;
+
+        if (testNum == 23)
+        {
+          Assert.IsTrue(countDiff <= 4);
+        }
+        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)
+        {
+          Assert.IsTrue(areaDiff / storedArea <= 0.02);
+        }
+      } //bottom of num loop
+
+    }
+  }
+}

+ 136 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestPolytree.cs

@@ -0,0 +1,136 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Clipper2Lib.UnitTests
+{
+
+  [TestClass]
+  public class TestPolytree
+  {
+    private void PolyPathContainsPoint(PolyPath64 pp, Point64 pt, ref int counter)
+    {
+      if (Clipper.PointInPolygon(pt, pp.Polygon!) != PointInPolygonResult.IsOutside)
+      {
+        if (pp.IsHole) --counter; else ++counter;
+      }
+      for (int i = 0; i < pp.Count; i++)
+      {
+        PolyPath64 child = (PolyPath64) pp[i];
+        PolyPathContainsPoint(child, pt, ref counter);
+      } 
+    }
+
+    private bool PolytreeContainsPoint(PolyTree64 pp, Point64 pt)
+    {
+      int counter = 0;
+      for (int i = 0; i < pp.Count; i++)
+      {
+        PolyPath64 child = (PolyPath64) pp[i];
+        PolyPathContainsPoint(child, pt, ref counter);
+      }
+      Assert.IsTrue(counter >= 0, "Polytree has too many holes");
+      return counter != 0;
+    }
+
+  private bool PolyPathFullyContainsChildren(PolyPath64 pp)
+    {
+      foreach (PolyPath64 child in pp.Cast<PolyPath64>())
+      {
+        foreach (Point64 pt in child.Polygon!)
+          if (Clipper.PointInPolygon(pt, pp.Polygon!) == PointInPolygonResult.IsOutside)
+            return false;
+        if (child.Count > 0 && !PolyPathFullyContainsChildren(child))
+          return false;
+      }
+      return true;
+    }
+
+    private bool CheckPolytreeFullyContainsChildren(PolyTree64 polytree)
+    {
+      for (int i = 0; i < polytree.Count; i++)
+      {
+        PolyPath64 child = (PolyPath64) polytree[i];
+        if (child.Count > 0 && !PolyPathFullyContainsChildren(child))
+          return false;
+      }    
+      return true;
+    }
+
+  [TestMethod]
+    public void TestPolytree2()
+    {
+      Paths64 subject = new(), subjectOpen = new(), clip = new();
+
+      Assert.IsTrue(ClipperFileIO.LoadTestNum("..\\..\\..\\..\\..\\..\\Tests\\PolytreeHoleOwner2.txt",
+        1, subject, subjectOpen, clip, out ClipType cliptype, out FillRule fillrule, 
+        out _, out _, out _),
+          "Unable to read PolytreeHoleOwner2.txt");
+
+      PolyTree64 solutionTree = new();
+      Paths64 solution_open = new();
+      Clipper64 clipper = new();
+
+      Path64 pointsOfInterestOutside = new()
+      {
+        new Point64(21887, 10420),
+        new Point64(21726, 10825),
+        new Point64(21662, 10845),
+        new Point64(21617, 10890)
+      };
+
+      foreach (Point64 pt in pointsOfInterestOutside)
+      {
+        foreach (Path64 path in subject)
+        {
+          Assert.IsTrue(Clipper.PointInPolygon(pt, path) == PointInPolygonResult.IsOutside, 
+            "outside point of interest found inside subject");
+        }
+      }
+
+      Path64 pointsOfInterestInside = new()
+      {
+        new Point64(21887, 10430),
+        new Point64(21843, 10520),
+        new Point64(21810, 10686),
+        new Point64(21900, 10461)
+      };
+
+      foreach (Point64 pt in pointsOfInterestInside)
+      {
+        int poi_inside_counter = 0;
+        foreach (Path64 path in subject)
+        {
+          if (Clipper.PointInPolygon(pt, path) == PointInPolygonResult.IsInside)
+            poi_inside_counter++;
+        }
+        Assert.IsTrue(poi_inside_counter == 1,
+          string.Format("poi_inside_counter - expected 1 but got {0}", poi_inside_counter));
+      }
+
+      clipper.AddSubject(subject);
+      clipper.AddOpenSubject(subjectOpen);
+      clipper.AddClip(clip);
+      clipper.Execute(cliptype, fillrule, solutionTree, solution_open);
+
+      Paths64 solutionPaths = Clipper.PolyTreeToPaths64(solutionTree);
+      double a1 = Clipper.Area(solutionPaths), a2 = solutionTree.Area();
+
+      Assert.IsTrue(a1 > 330000, 
+        string.Format("solution has wrong area - value expected: 331,052; value returned; {0} ", a1));
+
+      Assert.IsTrue(Math.Abs(a1 - a2) < 0.0001,
+        string.Format("solution tree has wrong area - value expected: {0}; value returned; {1} ", a1, a2));
+
+      Assert.IsTrue(CheckPolytreeFullyContainsChildren(solutionTree),
+        "The polytree doesn't properly contain its children");
+
+      foreach (Point64 pt in pointsOfInterestOutside)
+        Assert.IsFalse(PolytreeContainsPoint(solutionTree, pt),
+          "The polytree indicates it contains a point that it should not contain");
+
+      foreach (Point64 pt in pointsOfInterestInside)
+        Assert.IsTrue(PolytreeContainsPoint(solutionTree, pt),
+          "The polytree indicates it does not contain a point that it should contain");
+
+    }
+  }
+}

+ 31 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests1.csproj

@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <DefineConstants>$(DefineConstants)TRACE;</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>$(DefineConstants)TRACE;</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
+    <PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
+    <PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
+    <PackageReference Include="coverlet.collector" Version="3.0.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
+    <ProjectReference Include="..\..\Utils\ClipFileIO\Clipper.FileIO.csproj" />
+  </ItemGroup>
+  
+</Project>

+ 41 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests1/Tests1.sln

@@ -0,0 +1,41 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32804.467
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests1", "Tests1.csproj", "{C4139DC2-C778-4AA6-9643-0DDCECFDB366}"
+	ProjectSection(ProjectDependencies) = postProject
+		{8C60EC75-BCEA-453D-8E19-2BBF571A1DC4} = {8C60EC75-BCEA-453D-8E19-2BBF571A1DC4}
+		{9671D8F5-CD0F-4597-9B9B-08FBD9BC33F6} = {9671D8F5-CD0F-4597-9B9B-08FBD9BC33F6}
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib", "..\..\Clipper2Lib\Clipper2Lib.csproj", "{9671D8F5-CD0F-4597-9B9B-08FBD9BC33F6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper.FileIO", "..\..\Utils\ClipFileIO\Clipper.FileIO.csproj", "{8C60EC75-BCEA-453D-8E19-2BBF571A1DC4}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{C4139DC2-C778-4AA6-9643-0DDCECFDB366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C4139DC2-C778-4AA6-9643-0DDCECFDB366}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C4139DC2-C778-4AA6-9643-0DDCECFDB366}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C4139DC2-C778-4AA6-9643-0DDCECFDB366}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9671D8F5-CD0F-4597-9B9B-08FBD9BC33F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9671D8F5-CD0F-4597-9B9B-08FBD9BC33F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9671D8F5-CD0F-4597-9B9B-08FBD9BC33F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9671D8F5-CD0F-4597-9B9B-08FBD9BC33F6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8C60EC75-BCEA-453D-8E19-2BBF571A1DC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8C60EC75-BCEA-453D-8E19-2BBF571A1DC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8C60EC75-BCEA-453D-8E19-2BBF571A1DC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8C60EC75-BCEA-453D-8E19-2BBF571A1DC4}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {CACF3BB2-543F-425E-A9C6-85FDF75DD508}
+	EndGlobalSection
+EndGlobal

+ 158 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests2/Tests/TestZCallback1.cs

@@ -0,0 +1,158 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+// USINGZ compiler directive should have been set in project properties
+
+namespace Clipper2Lib.UnitTests
+{
+
+  [TestClass]
+  public class TestingZ_1
+  {
+
+    public class ClipperTest
+    {
+      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;
+      }
+    }
+
+    [TestMethod]
+    public void TestSubjUnion64()
+    {
+      Paths64 solution = new Paths64();
+      Paths64 subject = new Paths64();
+      subject.Add(Clipper.MakePath(new int[] { 100, 50, 10, 79, 65, 2, 65, 98, 10, 21 }));
+
+      Clipper64 c64 = new Clipper64();
+      ClipperTest ct = new ClipperTest();
+
+      c64.ZCallback = ct.MyCallback64;
+      c64.AddSubject(subject);
+      c64.Execute(ClipType.Union, FillRule.NonZero, solution);
+
+      //check that the Z for every second vertex == 1
+      Assert.IsTrue(solution.Count == 1 && solution[0].Count == 10);
+      for (int i = 0; i < solution[0].Count; i++)
+      {
+        if ((i & 1) == 1)
+          Assert.IsTrue(solution[0][i].Z == 0);
+        else
+          Assert.IsTrue(solution[0][i].Z == 1);
+      }
+    }
+
+    [TestMethod]
+    public void TestSubjClipUnion64()
+    {
+      Paths64 solution = new Paths64();
+      Paths64 subject = new Paths64();
+      Paths64 clip = new Paths64();
+      // union two triangles
+      subject.Add(Clipper.MakePath(new int[] { 10, 30, 80, 30, 45, 90 }));
+      clip.Add(Clipper.MakePath(new int[] { 10, 70, 80, 70, 45, 10 }));
+
+      Clipper64 c64 = new Clipper64();
+      ClipperTest ct = new ClipperTest();
+
+      c64.ZCallback = ct.MyCallback64;
+      c64.AddSubject(subject);
+      c64.AddClip(clip);
+      c64.Execute(ClipType.Union, FillRule.NonZero, solution);
+
+      //check that the Z for every second vertex == 1
+      Assert.IsTrue(solution.Count == 1 && solution[0].Count == 12);
+      for (int i = 0; i < solution[0].Count; i++)
+      {
+        if ((i & 1) == 1)
+          Assert.IsTrue(solution[0][i].Z == 0);
+        else
+          Assert.IsTrue(solution[0][i].Z == 1);
+      }
+    }
+
+    [TestMethod]
+    public void TestSubjUnionD()
+    {
+      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 cD = new ClipperD();
+      ClipperTest ct = new ClipperTest();
+
+      cD.ZCallback = ct.MyCallbackD;
+      cD.AddSubject(subject);
+      cD.Execute(ClipType.Union, FillRule.NonZero, solution);
+
+      //check that the Z for every second vertex == 1
+      Assert.IsTrue(solution.Count == 1 && solution[0].Count == 10);
+      for (int i = 0; i < solution[0].Count; i++)
+      {
+        if ((i & 1) == 1)
+          Assert.IsTrue(solution[0][i].z == 0);
+        else
+          Assert.IsTrue(solution[0][i].z == 1);
+      }
+    }
+
+    [TestMethod]
+    public void TestSubjClipUnionD()
+    {
+      PathsD solution = new PathsD();
+      PathsD subject = new PathsD();
+      PathsD clip = new PathsD();
+      // union two triangles
+      subject.Add(Clipper.MakePath(new double[] { 10, 30, 80, 30, 45, 90 }));
+      clip.Add(Clipper.MakePath(new double[] { 10, 70, 80, 70, 45, 10 }));
+
+      ClipperD cD = new ClipperD();
+      ClipperTest ct = new ClipperTest();
+
+      cD.ZCallback = ct.MyCallbackD;
+      cD.AddSubject(subject);
+      cD.AddClip(clip);
+      cD.Execute(ClipType.Union, FillRule.NonZero, solution);
+
+      //check that the Z for every second vertex == 1
+      Assert.IsTrue(solution.Count == 1 && solution[0].Count == 12);
+      for (int i = 0; i < solution[0].Count; i++)
+      {
+        if ((i & 1) == 1)
+          Assert.IsTrue(solution[0][i].z == 0);
+        else
+          Assert.IsTrue(solution[0][i].z == 1);
+      }
+    }
+
+    [TestMethod]
+    public void TestMysteryD()
+    {
+      ClipperD c = new ClipperD(5);
+      var bitePoly = new PathD()
+      {
+        new PointD(5, 5, 5), new PointD(10, 5, 5), new PointD(10, 10, 5), new PointD(5, 10, 5)
+      };
+
+      var surfacePoly = new PathD()
+      {
+        new PointD(0, 0, 5), new PointD(15, 0, 5), new PointD(15, 15, 5), new PointD(0, 15, 5)
+      };
+
+      c.AddSubject(surfacePoly);
+      c.AddClip(bitePoly);
+      PathsD solution = new PathsD();
+
+      c.Execute(ClipType.Difference, FillRule.EvenOdd, solution);
+      Console.WriteLine(Clipper.PathsDToString(solution));
+    }
+
+  }
+}

+ 30 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests2/TestsZ.csproj

@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <DefineConstants>$(DefineConstants)TRACE;USINGZ;</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>$(DefineConstants)TRACE;USINGZ;</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
+    <PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
+    <PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
+    <PackageReference Include="coverlet.collector" Version="3.1.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\USINGZ\Clipper2LibZ.csproj" />
+  </ItemGroup>
+
+</Project>

+ 31 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.Tests/Tests2/TestsZ.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32804.467
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestsZ", "TestsZ.csproj", "{BA651459-DAE8-4551-9E66-5F47C9BCDA4D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2LibZ", "..\..\USINGZ\Clipper2LibZ.csproj", "{45C3F035-9ABC-4CB0-B178-39DE365271F2}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{BA651459-DAE8-4551-9E66-5F47C9BCDA4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BA651459-DAE8-4551-9E66-5F47C9BCDA4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BA651459-DAE8-4551-9E66-5F47C9BCDA4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BA651459-DAE8-4551-9E66-5F47C9BCDA4D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{45C3F035-9ABC-4CB0-B178-39DE365271F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{45C3F035-9ABC-4CB0-B178-39DE365271F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{45C3F035-9ABC-4CB0-B178-39DE365271F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{45C3F035-9ABC-4CB0-B178-39DE365271F2}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {AA98C6DD-16D2-4C58-8A41-F215CB93159E}
+	EndGlobalSection
+EndGlobal

+ 60 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib.sln

@@ -0,0 +1,60 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32630.192
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib", "Clipper2Lib\Clipper2Lib.csproj", "{4CE00B81-F1B2-4012-9BBD-B0DB6BF8CAD3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib.Benchmark", "Clipper2Lib.Benchmark\Clipper2Lib.Benchmark.csproj", "{F8A168C6-286A-4ECF-8750-C505ADBCB7D5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3B52CBF2-1267-44C7-B447-C51ACDD25371}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2.SVG", "Utils\SVG\Clipper2.SVG.csproj", "{BC8E75BA-24E1-4D6A-AE54-0B1019C18EFE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper.FileIO", "Utils\ClipFileIO\Clipper.FileIO.csproj", "{2EF8FB02-E3F6-46CE-954F-5E1B0AD64DB7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleDemo", "Clipper2Lib.Examples\ConsoleDemo\ConsoleDemo.csproj", "{DBFB59C1-C08D-48DC-9A15-CD18B5897BE4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InflateDemo", "Clipper2Lib.Examples\InflateDemo\InflateDemo.csproj", "{DB7B56ED-474F-49A7-91F1-75EEB76A8B2B}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{4CE00B81-F1B2-4012-9BBD-B0DB6BF8CAD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4CE00B81-F1B2-4012-9BBD-B0DB6BF8CAD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4CE00B81-F1B2-4012-9BBD-B0DB6BF8CAD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4CE00B81-F1B2-4012-9BBD-B0DB6BF8CAD3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F8A168C6-286A-4ECF-8750-C505ADBCB7D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F8A168C6-286A-4ECF-8750-C505ADBCB7D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F8A168C6-286A-4ECF-8750-C505ADBCB7D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F8A168C6-286A-4ECF-8750-C505ADBCB7D5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BC8E75BA-24E1-4D6A-AE54-0B1019C18EFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BC8E75BA-24E1-4D6A-AE54-0B1019C18EFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BC8E75BA-24E1-4D6A-AE54-0B1019C18EFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BC8E75BA-24E1-4D6A-AE54-0B1019C18EFE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2EF8FB02-E3F6-46CE-954F-5E1B0AD64DB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2EF8FB02-E3F6-46CE-954F-5E1B0AD64DB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2EF8FB02-E3F6-46CE-954F-5E1B0AD64DB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2EF8FB02-E3F6-46CE-954F-5E1B0AD64DB7}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DBFB59C1-C08D-48DC-9A15-CD18B5897BE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DBFB59C1-C08D-48DC-9A15-CD18B5897BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DBFB59C1-C08D-48DC-9A15-CD18B5897BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DBFB59C1-C08D-48DC-9A15-CD18B5897BE4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DB7B56ED-474F-49A7-91F1-75EEB76A8B2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DB7B56ED-474F-49A7-91F1-75EEB76A8B2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DB7B56ED-474F-49A7-91F1-75EEB76A8B2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DB7B56ED-474F-49A7-91F1-75EEB76A8B2B}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {EB14E46A-0760-411E-B991-4E0B4DBBFF93}
+	EndGlobalSection
+EndGlobal

+ 735 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Core.cs

@@ -0,0 +1,735 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  19 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Core structures and functions for the Clipper Library           *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+using System.Threading;
+
+namespace Clipper2Lib
+{
+  public struct Point64
+  {
+    public long X;
+    public long Y;
+
+#if USINGZ
+    public long Z;
+
+    public Point64(Point64 pt)
+    {
+      X = pt.X;
+      Y = pt.Y;
+      Z = pt.Z;
+    }
+
+    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);
+    }
+    
+    public Point64(long x, long y, long z = 0)
+    {
+      X = x;
+      Y = y;
+      Z = z;
+    }
+
+    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);
+    }
+
+    public Point64(PointD pt)
+    {
+      X = (long) Math.Round(pt.x);
+      Y = (long) Math.Round(pt.y);
+      Z = pt.z;
+    }
+
+    public Point64(PointD pt, double scale)
+    {
+      X = (long) Math.Round(pt.x * scale);
+      Y = (long) Math.Round(pt.y * scale);
+      Z = pt.z;
+    }
+
+    public static bool operator ==(Point64 lhs, Point64 rhs)
+    {
+      return lhs.X == rhs.X && lhs.Y == rhs.Y;
+    }
+
+    public static bool operator !=(Point64 lhs, Point64 rhs)
+    {
+      return lhs.X != rhs.X || lhs.Y != rhs.Y;
+    }
+
+    public static Point64 operator +(Point64 lhs, Point64 rhs)
+    {
+      return new Point64(lhs.X + rhs.X, lhs.Y + rhs.Y, lhs.Z + rhs.Z);
+    }
+
+    public static Point64 operator -(Point64 lhs, Point64 rhs)
+    {
+      return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y, lhs.Z - rhs.Z);
+    }
+
+    public override string ToString()
+    {
+      return $"{X},{Y},{Z} "; // nb: trailing space
+    }
+
+#else
+    public Point64(Point64 pt)
+    {
+      X = pt.X;
+      Y = pt.Y;
+    }
+
+    public Point64(long x, long y)
+    {
+      X = x;
+      Y = y;
+    }
+
+    public Point64(double x, double y)
+    {
+      X = (long) Math.Round(x);
+      Y = (long) Math.Round(y);
+    }
+
+    public Point64(PointD pt)
+    {
+      X = (long) Math.Round(pt.x);
+      Y = (long) Math.Round(pt.y);
+    }
+
+    public Point64(Point64 pt, double scale)
+    {
+      X = (long) Math.Round(pt.X * scale);
+      Y = (long) Math.Round(pt.Y * scale);
+    }
+
+    public Point64(PointD pt, double scale)
+    {
+      X = (long) Math.Round(pt.x * scale);
+      Y = (long) Math.Round(pt.y * scale);
+    }
+
+    public static bool operator ==(Point64 lhs, Point64 rhs)
+    {
+      return lhs.X == rhs.X && lhs.Y == rhs.Y;
+    }
+
+    public static bool operator !=(Point64 lhs, Point64 rhs)
+    {
+      return lhs.X != rhs.X || lhs.Y != rhs.Y;
+    }
+
+    public static Point64 operator +(Point64 lhs, Point64 rhs)
+    {
+      return new Point64(lhs.X + rhs.X, lhs.Y + rhs.Y);
+    }
+
+    public static Point64 operator -(Point64 lhs, Point64 rhs)
+    {
+      return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y);
+    }
+    public override string ToString()
+    {
+      return $"{X},{Y} "; // nb: trailing space
+    }
+
+#endif
+    public override bool Equals(object? obj)
+    {
+      if (obj != null && obj is Point64 p)
+        return this == p;
+      return false;
+    }
+
+    public override int GetHashCode() { return 0; }
+  }
+
+  public struct PointD
+  {
+    public double x;
+    public double y;
+
+#if USINGZ
+    public long z;
+
+    public PointD(PointD pt)
+    {
+      x = pt.x;
+      y = pt.y;
+      z = pt.z;
+    }
+
+    public PointD(Point64 pt)
+    {
+      x = pt.X;
+      y = pt.Y;
+      z = pt.Z;
+    }
+
+    public PointD(Point64 pt, double scale)
+    {
+      x = pt.X * scale;
+      y = pt.Y * scale;
+      z = pt.Z;
+    }
+
+    public PointD(PointD pt, double scale)
+    {
+      x = pt.x * scale;
+      y = pt.y * scale;
+      z = pt.z;
+    }
+
+    public PointD(long x, long y, long z = 0)
+    {
+      this.x = x;
+      this.y = y;
+      this.z = z;
+    }
+
+    public PointD(double x, double y, long z = 0)
+    {
+      this.x = x;
+      this.y = y;
+      this.z = z;
+    }
+
+    public override string ToString()
+    {
+      return $"{x:F},{y:F},{z} ";
+    }
+
+#else
+    public PointD(PointD pt)
+    {
+      x = pt.x;
+      y = pt.y;
+    }
+
+    public PointD(Point64 pt)
+    {
+      x = pt.X;
+      y = pt.Y;
+    }
+
+    public PointD(PointD pt, double scale)
+    {
+      x = pt.x * scale;
+      y = pt.y * scale;
+    }
+
+    public PointD(Point64 pt, double scale)
+    {
+      x = pt.X * scale;
+      y = pt.Y * scale;
+    }
+
+    public PointD(long x, long y)
+    {
+      this.x = x;
+      this.y = y;
+    }
+
+    public PointD(double x, double y)
+    {
+      this.x = x;
+      this.y = y;
+    }
+
+    public override string ToString()
+    {
+      return $"{x:F},{y:F} ";
+    }
+
+#endif
+    public static bool operator ==(PointD lhs, PointD rhs)
+    {
+      return InternalClipper.IsAlmostZero(lhs.x - rhs.x) && 
+        InternalClipper.IsAlmostZero(lhs.y - rhs.y);
+    }
+
+    public static bool operator !=(PointD lhs, PointD rhs)
+    {
+      return !InternalClipper.IsAlmostZero(lhs.x - rhs.x) || 
+        !InternalClipper.IsAlmostZero(lhs.y - rhs.y);
+    }
+
+    public override bool Equals(object? obj)
+    {
+      if (obj != null && obj is PointD p)
+        return this == p;
+      return false;
+    }
+
+    public void Negate() { x = -x; y = -y; }
+
+    public override int GetHashCode() { return 0; }
+  }
+
+  public struct Rect64
+  {
+    public long left;
+    public long top;
+    public long right;
+    public long bottom;
+
+    public Rect64(long l, long t, long r, long b)
+    {
+      left = l;
+      top = t;
+      right = r;
+      bottom = b;
+    }
+
+    public Rect64(Rect64 rec)
+    {
+      left = rec.left;
+      top = rec.top;
+      right = rec.right;
+      bottom = rec.bottom;
+    }
+
+    public long Width
+    {
+      get => right - left;
+      set => right = left + value;
+    }
+
+    public long Height
+    {
+      get => bottom - top;
+      set => bottom = top + value;
+    }
+
+    public bool IsEmpty()
+    {
+      return bottom <= top || right <= left;
+    }
+
+    public Point64 MidPoint()
+    {
+      return new Point64((left + right) /2, (top + bottom)/2);
+    }
+
+    public bool Contains(Point64 pt)
+    {
+      return pt.X > left && pt.X < right &&
+        pt.Y > top && pt.Y < bottom;
+    }
+
+    public bool Contains(Rect64 rec)
+    {
+      return rec.left >= left && rec.right <= right &&
+        rec.top >= top && rec.bottom <= bottom;
+    }
+
+    public 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));
+    }
+
+    public Path64 AsPath()
+    {
+      Path64 result = new Path64(4)
+      {
+        new Point64(left, top),
+        new Point64(right, top),
+        new Point64(right, bottom),
+        new Point64(left, bottom)
+      };
+      return result;
+    }
+
+  }
+
+  public struct RectD
+  {
+    public double left;
+    public double top;
+    public double right;
+    public double bottom;
+
+    public RectD(double l, double t, double r, double b)
+    {
+      left = l;
+      top = t;
+      right = r;
+      bottom = b;
+    }
+
+    public RectD(RectD rec)
+    {
+      left = rec.left;
+      top = rec.top;
+      right = rec.right;
+      bottom = rec.bottom;
+    }
+
+    public double Width
+    {
+      get => right - left;
+      set => right = left + value;
+    }
+
+    public double Height
+    {
+      get => bottom - top;
+      set => bottom = top + value;
+    }
+
+    public bool IsEmpty()
+    {
+      return bottom <= top || right <= left;
+    }
+
+    public PointD MidPoint()
+    {
+      return new PointD((left + right) / 2, (top + bottom) / 2);
+    }
+
+    public bool Contains(PointD pt)
+    {
+      return pt.x > left && pt.x < right &&
+        pt.y > top && pt.y < bottom;
+    }
+
+    public bool Contains(RectD rec)
+    {
+      return rec.left >= left && rec.right <= right &&
+        rec.top >= top && rec.bottom <= bottom;
+    }
+
+    public 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()
+    {
+      PathD result = new PathD(4)
+      {
+        new PointD(left, top),
+        new PointD(right, top),
+        new PointD(right, bottom),
+        new PointD(left, bottom)
+      };
+      return result;
+    }
+
+  }
+
+  public class Path64 : List<Point64> 
+  {
+    private Path64() : base() { }
+    public Path64(int capacity = 0) : base(capacity) { }
+    public Path64(IEnumerable<Point64> path) : base(path) { }
+    public override string ToString()
+    {
+      string s = "";
+      foreach (Point64 p in this)
+        s = s + p.ToString() + " ";
+      return s;
+    }
+  }
+  public class Paths64 : List<Path64>
+  {
+    private Paths64() : base() { }
+    public Paths64(int capacity = 0) : base(capacity) { }
+    public Paths64(IEnumerable<Path64> paths) : base(paths) { }
+    public override string ToString()
+    {
+      string s = "";
+      foreach (Path64 p in this)
+        s = s + p.ToString() + "\n";
+      return s;
+    }
+  }
+
+  public class PathD : List<PointD>
+  {
+    private PathD() : base() { }
+    public PathD(int capacity = 0) : base(capacity) { }
+    public PathD(IEnumerable<PointD> path) : base(path) { }
+    public override string ToString()
+    {
+      string s = "";
+      foreach (PointD p in this)
+        s = s + p.ToString() + " ";
+      return s;
+    }
+  }
+
+  public class PathsD : List<PathD>
+  {
+    private PathsD() : base() { }
+    public PathsD(int capacity = 0) : base(capacity) { }
+    public PathsD(IEnumerable<PathD> paths) : base(paths) { }
+    public override string ToString()
+    {
+      string s = "";
+      foreach (PathD p in this)
+        s = s + p.ToString() + "\n";
+      return s;
+    }
+  }
+
+  // Note: all clipping operations except for Difference are commutative.
+  public enum ClipType
+  {
+    None,
+    Intersection,
+    Union,
+    Difference,
+    Xor
+  };
+
+  public enum PathType
+  {
+    Subject,
+    Clip
+  };
+
+  // By far the most widely used filling rules for polygons are EvenOdd
+  // and NonZero, sometimes called Alternate and Winding respectively.
+  // https://en.wikipedia.org/wiki/Nonzero-rule
+  public enum FillRule
+  {
+    EvenOdd,
+    NonZero,
+    Positive,
+    Negative
+  };
+
+  // PointInPolygon
+  internal enum PipResult
+  {
+    Inside,
+    Outside,
+    OnEdge
+  };
+
+  public static class InternalClipper
+  {
+    internal const long MaxInt64 = 9223372036854775807;
+    internal const long MaxCoord = MaxInt64 / 4;
+    internal const double max_coord = MaxCoord;
+    internal const double min_coord = -MaxCoord;
+    internal const long Invalid64 = MaxInt64;
+
+    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.";
+
+    internal static void CheckPrecision(int precision)
+    {
+      if (precision < -8 || precision > 8)
+        throw new Exception(precision_range_error);
+    }
+
+    internal static bool IsAlmostZero(double value)
+    {
+      return (Math.Abs(value) <= floatingPointTolerance);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static double CrossProduct(Point64 pt1, Point64 pt2, Point64 pt3)
+    {
+      // typecast to double to avoid potential int overflow
+      return ((double) (pt2.X - pt1.X) * (pt3.Y - pt2.Y) -
+              (double) (pt2.Y - pt1.Y) * (pt3.X - pt2.X));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static double DotProduct(Point64 pt1, Point64 pt2, Point64 pt3)
+    {
+      // typecast to double to avoid potential int overflow
+      return ((double) (pt2.X - pt1.X) * (pt3.X - pt2.X) +
+              (double) (pt2.Y - pt1.Y) * (pt3.Y - pt2.Y));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static double CrossProduct(PointD vec1, PointD vec2)
+    {
+      return (vec1.y * vec2.x - vec2.y * vec1.x);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static double DotProduct(PointD vec1, PointD vec2)
+    {
+      return (vec1.x * vec2.x + vec1.y * vec2.y);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static long CheckCastInt64(double val)
+    {
+      if ((val >= max_coord) || (val <= min_coord)) return Invalid64;
+      return (long)Math.Round(val);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static bool GetIntersectPt(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)
+      {
+        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;
+      }
+      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)
+    {
+      if (inclusive)
+      {
+        double res1 = CrossProduct(seg1a, seg2a, seg2b);
+        double res2 = CrossProduct(seg1b, seg2a, seg2b);
+        if (res1 * res2 > 0) return false;
+        double res3 = CrossProduct(seg2a, seg1a, seg1b);
+        double res4 = CrossProduct(seg2b, seg1a, seg1b);
+        if (res3 * res4 > 0) return false;
+        // ensure NOT collinear
+        return (res1 != 0 || res2 != 0 || res3 != 0 || res4 != 0);
+      }
+      else
+      {
+        return (CrossProduct(seg1a, seg2a, seg2b) * 
+          CrossProduct(seg1b, seg2a, seg2b) < 0) &&
+          (CrossProduct(seg2a, seg1a, seg1b) * 
+          CrossProduct(seg2b, seg1a, seg1b) < 0);
+      }
+    }
+    public static Point64 GetClosestPtOnSegment(Point64 offPt,
+    Point64 seg1, Point64 seg2)
+    {
+      if (seg1.X == seg2.X && seg1.Y == seg2.Y) return seg1;
+      double dx = (seg2.X - seg1.X);
+      double dy = (seg2.Y - seg1.Y);
+      double q = ((offPt.X - seg1.X) * dx +
+        (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));
+    }
+
+    public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon)
+    {
+      int len = polygon.Count, i = len - 1;
+
+      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 (i < len)
+      {
+        if (isAbove)
+        {
+          while (i < len && polygon[i].Y < pt.Y) i++;
+          if (i == len) break;
+        }
+        else
+        {
+          while (i < len && polygon[i].Y > pt.Y) i++;
+          if (i == len) break;
+        }
+
+        Point64 prev;
+
+        Point64 curr = polygon[i];
+        if (i > 0) prev = polygon[i - 1];
+        else prev = polygon[len - 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;
+          i++;
+          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) == isAbove) val = 1 - val;
+        }
+        isAbove = !isAbove;
+        i++;
+      }
+      if (val == 0)
+        return PointInPolygonResult.IsOutside;
+      return PointInPolygonResult.IsInside;
+    }
+
+  } // InternalClipper
+
+} // namespace

+ 3999 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Engine.cs

@@ -0,0 +1,3999 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  19 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  This is the main polygon clipping module                        *
+* Thanks    :  Special thanks to Thong Nguyen, Guus Kuiper, Phil Stopford,     *
+*           :  and Daniel Gosnell for their invaluable assistance with C#.     *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#nullable enable
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+
+namespace Clipper2Lib
+{
+
+  // Vertex: a pre-clipping data structure. It is used to separate polygons
+  // into ascending and descending 'bounds' (or sides) that start at local
+  // minima and ascend to a local maxima, before descending again.
+  [Flags]
+
+  public enum PointInPolygonResult
+  {
+    IsOn = 0,
+    IsInside = 1,
+    IsOutside = 2
+  };
+
+  [Flags]
+  internal enum VertexFlags
+  {
+    None = 0,
+    OpenStart = 1,
+    OpenEnd = 2,
+    LocalMax = 4,
+    LocalMin = 8
+  };
+
+  internal class Vertex
+  {
+    public readonly Point64 pt;
+    public Vertex? next;
+    public Vertex? prev;
+    public VertexFlags flags;
+
+    public Vertex(Point64 pt, VertexFlags flags, Vertex? prev)
+    {
+      this.pt = pt;
+      this.flags = flags;
+      next = null;
+      this.prev = prev;
+    }
+  };
+
+  internal readonly struct LocalMinima
+  {
+    public readonly Vertex vertex;
+    public readonly PathType polytype;
+    public readonly bool isOpen;
+
+    public LocalMinima(Vertex vertex, PathType polytype, bool isOpen = false)
+    {
+      this.vertex = vertex;
+      this.polytype = polytype;
+      this.isOpen = isOpen;
+    }
+
+    public static bool operator ==(LocalMinima lm1, LocalMinima lm2)
+    {
+      return ReferenceEquals(lm1.vertex, lm2.vertex);
+    }
+
+    public static bool operator !=(LocalMinima lm1, LocalMinima lm2)
+    {
+      return !(lm1 == lm2);
+    }
+
+    public override bool Equals(object? obj)
+    {
+      return obj is LocalMinima minima && this == minima;
+    }
+
+    public override int GetHashCode()
+    {
+      return vertex.GetHashCode();
+    }
+
+  };
+
+  // IntersectNode: a structure representing 2 intersecting edges.
+  // Intersections must be sorted so they are processed from the largest
+  // Y coordinates to the smallest while keeping edges adjacent.
+  internal struct IntersectNode
+  {
+    public readonly Point64 pt;
+    public readonly Active edge1;
+    public readonly Active edge2;
+
+    public IntersectNode(Point64 pt, Active edge1, Active edge2)
+    {
+      this.pt = pt;
+      this.edge1 = edge1;
+      this.edge2 = edge2;
+    }
+  };
+
+  internal struct LocMinSorter : IComparer<LocalMinima>
+  {
+    public int Compare(LocalMinima locMin1, LocalMinima locMin2)
+    {
+      return locMin2.vertex.pt.Y.CompareTo(locMin1.vertex.pt.Y);
+    }
+  }
+
+  // OutPt: vertex data structure for clipping solutions
+  internal class OutPt
+  {
+    public Point64 pt;
+    public OutPt? next;
+    public OutPt prev;
+    public OutRec outrec;
+    public Joiner? joiner;
+
+    public OutPt(Point64 pt, OutRec outrec)
+    {
+      this.pt = pt;
+      this.outrec = outrec;
+      next = this;
+      prev = this;
+      joiner = null;
+    }
+  };
+
+  // OutRec: path data structure for clipping solutions
+  internal class OutRec
+  {
+    public int idx;
+    public OutRec? owner;
+    public List<OutRec>? splits;
+    public Active? frontEdge;
+    public Active? backEdge;
+    public OutPt? pts;
+    public PolyPathNode? polypath;
+    public Rect64 bounds;
+    public Path64 path;
+    public bool isOpen;
+    public OutRec() 
+    { 
+      bounds = new Rect64(); 
+      path = new Path64(); 
+    }
+  };
+
+  // Joiner: structure used in merging "touching" solution polygons
+  internal class Joiner
+  {
+    public int idx;
+    public OutPt op1;
+    public OutPt? op2;
+    public Joiner? next1;
+    public Joiner? next2;
+    public Joiner? nextH;
+
+    public Joiner(OutPt op1, OutPt? op2, Joiner? nextH)
+    {
+      idx = -1;
+      this.nextH = nextH;
+      this.op1 = op1;
+      this.op2 = op2;
+      next1 = op1.joiner;
+      op1.joiner = this;
+
+      if (op2 != null)
+      {
+        next2 = op2.joiner;
+        op2.joiner = this;
+      }
+      else
+        next2 = null;
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // Important: UP and DOWN here are premised on Y-axis positive down
+  // displays, which is the orientation used in Clipper's development.
+  ///////////////////////////////////////////////////////////////////
+  
+  internal class Active
+  {
+    public Point64 bot;
+    public Point64 top;
+    public long curX; // current (updated at every new scanline)
+    public double dx;
+    public int windDx; // 1 or -1 depending on winding direction
+    public int windCount;
+    public int windCount2; // winding count of the opposite polytype
+    public OutRec? outrec;
+
+    // AEL: 'active edge list' (Vatti's AET - active edge table)
+    //     a linked list of all edges (from left to right) that are present
+    //     (or 'active') within the current scanbeam (a horizontal 'beam' that
+    //     sweeps from bottom to top over the paths in the clipping operation).
+    public Active? prevInAEL;
+    public Active? nextInAEL;
+
+    // SEL: 'sorted edge list' (Vatti's ST - sorted table)
+    //     linked list used when sorting edges into their new positions at the
+    //     top of scanbeams, but also (re)used to process horizontals.
+    public Active? prevInSEL;
+    public Active? nextInSEL;
+    public Active? jump;
+    public Vertex? vertexTop;
+    public LocalMinima localMin; // the bottom of an edge 'bound' (also Vatti)
+    internal bool isLeftBound;
+  };
+
+  public class ClipperBase
+  {
+    private ClipType _cliptype;
+    private FillRule _fillrule;
+    private Active? _actives;
+    private Active? _sel;
+    private Joiner? _horzJoiners;
+    private readonly List<LocalMinima> _minimaList;
+    private readonly List<IntersectNode> _intersectList;
+    private readonly List<Vertex> _vertexList;
+    private readonly List<OutRec> _outrecList;
+    private readonly List<Joiner?> _joinerList;
+    private readonly List<long> _scanlineList;
+    private int _currentLocMin;
+    private long _currentBotY;
+    private bool _isSortedMinimaList;
+    private bool _hasOpenPaths;
+    internal bool _using_polytree;
+    internal bool _succeeded;
+    public bool PreserveCollinear { get; set; }
+    public bool ReverseSolution { get; set; }
+
+#if USINGZ
+    public delegate void ZCallback64(Point64 bot1, Point64 top1,
+        Point64 bot2, Point64 top2, ref Point64 intersectPt);
+
+    public long DefaultZ { get; set; }
+    protected ZCallback64? _zCallback;
+#endif
+    public ClipperBase()
+    {
+      _minimaList = new List<LocalMinima>();
+      _intersectList = new List<IntersectNode>();
+      _vertexList = new List<Vertex>();
+      _outrecList = new List<OutRec>();
+      _joinerList = new List<Joiner?>();
+      _scanlineList = new List<long>();
+      PreserveCollinear = true;
+    }
+
+#if USINGZ
+    private bool XYCoordsEqual(Point64 pt1, Point64 pt2)
+    {
+      return (pt1.X == pt2.X && pt1.Y == pt2.Y);
+    }
+    
+    private void SetZ(Active e1, Active e2, ref Point64 intersectPt)
+    {
+      if (_zCallback == null) return;
+
+      // prioritize subject vertices over clip vertices
+      // and pass the subject vertices before clip vertices in the callback
+      if (GetPolyType(e1) == PathType.Subject)
+      {
+        if (XYCoordsEqual(intersectPt, e1.bot))
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, e1.bot.Z);
+        else if (XYCoordsEqual(intersectPt, e1.top))
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, e1.top.Z);
+        else if (XYCoordsEqual(intersectPt, e2.bot))
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, e2.bot.Z);
+        else if (XYCoordsEqual(intersectPt, e2.top))
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, e2.top.Z);
+        else
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, DefaultZ);
+        _zCallback(e1.bot, e1.top, e2.bot, e2.top, ref intersectPt);
+      }
+      else
+      {
+        if (XYCoordsEqual(intersectPt, e2.bot))
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, e2.bot.Z);
+        else if (XYCoordsEqual(intersectPt, e2.top))
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, e2.top.Z);
+        else if (XYCoordsEqual(intersectPt, e1.bot))
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, e1.bot.Z);
+        else if (XYCoordsEqual(intersectPt, e1.top))
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, e1.top.Z);
+        else
+          intersectPt = new Point64(intersectPt.X, intersectPt.Y, DefaultZ);
+        _zCallback(e2.bot, e2.top, e1.bot, e1.top, ref intersectPt);
+      }
+    }
+#endif
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsOdd(int val)
+    {
+      return ((val & 1) != 0);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsHotEdge(Active ae)
+    {
+      return ae.outrec != null;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsOpen(Active ae)
+    {
+      return ae.localMin.isOpen;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsOpenEnd(Active ae)
+    {
+      return ae.localMin.isOpen && IsOpenEnd(ae.vertexTop!);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsOpenEnd(Vertex v)
+    {
+      return (v.flags & (VertexFlags.OpenStart | VertexFlags.OpenEnd)) != VertexFlags.None;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Active? GetPrevHotEdge(Active ae)
+    {
+      Active? prev = ae.prevInAEL;
+      while (prev != null && (IsOpen(prev) || !IsHotEdge(prev)))
+        prev = prev.prevInAEL;
+      return prev;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsFront(Active ae)
+    {
+      return (ae == ae.outrec!.frontEdge);
+    }
+
+    /*******************************************************************************
+    *  Dx:                             0(90deg)                                    *
+    *                                  |                                           *
+    *               +inf (180deg) <--- o --. -inf (0deg)                          *
+    *******************************************************************************/
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static double GetDx(Point64 pt1, Point64 pt2)
+    {
+      double dy = pt2.Y - pt1.Y;
+      if (dy != 0)
+        return (pt2.X - pt1.X) / dy;
+      if (pt2.X > pt1.X)
+        return double.NegativeInfinity;
+      return double.PositiveInfinity;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static long TopX(Active ae, long currentY)
+    {
+      if ((currentY == ae.top.Y) || (ae.top.X == ae.bot.X)) return ae.top.X;
+      if (currentY == ae.bot.Y) return ae.bot.X;
+      return ae.bot.X + (long) Math.Round(ae.dx * (currentY - ae.bot.Y));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsHorizontal(Active ae)
+    {
+      return (ae.top.Y == ae.bot.Y);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsHeadingRightHorz(Active ae)
+    {
+      return (double.IsNegativeInfinity(ae.dx));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsHeadingLeftHorz(Active ae)
+    {
+      return (double.IsPositiveInfinity(ae.dx));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void SwapActives(ref Active ae1, ref Active ae2)
+    {
+      (ae2, ae1) = (ae1, ae2);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static PathType GetPolyType(Active ae)
+    {
+      return ae.localMin.polytype;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsSamePolyType(Active ae1, Active ae2)
+    {
+      return ae1.localMin.polytype == ae2.localMin.polytype;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void SetDx(Active ae)
+    {
+      ae.dx = GetDx(ae.bot, ae.top);
+    }
+
+    
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Vertex NextVertex(Active ae)
+    {
+      if (ae.windDx > 0)
+        return ae.vertexTop!.next!;
+      return ae.vertexTop!.prev!;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Vertex PrevPrevVertex(Active ae)
+    {
+      if (ae.windDx > 0)
+        return ae.vertexTop!.prev!.prev!;
+      return ae.vertexTop!.next!.next!;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsMaxima(Vertex vertex)
+    {
+      return ((vertex.flags & VertexFlags.LocalMax) != VertexFlags.None);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsMaxima(Active ae)
+    {
+      return IsMaxima(ae.vertexTop!);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Active? GetMaximaPair(Active ae)
+    {
+      Active? ae2;
+      ae2 = ae.nextInAEL;
+      while (ae2 != null)
+      {
+        if (ae2.vertexTop == ae.vertexTop) return ae2; // Found!
+        ae2 = ae2.nextInAEL;
+      }
+      return null;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Vertex? GetCurrYMaximaVertex(Active ae)
+    {
+      Vertex? result = ae.vertexTop;
+      if (ae.windDx > 0)
+        while (result!.next!.pt.Y == result.pt.Y) result = result.next;
+      else
+        while (result!.prev!.pt.Y == result.pt.Y) result = result.prev;
+      if (!IsMaxima(result)) result = null; // not a maxima
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Active? GetHorzMaximaPair(Active horz, Vertex maxVert)
+    {
+      // we can't be sure whether the MaximaPair is on the left or right, so ...
+      Active? result = horz.prevInAEL;
+      while (result != null && result.curX >= maxVert.pt.X)
+      {
+        if (result.vertexTop == maxVert) return result;  // Found!
+        result = result.prevInAEL;
+      }
+      result = horz.nextInAEL;
+      while (result != null && TopX(result, horz.top.Y) <= maxVert.pt.X)
+      {
+        if (result.vertexTop == maxVert) return result;  // Found!
+        result = result.nextInAEL;
+      }
+      return null;
+    }
+
+    private struct IntersectListSort : IComparer<IntersectNode>
+    {
+      public int Compare(IntersectNode a, IntersectNode b)
+      {
+        if (a.pt.Y == b.pt.Y)
+        {
+          if (a.pt.X == b.pt.X) return 0;
+          return (a.pt.X < b.pt.X) ? -1 : 1;
+        }
+        return (a.pt.Y > b.pt.Y) ? -1 : 1;
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void SetSides(OutRec outrec, Active startEdge, Active endEdge)
+    {
+      outrec.frontEdge = startEdge;
+      outrec.backEdge = endEdge;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void SwapOutrecs(Active ae1, Active ae2)
+    {
+      OutRec? or1 = ae1.outrec; // at least one edge has 
+      OutRec? or2 = ae2.outrec; // an assigned outrec
+      if (or1 == or2)
+      {
+        Active? ae = or1!.frontEdge;
+        or1.frontEdge = or1.backEdge;
+        or1.backEdge = ae;
+        return;
+      }
+
+      if (or1 != null)
+      {
+        if (ae1 == or1.frontEdge)
+          or1.frontEdge = ae2;
+        else
+          or1.backEdge = ae2;
+      }
+
+      if (or2 != null)
+      {
+        if (ae2 == or2.frontEdge)
+          or2.frontEdge = ae1;
+        else
+          or2.backEdge = ae1;
+      }
+
+      ae1.outrec = or2;
+      ae2.outrec = or1;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static double Area(OutPt op)
+    {
+      // https://en.wikipedia.org/wiki/Shoelace_formula
+      double area = 0.0;
+      OutPt op2 = op;
+      do
+      {
+        area += (double)(op2.prev.pt.Y + op2.pt.Y) * 
+          (op2.prev.pt.X - op2.pt.X);
+        op2 = op2.next!;
+      } while (op2 != op);
+      return area * 0.5;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static double AreaTriangle(Point64 pt1, Point64 pt2, Point64 pt3)
+    {
+      return (double) (pt3.Y + pt1.Y) * (pt3.X - pt1.X) +
+        (double) (pt1.Y + pt2.Y) * (pt1.X - pt2.X) +
+        (double) (pt2.Y + pt3.Y) * (pt2.X - pt3.X);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static OutRec? GetRealOutRec(OutRec? outRec)
+    {
+      while ((outRec != null) && (outRec.pts == null))
+        outRec = outRec.owner;
+      return outRec;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void UncoupleOutRec(Active ae)
+    {
+      OutRec? outrec = ae.outrec;
+      if (outrec == null) return;
+      outrec.frontEdge!.outrec = null;
+      outrec.backEdge!.outrec = null;
+      outrec.frontEdge = null;
+      outrec.backEdge = null;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool OutrecIsAscending(Active hotEdge)
+	  {
+		  return (hotEdge == hotEdge.outrec!.frontEdge);
+	  }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void SwapFrontBackSides(OutRec outrec)
+    {
+      // while this proc. is needed for open paths
+      // it's almost never needed for closed paths
+      Active ae2 = outrec.frontEdge!;
+      outrec.frontEdge = outrec.backEdge;
+      outrec.backEdge = ae2;
+      outrec.pts = outrec.pts!.next;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool EdgesAdjacentInAEL(IntersectNode inode)
+    {
+      return (inode.edge1.nextInAEL == inode.edge2) || (inode.edge1.prevInAEL == inode.edge2);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    protected void ClearSolution()
+    {
+      while (_actives != null) DeleteFromAEL(_actives);
+      _scanlineList.Clear();
+      DisposeIntersectNodes();
+      _joinerList.Clear();
+      _horzJoiners = null;
+      _outrecList.Clear();
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Clear()
+    {
+      ClearSolution();
+      _minimaList.Clear();
+      _vertexList.Clear();
+      _currentLocMin = 0;
+      _isSortedMinimaList = false;
+      _hasOpenPaths = false;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    protected void Reset()
+    {
+      if (!_isSortedMinimaList)
+      {
+        _minimaList.Sort(new LocMinSorter());
+        _isSortedMinimaList = true;
+      }
+
+      _scanlineList.Capacity = _minimaList.Count;
+      for (int i = _minimaList.Count - 1; i >= 0; i--)
+        _scanlineList.Add(_minimaList[i].vertex.pt.Y);
+
+      _currentBotY = 0;
+      _currentLocMin = 0;
+      _actives = null;
+      _sel = null;
+      _succeeded = true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void InsertScanline(long y)
+    {
+      int index = _scanlineList.BinarySearch(y);
+      if (index >= 0) return;
+      index = ~index;
+      _scanlineList.Insert(index, y);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool PopScanline(out long y)
+    {
+      int cnt = _scanlineList.Count - 1;
+      if (cnt < 0)
+      {
+        y = 0;
+        return false;
+      }
+
+      y = _scanlineList[cnt];
+      _scanlineList.RemoveAt(cnt--);
+      while (cnt >= 0 && y == _scanlineList[cnt])
+        _scanlineList.RemoveAt(cnt--);
+      return true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool HasLocMinAtY(long y)
+    {
+      return (_currentLocMin < _minimaList.Count && _minimaList[_currentLocMin].vertex.pt.Y == y);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private LocalMinima PopLocalMinima()
+    {
+      return _minimaList[_currentLocMin++];
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void AddLocMin(Vertex vert, PathType polytype, bool isOpen)
+    {
+      // make sure the vertex is added only once ...
+      if ((vert.flags & VertexFlags.LocalMin) != VertexFlags.None) return;
+      vert.flags |= VertexFlags.LocalMin;
+
+      LocalMinima lm = new LocalMinima(vert, polytype, isOpen);
+      _minimaList.Add(lm);
+    }
+
+    protected void AddPathsToVertexList(Paths64 paths, PathType polytype, bool isOpen)
+    {
+      int totalVertCnt = 0;
+      foreach (Path64 path in paths) totalVertCnt += path.Count;
+      _vertexList.Capacity = _vertexList.Count + totalVertCnt;
+
+      foreach (Path64 path in paths) 
+      {
+        Vertex? v0 = null, prev_v = null, curr_v;
+        foreach (Point64 pt in path)
+        {
+          if (v0 == null)
+          {
+            v0 = new Vertex(pt, VertexFlags.None, null);
+            _vertexList.Add(v0);
+            prev_v = v0;
+          }
+          else if (prev_v!.pt != pt) // ie skips duplicates
+          {
+            curr_v = new Vertex(pt, VertexFlags.None, prev_v);
+            _vertexList.Add(curr_v);
+            prev_v.next = curr_v;
+            prev_v = curr_v;
+          }
+        }
+        if (prev_v == null || prev_v.prev == null) continue;
+        if (!isOpen && prev_v.pt == v0!.pt) prev_v = prev_v.prev;
+        prev_v.next = v0;
+        v0!.prev = prev_v;
+        if (!isOpen && prev_v.next == prev_v) continue;
+
+        // OK, we have a valid path
+        bool going_up, going_up0;
+        if (isOpen)
+        {
+          curr_v = v0.next;
+          while (curr_v != v0 && curr_v!.pt.Y == v0.pt.Y)
+            curr_v = curr_v.next;
+          going_up = curr_v.pt.Y <= v0.pt.Y;
+          if (going_up)
+          {
+            v0.flags = VertexFlags.OpenStart;
+            AddLocMin(v0, polytype, true);
+          }
+          else
+            v0.flags = VertexFlags.OpenStart | VertexFlags.LocalMax;
+        }
+        else // closed path
+        {
+          prev_v = v0.prev;
+          while (prev_v != v0 && prev_v!.pt.Y == v0.pt.Y)
+            prev_v = prev_v.prev;
+          if (prev_v == v0)
+            continue; // only open paths can be completely flat
+          going_up = prev_v.pt.Y > v0.pt.Y;
+        }
+
+        going_up0 = going_up;
+        prev_v = v0;
+        curr_v = v0.next;
+        while (curr_v != v0)
+        {
+          if (curr_v!.pt.Y > prev_v.pt.Y && going_up)
+          {
+            prev_v.flags |= VertexFlags.LocalMax;
+            going_up = false;
+          }
+          else if (curr_v.pt.Y < prev_v.pt.Y && !going_up)
+          {
+            going_up = true;
+            AddLocMin(prev_v, polytype, isOpen);
+          }
+          prev_v = curr_v;
+          curr_v = curr_v.next;
+        }
+
+        if (isOpen)
+        {
+          prev_v.flags |= VertexFlags.OpenEnd;
+          if (going_up)
+            prev_v.flags |= VertexFlags.LocalMax;
+          else
+            AddLocMin(prev_v, polytype, isOpen);
+        }
+        else if (going_up != going_up0)
+        {
+          if (going_up0) AddLocMin(prev_v, polytype, false);
+          else prev_v.flags |= VertexFlags.LocalMax;
+        }
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddSubject(Path64 path)
+    {
+      AddPath(path, PathType.Subject);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddOpenSubject(Path64 path)
+    {
+      AddPath(path, PathType.Subject, true);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddClip(Path64 path)
+    {
+      AddPath(path, PathType.Clip);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    protected void AddPath(Path64 path, PathType polytype, bool isOpen = false)
+    {
+      Paths64 tmp = new Paths64(1) { path };
+      AddPaths(tmp, polytype, isOpen);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    protected void AddPaths(Paths64 paths, PathType polytype, bool isOpen = false)
+    {
+      if (isOpen) _hasOpenPaths = true;
+      _isSortedMinimaList = false;
+      AddPathsToVertexList(paths, polytype, isOpen);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool IsContributingClosed(Active ae)
+    {
+      switch (_fillrule)
+      {
+        case FillRule.Positive:
+          if (ae.windCount != 1) return false;
+          break;
+        case FillRule.Negative:
+          if (ae.windCount != -1) return false;
+          break;
+        case FillRule.NonZero:
+          if (Math.Abs(ae.windCount) != 1) return false;
+          break;
+      }
+
+      switch (_cliptype)
+      {
+        case ClipType.Intersection:
+          return _fillrule switch
+          {
+            FillRule.Positive => ae.windCount2 > 0,
+            FillRule.Negative => ae.windCount2 < 0,
+            _ => ae.windCount2 != 0,
+          };
+
+        case ClipType.Union:
+          return _fillrule switch
+          {
+            FillRule.Positive => ae.windCount2 <= 0,
+            FillRule.Negative => ae.windCount2 >= 0,
+            _ => ae.windCount2 == 0,
+          };
+
+        case ClipType.Difference:
+          bool result = _fillrule switch
+          {
+            FillRule.Positive => (ae.windCount2 <= 0),
+            FillRule.Negative => (ae.windCount2 >= 0),
+            _ => (ae.windCount2 == 0),
+          };
+          return (GetPolyType(ae) == PathType.Subject)? result : !result;
+
+        case ClipType.Xor:
+          return true; // XOr is always contributing unless open
+
+        default:
+          return false;
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool IsContributingOpen(Active ae)
+    {
+      bool isInClip, isInSubj;
+      switch (_fillrule)
+      {
+        case FillRule.Positive:
+          isInSubj = ae.windCount > 0;
+          isInClip = ae.windCount2 > 0;
+          break;
+        case FillRule.Negative:
+          isInSubj = ae.windCount < 0;
+          isInClip = ae.windCount2 < 0;
+          break;
+        default:
+          isInSubj = ae.windCount != 0;
+          isInClip = ae.windCount2 != 0;
+          break;
+      }
+
+      bool result = _cliptype switch
+      {
+        ClipType.Intersection => isInClip,
+        ClipType.Union => !isInSubj && !isInClip,
+        _ => !isInClip
+      };
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void SetWindCountForClosedPathEdge(Active ae)
+    {
+      // Wind counts refer to polygon regions not edges, so here an edge's WindCnt
+      // indicates the higher of the wind counts for the two regions touching the
+      // edge. (nb: Adjacent regions can only ever have their wind counts differ by
+      // one. Also, open paths have no meaningful wind directions or counts.)
+
+      Active? ae2 = ae.prevInAEL;
+      // find the nearest closed path edge of the same PolyType in AEL (heading left)
+      PathType pt = GetPolyType(ae);
+      while (ae2 != null && (GetPolyType(ae2) != pt || IsOpen(ae2))) ae2 = ae2.prevInAEL;
+
+      if (ae2 == null)
+      {
+        ae.windCount = ae.windDx;
+        ae2 = _actives;
+      }
+      else if (_fillrule == FillRule.EvenOdd)
+      {
+        ae.windCount = ae.windDx;
+        ae.windCount2 = ae2.windCount2;
+        ae2 = ae2.nextInAEL;
+      }
+      else
+      {
+        // NonZero, positive, or negative filling here ...
+        // when e2's WindCnt is in the SAME direction as its WindDx,
+        // then polygon will fill on the right of 'e2' (and 'e' will be inside)
+        // nb: neither e2.WindCnt nor e2.WindDx should ever be 0.
+        if (ae2.windCount * ae2.windDx < 0)
+        {
+          // opposite directions so 'ae' is outside 'ae2' ...
+          if (Math.Abs(ae2.windCount) > 1)
+          {
+            // outside prev poly but still inside another.
+            if (ae2.windDx * ae.windDx < 0)
+              // reversing direction so use the same WC
+              ae.windCount = ae2.windCount;
+            else
+              // otherwise keep 'reducing' the WC by 1 (i.e. towards 0) ...
+              ae.windCount = ae2.windCount + ae.windDx;
+          }
+          else
+            // now outside all polys of same polytype so set own WC ...
+            ae.windCount = (IsOpen(ae) ? 1 : ae.windDx);
+        }
+        else
+        {
+          //'ae' must be inside 'ae2'
+          if (ae2.windDx * ae.windDx < 0)
+            // reversing direction so use the same WC
+            ae.windCount = ae2.windCount;
+          else
+            // otherwise keep 'increasing' the WC by 1 (i.e. away from 0) ...
+            ae.windCount = ae2.windCount + ae.windDx;
+        }
+
+        ae.windCount2 = ae2.windCount2;
+        ae2 = ae2.nextInAEL; // i.e. get ready to calc WindCnt2
+      }
+
+      // update windCount2 ...
+      if (_fillrule == FillRule.EvenOdd)
+        while (ae2 != ae)
+        {
+          if (GetPolyType(ae2!) != pt && !IsOpen(ae2!))
+            ae.windCount2 = (ae.windCount2 == 0 ? 1 : 0);
+          ae2 = ae2!.nextInAEL;
+        }
+      else
+        while (ae2 != ae)
+        {
+          if (GetPolyType(ae2!) != pt && !IsOpen(ae2!))
+            ae.windCount2 += ae2!.windDx;
+          ae2 = ae2!.nextInAEL;
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void SetWindCountForOpenPathEdge(Active ae)
+    {
+      Active? ae2 = _actives;
+      if (_fillrule == FillRule.EvenOdd)
+      {
+        int cnt1 = 0, cnt2 = 0;
+        while (ae2 != ae)
+        {
+          if (GetPolyType(ae2!) == PathType.Clip)
+            cnt2++;
+          else if (!IsOpen(ae2!))
+            cnt1++;
+          ae2 = ae2!.nextInAEL;
+        }
+
+        ae.windCount = (IsOdd(cnt1) ? 1 : 0);
+        ae.windCount2 = (IsOdd(cnt2) ? 1 : 0);
+      }
+      else
+      {
+        while (ae2 != ae)
+        {
+          if (GetPolyType(ae2!) == PathType.Clip)
+            ae.windCount2 += ae2!.windDx;
+          else if (!IsOpen(ae2!))
+            ae.windCount += ae2!.windDx;
+          ae2 = ae2!.nextInAEL;
+        }
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsValidAelOrder(Active resident, Active newcomer)
+    {
+      if (newcomer.curX != resident.curX)
+        return newcomer.curX > resident.curX;
+
+      // get the turning direction  a1.top, a2.bot, a2.top
+      double d = InternalClipper.CrossProduct(resident.top, newcomer.bot, newcomer.top);
+      if (d != 0) return (d < 0);
+
+      // edges must be collinear to get here
+
+      // for starting open paths, place them according to
+      // the direction they're about to turn
+      if (!IsMaxima(resident) && (resident.top.Y > newcomer.top.Y))
+      {
+        return InternalClipper.CrossProduct(newcomer.bot, 
+          resident.top, NextVertex(resident).pt) <= 0;
+      }
+
+      if (!IsMaxima(newcomer) && (newcomer.top.Y > resident.top.Y))
+      {
+        return InternalClipper.CrossProduct(newcomer.bot,
+          newcomer.top, NextVertex(newcomer).pt) >= 0;
+      }
+
+      long y = newcomer.bot.Y;
+      bool newcomerIsLeft = newcomer.isLeftBound;
+
+      if (resident.bot.Y != y || resident.localMin.vertex.pt.Y != y)
+        return newcomer.isLeftBound;
+      // resident must also have just been inserted
+      if (resident.isLeftBound != newcomerIsLeft)
+        return newcomerIsLeft;
+      if (InternalClipper.CrossProduct(PrevPrevVertex(resident).pt,
+            resident.bot, resident.top) == 0) return true;
+      // compare turning direction of the alternate bound
+      return (InternalClipper.CrossProduct(PrevPrevVertex(resident).pt,
+        newcomer.bot, PrevPrevVertex(newcomer).pt) > 0) == newcomerIsLeft;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void InsertLeftEdge(Active ae)
+    {
+      Active ae2;
+
+      if (_actives == null)
+      {
+        ae.prevInAEL = null;
+        ae.nextInAEL = null;
+        _actives = ae;
+      }
+      else if (!IsValidAelOrder(_actives, ae))
+      {
+        ae.prevInAEL = null;
+        ae.nextInAEL = _actives;
+        _actives.prevInAEL = ae;
+        _actives = ae;
+      }
+      else
+      {
+        ae2 = _actives;
+        while (ae2.nextInAEL != null && IsValidAelOrder(ae2.nextInAEL, ae))
+          ae2 = ae2.nextInAEL;
+        ae.nextInAEL = ae2.nextInAEL;
+        if (ae2.nextInAEL != null) ae2.nextInAEL.prevInAEL = ae;
+        ae.prevInAEL = ae2;
+        ae2.nextInAEL = ae;
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void InsertRightEdge(Active ae, Active ae2)
+    {
+      ae2.nextInAEL = ae.nextInAEL;
+      if (ae.nextInAEL != null) ae.nextInAEL.prevInAEL = ae2;
+      ae2.prevInAEL = ae;
+      ae.nextInAEL = ae2;
+    }
+
+    private void InsertLocalMinimaIntoAEL(long botY)
+    {
+      LocalMinima localMinima;
+      Active? leftBound, rightBound;
+      // Add any local minima (if any) at BotY ...
+      // NB horizontal local minima edges should contain locMin.vertex.prev
+      while (HasLocMinAtY(botY))
+      {
+        localMinima = PopLocalMinima();
+        if ((localMinima.vertex.flags & VertexFlags.OpenStart) != VertexFlags.None)
+        {
+          leftBound = null;
+        }
+        else
+        {
+          leftBound = new Active
+          {
+            bot = localMinima.vertex.pt,
+            curX = localMinima.vertex.pt.X,
+            windDx = -1,
+            vertexTop = localMinima.vertex.prev,
+            top = localMinima.vertex.prev!.pt,
+            outrec = null,
+            localMin = localMinima
+          };
+          SetDx(leftBound);
+        }
+
+        if ((localMinima.vertex.flags & VertexFlags.OpenEnd) != VertexFlags.None)
+        {
+          rightBound = null;
+        }
+        else
+        {
+          rightBound = new Active
+          {
+            bot = localMinima.vertex.pt,
+            curX = localMinima.vertex.pt.X,
+            windDx = 1,
+            vertexTop = localMinima.vertex.next, // i.e. ascending
+            top = localMinima.vertex.next!.pt,
+            outrec = null,
+            localMin = localMinima
+          };
+          SetDx(rightBound);
+        }
+
+        // Currently LeftB is just the descending bound and RightB is the ascending.
+        // Now if the LeftB isn't on the left of RightB then we need swap them.
+        if (leftBound != null && rightBound != null)
+        {
+          if (IsHorizontal(leftBound))
+          {
+            if (IsHeadingRightHorz(leftBound)) SwapActives(ref leftBound, ref rightBound);
+          }
+          else if (IsHorizontal(rightBound))
+          {
+            if (IsHeadingLeftHorz(rightBound)) SwapActives(ref leftBound, ref rightBound);
+          }
+          else if (leftBound.dx < rightBound.dx)
+            SwapActives(ref leftBound, ref rightBound);
+          //so when leftBound has windDx == 1, the polygon will be oriented
+          //counter-clockwise in Cartesian coords (clockwise with inverted Y).
+        }
+        else if (leftBound == null)
+        {
+          leftBound = rightBound;
+          rightBound = null;
+        }
+
+        bool contributing;
+        leftBound!.isLeftBound = true;
+        InsertLeftEdge(leftBound);
+
+        if (IsOpen(leftBound))
+        {
+          SetWindCountForOpenPathEdge(leftBound);
+          contributing = IsContributingOpen(leftBound);
+        }
+        else
+        {
+          SetWindCountForClosedPathEdge(leftBound);
+          contributing = IsContributingClosed(leftBound);
+        }
+
+        if (rightBound != null)
+        {
+          rightBound.windCount = leftBound.windCount;
+          rightBound.windCount2 = leftBound.windCount2;
+          InsertRightEdge(leftBound, rightBound); ///////
+
+          if (contributing)
+          {
+            AddLocalMinPoly(leftBound, rightBound, leftBound.bot, true);
+            if (!IsHorizontal(leftBound) && TestJoinWithPrev1(leftBound))
+            {
+              OutPt op = AddOutPt(leftBound.prevInAEL!, leftBound.bot);
+              AddJoin(op, leftBound.outrec!.pts!);
+            }
+          }
+
+          while (rightBound.nextInAEL != null &&
+                 IsValidAelOrder(rightBound.nextInAEL, rightBound))
+          {
+            IntersectEdges(rightBound, rightBound.nextInAEL, rightBound.bot);
+            SwapPositionsInAEL(rightBound, rightBound.nextInAEL);
+          }
+
+          if (!IsHorizontal(rightBound) && TestJoinWithNext1(rightBound))
+          {
+            OutPt op = AddOutPt(rightBound.nextInAEL!, rightBound.bot);
+            AddJoin(rightBound.outrec!.pts!, op);
+          }
+
+          if (IsHorizontal(rightBound))
+            PushHorz(rightBound);
+          else
+            InsertScanline(rightBound.top.Y);
+        }
+        else if (contributing)
+          StartOpenPath(leftBound, leftBound.bot);
+
+        if (IsHorizontal(leftBound))
+          PushHorz(leftBound);
+        else
+          InsertScanline(leftBound.top.Y);
+      } // while (HasLocMinAtY())
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void PushHorz(Active ae)
+    {
+      ae.nextInSEL = _sel;
+      _sel = ae;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool PopHorz(out Active? ae)
+    {
+      ae = _sel;
+      if (_sel == null) return false;
+      _sel = _sel.nextInSEL;
+      return true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool TestJoinWithPrev1(Active e)
+    {
+      // this is marginally quicker than TestJoinWithPrev2
+      // but can only be used when e.PrevInAEL.currX is accurate
+      return IsHotEdge(e) && !IsOpen(e) &&
+             (e.prevInAEL != null) && (e.prevInAEL.curX == e.curX) &&
+             IsHotEdge(e.prevInAEL) && !IsOpen(e.prevInAEL) &&
+             (InternalClipper.CrossProduct(e.prevInAEL.top, e.bot, e.top) == 0);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool TestJoinWithPrev2(Active e, Point64 currPt)
+    {
+      return IsHotEdge(e) && !IsOpen(e) &&
+             (e.prevInAEL != null) && !IsOpen(e.prevInAEL) &&
+             IsHotEdge(e.prevInAEL) && (e.prevInAEL.top.Y < e.bot.Y) &&
+             (Math.Abs(TopX(e.prevInAEL, currPt.Y) - currPt.X) < 2) &&
+             (InternalClipper.CrossProduct(e.prevInAEL.top, currPt, e.top) == 0);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool TestJoinWithNext1(Active e)
+    {
+      // this is marginally quicker than TestJoinWithNext2
+      // but can only be used when e.NextInAEL.currX is accurate
+      return IsHotEdge(e) && !IsOpen(e) &&
+             (e.nextInAEL != null) && (e.nextInAEL.curX == e.curX) &&
+             IsHotEdge(e.nextInAEL) && !IsOpen(e.nextInAEL) &&
+             (InternalClipper.CrossProduct(e.nextInAEL.top, e.bot, e.top) == 0);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool TestJoinWithNext2(Active e, Point64 currPt)
+    {
+      return IsHotEdge(e) && !IsOpen(e) &&
+             (e.nextInAEL != null) && !IsOpen(e.nextInAEL) &&
+             IsHotEdge(e.nextInAEL) && (e.nextInAEL.top.Y < e.bot.Y) &&
+             (Math.Abs(TopX(e.nextInAEL, currPt.Y) - currPt.X) < 2) &&
+             (InternalClipper.CrossProduct(e.nextInAEL.top, currPt, e.top) == 0);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private OutPt AddLocalMinPoly(Active ae1, Active ae2, Point64 pt, bool isNew = false)
+    {
+      OutRec outrec = new OutRec();
+      _outrecList.Add(outrec);
+      outrec.idx = _outrecList.Count - 1;
+      outrec.pts = null;
+      outrec.polypath = null;
+      ae1.outrec = outrec;
+      ae2.outrec = outrec;
+
+      // Setting the owner and inner/outer states (above) is an essential
+      // precursor to setting edge 'sides' (ie left and right sides of output
+      // polygons) and hence the orientation of output paths ...
+
+      if (IsOpen(ae1))
+      {
+        outrec.owner = null;
+        outrec.isOpen = true;
+        if (ae1.windDx > 0)
+          SetSides(outrec, ae1, ae2);
+        else
+          SetSides(outrec, ae2, ae1);
+      }
+      else
+      {
+        outrec.isOpen = false;
+        Active? prevHotEdge = GetPrevHotEdge(ae1);
+        // e.windDx is the winding direction of the **input** paths
+        // and unrelated to the winding direction of output polygons.
+        // Output orientation is determined by e.outrec.frontE which is
+        // the ascending edge (see AddLocalMinPoly).
+        if (prevHotEdge != null)
+        {
+          outrec.owner = prevHotEdge.outrec;
+          if (OutrecIsAscending(prevHotEdge) == isNew)
+            SetSides(outrec, ae2, ae1);
+          else
+            SetSides(outrec, ae1, ae2);
+        }
+        else
+        {
+          outrec.owner = null;
+          if (isNew)
+            SetSides(outrec, ae1, ae2);
+          else
+            SetSides(outrec, ae2, ae1);
+        }
+      }
+
+      OutPt op = new OutPt(pt, outrec);
+      outrec.pts = op;
+      return op;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private OutPt? AddLocalMaxPoly(Active ae1, Active ae2, Point64 pt)
+    {
+      if (IsFront(ae1) == IsFront(ae2))
+      {
+        if (IsOpenEnd(ae1))
+          SwapFrontBackSides(ae1.outrec!);
+        else if (IsOpenEnd(ae2))
+          SwapFrontBackSides(ae2.outrec!);
+        else
+        {
+          _succeeded = false;
+          return null;
+        }
+      }
+
+      OutPt result = AddOutPt(ae1, pt);
+      if (ae1.outrec == ae2.outrec)
+      {
+        OutRec outrec = ae1.outrec!;
+        outrec.pts = result;
+        UncoupleOutRec(ae1);
+        if (!IsOpen(ae1))
+          CleanCollinear(outrec);
+        result = outrec.pts;
+
+        outrec.owner = GetRealOutRec(outrec.owner);
+        if (_using_polytree && outrec.owner is { frontEdge: null })
+            outrec.owner = GetRealOutRec(outrec.owner.owner);
+      }
+      // and to preserve the winding orientation of outrec ...
+      else if (IsOpen(ae1))
+      {
+        if (ae1.windDx < 0)
+          JoinOutrecPaths(ae1, ae2);
+        else
+          JoinOutrecPaths(ae2, ae1);
+      }
+      else if (ae1.outrec!.idx < ae2.outrec!.idx)
+        JoinOutrecPaths(ae1, ae2);
+      else
+        JoinOutrecPaths(ae2, ae1);
+
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void JoinOutrecPaths(Active ae1, Active ae2)
+    {
+      // join ae2 outrec path onto ae1 outrec path and then delete ae2 outrec path
+      // pointers. (NB Only very rarely do the joining ends share the same coords.)
+      OutPt p1Start = ae1.outrec!.pts!;
+      OutPt p2Start = ae2.outrec!.pts!;
+      OutPt p1End = p1Start.next!;
+      OutPt p2End = p2Start.next!;
+      if (IsFront(ae1))
+      {
+        p2End.prev = p1Start;
+        p1Start.next = p2End;
+        p2Start.next = p1End;
+        p1End.prev = p2Start;
+        ae1.outrec.pts = p2Start;
+        // nb: if IsOpen(e1) then e1 & e2 must be a 'maximaPair'
+        ae1.outrec.frontEdge = ae2.outrec.frontEdge;
+        if (ae1.outrec.frontEdge != null)
+          ae1.outrec.frontEdge!.outrec = ae1.outrec;
+      }
+      else
+      {
+        p1End.prev = p2Start;
+        p2Start.next = p1End;
+        p1Start.next = p2End;
+        p2End.prev = p1Start;
+
+        ae1.outrec.backEdge = ae2.outrec.backEdge;
+        if (ae1.outrec.backEdge != null)
+          ae1.outrec.backEdge!.outrec = ae1.outrec;
+      }
+
+      // an owner must have a lower idx otherwise
+      // it won't be a valid owner
+      if (ae2.outrec.owner != null &&
+        ae2.outrec.owner.idx < ae1.outrec.idx)
+      {
+        if (ae1.outrec.owner == null || ae2.outrec.owner.idx < ae1.outrec.owner.idx)
+            ae1.outrec.owner = ae2.outrec.owner;
+      }
+
+      // after joining, the ae2.OutRec must contains no vertices ...
+      ae2.outrec.frontEdge = null;
+      ae2.outrec.backEdge = null;
+      ae2.outrec.pts = null;
+      ae2.outrec.owner = ae1.outrec; // this may be redundant
+
+      if (IsOpenEnd(ae1))
+      {
+        ae2.outrec.pts = ae1.outrec.pts;
+        ae1.outrec.pts = null;
+      }
+
+      // and ae1 and ae2 are maxima and are about to be dropped from the Actives list.
+      ae1.outrec = null;
+      ae2.outrec = null;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static OutPt AddOutPt(Active ae, Point64 pt)
+    {
+      OutPt newOp;
+
+      // Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
+      // opFront[.Prev]* ~~~> opBack & opBack == opFront.Next
+      OutRec outrec = ae.outrec!;
+      bool toFront = IsFront(ae);
+      OutPt opFront = outrec.pts!;
+      OutPt opBack = opFront.next!;
+
+      if (toFront && (pt == opFront.pt)) newOp = opFront;
+      else if (!toFront && (pt == opBack.pt)) newOp = opBack;
+      else
+      {
+        newOp = new OutPt(pt, outrec);
+        opBack.prev = newOp;
+        newOp.prev = opFront;
+        newOp.next = opBack;
+        opFront.next = newOp;
+        if (toFront) outrec.pts = newOp;
+      }
+      return newOp;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private OutPt StartOpenPath(Active ae, Point64 pt)
+    {
+      OutRec outrec = new OutRec();
+      _outrecList.Add(outrec);
+      outrec.idx = _outrecList.Count - 1;
+      outrec.owner = null;
+      outrec.isOpen = true;
+      outrec.pts = null;
+      outrec.polypath = null;
+      if (ae.windDx > 0)
+      {
+        outrec.frontEdge = ae;
+        outrec.backEdge = null;
+      }
+      else 
+      {
+        outrec.frontEdge = null; 
+        outrec.backEdge = ae;
+      }
+
+      ae.outrec = outrec;
+      OutPt op = new OutPt(pt, outrec);
+      outrec.pts = op;
+      return op;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void UpdateEdgeIntoAEL(Active ae)
+    {
+      ae.bot = ae.top;
+      ae.vertexTop = NextVertex(ae);
+      ae.top = ae.vertexTop!.pt;
+      ae.curX = ae.bot.X;
+      SetDx(ae);
+      if (IsHorizontal(ae)) return;
+      InsertScanline(ae.top.Y);
+      if (TestJoinWithPrev1(ae))
+      {
+        OutPt op1 = AddOutPt(ae.prevInAEL!, ae.bot);
+        OutPt op2 = AddOutPt(ae, ae.bot);
+        AddJoin(op1, op2);
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Active? FindEdgeWithMatchingLocMin(Active e)
+    {
+      Active? result = e.nextInAEL;
+      while (result != null)
+      {
+        if (result.localMin == e.localMin) return result;
+        if (!IsHorizontal(result) && e.bot != result.bot) result = null;
+        else result = result.nextInAEL;
+      }
+      result = e.prevInAEL;
+      while (result != null)
+      {
+        if (result.localMin == e.localMin) return result;
+        if (!IsHorizontal(result) && e.bot != result.bot) return null;
+        result = result.prevInAEL;
+      }
+      return result;
+    }
+
+
+    private OutPt? IntersectEdges(Active ae1, Active ae2, Point64 pt)
+    {
+      OutPt? resultOp = null;
+
+      // MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
+      if (_hasOpenPaths && (IsOpen(ae1) || IsOpen(ae2)))
+      {
+        if (IsOpen(ae1) && IsOpen(ae2)) return null;
+        // the following line avoids duplicating quite a bit of code
+        if (IsOpen(ae2)) SwapActives(ref ae1, ref ae2);
+
+        if (_cliptype == ClipType.Union)
+        {
+          if (!IsHotEdge(ae2)) return null;
+        }
+        else if (ae2.localMin.polytype == PathType.Subject) 
+          return null;
+
+        switch (_fillrule)
+        {
+          case FillRule.Positive:
+            if (ae2.windCount != 1) return null; break;
+          case FillRule.Negative:
+            if (ae2.windCount != -1) return null; break;
+          default:
+            if (Math.Abs(ae2.windCount) != 1) return null; break;
+        }
+
+        // toggle contribution ...
+        if (IsHotEdge(ae1))
+        {
+          resultOp = AddOutPt(ae1, pt);
+#if USINGZ
+          SetZ(ae1, ae2, ref resultOp.pt);
+#endif
+          if (IsFront(ae1))
+            ae1.outrec!.frontEdge = null;
+          else
+            ae1.outrec!.backEdge = null;
+          ae1.outrec = null;
+        }
+
+        // horizontal edges can pass under open paths at a LocMins
+        else if (pt == ae1.localMin.vertex.pt &&
+          !IsOpenEnd(ae1.localMin.vertex))
+        {
+          // find the other side of the LocMin and
+          // if it's 'hot' join up with it ...
+          Active? ae3 = FindEdgeWithMatchingLocMin(ae1);
+          if (ae3 != null && IsHotEdge(ae3))
+          {
+            ae1.outrec = ae3.outrec;
+            if (ae1.windDx > 0)
+              SetSides(ae3.outrec!, ae1, ae3);
+            else
+              SetSides(ae3.outrec!, ae3, ae1);
+            return ae3.outrec!.pts;
+          }
+
+          resultOp = StartOpenPath(ae1, pt);
+        }
+        else
+          resultOp = StartOpenPath(ae1, pt);
+
+#if USINGZ
+        SetZ(ae1, ae2, ref resultOp.pt);
+#endif
+        return resultOp;
+      }
+
+      // MANAGING CLOSED PATHS FROM HERE ON
+
+      // UPDATE WINDING COUNTS...
+
+      int oldE1WindCount, oldE2WindCount;
+      if (ae1.localMin.polytype == ae2.localMin.polytype)
+      {
+        if (_fillrule == FillRule.EvenOdd)
+        {
+          oldE1WindCount = ae1.windCount;
+          ae1.windCount = ae2.windCount;
+          ae2.windCount = oldE1WindCount;
+        }
+        else
+        {
+          if (ae1.windCount + ae2.windDx == 0)
+            ae1.windCount = -ae1.windCount;
+          else
+            ae1.windCount += ae2.windDx;
+          if (ae2.windCount - ae1.windDx == 0)
+            ae2.windCount = -ae2.windCount;
+          else
+            ae2.windCount -= ae1.windDx;
+        }
+      }
+      else
+      {
+        if (_fillrule != FillRule.EvenOdd)
+          ae1.windCount2 += ae2.windDx;
+        else
+          ae1.windCount2 = (ae1.windCount2 == 0 ? 1 : 0);
+        if (_fillrule != FillRule.EvenOdd)
+          ae2.windCount2 -= ae1.windDx;
+        else
+          ae2.windCount2 = (ae2.windCount2 == 0 ? 1 : 0);
+      }
+
+      switch (_fillrule)
+      {
+        case FillRule.Positive:
+          oldE1WindCount = ae1.windCount;
+          oldE2WindCount = ae2.windCount;
+          break;
+        case FillRule.Negative:
+          oldE1WindCount = -ae1.windCount;
+          oldE2WindCount = -ae2.windCount;
+          break;
+        default:
+          oldE1WindCount = Math.Abs(ae1.windCount);
+          oldE2WindCount = Math.Abs(ae2.windCount);
+          break;
+      }      
+
+      bool e1WindCountIs0or1 = oldE1WindCount == 0 || oldE1WindCount == 1;
+      bool e2WindCountIs0or1 = oldE2WindCount == 0 || oldE2WindCount == 1;
+
+      if ((!IsHotEdge(ae1) && !e1WindCountIs0or1) || (!IsHotEdge(ae2) && !e2WindCountIs0or1)) return null;
+
+      // NOW PROCESS THE INTERSECTION ...
+
+      // if both edges are 'hot' ...
+      if (IsHotEdge(ae1) && IsHotEdge(ae2))
+      {
+        if ((oldE1WindCount != 0 && oldE1WindCount != 1) || (oldE2WindCount != 0 && oldE2WindCount != 1) ||
+            (ae1.localMin.polytype != ae2.localMin.polytype && _cliptype != ClipType.Xor))
+        {          
+          resultOp = AddLocalMaxPoly(ae1, ae2, pt);
+#if USINGZ
+          if (resultOp != null)
+            SetZ(ae1, ae2, ref resultOp.pt);
+#endif
+        }
+        else if (IsFront(ae1) || (ae1.outrec == ae2.outrec))
+        {
+          // this 'else if' condition isn't strictly needed but
+          // it's sensible to split polygons that ony touch at
+          // a common vertex (not at common edges).
+          resultOp = AddLocalMaxPoly(ae1, ae2, pt);
+          OutPt op2 = AddLocalMinPoly(ae1, ae2, pt);
+#if USINGZ
+          if (resultOp != null)
+            SetZ(ae1, ae2, ref resultOp.pt);
+          SetZ(ae1, ae2, ref op2.pt);
+#endif
+          if (resultOp != null && resultOp.pt == op2.pt &&
+            !IsHorizontal(ae1) && !IsHorizontal(ae2) &&
+            (InternalClipper.CrossProduct(ae1.bot, resultOp.pt, ae2.bot) == 0))
+            AddJoin(resultOp, op2);
+        }
+        else
+        {
+          // can't treat as maxima & minima
+          resultOp = AddOutPt(ae1, pt);
+#if USINGZ
+          OutPt op2 = AddOutPt(ae2, pt);
+          SetZ(ae1, ae2, ref resultOp.pt);
+          SetZ(ae1, ae2, ref op2.pt);
+#else
+          AddOutPt(ae2, pt);
+#endif
+          SwapOutrecs(ae1, ae2);
+        }
+      }
+
+      // if one or other edge is 'hot' ...
+      else if (IsHotEdge(ae1))
+      {
+        resultOp = AddOutPt(ae1, pt);
+#if USINGZ
+        SetZ(ae1, ae2, ref resultOp.pt);
+#endif
+        SwapOutrecs(ae1, ae2);
+      }
+      else if (IsHotEdge(ae2))
+      {
+        resultOp = AddOutPt(ae2, pt);
+#if USINGZ
+        SetZ(ae1, ae2, ref resultOp.pt);
+#endif
+        SwapOutrecs(ae1, ae2);
+      }
+
+      // neither edge is 'hot'
+      else
+      {
+        long e1Wc2, e2Wc2;
+        switch (_fillrule)
+        {
+          case FillRule.Positive:
+            e1Wc2 = ae1.windCount2;
+            e2Wc2 = ae2.windCount2;
+            break;
+          case FillRule.Negative:
+            e1Wc2 = -ae1.windCount2;
+            e2Wc2 = -ae2.windCount2;
+            break;
+          default:
+            e1Wc2 = Math.Abs(ae1.windCount2);
+            e2Wc2 = Math.Abs(ae2.windCount2);
+            break;
+        }
+
+        if (!IsSamePolyType(ae1, ae2))
+        {
+          resultOp = AddLocalMinPoly(ae1, ae2, pt);
+#if USINGZ
+          SetZ(ae1, ae2, ref resultOp.pt);
+#endif
+        }
+        else if (oldE1WindCount == 1 && oldE2WindCount == 1)
+        {
+          resultOp = null;
+          switch (_cliptype)
+          {
+            case ClipType.Union:
+              if (e1Wc2 > 0 && e2Wc2 > 0) return null;
+              resultOp = AddLocalMinPoly(ae1, ae2, pt);
+              break;
+
+            case ClipType.Difference:
+              if (((GetPolyType(ae1) == PathType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
+                  ((GetPolyType(ae1) == PathType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
+              {
+                resultOp = AddLocalMinPoly(ae1, ae2, pt);
+              }
+
+              break;
+
+            case ClipType.Xor:
+              resultOp = AddLocalMinPoly(ae1, ae2, pt);
+              break;
+
+            default: // ClipType.Intersection:
+              if (e1Wc2 <= 0 || e2Wc2 <= 0) return null;
+              resultOp = AddLocalMinPoly(ae1, ae2, pt);
+              break;
+          }
+#if USINGZ
+          if (resultOp != null) SetZ(ae1, ae2, ref resultOp.pt);
+#endif
+        }
+      }
+
+      return resultOp;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DeleteFromAEL(Active ae)
+    {
+      Active? prev = ae.prevInAEL;
+      Active? next = ae.nextInAEL;
+      if (prev == null && next == null && (ae != _actives)) return; // already deleted
+      if (prev != null)
+        prev.nextInAEL = next;
+      else
+        _actives = next;
+      if (next != null) next.prevInAEL = prev;
+      // delete &ae;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void AdjustCurrXAndCopyToSEL(long topY)
+    {
+      Active? ae = _actives;
+      _sel = ae;
+      while (ae != null)
+      {
+        ae.prevInSEL = ae.prevInAEL;
+        ae.nextInSEL = ae.nextInAEL;
+        ae.jump = ae.nextInSEL;
+        ae.curX = TopX(ae, topY);
+        // NB don't update ae.curr.Y yet (see AddNewIntersectNode)
+        ae = ae.nextInAEL;
+      }
+    }
+
+    protected void ExecuteInternal(ClipType ct, FillRule fillRule)
+    {
+      if (ct == ClipType.None) return;
+      _fillrule = fillRule;
+      _cliptype = ct;
+      Reset();
+      if (!PopScanline(out long y)) return;
+      while (_succeeded)
+      {
+        InsertLocalMinimaIntoAEL(y);
+        Active? ae;
+        while (PopHorz(out ae)) DoHorizontal(ae!);
+        ConvertHorzTrialsToJoins();
+        _currentBotY = y; // bottom of scanbeam
+        if (!PopScanline(out y))
+          break; // y new top of scanbeam
+        DoIntersections(y);
+        DoTopOfScanbeam(y);
+        while (PopHorz(out ae)) DoHorizontal(ae!);
+      }
+
+      if (_succeeded) ProcessJoinList();
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DoIntersections(long topY)
+    {
+      if (BuildIntersectList(topY))
+      {
+        ProcessIntersectList();
+        DisposeIntersectNodes();
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DisposeIntersectNodes()
+    {
+      _intersectList.Clear();
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void AddNewIntersectNode(Active ae1, Active ae2, long topY)
+    {
+      if (!InternalClipper.GetIntersectPt(
+        ae1.bot, ae1.top, ae2.bot, ae2.top, out Point64 ip))
+          ip = new Point64(ae1.curX, topY);
+
+      if (ip.Y > _currentBotY || ip.Y < topY)
+      {
+        double absDx1 = Math.Abs(ae1.dx);
+        double absDx2 = Math.Abs(ae2.dx);
+        if (absDx1 > 100 && absDx2 > 100)
+        {
+          if (absDx1 > absDx2)
+            ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top);
+          else
+            ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top);
+        }
+        else if (absDx1 > 100)
+          ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top);
+        else if (absDx2 > 100)
+          ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top);
+        else 
+        {
+          if (ip.Y < topY) ip.Y = topY; 
+          else ip.Y = _currentBotY;
+          if (absDx1 < absDx2) ip.X = TopX(ae1, ip.Y); 
+          else ip.X = TopX(ae2, topY);
+        }
+      }
+      IntersectNode node = new IntersectNode(ip, ae1, ae2);
+      _intersectList.Add(node);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Active? ExtractFromSEL(Active ae)
+    {
+      Active? res = ae.nextInSEL;
+      if (res != null)
+        res.prevInSEL = ae.prevInSEL;
+      ae.prevInSEL!.nextInSEL = res;
+      return res;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void Insert1Before2InSEL(Active ae1, Active ae2)
+    {
+      ae1.prevInSEL = ae2.prevInSEL;
+      if (ae1.prevInSEL != null)
+        ae1.prevInSEL.nextInSEL = ae1;
+      ae1.nextInSEL = ae2;
+      ae2.prevInSEL = ae1;
+    }
+
+    private bool BuildIntersectList(long topY)
+    {
+      if (_actives == null || _actives.nextInAEL == null) return false;
+
+      // Calculate edge positions at the top of the current scanbeam, and from this
+      // we will determine the intersections required to reach these new positions.
+      AdjustCurrXAndCopyToSEL(topY);
+
+      // Find all edge intersections in the current scanbeam using a stable merge
+      // sort that ensures only adjacent edges are intersecting. Intersect info is
+      // stored in FIntersectList ready to be processed in ProcessIntersectList.
+      // Re merge sorts see https://stackoverflow.com/a/46319131/359538
+
+      Active? left = _sel, right, lEnd, rEnd, currBase, prevBase, tmp;
+
+      while (left!.jump != null)
+      {
+        prevBase = null;
+        while (left != null && left.jump != null)
+        {
+          currBase = left;
+          right = left.jump;
+          lEnd = right;
+          rEnd = right.jump;
+          left.jump = rEnd;
+          while (left != lEnd && right != rEnd)
+          {
+            if (right!.curX < left!.curX)
+            {
+              tmp = right.prevInSEL!;
+              for (; ; )
+              {
+                AddNewIntersectNode(tmp, right, topY);
+                if (tmp == left) break;
+                tmp = tmp.prevInSEL!;
+              }
+
+              tmp = right;
+              right = ExtractFromSEL(tmp);
+              lEnd = right;
+              Insert1Before2InSEL(tmp, left);
+              if (left == currBase)
+              {
+                currBase = tmp;
+                currBase.jump = rEnd;
+                if (prevBase == null) _sel = currBase;
+                else prevBase.jump = currBase;
+              }
+            }
+            else left = left.nextInSEL;
+          }
+
+          prevBase = currBase;
+          left = rEnd;
+        }
+        left = _sel;
+      }
+
+      return _intersectList.Count > 0;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void ProcessIntersectList()
+    {
+      // We now have a list of intersections required so that edges will be
+      // correctly positioned at the top of the scanbeam. However, it's important
+      // that edge intersections are processed from the bottom up, but it's also
+      // crucial that intersections only occur between adjacent edges.
+
+      // First we do a quicksort so intersections proceed in a bottom up order ...
+      _intersectList.Sort(new IntersectListSort());
+
+      // Now as we process these intersections, we must sometimes adjust the order
+      // to ensure that intersecting edges are always adjacent ...
+      for (int i = 0; i < _intersectList.Count; ++i)
+      {
+        if (!EdgesAdjacentInAEL(_intersectList[i]))
+        {
+          int j = i + 1;
+          while (!EdgesAdjacentInAEL(_intersectList[j])) j++;
+          // swap
+          (_intersectList[j], _intersectList[i]) = 
+            (_intersectList[i], _intersectList[j]);
+        }
+
+        IntersectNode node = _intersectList[i];
+        IntersectEdges(node.edge1, node.edge2, node.pt);
+        SwapPositionsInAEL(node.edge1, node.edge2);
+
+        if (TestJoinWithPrev2(node.edge2, node.pt))
+        {
+          OutPt op1 = AddOutPt(node.edge2.prevInAEL!, node.pt);
+          OutPt op2 = AddOutPt(node.edge2, node.pt);
+          if (op1 != op2) AddJoin(op1, op2);
+        }
+        else if (TestJoinWithNext2(node.edge1, node.pt))
+        {
+          OutPt op1 = AddOutPt(node.edge1, node.pt);
+          OutPt op2 = AddOutPt(node.edge1.nextInAEL!, node.pt);
+          if (op1 != op2) AddJoin(op1, op2);
+        }
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void SwapPositionsInAEL(Active ae1, Active ae2)
+    {
+      // preconditon: ae1 must be immediately to the left of ae2
+      Active? next = ae2.nextInAEL;
+      if (next != null) next.prevInAEL = ae1;
+      Active? prev = ae1.prevInAEL;
+      if (prev != null) prev.nextInAEL = ae2;
+      ae2.prevInAEL = prev;
+      ae2.nextInAEL = ae1;
+      ae1.prevInAEL = ae2;
+      ae1.nextInAEL = next;
+      if (ae2.prevInAEL == null) _actives = ae2;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool ResetHorzDirection(Active horz, Active? maxPair,
+        out long leftX, out long rightX)
+    {
+      if (horz.bot.X == horz.top.X)
+      {
+        // the horizontal edge is going nowhere ...
+        leftX = horz.curX;
+        rightX = horz.curX;
+        Active? ae = horz.nextInAEL;
+        while (ae != null && ae != maxPair) ae = ae.nextInAEL;
+        return ae != null;
+      }
+
+      if (horz.curX < horz.top.X)
+      {
+        leftX = horz.curX;
+        rightX = horz.top.X;
+        return true;
+      }
+      leftX = horz.top.X;
+      rightX = horz.curX;
+      return false; // right to left
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool HorzIsSpike(Active horz)
+    {
+      Point64 nextPt = NextVertex(horz).pt;
+      return (horz.bot.X < horz.top.X) != (horz.top.X < nextPt.X);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void TrimHorz(Active horzEdge, bool preserveCollinear)
+    {
+      bool wasTrimmed = false;
+      Point64 pt = NextVertex(horzEdge).pt;
+
+      while (pt.Y == horzEdge.top.Y)
+      {
+        // always trim 180 deg. spikes (in closed paths)
+        // but otherwise break if preserveCollinear = true
+        if (preserveCollinear &&
+        (pt.X < horzEdge.top.X) != (horzEdge.bot.X < horzEdge.top.X))
+          break;
+
+        horzEdge.vertexTop = NextVertex(horzEdge);
+        horzEdge.top = pt;
+        wasTrimmed = true;
+        if (IsMaxima(horzEdge)) break;
+        pt = NextVertex(horzEdge).pt;
+      }
+      if (wasTrimmed) SetDx(horzEdge); // +/-infinity
+    }
+
+    private void DoHorizontal(Active horz)
+    /*******************************************************************************
+     * Notes: Horizontal edges (HEs) at scanline intersections (i.e. at the top or    *
+     * bottom of a scanbeam) are processed as if layered.The order in which HEs     *
+     * are processed doesn't matter. HEs intersect with the bottom vertices of      *
+     * other HEs[#] and with non-horizontal edges [*]. Once these intersections     *
+     * are completed, intermediate HEs are 'promoted' to the next edge in their     *
+     * bounds, and they in turn may be intersected[%] by other HEs.                 *
+     *                                                                              *
+     * eg: 3 horizontals at a scanline:    /   |                     /           /  *
+     *              |                     /    |     (HE3)o ========%========== o   *
+     *              o ======= o(HE2)     /     |         /         /                *
+     *          o ============#=========*======*========#=========o (HE1)           *
+     *         /              |        /       |       /                            *
+     *******************************************************************************/
+    {
+      Point64 pt;
+      bool horzIsOpen = IsOpen(horz);
+      long Y = horz.bot.Y;
+
+      Vertex? vertex_max = null;
+      Active? maxPair = null;
+
+      if (!horzIsOpen)
+      {
+        vertex_max = GetCurrYMaximaVertex(horz);
+        if (vertex_max != null)
+        {
+          maxPair = GetHorzMaximaPair(horz, vertex_max);
+          // remove 180 deg.spikes and also simplify
+          // consecutive horizontals when PreserveCollinear = true
+          if (vertex_max != horz.vertexTop)
+            TrimHorz(horz, PreserveCollinear);
+        }
+      }
+
+      bool isLeftToRight =
+        ResetHorzDirection(horz, maxPair, out long leftX, out long rightX);
+
+      if (IsHotEdge(horz))
+        AddOutPt(horz, new Point64(horz.curX, Y));
+
+      OutPt? op;
+      for (; ; )
+      {
+        if (horzIsOpen && IsMaxima(horz) && !IsOpenEnd(horz))
+        {
+          vertex_max = GetCurrYMaximaVertex(horz);
+          if (vertex_max != null)
+            maxPair = GetHorzMaximaPair(horz, vertex_max);
+        }
+
+        // loops through consec. horizontal edges (if open)
+        Active? ae;
+        if (isLeftToRight) ae = horz.nextInAEL;
+        else ae = horz.prevInAEL;
+
+        while (ae != null)
+        {
+          if (ae == maxPair)
+          {
+            if (IsHotEdge(horz))
+            {
+              while (horz.vertexTop != ae.vertexTop)
+              {
+                AddOutPt(horz, horz.top);
+                UpdateEdgeIntoAEL(horz);
+              }
+              op = AddLocalMaxPoly(horz, ae, horz.top);
+              if (op != null && !IsOpen(horz) && op.pt == horz.top)
+                AddTrialHorzJoin(op);
+            }
+
+            DeleteFromAEL(ae);
+            DeleteFromAEL(horz);
+            return;
+          }
+
+          // if horzEdge is a maxima, keep going until we reach
+          // its maxima pair, otherwise check for break conditions
+          if (vertex_max != horz.vertexTop || IsOpenEnd(horz))
+          {
+            // otherwise stop when 'ae' is beyond the end of the horizontal line
+            if ((isLeftToRight && ae.curX > rightX) ||
+                (!isLeftToRight && ae.curX < leftX)) break;
+
+            if (ae.curX == horz.top.X && !IsHorizontal(ae))
+            {
+              pt = NextVertex(horz).pt;
+              if (isLeftToRight)
+              {
+                // with open paths we'll only break once past horz's end
+                if (IsOpen(ae) && !IsSamePolyType(ae, horz) && !IsHotEdge(ae))
+                {
+                  if (TopX(ae, pt.Y) > pt.X) break;
+                }
+                // otherwise we'll only break when horz's outslope is greater than e's
+                else if (TopX(ae, pt.Y) >= pt.X) break;
+              }
+              else
+              {
+                // with open paths we'll only break once past horz's end
+                if (IsOpen(ae) && !IsSamePolyType(ae, horz) && !IsHotEdge(ae))
+                {
+                  if (TopX(ae, pt.Y) < pt.X) break;
+                }
+                // otherwise we'll only break when horz's outslope is greater than e's
+                else if (TopX(ae, pt.Y) <= pt.X) break;
+              }
+            }
+          }
+
+          pt = new Point64(ae.curX, Y);
+
+          if (isLeftToRight)
+          {
+            op = IntersectEdges(horz, ae, pt);
+            SwapPositionsInAEL(horz, ae);
+
+            if (IsHotEdge(horz) && op != null && 
+              !IsOpen(horz) && op.pt == pt) 
+              AddTrialHorzJoin(op);
+
+            if (!IsHorizontal(ae) && TestJoinWithPrev1(ae))
+            {
+              op = AddOutPt(ae.prevInAEL!, pt);
+              OutPt op2 = AddOutPt(ae, pt);
+              AddJoin(op, op2);
+            }
+
+            horz.curX = ae.curX;
+            ae = horz.nextInAEL;
+          }
+          else
+          {
+            op = IntersectEdges(ae, horz, pt);
+            SwapPositionsInAEL(ae, horz);
+
+            if (IsHotEdge(horz) && op != null &&
+              !IsOpen(horz) && op.pt == pt)
+              AddTrialHorzJoin(op);
+
+            if (!IsHorizontal(ae) && TestJoinWithNext1(ae))
+            {
+              op = AddOutPt(ae, pt);
+              OutPt op2 = AddOutPt(ae.nextInAEL!, pt);
+              AddJoin(op, op2);
+            }
+
+            horz.curX = ae.curX;
+            ae = horz.prevInAEL;
+          }
+        } // we've reached the end of this horizontal
+
+        // check if we've finished looping through consecutive horizontals
+        if (horzIsOpen && IsOpenEnd(horz))
+        {
+          if (IsHotEdge(horz))
+          {
+            AddOutPt(horz, horz.top);
+            if (IsFront(horz))
+              horz.outrec!.frontEdge = null;
+            else
+              horz.outrec!.backEdge = null;
+          }
+          horz.outrec = null;
+          DeleteFromAEL(horz); // ie open at top
+          return;
+        }
+
+        if (NextVertex(horz).pt.Y != horz.top.Y) break;
+
+        // there must be a following (consecutive) horizontal
+        if (IsHotEdge(horz))
+          AddOutPt(horz, horz.top);
+        UpdateEdgeIntoAEL(horz);
+
+        if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz))
+          TrimHorz(horz, true);
+
+        isLeftToRight = ResetHorzDirection(horz, maxPair, out leftX, out rightX);
+
+      } // end for loop and end of (possible consecutive) horizontals
+
+      if (IsHotEdge(horz))
+      {
+        op = AddOutPt(horz, horz.top);
+        if (!IsOpen(horz))
+          AddTrialHorzJoin(op);
+      }
+      else
+        op = null;
+
+      if ((horzIsOpen && !IsOpenEnd(horz)) ||
+        (!horzIsOpen && vertex_max != horz.vertexTop))
+      {
+        UpdateEdgeIntoAEL(horz); // this is the end of an intermediate horiz.
+        if (IsOpen(horz)) return;
+
+        if (isLeftToRight && TestJoinWithNext1(horz))
+        {
+          OutPt op2 = AddOutPt(horz.nextInAEL!, horz.bot);
+          AddJoin(op!, op2);
+        }
+        else if (!isLeftToRight && TestJoinWithPrev1(horz))
+        {
+          OutPt op2 = AddOutPt(horz.prevInAEL!, horz.bot);
+          AddJoin(op2, op!);
+        }
+      }
+      else if (IsHotEdge(horz)) 
+        AddLocalMaxPoly(horz, maxPair!, horz.top);
+      else
+      {
+        DeleteFromAEL(maxPair!);
+        DeleteFromAEL(horz);
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DoTopOfScanbeam(long y)
+    {
+      _sel = null; // sel_ is reused to flag horizontals (see PushHorz below)
+      Active? ae = _actives;
+      while (ae != null)
+      {
+        // NB 'ae' will never be horizontal here
+        if (ae.top.Y == y)
+        {
+          ae.curX = ae.top.X;
+          if (IsMaxima(ae))
+          {
+            ae = DoMaxima(ae); // TOP OF BOUND (MAXIMA)
+            continue;
+          }
+
+          // INTERMEDIATE VERTEX ...
+          if (IsHotEdge(ae))
+            AddOutPt(ae, ae.top);
+          UpdateEdgeIntoAEL(ae);
+          if (IsHorizontal(ae))
+            PushHorz(ae); // horizontals are processed later
+        }
+        else // i.e. not the top of the edge
+          ae.curX = TopX(ae, y);
+
+        ae = ae.nextInAEL;
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private Active? DoMaxima(Active ae)
+    {
+      Active? prevE;
+      Active? nextE, maxPair;
+      prevE = ae.prevInAEL;
+      nextE = ae.nextInAEL;
+
+      if (IsOpenEnd(ae))
+      {
+        if (IsHotEdge(ae)) AddOutPt(ae, ae.top);
+        if (!IsHorizontal(ae))
+        {
+          if (IsHotEdge(ae))
+          {
+            if (IsFront(ae))
+              ae.outrec!.frontEdge = null;
+            else
+              ae.outrec!.backEdge = null;
+            ae.outrec = null;
+          }
+          DeleteFromAEL(ae);
+        }
+        return nextE;
+      }
+
+      maxPair = GetMaximaPair(ae);
+      if (maxPair == null) return nextE; // eMaxPair is horizontal
+
+      // only non-horizontal maxima here.
+      // process any edges between maxima pair ...
+      while (nextE != maxPair)
+      {
+        IntersectEdges(ae, nextE!, ae.top);
+        SwapPositionsInAEL(ae, nextE!);
+        nextE = ae.nextInAEL;
+      }
+
+      if (IsOpen(ae))
+      {
+        if (IsHotEdge(ae))
+          AddLocalMaxPoly(ae, maxPair, ae.top);
+        DeleteFromAEL(maxPair);
+        DeleteFromAEL(ae);
+        return (prevE != null ? prevE.nextInAEL : _actives);
+      }
+
+      // here ae.nextInAel == ENext == EMaxPair ...
+      if (IsHotEdge(ae))
+        AddLocalMaxPoly(ae, maxPair, ae.top);
+
+      DeleteFromAEL(ae);
+      DeleteFromAEL(maxPair);
+      return (prevE != null ? prevE.nextInAEL : _actives);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsValidPath(OutPt op)
+    {
+      return (op.next != op);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool PtsReallyClose(Point64 pt1, Point64 pt2)
+    {
+      return (Math.Abs(pt1.X - pt2.X) < 2) && (Math.Abs(pt1.Y - pt2.Y) < 2);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsVerySmallTriangle(OutPt op)
+	  {
+		  return op.next!.next == op.prev &&
+			(PtsReallyClose(op.prev.pt, op.next.pt) ||
+				PtsReallyClose(op.pt, op.next.pt) ||
+				PtsReallyClose(op.pt, op.prev.pt));
+	  }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsValidClosedPath(OutPt? op)
+    {
+      return (op != null && op.next != op &&
+        (op.next != op.prev || !IsVerySmallTriangle(op)));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool ValueBetween(long val, long end1, long end2)
+    {
+      // NB accommodates axis aligned between where end1 == end2
+      return ((val != end1) == (val != end2)) &&
+        ((val > end1) == (val < end2));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool ValueEqualOrBetween(long val, long end1, long end2)
+    {
+      return (val == end1) || (val == end2) || ((val > end1) == (val < end2));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool PointEqualOrBetween(Point64 pt, Point64 corner1, Point64 corner2)
+    {
+      // NB points may not be collinear
+      return
+        ValueEqualOrBetween(pt.X, corner1.X, corner2.X) &&
+        ValueEqualOrBetween(pt.Y, corner1.Y, corner2.Y);
+    }
+
+    private static bool PointBetween(Point64 pt, Point64 corner1, Point64 corner2)
+    {
+      // NB points may not be collinear
+      return
+        ValueBetween(pt.X, corner1.X, corner2.X) &&
+        ValueBetween(pt.Y, corner1.Y, corner2.Y);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool CollinearSegsOverlap(Point64 seg1a, Point64 seg1b,
+      Point64 seg2a, Point64 seg2b)
+    {
+      // precondition: seg1 and seg2 are collinear      
+      if (seg1a.X == seg1b.X)
+      {
+        if (seg2a.X != seg1a.X || seg2a.X != seg2b.X) return false;
+      }
+      else if (seg1a.X < seg1b.X)
+      {
+        if (seg2a.X < seg2b.X)
+        {
+          if (seg2a.X >= seg1b.X || seg2b.X <= seg1a.X) return false;
+        }
+        else
+        {
+          if (seg2b.X >= seg1b.X || seg2a.X <= seg1a.X) return false;
+        }
+      }
+      else
+      {
+        if (seg2a.X < seg2b.X)
+        {
+          if (seg2a.X >= seg1a.X || seg2b.X <= seg1b.X) return false;
+        }
+        else
+        {
+          if (seg2b.X >= seg1a.X || seg2a.X <= seg1b.X) return false;
+        }
+      }
+
+      if (seg1a.Y == seg1b.Y)
+      {
+        if (seg2a.Y != seg1a.Y || seg2a.Y != seg2b.Y) return false;
+      }
+      else if (seg1a.Y < seg1b.Y)
+      {
+        if (seg2a.Y < seg2b.Y)
+        {
+          if (seg2a.Y >= seg1b.Y || seg2b.Y <= seg1a.Y) return false;
+        }
+        else
+        {
+          if (seg2b.Y >= seg1b.Y || seg2a.Y <= seg1a.Y) return false;
+        }
+      }
+      else
+      {
+        if (seg2a.Y < seg2b.Y)
+        {
+          if (seg2a.Y >= seg1a.Y || seg2b.Y <= seg1b.Y) return false;
+        }
+        else
+        {
+          if (seg2b.Y >= seg1a.Y || seg2a.Y <= seg1b.Y) return false;
+        }
+      }
+      return true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool HorzEdgesOverlap(long x1a, long x1b, long x2a, long x2b)
+    {
+      const long minOverlap = 2;
+      if (x1a > x1b + minOverlap)
+      {
+        if (x2a > x2b + minOverlap)
+          return !((x1a <= x2b) || (x2a <= x1b));
+        return !((x1a <= x2a) || (x2b <= x1b));
+      }
+
+      if (x1b > x1a + minOverlap)
+      {
+        if (x2a > x2b + minOverlap)
+          return !((x1b <= x2b) || (x2a <= x1a));
+        return !((x1b <= x2a) || (x2b <= x1a));
+      }
+      return false;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Joiner? GetHorzTrialParent(OutPt op)
+    {
+      Joiner? joiner = op.joiner;
+      while (joiner != null)
+      {
+        if (joiner.op1 == op)
+        {
+          if (joiner.next1 != null &&
+              joiner.next1.idx < 0) return joiner;
+          joiner = joiner.next1;
+        }
+        else
+        {
+          if (joiner.next2 != null &&
+              joiner.next2.idx < 0) return joiner;
+          joiner = joiner.next1;
+        }
+      }
+      return joiner;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool OutPtInTrialHorzList(OutPt op)
+    {
+      return op.joiner != null &&
+       ((op.joiner.idx < 0) || GetHorzTrialParent(op) != null);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool ValidateClosedPathEx(ref OutPt? op)
+    {
+      if (IsValidClosedPath(op)) return true;
+      if (op != null)
+        SafeDisposeOutPts(ref op);
+      return false;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static OutPt InsertOp(Point64 pt, OutPt insertAfter)
+    {
+      OutPt result = new OutPt(pt, insertAfter.outrec)
+      { next = insertAfter.next };
+      insertAfter.next!.prev = result;
+      insertAfter.next = result;
+      result.prev = insertAfter;
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static OutPt? DisposeOutPt(OutPt op)
+    {
+      OutPt? result = (op.next == op ? null : op.next);
+      op.prev.next = op.next;
+      op.next!.prev = op.prev;
+      // op == null;
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void SafeDisposeOutPts(ref OutPt op)
+    {
+      OutRec? outRec = GetRealOutRec(op.outrec);
+      if (outRec!.frontEdge != null)
+        outRec.frontEdge.outrec = null;
+      if (outRec.backEdge != null)
+        outRec.backEdge.outrec = null;
+
+      op.prev.next = null;
+      OutPt? op2 = op;
+      while (op2 != null)
+      {
+        SafeDeleteOutPtJoiners(op2);
+        op2 = op2.next;
+      }
+      outRec.pts = null;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void SafeDeleteOutPtJoiners(OutPt op)
+    {
+      Joiner? joiner = op.joiner;
+      if (joiner == null) return;
+
+      while (joiner != null)
+      {
+        if (joiner.idx < 0)
+          DeleteTrialHorzJoin(op);
+        else if (_horzJoiners != null)
+        {
+          if (OutPtInTrialHorzList(joiner.op1))
+            DeleteTrialHorzJoin(joiner.op1);
+          if (OutPtInTrialHorzList(joiner.op2!))
+            DeleteTrialHorzJoin(joiner.op2!);
+          DeleteJoin(joiner);
+        }
+        else
+          DeleteJoin(joiner);
+        joiner = op.joiner;
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void AddTrialHorzJoin(OutPt op)
+    {
+      // make sure 'op' isn't added more than once
+      if (!op.outrec.isOpen && !OutPtInTrialHorzList(op))
+        _horzJoiners = new Joiner(op, null, _horzJoiners);
+
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Joiner? FindTrialJoinParent(ref Joiner joiner, OutPt op)
+    {
+      Joiner? parent = joiner;
+      while (parent != null)
+      {
+        if (op == parent.op1)
+        {
+          if (parent.next1 != null && parent.next1.idx < 0)
+          {
+            joiner = parent.next1;
+            return parent;
+          }
+          parent = parent.next1;
+        }
+        else
+        {
+          if (parent.next2 != null && parent.next2.idx < 0)
+          {
+            joiner = parent.next2;
+            return parent;
+          }
+          parent = parent.next2;
+        }
+      }
+      return null;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DeleteTrialHorzJoin(OutPt op)
+    {
+      if (_horzJoiners == null) return;
+
+      Joiner? joiner = op.joiner;
+      Joiner? parentH, parentOp = null;
+      while (joiner != null)
+      {
+        if (joiner.idx < 0)
+        {
+          // first remove joiner from FHorzTrials
+          if (joiner == _horzJoiners)
+            _horzJoiners = joiner.nextH;
+          else
+          {
+            parentH = _horzJoiners;
+            while (parentH!.nextH != joiner)
+              parentH = parentH.nextH;
+            parentH.nextH = joiner.nextH;
+          }
+
+          // now remove joiner from op's joiner list
+          if (parentOp == null)
+          {
+            // joiner must be first one in list
+            op.joiner = joiner.next1;
+            // joiner == null;
+            joiner = op.joiner;
+          }
+          else
+          {
+            // the trial joiner isn't first
+            if (op == parentOp.op1)
+              parentOp.next1 = joiner.next1;
+            else
+              parentOp.next2 = joiner.next1;
+            // joiner = null;
+            joiner = parentOp;
+          }
+        }
+        else
+        {
+          // not a trial join so look further along the linked list
+          parentOp = FindTrialJoinParent(ref joiner, op);
+          if (parentOp == null) break;
+        }
+        // loop in case there's more than one trial join
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool GetHorzExtendedHorzSeg(ref OutPt op, out OutPt op2)
+    {
+      OutRec outRec = GetRealOutRec(op.outrec)!;
+      op2 = op;
+      if (outRec.frontEdge != null)
+      {
+        while (op.prev != outRec.pts &&
+          op.prev.pt.Y == op.pt.Y) op = op.prev;
+        while (op2 != outRec.pts &&
+          op2.next!.pt.Y == op2.pt.Y) op2 = op2.next;
+        return op2 != op;
+      }
+
+      while (op.prev != op2 && op.prev.pt.Y == op.pt.Y)
+        op = op.prev;
+      while (op2.next != op && op2.next!.pt.Y == op2.pt.Y)
+        op2 = op2.next;
+      return op2 != op && op2.next != op;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void ConvertHorzTrialsToJoins()
+    {
+      while (_horzJoiners != null)
+      {
+        Joiner? joiner = _horzJoiners;
+        _horzJoiners = _horzJoiners.nextH;
+        OutPt op1a = joiner.op1;
+        if (op1a.joiner == joiner)
+        {
+          op1a.joiner = joiner.next1;
+        }
+        else
+        {
+          Joiner joinerParent = FindJoinParent(joiner, op1a);
+          if (joinerParent.op1 == op1a)
+            joinerParent.next1 = joiner.next1;
+          else
+            joinerParent.next2 = joiner.next1;
+        }
+        // joiner = null;
+
+        if (!GetHorzExtendedHorzSeg(ref op1a, out OutPt op1b))
+        {
+          if (op1a.outrec.frontEdge == null)
+            CleanCollinear(op1a.outrec);
+          continue;
+        }
+
+        OutPt op2a;
+        bool joined = false;
+        joiner = _horzJoiners;
+        while (joiner != null)
+        {
+          op2a = joiner.op1;
+          if (GetHorzExtendedHorzSeg(ref op2a, out OutPt op2b) &&
+            HorzEdgesOverlap(op1a.pt.X, op1b.pt.X, op2a.pt.X, op2b.pt.X))
+          {
+            // overlap found so promote to a 'real' join
+            joined = true;
+            if (op1a.pt == op2b.pt)
+              AddJoin(op1a, op2b);
+            else if (op1b.pt == op2a.pt)
+              AddJoin(op1b, op2a);
+            else if (op1a.pt == op2a.pt)
+              AddJoin(op1a, op2a);
+            else if (op1b.pt == op2b.pt)
+              AddJoin(op1b, op2b);
+            else if (ValueBetween(op1a.pt.X, op2a.pt.X, op2b.pt.X))
+              AddJoin(op1a, InsertOp(op1a.pt, op2a));
+            else if (ValueBetween(op1b.pt.X, op2a.pt.X, op2b.pt.X))
+              AddJoin(op1b, InsertOp(op1b.pt, op2a));
+            else if (ValueBetween(op2a.pt.X, op1a.pt.X, op1b.pt.X))
+              AddJoin(op2a, InsertOp(op2a.pt, op1a));
+            else if (ValueBetween(op2b.pt.X, op1a.pt.X, op1b.pt.X))
+              AddJoin(op2b, InsertOp(op2b.pt, op1a));
+            break;
+          }
+          joiner = joiner.nextH;
+        }
+        if (!joined)
+          CleanCollinear(op1a.outrec);
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void AddJoin(OutPt op1, OutPt op2)
+    {
+      if ((op1.outrec == op2.outrec) && ((op1 == op2) ||
+        // unless op1.next or op1.prev crosses the start-end divide
+        // don't waste time trying to join adjacent vertices
+        ((op1.next == op2) && (op1 != op1.outrec.pts)) ||
+        ((op2.next == op1) && (op2 != op1.outrec.pts)))) return;
+
+      Joiner joiner = new Joiner(op1, op2, null) {idx = _joinerList.Count};
+      _joinerList.Add(joiner);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Joiner FindJoinParent(Joiner joiner, OutPt op)
+    {
+      Joiner result = op.joiner!;
+      for (; ; )
+      {
+        if (op == result.op1)
+        {
+          if (result.next1 == joiner) return result;
+          result = result.next1!;
+        }
+        else
+        {
+          if (result.next2 == joiner) return result;
+          result = result.next2!;
+        }
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DeleteJoin(Joiner joiner)
+    {
+      // This method deletes a single join, and it doesn't check for or
+      // delete trial horz. joins. For that, use the following method.
+      OutPt op1 = joiner.op1, op2 = joiner.op2!;
+
+      Joiner parentJnr;
+      if (op1.joiner != joiner)
+      {
+        parentJnr = FindJoinParent(joiner, op1);
+        if (parentJnr.op1 == op1)
+          parentJnr.next1 = joiner.next1;
+        else
+          parentJnr.next2 = joiner.next1;
+      }
+      else
+        op1.joiner = joiner.next1;
+
+      if (op2.joiner != joiner)
+      {
+        parentJnr = FindJoinParent(joiner, op2);
+        if (parentJnr.op1 == op2)
+          parentJnr.next1 = joiner.next2;
+        else
+          parentJnr.next2 = joiner.next2;
+      }
+      else
+        op2.joiner = joiner.next2;
+
+      _joinerList[joiner.idx] = null;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void ProcessJoinList()
+    {
+      // NB can't use foreach here because list may 
+      // contain nulls which can't be enumerated
+      for (int i = 0; i < _joinerList.Count; i++)
+      {
+        Joiner? j = _joinerList[i];
+        if (j == null) continue;
+        OutRec outrec = ProcessJoin(j);
+        CleanCollinear(outrec);
+      }
+      _joinerList.Clear();
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool CheckDisposeAdjacent(ref OutPt op, OutPt guard, OutRec outRec)
+    {
+      bool result = false;
+      while (op.prev != op)
+      {
+        if (op.pt == op.prev.pt && op != guard &&
+          op.prev.joiner != null && op.joiner == null)
+        {
+          if (op == outRec.pts) outRec.pts = op.prev;
+          op = DisposeOutPt(op)!;
+          op = op.prev;
+        }
+        else
+          break;
+      }
+
+      while (op.next != op)
+      {
+        if (op.pt == op.next!.pt && op != guard &&
+        op.next.joiner != null && op.joiner == null)
+        {
+          if (op == outRec.pts) outRec.pts = op.prev;
+          op = DisposeOutPt(op)!;
+          op = op.prev;
+        }
+        else
+          break;
+      }
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static double DistanceFromLineSqrd(Point64 pt, Point64 linePt1, Point64 linePt2)
+    {
+      // perpendicular distance of point (x0,y0) = (a*x0 + b*y0 + C)/Sqrt(a*a + b*b)
+      // where ax + by +c = 0 is the equation of the line
+      // see https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
+      double a = (linePt1.Y - linePt2.Y);
+      double b = (linePt2.X - linePt1.X);
+      double c = a * linePt1.X + b * linePt1.Y;
+      double q = a * pt.X + b * pt.Y - c;
+      return (q * q) / (a * a + b * b);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static double DistanceSqr(Point64 pt1, Point64 pt2)
+    {
+      return (double) (pt1.X - pt2.X) * (pt1.X - pt2.X) +
+             (double) (pt1.Y - pt2.Y) * (pt1.Y - pt2.Y);
+    }
+
+    private OutRec ProcessJoin(Joiner j)
+    {
+      OutPt op1 = j.op1, op2 = j.op2!;
+      OutRec or1 = GetRealOutRec(op1.outrec)!;
+      OutRec or2 = GetRealOutRec(op2.outrec)!;
+      DeleteJoin(j);
+
+      if (or2.pts == null) return or1;
+      if (!IsValidClosedPath(op2))
+      {
+        SafeDisposeOutPts(ref op2);
+        return or1;
+      }
+      if ((or1.pts == null) || !IsValidClosedPath(op1))
+      {
+        SafeDisposeOutPts(ref op1);
+        return or2;
+      }
+      if (or1 == or2 &&
+          ((op1 == op2) || (op1.next == op2) || (op1.prev == op2))) return or1;
+
+      CheckDisposeAdjacent(ref op1, op2, or1);
+      CheckDisposeAdjacent(ref op2, op1, or2);
+      if (op1.next == op2 || op2.next == op1) return or1;
+
+      OutRec result = or1;
+      for (; ; )
+      {
+        if (!IsValidPath(op1) || !IsValidPath(op2) ||
+          (or1 == or2 && (op1.prev == op2 || op1.next == op2))) return or1;
+
+        if (op1.prev.pt == op2.next!.pt ||
+            ((InternalClipper.CrossProduct(op1.prev.pt, op1.pt, op2.next.pt) == 0) &&
+             CollinearSegsOverlap(op1.prev.pt, op1.pt, op2.pt, op2.next.pt)))
+        {
+          if (or1 == or2)
+          {
+            // SPLIT REQUIRED
+            // make sure op1.prev and op2.next match positions
+            // by inserting an extra vertex if needed
+            if (op1.prev.pt != op2.next.pt)
+            {
+              if (PointEqualOrBetween(op1.prev.pt, op2.pt, op2.next.pt))
+                op2.next = InsertOp(op1.prev.pt, op2);
+              else
+                op1.prev = InsertOp(op2.next.pt, op1.prev);
+            }
+
+            // current              to     new
+            // op1.p[opA] >>> op1   ...    opA \   / op1
+            // op2.n[opB] <<< op2   ...    opB /   \ op2
+            OutPt opA = op1.prev, opB = op2.next;
+            opA.next = opB;
+            opB.prev = opA;
+            op1.prev = op2;
+            op2.next = op1;
+            CompleteSplit(op1, opA, or1);
+          }
+          else
+          {
+            // JOIN, NOT SPLIT
+            OutPt opA = op1.prev, opB = op2.next;
+            opA.next = opB;
+            opB.prev = opA;
+            op1.prev = op2;
+            op2.next = op1;
+
+            //SafeDeleteOutPtJoiners(op2);
+            //DisposeOutPt(op2);
+
+            if (or1.idx < or2.idx)
+            {
+              or1.pts = op1;
+              or2.pts = null;
+              if (or1.owner != null &&
+                (or2.owner == null || or2.owner.idx < or1.owner.idx))
+              {
+                or1.owner = or2.owner;
+              }
+              or2.owner = or1;
+            }
+            else
+            {
+              result = or2;
+              or2.pts = op1;
+              or1.pts = null;
+              if (or2.owner != null &&
+                (or1.owner == null || or1.owner.idx < or2.owner.idx))
+              {
+                or2.owner = or1.owner;
+              }
+              or1.owner = or2;
+            }
+          }
+          break;
+        }
+        if (op1.next!.pt == op2.prev.pt ||
+                 ((InternalClipper.CrossProduct(op1.next.pt, op2.pt, op2.prev.pt) == 0) &&
+                  CollinearSegsOverlap(op1.next.pt, op1.pt, op2.pt, op2.prev.pt)))
+        {
+          if (or1 == or2) 
+          {
+            // SPLIT REQUIRED
+            // make sure op2.prev and op1.next match positions
+            // by inserting an extra vertex if needed
+            if (op2.prev.pt != op1.next.pt)
+            {
+              if (PointEqualOrBetween(op2.prev.pt, op1.pt, op1.next.pt))
+                op1.next = InsertOp(op2.prev.pt, op1);
+              else
+                op2.prev = InsertOp(op1.next.pt, op2.prev);
+            }
+
+            // current              to     new
+            // op2.p[opA] >>> op2   ...    opA \   / op2
+            // op1.n[opB] <<< op1   ...    opB /   \ op1
+            OutPt opA = op2.prev, opB = op1.next;
+            opA.next = opB;
+            opB.prev = opA;
+            op2.prev = op1;
+            op1.next = op2;
+            CompleteSplit(op1, opA, or1);
+          }
+          else
+          {
+            // JOIN, NOT SPLIT
+            OutPt opA = op1.next, opB = op2.prev;
+            opA.prev = opB;
+            opB.next = opA;
+            op1.next = op2;
+            op2.prev = op1;
+
+            //SafeDeleteOutPtJoiners(op2);
+            //DisposeOutPt(op2);
+
+            if (or1.idx < or2.idx)
+            {
+              or1.pts = op1;
+              or2.pts = null;
+              if (or1.owner != null &&
+                (or2.owner == null || or2.owner.idx < or1.owner.idx))
+              {
+                or1.owner = or2.owner;
+              }
+              or2.owner = or1;
+            }
+            else
+            {
+              result = or2;
+              or2.pts = op1;
+              or1.pts = null;
+              if (or2.owner != null &&
+                (or1.owner == null || or1.owner.idx < or2.owner.idx))
+              {
+                or2.owner = or1.owner;
+              }
+              or1.owner = or2;
+            }
+          }
+          break;
+        }
+
+        if (PointBetween(op1.next.pt, op2.pt, op2.prev.pt) &&
+                 DistanceFromLineSqrd(op1.next.pt, op2.pt, op2.prev.pt) < 2.01)
+        {
+          InsertOp(op1.next.pt, op2.prev);
+          continue;
+        }
+        if (PointBetween(op2.next.pt, op1.pt, op1.prev.pt) &&
+                 DistanceFromLineSqrd(op2.next.pt, op1.pt, op1.prev.pt) < 2.01)
+        {
+          InsertOp(op2.next.pt, op1.prev);
+          continue;
+        }
+        if (PointBetween(op1.prev.pt, op2.pt, op2.next.pt) &&
+                 DistanceFromLineSqrd(op1.prev.pt, op2.pt, op2.next.pt) < 2.01)
+        {
+          InsertOp(op1.prev.pt, op2);
+          continue;
+        }
+        if (PointBetween(op2.prev.pt, op1.pt, op1.next.pt) &&
+                 DistanceFromLineSqrd(op2.prev.pt, op1.pt, op1.next.pt) < 2.01)
+        {
+          InsertOp(op2.prev.pt, op1);
+          continue;
+        }
+
+        // something odd needs tidying up
+        if (CheckDisposeAdjacent(ref op1, op2, or1)) continue;
+        if (CheckDisposeAdjacent(ref op2, op1, or1)) continue;
+        if (op1.prev.pt != op2.next!.pt &&
+          (DistanceSqr(op1.prev.pt, op2.next.pt) < 2.01))
+        {
+          op1.prev.pt = op2.next.pt;
+          continue;
+        }
+        if (op1.next!.pt != op2.prev.pt &&
+          (DistanceSqr(op1.next.pt, op2.prev.pt) < 2.01))
+        {
+          op2.prev.pt = op1.next.pt;
+          continue;
+        }
+        // OK, there doesn't seem to be a way to join after all
+        // so just tidy up the polygons
+        or1.pts = op1;
+        if (or2 != or1)
+        {
+          or2.pts = op2;
+          CleanCollinear(or2);
+        }
+        break;
+      }
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static void UpdateOutrecOwner(OutRec outrec)
+    {
+      OutPt opCurr = outrec.pts!;
+      for (;;)
+      {
+        opCurr.outrec = outrec;
+        opCurr = opCurr.next!;
+        if (opCurr == outrec.pts) return;
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void CompleteSplit(OutPt? op1, OutPt? op2, OutRec outrec)
+    {
+      double area1 = Area(op1!);
+      double area2 = Area(op2!);
+      bool signs_change = (area1 > 0) == (area2 < 0);
+
+      // delete trivial splits (with zero or almost zero areas)
+      if (area1 == 0 || (signs_change && Math.Abs(area1) < 2))
+      {
+        SafeDisposeOutPts(ref op1!);
+        outrec.pts = op2;
+      }
+      else if (area2 == 0 || (signs_change && Math.Abs(area2) < 2))
+      {
+        SafeDisposeOutPts(ref op2!);
+        outrec.pts = op1;
+      }
+      else
+      {
+        OutRec newOr = new OutRec() { idx = _outrecList.Count };
+        _outrecList.Add(newOr);
+        newOr.polypath = null;
+
+        if (_using_polytree)
+        {
+          outrec.splits ??= new List<OutRec>();
+          outrec.splits.Add(newOr);
+        }
+
+        if (Math.Abs(area1) >= Math.Abs(area2))
+        {
+          outrec.pts = op1;
+          newOr.pts = op2;
+        }
+        else
+        {
+          outrec.pts = op2;
+          newOr.pts = op1;
+        }
+
+        if ((area1 > 0) == (area2 > 0))
+          newOr.owner = outrec.owner;
+        else
+          newOr.owner = outrec;
+
+        UpdateOutrecOwner(newOr);
+        CleanCollinear(newOr);
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void CleanCollinear(OutRec? outrec)
+    {
+      outrec = GetRealOutRec(outrec);
+        if (outrec == null || outrec.isOpen || 
+        outrec.frontEdge != null || !ValidateClosedPathEx(ref outrec.pts)) 
+          return;
+
+      OutPt startOp = outrec.pts!;
+      OutPt? op2 = startOp;
+      for (; ; )
+      {
+        if (op2!.joiner != null) return;
+        // NB if preserveCollinear == true, then only remove 180 deg. spikes
+        if ((InternalClipper.CrossProduct(op2.prev.pt, op2.pt, op2.next!.pt) == 0) &&
+          ((op2.pt == op2.prev.pt) || (op2.pt == op2.next.pt) || !PreserveCollinear ||
+          (InternalClipper.DotProduct(op2.prev.pt, op2.pt, op2.next.pt) < 0)))
+        {
+          if (op2 == outrec.pts)
+            outrec.pts = op2.prev;
+          op2 = DisposeOutPt(op2);
+          if (!ValidateClosedPathEx(ref op2))
+          {
+            outrec.pts = null;
+            return;
+          }
+          startOp = op2!;
+          continue;
+        }
+        op2 = op2.next;
+        if (op2 == startOp) break;
+      }
+      FixSelfIntersects(outrec);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DoSplitOp(OutRec outrec, OutPt splitOp)
+    {
+      // splitOp.prev -> splitOp &&
+      // splitOp.next -> splitOp.next.next are intersecting
+      OutPt prevOp = splitOp.prev;
+      OutPt nextNextOp = splitOp.next!.next!;
+      outrec.pts = prevOp;
+      OutPt result = prevOp;
+
+      InternalClipper.GetIntersectPoint(
+          prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, out PointD ipD);
+      Point64 ip = new Point64(ipD);
+
+#if USINGZ
+      if (_zCallback != null)
+        _zCallback(prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, ref ip);
+#endif
+
+      double area1 = Area(prevOp);
+      double absArea1 = Math.Abs(area1);
+      
+      if (absArea1 < 2)
+      {
+        SafeDisposeOutPts(ref splitOp);
+        return;
+      }
+
+      // nb: area1 is the path's area *before* splitting, whereas area2 is
+      // the area of the triangle containing splitOp & splitOp.next.
+      // So the only way for these areas to have the same sign is if
+      // the split triangle is larger than the path containing prevOp or
+      // if there's more than one self=intersection.
+      double area2 = AreaTriangle(ip, splitOp.pt, splitOp.next.pt);
+      double absArea2 = Math.Abs(area2);
+
+      // de-link splitOp and splitOp.next from the path
+      // while inserting the intersection point
+      if (ip == prevOp.pt || ip == nextNextOp.pt)
+      {
+        nextNextOp.prev = prevOp;
+        prevOp.next = nextNextOp;
+      }
+      else
+      {
+        OutPt newOp2 = new OutPt(ip, prevOp.outrec) { prev = prevOp, next = nextNextOp };
+        nextNextOp.prev = newOp2;
+        prevOp.next = newOp2;
+      }
+
+      SafeDeleteOutPtJoiners(splitOp.next);
+      SafeDeleteOutPtJoiners(splitOp);
+
+      if (absArea2 > 1 &&
+          (absArea2 > absArea1 ||
+           ((area2 > 0) == (area1 > 0))))
+      {
+        OutRec newOutRec = new OutRec();
+        newOutRec.idx = _outrecList.Count;
+        _outrecList.Add(newOutRec);
+        newOutRec.owner = prevOp.outrec.owner;
+        newOutRec.polypath = null;
+        splitOp.outrec = newOutRec;
+        splitOp.next.outrec = newOutRec;
+
+        OutPt newOp = new OutPt(ip, newOutRec) { prev = splitOp.next, next = splitOp };
+        newOutRec.pts = newOp;
+        splitOp.prev = newOp;
+        splitOp.next.next = newOp;
+      }
+      //else { splitOp = null; splitOp.next = null; }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void FixSelfIntersects(OutRec outrec)
+    {
+      OutPt op2 = outrec.pts!;
+      for (; ; )
+      {
+        // triangles can't self-intersect
+        if (op2.prev == op2.next!.next) break;
+        if (InternalClipper.SegsIntersect(op2.prev.pt,
+                op2.pt, op2.next.pt, op2.next.next!.pt))
+        {
+          DoSplitOp(outrec, op2);
+          if (outrec.pts == null) return;
+          op2 = outrec.pts;
+          continue;
+        }
+        else
+          op2 = op2.next;
+        if (op2 == outrec.pts) break;
+      }
+    }
+
+    internal static bool BuildPath(OutPt op, bool reverse, bool isOpen, Path64 path)
+    {
+      if (op.next == op || (!isOpen && op.next == op.prev)) return false;
+      path.Clear();
+
+      Point64 lastPt;
+      OutPt op2;
+      if (reverse)
+      {
+        lastPt = op.pt;
+        op2 = op.prev;
+      }
+      else
+      {
+        op = op.next!;
+        lastPt = op.pt;
+        op2 = op.next!;
+      }
+      path.Add(lastPt);
+        
+      while (op2 != op)
+      {
+        if (op2.pt != lastPt)
+        {
+          lastPt = op2.pt;
+          path.Add(lastPt);
+        }
+        if (reverse)
+          op2 = op2.prev;
+        else
+          op2 = op2.next!;
+      }
+
+      if (path.Count == 3 && IsVerySmallTriangle(op2)) return false;
+      else return true;
+    }
+
+
+    protected bool BuildPaths(Paths64 solutionClosed, Paths64 solutionOpen)
+    {
+      solutionClosed.Clear();
+      solutionOpen.Clear();
+      solutionClosed.Capacity = _outrecList.Count;
+      solutionOpen.Capacity = _outrecList.Count;
+
+      foreach (OutRec outrec in _outrecList)
+      {
+        if (outrec.pts == null) continue;
+
+        Path64 path = new Path64();
+        if (outrec.isOpen)
+        {
+          if (BuildPath(outrec.pts!, ReverseSolution, true, path))
+              solutionOpen.Add(path);
+        }
+        else
+        {
+          // closed paths should always return a Positive orientation
+          // except when ReverseSolution == true
+          if (BuildPath(outrec.pts!, ReverseSolution, false, path))
+            solutionClosed.Add(path);
+        }
+      }
+      return true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool Path1InsidePath2(OutRec or1, OutRec or2)
+    {
+      PointInPolygonResult result;
+      OutPt op = or1.pts!;
+      do
+      {
+        result = InternalClipper.PointInPolygon(op.pt, or2.path);
+        if (result != PointInPolygonResult.IsOn) break;
+        op = op.next!;
+      } while (op != or1.pts);
+      if (result == PointInPolygonResult.IsOn)
+        return Area(op) < Area(or2.pts!);
+      return result == PointInPolygonResult.IsInside;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static Rect64 GetBounds(Path64 path)
+	  {
+		  if (path.Count == 0) return new Rect64();
+      Rect64 result = new Rect64(long.MaxValue, long.MaxValue, -long.MaxValue, -long.MaxValue);
+		  foreach (Point64 pt in path)
+		  {
+			  if (pt.X < result.left) result.left = pt.X;
+			  if (pt.X > result.right) result.right = pt.X;
+			  if (pt.Y < result.top) result.top = pt.Y;
+			  if (pt.Y > result.bottom) result.bottom = pt.Y;
+		  }
+		  return result;
+	  }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool DeepCheckOwner(OutRec outrec, OutRec owner)
+	  {
+      if (owner.bounds.IsEmpty()) 
+        owner.bounds = GetBounds(owner.path);
+      bool isInsideOwnerBounds = owner.bounds.Contains(outrec.bounds);
+
+      // while looking for the correct owner, check the owner's
+      // splits **before** checking the owner itself because
+      // splits can occur internally, and checking the owner
+      // first would miss the inner split's true ownership
+      if (owner.splits != null)
+        foreach (OutRec asplit in owner.splits!)
+        {
+          OutRec? split = GetRealOutRec(asplit);
+          if (split == null || split.idx <= owner.idx || split == outrec) continue;
+          if (split.splits != null && DeepCheckOwner(outrec, split)) return true;
+
+          if (split.path.Count == 0) 
+            BuildPath(split.pts!, ReverseSolution, false, split.path);
+          if (split.bounds.IsEmpty()) split.bounds = GetBounds(split.path);
+
+          if (split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec, split))
+			    {
+				    outrec.owner = split;
+				    return true;
+			    }
+		    }
+
+      // only continue when not inside recursion
+      if (owner != outrec.owner) return false;
+
+      for (;;)
+      {
+        if (isInsideOwnerBounds && Path1InsidePath2(outrec, outrec.owner!))
+          return true;
+        
+        outrec.owner = outrec.owner!.owner;
+        if (outrec.owner == null) return false;
+        isInsideOwnerBounds = outrec.owner.bounds.Contains(outrec.bounds);
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    protected bool BuildTree(PolyPathNode polytree, Paths64 solutionOpen)
+    {
+      polytree.Clear();
+      solutionOpen.Clear();
+      solutionOpen.Capacity = _outrecList.Count;
+
+      for (int i = 0; i < _outrecList.Count; i++)
+      {
+        OutRec outrec = _outrecList[i];
+        if (outrec.pts == null) continue;
+
+        if (outrec.isOpen)
+        {
+          Path64 open_path = new Path64();
+          if (BuildPath(outrec.pts!, ReverseSolution, true, open_path))
+            solutionOpen.Add(open_path);
+          continue;
+        }
+
+        if (!BuildPath(outrec.pts!, ReverseSolution, false, outrec.path)) continue;
+        if (outrec.bounds.IsEmpty()) outrec.bounds = GetBounds(outrec.path);
+        outrec.owner = GetRealOutRec(outrec.owner);
+        if (outrec.owner != null)
+          DeepCheckOwner(outrec, outrec.owner);
+
+        // swap order if outer/owner paths are preceeded by their inner paths
+        if (outrec.owner != null && outrec.owner.idx > outrec.idx)
+        {
+          int j = outrec.owner.idx;
+          outrec.owner.idx = i;
+          outrec.idx = j;
+          _outrecList[i] = _outrecList[j];
+          _outrecList[j] = outrec;
+          outrec = _outrecList[i];
+          outrec.owner = GetRealOutRec(outrec.owner);
+          BuildPath(outrec.pts!, ReverseSolution, false, outrec.path);
+          if (outrec.bounds.IsEmpty()) outrec.bounds = GetBounds(outrec.path);
+          if (outrec.owner != null)
+            DeepCheckOwner(outrec, outrec.owner);
+        }
+
+        PolyPathNode ownerPP;
+        if (outrec.owner != null && outrec.owner.polypath != null)
+          ownerPP = outrec.owner.polypath;
+        else
+          ownerPP = polytree;
+        outrec.polypath = ownerPP.AddChild(outrec.path);
+      }
+      return true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public Rect64 GetBounds()
+    {
+      Rect64 bounds = Clipper.MaxInvalidRect64;
+      foreach (Vertex t in _vertexList)
+      {
+        Vertex v = t;
+        do
+        {
+          if (v.pt.X < bounds.left) bounds.left = v.pt.X;
+          if (v.pt.X > bounds.right) bounds.right = v.pt.X;
+          if (v.pt.Y < bounds.top) bounds.top = v.pt.Y;
+          if (v.pt.Y > bounds.bottom) bounds.bottom = v.pt.Y;
+          v = v.next!;
+        } while (v != t);
+      }
+      return bounds.IsEmpty() ? new Rect64(0, 0, 0, 0) : bounds;
+    }
+
+  } // ClipperBase class
+
+
+  public class Clipper64 : ClipperBase
+  {
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal new void AddPath(Path64 path, PathType polytype, bool isOpen = false)
+    {
+      base.AddPath(path, polytype, isOpen);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal new void AddPaths(Paths64 paths, PathType polytype, bool isOpen = false)
+    {
+      base.AddPaths(paths, polytype, isOpen);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddSubject(Paths64 paths)
+    {
+      AddPaths(paths, PathType.Subject);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddOpenSubject(Paths64 paths)
+    {
+      AddPaths(paths, PathType.Subject, true);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddClip(Paths64 paths)
+    {
+      AddPaths(paths, PathType.Clip);
+    }
+
+    public bool Execute(ClipType clipType, FillRule fillRule,
+        Paths64 solutionClosed, Paths64 solutionOpen)
+    {
+      solutionClosed.Clear();
+      solutionOpen.Clear();
+      try
+      {
+        ExecuteInternal(clipType, fillRule);
+        BuildPaths(solutionClosed, solutionOpen);
+      }
+      catch
+      {
+        _succeeded = false;
+      }
+
+      ClearSolution();
+      return _succeeded;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool Execute(ClipType clipType, FillRule fillRule, Paths64 solutionClosed)
+    {
+      return Execute(clipType, fillRule, solutionClosed, new Paths64());
+    }
+
+    public bool Execute(ClipType clipType, FillRule fillRule, PolyTree64 polytree, Paths64 openPaths)
+    {
+      polytree.Clear();
+      openPaths.Clear();
+      _using_polytree = true;
+      try
+      {
+        ExecuteInternal(clipType, fillRule);
+        BuildTree(polytree, openPaths);
+      }
+      catch
+      {
+        _succeeded = false;
+      }
+
+      ClearSolution();
+      return _succeeded;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool Execute(ClipType clipType, FillRule fillRule, PolyTree64 polytree)
+    {
+      return Execute(clipType, fillRule, polytree, new Paths64());
+    }
+
+#if USINGZ
+    public ZCallback64? ZCallback {
+      get { return this._zCallback; }
+      set { this._zCallback = value; } 
+    }
+#endif
+
+  } // Clipper64 class
+
+  public class ClipperD : ClipperBase
+  {
+    private static string precision_range_error = "Error: Precision is out of range.";
+
+    private readonly double _scale;
+    private readonly double _invScale;
+
+#if USINGZ
+    public delegate void ZCallbackD(PointD bot1, PointD top1,
+        PointD bot2, PointD top2, ref PointD intersectPt);
+
+    public ZCallbackD? ZCallback { get; set; }
+
+    private void CheckZCallback()
+    {
+      if (ZCallback != null)
+        _zCallback = ZCB;
+      else
+        _zCallback = null;
+    }
+#endif
+
+    public ClipperD(int roundingDecimalPrecision = 2)
+    {
+      if (roundingDecimalPrecision < -8 || roundingDecimalPrecision > 8)
+        throw new ClipperLibException(precision_range_error);
+      _scale = Math.Pow(10, roundingDecimalPrecision);
+      _invScale = 1 / _scale;
+    }
+
+#if USINGZ
+    private void ZCB(Point64 bot1, Point64 top1,
+        Point64 bot2, Point64 top2, ref Point64 intersectPt)
+    {
+      // de-scale (x & y)
+      // temporarily convert integers to their initial float values
+      // this will slow clipping marginally but will make it much easier
+      // to understand the coordinates passed to the callback function
+      PointD tmp = new PointD(intersectPt);
+      //do the callback
+      ZCallback?.Invoke(
+        Clipper.ScalePointD(bot1, _invScale),
+        Clipper.ScalePointD(top1, _invScale),
+        Clipper.ScalePointD(bot2, _invScale),
+        Clipper.ScalePointD(top2, _invScale), ref tmp);
+      intersectPt = new Point64(intersectPt.X,
+          intersectPt.Y, tmp.z);
+    }
+#endif
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddPath(PathD path, PathType polytype, bool isOpen = false)
+    {
+      base.AddPath(Clipper.ScalePath64(path, _scale), polytype, isOpen);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddPaths(PathsD paths, PathType polytype, bool isOpen = false)
+    {
+      base.AddPaths(Clipper.ScalePaths64(paths, _scale), polytype, isOpen);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddSubject(PathD path)
+    {
+      AddPath(path, PathType.Subject);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddOpenSubject(PathD path)
+    {
+      AddPath(path, PathType.Subject, true);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddClip(PathD path)
+    {
+      AddPath(path, PathType.Clip);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddSubject(PathsD paths)
+    {
+      AddPaths(paths, PathType.Subject);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddOpenSubject(PathsD paths)
+    {
+      AddPaths(paths, PathType.Subject, true);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void AddClip(PathsD paths)
+    {
+      AddPaths(paths, PathType.Clip);
+    }
+
+    public bool Execute(ClipType clipType, FillRule fillRule,
+        PathsD solutionClosed, PathsD solutionOpen)
+    {
+      Paths64 solClosed64 = new Paths64(), solOpen64 = new Paths64();
+#if USINGZ
+      CheckZCallback();
+#endif
+
+      bool success = true;
+      solutionClosed.Clear();
+      solutionOpen.Clear();
+      try
+      {
+        ExecuteInternal(clipType, fillRule);
+        BuildPaths(solClosed64, solOpen64);
+      }
+      catch
+      {
+        success = false;
+      }
+
+      ClearSolution();
+      if (!success) return false;
+
+      solutionClosed.Capacity = solClosed64.Count;
+      foreach (Path64 path in solClosed64)
+        solutionClosed.Add(Clipper.ScalePathD(path, _invScale));
+      solutionOpen.Capacity = solOpen64.Count;
+      foreach (Path64 path in solOpen64)
+        solutionOpen.Add(Clipper.ScalePathD(path, _invScale));
+
+      return true;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool Execute(ClipType clipType, FillRule fillRule, PathsD solutionClosed)
+    {
+      return Execute(clipType, fillRule, solutionClosed, new PathsD());
+    }
+
+    public bool Execute(ClipType clipType, FillRule fillRule, PolyTreeD polytree, PathsD openPaths)
+    {
+      polytree.Clear();
+      (polytree as PolyPathD).Scale = _scale;
+#if USINGZ
+      CheckZCallback();
+#endif
+      openPaths.Clear();
+      Paths64 oPaths = new Paths64();
+      bool success = true;
+      try
+      {
+        ExecuteInternal(clipType, fillRule);
+        BuildTree(polytree, oPaths);
+      }
+      catch
+      {
+        success = false;
+      }
+      ClearSolution();
+      if (!success) return false;
+      if (oPaths.Count > 0)
+      {
+        openPaths.Capacity = oPaths.Count;        
+        foreach (Path64 path in oPaths)
+          openPaths.Add(Clipper.ScalePathD(path, _invScale));
+      }
+
+      return true;
+    }
+
+    public bool Execute(ClipType clipType, FillRule fillRule, PolyTreeD polytree)
+    {
+      return Execute(clipType, fillRule, polytree, new PathsD());
+    }
+  } // ClipperD class
+
+  public abstract class PolyPathNode : IEnumerable
+  {
+    internal PolyPathNode? _parent;
+    internal List<PolyPathNode> _childs = new List<PolyPathNode>();
+
+    public IEnumerator GetEnumerator()
+    {
+      return new NodeEnumerator(_childs);
+    }
+    private class NodeEnumerator : IEnumerator
+    {
+      private int position = -1;
+      private readonly List<PolyPathNode> _nodes;
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      public NodeEnumerator(List<PolyPathNode> nodes)
+      {
+        _nodes = new List<PolyPathNode>(nodes);
+      }
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      private IEnumerator getEnumerator()
+      {
+        return (IEnumerator) this;
+      }
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      public bool MoveNext()
+      {
+        position++;
+        return (position < _nodes.Count);
+      }
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      public void Reset()
+      {
+        position = -1;
+      }
+
+      public object Current
+      {
+        get
+        {
+          if (position < 0 || position >= _nodes.Count)
+            throw new InvalidOperationException();
+          return _nodes[position];
+        }
+      }
+
+    };
+
+    public bool IsHole => GetIsHole();
+
+    public PolyPathNode(PolyPathNode? parent = null) { _parent = parent; }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private bool GetIsHole()
+    {
+      bool result = true;
+      PolyPathNode? pp = _parent;
+      while (pp != null)
+      {
+        result = !result;
+        pp = pp._parent;
+      }
+
+      return result;
+    }
+
+    public int Count => _childs.Count;
+
+    internal abstract PolyPathNode AddChild(Path64 p);
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Clear()
+    {
+      _childs.Clear();
+    }
+  } // PolyPathBase class
+
+  public class PolyPath64 : PolyPathNode
+  {
+    public Path64? Polygon { get; private set; } // polytree root's polygon == null
+
+    public PolyPath64(PolyPathNode? parent = null) : base(parent) {}
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal override PolyPathNode AddChild(Path64 p)
+    {
+      PolyPathNode newChild = new PolyPath64(this);
+      (newChild as PolyPath64)!.Polygon = p;
+      _childs.Add(newChild);
+      return newChild;
+    }
+
+    [IndexerName("Child")]
+    public PolyPath64 this[int index]
+    {
+      get {
+        if (index < 0 || index >= _childs.Count)
+          throw new InvalidOperationException();
+        return (PolyPath64) _childs[index]; 
+      }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public double Area()
+    {
+      double result = Polygon == null ? 0 : Clipper.Area(Polygon);
+      foreach (PolyPathNode polyPathBase in _childs)
+      {
+        PolyPath64 child = (PolyPath64) polyPathBase;
+        result += child.Area();
+      }
+      return result;
+    }
+  }
+
+  public class PolyPathD : PolyPathNode
+  {
+    internal double Scale { get; set; }
+    public PathD? Polygon { get; private set; }
+
+    public PolyPathD(PolyPathNode? parent = null) : base(parent) {}
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal override PolyPathNode AddChild(Path64 p)
+    {
+      PolyPathNode newChild = new PolyPathD(this);
+      (newChild as PolyPathD)!.Scale = Scale;
+      (newChild as PolyPathD)!.Polygon = Clipper.ScalePathD(p, 1 / Scale);
+      _childs.Add(newChild);
+      return newChild;
+    }
+
+    [IndexerName("Child")]
+    public PolyPathD this[int index]
+    {
+      get
+      {
+        if (index < 0 || index >= _childs.Count)
+          throw new InvalidOperationException();
+        return (PolyPathD) _childs[index];
+      }
+    }
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public double Area()
+    {
+      double result = Polygon == null ? 0 : Clipper.Area(Polygon);
+      foreach (PolyPathNode polyPathBase in _childs)
+      {
+        PolyPathD child = (PolyPathD) polyPathBase;
+        result += child.Area();
+      }
+      return result;
+    }
+  }
+
+  public class PolyTree64 : PolyPath64 {}
+
+  public class PolyTreeD : PolyPathD
+  {
+    public new double Scale => base.Scale;
+  }
+
+  public class ClipperLibException : Exception
+  {
+    public ClipperLibException(string description) : base(description) {}
+  }
+} // namespace

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

@@ -0,0 +1,91 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  15 October 2022                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Minkowski Sum and Difference                                    *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#nullable enable
+using System;
+using System.Collections.Generic;
+
+namespace Clipper2Lib
+{
+  public class Minkowski
+  {
+    private static Paths64 MinkowskiInternal(Path64 pattern, Path64 path, bool isSum, bool isClosed)
+    {
+      int delta = isClosed ? 0 : 1;
+      int patLen = pattern.Count, pathLen = path.Count;
+      Paths64 tmp = new Paths64(pathLen);
+
+      foreach (Point64 pathPt in path)
+      {
+        Path64 path2 = new Path64(patLen);
+        if (isSum)
+        {
+          foreach (Point64 basePt in pattern)
+            path2.Add(pathPt + basePt);
+        }
+        else
+        {
+          foreach (Point64 basePt in pattern)
+            path2.Add(pathPt - basePt);
+        }
+        tmp.Add(path2);
+      }
+
+      Paths64 result = new Paths64((pathLen - delta) * patLen);
+      int g = isClosed ? pathLen - 1 : 0;
+
+      int h = patLen - 1;
+      for (int i = delta; i < pathLen; i++)
+      {
+        for (int j = 0; j < patLen; j++)
+        {
+          Path64 quad = new Path64(4)
+          {
+            tmp[g][h], tmp[i][h], tmp[i][j], tmp[g][j]
+          };
+          if (!Clipper.IsPositive(quad))
+            result.Add(Clipper.ReversePath(quad));
+          else
+            result.Add(quad);
+          h = j;
+        }
+        g = i;
+      }
+      return result;
+    }
+
+    public static Paths64 Sum(Path64 pattern, Path64 path, bool isClosed)
+    {
+      return Clipper.Union(MinkowskiInternal(pattern, path, true, isClosed), FillRule.NonZero);
+    }
+
+    public static PathsD Sum(PathD pattern, PathD path, bool isClosed, int decimalPlaces = 2)
+    {
+      double scale = Math.Pow(10, decimalPlaces);
+      Paths64 tmp = Clipper.Union(MinkowskiInternal(Clipper.ScalePath64(pattern, scale),
+        Clipper.ScalePath64(path, scale), true, isClosed), FillRule.NonZero);
+      return Clipper.ScalePathsD(tmp, 1 / scale);
+    }
+
+    public static Paths64 Diff(Path64 pattern, Path64 path, bool isClosed)
+    {
+      return Clipper.Union(MinkowskiInternal(pattern, path, false, isClosed), FillRule.NonZero);
+    }
+
+    public static PathsD Diff(PathD pattern, PathD path, bool isClosed, int decimalPlaces = 2)
+    {
+      double scale = Math.Pow(10, decimalPlaces);
+      Paths64 tmp = Clipper.Union(MinkowskiInternal(Clipper.ScalePath64(pattern, scale),
+        Clipper.ScalePath64(path, scale), false, isClosed), FillRule.NonZero);
+      return Clipper.ScalePathsD(tmp, 1 / scale);
+    }
+
+  }
+
+} // namespace

+ 541 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.Offset.cs

@@ -0,0 +1,541 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  15 October 2022                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Path Offset (Inflate/Shrink)                                    *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Clipper2Lib
+{
+  public enum JoinType
+  {
+    Square,
+    Round,
+    Miter
+  };
+
+  public enum EndType
+  {
+    Polygon,
+    Joined,
+    Butt,
+    Square,
+    Round
+  };
+
+  public class ClipperOffset
+  {
+
+    private class Group
+    {
+      internal Paths64 _inPaths;
+      internal Path64 _outPath;
+      internal Paths64 _outPaths;
+      internal JoinType _joinType;
+      internal EndType _endType;
+      internal bool _pathsReversed;
+
+      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;
+      }
+    }
+
+    private readonly List<Group> _pathGroups = new List<Group>();
+    private readonly PathD _normals = new PathD();
+    private readonly Paths64 solution = new Paths64();
+    private double _group_delta, _abs_group_delta, _tmpLimit, _stepsPerRad;
+    private JoinType _joinType;
+    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 ClipperOffset(double miterLimit = 2.0, 
+      double arcTolerance = 0.0, bool 
+      preserveCollinear = false, bool reverseSolution = false)
+    {
+      MiterLimit = miterLimit;
+      ArcTolerance = arcTolerance;
+      MergeGroups = true;
+      PreserveCollinear = preserveCollinear;
+      ReverseSolution = reverseSolution;
+    }
+
+    public void Clear()
+    {
+      _pathGroups.Clear();
+    }
+
+    public void AddPath(Path64 path, JoinType joinType, EndType endType)
+    {
+      int cnt = path.Count;
+      if (cnt == 0) return;
+      Paths64 pp = new Paths64(1) { path };
+      AddPaths(pp, joinType, endType);
+    }
+
+    public void AddPaths(Paths64 paths, JoinType joinType, EndType endType)
+    {
+      int cnt = paths.Count;
+      if (cnt == 0) return;
+      _pathGroups.Add(new Group(paths, joinType, endType));
+    }
+
+    public Paths64 Execute(double delta)
+    {
+      solution.Clear();
+      if (Math.Abs(delta) < 0.5)
+      {
+        foreach (Group group in _pathGroups)
+          foreach (Path64 path in group._inPaths)
+            solution.Add(path);
+        return solution;
+      }
+
+      _tmpLimit = (MiterLimit <= 1 ? 2.0 : 2.0 / Clipper.Sqr(MiterLimit));
+
+      foreach (Group group in _pathGroups)
+        DoGroupOffset(group, delta);
+
+      if (MergeGroups && _pathGroups.Count > 0)
+      {
+        // clean up self-intersections ...
+        Clipper64 c = new Clipper64()
+        {
+          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;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static PointD GetUnitNormal(Point64 pt1, Point64 pt2)
+    {
+      double dx = (pt2.X - pt1.X);
+      double dy = (pt2.Y - pt1.Y);
+      if ((dx == 0) && (dy == 0)) return new PointD();
+
+      double f = 1.0 / Math.Sqrt(dx * dx + dy * dy);
+      dx *= f;
+      dy *= f;
+
+      return new PointD(dy, -dx);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static int GetLowestPolygonIdx(Paths64 paths)
+    {
+      Point64 lp = new Point64(0, long.MinValue);
+      int result = -1;
+      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;
+          result = i;
+          lp = pt;
+        }
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static PointD TranslatePoint(PointD pt, double dx, double dy)
+    {
+	    return new PointD(pt.x + dx, pt.y + dy);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static PointD ReflectPoint(PointD pt, PointD pivot)
+    {
+      return new PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool AlmostZero(double value, double epsilon = 0.001)
+    {
+      return Math.Abs(value) < epsilon;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static double Hypotenuse(double x, double y)
+    {
+      return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static PointD NormalizeVector(PointD vec)
+    {
+	    double h = Hypotenuse(vec.x, vec.y);
+	    if (AlmostZero(h)) return new PointD(0,0);
+        double inverseHypot = 1 / h;
+	    return new PointD(vec.x* inverseHypot, vec.y* inverseHypot);
+    }
+
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static PointD GetAvgUnitVector(PointD vec1, PointD vec2)
+    {
+	    return NormalizeVector(new PointD(vec1.x + vec2.x, vec1.y + vec2.y));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static PointD IntersectPoint(PointD pt1a, PointD pt1b, PointD pt2a, PointD pt2b)
+    {
+      if (InternalClipper.IsAlmostZero(pt1a.x - pt1b.x)) //vertical
+      {
+        if (InternalClipper.IsAlmostZero(pt2a.x - pt2b.x)) return new PointD(0, 0);
+        double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
+        double b2 = pt2a.y - m2 * pt2a.x;
+        return new PointD(pt1a.x, m2* pt1a.x + b2);
+      }
+
+      if (InternalClipper.IsAlmostZero(pt2a.x - pt2b.x)) //vertical
+      {
+        double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
+        double b1 = pt1a.y - m1 * pt1a.x;
+        return new PointD(pt2a.x, m1* pt2a.x + b1);
+      }
+      else
+      {
+        double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
+        double b1 = pt1a.y - m1 * pt1a.x;
+        double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
+        double b2 = pt2a.y - m2 * pt2a.x;
+        if (InternalClipper.IsAlmostZero(m1 - m2)) return new PointD(0, 0);
+        double x = (b2 - b1) / (m1 - m2);
+        return new PointD(x, m1 * x + b1);
+      }
+    }
+
+    [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);
+    }
+
+    [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);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DoSquare(Group group, Path64 path, int j, int k)
+    {
+      PointD vec;
+      if (j == k)
+      {
+        vec = new PointD(_normals[0].y, -_normals[0].x);
+      }
+      else
+      {
+        vec = GetAvgUnitVector(
+          new PointD(-_normals[k].y, _normals[k].x),
+          new PointD(_normals[j].y, -_normals[j].x));
+      }
+      
+      // 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);
+
+      // 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);
+      // 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);
+        PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
+        //get the second intersect point through reflecion
+        group._outPath.Add(new Point64(ReflectPoint(pt, ptQ)));
+        group._outPath.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));
+        //get the second intersect point through reflecion
+        group._outPath.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(
+          path[j].X + (_normals[k].x + _normals[j].x) * q,
+          path[j].Y + (_normals[k].y + _normals[j].y) * q));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void DoRound(Group group, 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();
+
+      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++)
+      {
+        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));
+      }
+      group._outPath.Add(GetPerpendic(pt, _normals[j]));
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void BuildNormals(Path64 path)
+    {
+      int cnt = path.Count;
+      _normals.Clear();
+      _normals.Capacity = cnt;
+
+      for (int i = 0; i < cnt - 1; i++)
+        _normals.Add(GetUnitNormal(path[i], path[i + 1]));
+      _normals.Add(GetUnitNormal(path[cnt - 1], path[0]));
+    }
+
+    private void OffsetPoint(Group group, Path64 path, int j, ref int k)
+    {
+      // Let A = change in angle where edges join
+      // A == 0: ie no change in angle (flat join)
+      // A == PI: edges 'spike'
+      // sin(A) < 0: right turning
+      // cos(A) < 0: change in angle is more than 90 degree
+      double sinA = InternalClipper.CrossProduct(_normals[j], _normals[k]);
+      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))
+      {
+        group._outPath.Add(GetPerpendic(path[j], _normals[k]));
+        if (!almostNoAngle) group._outPath.Add(path[j]);
+        group._outPath.Add(GetPerpendic(path[j], _normals[j]));
+      }
+      else //convex
+      {
+        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);
+      }
+
+      k = j;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void OffsetPolygon(Group group, Path64 path)
+    {
+      group._outPath = 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);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private void OffsetOpenJoined(Group group, Path64 path)
+    {
+      OffsetPolygon(group, path);
+      path = Clipper.ReversePath(path);
+      BuildNormals(path);
+      OffsetPolygon(group, path);
+    }
+
+    private void OffsetOpenPath(Group group, Path64 path, EndType endType)
+    {
+      group._outPath = new Path64();
+      int highI = path.Count - 1;
+
+      // 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;
+      }
+
+      // offset the left side going forward
+      for (int i = 1, k = 0; i < highI; i++)
+        OffsetPoint(group, path, i, ref k);
+
+      // reverse normals ...
+      for (int i = highI; i > 0; i--)
+        _normals[i] = new PointD(-_normals[i - 1].x, -_normals[i - 1].y);
+      _normals[0] = _normals[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;
+      }
+
+      // 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);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private static bool IsFullyOpenEndType(EndType et)
+    {
+      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)
+      {
+        // 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;
+      }
+      else
+        group._pathsReversed = false;
+
+      _group_delta = delta;
+      _abs_group_delta = Math.Abs(_group_delta);
+      _joinType = group._joinType;
+
+      // calculate a sensible number of steps (for 360 deg for the given offset
+      if (group._joinType == JoinType.Round || group._endType == EndType.Round)
+      {
+        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;
+      }
+
+      foreach (Path64 p in group._inPaths)
+      {
+        Path64 path = Clipper.StripDuplicates(p, isClosedPaths);
+        int cnt = path.Count;
+        if (cnt == 0 || (cnt < 3 && !IsFullyOpenEndType(group._endType))) continue;
+
+        if (cnt == 1)
+        {
+          group._outPath = new Path64();
+          // single vertex so build a circle or square ...
+          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);
+          }
+          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();
+          }
+          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);
+        }
+      }
+
+      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);
+      }
+      solution.AddRange(group._outPaths);
+      group._outPaths.Clear();
+    }
+  }
+
+} // namespace

+ 564 - 0
polygon.mod/clipper2/CSharp/Clipper2Lib/Clipper.RectClip.cs

@@ -0,0 +1,564 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  FAST rectangular clipping                                       *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+
+namespace Clipper2Lib
+{
+    public class RectClip
+  {
+    protected enum Location
+    {
+      left, top, right, bottom, inside
+    };
+
+    readonly protected Rect64 rect_;
+    readonly protected Point64 mp_;
+    readonly protected Path64 rectPath_;
+    protected Path64 result_;
+    protected Location firstCross_;
+    protected List<Location> startLocs_ = new List<Location>();
+
+    internal RectClip(Rect64 rect)
+    {
+      rect_ = rect;
+      mp_ = rect.MidPoint();
+      rectPath_ = rect_.AsPath();
+      result_ = new Path64();
+      firstCross_ = Location.inside;
+    }
+
+    private static PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2)
+    {
+      PointInPolygonResult result = PointInPolygonResult.IsOn;
+      foreach (Point64 pt in path2)
+      {
+        result = Clipper.PointInPolygon(pt, path1);
+        if (result != PointInPolygonResult.IsOn) break;
+      }
+      return result;
+    }
+
+    private static bool IsClockwise(Location prev, Location curr, 
+      Point64 prevPt, Point64 currPt, Point64 rectMidPoint)
+    {
+      if (AreOpposites(prev, curr))
+        return InternalClipper.CrossProduct(prevPt, rectMidPoint, currPt) < 0;
+      else
+        return HeadingClockwise(prev, curr);
+    }
+
+    private static bool AreOpposites(Location prev, Location curr)
+    {
+      return Math.Abs((int)prev - (int) curr) == 2;
+    }
+
+    private static bool HeadingClockwise(Location prev, Location curr)
+    {
+      return ((int) prev + 1) % 4 == (int) curr;
+    }
+
+    private static Location GetAdjacentLocation(Location loc, bool isClockwise)
+    {
+      int delta = (isClockwise) ? 1 : 3;
+      return (Location)(((int) loc + delta) % 4);
+    }
+
+    private void AddCorner(Location prev, Location curr)
+    {
+      if (HeadingClockwise(prev, curr))
+        result_.Add(rectPath_[(int) prev]);
+      else
+        result_.Add(rectPath_[(int) curr]);
+    }
+
+    private void AddCorner(ref Location loc, bool isClockwise)
+    {
+      if (isClockwise)
+      {
+        result_.Add(rectPath_[(int) loc]);
+        loc = GetAdjacentLocation(loc, true);
+      }
+      else
+      {
+        loc = GetAdjacentLocation(loc, false);
+        result_.Add(rectPath_[(int) loc]);
+      }
+    }
+
+    static protected bool GetLocation(Rect64 rec, Point64 pt, out Location loc) 
+    {
+      if (pt.X == rec.left && pt.Y >= rec.top && pt.Y <= rec.bottom)
+      {
+        loc = Location.left; return false; // pt on rec
+      }
+      if (pt.X == rec.right && pt.Y >= rec.top && pt.Y <= rec.bottom)
+      {
+        loc = Location.right; return false; // pt on rec
+      }
+      if (pt.Y == rec.top && pt.X >= rec.left && pt.X <= rec.right)
+      {
+        loc = Location.top; return false; // pt on rec
+      }
+      if (pt.Y == rec.bottom && pt.X >= rec.left && pt.X <= rec.right)
+      {
+        loc = Location.bottom; return false; // pt on rec
+      }
+      if (pt.X < rec.left) loc = Location.left;
+      else if (pt.X > rec.right) loc = Location.right;
+      else if (pt.Y < rec.top)  loc = Location.top; 
+      else if (pt.Y > rec.bottom) loc = Location.bottom;
+      else loc = Location.inside;
+      return true;
+    }
+
+    static protected bool GetIntersection(Path64 rectPath, Point64 p, Point64 p2, ref Location loc, out Point64 ip)
+    {
+      // gets the pt of intersection between rectPath and segment(p, p2) that's closest to 'p'
+      // when result == false, loc will remain unchanged
+      ip = new Point64(); 
+      switch (loc)
+      {
+        case Location.left:
+          if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip);
+          }
+          else if (p.Y < rectPath[0].Y &&
+            InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip);
+            loc = Location.top;
+          }
+          else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip);
+            loc = Location.bottom;
+          }
+          else return false;
+          break;
+
+        case Location.right:
+          if (InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip);
+          }
+          else if (p.Y < rectPath[0].Y &&
+            InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip);
+            loc = Location.top;
+          }
+          else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip);
+            loc = Location.bottom;
+          }
+          else return false;
+          break;
+
+        case Location.top:
+          if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip);
+          }
+          else if (p.X < rectPath[0].X &&
+            InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip);
+            loc = Location.left;
+          }
+          else if (p.X > rectPath[1].X &&
+            InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip);
+            loc = Location.right;
+          }
+          else return false;
+          break;
+
+        case Location.bottom:
+          if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip);
+          }
+          else if (p.X < rectPath[3].X &&
+            InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip);
+            loc = Location.left;
+          }
+          else if (p.X > rectPath[2].X &&
+            InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip);
+            loc = Location.right;
+          }
+          else return false;
+          break;
+
+        case Location.inside:
+          if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip);
+            loc = Location.left;
+          }
+          else if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip);
+            loc = Location.top;
+          }
+          else if (InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip);
+            loc = Location.right;
+          }
+          else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true))
+          {
+            InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip);
+            loc = Location.bottom;
+          }
+          else return false;
+          break;
+      }
+      return true;
+    }
+
+    protected void GetNextLocation(Path64 path,
+      ref Location loc, ref int i, int highI)
+    {
+      switch (loc)
+      {
+        case Location.left:
+          {
+            while (i <= highI && path[i].X <= rect_.left) i++;
+            if (i > highI) break;
+            if (path[i].X >= rect_.right) loc = Location.right;
+            else if (path[i].Y <= rect_.top) loc = Location.top;
+            else if (path[i].Y >= rect_.bottom) loc = Location.bottom;
+            else loc = Location.inside;
+          }
+          break;
+
+        case Location.top:
+          {
+            while (i <= highI && path[i].Y <= rect_.top) i++;
+            if (i > highI) break;
+            if (path[i].Y >= rect_.bottom) loc = Location.bottom;
+            else if (path[i].X <= rect_.left) loc = Location.left;
+            else if (path[i].X >= rect_.right) loc = Location.right;
+            else loc = Location.inside;
+          }
+          break;
+
+        case Location.right:
+          {
+            while (i <= highI && path[i].X >= rect_.right) i++;
+            if (i > highI) break;
+            if (path[i].X <= rect_.left) loc = Location.left;
+            else if (path[i].Y <= rect_.top) loc = Location.top;
+            else if (path[i].Y >= rect_.bottom) loc = Location.bottom;
+            else loc = Location.inside;
+          }
+          break;
+
+        case Location.bottom:
+          {
+            while (i <= highI && path[i].Y >= rect_.bottom) i++;
+            if (i > highI) break;
+            if (path[i].Y <= rect_.top) loc = Location.top;
+            else if (path[i].X <= rect_.left) loc = Location.left;
+            else if (path[i].X >= rect_.right) loc = Location.right;
+            else loc = Location.inside;
+          }
+          break;
+
+        case Location.inside:
+          {
+            while (i <= highI)
+            {
+              if (path[i].X < rect_.left) loc = Location.left;
+              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_.Add(path[i]);
+                i++;
+                continue;
+              }
+              break;
+            }
+          }
+          break;
+      } // switch
+    }
+
+    internal Path64 ExecuteInternal(Path64 path)
+    {
+      if (path.Count < 3 || rect_.IsEmpty()) return new Path64();
+
+      result_.Clear();
+      startLocs_.Clear();
+      int i = 0, highI = path.Count - 1;
+      firstCross_ = Location.inside;
+      Location crossingLoc = Location.inside, prev;
+      if (!GetLocation(rect_, path[highI], out Location loc))
+      {
+        prev = loc;
+        i = highI - 1;
+        while (i >= 0 && !GetLocation(rect_, path[i], out prev)) i--;
+        if (i < 0) return path;
+        if (prev == Location.inside) loc = Location.inside;
+        i = 0;
+      }
+      Location startingLoc = loc;
+
+      ///////////////////////////////////////////////////
+      while (i <= highI)
+      {
+        prev = loc;
+        Location prevCrossLoc = crossingLoc;
+        GetNextLocation(path, ref loc, ref i, highI);
+        if (i > highI) break;
+
+        Point64 prevPt = (i == 0) ? path[highI] : path[i - 1];
+        crossingLoc = loc;
+        if (!GetIntersection(rectPath_, path[i], prevPt, ref crossingLoc, out Point64 ip))
+        {
+          // ie remaining outside (& crossingLoc still == loc)
+
+          if (prevCrossLoc == Location.inside)
+          {
+            bool isClockw = IsClockwise(prev, loc, prevPt, path[i], mp_);
+            do
+            {
+              startLocs_.Add(prev);
+              prev = GetAdjacentLocation(prev, isClockw);
+            } while (prev != loc);
+            crossingLoc = prevCrossLoc; // still not crossed 
+          }
+
+          else if (prev != Location.inside && prev != loc)
+          {
+            bool isClockw = IsClockwise(prev, loc, prevPt, path[i], mp_);
+            do
+            {
+              AddCorner(ref prev, isClockw);
+            } while (prev != loc);
+          }
+          ++i;
+          continue;
+        }
+
+        ////////////////////////////////////////////////////
+        // we must be crossing the rect boundary to get here
+        ////////////////////////////////////////////////////
+
+        if (loc == Location.inside) // path must be entering rect
+        {
+          if (firstCross_ == Location.inside)
+          {
+            firstCross_ = crossingLoc;
+            startLocs_.Add(prev);
+          }
+          else if (prev != crossingLoc)
+          {
+            bool isClockw = IsClockwise(prev, crossingLoc, prevPt, path[i], mp_);
+            do
+            {
+              AddCorner(ref prev, isClockw);
+            } while (prev != crossingLoc);
+          }
+        }
+        else if (prev != Location.inside)
+        {
+          // passing right through rect. 'ip' here will be the second 
+          // intersect pt but we'll also need the first intersect pt (ip2)
+          loc = prev;
+          GetIntersection(rectPath_, prevPt, path[i], ref loc, out Point64 ip2);
+          if (prevCrossLoc != Location.inside)
+            AddCorner(prevCrossLoc, loc);
+
+          if (firstCross_ == Location.inside)
+          {
+            firstCross_ = loc;
+            startLocs_.Add(prev);
+          }
+
+          loc = crossingLoc;
+          result_.Add(ip2);
+          if (ip == ip2)
+          {
+            // it's very likely that path[i] is on rect
+            GetLocation(rect_, path[i], out loc);
+            AddCorner(crossingLoc, loc);
+            crossingLoc = loc;
+            continue;
+          }
+        }
+        else // path must be exiting rect
+        {
+          loc = crossingLoc;
+          if (firstCross_ == Location.inside)
+            firstCross_ = crossingLoc;
+        }
+
+        result_.Add(ip);
+      } //while i <= highI
+        ///////////////////////////////////////////////////
+
+      // path must be entering rect
+      if (firstCross_ == Location.inside)
+      {
+        if (startingLoc == Location.inside) return path;
+        Rect64 tmp_rect = Clipper.GetBounds(path);
+        if (tmp_rect.Contains(rect_) &&
+          Path1ContainsPath2(path, rectPath_) 
+            != PointInPolygonResult.IsOutside) return rectPath_;
+        return new Path64();        
+      }
+
+      if (loc != Location.inside && 
+        (loc != firstCross_ || startLocs_.Count > 2))
+      {
+        if (startLocs_.Count > 0)
+        {
+          prev = loc;
+          foreach (Location loc2 in startLocs_)
+          {
+            if (prev == loc2) continue;
+            AddCorner(ref prev, HeadingClockwise(prev, loc2));
+            prev = loc2;
+          }
+          loc = prev;
+        }
+        if (loc != firstCross_)
+          AddCorner(ref loc, HeadingClockwise(loc, firstCross_));
+      }
+
+      if (result_.Count < 3) return new Path64();
+
+      // finally, tidy up result
+      int k = 0, len = result_.Count;
+      Point64 lastPt = result_[len -1];
+      Path64 result = new Path64(len) { result_[0] };
+      foreach (Point64 pt in result_.Skip(1))
+      {
+        if (InternalClipper.CrossProduct(lastPt, result[k], pt) != 0)
+        {
+          lastPt = result[k++];
+          result.Add(pt);
+        }
+        else
+          result[k] = pt;
+      }
+
+      if (k < 2) 
+        result.Clear();
+      else if (InternalClipper.CrossProduct(result[0], result[k - 1], result[k]) == 0)
+        result.RemoveAt(result.Count - 1);
+      return result;
+    }
+
+    internal Paths64 ExecuteInternal(Paths64 paths)
+    {
+      Paths64 result = new Paths64(paths.Count);
+      foreach(Path64 path in paths) 
+        if (rect_.Intersects(Clipper.GetBounds(path)))
+          result.Add(ExecuteInternal(path)); 
+      return result;
+    }
+  } // RectClip class
+
+  public class RectClipLines : RectClip
+  {
+    internal RectClipLines(Rect64 rect) : base(rect) { }
+
+    internal new Paths64 ExecuteInternal(Path64 path)
+    {
+      result_.Clear();
+      Paths64 result = new Paths64();
+      if (path.Count < 2 || rect_.IsEmpty()) return result;
+
+      Location prev = Location.inside;
+      int i = 1, highI = path.Count - 1;
+      if (!GetLocation(rect_, path[0], out Location loc))
+      {
+        while (i <= highI && !GetLocation(rect_, path[i], out prev)) i++;
+        if (i > highI)
+        {
+          result.Add(path);
+          return result;
+        }                   
+        if (prev == Location.inside) loc = Location.inside;
+        i = 1;
+      }
+      if (loc == Location.inside) result_.Add(path[0]);
+
+      ///////////////////////////////////////////////////
+      while (i <= highI)
+      {
+        prev = loc;
+        GetNextLocation(path, ref loc, ref i, highI);
+        if (i > highI) break;
+        Point64 prevPt = path[i - 1];
+
+        Location crossingLoc = loc;
+        if (!GetIntersection(rectPath_, path[i], prevPt, ref crossingLoc, out Point64 ip))
+        {
+          // ie remaining outside (& crossingLoc still == loc)
+          ++i;
+          continue;
+        }
+
+        ////////////////////////////////////////////////////
+        // we must be crossing the rect boundary to get here
+        ////////////////////////////////////////////////////
+
+        if (loc == Location.inside) // path must be entering rect
+        {
+          result_.Add(ip);
+        }
+        else if (prev != Location.inside)
+        {
+          // passing right through rect. 'ip' here will be the second 
+          // intersect pt but we'll also need the first intersect pt (ip2)
+          crossingLoc = prev;
+          GetIntersection(rectPath_, prevPt, path[i], ref crossingLoc, out Point64 ip2);
+          result_.Add(ip2);
+          result_.Add(ip);
+          result.Add(result_);
+          result_ = new Path64();
+        }
+        else // path must be exiting rect
+        {
+          result_.Add(ip);
+          result.Add(result_);
+          result_ = new Path64();
+        }
+      } //while i <= highI
+        ///////////////////////////////////////////////////
+
+      if (result_.Count > 1)
+      {
+        result.Add(result_);
+        result_ = new Path64();
+      }
+      return result;
+    } // RectClipOpen.ExecuteInternal
+
+  } // RectClipOpen class
+
+} // namespace

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

@@ -0,0 +1,973 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  19 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* 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.                 *
+* Thanks    :  Special thanks to Thong Nguyen, Guus Kuiper, Phil Stopford,     *
+*           :  and Daniel Gosnell for their invaluable assistance with C#.     *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace Clipper2Lib
+{
+
+  // PRE-COMPILER CONDITIONAL ...
+  // USINGZ: For user defined Z-coordinates. See Clipper.SetZ
+
+  public static class Clipper
+  {
+    private static Rect64 maxInvalidRect64 = new Rect64(
+      long.MaxValue, long.MaxValue, long.MinValue, long.MinValue);
+
+    private static RectD maxInvalidRectD = new RectD(
+      double.MaxValue, double.MaxValue, -double.MaxValue, -double.MaxValue);
+
+    public static Rect64 MaxInvalidRect64 => maxInvalidRect64;
+    public static RectD MaxInvalidRectD => maxInvalidRectD;
+
+    public static Paths64 Intersect(Paths64 subject, Paths64 clip, FillRule fillRule)
+    {
+      return BooleanOp(ClipType.Intersection, subject, clip, fillRule);
+    }
+
+    public static PathsD Intersect(PathsD subject, PathsD clip, 
+      FillRule fillRule, int precision = 2)
+    {
+      return BooleanOp(ClipType.Intersection,
+        subject, clip, fillRule, precision);
+    }
+
+    public static Paths64 Union(Paths64 subject, FillRule fillRule)
+    {
+      return BooleanOp(ClipType.Union, subject, null, fillRule);
+    }
+
+    public static Paths64 Union(Paths64 subject, Paths64 clip, FillRule fillRule)
+    {
+      return BooleanOp(ClipType.Union, subject, clip, fillRule);
+    }
+
+    public static PathsD Union(PathsD subject, FillRule fillRule)
+    {
+      return BooleanOp(ClipType.Union, subject, null, fillRule);
+    }
+
+    public static PathsD Union(PathsD subject, PathsD clip, 
+      FillRule fillRule, int precision = 2)
+    {
+      return BooleanOp(ClipType.Union,
+        subject, clip, fillRule, precision);
+    }
+
+    public static Paths64 Difference(Paths64 subject, Paths64 clip, FillRule fillRule)
+    {
+      return BooleanOp(ClipType.Difference, subject, clip, fillRule);
+    }
+
+    public static PathsD Difference(PathsD subject, PathsD clip, 
+      FillRule fillRule, int precision = 2)
+    {
+      return BooleanOp(ClipType.Difference,
+        subject, clip, fillRule, precision);
+    }
+
+    public static Paths64 Xor(Paths64 subject, Paths64 clip, FillRule fillRule)
+    {
+      return BooleanOp(ClipType.Xor, subject, clip, fillRule);
+    }
+
+    public static PathsD Xor(PathsD subject, PathsD clip, 
+      FillRule fillRule, int precision = 2)
+    {
+      return BooleanOp(ClipType.Xor, 
+        subject, clip, fillRule, precision);
+    }
+
+    public static Paths64 BooleanOp(ClipType clipType, 
+      Paths64? subject, Paths64? clip, FillRule fillRule)
+    {
+      Paths64 solution = new Paths64();
+      if (subject == null) return solution;
+      Clipper64 c = new Clipper64();
+      c.AddPaths(subject, PathType.Subject);
+      if (clip != null)
+        c.AddPaths(clip, PathType.Clip);
+      c.Execute(clipType, fillRule, solution);
+      return solution;
+    }
+
+    public static PathsD BooleanOp(ClipType clipType, PathsD subject, PathsD? clip, 
+      FillRule fillRule, int precision = 2)
+    {
+      PathsD solution = new PathsD();
+      ClipperD c = new ClipperD(precision);
+      c.AddSubject(subject);
+      if (clip != null)
+        c.AddClip(clip);
+      c.Execute(clipType, fillRule, solution);
+      return solution;
+    }
+
+    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);
+    }
+
+    public static PathsD InflatePaths(PathsD paths, double delta, JoinType joinType,
+      EndType endType, double miterLimit = 2.0, int precision = 2)
+    {
+      InternalClipper.CheckPrecision(precision);
+      double scale = Math.Pow(10, precision);
+      Paths64 tmp = ScalePaths64(paths, scale);
+      ClipperOffset co = new ClipperOffset(miterLimit);
+      co.AddPaths(tmp, joinType, endType);
+      tmp = co.Execute(delta * scale);
+      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;
+    }
+
+    public static PathD RectClip(RectD rect, PathD path, int precision = 2)
+    {
+      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);
+    }
+
+    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;
+    }
+    public static Paths64 RectClipLines(Rect64 rect, Path64 path)
+    {
+      if (rect.IsEmpty() || path.Count == 0) return new Paths64();
+      RectClipLines rco = new RectClipLines(rect);
+      return rco.ExecuteInternal(path);
+    }
+
+    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;
+    }
+
+    public static PathsD RectClipLines(RectD rect, PathD path, int precision = 2)
+    {
+      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);
+    }
+    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;
+      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;
+    }
+    public static Paths64 MinkowskiSum(Path64 pattern, Path64 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 double Area(Path64 path)
+    {
+      // https://en.wikipedia.org/wiki/Shoelace_formula
+      double a = 0.0;
+      int cnt = path.Count;
+      if (cnt < 3) return 0.0;
+      Point64 prevPt = path[cnt - 1];
+      foreach (Point64 pt in path)
+      {
+        a += (double) (prevPt.Y + pt.Y) * (prevPt.X - pt.X);
+        prevPt = pt;
+      }
+      return a * 0.5;
+    }
+
+    public static double Area(Paths64 paths)
+    {
+      double a = 0.0;
+      foreach (Path64 path in paths)
+        a += Area(path);
+      return a;
+    }
+
+    public static double Area(PathD path)
+    {
+      double a = 0.0;
+      int cnt = path.Count;
+      if (cnt < 3) return 0.0;
+      PointD prevPt = path[cnt - 1];
+      foreach (PointD pt in path)
+      {
+        a += (prevPt.y + pt.y) * (prevPt.x - pt.x);
+        prevPt = pt;
+      }
+      return a * 0.5;
+    }
+
+    public static double Area(PathsD paths)
+    {
+      double a = 0.0;
+      foreach (PathD path in paths)
+        a += Area(path);
+      return a;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool IsPositive(Path64 poly)
+    {
+      return Area(poly) >= 0;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool IsPositive(PathD poly)
+    {
+      return Area(poly) >= 0;
+    }
+
+    public static string Path64ToString(Path64 path)
+    {
+      string result = "";
+      foreach (Point64 pt in path)
+        result += pt.ToString();
+      return result + '\n';
+    }
+    public static string Paths64ToString(Paths64 paths)
+    {
+      string result = "";
+      foreach (Path64 path in paths)
+        result += Path64ToString(path);
+      return result;
+    }
+    public static string PathDToString(PathD path)
+    {
+      string result = "";
+      foreach (PointD pt in path)
+        result += pt.ToString();
+      return result + '\n';
+    }
+    public static string PathsDToString(PathsD paths)
+    {
+      string result = "";
+      foreach (PathD path in paths)
+        result += PathDToString(path);
+      return result;
+    }
+    public static Path64 OffsetPath(Path64 path, long dx, long dy)
+    {
+      Path64 result = new Path64(path.Count);
+      foreach (Point64 pt in path)
+        result.Add(new Point64(pt.X + dx, pt.Y + dy));
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static Point64 ScalePoint64(Point64 pt, double scale)
+    {
+      Point64 result = new Point64()
+      {
+        X = (long) (pt.X * scale),
+        Y = (long) (pt.Y * scale),
+#if USINGZ
+        Z = (long) (pt.Z),
+#endif
+      };
+      return result;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static PointD ScalePointD(Point64 pt, double scale)
+    {
+      PointD result = new PointD()
+      {
+        x = pt.X * scale,
+        y = pt.Y * scale,
+#if USINGZ
+        z = pt.Z,
+#endif
+      };
+      return result;
+    }
+
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static Rect64 ScaleRect(RectD rec, double scale)
+    {
+      Rect64 result = new Rect64()
+      {
+        left = (long) (rec.left * scale),
+        top = (long) (rec.top * scale),
+        right = (long) (rec.right * scale),
+        bottom = (long) (rec.bottom * scale)
+      };
+      return result;
+    }
+
+    public static Path64 ScalePath(Path64 path, double scale)
+    {
+      if (InternalClipper.IsAlmostZero(scale - 1)) return path;
+      Path64 result = new Path64(path.Count);
+#if USINGZ
+      foreach (Point64 pt in path)
+        result.Add(new Point64(pt.X * scale, pt.Y * scale, pt.Z));
+#else
+      foreach (Point64 pt in path)
+        result.Add(new Point64(pt.X * scale, pt.Y * scale));
+#endif
+      return result;
+    }
+
+    public static Paths64 ScalePaths(Paths64 paths, double scale)
+    {
+      if (InternalClipper.IsAlmostZero(scale - 1)) return paths;
+      Paths64 result = new Paths64(paths.Count);
+      foreach (Path64 path in paths)
+        result.Add(ScalePath(path, scale));
+      return result;
+    }
+
+    public static PathD ScalePath(PathD path, double scale)
+    {
+      if (InternalClipper.IsAlmostZero(scale - 1)) return path;
+      PathD result = new PathD(path.Count);
+      foreach (PointD pt in path)
+        result.Add(new PointD(pt, scale));
+      return result;
+    }
+
+    public static PathsD ScalePaths(PathsD paths, double scale)
+    {
+      if (InternalClipper.IsAlmostZero(scale - 1)) return paths;
+      PathsD result = new PathsD(paths.Count);
+      foreach (PathD path in paths)
+        result.Add(ScalePath(path, scale));
+      return result;
+    }
+
+    // Unlike ScalePath, both ScalePath64 & ScalePathD also involve type conversion
+    public static Path64 ScalePath64(PathD path, double scale)
+    {
+      int cnt = path.Count;
+      Path64 res = new Path64(cnt);
+      foreach (PointD pt in path)
+        res.Add(new Point64(pt, scale));
+      return res;
+    }
+
+    public static Paths64 ScalePaths64(PathsD paths, double scale)
+    {
+      int cnt = paths.Count;
+      Paths64 res = new Paths64(cnt);
+      foreach (PathD path in paths)
+        res.Add(ScalePath64(path, scale));
+      return res;
+    }
+
+    public static PathD ScalePathD(Path64 path, double scale)
+    {
+      int cnt = path.Count;
+      PathD res = new PathD(cnt);
+      foreach (Point64 pt in path)
+        res.Add(new PointD(pt, scale));
+      return res;
+    }
+
+    public static PathsD ScalePathsD(Paths64 paths, double scale)
+    {
+      int cnt = paths.Count;
+      PathsD res = new PathsD(cnt);
+      foreach (Path64 path in paths)
+        res.Add(ScalePathD(path, scale));
+      return res;
+    }
+
+    // The static functions Path64 and PathD convert path types without scaling
+    public static Path64 Path64(PathD path)
+    {
+      Path64 result = new Path64(path.Count);
+      foreach (PointD pt in path)
+        result.Add(new Point64(pt));
+      return result;
+    }
+
+    public static Paths64 Paths64(PathsD paths)
+    {
+      Paths64 result = new Paths64(paths.Count);
+      foreach (PathD path in paths)
+        result.Add(Path64(path));
+      return result;
+    }
+
+    public static PathsD PathsD(Paths64 paths)
+    {
+      PathsD result = new PathsD(paths.Count);
+      foreach (Path64 path in paths)
+        result.Add(PathD(path));
+      return result;
+    }
+
+    public static PathD PathD(Path64 path)
+    {
+      PathD result = new PathD(path.Count);
+      foreach (Point64 pt in path)
+        result.Add(new PointD(pt));
+      return result;
+    }
+
+    public static Path64 TranslatePath(Path64 path, long dx, long dy)
+    {
+      Path64 result = new Path64(path.Count);
+      foreach (Point64 pt in path)
+        result.Add(new Point64(pt.X + dx, pt.Y + dy));
+      return result;
+    }
+
+    public static Paths64 TranslatePaths(Paths64 paths, long dx, long dy)
+    {
+      Paths64 result = new Paths64(paths.Count);
+      foreach (Path64 path in paths)
+        result.Add(OffsetPath(path, dx, dy));
+      return result;
+    }
+
+    public static PathD TranslatePath(PathD path, double dx, double dy)
+    {
+      PathD result = new PathD(path.Count);
+      foreach (PointD pt in path)
+        result.Add(new PointD(pt.x + dx, pt.y + dy));
+      return result;
+    }
+
+    public static PathsD TranslatePaths(PathsD paths, double dx, double dy)
+    {
+      PathsD result = new PathsD(paths.Count);
+      foreach (PathD path in paths)
+        result.Add(TranslatePath(path, dx, dy));
+      return result;
+    }
+
+    public static Path64 ReversePath(Path64 path)
+    {
+      Path64 result = new Path64(path);
+      result.Reverse();
+      return result;
+    }
+
+    public static PathD ReversePath(PathD path)
+    {
+      PathD result = new PathD(path);
+      result.Reverse();
+      return result;
+    }
+
+    public static Paths64 ReversePaths(Paths64 paths)
+    {
+      Paths64 result = new Paths64(paths.Count);
+      foreach (Path64 t in paths)
+        result.Add(ReversePath(t));
+
+      return result;
+    }
+
+    public static PathsD ReversePaths(PathsD paths)
+    {
+      PathsD result = new PathsD(paths.Count);
+      foreach (PathD path in paths)
+        result.Add(ReversePath(path));
+      return result;
+    }
+
+    public static Rect64 GetBounds(Path64 path)
+    {
+      Rect64 result = MaxInvalidRect64;
+      foreach (Point64 pt in path)
+      {
+        if (pt.X < result.left) result.left = pt.X;
+        if (pt.X > result.right) result.right = pt.X;
+        if (pt.Y < result.top) result.top = pt.Y;
+        if (pt.Y > result.bottom) result.bottom = pt.Y;
+      }
+      return result.IsEmpty() ? new Rect64() : result;
+    }
+
+    public static Rect64 GetBounds(Paths64 paths)
+    {
+      Rect64 result = MaxInvalidRect64;
+      foreach (Path64 path in paths)
+        foreach (Point64 pt in path)
+        {
+          if (pt.X < result.left) result.left = pt.X;
+          if (pt.X > result.right) result.right = pt.X;
+          if (pt.Y < result.top) result.top = pt.Y;
+          if (pt.Y > result.bottom) result.bottom = pt.Y;
+        }
+      return result.IsEmpty() ? new Rect64() : result;
+    }
+
+    public static RectD GetBounds(PathD path)
+    {
+      RectD result = MaxInvalidRectD;
+      foreach (PointD pt in path)
+      {
+        if (pt.x < result.left) result.left = pt.x;
+        if (pt.x > result.right) result.right = pt.x;
+        if (pt.y < result.top) result.top = pt.y;
+        if (pt.y > result.bottom) result.bottom = pt.y;
+      }
+      return result.IsEmpty() ? new RectD() : result;
+    }
+
+    public static RectD GetBounds(PathsD paths)
+    {
+      RectD result = MaxInvalidRectD;
+      foreach (PathD path in paths)
+        foreach (PointD pt in path)
+        {
+          if (pt.x < result.left) result.left = pt.x;
+          if (pt.x > result.right) result.right = pt.x;
+          if (pt.y < result.top) result.top = pt.y;
+          if (pt.y > result.bottom) result.bottom = pt.y;
+        }
+      return result.IsEmpty() ? new RectD() : result;
+    }
+
+    public static Path64 MakePath(int[] arr)
+    {
+      int len = arr.Length / 2;
+      Path64 p = new Path64(len);
+      for (int i = 0; i < len; i++)
+        p.Add(new Point64(arr[i * 2], arr[i * 2 + 1]));
+      return p;
+    }
+
+    public static Path64 MakePath(long[] arr)
+    {
+      int len = arr.Length / 2;
+      Path64 p = new Path64(len);
+      for (int i = 0; i < len; i++)
+        p.Add(new Point64(arr[i * 2], arr[i * 2 + 1]));
+      return p;
+    }
+
+    public static PathD MakePath(double[] arr)
+    {
+      int len = arr.Length / 2;
+      PathD p = new PathD(len);
+      for (int i = 0; i < len; i++)
+        p.Add(new PointD(arr[i * 2], arr[i * 2 + 1]));
+      return p;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static double Sqr(double value)
+    {
+      return value * value;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool PointsNearEqual(PointD pt1, PointD pt2, double distanceSqrd)
+    {
+      return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y) < distanceSqrd;
+    }
+
+    public static PathD StripNearDuplicates(PathD path,
+        double minEdgeLenSqrd, bool isClosedPath)
+    {
+      int cnt = path.Count;
+      PathD result = new PathD(cnt);
+      if (cnt == 0) return result;
+      PointD lastPt = path[0];
+      result.Add(lastPt);
+      for (int i = 1; i < cnt; i++)
+        if (!PointsNearEqual(lastPt, path[i], minEdgeLenSqrd))
+        {
+          lastPt = path[i];
+          result.Add(lastPt);
+        }
+
+      if (isClosedPath && PointsNearEqual(lastPt, result[0], minEdgeLenSqrd))
+      {
+        result.RemoveAt(result.Count - 1);
+      }
+
+      return result;
+    }
+
+    public static Path64 StripDuplicates(Path64 path, bool isClosedPath)
+    {
+      int cnt = path.Count;
+      Path64 result = new Path64(cnt);
+      if (cnt == 0) return result;
+      Point64 lastPt = path[0];
+      result.Add(lastPt);
+      for (int i = 1; i < cnt; i++)
+        if (lastPt != path[i])
+        {
+          lastPt = path[i];
+          result.Add(lastPt);
+        }
+      if (isClosedPath && lastPt == result[0])
+        result.RemoveAt(result.Count - 1);
+      return result;
+    }
+
+    private static void AddPolyNodeToPaths(PolyPath64 polyPath, Paths64 paths)
+    {
+      if (polyPath.Polygon!.Count > 0)
+        paths.Add(polyPath.Polygon);
+      for (int i = 0; i < polyPath.Count; i++)
+        AddPolyNodeToPaths((PolyPath64) polyPath._childs[i], paths);
+    }
+
+    public static Paths64 PolyTreeToPaths64(PolyTree64 polyTree)
+    {
+      Paths64 result = new Paths64();
+      for (int i = 0; i < polyTree.Count; i++)
+        AddPolyNodeToPaths((PolyPath64) polyTree._childs[i], result);
+      return result;
+    }
+
+    public static void AddPolyNodeToPathsD(PolyPathD polyPath, PathsD paths)
+    {
+      if (polyPath.Polygon!.Count > 0)
+        paths.Add(polyPath.Polygon);
+      for (int i = 0; i < polyPath.Count; i++)
+        AddPolyNodeToPathsD((PolyPathD) polyPath._childs[i], paths);
+    }
+
+    public static PathsD PolyTreeToPathsD(PolyTreeD polyTree)
+    {
+      PathsD result = new PathsD();
+      foreach (PolyPathD polyPathBase in polyTree)
+      {
+        PolyPathD p = (PolyPathD)polyPathBase;
+        AddPolyNodeToPathsD(p, result);
+      }
+
+      return result;
+    }
+
+    public static double PerpendicDistFromLineSqrd(PointD pt, PointD line1, PointD line2)
+    {
+      double a = pt.x - line1.x;
+      double b = pt.y - line1.y;
+      double c = line2.x - line1.x;
+      double d = line2.y - line1.y;
+      if (c == 0 && d == 0) return 0;
+      return Sqr(a * d - c * b) / (c * c + d * d);
+    }
+
+    public static double PerpendicDistFromLineSqrd(Point64 pt, Point64 line1, Point64 line2)
+    {
+      double a = (double) pt.X - line1.X;
+      double b = (double) pt.Y - line1.Y;
+      double c = (double) line2.X - line1.X;
+      double d = (double) line2.Y - line1.Y;
+      if (c == 0 && d == 0) return 0;
+      return Sqr(a * d - c * b) / (c * c + d * d);
+    }
+
+    internal static void RDP(Path64 path, int begin, int end, double epsSqrd, List<bool> flags)
+    {
+      int idx = 0;
+      double max_d = 0;
+      while (end > begin && path[begin] == path[end]) flags[end--] = false;
+      for (int i = begin + 1; i < end; ++i)
+      {
+        // PerpendicDistFromLineSqrd - avoids expensive Sqrt()
+        double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
+        if (d <= max_d) continue;
+        max_d = d;
+        idx = i;
+      }
+      if (max_d <= epsSqrd) return;
+      flags[idx] = true;
+      if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
+      if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
+    }
+
+    public static Path64 RamerDouglasPeucker(Path64 path, double epsilon)
+    {
+      int len = path.Count;
+      if (len < 5) return path;
+      List<bool> flags = new List<bool>(new bool[len]) { [0] = true, [len - 1] = true };
+      RDP(path, 0, len - 1, Sqr(epsilon), flags);
+      Path64 result = new Path64(len);
+      for (int i = 0; i < len; ++i)
+        if (flags[i]) result.Add(path[i]);
+      return result;
+    }
+
+    public static Paths64 RamerDouglasPeucker(Paths64 paths, double epsilon)
+    {
+      Paths64 result = new Paths64(paths.Count);
+      foreach (Path64 path in paths)
+        result.Add(RamerDouglasPeucker(path, epsilon));
+      return result;
+    }
+
+    internal static void RDP(PathD path, int begin, int end, double epsSqrd, List<bool> flags)
+    {
+      int idx = 0;
+      double max_d = 0;
+      while (end > begin && path[begin] == path[end]) flags[end--] = false;
+      for (int i = begin + 1; i < end; ++i)
+      {
+        // PerpendicDistFromLineSqrd - avoids expensive Sqrt()
+        double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
+        if (d <= max_d) continue;
+        max_d = d;
+        idx = i;
+      }
+      if (max_d <= epsSqrd) return;
+      flags[idx] = true;
+      if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
+      if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
+    }
+
+    public static PathD RamerDouglasPeucker(PathD path, double epsilon)
+    {
+      int len = path.Count;
+      if (len < 5) return path;
+      List<bool> flags = new List<bool>(new bool[len]) { [0] = true, [len - 1] = true };
+      RDP(path, 0, len - 1, Sqr(epsilon), flags);
+      PathD result = new PathD(len);
+      for (int i = 0; i < len; ++i)
+        if (flags[i]) result.Add(path[i]);
+      return result;
+    }
+
+    public static PathsD RamerDouglasPeucker(PathsD paths, double epsilon)
+    {
+      PathsD result = new PathsD(paths.Count);
+      foreach (PathD path in paths)
+        result.Add(RamerDouglasPeucker(path, epsilon));
+      return result;
+    }
+
+    public static Path64 TrimCollinear(Path64 path, bool isOpen = false)
+    {
+      int len = path.Count;
+      int i = 0;
+      if (!isOpen)
+      {
+        while (i < len - 1 && InternalClipper.CrossProduct(
+          path[len - 1], path[i], path[i + 1]) == 0) i++;
+        while (i < len - 1 && InternalClipper.CrossProduct(
+          path[len - 2], path[len - 1], path[i]) == 0) len--;
+      }
+
+      if (len - i < 3)
+      {
+        if (!isOpen || len < 2 || path[0] == path[1])
+          return new Path64();
+        return path;
+      }
+
+      Path64 result = new Path64(len - i);
+      Point64 last = path[i];
+      result.Add(last);
+      for (i++; i < len - 1; i++)
+      {
+        if (InternalClipper.CrossProduct(
+              last, path[i], path[i + 1]) == 0) continue;
+        last = path[i];
+        result.Add(last);
+      }
+
+      if (isOpen)
+        result.Add(path[len - 1]);
+      else if (InternalClipper.CrossProduct(
+        last, path[len - 1], result[0]) != 0)
+        result.Add(path[len - 1]);
+      else
+      {
+        while (result.Count > 2 && InternalClipper.CrossProduct(
+          result[result.Count - 1], result[result.Count - 2], result[0]) == 0)
+            result.RemoveAt(result.Count - 1);
+        if (result.Count < 3)
+          result.Clear();
+      }
+      return result;
+    }
+
+    public static PathD TrimCollinear(PathD path, int precision, bool isOpen = false)
+    {
+      InternalClipper.CheckPrecision(precision);
+      double scale = Math.Pow(10, precision);
+      Path64 p = ScalePath64(path, scale);
+      p = TrimCollinear(p, isOpen);
+      return ScalePathD(p, 1 / scale);
+    }
+
+    public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon)
+    {
+      return InternalClipper.PointInPolygon(pt, polygon);
+    }
+
+    public static PointInPolygonResult PointInPolygon(PointD pt, 
+      PathD polygon, int precision = 2)
+    {
+      InternalClipper.CheckPrecision(precision);
+      double scale = Math.Pow(10, precision);
+      Point64 p = new Point64(pt, scale);
+      Path64 path = ScalePath64(polygon, scale);
+      return InternalClipper.PointInPolygon(p, path);
+    }
+
+    public static Path64 Ellipse(Point64 center,
+      double radiusX, double radiusY = 0, int steps = 0)
+    {
+      if (radiusX <= 0) return new Path64();
+      if (radiusY <= 0) radiusY = radiusX;
+      if (steps <= 2)
+        steps = (int) Math.Ceiling(Math.PI * Math.Sqrt((radiusX + radiusY) / 2));
+
+      double si = Math.Sin(2 * Math.PI / steps);
+      double co = Math.Cos(2 * Math.PI / steps);
+      double dx = co, dy = si;
+      Path64 result = new Path64(steps) { new Point64(center.X + radiusX, center.Y) };
+      for (int i = 1; i < steps; ++i)
+      {
+        result.Add(new Point64(center.X + radiusX * dx, center.Y + radiusY * dy));
+        double x = dx * co - dy * si;
+        dy = dy * co + dx * si;
+        dx = x;
+      }
+      return result;
+    }
+
+    public static PathD Ellipse(PointD center,
+      double radiusX, double radiusY = 0, int steps = 0)
+    {
+      if (radiusX <= 0) return new PathD();
+      if (radiusY <= 0) radiusY = radiusX;
+      if (steps <= 2)
+        steps = (int) Math.Ceiling(Math.PI * Math.Sqrt((radiusX + radiusY) / 2));
+
+      double si = Math.Sin(2 * Math.PI / steps);
+      double co = Math.Cos(2 * Math.PI / steps);
+      double dx = co, dy = si;
+      PathD result = new PathD(steps) { new PointD(center.x + radiusX, center.y) };
+      for (int i = 1; i < steps; ++i)
+      {
+        result.Add(new PointD(center.x + radiusX * dx, center.y + radiusY * dy));
+        double x = dx * co - dy * si;
+        dy = dy * co + dx * si;
+        dx = x;
+      }
+      return result;
+    }
+
+  } // Clipper
+} // namespace

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

@@ -0,0 +1,33 @@
+<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>

+ 21 - 0
polygon.mod/clipper2/CSharp/USINGZ/Clipper2LibZ.csproj

@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>8</LangVersion>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE;USINGZ;</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <DefineConstants>TRACE;DEBUG;USINGZ;</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <None Remove="*.ico" />
+  </ItemGroup>
+
+</Project>

+ 25 - 0
polygon.mod/clipper2/CSharp/USINGZ/Clipper2LibZ.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32804.467
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2LibZ", "Clipper2LibZ.csproj", "{72B3F298-F9CD-4B18-B205-B871E261491A}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{72B3F298-F9CD-4B18-B205-B871E261491A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{72B3F298-F9CD-4B18-B205-B871E261491A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{72B3F298-F9CD-4B18-B205-B871E261491A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{72B3F298-F9CD-4B18-B205-B871E261491A}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {5D82BF36-9B12-4274-8509-9C0D4128DC82}
+	EndGlobalSection
+EndGlobal

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

@@ -0,0 +1,275 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 September 2022                                               *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System;
+using System.IO;
+using System.Diagnostics;
+
+namespace Clipper2Lib
+{
+
+  public static class ClipperFileIO
+  {
+    public static Paths64 PathFromStr(string s)
+    {
+      if (s == null) return null;
+      Path64 p = new ();
+      Paths64 pp = new ();
+      int len = s.Length, i = 0, j;
+      while (i < len)
+      {
+        bool isNeg;
+        while (s[i] < 33 && i < len) i++;
+        if (i >= len) break;
+        //get X ...
+        isNeg = s[i] == 45;
+        if (isNeg) i++;
+        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 (isNeg) x = -x;
+        //skip space or comma between X & Y ...
+        i = j;
+        while (i < len && (s[i] == 32 || s[i] == 44)) i++;
+        //get Y ...
+        if (i >= len) break;
+        isNeg = s[i] == 45;
+        if (isNeg) i++;
+        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 (isNeg) y = -y;
+        p.Add(new Point64(x, y));
+        //skip trailing space, comma ...
+        i = j;
+        int nlCnt = 0;
+        while (i < len && (s[i] < 33 || s[i] == 44))
+        {
+          if (i >= len) break;
+          if (s[i] == 10)
+          {
+            nlCnt++;
+            if (nlCnt == 2)
+            {
+              if (p.Count > 2) pp.Add(p);
+              p = new Path64();
+            }
+          }
+          i++;
+        }
+      }
+      if (p.Count > 2) pp.Add(p);
+      return pp;
+    }
+    //------------------------------------------------------------------------------
+
+    public static bool LoadTestNum(string filename, int num,
+      Paths64 subj, Paths64 subj_open, Paths64 clip,
+      out ClipType ct, out FillRule fillRule, out long area, out int count, out string caption)
+    {
+      if (subj == null) subj = new Paths64(); else subj.Clear();
+      if (subj_open == null) subj_open = new Paths64(); else subj_open.Clear();
+      if (clip == null) clip = new Paths64(); else clip.Clear();
+      ct = ClipType.Intersection;
+      fillRule = FillRule.EvenOdd;
+      bool result = false;
+      int GetIdx;
+      if (num < 1) num = 1;
+      caption = "";
+      area = 0;
+      count = 0;
+      StreamReader reader;
+      try
+      {
+        reader = new StreamReader(filename);
+      }
+      catch
+      {
+        return false;
+      }
+      while (true)
+      {
+        string s = reader.ReadLine();
+        if (s == null) break;
+        
+        if (s.IndexOf("CAPTION: ", StringComparison.Ordinal) == 0)
+        {
+          num--;
+          if (num != 0) continue;
+          caption = s[9..]; 
+          result = true;
+          continue;
+        }
+
+        if (num > 0) continue;
+
+        if (s.IndexOf("CLIPTYPE: ", StringComparison.Ordinal) == 0)
+        {
+          if (s.IndexOf("INTERSECTION", StringComparison.Ordinal) > 0) ct = ClipType.Intersection;
+          else if (s.IndexOf("UNION", StringComparison.Ordinal) > 0) ct = ClipType.Union;
+          else if (s.IndexOf("DIFFERENCE", StringComparison.Ordinal) > 0) ct = ClipType.Difference;
+          else ct = ClipType.Xor;
+          continue;
+        }
+
+        if (s.IndexOf("FILLTYPE: ", StringComparison.Ordinal) == 0 ||
+            s.IndexOf("FILLRULE: ", StringComparison.Ordinal) == 0)
+        {
+          if (s.IndexOf("EVENODD", StringComparison.Ordinal) > 0) fillRule = FillRule.EvenOdd;
+          else if (s.IndexOf("POSITIVE", StringComparison.Ordinal) > 0) fillRule = FillRule.Positive;
+          else if (s.IndexOf("NEGATIVE", StringComparison.Ordinal) > 0) fillRule = FillRule.Negative;
+          else fillRule = FillRule.NonZero;
+          continue;
+        }
+
+        if (s.IndexOf("SOL_AREA: ", StringComparison.Ordinal) == 0)
+        {
+          area = long.Parse(s[10..]);
+          continue;
+        }
+
+        if (s.IndexOf("SOL_COUNT: ", StringComparison.Ordinal) == 0)
+        {
+          count = int.Parse(s[11..]);
+          continue;
+        }
+
+        if (s.IndexOf("SUBJECTS_OPEN", StringComparison.Ordinal) == 0) GetIdx = 2;
+        else if (s.IndexOf("SUBJECTS", StringComparison.Ordinal) == 0) GetIdx = 1;
+        else if (s.IndexOf("CLIPS", StringComparison.Ordinal) == 0) GetIdx = 3;
+        else continue;
+
+        while (true)
+        {
+          s = reader.ReadLine();
+          if (s == null) break;
+          Paths64 paths = PathFromStr(s); //0 or 1 path
+          if (paths == null || paths.Count == 0)
+          {
+            if (GetIdx == 3) return result;
+            if (s.IndexOf("SUBJECTS_OPEN", StringComparison.Ordinal) == 0) GetIdx = 2;
+            else if (s.IndexOf("CLIPS", StringComparison.Ordinal) == 0) GetIdx = 3;
+            else return result;
+            continue;
+          }
+          if (GetIdx == 1) subj.Add(paths[0]);
+          else if (GetIdx == 2) subj_open.Add(paths[0]);
+          else clip.Add(paths[0]);
+        }
+      }
+      return result;
+    }
+    //-----------------------------------------------------------------------
+
+    public static void SaveClippingOp(string filename, Paths64 subj,
+      Paths64 subj_open, Paths64 clip, ClipType ct, FillRule fillRule, bool append)
+    {
+      StreamWriter writer;
+      try
+      {
+        writer = new StreamWriter(filename, append);
+      }
+      catch
+      {
+        return;
+      }
+      writer.Write("CAPTION: 1. \r\n");
+      writer.Write("CLIPTYPE: {0}\r\n", ct.ToString().ToUpper());
+      writer.Write("FILLRULE: {0}\r\n", fillRule.ToString().ToUpper());
+      if (subj != null && subj.Count > 0)
+      {
+        writer.Write("SUBJECTS\r\n");
+        foreach (Path64 p in subj)
+        {
+          foreach (Point64 ip in p)
+            writer.Write("{0},{1} ", ip.X, ip.Y);
+          writer.Write("\r\n");
+        }
+      }
+      if (subj_open != null && subj_open.Count > 0)
+      {
+        writer.Write("SUBJECTS_OPEN\r\n");
+        foreach (Path64 p in subj_open)
+        {
+          foreach (Point64 ip in p)
+            writer.Write("{0},{1} ", ip.X, ip.Y);
+          writer.Write("\r\n");
+        }
+      }
+      if (clip != null && clip.Count > 0)
+      {
+        writer.Write("CLIPS\r\n");
+        foreach (Path64 p in clip)
+        {
+          foreach (Point64 ip in p)
+            writer.Write(ip.ToString());
+          writer.Write("\r\n");
+        }
+      }
+      writer.Close();
+    }
+
+    public static void SaveToBinFile(string filename, Paths64 paths)
+    {
+      FileStream filestream;
+      try
+      {
+        filestream = new FileStream(filename, FileMode.Create);
+      }
+      catch
+      {
+        return;
+      }
+      BinaryWriter writer;
+      try
+      {
+        writer = new BinaryWriter(filestream);
+      }
+      catch
+      {
+        return;
+      }
+      writer.Write(paths.Count);
+      foreach (Path64 path in paths)
+      {
+        writer.Write(path.Count);
+        foreach (Point64 pt in path)
+        {
+          writer.Write(pt.X);
+          writer.Write(pt.Y);
+        }
+      }
+      writer.Close();
+    }
+    //------------------------------------------------------------------------------
+
+    public static Paths64 AffineTranslatePaths(Paths64 paths, long dx, long dy)
+    {
+      Paths64 result = new (paths.Count);
+      foreach (Path64 path in paths)
+      {
+        Path64 p = new (path.Count);
+        foreach (Point64 pt in path)
+          p.Add(new Point64(pt.X + dx, pt.Y + dy));
+        result.Add(p);
+      }
+      return result;
+    }
+
+    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();
+    }
+
+  }
+}

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

@@ -0,0 +1,12 @@
+<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>

+ 79 - 0
polygon.mod/clipper2/CSharp/Utils/SVG/Clipper.SVG.Utils.cs

@@ -0,0 +1,79 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 September 2022                                               *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System.IO;
+
+namespace Clipper2Lib
+{
+
+  public static class SvgUtils
+  {
+
+    public static void AddCaption(SimpleSvgWriter 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)
+    {
+      if (!is_closed)
+        svg.AddPaths(paths, !is_joined, 0x0, 0xCCB3B3DA, 0.8);
+      else
+        svg.AddPaths(paths, false, 0x1800009C, 0xCCB3B3DA, 0.8);
+    }
+
+    public static void AddSubject(SimpleSvgWriter svg, PathsD paths,
+      bool is_closed = true, bool is_joined = true)
+    {
+      if (!is_closed)
+        svg.AddPaths(paths, !is_joined, 0x0, 0xCCB3B3DA, 0.8);
+      else
+        svg.AddPaths(paths, false, 0x1800009C, 0xCCB3B3DA, 0.8);
+    }
+
+    public static void AddClip(SimpleSvgWriter svg, Paths64 paths)
+    {
+      svg.AddPaths(paths, false, 0x129C0000, 0xCCFFA07A, 0.8);
+    }
+
+    public static void AddClip(SimpleSvgWriter svg, PathsD paths)
+    {
+      svg.AddPaths(paths, false, 0x129C0000, 0xCCFFA07A, 0.8);
+    }
+
+    public static void AddSolution(SimpleSvgWriter 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, 0x4080ff9C, 0xFF003300, 0.8, show_coords);
+    }
+
+    public static void AddSolution(SimpleSvgWriter svg, PathsD 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);
+    }
+
+    public static void SaveToFile(SimpleSvgWriter svg,
+      string filename, FillRule fill_rule,
+      int max_width = 0, int max_height = 0, int margin = 0)
+    {
+      if (File.Exists(filename)) File.Delete(filename);
+      svg.FillRule = fill_rule;
+      svg.SaveToFile(filename, max_width, max_height, margin);
+    }
+
+  }
+
+}

+ 273 - 0
polygon.mod/clipper2/CSharp/Utils/SVG/Clipper.SVG.cs

@@ -0,0 +1,273 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 September 2022                                               *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+
+namespace Clipper2Lib
+{
+  public class SimpleSvgWriter
+  {
+
+    public const uint black = 0xFF000000;
+    public const uint white = 0xFFFFFFFF;
+    public const uint maroon = 0xFF800000;
+
+    public const uint navy = 0xFF000080;
+    public const uint blue = 0xFF0000FF;
+    public const uint red = 0xFFFF0000;
+    public const uint green = 0xFF008000;
+    public const uint yellow = 0xFFFFFF00;
+    public const uint lime = 0xFF00FF00;
+    public const uint fuscia = 0xFFFF00FF;
+    public const uint aqua = 0xFF00FFFF;
+
+    private static RectD rectMax =
+      new (double.MaxValue, double.MaxValue, -double.MaxValue, -double.MaxValue);
+    public static RectD RectMax => rectMax;
+
+    private static RectD rectEmpty = new (0, 0, 0, 0);
+    public static RectD RectEmpty => rectEmpty;
+    internal static bool IsValidRect(RectD rec)
+    {
+      return rec.right >= rec.left && rec.bottom >= rec.top;
+    }
+
+    private readonly struct CoordStyle
+    {
+      public readonly string FontName;
+      public readonly int FontSize;
+      public readonly uint FontColor;
+      public CoordStyle(string fontname, int fontsize, uint fontcolor)
+      {
+        FontName = fontname;
+        FontSize = fontsize;
+        FontColor = fontcolor;
+      }
+    }
+
+    private readonly struct TextInfo
+    {
+      public readonly string text;
+      public readonly int fontSize;
+      public readonly uint fontColor;
+      public readonly int posX;
+      public readonly int posY;
+      public TextInfo(string text, int x, int y,
+        int fontsize = 12, uint fontcolor = black)
+      {
+        this.text = text;
+        posX = x;
+        posY = y;
+        fontSize = fontsize;
+        fontColor = fontcolor;
+      }
+    }
+
+    private readonly struct PolyInfo
+    {
+      public readonly PathsD paths;
+      public readonly uint BrushClr;
+      public readonly uint PenClr;
+      public readonly double PenWidth;
+      public readonly bool ShowCoords;
+      public readonly bool IsOpen;
+      public PolyInfo(PathsD paths, uint brushcolor, uint pencolor,
+        double penwidth, bool showcoords = false, bool isopen = false)
+      {
+        this.paths = new PathsD(paths);
+        BrushClr = brushcolor;
+        PenClr = pencolor;
+        PenWidth = penwidth;
+        ShowCoords = showcoords;
+        IsOpen = isopen;
+      }
+    }
+
+    public FillRule FillRule { get; set; }
+    private readonly List<PolyInfo> PolyInfoList = new ();
+    private readonly List<TextInfo> textInfos = new ();
+    private readonly CoordStyle coordStyle;
+
+    private const string svg_header = "<?xml version=\"1.0\" standalone=\"no\"?>\n" +
+      "<svg width=\"{0}px\" height=\"{1}px\" viewBox=\"0 0 {0} {1}\"" +
+      " version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n\n";
+    private const string svg_path_format = "\"\n style=\"fill:{0};" +
+        " fill-opacity:{1:f2}; fill-rule:{2}; stroke:{3};" +
+        " stroke-opacity:{4:f2}; stroke-width:{5:f2};\"/>\n\n";
+    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,
+      string coordFontName = "Verdana", int coordFontsize = 9, uint coordFontColor = black)
+    {
+      coordStyle = new CoordStyle(coordFontName, coordFontsize, coordFontColor);
+      FillRule = fillrule;
+    }
+
+    public void ClearPaths()
+    {
+      PolyInfoList.Clear();
+    }
+
+    public void ClearText()
+    {
+      textInfos.Clear();
+    }
+
+    public void ClearAll()
+    {
+      PolyInfoList.Clear();
+      textInfos.Clear();
+    }
+
+
+    public void AddPaths(Paths64 paths, bool IsOpen, 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));
+    }
+    //------------------------------------------------------------------------------
+
+    public void AddPaths(PathsD paths, bool IsOpen, uint brushColor,
+      uint penColor, double penWidth, bool showCoords = false)
+    {
+      if (paths.Count == 0) return;
+      PolyInfoList.Add(new PolyInfo(paths,
+        brushColor, penColor, penWidth, showCoords, IsOpen));
+    }
+
+    public void AddText(string cap, int posX, int posY, int fontSize, uint fontClr = black)
+    {
+      textInfos.Add(new TextInfo(cap, posX, posY, fontSize, fontClr));
+    }
+
+    private RectD GetBounds()
+    {
+      RectD bounds = new (RectMax);
+      foreach (PolyInfo pi in PolyInfoList)
+        foreach (PathD path in pi.paths)
+          foreach (PointD pt in path)
+          {
+            if (pt.x < bounds.left) bounds.left = pt.x;
+            if (pt.x > bounds.right) bounds.right = pt.x;
+            if (pt.y < bounds.top) bounds.top = pt.y;
+            if (pt.y > bounds.bottom) bounds.bottom = pt.y;
+          }
+      if (!IsValidRect(bounds))
+        return RectEmpty;
+      return bounds;
+    }
+
+    private static string ColorToHtml(uint clr)
+    {
+      return '#' + (clr & 0xFFFFFF).ToString("X6");
+    }
+
+    private static float GetAlpha(uint clr)
+    {
+      return ((float) (clr >> 24) / 255);
+    }
+
+    public bool SaveToFile(string filename, int maxWidth = 0, int maxHeight = 0, int margin = -1)
+    {
+      if (margin < 0) margin = 20;
+      RectD bounds = GetBounds();
+      if (bounds.IsEmpty()) return false;
+
+      double scale = 1.0;
+      if (maxWidth > 0 && maxHeight > 0)
+        scale = Math.Min(
+           (maxWidth - margin * 2) / bounds.Width,
+            (maxHeight - margin * 2) / bounds.Height);
+
+      long offsetX = margin - (long) (bounds.left * scale);
+      long offsetY = margin - (long) (bounds.top * scale);
+
+      StreamWriter writer;
+      try
+      {
+        writer = new StreamWriter(filename);
+      }
+      catch
+      {
+        return false;
+      }
+
+      if (maxWidth <= 0 || maxHeight <= 0)
+        writer.Write(svg_header, (bounds.right - bounds.left) + margin * 2,
+          (bounds.bottom - bounds.top) + margin * 2);
+      else
+        writer.Write(svg_header, maxWidth, maxHeight);
+
+      foreach (PolyInfo pi in PolyInfoList)
+      {
+        writer.Write(" <path d=\"");
+        foreach (PathD path in pi.paths)
+        {
+          if (path.Count < 2) continue;
+          if (!pi.IsOpen && path.Count < 3) continue;
+          writer.Write(string.Format(NumberFormatInfo.InvariantInfo, " M {0:f2} {1:f2}",
+              (path[0].x * scale + offsetX),
+              (path[0].y * scale + offsetY)));
+          for (int j = 1; j < path.Count; j++)
+          {
+            writer.Write(string.Format(NumberFormatInfo.InvariantInfo, " L {0:f2} {1:f2}",
+            (path[j].x * scale + offsetX),
+            (path[j].y * scale + offsetY)));
+          }
+          if (!pi.IsOpen) writer.Write(" z");
+        }
+
+        if (!pi.IsOpen)
+          writer.Write(string.Format(NumberFormatInfo.InvariantInfo, svg_path_format,
+              ColorToHtml(pi.BrushClr), GetAlpha(pi.BrushClr),
+              (FillRule == FillRule.EvenOdd ? "evenodd" : "nonzero"),
+              ColorToHtml(pi.PenClr), GetAlpha(pi.PenClr), pi.PenWidth));
+        else
+          writer.Write(string.Format(NumberFormatInfo.InvariantInfo, svg_path_format2,
+              ColorToHtml(pi.PenClr), GetAlpha(pi.PenClr), pi.PenWidth));
+
+        if (pi.ShowCoords)
+        {
+          writer.Write("<g font-family=\"{0}\" font-size=\"{1}\" fill=\"{2}\">\n", coordStyle.FontName, coordStyle.FontSize, ColorToHtml(coordStyle.FontColor));
+          foreach (PathD path in pi.paths)
+          {
+            foreach (PointD pt in path)
+            {
+#if USINGZ
+              writer.Write(string.Format(
+                  "<text x=\"{0}\" y=\"{1}\">{2},{3},{4}</text>\n",
+                  (int)(pt.x * scale + offsetX), (int)(pt.y * scale + offsetY), pt.x, pt.y, pt.z));
+#else
+              writer.Write("<text x=\"{0:f2}\" y=\"{1:f2}\">{2},{3}</text>\n", (pt.x * scale + offsetX), (pt.y * scale + offsetY), pt.x, pt.y);
+#endif
+            }
+          }
+          writer.Write("</g>\n\n");
+        }
+      }
+
+      foreach (TextInfo captionInfo in textInfos)
+      {
+        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("</svg>\n");
+      writer.Close();
+      return true;
+    }
+  }
+
+}

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

@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Clipper2Lib\Clipper2Lib.csproj" />
+  </ItemGroup>
+
+</Project>

+ 31 - 0
polygon.mod/clipper2/DLL/CPP_DLL/Clipper2_DLL.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32901.215
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Clipper2_Export", "Clipper2_Export.vcxproj", "{FB0A93CE-8E28-4E67-8D05-54B38B218F08}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Debug|x64.ActiveCfg = Debug|x64
+		{FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Debug|x64.Build.0 = Debug|x64
+		{FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Debug|x86.ActiveCfg = Debug|Win32
+		{FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Debug|x86.Build.0 = Debug|Win32
+		{FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Release|x64.ActiveCfg = Release|x64
+		{FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Release|x64.Build.0 = Release|x64
+		{FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Release|x86.ActiveCfg = Release|Win32
+		{FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Release|x86.Build.0 = Release|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {BD166825-2BC9-4724-BCF1-F70F90CB74F2}
+	EndGlobalSection
+EndGlobal

+ 185 - 0
polygon.mod/clipper2/DLL/CPP_DLL/Clipper2_Export.vcxproj

@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>16.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{fb0a93ce-8e28-4e67-8d05-54b38b218f08}</ProjectGuid>
+    <RootNamespace>Clipper2</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v142</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v142</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v142</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v142</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <TargetName>Clipper2_64</TargetName>
+    <IncludePath>..\..\CPP\Clipper2Lib\include;$(IncludePath)</IncludePath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <TargetName>Clipper2_32</TargetName>
+    <IncludePath>..\..\CPP\Clipper2Lib\include;$(IncludePath)</IncludePath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <TargetName>Clipper2_32</TargetName>
+    <IncludePath>..\..\CPP\Clipper2Lib\include;$(IncludePath)</IncludePath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <TargetName>Clipper2_64</TargetName>
+    <IncludePath>..\..\CPP\Clipper2Lib\include;$(IncludePath)</IncludePath>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <LanguageStandard>stdcpp17</LanguageStandard>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+      <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableUAC>false</EnableUAC>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <LanguageStandard>stdcpp17</LanguageStandard>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+      <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableUAC>false</EnableUAC>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <LanguageStandard>stdcpp17</LanguageStandard>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+      <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableUAC>false</EnableUAC>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <LanguageStandard>stdcpp17</LanguageStandard>
+      <LanguageStandard_C>stdc17</LanguageStandard_C>
+      <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableUAC>false</EnableUAC>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\CPP\Clipper2Lib\include\clipper2\clipper.core.h" />
+    <ClInclude Include="..\..\CPP\Clipper2Lib\include\clipper2\clipper.engine.h" />
+    <ClInclude Include="..\..\CPP\Clipper2Lib\include\clipper2\clipper.export.h" />
+    <ClInclude Include="..\..\CPP\Clipper2Lib\include\clipper2\clipper.h" />
+    <ClInclude Include="..\..\CPP\Clipper2Lib\include\clipper2\clipper.offset.h" />
+    <ClInclude Include="..\..\CPP\Clipper2Lib\include\clipper2\clipper.rectclip.h" />
+    <ClInclude Include="framework.h" />
+    <ClInclude Include="pch.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\CPP\Clipper2Lib\src\clipper.engine.cpp" />
+    <ClCompile Include="..\..\CPP\Clipper2Lib\src\clipper.offset.cpp" />
+    <ClCompile Include="..\..\CPP\Clipper2Lib\src\clipper.rectclip.cpp" />
+    <ClCompile Include="dllmain.cpp" />
+    <ClCompile Include="pch.cpp" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 21 - 0
polygon.mod/clipper2/DLL/CPP_DLL/dllmain.cpp

@@ -0,0 +1,21 @@
+#define NOMINMAX
+#include "pch.h"
+#include "clipper2/clipper.h"
+#include "clipper2/clipper.export.h"
+
+BOOL APIENTRY DllMain( HMODULE hModule,
+                       DWORD  ul_reason_for_call,
+                       LPVOID lpReserved
+                     )
+{
+    switch (ul_reason_for_call)
+    {
+    case DLL_PROCESS_ATTACH:
+    case DLL_THREAD_ATTACH:
+    case DLL_THREAD_DETACH:
+    case DLL_PROCESS_DETACH:
+        break;
+    }
+    return TRUE;
+}
+

+ 5 - 0
polygon.mod/clipper2/DLL/CPP_DLL/framework.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
+// Windows Header Files
+#include <windows.h>

+ 5 - 0
polygon.mod/clipper2/DLL/CPP_DLL/pch.cpp

@@ -0,0 +1,5 @@
+// pch.cpp: source file corresponding to the pre-compiled header
+
+#include "pch.h"
+
+// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

+ 13 - 0
polygon.mod/clipper2/DLL/CPP_DLL/pch.h

@@ -0,0 +1,13 @@
+// pch.h: This is a precompiled header file.
+// Files listed below are compiled only once, improving build performance for future builds.
+// This also affects IntelliSense performance, including code completion and many code browsing features.
+// However, files listed here are ALL re-compiled if any one of them is updated between builds.
+// Do not add files here that you will be updating frequently as this negates the performance advantage.
+
+#ifndef PCH_H
+#define PCH_H
+
+// add headers that you want to pre-compile here
+#include "framework.h"
+
+#endif //PCH_H

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

@@ -0,0 +1,663 @@
+ 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.
+

+ 1003 - 0
polygon.mod/clipper2/DLL/Delphi_TestApp/Test_DLL.dpr

@@ -0,0 +1,1003 @@
+program Test_DLL;
+
+// Make sure that the Clipper2 DLLS are in either
+// the OS Path or in the application's folder.
+
+{$APPTYPE CONSOLE}
+{$R *.res}
+
+uses
+  Windows,
+  ShellApi,
+  SysUtils,
+{$IFDEF USING_CLIPPER2_SRC}
+  Clipper.Core in '..\..\Delphi\Clipper2Lib\Clipper.Core.pas',
+  Clipper.Engine in '..\..\Delphi\Clipper2Lib\Clipper.Engine.pas',
+  Clipper in '..\..\Delphi\Clipper2Lib\Clipper.pas',
+{$ENDIF}
+  SvgWriter in 'SvgWriter.pas',
+  Timer in 'Timer.pas';
+type
+
+  ////////////////////////////////////////////////////////
+  // Clipper2 DLL structures
+  ////////////////////////////////////////////////////////
+
+{$IFNDEF USING_CLIPPER2_SRC}
+  TClipType = (ctNone, ctIntersection, ctUnion, ctDifference, ctXor);
+  TJoinType = (jtSquare, jtRound, jtMiter);
+  TEndType  = (etPolygon, etJoined, etButt, etSquare, etRound);
+  //TFillRule = (frEvenOdd, frNonZero, frPositive, frNegative); // see SvgWriter
+
+//  TPoint64 = record X,Y: Int64; end;    // see SvgWriter
+//  TPath64 = array of TPoint64;          // see SvgWriter
+//  TPaths64 = array of TPath64;          // see SvgWriter
+//  TRect64 = record l,t,r,b: Int64; end; // see SvgWriter
+{$ENDIF}
+
+  CInt64arr = array[0..$FFFF] of Int64;
+  CPath64 = ^CInt64arr;
+  CPath64arr = array[0..$FFFF] of CPath64;
+  CPaths64 = ^CPath64arr;
+
+//  TPointD = record X,Y: double; end;    // see SvgWriter
+//  TPathD = array of TPointD;            // see SvgWriter
+//  TPathsD = array of TPathD;            // see SvgWriter
+//  TRectD = record l,t,r,b: double; end; // see SvgWriter
+
+  CDblarr = array[0..$FFFF] of Double;
+  CPathD = ^CDblarr;
+  CPathDarr = array[0..$FFFF] of CPathD;
+  CPathsD = ^CPathDarr;
+
+
+  // nb: Pointer sizes could be 32bit or 64 bits
+  // and this will depend on how the DLL was compiled
+  // Obviously, DLLs compiled for 64bits won't work with
+  // applications compiled for 32bits (and vice versa).
+
+  PCPolyTree64 = ^CPolyTree64;
+  CPolyTree64 = packed record
+    polygon   : CPath64;        //pointer (32bit or 64bit)
+    isHole    : LongBool;       //32 bits
+    childCnt  : Int32;          //32 bits
+    childs    : PCPolyTree64;   //pointer (32bit or 64bit)
+  end;
+
+  PCPolyTreeD = ^CPolyTreeD;
+  CPolyTreeD = packed record
+    polygon   : CPathD;         //pointer (32bit or 64bit)
+    isHole    : LongBool;       //32 bits
+    childCnt  : Int32;          //32 bits
+    childs    : PCPolyTreeD;    //pointer (32bit or 64bit)
+  end;
+
+const
+{$IFDEF WIN64}
+  CLIPPER2_DLL = 'Clipper2_64.dll';
+{$ELSE}
+  CLIPPER2_DLL = 'Clipper2_32.dll';
+{$ENDIF}
+
+////////////////////////////////////////////////////////
+// Clipper2 DLL functions
+////////////////////////////////////////////////////////
+
+function Version(): PAnsiChar; cdecl;
+  external CLIPPER2_DLL name 'Version';
+
+procedure DisposeExportedCPath64(cp: CPath64); cdecl;
+  external CLIPPER2_DLL name 'DisposeExportedCPath64';
+procedure DisposeExportedCPaths64(var cps: CPaths64); cdecl;
+  external CLIPPER2_DLL name 'DisposeExportedCPaths64';
+procedure DisposeExportedCPathD(cp: CPathD); cdecl;
+  external CLIPPER2_DLL name 'DisposeExportedCPathD';
+procedure DisposeExportedCPathsD(var cp: CPathsD); cdecl;
+  external CLIPPER2_DLL name 'DisposeExportedCPathsD';
+procedure DisposeExportedCPolyTree64(var cpt: PCPolyTree64); cdecl;
+  external CLIPPER2_DLL name 'DisposeExportedCPolyTree64';
+procedure DisposeExportedCPolyTreeD(var cpt: PCPolyTreeD); cdecl;
+  external CLIPPER2_DLL name 'DisposeExportedCPolyTreeD';
+
+function BooleanOp64(cliptype: UInt8; fillrule: UInt8;
+  const subjects: CPaths64; const subjects_open: CPaths64;
+  const clips: CPaths64; out solution: CPaths64;
+  out solution_open: CPaths64;
+  preserve_collinear: boolean = true;
+  reverse_solution: boolean = false): integer;  cdecl;
+  external CLIPPER2_DLL name 'BooleanOp64';
+function BooleanOpPt64(cliptype: UInt8; fillrule: UInt8;
+  const subjects: CPaths64; const subjects_open: CPaths64;
+  const clips: CPaths64; out solution: PCPolyTree64;
+  out solution_open: CPaths64;
+  preserve_collinear: boolean = true;
+  reverse_solution: boolean = false): integer; cdecl;
+  external CLIPPER2_DLL name 'BooleanOpPt64';
+
+function BooleanOpD(cliptype: UInt8; fillrule: UInt8;
+  const subjects: CPathsD; const subjects_open: CPathsD;
+  const clips: CPathsD; out solution: CPathsD; out solution_open: CPathsD;
+  precision: integer = 2;
+  preserve_collinear: boolean = true;
+  reverse_solution: boolean = false): integer; cdecl;
+  external CLIPPER2_DLL name 'BooleanOpD';
+function BooleanOpPtD(cliptype: UInt8; fillrule: UInt8;
+  const subjects: CPathsD; const subjects_open: CPathsD;
+  const clips: CPathsD; out solution: PCPolyTreeD; out solution_open: CPathsD;
+  precision: integer = 2;
+  preserve_collinear: boolean = true;
+  reverse_solution: boolean = false): integer; cdecl;
+  external CLIPPER2_DLL name 'BooleanOpPtD';
+function InflatePaths64(const paths: CPaths64;
+  delta: double; jointype, endtype: UInt8; miter_limit: double = 2.0;
+  arc_tolerance: double = 0.0;
+  reverse_solution: Boolean = false): CPaths64; cdecl;
+  external CLIPPER2_DLL name 'InflatePaths64';
+function InflatePathsD(const paths: CPathsD;
+  delta: double; jointype, endtype: UInt8; precision: integer = 2;
+  miter_limit: double = 2.0; arc_tolerance: double = 0.0;
+  reverse_solution: Boolean = false): CPathsD; cdecl;
+  external CLIPPER2_DLL name 'InflatePathsD';
+
+function RectClip64(const rect: TRect64;
+  const paths: CPaths64): CPaths64; cdecl;
+  external CLIPPER2_DLL name 'RectClip64';
+function RectClipD(const rect: TRectD;
+  const paths: CPathsD; precision: integer = 2): CPathsD; cdecl;
+  external CLIPPER2_DLL name 'RectClipD';
+function RectClipLines64(const rect: TRect64;
+  const paths: CPaths64): CPaths64; cdecl;
+  external CLIPPER2_DLL name 'RectClipLines64';
+function RectClipLinesD(const rect: TRectD;
+  const paths: CPathsD; precision: integer = 2): CPathsD; cdecl;
+  external CLIPPER2_DLL name 'RectClipLinesD';
+
+////////////////////////////////////////////////////////
+// functions related to Clipper2 DLL structures
+////////////////////////////////////////////////////////
+
+procedure DisposeLocalCPath64(cp: CPath64);
+begin
+  FreeMem(cp);
+end;
+
+procedure DisposeLocalCPaths64(var cps: CPaths64);
+var
+  i, cnt: integer;
+begin
+  if cps = nil then Exit;
+  cnt := cps[0][1];
+  for i := 0 to cnt do //cnt +1
+    FreeMem(cps[i]);
+  FreeMem(cps);
+  cps := nil;
+end;
+
+procedure DisposeLocalCPathD(cp: CPathD);
+begin
+  FreeMem(cp);
+end;
+
+procedure DisposeLocalCPathsD(var cps: CPathsD);
+var
+  i, cnt: integer;
+begin
+  if not Assigned(cps) then Exit;
+  cnt := Round(cps[0][1]);
+  for i := 0 to cnt do //cnt +1
+    FreeMem(cps[i]);
+  FreeMem(cps);
+  cps := nil;
+end;
+
+////////////////////////////////////////////////////////
+// conversion functions
+////////////////////////////////////////////////////////
+
+function TPath64ToCPath64(const path: TPath64): CPath64;
+var
+  i, len: integer;
+begin
+  len := Length(path);
+  GetMem(Result, (2 + len * 2) * sizeof(Int64));
+  Result[0] := len;
+  Result[1] := 0;
+  for i := 0 to len -1 do
+  begin
+    Result[2 + i*2] := path[i].X;
+    Result[3 + i*2] := path[i].Y;
+  end;
+end;
+
+function TPathDToCPathD(const path: TPathD): CPathD;
+var
+  i, len: integer;
+begin
+  len := Length(path);
+  GetMem(Result, (2 + len * 2) * sizeof(Double));
+  Result[0] := len;
+  Result[1] := 0;
+  for i := 0 to len -1 do
+  begin
+    Result[2 + i*2] := path[i].X;
+    Result[3 + i*2] := path[i].Y;
+  end;
+end;
+
+function CPath64Cntrs(cnt1, cnt2: Int64): CPath64;
+begin
+  GetMem(Result, 2 * sizeof(Int64));
+  Result[0] := cnt1;
+  Result[1] := cnt2;
+end;
+
+function CPathDCntrs(cnt1, cnt2: integer): CPathD;
+begin
+  GetMem(Result, 2 * sizeof(double));
+  Result[0] := cnt1;
+  Result[1] := cnt2;
+end;
+
+function TPaths64ToCPaths64(const pp: TPaths64): CPaths64;
+var
+  i,j, len, len2: integer;
+begin
+  len := Length(pp);
+  len2 := len;
+  for i := 0 to len -1 do
+    if Length(pp[i]) = 0 then
+      dec(len2);
+  GetMem(Result, (1 + len2) * sizeof(Pointer));
+  Result[0] := CPath64Cntrs(0, len2); // add the counter 'path'
+  j := 1;
+  for i := 0 to len -1 do
+  begin
+    if Length(pp[i]) = 0 then continue;
+    Result[j] := TPath64ToCPath64(pp[i]);
+    inc(j);
+  end;
+end;
+
+function TPathsDToCPathsD(const pp: TPathsD): CPathsD;
+var
+  i,j, len, len2: integer;
+begin
+  len := Length(pp);
+  len2 := len;
+  for i := 0 to len -1 do
+    if Length(pp[i]) = 0 then
+      dec(len2);
+
+  GetMem(Result, (1 + len2) * sizeof(Pointer));
+  Result[0] := CPathDCntrs(0, len2); // add the counter 'path'
+  j := 1;
+  for i := 0 to len -1 do
+  begin
+    if Length(pp[i]) = 0 then continue;
+    Result[j] := TPathDToCPathD(pp[i]);
+    inc(j);
+  end;
+end;
+
+function CPath64ToPath64(cp: CPath64): TPath64;
+var
+  i: integer;
+  cnt: Int64;
+begin
+  if not Assigned(cp) then
+    cnt := 0 else
+    cnt := cp[0];
+  SetLength(Result, cnt);
+  for i := 0 to cnt -1 do
+  begin
+    Result[i].X := cp[2 + i*2];
+    Result[i].Y := cp[3 + i*2];
+  end;
+end;
+
+function CPathDToPathD(cp: CPathD): TPathD;
+var
+  i, cnt: integer;
+begin
+  if not Assigned(cp) then
+    cnt := 0 else
+    cnt := Round(cp[0]);
+  SetLength(Result, cnt);
+  for i := 0 to cnt -1 do
+  begin
+    Result[i].X := cp[2 + i*2];
+    Result[i].Y := cp[3 + i*2];
+  end;
+end;
+
+function CPaths64ToPaths64(cps: CPaths64): TPaths64;
+var
+  i: integer;
+  cnt: Int64;
+begin
+  if not Assigned(cps) then
+    cnt := 0 else
+    cnt := cps[0][1];
+  SetLength(Result, cnt);
+  for i := 1 to cnt do
+    Result[i-1] := CPath64ToPath64(cps[i]);
+end;
+
+function CPathsDToPathsD(cps: CPathsD): TPathsD;
+var
+  i, cnt: integer;
+begin
+  if not Assigned(cps) then
+    cnt := 0 else
+    cnt := Round(cps[0][1]);
+  SetLength(Result, cnt);
+  for i := 1 to cnt do
+    Result[i-1] := CPathDToPathD(cps[i]);
+end;
+
+{$IFNDEF USING_CLIPPER2_SRC}
+
+procedure AppendPath(var paths: TPaths64; const extra: TPath64); overload;
+var
+  len: Integer;
+begin
+  len := length(paths);
+  SetLength(paths, len +1);
+  paths[len] := extra;
+end;
+
+procedure AppendPath(var paths: TPathsD; const extra: TPathD); overload;
+var
+  len: Integer;
+begin
+  len := length(paths);
+  SetLength(paths, len +1);
+  paths[len] := extra;
+end;
+{$ENDIF}
+
+procedure CPt64Internal(cpt: PCPolyTree64; var paths: TPaths64);
+var
+  i: integer;
+  child: PCPolyTree64;
+begin
+  if Assigned(cpt.polygon) then
+    AppendPath(paths, CPath64ToPath64(cpt.polygon));
+  if cpt.childCnt = 0 then Exit;
+  child := cpt.childs;
+  for i := 0 to cpt.childCnt -1 do
+  begin
+    CPt64Internal(child, paths);
+    inc(child);
+  end;
+end;
+
+function CPolytree64ToPaths64(cpt: PCPolyTree64): TPaths64;
+var
+  i: integer;
+  child: PCPolyTree64;
+begin
+  Result := nil;
+  if not Assigned(cpt) or (cpt.childCnt = 0) then Exit;
+  child := cpt.childs;
+  for i := 0 to cpt.childCnt -1 do
+  begin
+    CPt64Internal(child, Result);
+    inc(child);
+  end;
+end;
+
+procedure CPtDInternal(cpt: PCPolyTreeD; var paths: TPathsD);
+var
+  i: integer;
+  child: PCPolyTreeD;
+begin
+  AppendPath(paths, CPathDToPathD(cpt.polygon));
+  if cpt.childCnt = 0 then Exit;
+  child := cpt.childs;
+  for i := 0 to cpt.childCnt -1 do
+  begin
+    CPtDInternal(child, paths);
+    inc(child);
+  end;
+end;
+
+function CPolytreeDToPathsD(cpt: PCPolyTreeD): TPathsD;
+var
+  i: integer;
+  child: PCPolyTreeD;
+begin
+  Result := nil;
+  if not Assigned(cpt) or (cpt.childCnt = 0) then Exit;
+  child := cpt.childs;
+  for i := 0 to cpt.childCnt -1 do
+  begin
+    CPtDInternal(child, Result);
+    inc(child);
+  end;
+end;
+
+////////////////////////////////////////////////////////
+// miscellaneous functions
+////////////////////////////////////////////////////////
+
+function MakePath64(vals: array of Int64): TPath64;
+var
+  i, len: integer;
+begin
+  len := Length(vals) div 2;
+  SetLength(Result, len);
+  for i := 0 to len -1 do
+  begin
+    Result[i].X := vals[i*2];
+    Result[i].Y := vals[i*2 +1];
+  end;
+end;
+
+function MakePathD(vals: array of double): TPathD;
+var
+  i, len: integer;
+begin
+  len := Length(vals) div 2;
+  SetLength(Result, len);
+  for i := 0 to len -1 do
+  begin
+    Result[i].X := vals[i*2];
+    Result[i].Y := vals[i*2 +1];
+  end;
+end;
+
+function MakeRandomPath(maxWidth, maxHeight, count: Integer;
+  margin: Integer = 10): TPath64;
+var
+  i: Integer;
+begin
+  setlength(Result, count);
+  for i := 0 to count -1 do with Result[i] do
+  begin
+    X := Random(maxWidth - 2 * margin) + margin;
+    Y := Random(maxHeight - 2 * margin) + margin;
+  end;
+end;
+
+function MakeRandomPathD(maxWidth, maxHeight, count: Integer;
+  margin: Integer = 10): TPathD;
+var
+  i: Integer;
+begin
+  setlength(Result, count);
+  for i := 0 to count -1 do with Result[i] do
+  begin
+    X := Random(maxWidth - 2 * margin) + margin;
+    Y := Random(maxHeight - 2 * margin) + margin;
+  end;
+end;
+
+function RectToPath64(const rec: TRect64): TPath64;
+begin
+  with rec do
+    Result := MakePath64([left, top, right, top,
+      right, bottom, left, bottom]);
+end;
+
+procedure WriteCPath64(p: CPath64);
+var
+  i, len: integer;
+  s: string;
+begin
+  len := p[0];
+  if len = 0 then Exit;
+  s := '';
+  for i := 0 to len -1 do
+    s := s +
+      inttostr(p[2 + i*2]) + ',' +
+      inttostr(p[3 + i*2]) + ', ';
+  WriteLn(s);
+end;
+
+procedure WritePath64(p: TPath64);
+var
+  i,len: integer;
+  s: string;
+begin
+  len := Length(p);
+  if len = 0 then Exit;
+  s := '';
+  for i := 0 to len -1 do
+    s := s +
+      inttostr(p[i].X) + ',' +
+      inttostr(p[i].Y) + ', ';
+  WriteLn(s);
+end;
+
+
+procedure WritePaths64(pp: TPaths64);
+var
+  i: integer;
+begin
+  for i := 0 to High(pp) do
+    WritePath64(pp[i]);
+end;
+
+procedure WritePathD(p: TPathD);
+var
+  i,len: integer;
+  s: string;
+begin
+  len := Length(p);
+  if len = 0 then Exit;
+  s := '';
+  for i := 0 to len -1 do
+    s := Format('%s%1.2f,%1.2f, ',
+      [s, p[i].X, p[i].Y]);
+  WriteLn(s);
+end;
+
+procedure WritePathsD(pp: TPathsD);
+var
+  i: integer;
+begin
+  for i := 0 to High(pp) do
+    WritePathD(pp[i]);
+end;
+
+procedure WriteCPolyTree64(pp: PCPolyTree64);
+var
+  i: integer;
+  child: PCPolyTree64;
+begin
+  if Assigned(pp.polygon) then
+    WriteCPath64(pp.polygon);
+  if pp.childCnt = 0 then Exit;
+  child := pp.childs;
+  for i := 0 to pp.childCnt -1 do
+  begin
+    WriteCPolyTree64(child);
+    inc(child);
+  end;
+end;
+
+procedure WriteCPathD(p: CPathD);
+var
+  i, len: integer;
+  s: string;
+begin
+  len := round(p[0]);
+  if len = 0 then Exit;
+  s := '';
+  for i := 0 to len -1 do
+    s := Format('%s%1.2f,%1.2f, ',
+      [s, p[2 + i*2], p[3 + i*2]]);
+  WriteLn(s);
+end;
+
+procedure WriteCPathsD(pp: CPathsD);
+var
+  i, len: integer;
+begin
+  len := Round(pp[0][1]);
+  for i := 1 to len do
+    WriteCPathD(pp[i]);
+end;
+
+procedure WriteCPolyTreeD(pp: PCPolyTreeD);
+var
+  i: integer;
+  child: PCPolyTreeD;
+begin
+  if Assigned(pp.polygon) then
+    WriteCPathD(pp.polygon);
+  if pp.childCnt = 0 then Exit;
+  child := pp.childs;
+  for i := 0 to pp.childCnt -1 do
+  begin
+    WriteCPolyTreeD(child);
+    inc(child);
+  end;
+end;
+
+procedure ShowSvgImage(const svgFilename: string);
+begin
+  ShellExecute(0, 'open',PChar(svgFilename), nil, nil, SW_SHOW);
+end;
+
+////////////////////////////////////////////////////////
+// test procedures
+////////////////////////////////////////////////////////
+
+const
+  displayWidth = 800;
+  displayHeight = 600;
+
+procedure Test_MegaStress(maxCnt: integer);
+var
+  i: integer;
+  sub, clp: TPathsD;
+  csub_local, cclp_local: CPathsD;
+  csol_extern, csolo_extern: CPathsD;
+begin
+  csol_extern   := nil;
+  csolo_extern  := nil;
+  SetLength(sub, 1);
+  SetLength(clp, 1);
+
+  for i := 1 to maxCnt do
+  begin
+    sub[0] := MakeRandomPathD(displayWidth, displayHeight, 50);
+    clp[0] := MakeRandomPathD(displayWidth, displayHeight, 50);
+    csub_local := TPathsDToCPathsD(sub);
+    cclp_local := TPathsDToCPathsD(clp);
+
+    BooleanOpD(Uint8(TClipType.ctIntersection),
+      Uint8(TFillRule.frNonZero),
+      csub_local, nil, cclp_local,
+      csol_extern, csolo_extern);
+
+    DisposeLocalCPathsD(csub_local);
+    DisposeLocalCPathsD(cclp_local);
+    DisposeExportedCPathsD(csol_extern);
+    DisposeExportedCPathsD(csolo_extern);
+    if i mod 100 = 0 then Write('.');
+  end;
+  WriteLn(#10'Passed!');
+end;
+
+procedure Test_Version();
+begin
+  Write(#10'Clipper2 DLL version: ');
+  WriteLn(Version);
+end;
+
+procedure Test_BooleanOp64(edgeCnt: integer);
+var
+  sub, clp: TPaths64;
+  csub_local, cclp_local: CPaths64;
+  csol_extern, csolo_extern: CPaths64;
+  svg: TSvgWriter;
+begin
+    // setup
+    csolo_extern := nil;
+    WriteLn(#10'Testing BooleanOp64');
+    SetLength(sub, 1);
+    sub[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt);
+    SetLength(clp, 1);
+    clp[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt);
+    // convert paths into DLL structures (will require local clean up)
+    csub_local := TPaths64ToCPaths64(sub);
+    cclp_local := TPaths64ToCPaths64(clp);
+
+    // do the DLL operation
+    BooleanOp64(Uint8(TClipType.ctIntersection),
+      Uint8(TFillRule.frNonZero),
+      csub_local, nil, cclp_local,
+      csol_extern, csolo_extern);
+
+    // optionally display result on the console
+    //WriteCPaths64(csol_extern);
+
+    // finally, display and clean up
+    svg := TSvgWriter.Create(frNonZero);
+    try
+      AddSubject(svg, sub);
+      AddClip(svg, clp);
+      AddSolution(svg, CPaths64ToPaths64(csol_extern));
+      SaveSvg(svg, 'BooleanOp64.svg', displayWidth, displayHeight);
+      ShowSvgImage('BooleanOp64.svg');
+    finally
+      svg.Free;
+    end;
+
+    DisposeLocalCPaths64(csub_local);
+    DisposeLocalCPaths64(cclp_local);
+    DisposeExportedCPaths64(csol_extern);
+    DisposeExportedCPaths64(csolo_extern);
+end;
+
+procedure Test_BooleanOpD(edgeCnt: integer);
+var
+  sub, clp: TPathsD;
+  csub_local, cclp_local: CPathsD;
+  csol_extern, csolo_extern: CPathsD;
+  svg: TSvgWriter;
+begin
+    // setup
+    csolo_extern := nil;
+    WriteLn(#10'Testing BooleanOpD');
+    SetLength(sub, 1);
+    sub[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt);
+    SetLength(clp, 1);
+    clp[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt);
+    // convert paths into DLL structures (will require local clean up)
+    csub_local := TPathsDToCPathsD(sub);
+    cclp_local := TPathsDToCPathsD(clp);
+
+    // do the DLL operation
+    BooleanOpD(Uint8(TClipType.ctIntersection),
+      Uint8(TFillRule.frNonZero),
+      csub_local, nil, cclp_local,
+      csol_extern, csolo_extern);
+
+    // optionally display result on the console
+    //WriteCPaths64(csol_extern);
+
+    // finally, display and clean up
+    svg := TSvgWriter.Create(frNonZero);
+    try
+      AddSubject(svg, sub);
+      AddClip(svg, clp);
+      AddSolution(svg, CPathsDToPathsD(csol_extern));
+      SaveSvg(svg, 'BooleanOpD.svg', displayWidth, displayHeight);
+      ShowSvgImage('BooleanOpD.svg');
+    finally
+      svg.Free;
+    end;
+
+    DisposeLocalCPathsD(csub_local);
+    DisposeLocalCPathsD(cclp_local);
+    DisposeExportedCPathsD(csol_extern);
+    DisposeExportedCPathsD(csolo_extern);
+end;
+
+procedure Test_BooleanOpPtD(edgeCnt: integer);
+var
+  sub, clp, sol: TPathsD;
+  csub_local, cclp_local: CPathsD;
+  csol_extern: PCPolyTreeD;
+  csolo_extern: CPathsD;
+  svg: TSvgWriter;
+begin
+    // setup
+    csolo_extern := nil;
+    WriteLn(#10'Testing BooleanOpPtD');
+    SetLength(sub, 1);
+    sub[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt);
+    SetLength(clp, 1);
+    clp[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt);
+    // convert paths into DLL structures (will require local clean up)
+    csub_local := TPathsDToCPathsD(sub);
+    cclp_local := TPathsDToCPathsD(clp);
+
+    // do the DLL operation
+    BooleanOpPtD(Uint8(TClipType.ctIntersection),
+      Uint8(TFillRule.frNonZero),
+      csub_local, nil, cclp_local,
+      csol_extern, csolo_extern);
+
+    // optionally display result on the console
+    //WriteCPaths64(csol_extern);
+
+    sol := CPolytreeDToPathsD(csol_extern);
+    DisposeExportedCPolyTreeD(csol_extern);
+    DisposeExportedCPathsD(csolo_extern);
+
+    // finally, display and clean up
+    svg := TSvgWriter.Create(frNonZero);
+    try
+      AddSubject(svg, sub);
+      AddClip(svg, clp);
+      AddSolution(svg, sol);
+      SaveSvg(svg, 'BooleanOpPtD.svg', displayWidth, displayHeight);
+      ShowSvgImage('BooleanOpPtD.svg');
+    finally
+      svg.Free;
+    end;
+
+    DisposeLocalCPathsD(csub_local);
+    DisposeLocalCPathsD(cclp_local);
+end;
+
+procedure Test_InflatePaths64(delta: double);
+var
+  sub: TPaths64;
+  csub_local: CPaths64;
+  csol_extern: CPaths64;
+  svg: TSvgWriter;
+begin
+    // setup
+    WriteLn(#10'Testing InflatePaths64');
+    SetLength(sub, 1);
+    sub[0] := MakeRandomPath(displayWidth, displayHeight, 7);
+    // convert path into DLL structure (will require local clean up)
+    csub_local := TPaths64ToCPaths64(sub);
+
+    // do the DLL operation
+    csol_extern := InflatePaths64(csub_local, delta,
+      UInt8(TJoinType.jtMiter), UInt8(TEndType.etPolygon), 2, 4);
+
+    // optionally display result on the console
+    //WriteCPaths64(csol_extern);
+
+    // finally, display and clean up
+    svg := TSvgWriter.Create(frNonZero);
+    try
+      AddSubject(svg, sub);
+      AddSolution(svg, CPaths64ToPaths64(csol_extern));
+      SaveSvg(svg, 'InflatePaths64.svg', displayWidth, displayHeight);
+      ShowSvgImage('InflatePaths64.svg');
+    finally
+      svg.Free;
+    end;
+
+    DisposeLocalCPaths64(csub_local);
+    DisposeExportedCPaths64(csol_extern);
+end;
+
+procedure Test_RectClip64(edgeCnt: integer);
+var
+  sub, clp: TPaths64;
+  csub_local: CPaths64;
+  csol_extern: CPaths64;
+  rec: TRect64;
+  svg: TSvgWriter;
+begin
+    // setup
+    WriteLn(#10'Testing RectClip64:');
+    SetLength(sub, 1);
+
+    sub[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt);
+    csub_local := TPaths64ToCPaths64(sub);
+
+    rec.Left := 80;
+    rec.Top := 80;
+    rec.Right := displayWidth - 80;
+    rec.Bottom := displayHeight -80;
+
+    // do the DLL operation
+    csol_extern := RectClip64(rec, csub_local);
+
+    // optionally display result on the console
+    //WriteCPaths64(csol_extern);
+
+    // finally, display and clean up
+
+    SetLength(clp, 1);
+    clp[0] := RectToPath64(rec);
+
+    svg := TSvgWriter.Create(frNonZero);
+    try
+      AddSubject(svg, sub);
+      AddClip(svg, clp);
+      AddSolution(svg, CPaths64ToPaths64(csol_extern));
+      SaveSvg(svg, 'RectClip64.svg', displayWidth, displayHeight);
+      ShowSvgImage('RectClip64.svg');
+    finally
+      svg.Free;
+    end;
+
+    DisposeLocalCPaths64(csub_local);
+    DisposeExportedCPaths64(csol_extern);
+end;
+
+procedure Test_RectClipLines64(edgeCnt: integer);
+var
+  sub, clp: TPaths64;
+  csub_local: CPaths64;
+  csolo_extern: CPaths64;
+  rec: TRect64;
+  svg: TSvgWriter;
+begin
+    // setup
+    WriteLn(#10'Testing RectClipLines64:');
+    SetLength(sub, 1);
+
+    sub[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt);
+    csub_local := TPaths64ToCPaths64(sub);
+
+    rec.Left := 80;
+    rec.Top := 80;
+    rec.Right := displayWidth - 80;
+    rec.Bottom := displayHeight -80;
+
+    // do the DLL operation
+    csolo_extern := RectClipLines64(rec, csub_local);
+
+    // optionally display result on the console
+    //WriteCPaths64(csol_extern);
+
+    // finally, display and clean up
+
+    SetLength(clp, 1);
+    clp[0] := RectToPath64(rec);
+
+    svg := TSvgWriter.Create(frNonZero);
+    try
+      AddOpenSubject(svg, sub);
+      AddClip(svg, clp); //rectangle
+      AddOpenSolution(svg, CPaths64ToPaths64(csolo_extern));
+      SaveSvg(svg, 'RectClipLines64.svg', displayWidth, displayHeight);
+      ShowSvgImage('RectClipLines64.svg');
+    finally
+      svg.Free;
+    end;
+
+    DisposeLocalCPaths64(csub_local);
+    DisposeExportedCPaths64(csolo_extern);
+end;
+
+procedure Test_Performance(lowThousand, hiThousand: integer);
+var
+  i: integer;
+  elapsed: double;
+  sub, clp: TPaths64;
+  csub_local, cclp_local: CPaths64;
+  csol_extern, csolo_extern: CPaths64;
+  svg: TSvgWriter;
+begin
+  csolo_extern := nil;
+  WriteLn(#10'Testing Performance');
+  for i := lowThousand to hiThousand do
+  begin
+    Write(format(#10' C++ DLL     - %d edges: ', [i*1000]));
+    // setup
+    SetLength(sub, 1);
+    sub[0] := MakeRandomPath(displayWidth, displayHeight, i*1000);
+    SetLength(clp, 1);
+    clp[0] := MakeRandomPath(displayWidth, displayHeight, i*1000);
+    // convert paths into DLL structures (will require local clean up)
+    csub_local := TPaths64ToCPaths64(sub);
+    cclp_local := TPaths64ToCPaths64(clp);
+
+    //using a code block for the timer
+    begin
+      InitTimer(elapsed);
+      // do the DLL operation
+      BooleanOp64(Uint8(TClipType.ctIntersection),
+        Uint8(TFillRule.frNonZero),
+        csub_local, nil, cclp_local,
+        csol_extern, csolo_extern);
+    end;
+    WriteLn(format('%1.3n secs', [elapsed]));
+
+{$IFDEF USING_CLIPPER2_SRC}
+    Write(format(' Pure delphi - %d edges: ', [i*1000]));
+    begin
+      InitTimer(elapsed);
+      Clipper.Intersect(sub, clp, Clipper.frNonZero);
+    end;
+    WriteLn(format('%1.3n secs', [elapsed]));
+{$ENDIF}
+
+    if i = hiThousand then
+    begin
+      svg := TSvgWriter.Create(frNonZero);
+      try
+        AddSubject(svg, sub);
+        AddClip(svg, clp);
+        AddSolution(svg, CPaths64ToPaths64(csol_extern));
+        SaveSvg(svg, 'Performance.svg', displayWidth, displayHeight);
+        ShowSvgImage('Performance.svg');
+      finally
+        svg.Free;
+      end;
+    end;
+
+    DisposeLocalCPaths64(csub_local);
+    DisposeLocalCPaths64(cclp_local);
+    DisposeExportedCPaths64(csol_extern);
+    DisposeExportedCPaths64(csolo_extern);
+
+  end; //bottom of for loop
+end;
+
+
+////////////////////////////////////////////////////////
+// main entry here
+////////////////////////////////////////////////////////
+
+var
+  s: string;
+begin
+  Randomize;
+  Test_Version();
+  Test_BooleanOp64(50);
+  Test_BooleanOpD(75);
+  Test_BooleanOpPtD(20);
+  Test_InflatePaths64(-10);
+  Test_RectClip64(25);
+  Test_RectClipLines64(25);
+  //Test_Performance(1, 5); // 1000 t0 5000
+  //Test_MegaStress(10000);
+
+  WriteLn(#10'Press Enter to quit.');
+  ReadLn(s);
+end.

+ 134 - 0
polygon.mod/clipper2/DLL/Delphi_TestApp/Test_DLL.dproj

@@ -0,0 +1,134 @@
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <ProjectGuid>{E2CCB41D-87F4-433F-9DAA-E38EF73B29B3}</ProjectGuid>
+        <MainSource>Test_DLL.dpr</MainSource>
+        <Base>True</Base>
+        <Config Condition="'$(Config)'==''">Debug</Config>
+        <TargetedPlatforms>2</TargetedPlatforms>
+        <AppType>Console</AppType>
+        <FrameworkType>None</FrameworkType>
+        <ProjectVersion>19.2</ProjectVersion>
+        <Platform Condition="'$(Platform)'==''">Win64</Platform>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
+        <Base_Win64>true</Base_Win64>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''">
+        <Cfg_1>true</Cfg_1>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''">
+        <Cfg_2>true</Cfg_2>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base)'!=''">
+        <DCC_E>false</DCC_E>
+        <DCC_F>false</DCC_F>
+        <DCC_K>false</DCC_K>
+        <DCC_N>false</DCC_N>
+        <DCC_S>false</DCC_S>
+        <DCC_ImageBase>00400000</DCC_ImageBase>
+        <SanitizedProjectName>Test_DLL</SanitizedProjectName>
+        <VerInfo_Locale>3081</VerInfo_Locale>
+        <VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName=</VerInfo_Keys>
+        <DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)</DCC_Namespace>
+        <Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
+        <Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base_Win64)'!=''">
+        <DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)</DCC_Namespace>
+        <BT_BuildType>Debug</BT_BuildType>
+        <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
+        <VerInfo_Locale>1033</VerInfo_Locale>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_1)'!=''">
+        <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
+        <DCC_DebugInformation>0</DCC_DebugInformation>
+        <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
+        <DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_2)'!=''">
+        <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
+        <DCC_Optimize>false</DCC_Optimize>
+        <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
+    </PropertyGroup>
+    <ItemGroup>
+        <DelphiCompile Include="$(MainSource)">
+            <MainSource>MainSource</MainSource>
+        </DelphiCompile>
+        <DCCReference Include="SvgWriter.pas"/>
+        <BuildConfiguration Include="Debug">
+            <Key>Cfg_2</Key>
+            <CfgParent>Base</CfgParent>
+        </BuildConfiguration>
+        <BuildConfiguration Include="Base">
+            <Key>Base</Key>
+        </BuildConfiguration>
+        <BuildConfiguration Include="Release">
+            <Key>Cfg_1</Key>
+            <CfgParent>Base</CfgParent>
+        </BuildConfiguration>
+    </ItemGroup>
+    <ProjectExtensions>
+        <Borland.Personality>Delphi.Personality.12</Borland.Personality>
+        <Borland.ProjectType/>
+        <BorlandProject>
+            <Delphi.Personality>
+                <Source>
+                    <Source Name="MainSource">Test_DLL.dpr</Source>
+                </Source>
+            </Delphi.Personality>
+            <Platforms>
+                <Platform value="Win64">True</Platform>
+            </Platforms>
+            <Deployment Version="3">
+                <DeployFile LocalName="Test_DLL.exe" Configuration="Debug" Class="ProjectOutput">
+                    <Platform Name="Win64">
+                        <RemoteName>Test_DLL.exe</RemoteName>
+                        <Overwrite>true</Overwrite>
+                    </Platform>
+                </DeployFile>
+                <DeployClass Name="ProjectUWPManifest">
+                    <Platform Name="Win32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win64">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="UWP_DelphiLogo150">
+                    <Platform Name="Win32">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win64">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="UWP_DelphiLogo44">
+                    <Platform Name="Win32">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win64">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
+            </Deployment>
+        </BorlandProject>
+        <ProjectFileVersion>12</ProjectFileVersion>
+    </ProjectExtensions>
+    <Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
+    <Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
+    <Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
+</Project>

+ 112 - 0
polygon.mod/clipper2/DLL/Delphi_TestApp/Timer.pas

@@ -0,0 +1,112 @@
+unit Timer;
+
+interface
+
+uses
+  Windows,
+  SysUtils;
+
+(*******************************************************************************
+*  Example:                                                                    *
+*                                                                              *
+*    var                                                                       *
+*      time: double;                                                           *
+*    begin                                                                     *
+*      ...                                                                     *
+*      begin                                                                   *
+*        InitTimer(time);                                                      *
+*        DoSomeLengthyOp();                                                    *
+*      end;                                                                    *
+*      Caption := Format('DoSomeLengthyOp took %1.3n secs', [time]);              *
+*      ....                                                                    *
+*    end;                                                                      *
+*******************************************************************************)
+
+{$IFDEF FPC}
+  {$MODE DELPHI}
+{$ENDIF}
+
+type
+
+  ITimer = interface
+  ['{A50173E8-6497-4F83-B4A8-9C0D5D709834}']
+  end;
+
+function InitTimer(out timeResult: double): ITimer;
+
+implementation
+
+type
+
+  PTimerData = ^TTimerData;
+  TTimerData = record
+    FVTable     : Pointer;
+    FRefCount   : Integer;
+    FResult     : PDouble;
+    FTimeFreq   : TLargeInteger;
+    FStartTime  : TLargeInteger;
+    FEndTime    : TLargeInteger;
+  end;
+
+{$IFDEF FPC}
+  TTimer = object
+{$ELSE}
+  TTimer = record
+{$ENDIF}
+  public
+    class function QueryInterface(Inst: PTimerData;
+      const IID: TGUID; out Obj): HResult; stdcall; static;
+    class function AddRef(Inst: PTimerData): Integer; stdcall; static;
+    class function Release(Inst: PTimerData): Integer; stdcall; static;
+  end;
+
+
+const
+  TimerVTable: array[0..2] of Pointer =
+  (
+    @TTimer.QueryInterface,
+    @TTimer.AddRef,
+    @TTimer.Release
+  );
+
+class function TTimer.QueryInterface(Inst: PTimerData;
+      const IID: TGUID; out Obj): HResult; stdcall;
+begin
+  Result:= E_NOINTERFACE;
+end;
+
+class function TTimer.AddRef(Inst: PTimerData): Integer; stdcall;
+begin
+  inc(inst.FRefCount);
+  Result := inst.FRefCount;
+  if inst.FRefCount <> 1 then Exit;
+  QueryPerformanceCounter(inst.FStartTime);
+end;
+
+class function TTimer.Release(Inst: PTimerData): Integer; stdcall;
+begin
+  QueryPerformanceCounter(inst.FEndTime);
+  with inst^ do
+  begin
+    dec(FRefCount);
+    Result := FRefCount;
+    if (Result <> 0) then Exit;
+    if assigned(FResult) then
+      FResult^ := (FEndTime - FStartTime)/FTimeFreq;
+  end;
+  Dispose(Inst);
+end;
+
+function InitTimer(out timeResult: double): ITimer;
+var
+  timer: PTimerData;
+begin
+  New(timer);
+  timer.FVTable:= @TimerVTable;
+  timer.FRefCount:= 0;
+  timer.FResult := @timeResult;
+  QueryPerformanceFrequency(timer.FTimeFreq);
+  Result := ITimer(timer);
+end;
+
+end.

+ 78 - 0
polygon.mod/clipper2/Delphi/BenchMark/DelphiBenchmark.dpr

@@ -0,0 +1,78 @@
+program DelphiBenchmark;
+
+{$APPTYPE CONSOLE}
+{$R *.res}
+
+uses
+  Windows,
+  ShellAPI,
+  SysUtils,
+  Classes,
+  Clipper in '..\Clipper2Lib\Clipper.pas',
+  Clipper.SVG in '..\Utils\Clipper.SVG.pas',
+  ClipMisc in '..\Utils\ClipMisc.pas',
+  Timer in '..\Utils\Timer.pas';
+
+var
+  s         : string;
+  i,j       : integer;
+  subj,clip : TPaths64;
+  solution  : TPaths64;
+  edgeCount : integer;
+  timerResult   : double;
+  timeTotal   : double;
+  /////////////////////////////
+  maxWidth  : integer   = 800;
+  maxHeight : integer   = 600;
+  minEdges  : integer   = 1000;
+  maxEdges  : integer   = 5000;
+  loopCount : integer   = 2;
+  /////////////////////////////
+begin
+  Randomize;
+  for j := (minEdges div 1000) to (maxEdges div 1000) do
+  begin
+    timeTotal := 0;
+    edgeCount := j * 1000;
+    for i := 1 to loopCount do
+    begin
+      if (i = 1) then
+      begin
+        WriteLn('Testing edge count: ' + Inttostr(edgeCount));
+        Write('Loop: 1 ');
+      end
+        else Write(inttostr(i) + ' ');
+
+      //make 2 random self-intersecting paths of 'edgeCount' length
+      setLength(subj, 1);
+      subj[0] := MakeRandomPath(maxWidth, maxHeight, edgeCount);
+      setLength(clip, 1);
+      clip[0] := MakeRandomPath(maxWidth, maxHeight, edgeCount);
+
+      begin
+        InitTimer(timerResult);
+        solution := Intersect(subj, clip, frNonZero);
+      end;
+      timeTotal := timeTotal + timerResult;
+
+    end; //bottom of loop;
+    WriteLn(Format('Average time: %1.0n msecs', [timeTotal*1000/loopCount]));
+    WriteLn('');
+  end; //bottom of edgecount loop
+
+  //now display the very last solution ...
+  with TSvgWriter.Create(frNonZero) do
+  try
+    AddText('sample', 0,0);
+    AddPaths(subj, false, $1000BBFF, $800099FF, 0.8);
+    AddPaths(clip, false, $12F99F00, $80FF9900, 0.8);
+    AddPaths(solution, false, $2000FF00, $FF006600, 0.8);
+    SaveToFile('test.svg');
+  finally
+    free;
+  end;
+  ShellExecute(0, nil, 'test.svg', Nil, Nil, SW_NORMAL);
+
+  WriteLn('Finished. Press Enter to exit.');
+  ReadLn(s);
+end.

+ 141 - 0
polygon.mod/clipper2/Delphi/BenchMark/DelphiBenchmark.dproj

@@ -0,0 +1,141 @@
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <ProjectGuid>{5FE618B1-2A40-45C1-B185-1352752C7484}</ProjectGuid>
+        <MainSource>DelphiBenchmark.dpr</MainSource>
+        <Base>True</Base>
+        <Config Condition="'$(Config)'==''">Release</Config>
+        <TargetedPlatforms>1</TargetedPlatforms>
+        <AppType>Console</AppType>
+        <FrameworkType>None</FrameworkType>
+        <ProjectVersion>19.2</ProjectVersion>
+        <Platform Condition="'$(Platform)'==''">Win64</Platform>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''">
+        <Cfg_1>true</Cfg_1>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win64)'!=''">
+        <Cfg_1_Win64>true</Cfg_1_Win64>
+        <CfgParent>Cfg_1</CfgParent>
+        <Cfg_1>true</Cfg_1>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''">
+        <Cfg_2>true</Cfg_2>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base)'!=''">
+        <DCC_E>false</DCC_E>
+        <DCC_F>false</DCC_F>
+        <DCC_K>false</DCC_K>
+        <DCC_N>false</DCC_N>
+        <DCC_S>false</DCC_S>
+        <DCC_ImageBase>00400000</DCC_ImageBase>
+        <SanitizedProjectName>DelphiBenchmark</SanitizedProjectName>
+        <VerInfo_Locale>3081</VerInfo_Locale>
+        <VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName=</VerInfo_Keys>
+        <DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;DUnitX.MemoryLeakMonitor;Winapi;Img32;$(DCC_Namespace)</DCC_Namespace>
+        <Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
+        <Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_1)'!=''">
+        <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
+        <DCC_DebugInformation>0</DCC_DebugInformation>
+        <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
+        <DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
+        <DCC_AssertionsAtRuntime>false</DCC_AssertionsAtRuntime>
+        <DCC_IOChecking>false</DCC_IOChecking>
+        <VerInfo_Locale>1033</VerInfo_Locale>
+        <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys>
+        <Manifest_File>(None)</Manifest_File>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
+        <DCC_AssertionsAtRuntime>false</DCC_AssertionsAtRuntime>
+        <DCC_ImportedDataReferences>false</DCC_ImportedDataReferences>
+        <DCC_IOChecking>false</DCC_IOChecking>
+        <VerInfo_Locale>1033</VerInfo_Locale>
+        <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
+        <Manifest_File>(None)</Manifest_File>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_2)'!=''">
+        <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
+        <DCC_Optimize>false</DCC_Optimize>
+        <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
+    </PropertyGroup>
+    <ItemGroup>
+        <DelphiCompile Include="$(MainSource)">
+            <MainSource>MainSource</MainSource>
+        </DelphiCompile>
+        <DCCReference Include="..\Clipper2Lib\Clipper.pas"/>
+        <DCCReference Include="..\Utils\Clipper.SVG.pas"/>
+        <DCCReference Include="..\Utils\ClipMisc.pas"/>
+        <DCCReference Include="..\Utils\Timer.pas"/>
+        <BuildConfiguration Include="Debug">
+            <Key>Cfg_2</Key>
+            <CfgParent>Base</CfgParent>
+        </BuildConfiguration>
+        <BuildConfiguration Include="Base">
+            <Key>Base</Key>
+        </BuildConfiguration>
+        <BuildConfiguration Include="Release">
+            <Key>Cfg_1</Key>
+            <CfgParent>Base</CfgParent>
+        </BuildConfiguration>
+    </ItemGroup>
+    <ProjectExtensions>
+        <Borland.Personality>Delphi.Personality.12</Borland.Personality>
+        <Borland.ProjectType/>
+        <BorlandProject>
+            <Delphi.Personality>
+                <Source>
+                    <Source Name="MainSource">DelphiBenchmark.dpr</Source>
+                </Source>
+                <Excluded_Packages>
+                    <Excluded_Packages Name="$(BDSBIN)\dcloffice2k270.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
+                    <Excluded_Packages Name="$(BDSBIN)\dclofficexp270.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
+                </Excluded_Packages>
+            </Delphi.Personality>
+            <Platforms>
+                <Platform value="Win64">True</Platform>
+            </Platforms>
+            <Deployment Version="3">
+                <DeployClass Name="ProjectUWPManifest">
+                    <Platform Name="Win32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win64">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="UWP_DelphiLogo150">
+                    <Platform Name="Win32">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win64">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="UWP_DelphiLogo44">
+                    <Platform Name="Win32">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
+            </Deployment>
+        </BorlandProject>
+        <ProjectFileVersion>12</ProjectFileVersion>
+    </ProjectExtensions>
+    <Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
+    <Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
+    <Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
+</Project>

+ 2074 - 0
polygon.mod/clipper2/Delphi/Clipper2Lib/Clipper.Core.pas

@@ -0,0 +1,2074 @@
+unit Clipper.Core;
+
+(*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  16 November 2022                                                *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Core Clipper Library module                                     *
+*              Contains structures and functions used throughout the library   *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************)
+
+{$I Clipper.inc}
+
+interface
+
+uses
+  SysUtils, Classes, Math;
+
+type
+  PPoint64  = ^TPoint64;
+  TPoint64  = record
+    X, Y: Int64;
+{$IFDEF USINGZ}
+    Z: Int64;
+{$ENDIF}
+  end;
+
+  PPointD   = ^TPointD;
+  TPointD   = record
+    X, Y: double;
+{$IFDEF USINGZ}
+    Z: Int64;
+{$ENDIF}
+  end;
+
+  // Path: a simple data structure representing a series of vertices, whether
+  // open (poly-line) or closed (polygon). Paths may be simple or complex (self
+  // intersecting). For simple polygons, consisting of a single non-intersecting
+  // path, path orientation is unimportant. However, for complex polygons and
+  // for overlapping polygons, various 'filling rules' define which regions will
+  // be inside (filled) and which will be outside (unfilled).
+
+  TPath64  = array of TPoint64;
+  TPaths64 = array of TPath64;
+  TArrayOfPaths = array of TPaths64;
+
+  TPathD = array of TPointD;
+  TPathsD = array of TPathD;
+  TArrayOfPathsD = array of TPathsD;
+
+  // The most commonly used filling rules for polygons are EvenOdd and NonZero.
+  // https://en.wikipedia.org/wiki/Even-odd_rule
+  // https://en.wikipedia.org/wiki/Nonzero-rule
+  TFillRule = (frEvenOdd, frNonZero, frPositive, frNegative);
+
+  TArrayOfBoolean = array of Boolean;
+  TArrayOfInteger = array of Integer;
+  TArrayOfDouble = array of double;
+
+  TRect64 = {$IFDEF RECORD_METHODS}record{$ELSE}object{$ENDIF}
+  private
+    function GetWidth: Int64; {$IFDEF INLINING} inline; {$ENDIF}
+    function GetHeight: Int64; {$IFDEF INLINING} inline; {$ENDIF}
+    function GetIsEmpty: Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+    function GetMidPoint: TPoint64; {$IFDEF INLINING} inline; {$ENDIF}
+  public
+    Left   : Int64;
+    Top    : Int64;
+    Right  : Int64;
+    Bottom : Int64;
+    function Contains(const pt: TPoint64; inclusive: Boolean = false): Boolean; overload;
+    function Contains(const rec: TRect64): Boolean; overload;
+    function Intersect(const rec: TRect64): TRect64;
+    function Intersects(const rec: TRect64): Boolean;
+    function AsPath: TPath64;
+    property Width: Int64 read GetWidth;
+    property Height: Int64 read GetHeight;
+    property IsEmpty: Boolean read GetIsEmpty;
+    property MidPoint: TPoint64 read GetMidPoint;
+  end;
+
+  TRectD = {$ifdef RECORD_METHODS}record{$else}object{$endif}
+  private
+    function GetWidth: double; {$IFDEF INLINING} inline; {$ENDIF}
+    function GetHeight: double; {$IFDEF INLINING} inline; {$ENDIF}
+    function GetIsEmpty: Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+    function GetMidPoint: TPointD; {$IFDEF INLINING} inline; {$ENDIF}
+  public
+    Left   : double;
+    Top    : double;
+    Right  : double;
+    Bottom : double;
+    function Contains(const pt: TPointD): Boolean; overload;
+    function Contains(const rec: TRectD): Boolean; overload;
+    function Intersects(const rec: TRectD): Boolean;
+    function AsPath: TPathD;
+    property Width: double read GetWidth;
+    property Height: double read GetHeight;
+    property IsEmpty: Boolean read GetIsEmpty;
+    property MidPoint: TPointD read GetMidPoint;
+  end;
+
+  TListEx = class
+  protected
+    fList       : TList;
+    fCount      : integer;
+  public
+    constructor Create(blockSize: integer = 8); virtual;
+    destructor Destroy; override;
+    procedure Clear; virtual;
+    function UnsafeGet(idx: integer): Pointer;
+    procedure UnsafeSwap(idx1, idx2: integer);
+    procedure UnsafeSet(idx: integer; val: Pointer);
+    function ListPtr: PPointer;
+    procedure Sort(Compare: TListSortCompare);
+    property Count: integer read fCount;
+  end;
+
+  TClipType = (ctNone, ctIntersection, ctUnion, ctDifference, ctXor);
+
+  TPointInPolygonResult = (pipInside, pipOutside, pipOn);
+
+  EClipper2LibException = class(Exception);
+
+function Area(const path: TPath64): Double; overload;
+function Area(const paths: TPaths64): Double; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function Area(const path: TPathD): Double; overload;
+function Area(const paths: TPathsD): Double; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function IsPositive(const path: TPath64): Boolean; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function IsPositive(const path: TPathD): Boolean; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function __Trunc(val: double): Int64; {$IFDEF INLINE} inline; {$ENDIF}
+
+function CrossProduct(const pt1, pt2, pt3: TPoint64): double; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function CrossProduct(const pt1, pt2, pt3: TPointD): double; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function CrossProduct(const vec1, vec2: TPointD): double; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function CrossProduct(vec1x, vec1y, vec2x, vec2y: double): double; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function DotProduct(const pt1, pt2, pt3: TPoint64): double;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function DistanceSqr(const pt1, pt2: TPoint64): double; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function DistanceSqr(const pt1, pt2: TPointD): double; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function DistanceFromLineSqrd(const pt, linePt1, linePt2: TPoint64): double; overload;
+function DistanceFromLineSqrd(const pt, linePt1, linePt2: TPointD): double; overload;
+
+function SegmentsIntersect(const s1a, s1b, s2a, s2b: TPoint64;
+  inclusive: Boolean = false): boolean; {$IFDEF INLINING} inline; {$ENDIF}
+
+function PointsEqual(const pt1, pt2: TPoint64): Boolean; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function PointsNearEqual(const pt1, pt2: TPointD): Boolean; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function PointsNearEqual(const pt1, pt2: TPointD; distanceSqrd: double): Boolean; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+{$IFDEF USINGZ}
+function Point64(const X, Y: Int64; Z: Int64 = 0): TPoint64; overload;
+{$IFDEF INLINING} inline; {$ENDIF}
+function Point64(const X, Y: Double; Z: Int64 = 0): TPoint64; overload;
+{$IFDEF INLINING} inline; {$ENDIF}
+function PointD(const X, Y: Double; Z: Int64 = 0): TPointD; overload;
+{$IFDEF INLINING} inline; {$ENDIF}
+{$ELSE}
+function Point64(const X, Y: Int64): TPoint64; overload; {$IFDEF INLINING} inline; {$ENDIF}
+function Point64(const X, Y: Double): TPoint64; overload; {$IFDEF INLINING} inline; {$ENDIF}
+function PointD(const X, Y: Double): TPointD; overload; {$IFDEF INLINING} inline; {$ENDIF}
+{$ENDIF}
+
+function Negate(const pt: TPointD): TPointD; {$IFDEF INLINING} inline; {$ENDIF}
+
+function Point64(const pt: TPointD): TPoint64; overload; {$IFDEF INLINING} inline; {$ENDIF}
+function PointD(const pt: TPoint64): TPointD; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function Rect64(const left, top, right, bottom: Int64): TRect64; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function Rect64(const recD: TRectD): TRect64; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function RectD(const left, top, right, bottom: double): TRectD; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function RectD(const rec64: TRect64): TRectD; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function GetBounds(const paths: TArrayOfPaths): TRect64; overload;
+function GetBounds(const paths: TPaths64): TRect64; overload;
+function GetBounds(const paths: TPathsD): TRectD; overload;
+function GetBounds(const path: TPath64): TRect64; overload;
+function GetBounds(const path: TPathD): TRectD; overload;
+
+function TranslatePoint(const pt: TPoint64; dx, dy: Int64): TPoint64; overload;
+function TranslatePoint(const pt: TPointD; dx, dy: double): TPointD; overload;
+
+procedure RotatePt(var pt: TPointD; const center: TPointD; sinA, cosA: double);
+procedure RotatePath(var path: TPathD; const center: TPointD; sinA, cosA: double);
+
+procedure InflateRect(var rec: TRect64; dx, dy: Int64); overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+procedure InflateRect(var rec: TRectD; dx, dy: double); overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function  UnionRect(const rec, rec2: TRect64): TRect64; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function  UnionRect(const rec, rec2: TRectD): TRectD; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function  RotateRect(const rec: TRect64; angleRad: double): TRect64; overload;
+function  RotateRect(const rec: TRectD; angleRad: double): TRectD; overload;
+procedure OffsetRect(var rec: TRect64; dx, dy: Int64); overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+procedure OffsetRect(var rec: TRectD; dx, dy: double); overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function ScaleRect(const rec: TRect64; scale: double): TRect64; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function ScaleRect(const rec: TRectD; scale: double): TRectD; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function ScalePoint(const pt: TPoint64; scale: double): TPointD;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function ScalePath(const path: TPath64; sx, sy: double): TPath64; overload;
+function ScalePath(const path: TPathD; sx, sy: double): TPath64; overload;
+function ScalePath(const path: TPath64; scale: double): TPath64; overload;
+function ScalePath(const path: TPathD; scale: double): TPath64; overload;
+
+function ScalePathD(const path: TPath64; sx, sy: double): TPathD; overload;
+function ScalePathD(const path: TPathD; sx, sy: double): TPathD; overload;
+function ScalePathD(const path: TPath64; scale: double): TPathD; overload;
+function ScalePathD(const path: TPathD; scale: double): TPathD; overload;
+
+function ScalePaths(const paths: TPaths64; sx, sy: double): TPaths64; overload;
+function ScalePaths(const paths: TPathsD; sx, sy: double): TPaths64; overload;
+function ScalePaths(const paths: TPaths64; scale: double): TPaths64; overload;
+function ScalePaths(const paths: TPathsD; scale: double): TPaths64; overload;
+
+function ScalePathsD(const paths: TPaths64; sx, sy: double): TPathsD; overload;
+function ScalePathsD(const paths: TPathsD; sx, sy: double): TPathsD; overload;
+function ScalePathsD(const paths: TPaths64; scale: double): TPathsD; overload;
+function ScalePathsD(const paths: TPathsD; scale: double): TPathsD; overload;
+
+function Path64(const pathD: TPathD): TPath64;
+function PathD(const path: TPath64): TPathD;
+function Paths64(const pathsD: TPathsD): TPaths64;
+function PathsD(const paths: TPaths64): TPathsD;
+
+function StripDuplicates(const path: TPath64; isClosedPath: Boolean = false): TPath64;
+function StripNearDuplicates(const path: TPathD;
+  minLenSqrd: double; isClosedPath: Boolean): TPathD;
+
+function ValueBetween(val, end1, end2: Int64): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function ValueEqualOrBetween(val, end1, end2: Int64): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function ReversePath(const path: TPath64): TPath64; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function ReversePath(const path: TPathD): TPathD; overload;
+function ReversePaths(const paths: TPaths64): TPaths64; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+function ReversePaths(const paths: TPathsD): TPathsD; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+procedure AppendPoint(var path: TPath64; const pt: TPoint64); overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+procedure AppendPoint(var path: TPathD; const pt: TPointD); overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function AppendPoints(const path, extra: TPath64): TPath64;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+procedure AppendPath(var paths: TPaths64; const extra: TPath64); overload;
+procedure AppendPath(var paths: TPathsD; const extra: TPathD); overload;
+procedure AppendPaths(var paths: TPaths64; const extra: TPaths64); overload;
+procedure AppendPaths(var paths: TPathsD; const extra: TPathsD); overload;
+
+function ArrayOfPathsToPaths(const ap: TArrayOfPaths): TPaths64;
+
+function GetIntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPoint64;
+  out ip: TPoint64): Boolean;
+
+function PointInPolygon(const pt: TPoint64; const polygon: TPath64): TPointInPolygonResult;
+
+function GetClosestPointOnSegment(const pt, seg1, seg2: TPoint64): TPoint64;
+  {$IFDEF INLINING} inline; {$ENDIF}
+
+function RamerDouglasPeucker(const path: TPath64; epsilon: double): TPath64; overload;
+function RamerDouglasPeucker(const paths: TPaths64; epsilon: double): TPaths64; overload;
+function RamerDouglasPeucker(const path: TPathD; epsilon: double): TPathD; overload;
+function RamerDouglasPeucker(const paths: TPathsD; epsilon: double): TPathsD; overload;
+
+procedure GetSinCos(angle: double; out sinA, cosA: double);
+function Ellipse(const rec: TRect64; steps: integer = 0): TPath64; overload;
+function Ellipse(const rec: TRectD; steps: integer = 0): TPathD; overload;
+
+procedure CheckPrecisionRange(var precision: integer);
+
+const
+  MaxInt64    = 9223372036854775807;
+  MaxCoord    = MaxInt64 div 4;
+  MinCoord    = - MaxCoord;
+  invalid64   = MaxInt64;
+  invalidD    = infinity;
+
+  NullPointD  : TPointD = (X: 0; Y: 0);
+  NullRect64  : TRect64 = (left: 0; top: 0; right: 0; Bottom: 0);
+  InvalidPt64 : TPoint64 = (X: invalid64; Y: invalid64);
+  InvalidPtD :  TPointD = (X: invalidD; Y: invalidD);
+
+  NullRectD   : TRectD = (left: 0; top: 0; right: 0; Bottom: 0);
+  Tolerance   : Double = 1.0E-12;
+
+implementation
+
+resourcestring
+  rsClipper_PrecisonErr = 'The decimal rounding value is invalid';
+
+//------------------------------------------------------------------------------
+// TRect64 methods ...
+//------------------------------------------------------------------------------
+
+function TRect64.GetWidth: Int64;
+begin
+  result := right - left;
+end;
+//------------------------------------------------------------------------------
+
+function TRect64.GetHeight: Int64;
+begin
+  result := bottom - top;
+end;
+//------------------------------------------------------------------------------
+
+function TRect64.GetIsEmpty: Boolean;
+begin
+  result := (bottom <= top) or (right <= left);
+end;
+//------------------------------------------------------------------------------
+
+function TRect64.GetMidPoint: TPoint64;
+begin
+  result := Point64((Left + Right) div 2, (Top + Bottom) div 2);
+end;
+//------------------------------------------------------------------------------
+
+function TRect64.Contains(const pt: TPoint64; inclusive: Boolean = false): Boolean;
+begin
+  if inclusive then
+    result := (pt.X >= Left) and (pt.X <= Right) and
+      (pt.Y >= Top) and (pt.Y <= Bottom)
+  else
+    result := (pt.X > Left) and (pt.X < Right) and
+      (pt.Y > Top) and (pt.Y < Bottom);
+end;
+//------------------------------------------------------------------------------
+
+function TRect64.Contains(const rec: TRect64): Boolean;
+begin
+  result := (rec.Left >= Left) and (rec.Right <= Right) and
+    (rec.Top >= Top) and (rec.Bottom <= Bottom);
+end;
+//------------------------------------------------------------------------------
+
+function TRect64.Intersects(const rec: TRect64): Boolean;
+begin
+  Result := (Max(Left, rec.Left) < Min(Right, rec.Right)) and
+    (Max(Top, rec.Top) < Min(Bottom, rec.Bottom));
+end;
+//------------------------------------------------------------------------------
+
+function TRect64.Intersect(const rec: TRect64): TRect64;
+begin
+  Result.Left := Max(Left, rec.Left);
+  Result.Top := Max(Top, rec.Top);
+  Result.Right := Min(Right, rec.Right);
+  Result.Bottom := Min(Bottom, rec.Bottom);
+  if IsEmpty then Result := NullRect64;
+end;
+//------------------------------------------------------------------------------
+
+function TRect64.AsPath: TPath64;
+begin
+  SetLength(Result, 4);
+  Result[0] := Point64(Left, Top);
+  Result[1] := Point64(Right, Top);
+  Result[2] := Point64(Right, Bottom);
+  Result[3] := Point64(Left, Bottom);
+end;
+
+//------------------------------------------------------------------------------
+// TRectD methods ...
+//------------------------------------------------------------------------------
+
+function TRectD.GetWidth: double;
+begin
+  result := right - left;
+end;
+//------------------------------------------------------------------------------
+
+function TRectD.GetHeight: double;
+begin
+  result := bottom - top;
+end;
+//------------------------------------------------------------------------------
+
+function TRectD.GetIsEmpty: Boolean;
+begin
+  result := (bottom <= top) or (right <= left);
+end;
+//------------------------------------------------------------------------------
+
+function TRectD.GetMidPoint: TPointD;
+begin
+  result := PointD((Left + Right) *0.5, (Top + Bottom) *0.5);
+end;
+//------------------------------------------------------------------------------
+
+function TRectD.Contains(const pt: TPointD): Boolean;
+begin
+  result := (pt.X > Left) and (pt.X < Right) and
+    (pt.Y > Top) and (pt.Y < Bottom);
+end;
+//------------------------------------------------------------------------------
+
+function TRectD.Contains(const rec: TRectD): Boolean;
+begin
+  result := (rec.Left >= Left) and (rec.Right <= Right) and
+    (rec.Top >= Top) and (rec.Bottom <= Bottom);
+end;
+//------------------------------------------------------------------------------
+
+function TRectD.Intersects(const rec: TRectD): Boolean;
+begin
+  Result := (Max(Left, rec.Left) < Min(Right, rec.Right)) and
+    (Max(Top, rec.Top) < Min(Bottom, rec.Bottom));
+end;
+//------------------------------------------------------------------------------
+
+function TRectD.AsPath: TPathD;
+begin
+  SetLength(Result, 4);
+  Result[0] := PointD(Left, Top);
+  Result[1] := PointD(Right, Top);
+  Result[2] := PointD(Right, Bottom);
+  Result[3] := PointD(Left, Bottom);
+end;
+
+//------------------------------------------------------------------------------
+// TListEx class
+//------------------------------------------------------------------------------
+
+constructor TListEx.Create(blockSize: integer = 8);
+begin
+  fList := TList.Create;
+end;
+//------------------------------------------------------------------------------
+
+destructor TListEx.Destroy;
+begin
+  Clear;
+  fList.Free;
+  inherited;
+end;
+//------------------------------------------------------------------------------
+
+procedure TListEx.Clear;
+begin
+  fCount := 0;
+  fList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure TListEx.Sort(Compare: TListSortCompare);
+begin
+  FList.Sort(Compare);
+end;
+//------------------------------------------------------------------------------
+
+function TListEx.UnsafeGet(idx: integer): Pointer;
+begin
+  Result := fList.List[idx];
+end;
+//------------------------------------------------------------------------------
+
+procedure TListEx.UnsafeSet(idx: integer; val: Pointer);
+begin
+  fList.List[idx] := val;
+end;
+//------------------------------------------------------------------------------
+
+procedure TListEx.UnsafeSwap(idx1, idx2: integer);
+var
+  p: Pointer;
+begin
+  p := UnsafeGet(idx1);
+  fList.List[idx1] := fList.List[idx2];
+  fList.List[idx2] := p;
+end;
+//------------------------------------------------------------------------------
+
+function TListEx.ListPtr: PPointer;
+begin
+  if fCount = 0 then
+    Result := nil else
+    Result := @fList.List[0];
+end;
+
+//------------------------------------------------------------------------------
+// Miscellaneous Functions ...
+//------------------------------------------------------------------------------
+
+procedure CheckPrecisionRange(var precision: integer);
+begin
+  if (precision < -8) or (precision > 8) then
+      Raise EClipper2LibException(rsClipper_PrecisonErr);
+end;
+//------------------------------------------------------------------------------
+
+procedure RaiseError(const msg: string); {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  raise EClipper2LibException.Create(msg);
+end;
+//------------------------------------------------------------------------------
+
+function PointsEqual(const pt1, pt2: TPoint64): Boolean;
+begin
+  Result := (pt1.X = pt2.X) and (pt1.Y = pt2.Y);
+end;
+//------------------------------------------------------------------------------
+
+function PointsNearEqual(const pt1, pt2: TPointD): Boolean;
+begin
+  Result := (Abs(pt1.X - pt2.X) < Tolerance) and
+    (Abs(pt1.Y - pt2.Y) < Tolerance);
+end;
+//------------------------------------------------------------------------------
+
+function PointsNearEqual(const pt1, pt2: TPointD; distanceSqrd: double): Boolean;
+begin
+  Result := Sqr(pt1.X - pt2.X) + Sqr(pt1.Y - pt2.Y) < distanceSqrd;
+end;
+//------------------------------------------------------------------------------
+
+function StripDuplicates(const path: TPath64; isClosedPath: Boolean): TPath64;
+var
+  i,j, len: integer;
+begin
+  len := length(path);
+  SetLength(Result, len);
+  if len = 0 then Exit;
+  Result[0] := path[0];
+  j := 0;
+  for i := 1 to len -1 do
+    if not PointsEqual(Result[j], path[i]) then
+    begin
+      inc(j);
+      Result[j] := path[i];
+    end;
+  if isClosedPath and PointsEqual(Result[0], path[j]) then dec(j);
+  SetLength(Result, j +1);
+end;
+//------------------------------------------------------------------------------
+
+function StripNearDuplicates(const path: TPathD;
+  minLenSqrd: double; isClosedPath: Boolean): TPathD;
+var
+  i,j, len: integer;
+begin
+  len := length(path);
+  SetLength(Result, len);
+  if len = 0 then Exit;
+
+  Result[0] := path[0];
+  j := 0;
+  for i := 1 to len -1 do
+    if not PointsNearEqual(Result[j], path[i], minLenSqrd) then
+    begin
+      inc(j);
+      Result[j] := path[i];
+    end;
+
+  if isClosedPath and
+    PointsNearEqual(Result[j], Result[0], minLenSqrd) then dec(j);
+  SetLength(Result, j +1);
+end;
+//------------------------------------------------------------------------------
+
+function ValueBetween(val, end1, end2: Int64): Boolean;
+begin
+  // nb: accommodates axis aligned between where end1 == end2
+  Result := ((val <> end1) = (val <> end2)) and
+    ((val > end1) = (val < end2));
+end;
+//------------------------------------------------------------------------------
+
+function ValueEqualOrBetween(val, end1, end2: Int64): Boolean;
+begin
+  Result := (val = end1) or (val = end2) or
+    ((val > end1) = (val < end2));
+end;
+//------------------------------------------------------------------------------
+
+function ScaleRect(const rec: TRect64; scale: double): TRect64;
+begin
+  Result.Left := Round(rec.Left * scale);
+  Result.Top := Round(rec.Top * scale);
+  Result.Right := Round(rec.Right * scale);
+  Result.Bottom := Round(rec.Bottom * scale);
+end;
+//------------------------------------------------------------------------------
+
+function ScaleRect(const rec: TRectD; scale: double): TRectD;
+begin
+  Result.Left := rec.Left * scale;
+  Result.Top := rec.Top * scale;
+  Result.Right := rec.Right * scale;
+  Result.Bottom := rec.Bottom * scale;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePoint(const pt: TPoint64; scale: double): TPointD;
+begin
+  Result.X := pt.X * scale;
+  Result.Y := pt.Y * scale;
+{$IFDEF USINGZ}
+  Result.Z := pt.Z;
+{$ENDIF}
+end;
+//------------------------------------------------------------------------------
+
+function ScalePath(const path: TPath64; sx, sy: double): TPath64;
+var
+  i,len: integer;
+begin
+  if sx = 0 then sx := 1;
+  if sy = 0 then sy := 1;
+  len := length(path);
+  setlength(result, len);
+  for i := 0 to len -1 do
+  begin
+    result[i].X := Round(path[i].X * sx);
+    result[i].Y := Round(path[i].Y * sy);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePath(const path: TPathD; sx, sy: double): TPath64;
+var
+  i,j, len: integer;
+begin
+  if sx = 0 then sx := 1;
+  if sy = 0 then sy := 1;
+  len := length(path);
+  setlength(result, len);
+  if len = 0 then Exit;
+  j := 1;
+  result[0].X := Round(path[0].X * sx);
+  result[0].Y := Round(path[0].Y * sy);
+  for i := 1 to len -1 do
+  begin
+    result[j].X := Round(path[i].X * sx);
+    result[j].Y := Round(path[i].Y * sy);
+    if (result[j].X <> result[j-1].X) or
+      (result[j].Y <> result[j-1].Y) then inc(j);
+  end;
+  setlength(result, j);
+end;
+//------------------------------------------------------------------------------
+
+function ScalePath(const path: TPath64; scale: double): TPath64;
+var
+  i,j, len: integer;
+begin
+  len := length(path);
+  setlength(result, len);
+  if len = 0 then Exit;
+  j := 1;
+  result[0].X := Round(path[0].X * scale);
+  result[0].Y := Round(path[0].Y * scale);
+  for i := 1 to len -1 do
+  begin
+    result[j].X := Round(path[i].X * scale);
+    result[j].Y := Round(path[i].Y * scale);
+    if (result[j].X <> result[j-1].X) or
+      (result[j].Y <> result[j-1].Y) then inc(j);
+  end;
+  setlength(result, j);
+end;
+//------------------------------------------------------------------------------
+
+function ScalePath(const path: TPathD; scale: double): TPath64;
+var
+  i,len: integer;
+begin
+  len := length(path);
+  setlength(result, len);
+  for i := 0 to len -1 do
+  begin
+    result[i].X := Round(path[i].X * scale);
+    result[i].Y := Round(path[i].Y * scale);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePaths(const paths: TPaths64; sx, sy: double): TPaths64;
+var
+  i,len: integer;
+begin
+  if sx = 0 then sx := 1;
+  if sy = 0 then sy := 1;
+  len := length(paths);
+  setlength(result, len);
+  for i := 0 to len -1 do
+    result[i] := ScalePath(paths[i], sx, sy);
+end;
+//------------------------------------------------------------------------------
+
+function ScalePaths(const paths: TPathsD; sx, sy: double): TPaths64;
+var
+  i,len: integer;
+begin
+  if sx = 0 then sx := 1;
+  if sy = 0 then sy := 1;
+  len := length(paths);
+  setlength(result, len);
+  for i := 0 to len -1 do
+    result[i] := ScalePath(paths[i], sx, sy);
+end;
+//------------------------------------------------------------------------------
+
+function ScalePathD(const path: TPath64; sx, sy: double): TPathD;
+var
+  i: integer;
+begin
+  setlength(result, length(path));
+  for i := 0 to high(path) do
+  begin
+    result[i].X := path[i].X * sx;
+    result[i].Y := path[i].Y * sy;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePathD(const path: TPathD; sx, sy: double): TPathD;
+var
+  i: integer;
+begin
+  setlength(result, length(path));
+  for i := 0 to high(path) do
+  begin
+    result[i].X := path[i].X * sx;
+    result[i].Y := path[i].Y * sy;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePathD(const path: TPath64; scale: double): TPathD;
+var
+  i: integer;
+begin
+  setlength(result, length(path));
+  for i := 0 to high(path) do
+  begin
+    result[i].X := path[i].X * scale;
+    result[i].Y := path[i].Y * scale;
+{$IFDEF USINGZ}
+    result[i].Z := path[i].Z;
+{$ENDIF}
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePathD(const path: TPathD; scale: double): TPathD;
+var
+  i: integer;
+begin
+  setlength(result, length(path));
+  for i := 0 to high(path) do
+  begin
+    result[i].X := path[i].X * scale;
+    result[i].Y := path[i].Y * scale;
+{$IFDEF USINGZ}
+    result[i].Z := path[i].Z;
+{$ENDIF}
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePathsD(const paths: TPaths64; sx, sy: double): TPathsD;
+var
+  i,j: integer;
+begin
+  if sx = 0 then sx := 1;
+  if sy = 0 then sy := 1;
+  setlength(result, length(paths));
+  for i := 0 to high(paths) do
+  begin
+    setlength(result[i], length(paths[i]));
+    for j := 0 to high(paths[i]) do
+    begin
+      result[i][j].X := (paths[i][j].X * sx);
+      result[i][j].Y := (paths[i][j].Y * sy);
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePathsD(const paths: TPathsD; sx, sy: double): TPathsD;
+var
+  i,j: integer;
+begin
+  if sx = 0 then sx := 1;
+  if sy = 0 then sy := 1;
+  setlength(result, length(paths));
+  for i := 0 to high(paths) do
+  begin
+    setlength(result[i], length(paths[i]));
+    for j := 0 to high(paths[i]) do
+    begin
+      result[i][j].X := paths[i][j].X * sx;
+      result[i][j].Y := paths[i][j].Y * sy;
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePaths(const paths: TPaths64; scale: double): TPaths64;
+var
+  i,j: integer;
+begin
+  setlength(result, length(paths));
+  for i := 0 to high(paths) do
+  begin
+    setlength(result[i], length(paths[i]));
+    for j := 0 to high(paths[i]) do
+    begin
+      result[i][j].X := Round(paths[i][j].X * scale);
+      result[i][j].Y := Round(paths[i][j].Y * scale);
+{$IFDEF USINGZ}
+      result[i][j].Z := paths[i][j].Z;
+{$ENDIF}
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePaths(const paths: TPathsD; scale: double): TPaths64;
+var
+  i,j: integer;
+begin
+  setlength(result, length(paths));
+  for i := 0 to high(paths) do
+  begin
+    setlength(result[i], length(paths[i]));
+    for j := 0 to high(paths[i]) do
+    begin
+      result[i][j].X := Round(paths[i][j].X * scale);
+      result[i][j].Y := Round(paths[i][j].Y * scale);
+{$IFDEF USINGZ}
+      result[i][j].Z := paths[i][j].Z;
+{$ENDIF}
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePathsD(const paths: TPaths64; scale: double): TPathsD; overload;
+var
+  i,j: integer;
+begin
+  setlength(result, length(paths));
+  for i := 0 to high(paths) do
+  begin
+    setlength(result[i], length(paths[i]));
+    for j := 0 to high(paths[i]) do
+    begin
+      result[i][j].X := paths[i][j].X * scale;
+      result[i][j].Y := paths[i][j].Y * scale;
+{$IFDEF USINGZ}
+      result[i][j].Z := paths[i][j].Z;
+{$ENDIF}
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ScalePathsD(const paths: TPathsD; scale: double): TPathsD; overload;
+var
+  i,j: integer;
+begin
+  setlength(result, length(paths));
+  for i := 0 to high(paths) do
+  begin
+    setlength(result[i], length(paths[i]));
+    for j := 0 to high(paths[i]) do
+    begin
+      result[i][j].X := paths[i][j].X * scale;
+      result[i][j].Y := paths[i][j].Y * scale;
+{$IFDEF USINGZ}
+      result[i][j].Z := paths[i][j].Z;
+{$ENDIF}
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function Path64(const pathD: TPathD): TPath64;
+var
+  i, len: integer;
+begin
+  len := Length(pathD);
+  setLength(Result, len);
+  for i := 0 to len -1 do
+  begin
+    Result[i].X := Round(pathD[i].X);
+    Result[i].Y := Round(pathD[i].Y);
+  end;
+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 Paths64(const pathsD: TPathsD): TPaths64;
+var
+  i, len: integer;
+begin
+  len := Length(pathsD);
+  setLength(Result, len);
+  for i := 0 to len -1 do
+    Result[i] := Path64(pathsD[i]);
+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 ReversePath(const path: TPath64): TPath64;
+var
+  i, highI: Integer;
+begin
+  highI := high(path);
+  SetLength(Result, highI +1);
+  for i := 0 to highI do
+    Result[i] := path[highI - i];
+end;
+//------------------------------------------------------------------------------
+
+function ReversePath(const path: TPathD): TPathD;
+var
+  i, highI: Integer;
+begin
+  highI := high(path);
+  SetLength(Result, highI +1);
+  for i := 0 to highI do
+    Result[i] := path[highI - i];
+end;
+//------------------------------------------------------------------------------
+
+function ReversePaths(const paths: TPaths64): TPaths64;
+var
+  i, j, highJ: Integer;
+begin
+  i := length(paths);
+  SetLength(Result, i);
+  for i := 0 to i -1 do
+  begin
+    highJ := high(paths[i]);
+    SetLength(Result[i], highJ+1);
+    for j := 0 to highJ do
+      Result[i][j] := paths[i][highJ - j];
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function ReversePaths(const paths: TPathsD): TPathsD;
+var
+  i, j, highJ: Integer;
+begin
+  i := length(paths);
+  SetLength(Result, i);
+  for i := 0 to i -1 do
+  begin
+    highJ := high(paths[i]);
+    SetLength(Result[i], highJ+1);
+    for j := 0 to highJ do
+      Result[i][j] := paths[i][highJ - j];
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure AppendPoint(var path: TPath64; const pt: TPoint64);
+var
+  len: Integer;
+begin
+  len := length(path);
+  SetLength(path, len +1);
+  path[len] := pt;
+end;
+//------------------------------------------------------------------------------
+
+function AppendPoints(const path, extra: TPath64): TPath64;
+var
+  len1, len2: Integer;
+begin
+  len1 := length(path);
+  len2 := length(extra);
+  SetLength(Result, len1 + len2);
+  if len1 > 0 then
+    Move(path[0], Result[0], len1 * sizeOf(TPoint64));
+  if len2 > 0 then
+    Move(extra[0], Result[len1], len2 * sizeOf(TPoint64));
+end;
+//------------------------------------------------------------------------------
+
+procedure AppendPoint(var path: TPathD; const pt: TPointD);
+var
+  len: Integer;
+begin
+  len := length(path);
+  SetLength(path, len +1);
+  path[len] := pt;
+end;
+//------------------------------------------------------------------------------
+
+procedure AppendPath(var paths: TPaths64; const extra: TPath64);
+var
+  len: Integer;
+begin
+  len := length(paths);
+  SetLength(paths, len +1);
+  paths[len] := extra;
+end;
+//------------------------------------------------------------------------------
+
+procedure AppendPath(var paths: TPathsD; const extra: TPathD);
+var
+  len: Integer;
+begin
+  len := length(paths);
+  SetLength(paths, len +1);
+  paths[len] := extra;
+end;
+//------------------------------------------------------------------------------
+
+procedure AppendPaths(var paths: TPaths64; const extra: TPaths64);
+var
+  i, len1, len2: Integer;
+begin
+  len1 := length(paths);
+  len2 := length(extra);
+  SetLength(paths, len1 + len2);
+  for i := 0 to len2 -1 do
+    paths[len1 + i] := extra[i];
+end;
+//------------------------------------------------------------------------------
+
+procedure AppendPaths(var paths: TPathsD; const extra: TPathsD);
+var
+  i, len1, len2: Integer;
+begin
+  len1 := length(paths);
+  len2 := length(extra);
+  SetLength(paths, len1 + len2);
+  for i := 0 to len2 -1 do
+    paths[len1 + i] := extra[i];
+end;
+//------------------------------------------------------------------------------
+
+function ArrayOfPathsToPaths(const ap: TArrayOfPaths): TPaths64;
+var
+  i,j,k, len, cnt: integer;
+begin
+  cnt := 0;
+  len := length(ap);
+  for i := 0 to len -1 do
+    inc(cnt, length(ap[i]));
+  k := 0;
+  setlength(result, cnt);
+  for i := 0 to len -1 do
+    for j := 0 to length(ap[i]) -1 do
+    begin
+      result[k] := ap[i][j];
+      inc(k);
+    end;
+end;
+//------------------------------------------------------------------------------
+
+{$IFDEF USINGZ}
+function Point64(const X, Y: Int64; Z: Int64): TPoint64;
+begin
+  Result.X := X;
+  Result.Y := Y;
+  Result.Z := Z;
+end;
+//------------------------------------------------------------------------------
+
+function Point64(const X, Y: Double; Z: Int64): TPoint64;
+begin
+  Result.X := Round(X);
+  Result.Y := Round(Y);
+  Result.Z := Z;
+end;
+//------------------------------------------------------------------------------
+
+function PointD(const X, Y: Double; Z: Int64): TPointD;
+begin
+  Result.X := X;
+  Result.Y := Y;
+  Result.Z := Z;
+end;
+//------------------------------------------------------------------------------
+
+function Point64(const pt: TPointD): TPoint64;
+begin
+  Result.X := Round(pt.X);
+  Result.Y := Round(pt.Y);
+  Result.Z := pt.Z;
+end;
+//------------------------------------------------------------------------------
+
+function PointD(const pt: TPoint64): TPointD;
+begin
+  Result.X := pt.X;
+  Result.Y := pt.Y;
+  Result.Z := pt.Z;
+end;
+//------------------------------------------------------------------------------
+
+{$ELSE}
+
+function Point64(const X, Y: Int64): TPoint64;
+begin
+  Result.X := X;
+  Result.Y := Y;
+end;
+//------------------------------------------------------------------------------
+
+function Point64(const X, Y: Double): TPoint64;
+begin
+  Result.X := Round(X);
+  Result.Y := Round(Y);
+end;
+//------------------------------------------------------------------------------
+
+function PointD(const X, Y: Double): TPointD;
+begin
+  Result.X := X;
+  Result.Y := Y;
+end;
+//------------------------------------------------------------------------------
+
+function Point64(const pt: TPointD): TPoint64;
+begin
+  Result.X := Round(pt.X);
+  Result.Y := Round(pt.Y);
+end;
+//------------------------------------------------------------------------------
+
+function PointD(const pt: TPoint64): TPointD;
+begin
+  Result.X := pt.X;
+  Result.Y := pt.Y;
+end;
+//------------------------------------------------------------------------------
+{$ENDIF}
+
+function Negate(const pt: TPointD): TPointD;
+begin
+  Result.X := -pt.X;
+  Result.Y := -pt.Y;
+end;
+//------------------------------------------------------------------------------
+
+function Rect64(const left, top, right, bottom: Int64): TRect64;
+begin
+  Result.Left   := left;
+  Result.Top    := top;
+  Result.Right  := right;
+  Result.Bottom := bottom;
+end;
+//------------------------------------------------------------------------------
+
+function Rect64(const recD: TRectD): TRect64;
+begin
+  Result.Left   := Floor(recD.left);
+  Result.Top    := Floor(recD.top);
+  Result.Right  := Ceil(recD.right);
+  Result.Bottom := Ceil(recD.bottom);
+end;
+//------------------------------------------------------------------------------
+
+function RectD(const left, top, right, bottom: double): TRectD;
+begin
+  Result.Left   := left;
+  Result.Top    := top;
+  Result.Right  := right;
+  Result.Bottom := bottom;
+end;
+//------------------------------------------------------------------------------
+
+function RectD(const rec64: TRect64): TRectD; overload;
+begin
+  Result.Left   := rec64.left;
+  Result.Top    := rec64.top;
+  Result.Right  := rec64.right;
+  Result.Bottom := rec64.bottom;
+end;
+//------------------------------------------------------------------------------
+
+function GetBounds(const paths: TArrayOfPaths): TRect64; overload;
+var
+  i,j,k: Integer;
+  p: PPoint64;
+begin
+  Result := Rect64(MaxInt64, MaxInt64, -MaxInt64, -MaxInt64);
+  for i := 0 to High(paths) do
+    for j := 0 to High(paths[i]) do
+      if Assigned(paths[i][j]) then
+      begin
+        p := @paths[i][j][0];
+        for k := 0 to High(paths[i][j]) 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 := NullRect64;
+end;
+//------------------------------------------------------------------------------
+
+function GetBounds(const paths: TPaths64): TRect64;
+var
+  i,j: Integer;
+  p: PPoint64;
+begin
+  Result := Rect64(MaxInt64, MaxInt64, -MaxInt64, -MaxInt64);
+  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 := NullRect64;
+end;
+//------------------------------------------------------------------------------
+
+function GetBounds(const paths: TPathsD): TRectD;
+var
+  i,j: Integer;
+  p: PPointD;
+begin
+  Result := RectD(MaxDouble, MaxDouble, -MaxDouble, -MaxDouble);
+  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 GetBounds(const path: TPath64): TRect64;
+var
+  i, len: Integer;
+  p: PPoint64;
+begin
+  len := Length(path);
+  if len = 0 then
+  begin
+    Result := NullRect64;
+    Exit;
+  end;
+
+  Result := Rect64(MaxInt64, MaxInt64, -MaxInt64, -MaxInt64);
+  p := @path[0];
+  for i := 0 to High(path) 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;
+//------------------------------------------------------------------------------
+
+function GetBounds(const path: TPathD): TRectD;
+var
+  i, len: Integer;
+  p: PPointD;
+begin
+  len := Length(path);
+  if len = 0 then
+  begin
+    Result := NullRectD;
+    Exit;
+  end;
+
+  Result := RectD(infinity, infinity, -infinity, -infinity);
+  p := @path[0];
+  for i := 0 to High(path) 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;
+//------------------------------------------------------------------------------
+
+function TranslatePoint(const pt: TPoint64; dx, dy: Int64): TPoint64;
+begin
+  Result.X := pt.X + dx;
+  Result.Y := pt.Y + dy;
+end;
+//------------------------------------------------------------------------------
+
+function TranslatePoint(const pt: TPointD; dx, dy: double): TPointD;
+begin
+  Result.X := pt.X + dx;
+  Result.Y := pt.Y + dy;
+end;
+//------------------------------------------------------------------------------
+
+procedure InflateRect(var rec: TRect64; dx, dy: Int64);
+begin
+  dec(rec.Left, dx);
+  inc(rec.Right, dx);
+  dec(rec.Top, dy);
+  inc(rec.Bottom, dy);
+end;
+//------------------------------------------------------------------------------
+
+procedure InflateRect(var rec: TRectD; dx, dy: double);
+begin
+  rec.Left := rec.Left - dx;
+  rec.Right := rec.Right + dx;
+  rec.Top := rec.Top - dy;
+  rec.Bottom := rec.Bottom + dy;
+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 RotateRect(const rec: TRectD; angleRad: double): TRectD;
+var
+  i: integer;
+  sinA, cosA: double;
+  cp: TPointD;
+  pts: TPathD;
+begin
+  setLength(pts, 4);
+  sinA := Sin(-angleRad);
+  cosA := cos(-angleRad);
+  cp.X := (rec.Right + rec.Left) / 2;
+  cp.Y := (rec.Bottom + rec.Top) / 2;
+  pts[0] := PointD(rec.Left, rec.Top);
+  pts[1] := PointD(rec.Right, rec.Top);
+  pts[2] := PointD(rec.Left, rec.Bottom);
+  pts[3] := PointD(rec.Right, rec.Bottom);
+  for i := 0 to 3 do RotatePt(pts[i], cp, sinA, cosA);
+  result.Left := pts[0].X;
+  result.Right := result.Left;
+  result.Top := pts[0].Y;
+  result.Bottom := result.Top;
+  for i := 1 to 3 do
+  begin
+    if pts[i].X < result.Left then result.Left := pts[i].X;
+    if pts[i].Y < result.Top then result.Top := pts[i].Y;
+    if pts[i].X > result.Right then result.Right := pts[i].X;
+    if pts[i].Y > result.Bottom then result.Bottom := pts[i].Y;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function RotateRect(const rec: TRect64; angleRad: double): TRect64;
+var
+  recD: TRectD;
+begin
+  recD := RectD(rec.Left, rec.Top, rec.Right, rec.Bottom);
+  recD := RotateRect(recD, angleRad);
+  result.Left := Floor(recD.Left);
+  result.Top := Floor(recD.Top);
+  result.Right := Ceil(recD.Right);
+  result.Bottom := Ceil(recD.Bottom);
+end;
+//------------------------------------------------------------------------------
+
+procedure OffsetRect(var rec: TRect64; dx, dy: Int64);
+begin
+  inc(rec.Left, dx); inc(rec.Top, dy);
+  inc(rec.Right, dx); inc(rec.Bottom, dy);
+end;
+//------------------------------------------------------------------------------
+
+procedure OffsetRect(var rec: TRectD; dx, dy: double);
+begin
+  rec.Left   := rec.Left   + dx;
+  rec.Right  := rec.Right  + dx;
+  rec.Top    := rec.Top    + dy;
+  rec.Bottom := rec.Bottom + dy;
+end;
+//------------------------------------------------------------------------------
+
+function UnionRect(const rec, rec2: TRect64): TRect64;
+begin
+  // nb: don't use rec.IsEmpty as this will
+  // reject open axis-aligned flat paths
+  if (rec.Width <= 0) and (rec.Height <= 0) then result := rec2
+  else if (rec2.Width <= 0) and (rec2.Height <= 0) then result := rec
+  else
+  begin
+    result.Left := min(rec.Left, rec2.Left);
+    result.Right := max(rec.Right, rec2.Right);
+    result.Top := min(rec.Top, rec2.Top);
+    result.Bottom := max(rec.Bottom, rec2.Bottom);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function UnionRect(const rec, rec2: TRectD): TRectD;
+begin
+  // nb: don't use rec.IsEmpty as this will
+  // reject open axis-aligned flat paths
+  if (rec.Width <= 0) and (rec.Height <= 0) then result := rec2
+  else if (rec2.Width <= 0) and (rec2.Height <= 0) then result := rec
+  else
+  begin
+    result.Left := min(rec.Left, rec2.Left);
+    result.Right := max(rec.Right, rec2.Right);
+    result.Top := min(rec.Top, rec2.Top);
+    result.Bottom := max(rec.Bottom, rec2.Bottom);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function Area(const path: TPath64): Double;
+var
+  i, highI: Integer;
+  d: double;
+  p1,p2: PPoint64;
+begin
+  // shoelace formula
+  Result := 0.0;
+  highI := High(path);
+  if highI < 2 then Exit;
+  p1 := @path[highI];
+  p2 := @path[0];
+  for i := 0 to highI do
+  begin
+    d := (p1.Y + p2.Y); // needed for Delphi7
+    Result := Result + d * (p1.X - p2.X);
+    p1 := p2; inc(p2);
+  end;
+  Result := Result * 0.5;
+end;
+//------------------------------------------------------------------------------
+
+function Area(const paths: TPaths64): Double;
+var
+  i: integer;
+begin
+  Result := 0;
+  for i := 0 to High(paths) do
+    Result := Result + Area(paths[i]);
+end;
+//------------------------------------------------------------------------------
+
+function Area(const path: TPathD): Double;
+var
+  i, highI: Integer;
+  p1,p2: PPointD;
+begin
+  // https://en.wikipedia.org/wiki/Shoelace_formula
+  Result := 0.0;
+  highI := High(path);
+  if highI < 2 then Exit;
+  p1 := @path[highI];
+  p2 := @path[0];
+  for i := 0 to highI do
+  begin
+    Result := Result + (p1.Y + p2.Y) * (p1.X - p2.X);
+    p1 := p2; inc(p2);
+  end;
+  Result := Result * 0.5;
+end;
+//------------------------------------------------------------------------------
+
+function Area(const paths: TPathsD): Double;
+var
+  i: integer;
+begin
+  Result := 0;
+  for i := 0 to High(paths) do
+    Result := Result + Area(paths[i]);
+end;
+//------------------------------------------------------------------------------
+
+function IsPositive(const path: TPath64): Boolean;
+begin
+  Result := (Area(path) >= 0);
+end;
+//------------------------------------------------------------------------------
+
+function IsPositive(const path: TPathD): Boolean;
+begin
+  Result := (Area(path) >= 0);
+end;
+//------------------------------------------------------------------------------
+
+function CrossProduct(const pt1, pt2, pt3: TPoint64): double;
+begin
+  result := CrossProduct(
+    pt2.X - pt1.X, pt2.Y - pt1.Y,
+    pt3.X - pt2.X, pt3.Y - pt2.Y);
+end;
+//------------------------------------------------------------------------------
+
+function CrossProduct(const pt1, pt2, pt3: TPointD): double;
+begin
+  result := CrossProduct(
+    pt2.X - pt1.X, pt2.Y - pt1.Y,
+    pt3.X - pt2.X, pt3.Y - pt2.Y);
+end;
+//------------------------------------------------------------------------------
+
+function CrossProduct(const vec1, vec2: TPointD): double;
+begin
+  result := (vec1.X * vec2.Y - vec1.Y * vec2.X);
+end;
+//------------------------------------------------------------------------------
+
+function CrossProduct(vec1x, vec1y, vec2x, vec2y: double): double;
+begin
+  result := (vec1x * vec2y - vec1y * vec2x);
+end;
+//------------------------------------------------------------------------------
+
+function DotProduct(const pt1, pt2, pt3: TPoint64): double;
+var
+  x1,x2,y1,y2: double; // avoids potential int overflow
+begin
+  x1 := pt2.X - pt1.X;
+  y1 := pt2.Y - pt1.Y;
+  x2 := pt3.X - pt2.X;
+  y2 := pt3.Y - pt2.Y;
+  result := (x1 * x2 + y1 * y2);
+end;
+//------------------------------------------------------------------------------
+
+function SqrInt64(val: Int64): double; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := val; // force conversion
+  Result := Result * Result;
+end;
+//------------------------------------------------------------------------------
+
+function DistanceSqr(const pt1, pt2: TPoint64): double;
+begin
+  Result := SqrInt64(pt1.X - pt2.X) + SqrInt64(pt1.Y - pt2.Y);
+end;
+//------------------------------------------------------------------------------
+
+function DistanceSqr(const pt1, pt2: TPointD): double;
+begin
+  Result := Sqr(pt1.X - pt2.X) + Sqr(pt1.Y - pt2.Y);
+end;
+//------------------------------------------------------------------------------
+
+function DistanceFromLineSqrd(const pt, linePt1, linePt2: TPoint64): double;
+var
+  a,b,c: double;
+begin
+  // perpendicular distance of point (x0,y0) = (a*x0 + b*y0 + C)/Sqrt(a*a + b*b)
+  // where ax + by +c = 0 is the equation of the line
+  // see https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
+	a := (linePt1.Y - linePt2.Y);
+	b := (linePt2.X - linePt1.X);
+	c := a * linePt1.X + b * linePt1.Y;
+	c := a * pt.x + b * pt.y - c;
+	Result := (c * c) / (a * a + b * b);
+end;
+//---------------------------------------------------------------------------
+
+function DistanceFromLineSqrd(const pt, linePt1, linePt2: TPointD): double;
+var
+  a,b,c: double;
+begin
+	a := (linePt1.Y - linePt2.Y);
+	b := (linePt2.X - linePt1.X);
+	c := a * linePt1.X + b * linePt1.Y;
+	c := a * pt.x + b * pt.y - c;
+	Result := (c * c) / (a * a + b * b);
+end;
+//---------------------------------------------------------------------------
+
+function CleanPath(const path: TPath64): TPath64;
+var
+  i,j, len: integer;
+  prev: TPoint64;
+begin
+  Result := nil;
+  len := Length(path);
+  while (len > 2) and
+   (CrossProduct(path[len-2], path[len-1], path[0]) = 0) do dec(len);
+  SetLength(Result, len);
+  if (len < 2) then Exit;
+  prev := path[len -1];
+  j := 0;
+  for i := 0 to len -2 do
+  begin
+    if CrossProduct(prev, path[i], path[i+1]) = 0 then Continue;
+    Result[j] := path[i];
+    inc(j);
+    prev := path[i];
+  end;
+  Result[j] := path[len -1];
+  SetLength(Result, j+1);
+end;
+//------------------------------------------------------------------------------
+
+function SegmentsIntersect(const s1a, s1b, s2a, s2b: TPoint64;
+  inclusive: Boolean): boolean;
+var
+  res1, res2, res3, res4: double;
+begin
+  if inclusive then
+  begin
+    //result can include segments that only touch
+    Result := false;
+    res1 := CrossProduct(s1a, s2a, s2b);
+    res2 := CrossProduct(s1b, s2a, s2b);
+    if (res1 * res2 > 0) then Exit;
+    res3 := CrossProduct(s2a, s1a, s1b);
+    res4 := CrossProduct(s2b, s1a, s1b);
+    if (res3 * res4 > 0) then Exit;
+    Result := (res1 <> 0) or (res2 <> 0) or
+      (res3 <> 0) or (res4 <> 0); // ensures not collinear
+  end else
+  begin
+    result := (CrossProduct(s1a, s2a, s2b) * CrossProduct(s1b, s2a, s2b) < 0) and
+      (CrossProduct(s2a, s1a, s1b) * CrossProduct(s2b, s1a, s1b) < 0);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function __Trunc(val: double): Int64; {$IFDEF INLINE} inline; {$ENDIF}
+var
+  exp: integer;
+  i64: UInt64 absolute val;
+const
+  shl51: UInt64 =  UInt64(1) shl 51;
+begin
+  Result := 0;
+  if i64 = 0 then Exit;
+  exp := Integer(Cardinal(i64 shr 52) and $7FF) - 1023;
+  //nb: when exp == 1024 then val == INF or NAN.
+  if exp < 0 then
+    Exit
+  else if exp > 52 then
+  begin
+    Result := ((i64 and $1FFFFFFFFFFFFF) shl (exp - 52)) or (UInt64(1) shl exp)
+  end else
+  begin
+    Result := ((i64 and $1FFFFFFFFFFFFF) shr (52 - exp)) or (UInt64(1) shl exp);
+    //the following line will round
+    //if (i64 and (shl51 shr (exp)) <> 0) then inc(Result);
+  end;
+  if val < 0 then Result := -Result;
+end;
+//------------------------------------------------------------------------------
+
+function CheckCastInt64(val: double): Int64; {$IFDEF INLINE} inline; {$ENDIF}
+begin
+  if (val >= MaxCoord) or (val <= MinCoord) then
+    Raise EClipper2LibException.Create('overflow error.');
+  Result := Trunc(val);
+  //Result := __Trunc(val);
+end;
+//------------------------------------------------------------------------------
+
+function GetIntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPoint64;
+  out ip: TPoint64): Boolean;
+var
+  dx1,dy1, dx2,dy2, qx,qy, cp: double;
+begin
+  // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
+  dy1 := (ln1b.y - ln1a.y);
+  dx1 := (ln1b.x - ln1a.x);
+  dy2 := (ln2b.y - ln2a.y);
+  dx2 := (ln2b.x - ln2a.x);
+  cp  := dy1 * dx2 - dy2 * dx1;
+  if (cp = 0.0) then
+  begin
+    Result := false;
+    Exit;
+  end;
+  qx := dx1 * ln1a.y - dy1 * ln1a.x;
+  qy := dx2 * ln2a.y - dy2 * ln2a.x;
+  ip.X := CheckCastInt64((dx1 * qy - dx2 * qx) / cp);
+  ip.Y := CheckCastInt64((dy1 * qy - dy2 * qx) / cp);
+  Result := (ip.x <> invalid64) and (ip.y <> invalid64);
+end;
+//------------------------------------------------------------------------------
+
+function PointInPolygon(const pt: TPoint64;
+  const polygon: TPath64): TPointInPolygonResult;
+var
+  i, len, val: Integer;
+  isAbove: Boolean;
+  d: Double; // used to avoid integer overflow
+  curr, prev, first, stop: PPoint64;
+begin
+  result := pipOutside;
+  len := Length(polygon);
+  if len < 3 then Exit;
+
+  i := len -1;
+  first := @polygon[0];
+
+  while (i >= 0) and (polygon[i].Y = pt.Y) do dec(i);
+  if i < 0 then Exit;
+  isAbove := polygon[i].Y < pt.Y;
+
+  Result := pipOn;
+  stop := @polygon[len -1];
+  inc(stop); // stop is just past the last point
+
+  curr := first;
+  val := 0;
+
+  while (curr <> stop) do
+  begin
+    if isAbove then
+    begin
+      while (curr <> stop) and (curr.Y < pt.Y) do inc(curr);
+      if (curr = stop) then break;
+    end else
+    begin
+      while (curr <> stop) and (curr.Y > pt.Y) do inc(curr);
+      if (curr = stop) then break;
+    end;
+
+    if curr = first then
+      prev := stop else
+      prev := curr;
+    dec(prev);
+
+    if (curr.Y = pt.Y) then
+    begin
+      if (curr.X = pt.X) or ((curr.Y = prev.Y) and
+        ((pt.X < prev.X) <> (pt.X < curr.X))) then Exit;
+      inc(curr);
+      Continue;
+    end;
+
+    if (pt.X < curr.X) and (pt.X < prev.X) then
+      // we're only interested in edges crossing on the left
+    else if((pt.X > prev.X) and (pt.X > curr.X)) then
+      val := 1 - val // toggle val
+    else
+    begin
+      d := CrossProduct(prev^, curr^, pt);
+      if d = 0 then Exit; // ie point on path
+      if (d < 0) = isAbove then val := 1 - val;
+    end;
+
+    isAbove := not isAbove;
+    inc(curr);
+  end;
+  if val = 0 then
+     result := pipOutside else
+     result := pipInside;
+end;
+//------------------------------------------------------------------------------
+
+procedure GetSinCos(angle: double; out sinA, cosA: double);
+  {$IFDEF INLINE} inline; {$ENDIF}
+{$IFNDEF FPC}
+var s, c: extended;
+{$ENDIF}
+begin
+{$IFDEF FPC}
+  Math.SinCos(angle, sinA, cosA);
+{$ELSE}
+  Math.SinCos(angle, s, c);
+  sinA := s; cosA := c;
+{$ENDIF}
+end;
+//------------------------------------------------------------------------------
+
+function Ellipse(const rec: TRect64; steps: integer): TPath64;
+begin
+  Result := Path64(Ellipse(RectD(rec), steps));
+end;
+//------------------------------------------------------------------------------
+
+function Ellipse(const rec: TRectD; steps: integer): TPathD;
+var
+  i: Integer;
+  sinA, cosA: double;
+  centre, radius, delta: TPointD;
+begin
+  result := nil;
+  if rec.IsEmpty then Exit;
+  with rec do
+  begin
+    centre := rec.MidPoint;
+    radius := PointD(Width * 0.5, Height  * 0.5);
+  end;
+  if (steps < 3) then
+    steps := Ceil(PI * sqrt(rec.width + rec.height));
+  GetSinCos(2 * Pi / Steps, sinA, cosA);
+  delta.x := cosA; delta.y := sinA;
+  SetLength(Result, Steps);
+  Result[0] := PointD(centre.X + radius.X, centre.Y);
+  for i := 1 to steps -1 do
+  begin
+    Result[i] := PointD(centre.X + radius.X * delta.x,
+      centre.Y + radius.y * delta.y);
+    delta :=  PointD(delta.X * cosA - delta.Y * sinA,
+      delta.Y * cosA + delta.X * sinA);
+  end; // rotates clockwise
+end;
+//------------------------------------------------------------------------------
+
+function GetClosestPointOnSegment(const pt, seg1, seg2: TPoint64): TPoint64;
+var
+  dx, dy, q: double;
+begin
+    if (seg1.X = seg2.X) and (seg1.Y = seg2.Y) then
+    begin
+      Result := seg1;
+      Exit;
+    end;
+    dx := (seg2.X - seg1.X);
+    dy := (seg2.Y - seg1.Y);
+    q := ((pt.X - seg1.X) * dx + (pt.Y - seg1.Y) * dy) / (Sqr(dx) + Sqr(dy));
+    if (q < 0) then q := 0
+    else if (q > 1) then q := 1;
+    Result := Point64(
+      seg1.X + Round(q * dx),
+      seg1.Y + Round(q * dy));
+end;
+//------------------------------------------------------------------------------
+
+function PerpendicDistFromLineSqrd(const pt, line1, line2: TPoint64): double; overload;
+var
+  a,b,c,d: double;
+begin
+  a := pt.X - line1.X;
+  b := pt.Y - line1.Y;
+  c := line2.X - line1.X;
+  d := line2.Y - line1.Y;
+  if (c = 0) and (d = 0) then
+    result := 0 else
+    result := Sqr(a * d - c * b) / (c * c + d * d);
+end;
+//------------------------------------------------------------------------------
+
+procedure RDP(const path: TPath64; startIdx, endIdx: integer;
+  epsilonSqrd: double; var boolArray: TArrayOfBoolean); overload;
+var
+  i, idx: integer;
+  d, maxD: double;
+begin
+  idx := 0;
+  maxD := 0;
+	while (endIdx > startIdx) and
+    PointsEqual(path[startIdx], path[endIdx]) do
+    begin
+      boolArray[endIdx] := false;
+      dec(endIdx);
+    end;
+  for i := startIdx +1 to endIdx -1 do
+  begin
+    // PerpendicDistFromLineSqrd - avoids expensive Sqrt()
+    d := PerpendicDistFromLineSqrd(path[i], path[startIdx], path[endIdx]);
+    if d <= maxD then Continue;
+    maxD := d;
+    idx := i;
+  end;
+  if maxD < epsilonSqrd then Exit;
+  boolArray[idx] := true;
+  if idx > startIdx + 1 then RDP(path, startIdx, idx, epsilonSqrd, boolArray);
+  if endIdx > idx + 1 then RDP(path, idx, endIdx, epsilonSqrd, boolArray);
+end;
+//------------------------------------------------------------------------------
+
+function PerpendicDistFromLineSqrd(const pt, line1, line2: TPointD): double; overload;
+var
+  a,b,c,d: double;
+begin
+  a := pt.X - line1.X;
+  b := pt.Y - line1.Y;
+  c := line2.X - line1.X;
+  d := line2.Y - line1.Y;
+  if (c = 0) and (d = 0) then
+    result := 0 else
+    result := Sqr(a * d - c * b) / (c * c + d * d);
+end;
+//------------------------------------------------------------------------------
+
+procedure RDP(const path: TPathD; startIdx, endIdx: integer;
+  epsilonSqrd: double; var boolArray: TArrayOfBoolean); overload;
+var
+  i, idx: integer;
+  d, maxD: double;
+begin
+  idx := 0;
+  maxD := 0;
+	while (endIdx > startIdx) and
+    PointsNearEqual(path[startIdx], path[endIdx]) do
+    begin
+      boolArray[endIdx] := false;
+      dec(endIdx);
+    end;
+  for i := startIdx +1 to endIdx -1 do
+  begin
+    // PerpendicDistFromLineSqrd - avoids expensive Sqrt()
+    d := PerpendicDistFromLineSqrd(path[i], path[startIdx], path[endIdx]);
+    if d <= maxD then Continue;
+    maxD := d;
+    idx := i;
+  end;
+  if maxD < epsilonSqrd then Exit;
+  boolArray[idx] := true;
+  if idx > startIdx + 1 then RDP(path, startIdx, idx, epsilonSqrd, boolArray);
+  if endIdx > idx + 1 then RDP(path, idx, endIdx, epsilonSqrd, boolArray);
+end;
+//------------------------------------------------------------------------------
+
+function RamerDouglasPeucker(const path: TPath64; epsilon: double): TPath64;
+var
+  i,j, len: integer;
+  boolArray: TArrayOfBoolean;
+begin
+  len := length(path);
+  if len < 5 then
+  begin
+    result := Copy(path, 0, len);
+    Exit;
+  end;
+  SetLength(boolArray, len); // already zero initialized
+  boolArray[0] := true;
+  boolArray[len -1] := true;
+  RDP(path, 0, len -1, Sqr(epsilon), boolArray);
+  j := 0;
+  SetLength(Result, len);
+  for i := 0 to len -1 do
+    if boolArray[i] then
+    begin
+      Result[j] := path[i];
+      inc(j);
+    end;
+  SetLength(Result, j);
+end;
+//------------------------------------------------------------------------------
+
+function RamerDouglasPeucker(const paths: TPaths64; epsilon: double): TPaths64;
+var
+  i, len: integer;
+begin
+  len := Length(paths);
+  SetLength(Result, len);
+  for i := 0 to len -1 do
+    Result[i] := RamerDouglasPeucker(paths[i], epsilon);
+end;
+//------------------------------------------------------------------------------
+
+function RamerDouglasPeucker(const path: TPathD; epsilon: double): TPathD; overload;
+var
+  i,j, len: integer;
+  boolArray: TArrayOfBoolean;
+begin
+  len := length(path);
+  if len < 5 then
+  begin
+    result := Copy(path, 0, len);
+    Exit;
+  end;
+  SetLength(boolArray, len); // already zero initialized
+  boolArray[0] := true;
+  boolArray[len -1] := true;
+  RDP(path, 0, len -1, Sqr(epsilon), boolArray);
+  j := 0;
+  SetLength(Result, len);
+  for i := 0 to len -1 do
+    if boolArray[i] then
+    begin
+      Result[j] := path[i];
+      inc(j);
+    end;
+  SetLength(Result, j);
+end;
+//------------------------------------------------------------------------------
+
+function RamerDouglasPeucker(const paths: TPathsD; epsilon: double): TPathsD; overload;
+var
+  i, len: integer;
+begin
+  len := Length(paths);
+  SetLength(Result, len);
+  for i := 0 to len -1 do
+    Result[i] := RamerDouglasPeucker(paths[i], epsilon);
+end;
+//------------------------------------------------------------------------------
+
+end.
+

+ 4546 - 0
polygon.mod/clipper2/Delphi/Clipper2Lib/Clipper.Engine.pas

@@ -0,0 +1,4546 @@
+unit Clipper.Engine;
+
+(*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  19 November 2022                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  This is the main polygon clipping module                        *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************)
+
+interface
+
+{$I Clipper.inc}
+
+uses
+  Classes, Math, Clipper.Core;
+
+type
+  //PathType:
+  //  1. only subject paths may be open
+  //  2. for closed paths, all boolean clipping operations except for
+  //     Difference are commutative. (In other words, subjects and clips
+  //     could be swapped and the same solution will be returned.)
+  TPathType = (ptSubject, ptClip);
+
+  // Vertex: a pre-clipping data structure. It is used to separate polygons
+  // into ascending and descending 'bounds' (or sides) that start at local
+  // minima and ascend to a local maxima, before descending again.
+
+  TVertexFlag = (vfOpenStart, vfOpenEnd, vfLocMax, vfLocMin);
+  TVertexFlags = set of TVertexFlag;
+
+  PVertex = ^TVertex;
+  TVertex = record
+    pt    : TPoint64;
+    next  : PVertex;
+    prev  : PVertex;
+    flags : TVertexFlags;
+  end;
+
+  PPLocalMinima = ^PLocalMinima;
+  PLocalMinima = ^TLocalMinima;
+  TLocalMinima = record
+    vertex    : PVertex;
+    polytype  : TPathType;
+    isOpen    : Boolean;
+  end;
+
+  TLocMinList = class(TListEx)
+  public
+    function Add: PLocalMinima;
+    procedure Clear; override;
+  end;
+
+  // forward declarations
+  PPOutRec = ^POutRec;
+  POutRec = ^TOutRec;
+  PJoiner = ^TJoiner;
+  PActive = ^TActive;
+  TPolyPathBase = class;
+  TPolyTree64   = class;
+  TPolyTreeD    = class;
+
+  // OutPt: vertex data structure for clipping solutions
+  POutPt = ^TOutPt;
+  TOutPt = record
+    pt       : TPoint64;
+    next     : POutPt;
+    prev     : POutPt;
+    outrec   : POutRec;
+    joiner   : PJoiner;
+  end;
+
+  TOutRecArray = array of POutRec;
+
+  // OutRec: path data structure for clipping solutions
+  TOutRec = record
+    idx      : Integer;
+    owner    : POutRec;
+    splits   : TOutRecArray;
+    frontE   : PActive;
+    backE    : PActive;
+    pts      : POutPt;
+    polypath : TPolyPathBase;
+    bounds   : TRect64;
+    path     : TPath64;
+    isOpen   : Boolean;
+  end;
+
+  TOutRecList = class(TListEx)
+  public
+    function Add: POutRec;
+    procedure Clear; override;
+  end;
+
+
+  // Joiner: structure used in merging "touching" solution polygons
+  TJoiner = record
+    idx   : integer;
+    op1   : POutPt;
+    op2   : POutPt;
+    next1 : PJoiner;
+    next2 : PJoiner;
+    nextH : PJoiner;
+  end;
+
+  ///////////////////////////////////////////////////////////////////
+  // Important: UP and DOWN here are premised on Y-axis positive down
+  // displays, which is the orientation used in Clipper's development.
+  ///////////////////////////////////////////////////////////////////
+
+  // Active: represents an edge in the Active Edge Table (Vatti's AET)
+  TActive = record
+    bot      : TPoint64;
+    top      : TPoint64;
+    currX    : Int64;
+    dx       : Double;        // inverse of edge slope (zero = vertical)
+    windDx   : Integer;       // wind direction (ascending: +1; descending: -1)
+    windCnt  : Integer;       // current wind count
+    windCnt2 : Integer;       // current wind count of the opposite TPolyType
+    outrec   : POutRec;
+    // AEL: 'active edge list' (Vatti's AET - active edge table)
+    //     a linked list of all edges (from left to right) that are present
+    //     (or 'active') within the current scanbeam (a horizontal 'beam' that
+    //     sweeps from bottom to top over the paths in the clipping operation).
+    prevInAEL: PActive;
+    nextInAEL: PActive;
+    // SEL: 'sorted edge list' (Vatti's ST - sorted table)
+    //     linked list used when sorting edges into their new positions at the
+    //     top of scanbeams, but also (re)used to process horizontals.
+    prevInSEL: PActive;
+    nextInSEL: PActive;
+    jump     : PActive;       // fast merge sorting (see BuildIntersectList())
+    vertTop  : PVertex;
+    locMin   : PLocalMinima;  // the bottom of an edge 'bound' (also Vatti)
+    isLeftB  : Boolean;
+  end;
+
+  // IntersectNode: a structure representing 2 intersecting edges.
+  // Intersections must be sorted so they are processed from the largest
+  // Y coordinates to the smallest while keeping edges adjacent.
+  PPIntersectNode = ^PIntersectNode;
+  PIntersectNode = ^TIntersectNode;
+  TIntersectNode = record
+    active1 : PActive;
+    active2 : PActive;
+    pt      : TPoint64;
+  end;
+
+  // Scanline: a virtual line representing current position
+  // while processing edges using a "sweep line" algorithm.
+  PScanLine = ^TScanLine;
+  TScanLine = record
+    y     : Int64;
+    next  : PScanLine;
+  end;
+
+  {$IFDEF USINGZ}
+  TZCallback64 = procedure (const bot1, top1, bot2, top2: TPoint64;
+    var intersectPt: TPoint64) of object;
+  TZCallbackD = procedure (const bot1, top1, bot2, top2: TPointD;
+    var intersectPt: TPointD) of object;
+  {$ENDIF}
+
+
+  // ClipperBase: abstract base
+  TClipperBase = class
+  {$IFDEF STRICT}strict{$ENDIF} private
+    FBotY               : Int64;
+    FScanLine           : PScanLine;
+    FCurrentLocMinIdx   : Integer;
+    FClipType           : TClipType;
+    FFillRule           : TFillRule;
+    FPreserveCollinear  : Boolean;
+    FIntersectList      : TList;
+    FOutRecList         : TOutRecList;
+    FLocMinList         : TLocMinList;
+    FVertexArrayList    : TList;
+    FJoinerList         : TList;
+    // FActives: see AEL above
+    FActives            : PActive;
+    // FSel: see SEL above.
+    // BUT also used to store horz. edges for later processing
+    FSel                : PActive;
+    FHorzTrials         : PJoiner;
+    FHasOpenPaths       : Boolean;
+    FLocMinListSorted   : Boolean;
+    FSucceeded          : Boolean;
+    FReverseSolution    : Boolean;
+  {$IFDEF USINGZ}
+    fDefaultZ           : Int64;
+    fZCallback          : TZCallback64;
+  {$ENDIF}
+    procedure Reset;
+    procedure InsertScanLine(const Y: Int64);
+    function  PopScanLine(out Y: Int64): Boolean;
+    function  PopLocalMinima(Y: Int64;
+      out localMinima: PLocalMinima): Boolean;
+    procedure DisposeScanLineList;
+    procedure DisposeOutRecsAndJoiners;
+    procedure DisposeVerticesAndLocalMinima;
+    function  IsContributingClosed(e: PActive): Boolean;
+    function  IsContributingOpen(e: PActive): Boolean;
+    procedure SetWindCountForClosedPathEdge(e: PActive);
+    procedure SetWindCountForOpenPathEdge(e: PActive);
+    procedure InsertLocalMinimaIntoAEL(const botY: Int64);
+    procedure InsertLeftEdge(e: PActive);
+    procedure PushHorz(e: PActive); {$IFDEF INLINING} inline; {$ENDIF}
+    function  PopHorz(out e: PActive): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+    function  StartOpenPath(e: PActive; const pt: TPoint64): POutPt;
+    procedure UpdateEdgeIntoAEL(var e: PActive);
+    function  IntersectEdges(e1, e2: PActive; pt: TPoint64): POutPt;
+    procedure DeleteEdges(var e: PActive);
+    procedure DeleteFromAEL(e: PActive);
+    procedure AdjustCurrXAndCopyToSEL(topY: Int64);
+    procedure DoIntersections(const topY: Int64);
+    procedure DisposeIntersectNodes;
+    procedure AddNewIntersectNode(e1, e2: PActive; topY: Int64);
+    function  BuildIntersectList(const topY: Int64): Boolean;
+    procedure ProcessIntersectList;
+    procedure SwapPositionsInAEL(e1, e2: PActive);
+    function  AddOutPt(e: PActive; const pt: TPoint64): POutPt;
+    function  AddLocalMinPoly(e1, e2: PActive;
+      const pt: TPoint64; IsNew: Boolean = false): POutPt;
+    function  AddLocalMaxPoly(e1, e2: PActive; const pt: TPoint64): POutPt;
+    procedure JoinOutrecPaths(e1, e2: PActive);
+    function  DoMaxima(e: PActive): PActive;
+    procedure DoHorizontal(horzEdge: PActive);
+    procedure DoTopOfScanbeam(Y: Int64);
+    procedure UpdateOutrecOwner(outRec: POutRec);
+    procedure AddTrialHorzJoin(op: POutPt);
+    procedure DeleteTrialHorzJoin(op: POutPt);
+    procedure ConvertHorzTrialsToJoins;
+    procedure AddJoin(op1, op2: POutPt);
+    procedure SafeDeleteOutPtJoiners(op: POutPt);
+    procedure DeleteJoin(joiner: PJoiner);
+    procedure ProcessJoinList;
+    function  ProcessJoin(joiner: PJoiner): POutRec;
+    function  ValidateClosedPathEx(var op: POutPt): Boolean;
+    procedure CompleteSplit(op1, op2: POutPt; OutRec: POutRec);
+    procedure SafeDisposeOutPts(var op: POutPt);
+    procedure CleanCollinear(outRec: POutRec);
+    procedure DoSplitOp(outrec: POutRec; splitOp: POutPt);
+    procedure FixSelfIntersects(outrec: POutRec);
+  protected
+    FUsingPolytree : Boolean;
+    procedure AddPath(const path: TPath64;
+      pathType: TPathType; isOpen: Boolean);
+    procedure AddPaths(const paths: TPaths64;
+      pathType: TPathType; isOpen: Boolean);
+    procedure ClearSolution; // unlike Clear, CleanUp preserves added paths
+    procedure ExecuteInternal(clipType: TClipType;
+      fillRule: TFillRule; usingPolytree: Boolean);
+    function DeepCheckOwner(outrec, owner: POutRec): Boolean;
+    function  BuildPaths(out closedPaths, openPaths: TPaths64): Boolean;
+    procedure BuildTree(polytree: TPolyPathBase; out openPaths: TPaths64);
+  {$IFDEF USINGZ}
+    procedure SetZ( e1, e2: PActive; var intersectPt: TPoint64);
+    property  ZCallback : TZCallback64 read fZCallback write fZCallback;
+    property  DefaultZ : Int64 READ fDefaultZ write fDefaultZ;
+  {$ENDIF}
+    property  Succeeded : Boolean read FSucceeded;
+  public
+    constructor Create; virtual;
+    destructor Destroy; override;
+    procedure Clear;
+    function GetBounds: TRect64;
+    property PreserveCollinear: Boolean read
+      FPreserveCollinear write FPreserveCollinear;
+    property ReverseSolution: Boolean read
+      FReverseSolution write FReverseSolution;
+  end;
+
+  TClipper64 = class(TClipperBase) // for integer coordinates
+  public
+    procedure AddSubject(const subject: TPath64); overload;
+    procedure AddSubject(const subjects: TPaths64); overload;
+    procedure AddOpenSubject(const subject: TPath64); overload;
+    procedure AddOpenSubject(const subjects: TPaths64); overload;
+    procedure AddClip(const clip: TPath64); overload;
+    procedure AddClip(const clips: TPaths64); overload;
+    function  Execute(clipType: TClipType; fillRule: TFillRule;
+      out closedSolutions: TPaths64): Boolean; overload; virtual;
+    function  Execute(clipType: TClipType; fillRule: TFillRule;
+      out closedSolutions, openSolutions: TPaths64): Boolean; overload; virtual;
+    function  Execute(clipType: TClipType; fillRule: TFillRule;
+      var solutionTree: TPolyTree64; out openSolutions: TPaths64): Boolean; overload; virtual;
+  {$IFDEF USINGZ}
+    property  ZCallback;
+  {$ENDIF}
+  end;
+
+  // PolyPathBase: ancestor of TPolyPath and TPolyPathD
+  TPolyPathBase = class
+  {$IFDEF STRICT}strict{$ENDIF} private
+    FParent     : TPolyPathBase;
+    FChildList  : TList;
+    function    GetChildCnt: Integer;
+    function    GetIsHole: Boolean;
+  protected
+    function    GetChild(index: Integer): TPolyPathBase;
+    function    AddChild(const path: TPath64): TPolyPathBase; virtual; abstract;
+    property    ChildList: TList read FChildList;
+    property    Parent: TPolyPathBase read FParent write FParent;
+  public
+    constructor Create;  virtual;
+    destructor  Destroy; override;
+    procedure   Clear; virtual;
+    property    IsHole: Boolean read GetIsHole;
+    property    Count: Integer read GetChildCnt;
+    property    Child[index: Integer]: TPolyPathBase read GetChild; default;
+  end;
+
+  TPolyPath64 = class(TPolyPathBase)
+  {$IFDEF STRICT}strict{$ENDIF} private
+    FPath : TPath64;
+    function    GetChild64(index: Integer): TPolyPath64;
+  protected
+    function AddChild(const path: TPath64): TPolyPathBase; override;
+  public
+    property Child[index: Integer]: TPolyPath64 read GetChild64; default;
+    property Polygon: TPath64 read FPath;
+  end;
+
+  // PolyTree: is intended as a READ-ONLY data structure to receive closed path
+  // solutions to clipping operations. While this structure is more complex than
+  // the alternative TPaths structure, it does model path ownership (ie paths
+  // that are contained by other paths). This will be useful to some users.
+  TPolyTree64 = class(TPolyPath64);
+
+  // FLOATING POINT POLYGON COORDINATES (D suffix to indicate double precision)
+  // To preserve numerical robustness, clipping must be done using integer
+  // coordinates. Consequently, polygons that are defined with floating point
+  // coordinates will need these converted into integer values together with
+  // scaling to achieve the desired floating point precision.
+
+  TClipperD = class(TClipperBase) // for floating point coordinates
+  {$IFDEF STRICT}strict{$ENDIF} private
+    FScale: double;
+    FInvScale: double;
+  {$IFDEF USINGZ}
+    fZCallback : TZCallbackD;
+    procedure ZCB(const bot1, top1, bot2, top2: TPoint64;
+      var intersectPt: TPoint64);
+    procedure CheckCallback;
+  {$ENDIF}
+  public
+    procedure AddSubject(const pathD: TPathD); overload;
+    procedure AddSubject(const pathsD: TPathsD); overload;
+    procedure AddOpenSubject(const pathD: TPathD); overload;
+    procedure AddOpenSubject(const pathsD: TPathsD); overload;
+    procedure AddClip(const pathD: TPathD); overload;
+    procedure AddClip(const pathsD: TPathsD); overload;
+    constructor Create(precision: integer = 2);
+      reintroduce; overload;
+    function Execute(clipType: TClipType; fillRule: TFillRule;
+      out closedSolutions: TPathsD): Boolean; overload;
+    function  Execute(clipType: TClipType; fillRule: TFillRule;
+      out closedSolutions, openSolutions: TPathsD): Boolean; overload;
+    function  Execute(clipType: TClipType; fillRule: TFillRule;
+      var solutionsTree: TPolyTreeD; out openSolutions: TPathsD): Boolean; overload;
+{$IFDEF USINGZ}
+    property  ZCallback : TZCallbackD read fZCallback write fZCallback;
+{$ENDIF}
+  end;
+
+  TPolyPathD = class(TPolyPathBase)
+  {$IFDEF STRICT}strict{$ENDIF} private
+    FPath   : TPathD;
+    function  GetChildD(index: Integer): TPolyPathD;
+  protected
+    FScale  : double;
+    function  AddChild(const path: TPath64): TPolyPathBase; override;
+  public
+    property  Polygon: TPathD read FPath;
+    property Child[index: Integer]: TPolyPathD read GetChildD; default;
+  end;
+
+  TPolyTreeD = class(TPolyPathD)
+  protected
+    procedure SetScale(value: double); // alternative to friend class
+  public
+    property  Scale: double read FScale;
+  end;
+
+implementation
+
+//OVERFLOWCHECKS OFF is a necessary workaround for a compiler bug that very
+//occasionally reports incorrect overflow errors in Delphi versions before 10.2.
+//see https://forums.embarcadero.com/message.jspa?messageID=871444
+{$OVERFLOWCHECKS OFF}
+
+resourcestring
+  rsClipper_PolyTreeErr = 'The TPolyTree parameter must be assigned.';
+  rsClipper_ClippingErr = 'Undefined clipping error';
+
+const
+  DefaultClipperDScale = 100;
+
+
+//------------------------------------------------------------------------------
+// TLocMinList class
+//------------------------------------------------------------------------------
+
+function TLocMinList.Add: PLocalMinima;
+begin
+  fList.Add(new(PLocalMinima));
+  Result := UnsafeGet(fCount);
+  inc(fCount);
+end;
+//------------------------------------------------------------------------------
+
+procedure TLocMinList.Clear;
+var
+  i: integer;
+  pplm: PPLocalMinima;
+begin
+  if fCount = 0 then Exit;
+  pplm := PPLocalMinima(ListPtr);
+  for i := 0 to fCount -1 do
+  begin
+    Dispose(pplm^);
+    inc(pplm);
+  end;
+  inherited;
+end;
+
+//------------------------------------------------------------------------------
+// TOutRecList class
+//------------------------------------------------------------------------------
+
+function TOutRecList.Add: POutRec;
+begin
+  fList.Add(new(POutRec));
+  Result := UnsafeGet(fCount);
+  FillChar(Result^, SizeOf(TOutRec), 0);
+  Result.idx := fCount;
+  inc(fCount);
+end;
+//------------------------------------------------------------------------------
+
+procedure TOutRecList.Clear;
+var
+  i: integer;
+  ppor: PPOutRec;
+  por: POutRec;
+  op, tmpPp: POutPt;
+begin
+  if fCount = 0 then Exit;
+  ppor := PPOutRec(ListPtr);
+  for i := 0 to fCount -1 do
+  begin
+    por := ppor^;
+    inc(ppor);
+    if Assigned(por.pts) then
+    begin
+      op := por.pts;
+      op.prev.next := nil;
+      while Assigned(op) do
+      begin
+        tmpPp := op;
+        op := op.next;
+        Dispose(tmpPp);
+      end;
+    end;
+    Dispose(por);
+  end;
+  inherited;
+end;
+
+//------------------------------------------------------------------------------
+// Miscellaneous Functions ...
+//------------------------------------------------------------------------------
+
+function UnsafeGet(List: TList; Index: Integer): Pointer;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := List.List[Index];
+end;
+//------------------------------------------------------------------------------
+
+function IsOpen(e: PActive): Boolean; overload; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := e.locMin.isOpen;
+end;
+//------------------------------------------------------------------------------
+
+function IsOpenEnd(e: PActive): Boolean; overload; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := e.locMin.isOpen and
+    (e.vertTop.flags * [vfOpenStart, vfOpenEnd] <> []);
+end;
+//------------------------------------------------------------------------------
+
+function IsHotEdge(e: PActive): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := assigned(e.outrec);
+end;
+//------------------------------------------------------------------------------
+
+function GetPrevHotEdge(e: PActive): PActive; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := e.prevInAEL;
+  while assigned(Result) and (IsOpen(Result) or not IsHotEdge(Result)) do
+    Result := Result.prevInAEL;
+end;
+//------------------------------------------------------------------------------
+
+function IsFront(e: PActive): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := (e = e.outrec.frontE);
+end;
+//------------------------------------------------------------------------------
+
+function IsValidPath(op: POutPt): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  result := assigned(op) and (op.next <> op);
+end;
+//------------------------------------------------------------------------------
+
+function PtsReallyClose(const pt1, pt2: TPoint64): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := (abs(pt1.X - pt2.X) < 2) and (abs(pt1.Y - pt2.Y) < 2);
+end;
+//------------------------------------------------------------------------------
+
+function IsVerySmallTriangle(op: POutPt): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  //also treat inconsequential polygons as invalid
+  Result := (op.next.next = op.prev) and
+    (PtsReallyClose(op.prev.pt, op.next.pt) or
+    PtsReallyClose(op.pt, op.next.pt) or
+    PtsReallyClose(op.pt, op.prev.pt));
+end;
+//------------------------------------------------------------------------------
+
+function IsValidClosedPath(op: POutPt): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  result := assigned(op) and (op.next <> op) and
+    (op.next <> op.prev) and not IsVerySmallTriangle(op);
+end;
+//------------------------------------------------------------------------------
+
+(*******************************************************************************
+*  Dx:                             0(90deg)                                    *
+*                                  |                                           *
+*               +inf (180deg) <--- o ---> -inf (0deg)                          *
+*******************************************************************************)
+
+function GetDx(const pt1, pt2: TPoint64): double;
+  {$IFDEF INLINING} inline; {$ENDIF}
+var
+  dy: Int64;
+begin
+  dy := (pt2.Y - pt1.Y);
+  if dy <> 0 then result := (pt2.X - pt1.X) / dy
+  else if (pt2.X > pt1.X) then result := NegInfinity
+  else result := Infinity;
+end;
+//------------------------------------------------------------------------------
+
+function TopX(e: PActive; const currentY: Int64): Int64; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  if (currentY = e.top.Y) or (e.top.X = e.bot.X) then Result := e.top.X
+  else if (currentY = e.bot.Y) then Result := e.bot.X
+  else Result := e.bot.X + Round(e.dx*(currentY - e.bot.Y));
+end;
+//------------------------------------------------------------------------------
+
+function IsHorizontal(e: PActive): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := (e.top.Y = e.bot.Y);
+end;
+//------------------------------------------------------------------------------
+
+function IsHeadingRightHorz(e: PActive): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := (e.dx = NegInfinity);
+end;
+//------------------------------------------------------------------------------
+
+function IsHeadingLeftHorz(e: PActive): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := (e.dx = Infinity);
+end;
+//------------------------------------------------------------------------------
+
+procedure SwapActives(var e1, e2: PActive); {$IFDEF INLINING} inline; {$ENDIF}
+var
+  e: PActive;
+begin
+  e := e1; e1 := e2; e2 := e;
+end;
+//------------------------------------------------------------------------------
+
+function GetPolyType(const e: PActive): TPathType;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := e.locMin.polytype;
+end;
+//------------------------------------------------------------------------------
+
+function IsSamePolyType(const e1, e2: PActive): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := e1.locMin.polytype = e2.locMin.polytype;
+end;
+//------------------------------------------------------------------------------
+
+procedure SetDx(e: PActive);  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  e.dx := GetDx(e.bot, e.top);
+end;
+//------------------------------------------------------------------------------
+
+function IsLeftBound(e: PActive): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := e.isLeftB;
+end;
+//------------------------------------------------------------------------------
+
+function NextVertex(e: PActive): PVertex; // ie heading (inverted Y-axis) "up"
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  if e.windDx > 0 then
+    Result := e.vertTop.next else
+    Result := e.vertTop.prev;
+end;
+//------------------------------------------------------------------------------
+
+//PrevPrevVertex: useful to get the (inverted Y-axis) top of the
+//alternate edge (ie left or right bound) during edge insertion.
+function PrevPrevVertex(e: PActive): PVertex;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  if e.windDx > 0 then
+    Result := e.vertTop.prev.prev else
+    Result := e.vertTop.next.next;
+end;
+//------------------------------------------------------------------------------
+
+function IsMaxima(vertex: PVertex): Boolean; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := vfLocMax in vertex.flags;
+end;
+//------------------------------------------------------------------------------
+
+function IsMaxima(e: PActive): Boolean; overload;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := vfLocMax in e.vertTop.flags;
+end;
+//------------------------------------------------------------------------------
+
+function GetCurrYMaximaVertex(e: PActive): PVertex;
+begin
+  // nb: function not safe with open paths
+  Result := e.vertTop;
+  if e.windDx > 0 then
+    while Result.next.pt.Y = Result.pt.Y do  Result := Result.next
+  else
+    while Result.prev.pt.Y = Result.pt.Y do  Result := Result.prev;
+  if not IsMaxima(Result) then Result := nil; // not a maxima
+end;
+//------------------------------------------------------------------------------
+
+function GetMaximaPair(e: PActive): PActive;
+begin
+  Result := e.nextInAEL;
+  while assigned(Result) do
+  begin
+    if Result.vertTop = e.vertTop then Exit;  // Found!
+    Result := Result.nextInAEL;
+  end;
+  Result := nil;
+end;
+//------------------------------------------------------------------------------
+
+function GetHorzMaximaPair(horz: PActive; maxVert: PVertex): PActive;
+begin
+  // we can't be sure whether the MaximaPair is on the left or right, so ...
+  Result := horz.prevInAEL;
+  while assigned(Result) and (Result.currX >= maxVert.pt.X) do
+  begin
+    if Result.vertTop = maxVert then Exit;  // Found!
+    Result := Result.prevInAEL;
+  end;
+  Result := horz.nextInAEL;
+  while assigned(Result) and (TopX(Result, horz.top.Y) <= maxVert.pt.X) do
+  begin
+    if Result.vertTop = maxVert then Exit;  // Found!
+    Result := Result.nextInAEL;
+  end;
+  Result := nil;
+end;
+//------------------------------------------------------------------------------
+
+function PointCount(pts: POutPt): Integer; {$IFDEF INLINING} inline; {$ENDIF}
+var
+  p: POutPt;
+begin
+  Result := 0;
+  if not Assigned(pts) then Exit;
+  p := pts;
+  repeat
+    Inc(Result);
+    p := p.next;
+  until p = pts;
+end;
+//------------------------------------------------------------------------------
+
+function GetRealOutRec(outRec: POutRec): POutRec;
+ {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := outRec;
+  while Assigned(Result) and not Assigned(Result.pts) do
+    Result := Result.owner;
+end;
+//------------------------------------------------------------------------------
+
+procedure UncoupleOutRec(e: PActive);
+var
+  outRec: POutRec;
+begin
+  if not Assigned(e.outrec) then Exit;
+  outRec := e.outrec;
+  outRec.frontE.outrec := nil;
+  outRec.backE.outrec := nil;
+  outRec.frontE := nil;
+  outRec.backE := nil;
+end;
+//------------------------------------------------------------------------------
+
+procedure AddPathsToVertexList(const paths: TPaths64;
+  polyType: TPathType; isOpen: Boolean;
+  vertexList: TList; LocMinList: TLocMinList);
+var
+  i, j, len, totalVerts: integer;
+  p: PPoint64;
+  v, va0, vaCurr, vaPrev: PVertex;
+  ascending, ascending0: Boolean;
+
+  procedure AddLocMin(vert: PVertex);
+  var
+    lm: PLocalMinima;
+  begin
+    if vfLocMin in vert.flags then Exit;  // ie already added
+    Include(vert.flags, vfLocMin);
+    lm := LocMinList.Add;
+    lm.vertex := vert;
+    lm.polytype := polyType;
+    lm.isOpen := isOpen;
+  end;
+  //---------------------------------------------------------
+
+begin
+  // count the total (maximum) number of vertices required
+  totalVerts := 0;
+  for i := 0 to High(paths) do
+    totalVerts := totalVerts + Length(paths[i]);
+  if (totalVerts = 0) then Exit;
+  // allocate memory
+  GetMem(v, sizeof(TVertex) * totalVerts);
+  vertexList.Add(v);
+
+  for i := 0 to High(paths) do
+  begin
+    len := Length(paths[i]);
+    if len = 0 then Continue;
+    p := @paths[i][0];
+    va0 := v; vaCurr := v;
+    vaCurr.pt := p^;
+    vaCurr.prev := nil;
+    inc(p);
+    vaCurr.flags := [];
+    vaPrev := vaCurr;
+    inc(vaCurr);
+    for j := 1 to len -1 do
+    begin
+      if PointsEqual(vaPrev.pt, p^) then
+      begin
+        inc(p);
+        Continue; // skips duplicates
+      end;
+      vaPrev.next := vaCurr;
+      vaCurr.prev := vaPrev;
+      vaCurr.pt := p^;
+      vaCurr.flags := [];
+      vaPrev := vaCurr;
+      inc(vaCurr);
+      inc(p);
+    end;
+    if not Assigned(vaPrev.prev) then Continue;
+    if not isOpen and PointsEqual(vaPrev.pt, va0.pt) then
+      vaPrev := vaPrev.prev;
+
+    vaPrev.next := va0;
+    va0.prev := vaPrev;
+    v := vaCurr; // ie get ready for next path
+    if isOpen and (va0.next = va0) then Continue;
+
+    // now find and assign local minima
+    if (isOpen) then
+    begin
+      vaCurr := va0.next;
+      while (vaCurr <> va0) and (vaCurr.pt.Y = va0.pt.Y) do
+        vaCurr := vaCurr.next;
+      ascending := vaCurr.pt.Y <= va0.pt.Y;
+      if (ascending) then
+      begin
+        va0.flags := [vfOpenStart];
+        AddLocMin(va0);
+      end
+      else
+        va0.flags := [vfOpenStart, vfLocMax];
+    end else
+    begin
+      // closed path
+      vaPrev := va0.prev;
+      while (vaPrev <> va0) and (vaPrev.pt.Y = va0.pt.Y) do
+        vaPrev := vaPrev.prev;
+      if (vaPrev = va0) then
+        Continue; // only open paths can be completely flat
+      ascending := vaPrev.pt.Y > va0.pt.Y;
+    end;
+
+    ascending0 := ascending;
+    vaPrev := va0;
+    vaCurr := va0.next;
+    while (vaCurr <> va0) do
+    begin
+      if (vaCurr.pt.Y > vaPrev.pt.Y) and ascending then
+      begin
+        Include(vaPrev.flags, vfLocMax);
+        ascending := false;
+      end
+      else if (vaCurr.pt.Y < vaPrev.pt.Y) and not ascending then
+      begin
+        ascending := true;
+        AddLocMin(vaPrev);
+      end;
+      vaPrev := vaCurr;
+      vaCurr := vaCurr.next;
+    end;
+
+    if (isOpen) then
+    begin
+      Include(vaPrev.flags, vfOpenEnd);
+      if ascending then
+        Include(vaPrev.flags, vfLocMax) else
+        AddLocMin(vaPrev);
+    end
+    else if (ascending <> ascending0) then
+    begin
+      if (ascending0) then AddLocMin(vaPrev)
+      else Include(vaPrev.flags, vfLocMax);
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function BuildPath(op: POutPt; reverse, isOpen: Boolean;
+  out path: TPath64): Boolean;
+var
+  i,j, cnt: integer;
+begin
+  cnt := PointCount(op);
+  if (cnt < 3) and (not isOpen or (Cnt < 2)) then
+  begin
+    Result := false;
+    Exit;
+  end;
+
+  if (cnt = 3) and IsVerySmallTriangle(op) then
+  begin
+    Result := false;
+    Exit;
+  end;
+
+  setLength(path, cnt);
+  if reverse then
+  begin
+    path[0] := op.pt;
+    op := op.prev;
+  end else
+  begin
+    op := op.next;
+    path[0] := op.pt;
+    op := op.next;
+  end;
+  j := 0;
+  for i := 0 to cnt -2 do
+  begin
+    if not PointsEqual(path[j], op.pt) then
+    begin
+      inc(j);
+      path[j] := op.pt;
+    end;
+    if reverse then op := op.prev else op := op.next;
+  end;
+
+  setLength(path, j+1);
+  if isOpen then
+    Result := (j > 0) else
+    Result := (j > 1);
+end;
+//------------------------------------------------------------------------------
+
+function DisposeOutPt(op: POutPt): POutPt;
+begin
+  if op.next = op then
+    Result := nil else
+    Result := op.next;
+  op.prev.next := op.next;
+  op.next.prev := op.prev;
+  Dispose(Op);
+end;
+//------------------------------------------------------------------------------
+
+procedure DisposeOutPts(op: POutPt); {$IFDEF INLINING} inline; {$ENDIF}
+var
+  tmpPp: POutPt;
+begin
+  op.prev.next := nil;
+  while Assigned(op) do
+  begin
+    tmpPp := op;
+    op := op.next;
+    Dispose(tmpPp);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function LocMinListSort(item1, item2: Pointer): Integer;
+var
+  q: Int64;
+  lm1: PLocalMinima absolute item1;
+  lm2: PLocalMinima absolute item2;
+begin
+  q := lm2.vertex.pt.Y - lm1.vertex.pt.Y;
+  if q < 0 then
+    Result := -1
+  else if q > 0 then
+    Result := 1
+  else
+  begin
+    q := lm2.vertex.pt.X - lm1.vertex.pt.X;
+    if q < 0 then Result := 1
+    else if q > 0 then Result := -1
+    else Result := 0;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure SetSides(outRec: POutRec; startEdge, endEdge: PActive);
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  outRec.frontE := startEdge;
+  outRec.backE := endEdge;
+end;
+//------------------------------------------------------------------------------
+
+procedure SwapOutRecs(e1, e2: PActive);
+var
+  or1, or2: POutRec;
+  e: PActive;
+begin
+  or1 := e1.outrec;
+  or2 := e2.outrec;
+  if (or1 = or2) then
+  begin
+    // nb: at least one edge is 'hot'
+    e := or1.frontE;
+    or1.frontE := or1.backE;
+    or1.backE := e;
+    Exit;
+  end;
+  if assigned(or1) then
+  begin
+    if e1 = or1.frontE then
+      or1.frontE := e2 else
+      or1.backE := e2;
+  end;
+  if assigned(or2) then
+  begin
+    if e2 = or2.frontE then
+      or2.frontE := e1 else
+      or2.backE := e1;
+  end;
+  e1.outrec := or2;
+  e2.outrec := or1;
+end;
+//------------------------------------------------------------------------------
+
+function Area(op: POutPt): Double;
+var
+  op2: POutPt;
+  d: double;
+begin
+  // https://en.wikipedia.org/wiki/Shoelace_formula
+  Result := 0;
+  if not Assigned(op) then Exit;
+  op2 := op;
+  repeat
+    d := (op2.prev.pt.Y + op2.pt.Y);
+    Result := Result + d * (op2.prev.pt.X - op2.pt.X);
+    op2 := op2.next;
+  until op2 = op;
+  Result := Result * 0.5;
+end;
+//------------------------------------------------------------------------------
+
+function AreaTriangle(const pt1, pt2, pt3: TPoint64): double;
+var
+  d1,d2,d3,d4,d5,d6: double;
+begin
+  d1 := (pt3.y + pt1.y);
+  d2 := (pt3.x - pt1.x);
+  d3 := (pt1.y + pt2.y);
+  d4 := (pt1.x - pt2.x);
+  d5 := (pt2.y + pt3.y);
+  d6 := (pt2.x - pt3.x);
+  result := d1 * d2 + d3 *d4 + d5 *d6;
+end;
+//------------------------------------------------------------------------------
+
+procedure ReverseOutPts(op: POutPt);
+var
+  op1, op2: POutPt;
+begin
+  if not Assigned(op) then Exit;
+  op1 := op;
+  repeat
+    op2:= op1.next;
+    op1.next := op1.prev;
+    op1.prev := op2;
+    op1 := op2;
+  until op1 = op;
+end;
+//------------------------------------------------------------------------------
+
+function OutrecIsAscending(hotEdge: PActive): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := (hotEdge = hotEdge.outrec.frontE);
+end;
+//------------------------------------------------------------------------------
+
+procedure SwapFrontBackSides(outRec: POutRec); {$IFDEF INLINING} inline; {$ENDIF}
+var
+  e2: PActive;
+begin
+  // while this proc. is needed for open paths
+  // it's almost never needed for closed paths
+  e2 := outRec.frontE;
+  outRec.frontE := outRec.backE;
+  outRec.backE := e2;
+  outRec.pts := outRec.pts.next;
+end;
+//------------------------------------------------------------------------------
+
+function EdgesAdjacentInAEL(node: PIntersectNode): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+var
+  active1, active2: PActive;
+begin
+  active1 := node.active1;
+  active2 := node.active2;
+  Result := (active1.nextInAEL = active2) or (active1.prevInAEL = active2);
+end;
+//------------------------------------------------------------------------------
+
+function TestJoinWithPrev1(e: PActive): Boolean;
+begin
+  // this is marginally quicker than TestJoinWithPrev2
+  // but can only be used when e.PrevInAEL.currX is accurate
+  Result := IsHotEdge(e) and not IsOpen(e) and
+    Assigned(e.prevInAEL) and (e.prevInAEL.currX = e.currX) and
+    IsHotEdge(e.prevInAEL) and not IsOpen(e.prevInAEL) and
+    (CrossProduct(e.prevInAEL.top, e.bot, e.top) = 0);
+end;
+//------------------------------------------------------------------------------
+
+function TestJoinWithPrev2(e: PActive; const currPt: TPoint64): Boolean;
+begin
+  Result := IsHotEdge(e) and not IsOpen(e) and
+    Assigned(e.prevInAEL) and not IsOpen(e.prevInAEL) and
+    IsHotEdge(e.prevInAEL) and
+    (Abs(TopX(e.prevInAEL, currPt.Y) - currPt.X) < 2) and
+    (e.prevInAEL.top.Y < currPt.Y) and
+    (CrossProduct(e.prevInAEL.top, currPt, e.top) = 0);
+end;
+//------------------------------------------------------------------------------
+
+function TestJoinWithNext1(e: PActive): Boolean;
+begin
+  // this is marginally quicker than TestJoinWithNext2
+  // but can only be used when e.NextInAEL.currX is accurate
+  Result := IsHotEdge(e) and Assigned(e.nextInAEL) and
+    IsHotEdge(e.nextInAEL) and not IsOpen(e) and
+    not IsOpen(e.nextInAEL) and
+    (e.nextInAEL.currX = e.currX) and
+    (CrossProduct(e.nextInAEL.top, e.bot, e.top) = 0);
+end;
+//------------------------------------------------------------------------------
+
+function TestJoinWithNext2(e: PActive; const currPt: TPoint64): Boolean;
+begin
+  Result := IsHotEdge(e) and Assigned(e.nextInAEL) and
+    IsHotEdge(e.nextInAEL) and not IsOpen(e) and
+    not IsOpen(e.nextInAEL) and
+    (Abs(TopX(e.nextInAEL, currPt.Y) - currPt.X) < 2) and                   // safer
+    (e.nextInAEL.top.Y < currPt.Y) and
+    (CrossProduct(e.nextInAEL.top, currPt, e.top) = 0);
+end;
+//------------------------------------------------------------------------------
+
+function GetHorzTrialParent(op: POutPt): PJoiner;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := op.joiner;
+  while Assigned(Result) do
+    if Result.op1 = op then
+    begin
+      if Assigned(Result.next1) and
+        (Result.next1.idx < 0) then Exit
+      else Result := Result.next1;
+    end else
+    begin
+      if Assigned(Result.next2) and
+        (Result.next2.idx < 0) then Exit
+      else Result := Result.next1;
+    end;
+end;
+//------------------------------------------------------------------------------
+
+function MakeDummyJoiner(horz: POutPt; nextJoiner: PJoiner): PJoiner;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  new(Result);
+  Result.idx := -1;
+  Result.op1 := horz;
+  Result.op2 := nil;
+  Result.next1 := horz.joiner;
+  horz.joiner := Result;
+  Result.next2 := nil;
+  Result.nextH := nextJoiner;
+end;
+//------------------------------------------------------------------------------
+
+function OutPtInTrialHorzList(op: POutPt): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := Assigned(op.joiner) and
+    ((op.joiner.idx < 0) or Assigned(GetHorzTrialParent(op)));
+end;
+//------------------------------------------------------------------------------
+
+function InsertOp(const pt: TPoint64; insertAfter: POutPt): POutPt;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  new(Result);
+  Result.pt := pt;
+  Result.joiner := nil;
+  Result.outrec := insertAfter.outrec;
+  Result.next := insertAfter.next;
+  insertAfter.next.prev := Result;
+  insertAfter.next := Result;
+  Result.prev := insertAfter;
+end;
+
+//------------------------------------------------------------------------------
+// TClipperBase methods ...
+//------------------------------------------------------------------------------
+
+constructor TClipperBase.Create;
+begin
+  FLocMinList       := TLocMinList.Create(4);
+  FOutRecList       := TOutRecList.Create(4);
+  FJoinerList       := TList.Create;
+  FIntersectList    := TList.Create;
+  FVertexArrayList  := TList.Create;
+  FPreserveCollinear  := true;
+  FReverseSolution    := false;
+end;
+//------------------------------------------------------------------------------
+
+destructor TClipperBase.Destroy;
+begin
+  Clear;
+  FLocMinList.Free;
+  FOutRecList.Free;
+  FJoinerList.Free;
+  FIntersectList.Free;
+  FVertexArrayList.Free;
+  inherited;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.ClearSolution;
+var
+  dummy: Int64;
+begin
+  try
+    // in case of exceptions ...
+    DeleteEdges(FActives);
+    while assigned(FScanLine) do PopScanLine(dummy);
+    DisposeIntersectNodes;
+
+    DisposeScanLineList;
+    DisposeOutRecsAndJoiners;
+    FHorzTrials := nil;
+  except
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.Clear;
+begin
+  ClearSolution;
+  DisposeVerticesAndLocalMinima;
+  FCurrentLocMinIdx := 0;
+  FLocMinListSorted := false;
+  FHasOpenPaths := False;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.Reset;
+var
+  i: Integer;
+begin
+  if not FLocMinListSorted then
+  begin
+    FLocMinList.Sort(LocMinListSort);
+    FLocMinListSorted := true;
+  end;
+
+  for i := FLocMinList.Count -1 downto 0 do
+    InsertScanLine(PLocalMinima(FLocMinList.UnsafeGet(i)).vertex.pt.Y);
+  FCurrentLocMinIdx := 0;
+  FActives := nil;
+  FSel := nil;
+  FSucceeded := true;
+end;
+//------------------------------------------------------------------------------
+
+{$IFDEF USINGZ}
+function XYCoordsEqual(const pt1, pt2: TPoint64): Boolean;
+begin
+  Result := (pt1.X = pt2.X) and (pt1.Y = pt2.Y);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.SetZ(e1, e2: PActive; var intersectPt: TPoint64);
+begin
+  if not Assigned(fZCallback) then Exit;
+
+  // prioritize subject vertices over clip vertices
+  // and pass the subject vertices before clip vertices in the callback
+  if (GetPolyType(e1) = ptSubject) then
+  begin
+    if (XYCoordsEqual(intersectPt, e1.bot)) then intersectPt.Z := e1.bot.Z
+    else if (XYCoordsEqual(intersectPt, e1.top)) then intersectPt.Z := e1.top.Z
+    else if (XYCoordsEqual(intersectPt, e2.bot)) then intersectPt.Z := e2.bot.Z
+    else if (XYCoordsEqual(intersectPt, e2.top)) then intersectPt.Z := e2.top.Z
+    else intersectPt.Z := fDefaultZ;
+    fZCallback(e1.bot, e1.top, e2.bot, e2.top, intersectPt);
+  end else
+  begin
+    if (XYCoordsEqual(intersectPt, e2.bot)) then intersectPt.Z := e2.bot.Z
+    else if (XYCoordsEqual(intersectPt, e2.top)) then intersectPt.Z := e2.top.Z
+    else if (XYCoordsEqual(intersectPt, e1.bot)) then intersectPt.Z := e1.bot.Z
+    else if (XYCoordsEqual(intersectPt, e1.top)) then intersectPt.Z := e1.top.Z
+    else intersectPt.Z := fDefaultZ;
+    fZCallback(e2.bot, e2.top, e1.bot, e1.top, intersectPt);
+  end;
+end;
+//------------------------------------------------------------------------------
+{$ENDIF}
+
+procedure TClipperBase.InsertScanLine(const Y: Int64);
+var
+  newSl, sl: PScanLine;
+begin
+  // The scanline list is a single-linked list of all the Y coordinates of
+  // subject and clip vertices in the clipping operation (sorted descending).
+  // However, only scanline Y's at Local Minima are inserted before clipping
+  // starts. While scanlines are removed sequentially during the sweep operation,
+  // new scanlines are only inserted whenever edge bounds are updated. This keeps
+  // the scanline list relatively short, optimising performance.
+  if not Assigned(FScanLine) then
+  begin
+    new(newSl);
+    newSl.y := Y;
+    FScanLine := newSl;
+    newSl.next := nil;
+  end else if Y > FScanLine.y then
+  begin
+    new(newSl);
+    newSl.y := Y;
+    newSl.next := FScanLine;
+    FScanLine := newSl;
+  end else
+  begin
+    sl := FScanLine;
+    while Assigned(sl.next) and (Y <= sl.next.y) do
+      sl := sl.next;
+    if Y = sl.y then Exit; // skip duplicates
+    new(newSl);
+    newSl.y := Y;
+    newSl.next := sl.next;
+    sl.next := newSl;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.PopScanLine(out Y: Int64): Boolean;
+var
+  sl: PScanLine;
+begin
+  Result := assigned(FScanLine);
+  if not Result then Exit;
+  Y := FScanLine.y;
+  sl := FScanLine;
+  FScanLine := FScanLine.next;
+  dispose(sl);
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.PopLocalMinima(Y: Int64;
+  out localMinima: PLocalMinima): Boolean;
+begin
+  Result := false;
+  if FCurrentLocMinIdx = FLocMinList.Count then Exit;
+  localMinima := PLocalMinima(FLocMinList.UnsafeGet(FCurrentLocMinIdx));
+  if (localMinima.vertex.pt.Y = Y) then
+  begin
+    inc(FCurrentLocMinIdx);
+    Result := true;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DisposeScanLineList;
+var
+  sl: PScanLine;
+begin
+  while Assigned(FScanLine) do
+  begin
+    sl := FScanLine.next;
+    Dispose(FScanLine);
+    FScanLine := sl;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DisposeOutRecsAndJoiners;
+var
+  i: Integer;
+begin
+  // just in case joiners haven't already been disposed
+  for i := 0 to FJoinerList.Count -1 do
+    if Assigned(UnsafeGet(FJoinerList, i)) then
+      Dispose(PJoiner(UnsafeGet(FJoinerList, i)));
+  FJoinerList.Clear;
+  FHorzTrials := nil;
+  FOutRecList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DisposeVerticesAndLocalMinima;
+var
+  i: Integer;
+begin
+  FLocMinList.Clear;
+  for i := 0 to FVertexArrayList.Count -1 do
+    FreeMem(UnsafeGet(FVertexArrayList, i));
+  FVertexArrayList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.AddPath(const path: TPath64;
+  pathType: TPathType; isOpen: Boolean);
+var
+  pp: TPaths64;
+begin
+  SetLength(pp, 1);
+  pp[0] := path;
+  AddPaths(pp, pathType, isOpen);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.AddPaths(const paths: TPaths64;
+  pathType: TPathType; isOpen: Boolean);
+begin
+  if isOpen then FHasOpenPaths := true;
+  FLocMinListSorted := false;
+  AddPathsToVertexList(paths, pathType, isOpen,
+    FVertexArrayList, FLocMinList);
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.IsContributingClosed(e: PActive): Boolean;
+begin
+  Result := false;
+  case FFillRule of
+    frNonZero: if abs(e.windCnt) <> 1 then Exit;
+    frPositive: if (e.windCnt <> 1) then Exit;
+    frNegative: if (e.windCnt <> -1) then Exit;
+  end;
+
+  case FClipType of
+    ctIntersection:
+      case FFillRule of
+        frPositive: Result := (e.windCnt2 > 0);
+        frNegative: Result := (e.windCnt2 < 0);
+        else Result := (e.windCnt2 <> 0);
+      end;
+    ctUnion:
+      case FFillRule of
+        frPositive: Result := (e.windCnt2 <= 0);
+        frNegative: Result := (e.windCnt2 >= 0);
+        else Result := (e.windCnt2 = 0);
+      end;
+    ctDifference:
+      begin
+        case FFillRule of
+          frPositive: Result := (e.windCnt2 <= 0);
+          frNegative: Result := (e.windCnt2 >= 0);
+          else Result := (e.windCnt2 = 0);
+        end;
+        if GetPolyType(e) <> ptSubject then Result := not Result;
+      end;
+    ctXor:
+        Result := true;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.IsContributingOpen(e: PActive): Boolean;
+var
+  isInSubj, isInClip: Boolean;
+begin
+    case FFillRule of
+      frPositive:
+        begin
+          isInSubj := e.windCnt > 0;
+          isInClip := e.windCnt2 > 0;
+        end;
+      frNegative:
+        begin
+          isInSubj := e.windCnt < 0;
+          isInClip := e.windCnt2 < 0;
+        end;
+      else
+        begin
+          isInSubj := e.windCnt <> 0;
+          isInClip := e.windCnt2 <> 0;
+        end;
+    end;
+
+    case FClipType of
+      ctIntersection: Result := isInClip;
+      ctUnion: Result := not isInSubj and not isInClip;
+      else Result := not isInClip;
+    end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.SetWindCountForClosedPathEdge(e: PActive);
+var
+  e2: PActive;
+begin
+  // Wind counts refer to polygon regions not edges, so here an edge's WindCnt
+  // indicates the higher of the wind counts for the two regions touching the
+  // edge. (nb: Adjacent regions can only ever have their wind counts differ by
+  // one. Also, open paths have no meaningful wind directions or counts.)
+
+  e2 := e.prevInAEL;
+  // find the nearest closed path edge of the same PolyType in AEL (heading left)
+  while Assigned(e2) and (not IsSamePolyType(e2, e) or IsOpen(e2)) do
+    e2 := e2.prevInAEL;
+
+  if not Assigned(e2) then
+  begin
+    e.windCnt := e.windDx;
+    e2 := FActives;
+  end
+  else if (FFillRule = frEvenOdd) then
+  begin
+    e.windCnt := e.windDx;
+    e.windCnt2 := e2.windCnt2;
+    e2 := e2.nextInAEL;
+  end else
+  begin
+    // NonZero, positive, or negative filling here ...
+    // when e2's WindCnt is in the SAME direction as its WindDx,
+    // then polygon will fill on the right of 'e2' (and 'e' will be inside)
+    // nb: neither e2.WindCnt nor e2.WindDx should ever be 0.
+    if (e2.windCnt * e2.windDx < 0) then
+    begin
+      // opposite directions so 'e' is outside 'e2' ...
+      if (Abs(e2.windCnt) > 1) then
+      begin
+        // outside prev poly but still inside another.
+        if (e2.windDx * e.windDx < 0) then
+          // reversing direction so use the same WC
+          e.windCnt := e2.windCnt else
+          // otherwise keep 'reducing' the WC by 1 (ie towards 0) ...
+          e.windCnt := e2.windCnt + e.windDx;
+      end
+      // now outside all polys of same polytype so set own WC ...
+      else e.windCnt := e.windDx;
+    end else
+    begin
+      //'e' must be inside 'e2'
+      if (e2.windDx * e.windDx < 0) then
+        // reversing direction so use the same WC
+        e.windCnt := e2.windCnt
+      else
+        // otherwise keep 'increasing' the WC by 1 (ie away from 0) ...
+        e.windCnt := e2.windCnt + e.windDx;
+    end;
+    e.windCnt2 := e2.windCnt2;
+    e2 := e2.nextInAEL;
+  end;
+
+  // update WindCnt2 ...
+  if FFillRule = frEvenOdd then
+    while (e2 <> e) do
+    begin
+      if IsSamePolyType(e2, e) or IsOpen(e2) then // do nothing
+      else if e.windCnt2 = 0 then e.windCnt2 := 1
+      else e.windCnt2 := 0;
+      e2 := e2.nextInAEL;
+    end
+  else
+    while (e2 <> e) do
+    begin
+      if not IsSamePolyType(e2, e) and not IsOpen(e2) then
+        Inc(e.windCnt2, e2.windDx);
+      e2 := e2.nextInAEL;
+    end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.SetWindCountForOpenPathEdge(e: PActive);
+var
+  e2: PActive;
+  cnt1, cnt2: Integer;
+begin
+  e2 := FActives;
+  if FFillRule = frEvenOdd then
+  begin
+    cnt1 := 0;
+    cnt2 := 0;
+    while (e2 <> e) do
+    begin
+      if (GetPolyType(e2) = ptClip) then inc(cnt2)
+      else if not IsOpen(e2) then inc(cnt1);
+      e2 := e2.nextInAEL;
+    end;
+    if Odd(cnt1) then e.windCnt := 1 else e.windCnt := 0;
+    if Odd(cnt2) then e.windCnt2 := 1 else e.windCnt2 := 0;
+  end else
+  begin
+    // if FClipType in [ctUnion, ctDifference] then e.WindCnt := e.WindDx;
+    while (e2 <> e) do
+    begin
+      if (GetPolyType(e2) = ptClip) then inc(e.windCnt2, e2.windDx)
+      else if not IsOpen(e2) then inc(e.windCnt, e2.windDx);
+      e2 := e2.nextInAEL;
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function IsValidAelOrder(resident, newcomer: PActive): Boolean;
+var
+  botY: Int64;
+  newcomerIsLeft: Boolean;
+  d: double;
+begin
+  if newcomer.currX <> resident.currX then
+  begin
+    Result := newcomer.currX > resident.currX;
+    Exit;
+  end;
+
+  // get the turning direction  a1.top, a2.bot, a2.top
+  d := CrossProduct(resident.top, newcomer.bot, newcomer.top);
+  if d <> 0 then
+  begin
+    Result := d < 0;
+    Exit;
+  end;
+
+  // edges must be collinear to get here
+
+  if not IsMaxima(resident) and
+    (resident.top.Y > newcomer.top.Y) then
+  begin
+    Result := CrossProduct(newcomer.bot,
+      resident.top, NextVertex(resident).pt) <= 0;
+    Exit;
+  end
+  else if not IsMaxima(newcomer) and
+    (newcomer.top.Y > resident.top.Y) then
+  begin
+    Result := CrossProduct(newcomer.bot,
+      newcomer.top, NextVertex(newcomer).pt) >= 0;
+    Exit;
+  end;
+
+  botY := newcomer.bot.Y;
+  newcomerIsLeft := IsLeftBound(newcomer);
+
+  if (resident.bot.Y <> botY) or
+    (resident.locMin.vertex.pt.Y <> botY) then
+      Result := newcomerIsLeft
+  // resident must also have just been inserted
+  else if IsLeftBound(resident) <> newcomerIsLeft then
+    Result := newcomerIsLeft
+  else if (CrossProduct(PrevPrevVertex(resident).pt,
+    resident.bot, resident.top) = 0) then
+      Result := true
+  else
+    // otherwise compare turning direction of the alternate bound
+    Result := (CrossProduct(PrevPrevVertex(resident).pt,
+      newcomer.bot, PrevPrevVertex(newcomer).pt) > 0) = newcomerIsLeft;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.InsertLeftEdge(e: PActive);
+var
+  e2: PActive;
+begin
+  if not Assigned(FActives) then
+  begin
+    e.prevInAEL := nil;
+    e.nextInAEL := nil;
+    FActives := e;
+  end
+  else if not IsValidAelOrder(FActives, e) then
+  begin
+    e.prevInAEL := nil;
+    e.nextInAEL := FActives;
+    FActives.prevInAEL := e;
+    FActives := e;
+  end else
+  begin
+    e2 := FActives;
+    while Assigned(e2.nextInAEL) and IsValidAelOrder(e2.nextInAEL, e) do
+      e2 := e2.nextInAEL;
+    e.nextInAEL := e2.nextInAEL;
+    if Assigned(e2.nextInAEL) then e2.nextInAEL.prevInAEL := e;
+    e.prevInAEL := e2;
+    e2.nextInAEL := e;
+  end;
+end;
+//----------------------------------------------------------------------
+
+procedure InsertRightEdge(e, e2: PActive);
+begin
+  e2.nextInAEL := e.nextInAEL;
+  if Assigned(e.nextInAEL) then e.nextInAEL.prevInAEL := e2;
+  e2.prevInAEL := e;
+  e.nextInAEL := e2;
+end;
+//----------------------------------------------------------------------
+
+procedure TClipperBase.InsertLocalMinimaIntoAEL(const botY: Int64);
+var
+  leftB, rightB: PActive;
+  op: POutPt;
+  locMin: PLocalMinima;
+  contributing: Boolean;
+begin
+  // Add local minima (if any) at BotY ...
+  // nb: horizontal local minima edges should contain locMin.Vertex.prev
+
+  while PopLocalMinima(botY, locMin) do
+  begin
+    if (vfOpenStart in locMin.vertex.flags) then
+    begin
+      leftB := nil;
+    end else
+    begin
+      new(leftB);
+      FillChar(leftB^, sizeof(TActive), 0);
+      leftB.locMin := locMin;
+      leftB.outrec := nil;
+      leftB.bot := locMin.vertex.pt;
+      leftB.windDx := -1;
+      leftB.vertTop := locMin.vertex.prev;
+      leftB.top := leftB.vertTop.pt;
+      leftB.currX := leftB.bot.X;
+      SetDx(leftB);
+    end;
+
+    if (vfOpenEnd in locMin.vertex.flags) then
+    begin
+      rightB := nil;
+    end else
+    begin
+      new(rightB);
+      FillChar(rightB^, sizeof(TActive), 0);
+      rightB.locMin := locMin;
+      rightB.outrec := nil;
+      rightB.bot := locMin.vertex.pt;
+      rightB.windDx := 1;
+      rightB.vertTop := locMin.vertex.next;
+      rightB.top := rightB.vertTop.pt;
+      rightB.currX := rightB.bot.X;
+      SetDx(rightB);
+    end;
+    // Currently LeftB is just descending and RightB is ascending,
+    // so now we swap them if LeftB isn't actually on the left.
+    if assigned(leftB) and assigned(rightB) then
+    begin
+      if IsHorizontal(leftB) then
+      begin
+        if IsHeadingRightHorz(leftB) then SwapActives(leftB, rightB);
+      end
+      else if IsHorizontal(rightB) then
+      begin
+        if IsHeadingLeftHorz(rightB) then SwapActives(leftB, rightB);
+      end
+      else if (leftB.dx < rightB.dx) then SwapActives(leftB, rightB);
+      //so when leftB has windDx == 1, the polygon will be oriented
+      //counter-clockwise in Cartesian coords (clockwise with inverted Y).
+    end
+    else if not assigned(leftB) then
+    begin
+      leftB := rightB;
+      rightB := nil;
+    end;
+    LeftB.isLeftB := true; // nb: we can't use winddx instead
+
+    InsertLeftEdge(leftB);                   ////////////////
+
+    if IsOpen(leftB) then
+    begin
+      SetWindCountForOpenPathEdge(leftB);
+      contributing := IsContributingOpen(leftB);
+    end else
+    begin
+      SetWindCountForClosedPathEdge(leftB);
+      contributing := IsContributingClosed(leftB);
+    end;
+
+    if assigned(rightB) then
+    begin
+      rightB.windCnt := leftB.windCnt;
+      rightB.windCnt2 := leftB.windCnt2;
+      InsertRightEdge(leftB, rightB);        ////////////////
+
+      if contributing then
+      begin
+        AddLocalMinPoly(leftB, rightB, leftB.bot, true);
+
+        if not IsHorizontal(leftB) and
+          TestJoinWithPrev1(leftB) then
+        begin
+          op := AddOutPt(leftB.prevInAEL, leftB.bot);
+          AddJoin(op, leftB.outrec.pts);
+        end;
+      end;
+
+      while Assigned(rightB.nextInAEL) and
+        IsValidAelOrder(rightB.nextInAEL, rightB) do
+      begin
+        IntersectEdges(rightB, rightB.nextInAEL, rightB.bot);
+        SwapPositionsInAEL(rightB, rightB.nextInAEL);
+      end;
+
+      if not IsHorizontal(rightB) and
+        TestJoinWithNext1(rightB) then
+      begin
+        op := AddOutPt(rightB.nextInAEL, rightB.bot);
+        AddJoin(rightB.outrec.pts, op);
+      end;
+
+      if IsHorizontal(rightB) then
+        PushHorz(rightB) else
+        InsertScanLine(rightB.top.Y);
+    end
+    else if contributing then
+      StartOpenPath(leftB, leftB.bot);
+
+    if IsHorizontal(leftB) then
+      PushHorz(leftB) else
+      InsertScanLine(leftB.top.Y);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.PushHorz(e: PActive);
+begin
+  if assigned(FSel) then
+    e.nextInSEL := FSel else
+    e.nextInSEL := nil;
+  FSel := e;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.PopHorz(out e: PActive): Boolean;
+begin
+  Result := assigned(FSel);
+  if not Result then Exit;
+  e := FSel;
+  FSel := FSel.nextInSEL;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.AddLocalMinPoly(e1, e2: PActive;
+  const pt: TPoint64; IsNew: Boolean = false): POutPt;
+var
+  newOr: POutRec;
+  prevHotEdge: PActive;
+begin
+  newOr := FOutRecList.Add;
+  e1.outrec := newOr;
+  e2.outrec := newOr;
+
+  // Setting the owner and inner/outer states (above) is an essential
+  // precursor to setting edge 'sides' (ie left and right sides of output
+  // polygons) and hence the orientation of output paths ...
+
+  if IsOpen(e1) then
+  begin
+    newOr.owner := nil;
+    newOr.isOpen := true;
+    if e1.windDx > 0 then
+      SetSides(newOr, e1, e2) else
+      SetSides(newOr, e2, e1);
+  end else
+  begin
+    prevHotEdge := GetPrevHotEdge(e1);
+    newOr.isOpen := false;
+    // e.windDx is the winding direction of the **input** paths
+    // and unrelated to the winding direction of output polygons.
+    // Output orientation is determined by e.outrec.frontE which is
+    // the ascending edge (see AddLocalMinPoly).
+    if Assigned(prevHotEdge) then
+    begin
+      newOr.owner := prevHotEdge.outrec;
+      if OutrecIsAscending(prevHotEdge) = isNew then
+        SetSides(newOr, e2, e1) else
+        SetSides(newOr, e1, e2);
+    end else
+    begin
+      newOr.owner := nil;
+      if isNew then
+        SetSides(newOr, e1, e2) else
+        SetSides(newOr, e2, e1);
+    end;
+  end;
+
+  new(Result);
+  newOr.pts := Result;
+  Result.pt := pt;
+  Result.joiner := nil;
+  Result.outrec := newOr;
+  Result.prev := Result;
+  Result.next := Result;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.SafeDisposeOutPts(var op: POutPt);
+var
+  tmpOp: POutPt;
+  outRec: POutRec;
+begin
+  outRec := GetRealOutRec(op.outrec);
+  if Assigned(outRec.frontE) then
+    outRec.frontE.outrec := nil;
+  if Assigned(outRec.backE) then
+    outRec.backE.outrec := nil;
+  op.prev.next := nil;
+  while Assigned(op) do
+  begin
+    SafeDeleteOutPtJoiners(op);
+    tmpOp := op;
+    op := op.next;
+    Dispose(tmpOp);
+  end;
+  outRec.pts := nil; //must do this last (due to var parameter)
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.CleanCollinear(outRec: POutRec);
+var
+  op2, startOp: POutPt;
+begin
+  outRec := GetRealOutRec(outRec);
+  if not Assigned(outRec) or
+    outRec.isOpen or
+    Assigned(outRec.frontE) or
+    not ValidateClosedPathEx(outRec.pts) then
+      Exit;
+
+  startOp := outRec.pts;
+  op2 := startOp;
+  while true do
+  begin
+    if Assigned(op2.joiner) then Exit;
+    if (CrossProduct(op2.prev.pt, op2.pt, op2.next.pt) = 0) and
+      (PointsEqual(op2.pt,op2.prev.pt) or
+      PointsEqual(op2.pt,op2.next.pt) or
+      not FPreserveCollinear or
+      (DotProduct(op2.prev.pt, op2.pt, op2.next.pt) < 0)) then
+    begin
+      if op2 = outRec.pts then outRec.pts := op2.prev;
+      op2 := DisposeOutPt(op2);
+      if not ValidateClosedPathEx(op2) then
+      begin
+        outRec.pts := nil;
+        Exit;
+      end;
+      startOp := op2;
+      Continue;
+    end;
+    op2 := op2.next;
+    if op2 = startOp then Break;
+  end;
+  FixSelfIntersects(outRec);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DoSplitOp(outrec: POutRec; splitOp: POutPt);
+var
+  newOp, newOp2, prevOp, nextNextOp: POutPt;
+  ip: TPoint64;
+  area1, area2, absArea1, absArea2: double;
+  newOutRec: POutRec;
+begin
+  // splitOp.prev -> splitOp &&
+  // splitOp.next -> splitOp.next.next are intersecting
+  prevOp := splitOp.prev;
+  nextNextOp := splitOp.next.next;
+  outrec.pts := prevOp;
+  GetIntersectPoint(
+    prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, ip);
+{$IFDEF USINGZ}
+  if Assigned(fZCallback) then
+    fZCallback(prevOp.Pt, splitOp.Pt, splitOp.Next.Pt, nextNextOp.Pt, ip);
+{$ENDIF}
+  area1 := Area(outrec.pts);
+  absArea1 := abs(area1);
+
+  if absArea1 < 2 then
+  begin
+    SafeDisposeOutPts(splitOp);
+    Exit;
+  end;
+
+  // nb: area1 is the path's area *before* splitting, whereas area2 is
+  // the area of the triangle containing splitOp & splitOp.next.
+  // So the only way for these areas to have the same sign is if
+  // the split triangle is larger than the path containing prevOp or
+  // if there's more than one self=intersection.
+  area2 := AreaTriangle(ip, splitOp.pt, splitOp.next.pt);
+  absArea2 := abs(area2);
+
+  // de-link splitOp and splitOp.next from the path
+  // while inserting the intersection point
+  if PointsEqual(ip, prevOp.pt) or
+    PointsEqual(ip, nextNextOp.pt) then
+  begin
+    nextNextOp.prev := prevOp;
+    prevOp.next := nextNextOp;
+  end else
+  begin
+    new(newOp2);
+    newOp2.pt := ip;
+    newOp2.joiner := nil;
+    newOp2.outrec := outrec;
+    newOp2.prev := prevOp;
+    newOp2.next := nextNextOp;
+    nextNextOp.prev := newOp2;
+    prevOp.next := newOp2;
+  end;
+
+  SafeDeleteOutPtJoiners(splitOp.next);
+  SafeDeleteOutPtJoiners(splitOp);
+
+  if (absArea2 > 1) and
+    ((absArea2 > absArea1) or
+    ((area2 > 0) = (area1 > 0))) then
+  begin
+    newOutRec := FOutRecList.Add;
+    newOutRec.owner := outrec.owner;
+    newOutRec.isOpen := false;
+
+    splitOp.outrec := newOutRec;
+    splitOp.next.outrec := newOutRec;
+    new(newOp);
+    newOp.pt := ip;
+    newOp.joiner := nil;
+    newOp.outrec := newOutRec;
+    newOp.prev := splitOp.next;
+    newOp.next := splitOp;
+    splitOp.prev := newOp;
+    splitOp.next.next := newOp;
+    newOutRec.pts := newOp;
+  end else
+  begin
+    Dispose(splitOp.next);
+    Dispose(splitOp);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.FixSelfIntersects(outrec: POutRec);
+var
+  op2: POutPt;
+begin
+  op2 := outrec.pts;
+  while true do
+  begin
+    // triangles can't self-intersect
+    if (op2.prev = op2.next.next) then
+      Break
+    else if SegmentsIntersect(op2.prev.pt, op2.pt,
+      op2.next.pt, op2.next.next.pt) then
+    begin
+      DoSplitOp(outrec, op2);
+      if not assigned(outrec.pts) then Break;
+      op2 := outrec.pts;
+      Continue;
+    end else
+      op2 := op2.next;
+    if (op2 = outrec.pts) then Break;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.AddLocalMaxPoly(e1, e2: PActive; const pt: TPoint64): POutPt;
+var
+  outRec: POutRec;
+begin
+  if (IsFront(e1) = IsFront(e2)) then
+  begin
+    if IsOpenEnd(e1) then
+      SwapFrontBackSides(e1.outrec)
+    else if IsOpenEnd(e2) then
+      SwapFrontBackSides(e2.outrec)
+    else
+    begin
+      FSucceeded := false;
+      Result := nil;
+      Exit;
+    end;
+  end;
+
+  Result := AddOutPt(e1, pt);
+  if (e1.outrec = e2.outrec) then
+  begin
+    outRec := e1.outrec;
+    outRec.pts := Result;
+    UncoupleOutRec(e1);
+    if not IsOpen(e1) then
+      CleanCollinear(outRec);
+    Result := outRec.pts;
+    outRec.owner := GetRealOutRec(outRec.owner);
+    if FUsingPolytree and Assigned(outRec.owner) and
+      not Assigned(outRec.owner.frontE) then
+        outRec.owner := GetRealOutRec(outRec.owner.owner);
+  end
+  // and to preserve the winding orientation of Outrec ...
+  else if IsOpen(e1) then
+  begin
+    if e1.windDx < 0 then
+      JoinOutrecPaths(e1, e2) else
+      JoinOutrecPaths(e2, e1);
+  end
+  else if e1.outrec.idx < e2.outrec.idx then
+    JoinOutrecPaths(e1, e2)
+  else
+    JoinOutrecPaths(e2, e1);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.JoinOutrecPaths(e1, e2: PActive);
+var
+  p1_start, p1_end, p2_start, p2_end: POutPt;
+begin
+  // join e2 outrec path onto e1 outrec path and then delete e2 outrec path
+  // pointers. (see joining_outpt.svg)
+  p1_start :=  e1.outrec.pts;
+  p2_start :=  e2.outrec.pts;
+  p1_end := p1_start.next;
+  p2_end := p2_start.next;
+
+  if IsFront(e1) then
+  begin
+    p2_end.prev := p1_start;
+    p1_start.next := p2_end;
+    p2_start.next := p1_end;
+    p1_end.prev := p2_start;
+    e1.outrec.pts := p2_start;
+    // nb: if IsOpen(e1) then e1 & e2 must be a 'maximaPair'
+    e1.outrec.frontE := e2.outrec.frontE;
+    if Assigned(e1.outrec.frontE) then
+      e1.outrec.frontE.outrec := e1.outrec;
+  end else
+  begin
+    p1_end.prev := p2_start;
+    p2_start.next := p1_end;
+    p1_start.next := p2_end;
+    p2_end.prev := p1_start;
+
+    e1.outrec.backE := e2.outrec.backE;
+    if Assigned(e1.outrec.backE) then
+      e1.outrec.backE.outrec := e1.outrec;
+  end;
+
+  // an owner must have a lower idx otherwise
+  // it won't be a valid owner
+  if assigned(e2.outrec.owner) and
+    (e2.outrec.owner.idx < e1.outrec.idx) then
+  begin
+    if not assigned(e1.outrec.owner) or
+      (e2.outrec.owner.idx < e1.outrec.owner.idx) then
+        e1.outrec.owner := e2.outrec.owner;
+  end;
+
+  // after joining, the e2.OutRec mustn't contains vertices
+  e2.outrec.frontE := nil;
+  e2.outrec.backE := nil;
+  e2.outrec.pts := nil;
+  e2.outrec.owner := e1.outrec;
+
+  if IsOpenEnd(e1) then
+  begin
+    e2.outrec.pts := e1.outrec.pts;
+    e1.outrec.pts := nil;
+  end;
+
+  // and e1 and e2 are maxima and are about to be dropped from the Actives list.
+  e1.outrec := nil;
+  e2.outrec := nil;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.AddOutPt(e: PActive; const pt: TPoint64): POutPt;
+var
+  opFront, opBack: POutPt;
+  toFront: Boolean;
+  outrec: POutRec;
+begin
+  // Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
+  // opFront[.Prev]* ~~~> opBack & opBack == opFront.Next
+  outrec := e.outrec;
+  toFront := IsFront(e);
+  opFront := outrec.pts;
+  opBack := opFront.next;
+
+  if toFront then
+  begin
+    if PointsEqual(pt, opFront.pt) then
+    begin
+      result := opFront;
+      Exit;
+    end;
+  end
+  else if PointsEqual(pt, opBack.pt) then
+  begin
+    result := opBack;
+    Exit;
+  end;
+
+  new(Result);
+  Result.pt := pt;
+  Result.joiner := nil;
+  Result.outrec := outrec;
+  opBack.prev := Result;
+  Result.prev := opFront;
+  Result.next := opBack;
+  opFront.next := Result;
+  if toFront then outrec.pts := Result;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.AddJoin(op1, op2: POutPt);
+var
+  joiner: PJoiner;
+begin
+  if (op1.outrec = op2.outrec) and ((op1 = op2) or
+  // unless op1.next or op1.prev crosses the start-end divide
+  // don't waste time trying to join adjacent vertices
+  ((op1.next = op2) and (op1 <> op1.outrec.pts)) or
+  ((op2.next = op1) and (op2 <> op1.outrec.pts))) then Exit;
+
+  new(joiner);
+  joiner.idx := FJoinerList.Add(joiner);
+  joiner.op1 := op1;
+  joiner.op2 := op2;
+  joiner.nextH := nil;
+  joiner.next1 := op1.joiner;
+  joiner.next2 := op2.joiner;
+  op1.joiner := joiner;
+  op2.joiner := joiner;
+end;
+//------------------------------------------------------------------------------
+
+function FindJoinParent(joiner: PJoiner; op: POutPt): PJoiner;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result := op.joiner;
+  while true do
+  begin
+    if (op = Result.op1) then
+    begin
+      if Result.next1 = joiner then Exit
+      else Result := Result.next1;
+    end else
+    begin
+      if Result.next2 = joiner then Exit
+      else Result := Result.next2;
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DeleteJoin(joiner: PJoiner);
+var
+  op1, op2: POutPt;
+  parentJnr: PJoiner;
+begin
+  // This method deletes a single join, and it doesn't check for or
+  // delete trial horz. joins. For that, use the following method.
+
+  op1 := joiner.op1;
+  op2 := joiner.op2;
+
+  // both op1 and op2 can be associated with multiple joiners which
+  // are chained together so we need to break and rejoin that chain
+
+  if op1.joiner <> joiner then
+  begin
+    parentJnr := FindJoinParent(joiner, op1);
+    if parentJnr.op1 = op1 then
+      parentJnr.next1 := joiner.next1 else
+      parentJnr.next2 := joiner.next1;
+  end else
+    op1.joiner := Joiner.next1;
+
+  if op2.joiner <> joiner then
+  begin
+    parentJnr := FindJoinParent(joiner, op2);
+    if parentJnr.op1 = op2 then
+      parentJnr.next1 := joiner.next2 else
+      parentJnr.next2 := joiner.next2;
+  end else
+    op2.joiner := joiner.next2;
+
+  FJoinerList[joiner.idx] := nil;
+  Dispose(joiner);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.SafeDeleteOutPtJoiners(op: POutPt);
+var
+  joiner: PJoiner;
+begin
+  if not Assigned(op.joiner) then Exit;
+  joiner := op.joiner;
+  while Assigned(joiner) do
+  begin
+    if joiner.idx < 0 then
+      DeleteTrialHorzJoin(op)
+    else if Assigned(FHorzTrials) then
+    begin
+      if OutPtInTrialHorzList(joiner.op1) then
+        DeleteTrialHorzJoin(joiner.op1);
+      if OutPtInTrialHorzList(joiner.op2) then
+        DeleteTrialHorzJoin(joiner.op2);
+      DeleteJoin(joiner);
+    end else
+      DeleteJoin(joiner);
+
+    joiner := op.joiner;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.ProcessJoinList;
+var
+  i: integer;
+  joiner: PJoiner;
+  outRec: POutRec;
+begin
+  for i := 0 to FJoinerList.Count -1 do
+  begin
+    if Assigned(UnsafeGet(FJoinerList, i)) then
+    begin
+      joiner := UnsafeGet(FJoinerList, i);
+      outrec := ProcessJoin(joiner);
+      CleanCollinear(outRec);
+    end;
+  end;
+  FJoinerList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.UpdateOutrecOwner(outRec: POutRec);
+var
+  opCurr  : POutPt;
+begin
+  opCurr := outRec.pts;
+  repeat
+    opCurr.outrec := outRec;
+    opCurr := opCurr.next;
+  until opCurr = outRec.pts;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.CompleteSplit(op1, op2: POutPt; OutRec: POutRec);
+var
+  i: integer;
+  area1, area2: double;
+  signsChange: Boolean;
+  newOr: POutRec;
+begin
+  area1 := Area(op1);
+  area2 := Area(op2);
+  signsChange := (area1 > 0) = (area2 < 0);
+
+  // delete trivial splits (with zero or almost zero areas)
+  if (area1 = 0) or (signsChange and (Abs(area1) < 2)) then
+  begin
+    SafeDisposeOutPts(op1);
+    OutRec.pts := op2;
+  end
+  else if (area2 = 0) or (signsChange and (Abs(area2) < 2)) then
+  begin
+    SafeDisposeOutPts(op2);
+    OutRec.pts := op1;
+  end
+  else
+  begin
+    newOr := FOutRecList.Add;
+
+    if (FUsingPolytree) then
+    begin
+      i := Length(OutRec.splits);
+      SetLength(OutRec.splits, i +1);
+      OutRec.splits[i] := newOr;
+    end;
+
+    if Abs(area1) >= Abs(area2) then
+    begin
+      OutRec.pts := op1;
+      newOr.pts := op2;
+    end else
+    begin
+      OutRec.pts := op2;
+      newOr.pts := op1;
+    end;
+
+    if (area1 > 0) = (area2 > 0) then
+      newOr.owner := OutRec.owner else
+      newOr.owner := OutRec;
+
+    UpdateOutrecOwner(newOr);
+    CleanCollinear(newOr);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function CollinearSegsOverlap(const  seg1a, seg1b,
+  seg2a, seg2b: TPoint64): Boolean;
+begin
+  // precondition: seg1 and seg2 are collinear
+  Result := false;
+  if (seg1a.X = seg1b.X) then
+  begin
+    if (seg2a.X <> seg1a.X) or (seg2a.X <> seg2b.X) then Exit;
+  end
+  else if (seg1a.X < seg1b.X) then
+  begin
+    if (seg2a.X < seg2b.X) then
+    begin
+      if (seg2a.X >= seg1b.X) or (seg2b.X <= seg1a.X) then Exit;
+    end
+    else
+      if (seg2b.X >= seg1b.X) or (seg2a.X <= seg1a.X) then Exit;
+  end else
+  begin
+    if (seg2a.X < seg2b.X) then
+    begin
+      if (seg2a.X >= seg1a.X) or (seg2b.X <= seg1b.X) then Exit;
+    end else
+      if (seg2b.X >= seg1a.X) or (seg2a.X <= seg1b.X) then Exit;
+  end;
+
+  if (seg1a.Y = seg1b.Y) then
+  begin
+    if (seg2a.Y <> seg1a.Y) or (seg2a.Y <> seg2b.Y) then Exit;
+  end
+  else if (seg1a.Y < seg1b.Y) then
+  begin
+    if (seg2a.Y < seg2b.Y) then
+    begin
+      if (seg2a.Y >= seg1b.Y) or (seg2b.Y <= seg1a.Y) then Exit;
+    end else
+      if (seg2b.Y >= seg1b.Y) or (seg2a.Y <= seg1a.Y) then Exit;
+  end else
+  begin
+    if (seg2a.Y < seg2b.Y) then
+    begin
+      if (seg2a.Y >= seg1a.Y) or (seg2b.Y <= seg1b.Y) then Exit;
+    end else
+      if (seg2b.Y >= seg1a.Y) or (seg2a.Y <= seg1b.Y) then Exit;
+  end;
+  Result := true;
+end;
+//------------------------------------------------------------------------------
+
+function PointEqualOrBetween(const pt, corner1, corner2: TPoint64): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  // nb: points may not be collinear
+  Result := ValueEqualOrBetween(pt.X, corner1.X, corner2.X) and
+    ValueEqualOrBetween(pt.Y, corner1.Y, corner2.Y);
+end;
+//------------------------------------------------------------------------------
+
+function PointBetween(const pt, corner1, corner2: TPoint64): Boolean;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  // nb: points may not be collinear
+  Result := ValueBetween(pt.X, corner1.X, corner2.X) and
+    ValueBetween(pt.Y, corner1.Y, corner2.Y);
+end;
+//------------------------------------------------------------------------------
+
+function CheckDisposeAdjacent(var op: POutPt; guard: POutPt;
+  outRec: POutRec): Boolean;
+begin
+  Result := false;
+  while (op.prev <> op) do
+  begin
+    if PointsEqual(op.pt, op.prev.pt) and
+      (op <> guard) and Assigned(op.prev.joiner) and
+      not Assigned(op.joiner) then
+    begin
+      if op = outRec.pts then outRec.pts := op.prev;
+      op := DisposeOutPt(op);
+      op := op.prev;
+    end else
+      break;
+  end;
+  while (op.next <> op) do
+  begin
+    if PointsEqual(op.pt, op.next.pt) and
+      (op <> guard) and Assigned(op.next.joiner) and
+      not Assigned(op.joiner) then
+    begin
+      if op = outRec.pts then outRec.pts := op.prev;
+      op := DisposeOutPt(op);
+      op := op.prev;
+    end else
+      break;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.ProcessJoin(joiner: PJoiner): POutRec;
+var
+  op1, op2: POutPt;
+  opA, opB: POutPt;
+  or1, or2: POutRec;
+begin
+  op1 := joiner.op1;
+  op2 := joiner.op2;
+
+  or1 := GetRealOutRec(op1.outrec);
+  or2 := GetRealOutRec(op2.outrec);
+  op1.outrec := or1;
+  op2.outrec := or2;
+  DeleteJoin(joiner);
+
+  Result := or1;
+  if not Assigned(or2.pts) then
+    Exit
+  else if not IsValidClosedPath(op2) then
+  begin
+    SafeDisposeOutPts(op2);
+    Exit;
+  end
+  else if not Assigned(or1.pts) or
+    not IsValidClosedPath(op1) then
+  begin
+    SafeDisposeOutPts(op1);
+    Result := or2; // ie tidy or2 in calling function;
+    Exit;
+  end
+  else if (or1 = or2) and ((op1 = op2) or
+    (op1.next = op2) or (op1.prev = op2)) then
+  begin
+    Exit;
+  end;
+
+  CheckDisposeAdjacent(op1, op2, or1);
+  CheckDisposeAdjacent(op2, op1, or2);
+  if (op1.next = op2) or (op2.next = op1) then Exit;
+
+  while True do
+  begin
+    if not IsValidPath(op1) or not IsValidPath(op2) or
+      ((or1 = or2) and ((op1.prev = op2) or (op1.next = op2))) then Exit;
+
+    if PointsEqual(op1.prev.pt, op2.next.pt) or
+    ((CrossProduct(op1.prev.pt, op1.pt, op2.next.pt) = 0) and
+      CollinearSegsOverlap(op1.prev.pt, op1.pt, op2.pt, op2.next.pt)) then
+    begin
+      if or1 = or2 then
+      begin
+        // SPLIT REQUIRED
+        // make sure op1.prev and op2.next match positions
+        // by inserting an extra vertex if needed
+        if not PointsEqual(op1.prev.pt, op2.next.pt) then
+        begin
+          if PointEqualOrBetween(op1.prev.pt, op2.pt, op2.next.pt) then
+            op2.next := InsertOp(op1.prev.pt, op2) else
+            op1.prev := InsertOp(op2.next.pt, op1.prev);
+        end;
+        // current              to     new
+        // op1.p[opA] >>> op1   ...    opA \   / op1
+        // op2.n[opB] <<< op2   ...    opB /   \ op2
+        opA := op1.prev;
+        opB := op2.next;
+        opA.next := opB;
+        opB.prev := opA;
+        op1.prev := op2;
+        op2.next := op1;
+        CompleteSplit(op1, opA, or1);
+      end else
+      begin
+        // JOIN, NOT SPLIT
+        opA := op1.prev;
+        opB := op2.next;
+        opA.next := opB;
+        opB.prev := opA;
+        op1.prev := op2;
+        op2.next := op1;
+
+        if (or1.idx < or2.idx) then
+        begin
+          or1.pts := op1;
+          or2.pts := nil;
+          if Assigned(or1.owner) and
+            (not Assigned(or2.owner) or
+							(or2.owner.idx < or1.owner.idx)) then
+								or1.owner := or2.owner;
+          or2.owner := or1
+        end else
+        begin
+          or2.pts := op1;
+          or1.pts := nil;
+          if Assigned(or2.owner) and
+            (not Assigned(or1.owner) or
+							(or1.owner.idx < or2.owner.idx)) then
+								or2.owner := or1.owner;
+          or1.owner := or2;
+        end;
+      end;
+      Break;
+    end
+    else if PointsEqual(op1.next.pt, op2.prev.pt) or
+      ((CrossProduct(op1.next.pt, op2.pt, op2.prev.pt) = 0) and
+       CollinearSegsOverlap(op1.next.pt, op1.pt, op2.pt, op2.prev.pt)) then
+    begin
+      if or1 = or2 then
+      begin
+        // SPLIT REQUIRED
+        // make sure op2.prev and op1.next match positions
+        // by inserting an extra vertex if needed
+        if not PointsEqual(op1.next.pt, op2.prev.pt) then
+        begin
+          if PointEqualOrBetween(op2.prev.pt, op1.pt, op1.next.pt) then
+            op1.next := InsertOp(op2.prev.pt, op1) else
+            op2.prev := InsertOp(op1.next.pt, op2.prev);
+        end;
+        // current              to     new
+        // op2.p[opA] >>> op2   ...    opA \   / op2
+        // op1.n[opB] <<< op1   ...    opB /   \ op1
+        opA := op2.prev;
+        opB := op1.next;
+        opA.next := opB;
+        opB.prev := opA;
+        op2.prev := op1;
+        op1.next := op2;
+        CompleteSplit(op1, opA, or1);
+      end else
+      begin
+        // JOIN, NOT SPLIT
+        opA := op1.next;
+        opB := op2.prev;
+        opA.prev := opB;
+        opB.next := opA;
+        op1.next := op2;
+        op2.prev := op1;
+
+//        SafeDeleteOutPtJoiners(op2);
+//        DisposeOutPt(op2);
+
+        if or1.idx < or2.idx then
+        begin
+          or1.pts := op1;
+          or2.pts := nil;
+          if Assigned(or1.owner) and
+            (not Assigned(or2.owner) or
+							(or2.owner.idx < or1.owner.idx)) then
+								or1.owner := or2.owner;
+          or2.owner := or1;
+        end else
+        begin
+          Result := or2;
+          or2.pts := op1;
+          or1.pts := nil;
+          if Assigned(or2.owner) and
+            (not Assigned(or1.owner) or
+							(or1.owner.idx < or2.owner.idx)) then
+								or2.owner := or1.owner;
+          or1.owner := or2;
+        end;
+      end;
+      Break;
+    end
+    else if PointBetween(op1.next.pt, op2.pt, op2.prev.pt) and
+      (DistanceFromLineSqrd(op1.next.pt, op2.pt, op2.prev.pt) < 2.01) then
+    begin
+      InsertOp(op1.next.pt, op2.prev);
+      Continue;
+    end
+    else if PointBetween(op2.next.pt, op1.pt, op1.prev.pt) and
+      (DistanceFromLineSqrd(op2.next.pt, op1.pt, op1.prev.pt) < 2.01) then
+    begin
+      InsertOp(op2.next.pt, op1.prev);
+      Continue;
+    end
+    else if PointBetween(op1.prev.pt, op2.pt, op2.next.pt) and
+      (DistanceFromLineSqrd(op1.prev.pt, op2.pt, op2.next.pt) < 2.01) then
+    begin
+      InsertOp(op1.prev.pt, op2);
+      Continue;
+    end
+    else if PointBetween(op2.prev.pt, op1.pt, op1.next.pt) and
+      (DistanceFromLineSqrd(op2.prev.pt, op1.pt, op1.next.pt) < 2.01) then
+    begin
+      InsertOp(op2.prev.pt, op1);
+      Continue;
+    end;
+
+    // something odd needs tidying up
+    if CheckDisposeAdjacent(op1, op2, or1) then Continue
+    else if CheckDisposeAdjacent(op2, op1, or1) then Continue
+    else if not PointsEqual(op1.prev.pt, op2.next.pt) and
+      (DistanceSqr(op1.prev.pt, op2.next.pt) < 2.01) then
+    begin
+      op1.prev.pt := op2.next.pt;
+      Continue;
+    end
+    else if not PointsEqual(op1.next.pt, op2.prev.pt) and
+      (DistanceSqr(op1.next.pt, op2.prev.pt) < 2.01) then
+    begin
+      op2.prev.pt := op1.next.pt;
+      Continue;
+    end else
+    begin
+      // OK, there doesn't seem to be a way to join afterall
+      // so just tidy up the polygons
+      or1.pts := op1;
+      if or2 <> or1 then
+      begin
+        or2.pts := op2;
+        CleanCollinear(or2);
+      end;
+      Break;
+    end;
+  end; // end while
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.StartOpenPath(e: PActive; const pt: TPoint64): POutPt;
+var
+  newOr: POutRec;
+begin
+  newOr := FOutRecList.Add;
+  newOr.isOpen := true;
+
+  if e.windDx > 0 then
+  begin
+    newOr.frontE := e;
+    newOr.backE := nil;
+  end else
+  begin
+    newOr.frontE := nil;
+    newOr.backE := e;
+  end;
+  e.outrec := newOr;
+
+  new(Result);
+  newOr.pts := Result;
+  Result.pt := pt;
+  Result.joiner := nil;
+  Result.prev := Result;
+  Result.next := Result;
+  Result.outrec := newOr;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.UpdateEdgeIntoAEL(var e: PActive);
+var
+  op1, op2: POutPt;
+begin
+  e.bot := e.top;
+  e.vertTop := NextVertex(e);
+  e.top := e.vertTop.pt;
+  e.currX := e.bot.X;
+  SetDx(e);
+  if IsHorizontal(e) then Exit;
+  InsertScanLine(e.top.Y);
+  if TestJoinWithPrev1(e) then
+  begin
+    op1 := AddOutPt(e.prevInAEL, e.bot);
+    op2 := AddOutPt(e, e.bot);
+    AddJoin(op1, op2);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function FindEdgeWithMatchingLocMin(e: PActive): PActive;
+begin
+  Result := e.nextInAEL;
+  while Assigned(Result) do
+  begin
+    if (Result.locMin = e.locMin) then Exit;
+    if not IsHorizontal(Result) and
+      not PointsEqual(e.bot, Result.bot) then Result := nil
+    else Result := Result.nextInAEL;
+  end;
+  Result := e.prevInAEL;
+  while Assigned(Result) do
+  begin
+    if (Result.locMin = e.locMin) then Exit;
+    if not IsHorizontal(Result) and
+      not PointsEqual(e.bot, Result.bot) then Result := nil
+    else
+      Result := Result.prevInAEL;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+{$IFNDEF USINGZ}
+{$HINTS OFF}
+{$ENDIF}
+function TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64): POutPt;
+var
+  e1WindCnt, e2WindCnt, e1WindCnt2, e2WindCnt2: Integer;
+  e3: PActive;
+  op2: POutPt;
+begin
+  Result := nil;
+
+  // MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
+  if FHasOpenPaths and (IsOpen(e1) or IsOpen(e2)) then
+  begin
+    if IsOpen(e1) and IsOpen(e2) then Exit;
+    // the following line avoids duplicating quite a bit of code
+    if IsOpen(e2) then SwapActives(e1, e2);
+
+    case FClipType of
+      ctUnion: if not IsHotEdge(e2) then Exit;
+      else if e2.locMin.polytype = ptSubject then Exit;
+    end;
+    case FFillRule of
+      frPositive: if e2.windCnt <> 1 then Exit;
+      frNegative: if e2.windCnt <> -1 then Exit;
+      else if (abs(e2.windCnt) <> 1) then Exit;
+    end;
+
+    // toggle contribution ...
+    if IsHotEdge(e1) then
+    begin
+      Result := AddOutPt(e1, pt);
+      if IsFront(e1) then
+        e1.outrec.frontE := nil else
+        e1.outrec.backE := nil;
+      e1.outrec := nil;
+    end
+    // horizontal edges can pass under open paths at a LocMins
+    else if PointsEqual(pt, e1.locMin.vertex.pt) and
+      (e1.locMin.vertex.flags * [vfOpenStart, vfOpenEnd] = []) then
+    begin
+      // find the other side of the LocMin and
+      // if it's 'hot' join up with it ...
+      e3 := FindEdgeWithMatchingLocMin(e1);
+      if assigned(e3) and IsHotEdge(e3) then
+      begin
+        e1.outrec := e3.outrec;
+        if e1.windDx > 0 then
+          SetSides(e3.outrec, e1, e3) else
+          SetSides(e3.outrec, e3, e1);
+        Result := e3.outrec.pts;
+        Exit;
+      end
+      else
+        Result := StartOpenPath(e1, pt);
+    end
+    else
+      Result := StartOpenPath(e1, pt);
+
+    {$IFDEF USINGZ}
+    SetZ(e1, e2, Result.pt);
+    {$ENDIF}
+    Exit;
+  end;
+
+  // MANAGING CLOSED PATHS FROM HERE ON
+
+  // FIRST, UPDATE WINDING COUNTS
+  if IsSamePolyType(e1, e2) then
+  begin
+    if FFillRule = frEvenOdd then
+    begin
+      e1WindCnt := e1.windCnt;
+      e1.windCnt := e2.windCnt;
+      e2.windCnt := e1WindCnt;
+    end else
+    begin
+      if e1.windCnt + e2.windDx = 0 then
+        e1.windCnt := -e1.windCnt else
+        Inc(e1.windCnt, e2.windDx);
+      if e2.windCnt - e1.windDx = 0 then
+        e2.windCnt := -e2.windCnt else
+        Dec(e2.windCnt, e1.windDx);
+    end;
+  end else
+  begin
+    if FFillRule <> frEvenOdd then Inc(e1.windCnt2, e2.windDx)
+    else if e1.windCnt2 = 0 then e1.windCnt2 := 1
+    else e1.windCnt2 := 0;
+
+    if FFillRule <> frEvenOdd then Dec(e2.windCnt2, e1.windDx)
+    else if e2.windCnt2 = 0 then e2.windCnt2 := 1
+    else e2.windCnt2 := 0;
+  end;
+
+  case FFillRule of
+    frPositive:
+      begin
+        e1WindCnt := e1.windCnt;
+        e2WindCnt := e2.windCnt;
+      end;
+    frNegative:
+      begin
+        e1WindCnt := -e1.windCnt;
+        e2WindCnt := -e2.windCnt;
+      end;
+    else
+      begin
+        e1WindCnt := abs(e1.windCnt);
+        e2WindCnt := abs(e2.windCnt);
+      end;
+  end;
+
+  if (not IsHotEdge(e1) and not (e1WindCnt in [0,1])) or
+    (not IsHotEdge(e2) and not (e2WindCnt in [0,1])) then Exit;
+
+  // NOW PROCESS THE INTERSECTION
+
+  // if both edges are 'hot' ...
+  if IsHotEdge(e1) and IsHotEdge(e2) then
+  begin
+    if not (e1WindCnt in [0,1]) or not (e2WindCnt in [0,1]) or
+      (not IsSamePolyType(e1, e2) and (fClipType <> ctXor)) then
+    begin
+      Result := AddLocalMaxPoly(e1, e2, pt);
+      {$IFDEF USINGZ}
+      if Assigned(Result) then SetZ(e1, e2, Result.pt);
+      {$ENDIF}
+
+    end else if IsFront(e1) or (e1.outrec = e2.outrec) then
+    begin
+      // this 'else if' condition isn't strictly needed but
+      // it's sensible to split polygons that ony touch at
+      // a common vertex (not at common edges).
+      Result := AddLocalMaxPoly(e1, e2, pt);
+      op2 := AddLocalMinPoly(e1, e2, pt);
+      {$IFDEF USINGZ}
+      if Assigned(Result) then SetZ(e1, e2, Result.pt);
+      SetZ(e1, e2, op2.pt);
+      {$ENDIF}
+      if Assigned(Result) and PointsEqual(Result.pt, op2.pt) and
+        not IsHorizontal(e1) and not IsHorizontal(e2) and
+        (CrossProduct(e1.bot, Result.pt, e2.bot) = 0) then
+          AddJoin(Result, op2);
+    end else
+    begin
+      // can't treat as maxima & minima
+      Result := AddOutPt(e1, pt);
+      op2 := AddOutPt(e2, pt);
+      {$IFDEF USINGZ}
+      SetZ(e1, e2, Result.pt);
+      SetZ(e1, e2, op2.pt);
+      {$ENDIF}
+      SwapOutRecs(e1, e2);
+    end;
+  end
+
+  // if one or other edge is 'hot' ...
+  else if IsHotEdge(e1) then
+  begin
+    Result := AddOutPt(e1, pt);
+    {$IFDEF USINGZ}
+    SetZ(e1, e2, Result.pt);
+    {$ENDIF}
+    SwapOutRecs(e1, e2);
+  end
+  else if IsHotEdge(e2) then
+  begin
+    Result := AddOutPt(e2, pt);
+    {$IFDEF USINGZ}
+    SetZ(e1, e2, Result.pt);
+    {$ENDIF}
+    SwapOutRecs(e1, e2);
+  end
+
+  // else neither edge is 'hot'
+  else
+  begin
+    case FFillRule of
+      frPositive:
+        begin
+          e1WindCnt2 := e1.windCnt2;
+          e2WindCnt2 := e2.windCnt2;
+        end;
+      frNegative:
+        begin
+          e1WindCnt2 := -e1.windCnt2;
+          e2WindCnt2 := -e2.windCnt2;
+        end;
+      else
+        begin
+          e1WindCnt2 := abs(e1.windCnt2);
+          e2WindCnt2 := abs(e2.windCnt2);
+        end;
+    end;
+
+    if not IsSamePolyType(e1, e2) then
+    begin
+      Result := AddLocalMinPoly(e1, e2, pt, false);
+      {$IFDEF USINGZ}
+      SetZ(e1, e2, Result.pt);
+      {$ENDIF}
+    end
+    else if (e1WindCnt = 1) and (e2WindCnt = 1) then
+    begin
+      Result := nil;
+      case FClipType of
+        ctIntersection:
+          if (e1WindCnt2 <= 0) or (e2WindCnt2 <= 0) then Exit
+          else Result := AddLocalMinPoly(e1, e2, pt, false);
+        ctUnion:
+          if (e1WindCnt2 <= 0) and (e2WindCnt2 <= 0) then
+            Result := AddLocalMinPoly(e1, e2, pt, false);
+        ctDifference:
+          if ((GetPolyType(e1) = ptClip) and
+                (e1WindCnt2 > 0) and (e2WindCnt2 > 0)) or
+              ((GetPolyType(e1) = ptSubject) and
+                (e1WindCnt2 <= 0) and (e2WindCnt2 <= 0)) then
+            Result := AddLocalMinPoly(e1, e2, pt, false);
+        else // xOr
+            Result := AddLocalMinPoly(e1, e2, pt, false);
+      end;
+      {$IFDEF USINGZ}
+      if assigned(Result) then SetZ(e1, e2, Result.pt);
+      {$ENDIF}
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+{$IFNDEF USINGZ}
+{$HINTS ON}
+{$ENDIF}
+
+function TClipperBase.ValidateClosedPathEx(var op: POutPt): Boolean;
+begin
+  Result := IsValidClosedPath(op);
+  if Result then Exit;
+  if Assigned(op) then
+    SafeDisposeOutPts(op);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DeleteEdges(var e: PActive);
+var
+  e2: PActive;
+begin
+  while Assigned(e) do
+  begin
+    e2 := e;
+    e := e.nextInAEL;
+    Dispose(e2);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DeleteFromAEL(e: PActive);
+var
+  aelPrev, aelNext: PActive;
+begin
+  aelPrev := e.prevInAEL;
+  aelNext := e.nextInAEL;
+  if not Assigned(aelPrev) and not Assigned(aelNext) and
+    (e <> FActives) then Exit; // already deleted
+  if Assigned(aelPrev) then aelPrev.nextInAEL := aelNext
+  else FActives := aelNext;
+  if Assigned(aelNext) then aelNext.prevInAEL := aelPrev;
+  Dispose(e);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.AdjustCurrXAndCopyToSEL(topY: Int64);
+var
+  e: PActive;
+begin
+  FSel := FActives;
+  e := FActives;
+  while Assigned(e) do
+  begin
+    e.prevInSEL := e.prevInAEL;
+    e.nextInSEL := e.nextInAEL;
+    e.jump := e.nextInSEL;
+    e.currX := TopX(e, topY);
+    e := e.nextInAEL;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.ExecuteInternal(clipType: TClipType;
+  fillRule: TFillRule; usingPolytree: Boolean);
+var
+  Y: Int64;
+  e: PActive;
+begin
+  if clipType = ctNone then Exit;
+  FFillRule := fillRule;
+  FClipType := clipType;
+  Reset;
+  if not PopScanLine(Y) then Exit;
+  while FSucceeded do
+  begin
+    InsertLocalMinimaIntoAEL(Y);
+    while PopHorz(e) do DoHorizontal(e);
+    ConvertHorzTrialsToJoins;
+    FBotY := Y;                       // FBotY == bottom of current scanbeam
+    if not PopScanLine(Y) then Break; // Y     == top of current scanbeam
+    DoIntersections(Y);
+    DoTopOfScanbeam(Y);
+    while PopHorz(e) do DoHorizontal(e);
+  end;
+  if FSucceeded then ProcessJoinList;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DoIntersections(const topY: Int64);
+begin
+  if BuildIntersectList(topY) then
+  try
+    ProcessIntersectList;
+  finally
+    DisposeIntersectNodes;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DisposeIntersectNodes;
+var
+  i: Integer;
+begin
+  for i := 0 to FIntersectList.Count - 1 do
+    Dispose(PIntersectNode(UnsafeGet(FIntersectList,i)));
+  FIntersectList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure Nullop;
+begin
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.AddNewIntersectNode(e1, e2: PActive; topY: Int64);
+var
+  ip: TPoint64;
+  absDx1, absDx2: double;
+  node: PIntersectNode;
+begin
+  if not GetIntersectPoint(e1.bot, e1.top, e2.bot, e2.top, ip) then
+    ip := Point64(e1.currX, topY);
+  // Rounding errors can occasionally place the calculated intersection
+  // point either below or above the scanbeam, so check and correct ...
+  if (ip.Y > FBotY) or (ip.Y < topY) then
+  begin
+    absDx1 := Abs(e1.dx);
+    absDx2 := Abs(e2.dx);
+    if (absDx1 > 100) and (absDx2 > 100) then
+    begin
+      if (absDx1 > absDx2) then
+        ip := GetClosestPointOnSegment(ip, e1.bot, e1.top) else
+        ip := GetClosestPointOnSegment(ip, e2.bot, e2.top);
+    end
+    else if (absDx1 > 100) then
+      ip := GetClosestPointOnSegment(ip, e1.bot, e1.top)
+    else if (absDx2 > 100) then
+      ip := GetClosestPointOnSegment(ip, e2.bot, e2.top)
+    else
+    begin
+      if (ip.Y < topY) then
+        ip.Y := topY else
+        ip.Y := fBotY;
+      if (absDx1 < absDx2)  then
+        ip.X := TopX(e1, topY) else
+        ip.X := TopX(e2, topY);
+    end;
+  end;
+  new(node);
+  node.active1 := e1;
+  node.active2 := e2;
+  node.pt := ip;
+  FIntersectList.Add(node);
+end;
+//------------------------------------------------------------------------------
+
+function ExtractFromSEL(edge: PActive): PActive;
+begin
+  // nb: edge.PrevInSEL is always assigned
+  Result := edge.nextInSEL;
+  if Assigned(Result) then
+    Result.prevInSEL := edge.prevInSEL;
+  edge.prevInSEL.nextInSEL := Result;
+end;
+//------------------------------------------------------------------------------
+
+procedure Insert1Before2InSEL(edge1, edge2: PActive);
+begin
+  edge1.prevInSEL := edge2.prevInSEL;
+  if Assigned(edge1.prevInSEL) then
+    edge1.prevInSEL.nextInSEL := edge1;
+  edge1.nextInSEL := edge2;
+  edge2.prevInSEL := edge1;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.BuildIntersectList(const topY: Int64): Boolean;
+var
+  q, base,prevBase,left,right, lend, rend: PActive;
+begin
+  result := false;
+  if not Assigned(FActives) or not Assigned(FActives.nextInAEL) then Exit;
+
+  // Calculate edge positions at the top of the current scanbeam, and from this
+  // we will determine the intersections required to reach these new positions.
+  AdjustCurrXAndCopyToSEL(topY);
+
+  // Find all edge intersections in the current scanbeam using a stable merge
+  // sort that ensures only adjacent edges are intersecting. Intersect info is
+  // stored in FIntersectList ready to be processed in ProcessIntersectList.
+  left := FSel;
+  while Assigned(left.jump) do
+  begin
+    prevBase := nil;
+    while Assigned(left) and Assigned(left.jump) do
+    begin
+      base := left;
+      right := left.jump;
+      rend  := right.jump;
+      left.jump := rend;
+      lend := right; rend := right.jump;
+      while (left <> lend) and (right <> rend) do
+      begin
+        if right.currX < left.currX then
+        begin
+          // save edge intersections
+          q := right.prevInSEL;
+          while true do
+          begin
+            AddNewIntersectNode(q, right, topY);
+            if q = left then Break;
+            q := q.prevInSEL;
+          end;
+
+          // now move the out of place edge on the right
+          // to its new ordered place on the left.
+          q := right;
+          right := ExtractFromSEL(q); // ie returns the new right
+          lend := right;
+          Insert1Before2InSEL(q, left);
+          if left = base then
+          begin
+            base := q;
+            base.jump := rend;
+            if Assigned(prevBase) then
+              prevBase.jump := base else
+              FSel := base;
+          end;
+        end else
+          left := left.nextInSEL;
+      end;
+      prevBase := base;
+      left := rend;
+    end;
+    left := FSel;
+  end;
+  result := FIntersectList.Count > 0;
+end;
+//------------------------------------------------------------------------------
+
+function IntersectListSort(node1, node2: Pointer): Integer;
+var
+  pt1, pt2: PPoint64;
+  i: Int64;
+begin
+  if node1 = node2 then
+  begin
+    Result := 0;
+    Exit;
+  end;
+  pt1 := @PIntersectNode(node1).pt;
+  pt2 := @PIntersectNode(node2).pt;
+  i := pt2.Y - pt1.Y;
+  // note to self - can't return int64 values :)
+  if i > 0 then Result := 1
+  else if i < 0 then Result := -1
+  else if (pt1 = pt2) then Result := 0
+  else
+  begin
+    // Sort by X too. Not essential, but it significantly
+    // speeds up the secondary sort in ProcessIntersectList .
+    i := pt1.X - pt2.X;
+    if i > 0 then Result := 1
+    else if i < 0 then Result := -1
+    else Result := 0;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.ProcessIntersectList;
+var
+  i: Integer;
+  nodeQ: PIntersectNode;
+  nodeI, nodeJ: PPIntersectNode;
+  op1, op2: POutpt;
+begin
+  // The list of required intersections now needs to be processed in a
+  // specific order such that intersection points with the largest Y coords
+  // are processed before those with the smallest Y coords. However,
+  // it's critical that edges are adjacent at the time of intersection, but
+  // that can only be checked during processing (when edge positions change).
+
+  // First we do a quicksort so that intersections will be processed
+  // mostly from largest Y to smallest
+  FIntersectList.Sort(IntersectListSort);
+
+  nodeI := @FIntersectList.List[0];
+  for i := 0 to FIntersectList.Count - 1 do
+  begin
+    // during processing, make sure edges are adjacent before
+    // proceeding, and swapping the order if they aren't adjacent.
+    if not EdgesAdjacentInAEL(nodeI^) then
+    begin
+      nodeJ := nodeI;
+      repeat
+        inc(nodeJ);
+      until EdgesAdjacentInAEL(nodeJ^);
+
+      // now swap intersection order
+      nodeQ := nodeI^;
+      nodeI^ := nodeJ^;
+      nodeJ^ := nodeQ;
+    end;
+
+    // now process the intersection
+    with nodeI^^ do
+    begin
+      IntersectEdges(active1, active2, pt);
+      SwapPositionsInAEL(active1, active2);
+
+      if TestJoinWithPrev2(active2, pt) then
+      begin
+        op1 := AddOutPt(active2.prevInAEL, pt);
+        op2 := AddOutPt(active2, pt);
+        if op1 <> op2 then
+          AddJoin(op1, op2);
+      end
+      else if TestJoinWithNext2(active1, pt) then
+      begin
+        op1 := AddOutPt(active1, pt);
+        op2 := AddOutPt(active1.nextInAEL, pt);
+        if op1 <> op2 then
+          AddJoin(op1, op2);
+      end;
+    end;
+    inc(nodeI);
+  end;
+  // Edges should once again be correctly ordered (left to right) in the AEL.
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.SwapPositionsInAEL(e1, e2: PActive);
+var
+  prev, next: PActive;
+begin
+  // preconditon: e1 must be immediately prior to e2
+  next := e2.nextInAEL;
+  if Assigned(next) then next.prevInAEL := e1;
+  prev := e1.prevInAEL;
+  if Assigned(prev) then prev.nextInAEL := e2;
+  e2.prevInAEL := prev;
+  e2.nextInAEL := e1;
+  e1.prevInAEL := e2;
+  e1.nextInAEL := next;
+  if not Assigned(e2.prevInAEL) then FActives := e2;
+end;
+//------------------------------------------------------------------------------
+
+function HorzIsSpike(horzEdge: PActive): Boolean;
+var
+  nextPt: TPoint64;
+begin
+  nextPt := NextVertex(horzEdge).pt;
+  Result := (nextPt.Y = horzEdge.top.Y) and
+    (horzEdge.bot.X < horzEdge.top.X) <> (horzEdge.top.X < nextPt.X);
+end;
+//------------------------------------------------------------------------------
+
+procedure TrimHorz(horzEdge: PActive; preserveCollinear: Boolean);
+var
+  pt: TPoint64;
+  wasTrimmed: Boolean;
+begin
+  wasTrimmed := false;
+  pt := NextVertex(horzEdge).pt;
+  while (pt.Y = horzEdge.top.Y) do
+  begin
+    // always trim 180 deg. spikes (in closed paths)
+    // but otherwise break if preserveCollinear = true
+    if preserveCollinear and
+    ((pt.X < horzEdge.top.X) <> (horzEdge.bot.X < horzEdge.top.X)) then
+      break;
+
+    horzEdge.vertTop := NextVertex(horzEdge);
+    horzEdge.top := pt;
+    wasTrimmed := true;
+    if IsMaxima(horzEdge) then Break;
+    pt := NextVertex(horzEdge).pt;
+  end;
+  if wasTrimmed then SetDx(horzEdge); // +/-infinity
+end;
+//------------------------------------------------------------------------------
+
+function HorzEdgesOverlap(x1a, x1b, x2a, x2b: Int64): Boolean;
+const
+  minOverlap: Int64 = 2;
+begin
+  if x1a > x1b + minOverlap then
+  begin
+    if x2a > x2b + minOverlap then
+      Result := not ((x1a <= x2b) or (x2a <= x1b)) else
+      Result := not ((x1a <= x2a) or (x2b <= x1b));
+  end
+  else if x1b > x1a + minOverlap then
+  begin
+    if x2a > x2b + minOverlap then
+      Result := not ((x1b <= x2b) or (x2a <= x1a)) else
+      Result := not ((x1b <= x2a) or (x2b <= x1a));
+  end else
+    Result := false;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.AddTrialHorzJoin(op: POutPt);
+begin
+  // make sure 'op' isn't added more than once
+  if not (op.outrec.isOpen) and not OutPtInTrialHorzList(op) then
+    FHorzTrials := MakeDummyJoiner(op, FHorzTrials);
+end;
+//------------------------------------------------------------------------------
+
+function FindTrialJoinParent(var joiner: PJoiner; op: POutPt): PJoiner;
+begin
+  Result := joiner;
+  while Assigned(Result) do
+  begin
+    if (op = Result.op1) then
+    begin
+      if Assigned(Result.next1) and (Result.next1.idx < 0) then
+      begin
+        joiner := Result.next1;
+        Exit;
+      end;
+      Result := Result.next1;
+    end else
+    begin
+      if Assigned(Result.next2) and (Result.next2.idx < 0) then
+      begin
+        joiner := Result.next2;
+        Exit;
+      end;
+      Result := Result.next2;
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DeleteTrialHorzJoin(op: POutPt);
+var
+  joiner, parentOp, parentH: PJoiner;
+begin
+  if not Assigned(FHorzTrials) then Exit;
+  joiner := op.joiner;
+  parentOp := nil;
+  while Assigned(joiner) do
+  begin
+    if (joiner.idx < 0) then
+    begin
+      // first remove joiner from FHorzTrials list
+      if joiner = FHorzTrials then
+        FHorzTrials := joiner.nextH
+      else
+      begin
+        parentH := FHorzTrials;
+        while parentH.nextH <> joiner do
+          parentH := parentH.nextH;
+        parentH.nextH := joiner.nextH;
+      end;
+      // now remove joiner from op's joiner list
+      if not Assigned(parentOp) then
+      begin
+        // joiner must be first one in list
+        op.joiner := joiner.next1;
+        Dispose(joiner);
+        joiner := op.joiner;
+      end else
+      begin
+        // this trial joiner isn't op's first
+        // nb: trial joiners only have a single 'op'
+        if op = parentOp.op1 then
+          parentOp.next1 := joiner.next1 else
+          parentOp.next2 := joiner.next1; // never joiner.next2
+        Dispose(joiner);
+        joiner := parentOp;
+      end;
+      // loop in case there's more than one trial join
+    end else
+    begin
+      // not a trial join but just to be sure there isn't one
+      // a little deeper, look further along the linked list
+      parentOp := FindTrialJoinParent(joiner, op);
+      if not Assigned(parentOp) then Break;
+    end;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function GetHorzExtendedHorzSeg(var op, op2: POutPt): Boolean;
+var
+  outRec: POutRec;
+begin
+  outRec := GetRealOutRec(op.outrec);
+  op2 := op;
+  if Assigned(outRec.frontE) then
+  begin
+    while (op.prev <> outRec.pts) and
+      (op.prev.pt.Y = op.pt.Y) do op := op.prev;
+    while (op2 <> outRec.pts) and
+      (op2.next.pt.Y = op2.pt.Y) do op2 := op2.next;
+    Result := (op2 <> op);
+  end else
+  begin
+    while (op.prev <> op2) and
+      (op.prev.pt.Y = op.pt.Y) do op := op.prev;
+    while (op2.next <> op) and
+      (op2.next.pt.Y = op2.pt.Y) do op2 := op2.next;
+    Result := (op2 <> op) and (op2.next <> op);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.ConvertHorzTrialsToJoins;
+var
+  op1a, op1b, op2a, op2b: POutPt;
+  joiner, joinerParent: PJoiner;
+  joined: Boolean;
+begin
+  while Assigned(FHorzTrials) do
+  begin
+    joiner := FHorzTrials;
+    FHorzTrials := FHorzTrials.nextH;
+    op1a := joiner.op1;
+    if op1a.joiner = joiner then
+    begin
+      op1a.joiner := joiner.next1;
+    end else
+    begin
+      joinerParent := FindJoinParent(joiner, op1a);
+      if joinerParent.op1 = op1a then
+        joinerParent.next1 := joiner.next1 else
+        joinerParent.next2 := joiner.next1;
+    end;
+    Dispose(joiner);
+
+    if not GetHorzExtendedHorzSeg(op1a, op1b) then
+    begin
+      CleanCollinear(op1a.outrec);
+      Continue;
+    end;
+
+    joined := false;
+    joiner := FHorzTrials;
+    while Assigned(joiner) do
+    begin
+      op2a := joiner.op1;
+      if GetHorzExtendedHorzSeg(op2a, op2b) and
+        HorzEdgesOverlap(op1a.pt.X, op1b.pt.X, op2a.pt.X, op2b.pt.X) then
+      begin
+        joined := true;
+        // overlap found so promote to a 'real' join
+        if PointsEqual(op1a.pt, op2b.pt) then
+          AddJoin(op1a, op2b)
+        else if PointsEqual(op1a.pt, op2a.pt) then
+          AddJoin(op1a, op2a)
+        else if PointsEqual(op1b.pt, op2a.pt) then
+          AddJoin(op1b, op2a)
+        else if PointsEqual(op1b.pt, op2b.pt) then
+          AddJoin(op1b, op2b)
+        else if ValueBetween(op1a.pt.X, op2a.pt.X, op2b.pt.X) then
+          AddJoin(op1a, InsertOp(op1a.pt, op2a))
+        else if ValueBetween(op1b.pt.X, op2a.pt.X, op2b.pt.X) then
+          AddJoin(op1b, InsertOp(op1b.pt, op2a))
+        else if ValueBetween(op2a.pt.X, op1a.pt.X, op1b.pt.X) then
+          AddJoin(op2a, InsertOp(op2a.pt, op1a))
+        else if ValueBetween(op2b.pt.X, op1a.pt.X, op1b.pt.X) then
+          AddJoin(op2b, InsertOp(op2b.pt, op1a));
+        Break;
+      end;
+      joiner := joiner.nextH;
+    end;
+    if not joined then
+      CleanCollinear(op1a.outrec);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DoHorizontal(horzEdge: PActive);
+var
+  maxPair: PActive;
+  horzLeft, horzRight: Int64;
+
+  function ResetHorzDirection: Boolean;
+  var
+    e: PActive;
+  begin
+    if (horzEdge.bot.X = horzEdge.top.X) then
+    begin
+      // the horizontal edge is going nowhere ...
+      horzLeft := horzEdge.currX;
+      horzRight := horzEdge.currX;
+      e := horzEdge.nextInAEL;
+      while assigned(e) and (e <> maxPair) do
+        e := e.nextInAEL;
+      Result := assigned(e);
+      // nb: this block isn't yet redundant
+    end
+    else if horzEdge.currX < horzEdge.top.X then
+    begin
+      horzLeft := horzEdge.currX;
+      horzRight := horzEdge.top.X;
+      Result := true;
+    end else
+    begin
+      horzLeft := horzEdge.top.X;
+      horzRight := horzEdge.currX;
+      Result := false;
+    end;
+  end;
+  //------------------------------------------------------------------------
+
+var
+  Y: Int64;
+  e: PActive;
+  pt: TPoint64;
+  op, op2: POutPt;
+  maxVertex: PVertex;
+  isLeftToRight, horzIsOpen: Boolean;
+begin
+(*******************************************************************************
+* Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or    *
+* bottom of a scanbeam) are processed as if layered. The order in which HEs    *
+* are processed doesn't matter. HEs intersect with the bottom vertices of      *
+* other HEs [#] and with non-horizontal edges [*]. Once these intersections    *
+* are completed, intermediate HEs are 'promoted' to the next edge in their     *
+* bounds, and they in turn may be intersected [%] by other HEs.                *
+*                                                                              *
+* eg: 3 horizontals at a scanline:  /   |                     /          /     *
+*              |                   /    |    (HE3) o=========%==========o      *
+*              o=======o (HE2)    /     |         /         /                  *
+*         o============#=========*======*========#=========o (HE1)             *
+*        /             |        /       |       /                              *
+*******************************************************************************)
+
+  horzIsOpen := IsOpen(horzEdge);
+  Y := horzEdge.bot.Y;
+  maxVertex := nil;
+  maxPair := nil;
+
+  if not horzIsOpen then
+  begin
+    maxVertex := GetCurrYMaximaVertex(horzEdge);
+    if Assigned(maxVertex) then
+    begin
+      maxPair := GetHorzMaximaPair(horzEdge, maxVertex);
+      // remove 180 deg.spikes and also simplify
+      // consecutive horizontals when PreserveCollinear = true
+      if (maxVertex <> horzEdge.vertTop) then
+          TrimHorz(horzEdge, FPreserveCollinear);
+    end;
+  end;
+
+  isLeftToRight := ResetHorzDirection;
+
+  // nb: TrimHorz above hence not using Bot.X here
+  if IsHotEdge(horzEdge) then
+  {$IFDEF USINGZ}
+    AddOutPt(horzEdge, Point64(horzEdge.currX, Y, horzEdge.bot.Z));
+  {$ELSE}
+    AddOutPt(horzEdge, Point64(horzEdge.currX, Y));
+  {$ENDIF}
+
+  while true do // loop through consec. horizontal edges
+  begin
+
+    if horzIsOpen and
+      IsMaxima(horzEdge) and not IsOpenEnd(horzEdge) then
+    begin
+      maxVertex := GetCurrYMaximaVertex(horzEdge);
+      if Assigned(maxVertex) then
+        maxPair := GetHorzMaximaPair(horzEdge, maxVertex);
+    end;
+
+    if isLeftToRight  then
+      e := horzEdge.nextInAEL else
+      e := horzEdge.prevInAEL;
+
+    while assigned(e) do
+    begin
+      if (e = maxPair) then
+      begin
+        if IsHotEdge(horzEdge) then
+        begin
+          while horzEdge.vertTop <> e.vertTop do
+          begin
+            AddOutPt(horzEdge, horzEdge.top);
+            UpdateEdgeIntoAEL(horzEdge);
+          end;
+          op := AddLocalMaxPoly(e, horzEdge, horzEdge.top);
+          if Assigned(op) and not IsOpen(horzEdge) and
+            PointsEqual(op.pt, horzEdge.top) then
+              AddTrialHorzJoin(op);
+        end;
+        // remove horzEdge's maxPair from AEL
+        DeleteFromAEL(e);
+        DeleteFromAEL(horzEdge);
+        Exit;
+      end;
+
+      // if horzEdge is a maxima, keep going until we reach
+      // its maxima pair, otherwise check for Break conditions
+      if (maxVertex <> horzEdge.vertTop) or IsOpenEnd(horzEdge) then
+      begin
+        // otherwise stop when 'e' is beyond the end of the horizontal line
+        if (isLeftToRight and (e.currX > horzRight)) or
+          (not isLeftToRight and (e.currX < horzLeft)) then Break;
+
+        if (e.currX = horzEdge.top.X) and not IsHorizontal(e) then
+        begin
+          pt := NextVertex(horzEdge).pt;
+
+          // to maximize the possibility of putting open edges into
+          // solutions, we'll only break if it's past HorzEdge's end
+          if IsOpen(E) and not IsSamePolyType(E, horzEdge) and
+            not IsHotEdge(e) then
+          begin
+            if (isLeftToRight and (TopX(E, pt.Y) > pt.X)) or
+              (not isLeftToRight and (TopX(E, pt.Y) < pt.X)) then Break;
+          end
+          // otherwise for edges at horzEdge's end, only stop when horzEdge's
+          // outslope is greater than e's slope when heading right or when
+          // horzEdge's outslope is less than e's slope when heading left.
+          else if (isLeftToRight and (TopX(E, pt.Y) >= pt.X)) or
+              (not isLeftToRight and (TopX(E, pt.Y) <= pt.X)) then Break;
+        end;
+      end;
+
+      pt := Point64(e.currX, Y);
+
+      if (isLeftToRight) then
+      begin
+        op := IntersectEdges(horzEdge, e, pt);
+        //nb: Op.outrec will differ from horzEdge.outrec when IsOpen(e)
+        SwapPositionsInAEL(horzEdge, e);
+
+        if IsHotEdge(horzEdge) and Assigned(op) and
+          not IsOpen(horzEdge) and PointsEqual(op.pt, pt) then
+            AddTrialHorzJoin(op);
+
+        if not IsHorizontal(e) and
+          TestJoinWithPrev1(e) then
+        begin
+          op := AddOutPt(e.prevInAEL, pt);
+          op2 := AddOutPt(e, pt);
+          AddJoin(op, op2);
+        end;
+        horzEdge.currX := e.currX;
+        e := horzEdge.nextInAEL;
+      end else
+      begin
+        op := IntersectEdges(e, horzEdge, pt);
+        //nb: Op.outrec will differ from horzEdge.outrec when IsOpen(e)
+        SwapPositionsInAEL(e, horzEdge);
+
+        if IsHotEdge(horzEdge) and Assigned(op) and
+          not IsOpen(horzEdge) and
+          PointsEqual(op.pt, pt) then
+            AddTrialHorzJoin(op);
+
+        if not IsHorizontal(e) and
+          TestJoinWithNext1(e) then
+        begin
+          op := AddOutPt(e, pt);
+          op2 := AddOutPt(e.nextInAEL, pt);
+          AddJoin(op, op2);
+        end;
+        horzEdge.currX := e.currX;
+        e := horzEdge.prevInAEL;
+      end;
+    end; // we've reached the end of this horizontal
+
+    // check if we've finished looping through consecutive horizontals
+    if horzIsOpen and IsOpenEnd(horzEdge) then
+    begin
+      if IsHotEdge(horzEdge) then
+      begin
+        AddOutPt(horzEdge, horzEdge.top);
+        if IsFront(horzEdge) then
+          horzEdge.outrec.frontE := nil else
+          horzEdge.outrec.backE := nil;
+        horzEdge.outrec := nil;
+      end;
+      DeleteFromAEL(horzEdge); // ie open at top
+      Exit;
+    end
+    else if (NextVertex(horzEdge).pt.Y <> horzEdge.top.Y) then
+      Break;
+
+    // there must be a following (consecutive) horizontal
+
+    if IsHotEdge(horzEdge) then
+      AddOutPt(horzEdge, horzEdge.top);
+    UpdateEdgeIntoAEL(horzEdge);
+
+    if PreserveCollinear and
+      not horzIsOpen and HorzIsSpike(horzEdge) then
+        TrimHorz(horzEdge, true);
+
+    isLeftToRight := ResetHorzDirection;
+  end; // end while horizontal
+
+  if IsHotEdge(horzEdge) then
+  begin
+    op := AddOutPt(horzEdge, horzEdge.top);
+    if not IsOpen(horzEdge) then
+      AddTrialHorzJoin(op);
+  end else
+    op := nil;
+
+  if (horzIsOpen and not IsOpenEnd(horzEdge)) or
+    (not horzIsOpen and (maxVertex <> horzEdge.vertTop)) then
+  begin
+    UpdateEdgeIntoAEL(horzEdge); // this is the end of an intermediate horiz.
+    if IsOpen(horzEdge) then Exit;
+
+    if isLeftToRight and TestJoinWithNext1(horzEdge) then
+    begin
+      op2 := AddOutPt(horzEdge.nextInAEL, horzEdge.bot);
+      AddJoin(op, op2);
+    end
+    else if not isLeftToRight and TestJoinWithPrev1(horzEdge) then
+    begin
+      op2 := AddOutPt(horzEdge.prevInAEL, horzEdge.bot);
+      AddJoin(op2, op);
+    end;
+
+  end
+  else if IsHotEdge(horzEdge) then
+    AddLocalMaxPoly(horzEdge, maxPair, horzEdge.top)
+  else
+  begin
+    DeleteFromAEL(maxPair);
+    DeleteFromAEL(horzEdge);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DoTopOfScanbeam(Y: Int64);
+var
+  e: PActive;
+begin
+  // FSel is reused to flag horizontals (see PushHorz below)
+  FSel := nil;
+  e := FActives;
+  while Assigned(e) do
+  begin
+    // nb: 'e' will never be horizontal here
+    if (e.top.Y = Y) then
+    begin
+      e.currX := e.top.X;
+      if IsMaxima(e) then
+      begin
+        e := DoMaxima(e);  // TOP OF BOUND (MAXIMA)
+        Continue;
+      end else
+      begin
+        // INTERMEDIATE VERTEX ...
+        if IsHotEdge(e) then
+          AddOutPt(e, e.top);
+        UpdateEdgeIntoAEL(e);
+        if IsHorizontal(e) then
+          PushHorz(e);
+      end;
+    end else
+      e.currX := TopX(e, Y);
+    e := e.nextInAEL;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.DoMaxima(e: PActive): PActive;
+var
+  eNext, ePrev, eMaxPair: PActive;
+begin
+  ePrev := e.prevInAEL;
+  eNext := e.nextInAEL;
+  Result := eNext;
+
+  if IsOpenEnd(e) then
+  begin
+    if IsHotEdge(e) then AddOutPt(e, e.top);
+    if not IsHorizontal(e) then
+    begin
+      if IsHotEdge(e) then
+      begin
+        if IsFront(e) then
+          e.outrec.frontE := nil else
+          e.outrec.backE := nil;
+        e.outrec := nil;
+      end;
+      DeleteFromAEL(e);
+    end;
+    Exit;
+  end else
+  begin
+    eMaxPair := GetMaximaPair(e);
+    if not assigned(eMaxPair) then Exit; // EMaxPair is a horizontal ...
+  end;
+
+  // only non-horizontal maxima here.
+  // process any edges between maxima pair ...
+  while (eNext <> eMaxPair) do
+  begin
+    IntersectEdges(e, eNext, e.top);
+    SwapPositionsInAEL(e, eNext);
+    eNext := e.nextInAEL;
+  end;
+
+  if IsOpen(e) then
+  begin
+    // must be in the middle of an open path
+    if IsHotEdge(e) then
+      AddLocalMaxPoly(e, eMaxPair, e.top);
+    DeleteFromAEL(eMaxPair);
+    DeleteFromAEL(e);
+
+    if assigned(ePrev) then
+      Result := ePrev.nextInAEL else
+      Result := FActives;
+  end else
+  begin
+    // here E.NextInAEL == ENext == EMaxPair ...
+    if IsHotEdge(e) then
+      AddLocalMaxPoly(e, eMaxPair, e.top);
+
+    DeleteFromAEL(e);
+    DeleteFromAEL(eMaxPair);
+    if assigned(ePrev) then
+      Result := ePrev.nextInAEL else
+      Result := FActives;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.BuildPaths(out closedPaths, openPaths: TPaths64): Boolean;
+var
+  i, cntClosed, cntOpen: Integer;
+  ppor: PPOutRec;
+  outRec: POutRec;
+begin
+  try
+    cntClosed := 0; cntOpen := 0;
+    SetLength(closedPaths, FOutRecList.Count);
+    if FHasOpenPaths then
+      SetLength(openPaths, FOutRecList.Count);
+
+    if FOutRecList.Count > 0 then
+    begin
+      ppor := PPOutRec(FOutRecList.ListPtr);
+      for i := 0 to FOutRecList.Count -1 do
+      begin
+        outRec := ppor^;
+        inc(ppor);
+
+        if not assigned(outRec.pts) then Continue;
+        if outRec.isOpen then
+        begin
+          if BuildPath(outRec.pts, FReverseSolution,
+            true, openPaths[cntOpen]) then
+              inc(cntOpen);
+        end else
+        begin
+          // closed paths should always return a Positive orientation
+          // except when ReverseSolution == true
+          if BuildPath(outRec.pts, FReverseSolution,
+            false, closedPaths[cntClosed]) then
+              inc(cntClosed);
+        end;
+      end;
+    end;
+    SetLength(closedPaths, cntClosed);
+    SetLength(openPaths, cntOpen);
+    result := true;
+  except
+    result := false;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function Path1InsidePath2(const or1, or2: POutRec): Boolean;
+var
+  op: POutPt;
+  pipResult: TPointInPolygonResult;
+begin
+  op := or1.pts;
+  repeat
+    pipResult := PointInPolygon(op.pt, or2.path);
+    if pipResult <> pipOn then Break;
+    op := op.next;
+  until op = or1.pts;
+  if (pipResult = pipOn) then
+  begin
+     Result := Area(op) < Area(or2.pts);
+  end else
+    Result := pipResult = pipInside;
+end;
+//------------------------------------------------------------------------------
+
+function GetBounds(const path: TPath64): TRect64;
+var
+  i: integer;
+  pX, pY: PInt64;
+begin
+  if Length(path) = 0 then
+  begin
+    Result := NullRect64;
+    Exit;
+  end;
+  result := Rect64(MaxInt64, MaxInt64, -MaxInt64, -MaxInt64);
+  pX := @path[0].X;
+  pY := @path[0].Y;
+
+  for i := 0 to High(path) do
+  begin
+
+    if (pX^ < result.left) then result.left := pX^;
+    if (pX^ > result.right) then result.right := pX^;
+    if (pY^ < result.top) then result.top := pY^;
+    if (pY^ > result.bottom) then result.bottom := pY^;
+    inc(pX, 2); inc(pY, 2);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.DeepCheckOwner(outrec, owner: POutRec): Boolean;
+var
+  i: integer;
+  split: POutRec;
+  isInsideOwnerBounds: Boolean;
+begin
+  if (owner.bounds.IsEmpty) then
+    owner.bounds := Clipper.Engine.GetBounds(owner.path);
+  isInsideOwnerBounds := owner.bounds.Contains(outrec.bounds);
+
+  // while looking for the correct owner, check the owner's
+  // splits **before** checking the owner itself because
+  // splits can occur internally, and checking the owner
+  // first would miss the inner split's true ownership
+  result := false;
+  for i := 0 to High(owner.splits) do
+  begin
+    split :=GetRealOutRec(owner.splits[i]);
+    if not Assigned(split) or
+      (split.idx <= owner.idx) or (split = outrec) then
+        Continue;
+
+    if Assigned(split.splits) and DeepCheckOwner(outrec, split) then
+    begin
+      Result := true;
+      Exit;
+    end;
+
+    if Length(split.path) = 0 then
+      BuildPath(split.pts, FReverseSolution, false, split.path);
+    if split.bounds.IsEmpty then
+      split.bounds := Clipper.Engine.GetBounds(split.path);
+    if split.bounds.Contains(OutRec.bounds) and
+      Path1InsidePath2(OutRec, split) then
+    begin
+      outRec.owner := split;
+      Result := true;
+      Exit;
+    end;
+  end;
+
+  // only continue when not inside recursion
+  if (owner <> outrec.owner) then Exit;
+
+  while true do
+  begin
+    if isInsideOwnerBounds and
+      Path1InsidePath2(outrec, outrec.owner) then
+    begin
+      Result := true;
+      Exit;
+    end;
+    outrec.owner := outrec.owner.owner;
+    if not assigned(outrec.owner) then Exit;
+    isInsideOwnerBounds := outrec.owner.bounds.Contains(outrec.bounds);
+  end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.BuildTree(polytree: TPolyPathBase; out openPaths: TPaths64);
+var
+  i,j         : Integer;
+  cntOpen     : Integer;
+  outRec      : POutRec;
+  openPath    : TPath64;
+  ownerPP     : TPolyPathBase;
+  ppor        : PPOutRec;
+begin
+  try
+    polytree.Clear;
+    if FHasOpenPaths then
+      setLength(openPaths, FOutRecList.Count);
+    cntOpen := 0;
+
+    if FOutRecList.Count > 0 then
+    begin
+      ppor := PPOutRec(FOutRecList.ListPtr);
+      for i := 0 to FOutRecList.Count -1 do
+      begin
+        outRec := ppor^;
+        inc(ppor);
+        if not assigned(outRec.pts) then Continue;
+
+        if outRec.isOpen then
+        begin
+          if BuildPath(outRec.pts,
+            FReverseSolution, true, openPath) then
+          begin
+            openPaths[cntOpen] := openPath;
+            inc(cntOpen);
+          end;
+          Continue;
+        end;
+
+        if not BuildPath(outRec.pts, FReverseSolution, false, outRec.path) then
+          Continue;
+        if outrec.bounds.IsEmpty then
+          outrec.bounds := Clipper.Engine.GetBounds(outrec.path);
+        outrec.owner := GetRealOutRec(outrec.owner);
+        if assigned(outRec.owner) then
+          DeepCheckOwner(outRec, outRec.owner);
+
+        // swap the order when a child preceeds its owner
+        // (because owners must preceed children in polytrees)
+        if assigned(outRec.owner) and
+          (outRec.owner.idx > outRec.idx) then
+        begin
+          j := outRec.owner.idx;
+          FOutRecList.UnsafeSwap(i,j);
+          POutrec(FOutRecList.UnsafeGet(j)).idx := j;
+          outRec := POutrec(FOutRecList.UnsafeGet(i));
+          outRec.idx := i;
+
+          outRec.owner := GetRealOutRec(outRec.owner);
+          BuildPath(outRec.pts, FReverseSolution, false, outRec.path);
+          if (outRec.bounds.IsEmpty) then
+            outRec.bounds := Clipper.Engine.GetBounds(outRec.path);
+          if Assigned(outRec.owner) then
+            DeepCheckOwner(outRec, outRec.owner);
+        end;
+
+        if assigned(outRec.owner) and assigned(outRec.owner.polypath) then
+          ownerPP := outRec.owner.polypath else
+          ownerPP := polytree;
+
+        outRec.polypath := ownerPP.AddChild(outRec.path);
+      end;
+    end;
+    setLength(openPaths, cntOpen);
+  except
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.GetBounds: TRect64;
+var
+  i: Integer;
+  v, vStart: PVertex;
+begin
+  Result := Rect64(MaxInt64, MaxInt64, -MaxInt64, -MaxInt64);
+  for i := 0 to FVertexArrayList.Count -1 do
+  begin
+    vStart := UnsafeGet(FVertexArrayList, i);
+    v := vStart;
+    repeat
+      if v.pt.X < Result.Left then Result.Left := v.pt.X
+      else if v.pt.X > Result.Right then Result.Right := v.pt.X;
+      if v.pt.Y < Result.Top then Result.Top := v.pt.Y
+      else if v.pt.Y > Result.Bottom then Result.Bottom := v.pt.Y;
+      v := v.next;
+    until v = vStart;
+  end;
+  if Result.Left > Result.Right then Result := NullRect64;
+end;
+
+//------------------------------------------------------------------------------
+// TClipper methods
+//------------------------------------------------------------------------------
+
+procedure TClipper64.AddSubject(const subject: TPath64);
+begin
+  AddPath(subject, ptSubject, false);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper64.AddSubject(const subjects: TPaths64);
+begin
+  AddPaths(subjects, ptSubject, false);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper64.AddOpenSubject(const subject: TPath64);
+begin
+  AddPath(subject, ptSubject, true);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper64.AddOpenSubject(const subjects: TPaths64);
+begin
+  AddPaths(subjects, ptSubject, true);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper64.AddClip(const clip: TPath64);
+begin
+  AddPath(clip, ptClip, false);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper64.AddClip(const clips: TPaths64);
+begin
+  AddPaths(clips, ptClip, false);
+end;
+//------------------------------------------------------------------------------
+
+function TClipper64.Execute(clipType: TClipType;
+  fillRule: TFillRule; out closedSolutions: TPaths64): Boolean;
+var
+  dummy: TPaths64;
+begin
+  FUsingPolytree := false;
+  closedSolutions := nil;
+  try try
+    ExecuteInternal(clipType, fillRule, false);
+    BuildPaths(closedSolutions, dummy);
+    Result := Succeeded;
+  except
+    Result := false;
+  end;
+  finally
+    ClearSolution;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper64.Execute(clipType: TClipType; fillRule: TFillRule;
+  out closedSolutions, openSolutions: TPaths64): Boolean;
+begin
+  closedSolutions := nil;
+  openSolutions := nil;
+  FUsingPolytree := false;
+  try try
+    ExecuteInternal(clipType, fillRule, false);
+    BuildPaths(closedSolutions, openSolutions);
+    Result := Succeeded;
+  except
+    Result := false;
+  end;
+  finally
+    ClearSolution;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper64.Execute(clipType: TClipType; fillRule: TFillRule;
+  var solutionTree: TPolyTree64; out openSolutions: TPaths64): Boolean;
+begin
+  if not assigned(solutionTree) then
+    Raise EClipper2LibException(rsClipper_PolyTreeErr);
+  solutionTree.Clear;
+  FUsingPolytree := true;
+  openSolutions := nil;
+  try try
+    ExecuteInternal(clipType, fillRule, true);
+    BuildTree(solutionTree, openSolutions);
+    Result := Succeeded;
+  except
+    Result := false;
+  end;
+  finally
+    ClearSolution;
+  end;
+end;
+
+//------------------------------------------------------------------------------
+// TPolyPathBase methods
+//------------------------------------------------------------------------------
+
+constructor TPolyPathBase.Create;
+begin
+  FChildList := TList.Create;
+end;
+//------------------------------------------------------------------------------
+
+destructor TPolyPathBase.Destroy;
+begin
+  Clear;
+  FChildList.Free;
+  inherited Destroy;
+end;
+//------------------------------------------------------------------------------
+
+type
+  PPolyPathBase = ^TPolyPathBase;
+
+procedure TPolyPathBase.Clear;
+var
+  i: integer;
+  ppb: PPolyPathBase;
+begin
+  if FChildList.Count = 0 then Exit;
+  ppb := @FChildList.List[0];
+  for i := 0 to FChildList.Count -1 do
+  begin
+    ppb^.Free;
+    inc(ppb);
+  end;
+  FChildList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+function  TPolyPathBase.GetChild(index: Integer): TPolyPathBase;
+begin
+  if (index < 0) or (index >= FChildList.Count) then
+    Result := nil else
+    Result := FChildList[index];
+end;
+//------------------------------------------------------------------------------
+
+function  TPolyPathBase.GetIsHole: Boolean;
+var
+  pp: TPolyPathBase;
+begin
+  pp := FParent;
+  result := assigned(pp);
+  if not Result then Exit;
+  while assigned(pp) do
+  begin
+    result := not result;
+    pp := pp.FParent;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function  TPolyPathBase.GetChildCnt: Integer;
+begin
+  Result := FChildList.Count;
+end;
+
+//------------------------------------------------------------------------------
+//TPolyPath method
+//------------------------------------------------------------------------------
+
+function TPolyPath64.AddChild(const path: TPath64): TPolyPathBase;
+begin
+  Result := TPolyPath64.Create;
+  Result.Parent := self;
+  TPolyPath64(Result).FPath := path;;
+  ChildList.Add(Result);
+end;
+//------------------------------------------------------------------------------
+
+function TPolyPath64.GetChild64(index: Integer): TPolyPath64;
+begin
+  Result := TPolyPath64(GetChild(index));
+end;
+
+//------------------------------------------------------------------------------
+// TClipperD methods
+//------------------------------------------------------------------------------
+
+constructor TClipperD.Create(precision: integer);
+begin
+  inherited Create;
+  CheckPrecisionRange(precision);
+  FScale := Math.Power(10, precision);
+  FInvScale := 1/FScale;
+end;
+//------------------------------------------------------------------------------
+
+{$IFDEF USINGZ}
+procedure TClipperD.CheckCallback;
+begin
+  // only when the user defined ZCallback function has been assigned
+  // do we assign the proxy callback ZCB to ClipperBase
+  if Assigned(ZCallback) then
+    inherited ZCallback := ZCB else
+    inherited ZCallback := nil;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperD.ZCB(const bot1, top1, bot2, top2: TPoint64;
+  var intersectPt: TPoint64);
+var
+  tmp: TPointD;
+begin
+  if not assigned(fZCallback) then Exit;
+  // de-scale (x & y)
+  // temporarily convert integers to their initial float values
+  // this will slow clipping marginally but will make it much easier
+  // to understand the coordinates passed to the callback function
+  tmp := ScalePoint(intersectPt, FInvScale);
+  //do the callback
+  fZCallback(
+    ScalePoint(bot1, FInvScale),
+    ScalePoint(top1, FInvScale),
+    ScalePoint(bot2, FInvScale),
+    ScalePoint(top2, FInvScale), tmp);
+  intersectPt.Z := tmp.Z;
+end;
+//------------------------------------------------------------------------------
+{$ENDIF}
+
+procedure TClipperD.AddSubject(const pathD: TPathD);
+var
+  p: TPath64;
+begin
+  if FScale = 0 then FScale := DefaultClipperDScale;
+  p := ScalePath(pathD, FScale);
+  AddPath(p, ptSubject, false);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperD.AddSubject(const pathsD: TPathsD);
+var
+  pp: TPaths64;
+begin
+  if FScale = 0 then FScale := DefaultClipperDScale;
+  pp := ScalePaths(pathsD, FScale);
+  AddPaths(pp, ptSubject, false);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperD.AddOpenSubject(const pathD: TPathD);
+var
+  p: TPath64;
+begin
+  if FScale = 0 then FScale := DefaultClipperDScale;
+  p := ScalePath(pathD, FScale);
+  AddPath(p, ptSubject, true);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperD.AddOpenSubject(const pathsD: TPathsD);
+var
+  pp: TPaths64;
+begin
+  if FScale = 0 then FScale := DefaultClipperDScale;
+  pp := ScalePaths(pathsD, FScale);
+  AddPaths(pp, ptSubject, true);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperD.AddClip(const pathD: TPathD);
+var
+  p: TPath64;
+begin
+  if FScale = 0 then FScale := DefaultClipperDScale;
+  p := ScalePath(pathD, FScale);
+  AddPath(p, ptClip, false);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperD.AddClip(const pathsD: TPathsD);
+var
+  pp: TPaths64;
+begin
+  if FScale = 0 then FScale := DefaultClipperDScale;
+  pp := ScalePaths(pathsD, FScale);
+  AddPaths(pp, ptClip, false);
+end;
+//------------------------------------------------------------------------------
+
+function TClipperD.Execute(clipType: TClipType; fillRule: TFillRule;
+  out closedSolutions: TPathsD): Boolean;
+var
+  dummy: TPathsD;
+begin
+  Result := Execute(clipType, fillRule, closedSolutions, dummy);
+end;
+//------------------------------------------------------------------------------
+
+function TClipperD.Execute(clipType: TClipType; fillRule: TFillRule;
+  out closedSolutions, openSolutions: TPathsD): Boolean;
+var
+  solClosed, solOpen: TPaths64;
+begin
+{$IFDEF USINGZ}
+    CheckCallback;
+{$ENDIF}
+  closedSolutions := nil;
+  openSolutions := nil;
+  try try
+    ExecuteInternal(clipType, fillRule, false);
+    Result := BuildPaths(solClosed, solOpen);
+    if not Result then Exit;
+    closedSolutions := ScalePathsD(solClosed, FInvScale);
+    openSolutions := ScalePathsD(solOpen, FInvScale);
+  except
+    Result := false;
+  end;
+  finally
+    ClearSolution;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperD.Execute(clipType: TClipType; fillRule: TFillRule;
+  var solutionsTree: TPolyTreeD; out openSolutions: TPathsD): Boolean;
+var
+  open_Paths: TPaths64;
+begin
+  if not assigned(solutionsTree) then
+    Raise EClipper2LibException(rsClipper_PolyTreeErr);
+{$IFDEF USINGZ}
+    CheckCallback;
+{$ENDIF}
+  solutionsTree.Clear;
+  FUsingPolytree := true;
+  solutionsTree.SetScale(fScale);
+  openSolutions := nil;
+  try try
+    ExecuteInternal(clipType, fillRule, true);
+    BuildTree(solutionsTree, open_Paths);
+    openSolutions := ScalePathsD(open_Paths, FInvScale);
+    Result := true;
+  except
+    Result := false;
+  end;
+  finally
+    ClearSolution;
+  end;
+end;
+
+//------------------------------------------------------------------------------
+// TPolyPathD methods
+//------------------------------------------------------------------------------
+
+function TPolyPathD.AddChild(const path: TPath64): TPolyPathBase;
+begin
+  Result := TPolyPathD.Create;
+  Result.Parent := self;
+  TPolyPathD(Result).fScale := fScale;
+  TPolyPathD(Result).FPath := ScalePathD(path, 1/FScale);
+  ChildList.Add(Result);
+end;
+//------------------------------------------------------------------------------
+
+function TPolyPathD.GetChildD(index: Integer): TPolyPathD;
+begin
+  Result := TPolyPathD(GetChild(index));
+end;
+
+//------------------------------------------------------------------------------
+// TPolyTreeD
+//------------------------------------------------------------------------------
+
+procedure TPolyTreeD.SetScale(value: double);
+begin
+  FScale := value;
+end;
+//------------------------------------------------------------------------------
+
+end.
+

+ 138 - 0
polygon.mod/clipper2/Delphi/Clipper2Lib/Clipper.Minkowski.pas

@@ -0,0 +1,138 @@
+unit Clipper.Minkowski;
+
+(*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  15 October 2022                                                 *
+* Copyright :  Angus Johnson 2010-2022                                         *
+* Purpose   :  Minkowski Addition and Difference                               *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************)
+
+{$I Clipper.inc}
+
+interface
+
+uses
+  Classes, Math, Clipper.Core;
+
+function MinkowskiSum(const Pattern, Path: TPath64;
+  PathIsClosed: Boolean): TPaths64; overload;
+function MinkowskiSum(const Pattern, Path: TPathD;
+  PathIsClosed: Boolean; decimalPlaces: integer = 2): TPathsD; overload;
+function MinkowskiDiff(const path1, path2: TPath64): TPaths64; overload;
+function MinkowskiDiff(const Pattern, Path: TPathD;
+  PathIsClosed: Boolean; decimalPlaces: integer): TPathsD; overload;
+
+implementation
+
+uses
+  Clipper;
+
+function AddPoints(val1, val2: TPoint64): TPoint64;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result.X := val1.X + val2.X;
+  Result.Y := val1.Y + val2.Y;
+end;
+//------------------------------------------------------------------------------
+
+function SubtractPoints(val1, val2: TPoint64): TPoint64;
+  {$IFDEF INLINING} inline; {$ENDIF}
+begin
+  Result.X := val1.X - val2.X;
+  Result.Y := val1.Y - val2.Y;
+end;
+//------------------------------------------------------------------------------
+
+function Minkowski(const Base, Path: TPath64;
+  IsSum: Boolean; IsClosed: Boolean): TPaths64;
+var
+  i,j,k,g,h, delta, baseLen, pathLen: integer;
+  tmp: TPaths64;
+  quad: TPath64;
+begin
+  if IsClosed then
+    delta := 0 else
+    delta := 1;
+  baseLen := Length(Base);
+  pathLen := Length(Path);
+  setLength(tmp, pathLen);
+
+  for i := 0 to pathLen -1 do
+  begin
+    setLength(tmp[i], baseLen);
+    if IsSum then
+      for j := 0 to baseLen -1 do
+        tmp[i][j] := AddPoints(Path[i], Base[j])
+    else
+      for j := 0 to baseLen -1 do
+        tmp[i][j] := SubtractPoints(Path[i], Base[j]);
+  end;
+
+  SetLength(quad, 4);
+  SetLength(Result, (pathLen - delta) * baseLen);
+
+  if IsClosed then
+    g := pathLen - 1 else
+    g := 0;
+
+  for i := delta to pathLen - 1 do
+  begin
+    h := baseLen - 1;
+    k := (i - delta) * baseLen;
+    for j := 0 to baseLen - 1 do
+    begin
+      quad[0] := tmp[g][h];
+      quad[1] := tmp[i][h];
+      quad[2] := tmp[i][(j)];
+      quad[3] := tmp[g][(j)];
+      if not IsPositive(quad) then
+        Result[k + j] := ReversePath(quad) else
+        Result[k + j] := copy(quad, 0, 4);
+      h := j;
+    end;
+    g := i;
+  end;
+end;
+//------------------------------------------------------------------------------
+
+function MinkowskiSum(const Pattern, Path: TPath64; PathIsClosed: Boolean): TPaths64;
+begin
+   Result := Union( Minkowski(Pattern, Path, true, PathIsClosed), frNonZero);
+end;
+//------------------------------------------------------------------------------
+
+function MinkowskiSum(const Pattern, Path: TPathD;
+  PathIsClosed: Boolean; decimalPlaces: integer): TPathsD;
+var
+  tmp: TPaths64;
+  scale: double;
+begin
+  scale := Power(10, decimalPlaces);
+  tmp := Union( Minkowski(ScalePath(Pattern, scale),
+    ScalePath(Path, scale), true, PathIsClosed), frNonZero);
+  Result := ScalePathsD(tmp, 1/scale);
+end;
+//------------------------------------------------------------------------------
+
+function MinkowskiDiff(const path1, path2: TPath64): TPaths64;
+begin
+  Result := Union( Minkowski(path1, path2, false, true), frNonZero);
+end;
+//------------------------------------------------------------------------------
+
+function MinkowskiDiff(const Pattern, Path: TPathD;
+  PathIsClosed: Boolean; decimalPlaces: integer): TPathsD;
+var
+  tmp: TPaths64;
+  scale: double;
+begin
+  scale := Power(10, decimalPlaces);
+  tmp := Union( Minkowski(ScalePath(Pattern, scale),
+    ScalePath(Path, scale), false, PathIsClosed), frNonZero);
+  Result := ScalePathsD(tmp, 1/scale);
+end;
+//------------------------------------------------------------------------------
+
+end.
+

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