Browse Source

Add 2D navigation mesh baking

Adds 2D navigation mesh baking.
smix8 1 year ago
parent
commit
0ee7e3102b
44 changed files with 10701 additions and 532 deletions
  1. 5 0
      COPYRIGHT.txt
  2. 1 0
      SConstruct
  3. 18 0
      core/SCsub
  4. 65 0
      doc/classes/NavigationMeshSourceGeometryData2D.xml
  5. 89 29
      doc/classes/NavigationPolygon.xml
  6. 19 0
      doc/classes/NavigationRegion2D.xml
  7. 30 0
      doc/classes/NavigationServer2D.xml
  8. 80 8
      editor/plugins/navigation_polygon_editor_plugin.cpp
  9. 25 1
      editor/plugins/navigation_polygon_editor_plugin.h
  10. 12 1
      main/main.cpp
  11. 1 1
      modules/navigation/config.py
  12. 8 10
      modules/navigation/godot_navigation_server.cpp
  13. 1 0
      modules/navigation/godot_navigation_server.h
  14. 369 0
      modules/navigation/godot_navigation_server_2d.cpp
  15. 234 0
      modules/navigation/godot_navigation_server_2d.h
  16. 830 0
      modules/navigation/nav_mesh_generator_2d.cpp
  17. 100 0
      modules/navigation/nav_mesh_generator_2d.h
  18. 7 0
      modules/navigation/register_types.cpp
  19. 31 4
      scene/2d/navigation_region_2d.cpp
  20. 3 0
      scene/2d/navigation_region_2d.h
  21. 2 0
      scene/register_scene_types.cpp
  22. 138 0
      scene/resources/navigation_mesh_source_geometry_data_2d.cpp
  23. 78 0
      scene/resources/navigation_mesh_source_geometry_data_2d.h
  24. 131 1
      scene/resources/navigation_polygon.cpp
  25. 50 1
      scene/resources/navigation_polygon.h
  26. 129 377
      servers/navigation_server_2d.cpp
  27. 121 97
      servers/navigation_server_2d.h
  28. 155 0
      servers/navigation_server_2d_dummy.h
  29. 1 0
      servers/navigation_server_3d.h
  30. 1 0
      servers/navigation_server_3d_dummy.h
  31. 2 2
      tests/test_main.cpp
  32. 12 0
      thirdparty/README.md
  33. 23 0
      thirdparty/clipper2/LICENSE
  34. 35 0
      thirdparty/clipper2/clipper2-exceptions.patch
  35. 846 0
      thirdparty/clipper2/include/clipper2/clipper.core.h
  36. 610 0
      thirdparty/clipper2/include/clipper2/clipper.engine.h
  37. 774 0
      thirdparty/clipper2/include/clipper2/clipper.export.h
  38. 776 0
      thirdparty/clipper2/include/clipper2/clipper.h
  39. 120 0
      thirdparty/clipper2/include/clipper2/clipper.minkowski.h
  40. 114 0
      thirdparty/clipper2/include/clipper2/clipper.offset.h
  41. 82 0
      thirdparty/clipper2/include/clipper2/clipper.rectclip.h
  42. 2979 0
      thirdparty/clipper2/src/clipper.engine.cpp
  43. 618 0
      thirdparty/clipper2/src/clipper.offset.cpp
  44. 976 0
      thirdparty/clipper2/src/clipper.rectclip.cpp

+ 5 - 0
COPYRIGHT.txt

@@ -171,6 +171,11 @@ Comment: CA certificates
 Copyright: Mozilla Contributors
 License: MPL-2.0
 
+Files: ./thirdparty/clipper2/
+Comment: Clipper2
+Copyright: 2010-2013, Angus Johnson
+License: BSL-1.0
+
 Files: ./thirdparty/cvtt/
 Comment: Convection Texture Tools Stand-Alone Kernels
 Copyright: 2018, Eric Lasota

+ 1 - 0
SConstruct

@@ -226,6 +226,7 @@ opts.Add("scu_limit", "Max includes per SCU file when using scu_build (determine
 # Thirdparty libraries
 opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True))
 opts.Add(BoolVariable("builtin_certs", "Use the built-in SSL certificates bundles", True))
+opts.Add(BoolVariable("builtin_clipper2", "Use the built-in Clipper2 library", True))
 opts.Add(BoolVariable("builtin_embree", "Use the built-in Embree library", True))
 opts.Add(BoolVariable("builtin_enet", "Use the built-in ENet library", True))
 opts.Add(BoolVariable("builtin_freetype", "Use the built-in FreeType library", True))

+ 18 - 0
core/SCsub

@@ -89,6 +89,24 @@ if env["brotli"] and env["builtin_brotli"]:
 
     env_thirdparty.add_source_files(thirdparty_obj, thirdparty_brotli_sources)
 
+# Clipper2 Thirdparty source files used for polygon and polyline boolean operations.
+if env["builtin_clipper2"]:
+    thirdparty_clipper_dir = "#thirdparty/clipper2/"
+    thirdparty_clipper_sources = [
+        "src/clipper.engine.cpp",
+        "src/clipper.offset.cpp",
+        "src/clipper.rectclip.cpp",
+    ]
+    thirdparty_clipper_sources = [thirdparty_clipper_dir + file for file in thirdparty_clipper_sources]
+
+    env_thirdparty.Prepend(CPPPATH=[thirdparty_clipper_dir + "include"])
+    env.Prepend(CPPPATH=[thirdparty_clipper_dir + "include"])
+
+    env_thirdparty.Append(CPPDEFINES=["CLIPPER2_ENABLED"])
+    env.Append(CPPDEFINES=["CLIPPER2_ENABLED"])
+
+    env_thirdparty.add_source_files(thirdparty_obj, thirdparty_clipper_sources)
+
 # Zlib library, can be unbundled
 if env["builtin_zlib"]:
     thirdparty_zlib_dir = "#thirdparty/zlib/"

+ 65 - 0
doc/classes/NavigationMeshSourceGeometryData2D.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="NavigationMeshSourceGeometryData2D" inherits="Resource" is_experimental="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+	<brief_description>
+		Container for parsed source geometry data used in navigation mesh baking.
+	</brief_description>
+	<description>
+		Container for parsed source geometry data used in navigation mesh baking.
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="add_obstruction_outline">
+			<return type="void" />
+			<param index="0" name="shape_outline" type="PackedVector2Array" />
+			<description>
+				Adds the outline points of a shape as obstructed area.
+			</description>
+		</method>
+		<method name="add_traversable_outline">
+			<return type="void" />
+			<param index="0" name="shape_outline" type="PackedVector2Array" />
+			<description>
+				Adds the outline points of a shape as traversable area.
+			</description>
+		</method>
+		<method name="clear">
+			<return type="void" />
+			<description>
+				Clears the internal data.
+			</description>
+		</method>
+		<method name="get_obstruction_outlines" qualifiers="const">
+			<return type="PackedVector2Array[]" />
+			<description>
+				Returns all the obstructed area outlines arrays.
+			</description>
+		</method>
+		<method name="get_traversable_outlines" qualifiers="const">
+			<return type="PackedVector2Array[]" />
+			<description>
+				Returns all the traversable area outlines arrays.
+			</description>
+		</method>
+		<method name="has_data">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] when parsed source geometry data exists.
+			</description>
+		</method>
+		<method name="set_obstruction_outlines">
+			<return type="void" />
+			<param index="0" name="obstruction_outlines" type="PackedVector2Array[]" />
+			<description>
+				Sets all the obstructed area outlines arrays.
+			</description>
+		</method>
+		<method name="set_traversable_outlines">
+			<return type="void" />
+			<param index="0" name="traversable_outlines" type="PackedVector2Array[]" />
+			<description>
+				Sets all the traversable area outlines arrays.
+			</description>
+		</method>
+	</methods>
+</class>

+ 89 - 29
doc/classes/NavigationPolygon.xml

@@ -1,44 +1,44 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <class name="NavigationPolygon" inherits="Resource" is_experimental="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
 	<brief_description>
-		A navigation polygon that defines traversable areas and obstacles.
+		A 2D navigation mesh that describes a traversable surface for pathfinding.
 	</brief_description>
 	<description>
-		There are two ways to create polygons. Either by using the [method add_outline] method, or using the [method add_polygon] method.
-		Using [method add_outline]:
+		A navigation mesh can be created either by baking it with the help of the [NavigationServer2D], or by adding vertices and convex polygon indices arrays manually.
+		To bake a navigation mesh at least one outline needs to be added that defines the outer bounds of the baked area.
 		[codeblocks]
 		[gdscript]
-		var polygon = NavigationPolygon.new()
-		var outline = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
-		polygon.add_outline(outline)
-		polygon.make_polygons_from_outlines()
-		$NavigationRegion2D.navigation_polygon = polygon
+		var new_navigation_mesh = NavigationPolygon.new()
+		var bounding_outline = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
+		new_navigation_mesh.add_outline(bounding_outline)
+		NavigationServer2D.bake_from_source_geometry_data(new_navigation_mesh, NavigationMeshSourceGeometryData2D.new());
+		$NavigationRegion2D.navigation_polygon = new_navigation_mesh
 		[/gdscript]
 		[csharp]
-		var polygon = new NavigationPolygon();
-		var outline = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
-		polygon.AddOutline(outline);
-		polygon.MakePolygonsFromOutlines();
-		GetNode&lt;NavigationRegion2D&gt;("NavigationRegion2D").NavigationPolygon = polygon;
+		var newNavigationMesh = new NavigationPolygon();
+		var boundingOutline = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
+		newNavigationMesh.AddOutline(boundingOutline);
+		NavigationServer2D.BakeFromSourceGeometryData(newNavigationMesh, new NavigationMeshSourceGeometryData2D());
+		GetNode&lt;NavigationRegion2D&gt;("NavigationRegion2D").NavigationPolygon = newNavigationMesh;
 		[/csharp]
 		[/codeblocks]
-		Using [method add_polygon] and indices of the vertices array.
+		Adding vertices and polygon indices manually.
 		[codeblocks]
 		[gdscript]
-		var polygon = NavigationPolygon.new()
-		var vertices = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
-		polygon.vertices = vertices
-		var indices = PackedInt32Array([0, 1, 2, 3])
-		polygon.add_polygon(indices)
-		$NavigationRegion2D.navigation_polygon = polygon
+		var new_navigation_mesh = NavigationPolygon.new()
+		var new_vertices = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
+		new_navigation_mesh.vertices = new_vertices
+		var new_polygon_indices = PackedInt32Array([0, 1, 2, 3])
+		new_navigation_mesh.add_polygon(new_polygon_indices)
+		$NavigationRegion2D.navigation_polygon = new_navigation_mesh
 		[/gdscript]
 		[csharp]
-		var polygon = new NavigationPolygon();
-		var vertices = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
-		polygon.Vertices = vertices;
-		var indices = new int[] { 0, 1, 2, 3 };
-		polygon.AddPolygon(indices);
-		GetNode&lt;NavigationRegion2D&gt;("NavigationRegion2D").NavigationPolygon = polygon;
+		var newNavigationMesh = new NavigationPolygon();
+		var newVertices = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
+		newNavigationMesh.Vertices = newVertices;
+		var newPolygonIndices = new int[] { 0, 1, 2, 3 };
+		newNavigationMesh.AddPolygon(newPolygonIndices);
+		GetNode&lt;NavigationRegion2D&gt;("NavigationRegion2D").NavigationPolygon = newNavigationMesh;
 		[/csharp]
 		[/codeblocks]
 	</description>
@@ -51,7 +51,7 @@
 			<return type="void" />
 			<param index="0" name="outline" type="PackedVector2Array" />
 			<description>
-				Appends a [PackedVector2Array] that contains the vertices of an outline to the internal array that contains all the outlines. You have to call [method make_polygons_from_outlines] in order for this array to be converted to polygons that the engine will use.
+				Appends a [PackedVector2Array] that contains the vertices of an outline to the internal array that contains all the outlines.
 			</description>
 		</method>
 		<method name="add_outline_at_index">
@@ -59,7 +59,7 @@
 			<param index="0" name="outline" type="PackedVector2Array" />
 			<param index="1" name="index" type="int" />
 			<description>
-				Adds a [PackedVector2Array] that contains the vertices of an outline to the internal array that contains all the outlines at a fixed position. You have to call [method make_polygons_from_outlines] in order for this array to be converted to polygons that the engine will use.
+				Adds a [PackedVector2Array] that contains the vertices of an outline to the internal array that contains all the outlines at a fixed position.
 			</description>
 		</method>
 		<method name="add_polygon">
@@ -106,6 +106,13 @@
 				Returns the number of outlines that were created in the editor or by script.
 			</description>
 		</method>
+		<method name="get_parsed_collision_mask_value" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="layer_number" type="int" />
+			<description>
+				Returns whether or not the specified layer of the [member parsed_collision_mask] is enabled, given a [param layer_number] between 1 and 32.
+			</description>
+		</method>
 		<method name="get_polygon">
 			<return type="PackedInt32Array" />
 			<param index="0" name="idx" type="int" />
@@ -125,10 +132,11 @@
 				Returns a [PackedVector2Array] containing all the vertices being used to create the polygons.
 			</description>
 		</method>
-		<method name="make_polygons_from_outlines">
+		<method name="make_polygons_from_outlines" is_deprecated="true">
 			<return type="void" />
 			<description>
 				Creates polygons from the outlines added in the editor or by script.
+				[i]Deprecated.[/i] This function is deprecated, and might be removed in a future release. Use [method NavigationServer2D.parse_source_geometry_data] and [method NavigationServer2D.bake_from_source_geometry_data] instead.
 			</description>
 		</method>
 		<method name="remove_outline">
@@ -146,6 +154,14 @@
 				Changes an outline created in the editor or by script. You have to call [method make_polygons_from_outlines] for the polygons to update.
 			</description>
 		</method>
+		<method name="set_parsed_collision_mask_value">
+			<return type="void" />
+			<param index="0" name="layer_number" type="int" />
+			<param index="1" name="value" type="bool" />
+			<description>
+				Based on [param value], enables or disables the specified layer in the [member parsed_collision_mask], given a [param layer_number] between 1 and 32.
+			</description>
+		</method>
 		<method name="set_vertices">
 			<return type="void" />
 			<param index="0" name="vertices" type="PackedVector2Array" />
@@ -155,8 +171,52 @@
 		</method>
 	</methods>
 	<members>
+		<member name="agent_radius" type="float" setter="set_agent_radius" getter="get_agent_radius" default="10.0">
+			The distance to erode/shrink the walkable surface when baking the navigation mesh.
+		</member>
 		<member name="cell_size" type="float" setter="set_cell_size" getter="get_cell_size" default="1.0">
 			The cell size used to rasterize the navigation mesh vertices. Must match with the cell size on the navigation map.
 		</member>
+		<member name="parsed_collision_mask" type="int" setter="set_parsed_collision_mask" getter="get_parsed_collision_mask" default="4294967295">
+			The physics layers to scan for static colliders.
+			Only used when [member parsed_geometry_type] is [constant PARSED_GEOMETRY_STATIC_COLLIDERS] or [constant PARSED_GEOMETRY_BOTH].
+		</member>
+		<member name="parsed_geometry_type" type="int" setter="set_parsed_geometry_type" getter="get_parsed_geometry_type" enum="NavigationPolygon.ParsedGeometryType" default="2">
+			Determines which type of nodes will be parsed as geometry. See [enum ParsedGeometryType] for possible values.
+		</member>
+		<member name="source_geometry_group_name" type="StringName" setter="set_source_geometry_group_name" getter="get_source_geometry_group_name" default="&amp;&quot;navigation_polygon_source_geometry_group&quot;">
+			The group name of nodes that should be parsed for baking source geometry.
+			Only used when [member source_geometry_mode] is [constant SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN] or [constant SOURCE_GEOMETRY_GROUPS_EXPLICIT].
+		</member>
+		<member name="source_geometry_mode" type="int" setter="set_source_geometry_mode" getter="get_source_geometry_mode" enum="NavigationPolygon.SourceGeometryMode" default="0">
+			The source of the geometry used when baking. See [enum SourceGeometryMode] for possible values.
+		</member>
 	</members>
+	<constants>
+		<constant name="PARSED_GEOMETRY_MESH_INSTANCES" value="0" enum="ParsedGeometryType">
+			Parses mesh instances as obstruction geometry. This includes [Polygon2D], [MeshInstance2D], [MultiMeshInstance2D], and [TileMap] nodes.
+			Meshes are only parsed when they use a 2D vertices surface format.
+		</constant>
+		<constant name="PARSED_GEOMETRY_STATIC_COLLIDERS" value="1" enum="ParsedGeometryType">
+			Parses [StaticBody2D] and [TileMap] colliders as obstruction geometry. The collider should be in any of the layers specified by [member parsed_collision_mask].
+		</constant>
+		<constant name="PARSED_GEOMETRY_BOTH" value="2" enum="ParsedGeometryType">
+			Both [constant PARSED_GEOMETRY_MESH_INSTANCES] and [constant PARSED_GEOMETRY_STATIC_COLLIDERS].
+		</constant>
+		<constant name="PARSED_GEOMETRY_MAX" value="3" enum="ParsedGeometryType">
+			Represents the size of the [enum ParsedGeometryType] enum.
+		</constant>
+		<constant name="SOURCE_GEOMETRY_ROOT_NODE_CHILDREN" value="0" enum="SourceGeometryMode">
+			Scans the child nodes of the root node recursively for geometry.
+		</constant>
+		<constant name="SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN" value="1" enum="SourceGeometryMode">
+			Scans nodes in a group and their child nodes recursively for geometry. The group is specified by [member source_geometry_group_name].
+		</constant>
+		<constant name="SOURCE_GEOMETRY_GROUPS_EXPLICIT" value="2" enum="SourceGeometryMode">
+			Uses nodes in a group for geometry. The group is specified by [member source_geometry_group_name].
+		</constant>
+		<constant name="SOURCE_GEOMETRY_MAX" value="3" enum="SourceGeometryMode">
+			Represents the size of the [enum SourceGeometryMode] enum.
+		</constant>
+	</constants>
 </class>

+ 19 - 0
doc/classes/NavigationRegion2D.xml

@@ -16,6 +16,13 @@
 		<link title="Using NavigationRegions">$DOCS_URL/tutorials/navigation/navigation_using_navigationregions.html</link>
 	</tutorials>
 	<methods>
+		<method name="bake_navigation_polygon">
+			<return type="void" />
+			<param index="0" name="on_thread" type="bool" default="true" />
+			<description>
+				Bakes the [NavigationPolygon]. If [param on_thread] is set to [code]true[/code] (default), the baking is done on a separate thread.
+			</description>
+		</method>
 		<method name="get_avoidance_layer_value" qualifiers="const">
 			<return type="bool" />
 			<param index="0" name="layer_number" type="int" />
@@ -93,4 +100,16 @@
 			If enabled the navigation region will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
 		</member>
 	</members>
+	<signals>
+		<signal name="bake_finished">
+			<description>
+				Emitted when a navigation polygon bake operation is completed.
+			</description>
+		</signal>
+		<signal name="navigation_polygon_changed">
+			<description>
+				Emitted when the used navigation polygon is replaced or changes to the internals of the current navigation polygon are committed.
+			</description>
+		</signal>
+	</signals>
 </class>

+ 30 - 0
doc/classes/NavigationServer2D.xml

@@ -182,6 +182,24 @@
 				Replaces the internal velocity in the collision avoidance simulation with [param velocity] for the specified [param agent]. When an agent is teleported to a new position far away this function should be used in the same frame. If called frequently this function can get agents stuck.
 			</description>
 		</method>
+		<method name="bake_from_source_geometry_data">
+			<return type="void" />
+			<param index="0" name="navigation_polygon" type="NavigationPolygon" />
+			<param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData2D" />
+			<param index="2" name="callback" type="Callable" default="Callable()" />
+			<description>
+				Bakes the provided [param navigation_polygon] with the data from the provided [param source_geometry_data]. After the process is finished the optional [param callback] will be called.
+			</description>
+		</method>
+		<method name="bake_from_source_geometry_data_async">
+			<return type="void" />
+			<param index="0" name="navigation_polygon" type="NavigationPolygon" />
+			<param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData2D" />
+			<param index="2" name="callback" type="Callable" default="Callable()" />
+			<description>
+				Bakes the provided [param navigation_polygon] with the data from the provided [param source_geometry_data] as an async task running on a background thread. After the process is finished the optional [param callback] will be called.
+			</description>
+		</method>
 		<method name="free_rid">
 			<return type="void" />
 			<param index="0" name="rid" type="RID" />
@@ -579,6 +597,18 @@
 				Sets the outline vertices for the obstacle. If the vertices are winded in clockwise order agents will be pushed in by the obstacle, else they will be pushed out.
 			</description>
 		</method>
+		<method name="parse_source_geometry_data">
+			<return type="void" />
+			<param index="0" name="navigation_polygon" type="NavigationPolygon" />
+			<param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData2D" />
+			<param index="2" name="root_node" type="Node" />
+			<param index="3" name="callback" type="Callable" default="Callable()" />
+			<description>
+				Parses the [SceneTree] for source geometry according to the properties of [param navigation_polygon]. Updates the provided [param source_geometry_data] resource with the resulting data. The resource can then be used to bake a navigation mesh with [method bake_from_source_geometry_data]. After the process is finished the optional [param callback] will be called.
+				[b]Note:[/b] This function needs to run on the main thread or with a deferred call as the SceneTree is not thread-safe.
+				[b]Performance:[/b] While convenient, reading data arrays from [Mesh] resources can affect the frame rate negatively. The data needs to be received from the GPU, stalling the [RenderingServer] in the process. For performance prefer the use of e.g. collision shapes or creating the data arrays entirely in code.
+			</description>
+		</method>
 		<method name="query_path" qualifiers="const">
 			<return type="void" />
 			<param index="0" name="parameters" type="NavigationPathQueryParameters2D" />

+ 80 - 8
editor/plugins/navigation_polygon_editor_plugin.cpp

@@ -32,6 +32,8 @@
 
 #include "editor/editor_node.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "scene/2d/navigation_region_2d.h"
+#include "scene/gui/dialogs.h"
 
 Ref<NavigationPolygon> NavigationPolygonEditor::_ensure_navpoly() const {
 	Ref<NavigationPolygon> navpoly = node->get_navigation_polygon();
@@ -71,7 +73,6 @@ Variant NavigationPolygonEditor::_get_polygon(int p_idx) const {
 void NavigationPolygonEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
 	Ref<NavigationPolygon> navpoly = _ensure_navpoly();
 	navpoly->set_outline(p_idx, p_polygon);
-	navpoly->make_polygons_from_outlines();
 }
 
 void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) {
@@ -79,8 +80,6 @@ void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) {
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	undo_redo->add_do_method(navpoly.ptr(), "add_outline", p_polygon);
 	undo_redo->add_undo_method(navpoly.ptr(), "remove_outline", navpoly->get_outline_count());
-	undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines");
-	undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines");
 }
 
 void NavigationPolygonEditor::_action_remove_polygon(int p_idx) {
@@ -88,8 +87,6 @@ void NavigationPolygonEditor::_action_remove_polygon(int p_idx) {
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	undo_redo->add_do_method(navpoly.ptr(), "remove_outline", p_idx);
 	undo_redo->add_undo_method(navpoly.ptr(), "add_outline_at_index", navpoly->get_outline(p_idx), p_idx);
-	undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines");
-	undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines");
 }
 
 void NavigationPolygonEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
@@ -97,8 +94,6 @@ void NavigationPolygonEditor::_action_set_polygon(int p_idx, const Variant &p_pr
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	undo_redo->add_do_method(navpoly.ptr(), "set_outline", p_idx, p_polygon);
 	undo_redo->add_undo_method(navpoly.ptr(), "set_outline", p_idx, p_previous);
-	undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines");
-	undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines");
 }
 
 bool NavigationPolygonEditor::_has_resource() const {
@@ -119,7 +114,84 @@ void NavigationPolygonEditor::_create_resource() {
 	_menu_option(MODE_CREATE);
 }
 
-NavigationPolygonEditor::NavigationPolygonEditor() {}
+NavigationPolygonEditor::NavigationPolygonEditor() {
+	bake_hbox = memnew(HBoxContainer);
+	add_child(bake_hbox);
+
+	button_bake = memnew(Button);
+	button_bake->set_flat(true);
+	bake_hbox->add_child(button_bake);
+	button_bake->set_toggle_mode(true);
+	button_bake->set_text(TTR("Bake NavigationPolygon"));
+	button_bake->set_tooltip_text(TTR("Bakes the NavigationPolygon by first parsing the scene for source geometry and then creating the navigation polygon vertices and polygons."));
+	button_bake->connect("pressed", callable_mp(this, &NavigationPolygonEditor::_bake_pressed));
+
+	button_reset = memnew(Button);
+	button_reset->set_flat(true);
+	bake_hbox->add_child(button_reset);
+	button_reset->set_text(TTR("Clear NavigationPolygon"));
+	button_reset->set_tooltip_text(TTR("Clears the internal NavigationPolygon outlines, vertices and polygons."));
+	button_reset->connect("pressed", callable_mp(this, &NavigationPolygonEditor::_clear_pressed));
+
+	bake_info = memnew(Label);
+	bake_hbox->add_child(bake_info);
+
+	err_dialog = memnew(AcceptDialog);
+	add_child(err_dialog);
+	node = nullptr;
+}
+
+void NavigationPolygonEditor::_notification(int p_what) {
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE: {
+			button_bake->set_icon(get_theme_icon(SNAME("Bake"), SNAME("EditorIcons")));
+			button_reset->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")));
+		} break;
+	}
+}
+
+void NavigationPolygonEditor::_bake_pressed() {
+	button_bake->set_pressed(false);
+
+	ERR_FAIL_NULL(node);
+	Ref<NavigationPolygon> navigation_polygon = node->get_navigation_polygon();
+	if (!navigation_polygon.is_valid()) {
+		err_dialog->set_text(TTR("A NavigationPolygon resource must be set or created for this node to work."));
+		err_dialog->popup_centered();
+		return;
+	}
+
+	node->bake_navigation_polygon(true);
+
+	node->queue_redraw();
+}
+
+void NavigationPolygonEditor::_clear_pressed() {
+	if (node) {
+		if (node->get_navigation_polygon().is_valid()) {
+			node->get_navigation_polygon()->clear();
+		}
+	}
+
+	button_bake->set_pressed(false);
+	bake_info->set_text("");
+
+	if (node) {
+		node->queue_redraw();
+	}
+}
+
+void NavigationPolygonEditor::_update_polygon_editing_state() {
+	if (!_get_node()) {
+		return;
+	}
+
+	if (node != nullptr && node->get_navigation_polygon().is_valid()) {
+		bake_hbox->show();
+	} else {
+		bake_hbox->hide();
+	}
+}
 
 NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin() :
 		AbstractPolygon2DEditorPlugin(memnew(NavigationPolygonEditor), "NavigationRegion2D") {

+ 25 - 1
editor/plugins/navigation_polygon_editor_plugin.h

@@ -32,16 +32,38 @@
 #define NAVIGATION_POLYGON_EDITOR_PLUGIN_H
 
 #include "editor/plugins/abstract_polygon_2d_editor.h"
-#include "scene/2d/navigation_region_2d.h"
+
+#include "editor/editor_plugin.h"
+
+class AcceptDialog;
+class HBoxContainer;
+class NavigationPolygon;
+class NavigationRegion2D;
 
 class NavigationPolygonEditor : public AbstractPolygon2DEditor {
+	friend class NavigationPolygonEditorPlugin;
+
 	GDCLASS(NavigationPolygonEditor, AbstractPolygon2DEditor);
 
 	NavigationRegion2D *node = nullptr;
 
 	Ref<NavigationPolygon> _ensure_navpoly() const;
 
+	AcceptDialog *err_dialog = nullptr;
+
+	HBoxContainer *bake_hbox = nullptr;
+	Button *button_bake = nullptr;
+	Button *button_reset = nullptr;
+	Label *bake_info = nullptr;
+
+	void _bake_pressed();
+	void _clear_pressed();
+
+	void _update_polygon_editing_state();
+
 protected:
+	void _notification(int p_what);
+
 	virtual Node2D *_get_node() const override;
 	virtual void _set_node(Node *p_polygon) override;
 
@@ -63,6 +85,8 @@ public:
 class NavigationPolygonEditorPlugin : public AbstractPolygon2DEditorPlugin {
 	GDCLASS(NavigationPolygonEditorPlugin, AbstractPolygon2DEditorPlugin);
 
+	NavigationPolygonEditor *navigation_polygon_editor = nullptr;
+
 public:
 	NavigationPolygonEditorPlugin();
 };

+ 12 - 1
main/main.cpp

@@ -70,6 +70,7 @@
 #include "servers/movie_writer/movie_writer.h"
 #include "servers/movie_writer/movie_writer_mjpeg.h"
 #include "servers/navigation_server_2d.h"
+#include "servers/navigation_server_2d_dummy.h"
 #include "servers/navigation_server_3d.h"
 #include "servers/navigation_server_3d_dummy.h"
 #include "servers/physics_server_2d.h"
@@ -339,8 +340,14 @@ void initialize_navigation_server() {
 	navigation_server_3d->init();
 
 	// Init 2D Navigation Server
-	navigation_server_2d = memnew(NavigationServer2D);
+	navigation_server_2d = NavigationServer2DManager::new_default_server();
+	if (!navigation_server_2d) {
+		WARN_PRINT_ONCE("No NavigationServer2D implementation has been registered! Falling back to a dummy implementation: navigation features will be unavailable.");
+		navigation_server_2d = memnew(NavigationServer2DDummy);
+	}
+
 	ERR_FAIL_NULL_MSG(navigation_server_2d, "Failed to initialize NavigationServer2D.");
+	navigation_server_2d->init();
 }
 
 void finalize_navigation_server() {
@@ -350,6 +357,7 @@ void finalize_navigation_server() {
 	navigation_server_3d = nullptr;
 
 	ERR_FAIL_NULL(navigation_server_2d);
+	navigation_server_2d->finish();
 	memdelete(navigation_server_2d);
 	navigation_server_2d = nullptr;
 }
@@ -3490,6 +3498,9 @@ bool Main::iteration() {
 	// process all our active interfaces
 	XRServer::get_singleton()->_process();
 
+	NavigationServer2D::get_singleton()->sync();
+	NavigationServer3D::get_singleton()->sync();
+
 	for (int iters = 0; iters < advance.physics_steps; ++iters) {
 		if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) {
 			Input::get_singleton()->flush_buffered_events();

+ 1 - 1
modules/navigation/config.py

@@ -1,5 +1,5 @@
 def can_build(env, platform):
-    return True
+    return not env["disable_3d"]
 
 
 def configure(env):

+ 8 - 10
modules/navigation/godot_navigation_server.cpp

@@ -1086,6 +1086,14 @@ void GodotNavigationServer::map_force_update(RID p_map) {
 	map->sync();
 }
 
+void GodotNavigationServer::sync() {
+#ifndef _3D_DISABLED
+	if (navmesh_generator_3d) {
+		navmesh_generator_3d->sync();
+	}
+#endif // _3D_DISABLED
+}
+
 void GodotNavigationServer::process(real_t p_delta_time) {
 	flush_queries();
 
@@ -1093,16 +1101,6 @@ void GodotNavigationServer::process(real_t p_delta_time) {
 		return;
 	}
 
-#ifndef _3D_DISABLED
-	// Sync finished navmesh bakes before doing NavMap updates.
-	if (navmesh_generator_3d) {
-		navmesh_generator_3d->sync();
-		// Finished bakes emit callbacks and users might have reacted to those.
-		// Flush queue again so users do not have to wait for the next sync.
-		flush_queries();
-	}
-#endif // _3D_DISABLED
-
 	int _new_pm_region_count = 0;
 	int _new_pm_agent_count = 0;
 	int _new_pm_link_count = 0;

+ 1 - 0
modules/navigation/godot_navigation_server.h

@@ -243,6 +243,7 @@ public:
 	void flush_queries();
 	virtual void process(real_t p_delta_time) override;
 	virtual void init() override;
+	virtual void sync() override;
 	virtual void finish() override;
 
 	virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override;

+ 369 - 0
modules/navigation/godot_navigation_server_2d.cpp

@@ -0,0 +1,369 @@
+/**************************************************************************/
+/*  godot_navigation_server_2d.cpp                                        */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "godot_navigation_server_2d.h"
+
+#ifdef CLIPPER2_ENABLED
+#include "nav_mesh_generator_2d.h"
+#endif // CLIPPER2_ENABLED
+
+#include "servers/navigation_server_3d.h"
+
+#define FORWARD_0(FUNC_NAME)                                     \
+	GodotNavigationServer2D::FUNC_NAME() {                       \
+		return NavigationServer3D::get_singleton()->FUNC_NAME(); \
+	}
+
+#define FORWARD_0_C(FUNC_NAME)                                   \
+	GodotNavigationServer2D::FUNC_NAME()                         \
+			const {                                              \
+		return NavigationServer3D::get_singleton()->FUNC_NAME(); \
+	}
+
+#define FORWARD_1(FUNC_NAME, T_0, D_0, CONV_0)                              \
+	GodotNavigationServer2D::FUNC_NAME(T_0 D_0) {                           \
+		return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \
+	}
+
+#define FORWARD_1_C(FUNC_NAME, T_0, D_0, CONV_0)                            \
+	GodotNavigationServer2D::FUNC_NAME(T_0 D_0)                             \
+			const {                                                         \
+		return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \
+	}
+
+#define FORWARD_1_R_C(CONV_R, FUNC_NAME, T_0, D_0, CONV_0)                          \
+	GodotNavigationServer2D::FUNC_NAME(T_0 D_0)                                     \
+			const {                                                                 \
+		return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0))); \
+	}
+
+#define FORWARD_2(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1)                         \
+	GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) {                               \
+		return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \
+	}
+
+#define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1)                       \
+	GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1)                                 \
+			const {                                                                      \
+		return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \
+	}
+
+#define FORWARD_2_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1)                     \
+	GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1)                                         \
+			const {                                                                              \
+		return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \
+	}
+
+#define FORWARD_5_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4)      \
+	GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4)                                                     \
+			const {                                                                                                                     \
+		return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \
+	}
+
+static RID rid_to_rid(const RID d) {
+	return d;
+}
+
+static bool bool_to_bool(const bool d) {
+	return d;
+}
+
+static int int_to_int(const int d) {
+	return d;
+}
+
+static uint32_t uint32_to_uint32(const uint32_t d) {
+	return d;
+}
+
+static real_t real_to_real(const real_t d) {
+	return d;
+}
+
+static Vector3 v2_to_v3(const Vector2 d) {
+	return Vector3(d.x, 0.0, d.y);
+}
+
+static Vector2 v3_to_v2(const Vector3 &d) {
+	return Vector2(d.x, d.z);
+}
+
+static Vector<Vector3> vector_v2_to_v3(const Vector<Vector2> &d) {
+	Vector<Vector3> nd;
+	nd.resize(d.size());
+	for (int i(0); i < nd.size(); i++) {
+		nd.write[i] = v2_to_v3(d[i]);
+	}
+	return nd;
+}
+
+static Vector<Vector2> vector_v3_to_v2(const Vector<Vector3> &d) {
+	Vector<Vector2> nd;
+	nd.resize(d.size());
+	for (int i(0); i < nd.size(); i++) {
+		nd.write[i] = v3_to_v2(d[i]);
+	}
+	return nd;
+}
+
+static Transform3D trf2_to_trf3(const Transform2D &d) {
+	Vector3 o(v2_to_v3(d.get_origin()));
+	Basis b;
+	b.rotate(Vector3(0, -1, 0), d.get_rotation());
+	b.scale(v2_to_v3(d.get_scale()));
+	return Transform3D(b, o);
+}
+
+static ObjectID id_to_id(const ObjectID &id) {
+	return id;
+}
+
+static Callable callable_to_callable(const Callable &c) {
+	return c;
+}
+
+static Ref<NavigationMesh> poly_to_mesh(Ref<NavigationPolygon> d) {
+	if (d.is_valid()) {
+		return d->get_navigation_mesh();
+	} else {
+		return Ref<NavigationMesh>();
+	}
+}
+
+void GodotNavigationServer2D::init() {
+#ifdef CLIPPER2_ENABLED
+	navmesh_generator_2d = memnew(NavMeshGenerator2D);
+	ERR_FAIL_NULL_MSG(navmesh_generator_2d, "Failed to init NavMeshGenerator2D.");
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::sync() {
+#ifdef CLIPPER2_ENABLED
+	if (navmesh_generator_2d) {
+		navmesh_generator_2d->sync();
+	}
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::finish() {
+#ifdef CLIPPER2_ENABLED
+	if (navmesh_generator_2d) {
+		navmesh_generator_2d->finish();
+		memdelete(navmesh_generator_2d);
+		navmesh_generator_2d = nullptr;
+	}
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+	ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
+	ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon.");
+	ERR_FAIL_NULL_MSG(p_root_node, "No parsing root node specified.");
+	ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree.");
+
+#ifdef CLIPPER2_ENABLED
+	ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton());
+	NavMeshGenerator2D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback);
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback) {
+	ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon.");
+	ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D.");
+
+#ifdef CLIPPER2_ENABLED
+	ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton());
+	NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // CLIPPER2_ENABLED
+}
+
+void GodotNavigationServer2D::bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback) {
+	ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+	ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D.");
+
+#ifdef CLIPPER2_ENABLED
+	ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton());
+	NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // CLIPPER2_ENABLED
+}
+
+GodotNavigationServer2D::GodotNavigationServer2D() {}
+
+GodotNavigationServer2D::~GodotNavigationServer2D() {}
+
+TypedArray<RID> FORWARD_0_C(get_maps);
+
+TypedArray<RID> FORWARD_1_C(map_get_links, RID, p_map, rid_to_rid);
+
+TypedArray<RID> FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid);
+
+TypedArray<RID> FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid);
+
+TypedArray<RID> FORWARD_1_C(map_get_obstacles, RID, p_map, rid_to_rid);
+
+RID FORWARD_1_C(region_get_map, RID, p_region, rid_to_rid);
+
+RID FORWARD_1_C(agent_get_map, RID, p_agent, rid_to_rid);
+
+RID FORWARD_0(map_create);
+
+void FORWARD_2(map_set_active, RID, p_map, bool, p_active, rid_to_rid, bool_to_bool);
+
+bool FORWARD_1_C(map_is_active, RID, p_map, rid_to_rid);
+
+void GodotNavigationServer2D::map_force_update(RID p_map) {
+	NavigationServer3D::get_singleton()->map_force_update(p_map);
+}
+
+void FORWARD_2(map_set_cell_size, RID, p_map, real_t, p_cell_size, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(map_get_cell_size, RID, p_map, rid_to_rid);
+
+void FORWARD_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(map_get_use_edge_connections, RID, p_map, rid_to_rid);
+
+void FORWARD_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(map_get_edge_connection_margin, RID, p_map, rid_to_rid);
+
+void FORWARD_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(map_get_link_connection_radius, RID, p_map, rid_to_rid);
+
+Vector<Vector2> FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, uint32_t, p_layers, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool, uint32_to_uint32);
+
+Vector2 FORWARD_2_R_C(v3_to_v2, map_get_closest_point, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
+RID FORWARD_2_C(map_get_closest_point_owner, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
+
+RID FORWARD_0(region_create);
+
+void FORWARD_2(region_set_enabled, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(region_get_enabled, RID, p_region, rid_to_rid);
+void FORWARD_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(region_get_use_edge_connections, RID, p_region, rid_to_rid);
+
+void FORWARD_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(region_get_enter_cost, RID, p_region, rid_to_rid);
+void FORWARD_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(region_get_travel_cost, RID, p_region, rid_to_rid);
+void FORWARD_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id, rid_to_rid, id_to_id);
+ObjectID FORWARD_1_C(region_get_owner_id, RID, p_region, rid_to_rid);
+bool FORWARD_2_C(region_owns_point, RID, p_region, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
+
+void FORWARD_2(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid);
+void FORWARD_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32);
+uint32_t FORWARD_1_C(region_get_navigation_layers, RID, p_region, rid_to_rid);
+void FORWARD_2(region_set_transform, RID, p_region, Transform2D, p_transform, rid_to_rid, trf2_to_trf3);
+
+void GodotNavigationServer2D::region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) {
+	NavigationServer3D::get_singleton()->region_set_navigation_mesh(p_region, poly_to_mesh(p_navigation_polygon));
+}
+
+int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid);
+Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
+Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
+
+RID FORWARD_0(link_create);
+
+void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid);
+RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_enabled, RID, p_link, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(link_get_enabled, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32);
+uint32_t FORWARD_1_C(link_get_navigation_layers, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_start_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3);
+Vector2 FORWARD_1_R_C(v3_to_v2, link_get_start_position, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_end_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3);
+Vector2 FORWARD_1_R_C(v3_to_v2, link_get_end_position, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(link_get_enter_cost, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost, rid_to_rid, real_to_real);
+real_t FORWARD_1_C(link_get_travel_cost, RID, p_link, rid_to_rid);
+void FORWARD_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id, rid_to_rid, id_to_id);
+ObjectID FORWARD_1_C(link_get_owner_id, RID, p_link, rid_to_rid);
+
+RID GodotNavigationServer2D::agent_create() {
+	RID agent = NavigationServer3D::get_singleton()->agent_create();
+	return agent;
+}
+
+void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(agent_get_avoidance_enabled, RID, p_agent, rid_to_rid);
+void FORWARD_2(agent_set_map, RID, p_agent, RID, p_map, rid_to_rid, rid_to_rid);
+void FORWARD_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_dist, rid_to_rid, real_to_real);
+void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int);
+void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real);
+void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real);
+void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real);
+void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real);
+void FORWARD_2(agent_set_velocity_forced, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3);
+void FORWARD_2(agent_set_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3);
+void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3);
+
+bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid);
+void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid);
+
+void FORWARD_1(free, RID, p_object, rid_to_rid);
+void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable);
+
+void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32);
+void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32);
+void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real);
+
+RID GodotNavigationServer2D::obstacle_create() {
+	RID obstacle = NavigationServer3D::get_singleton()->obstacle_create();
+	return obstacle;
+}
+void FORWARD_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(obstacle_get_avoidance_enabled, RID, p_obstacle, rid_to_rid);
+void FORWARD_2(obstacle_set_map, RID, p_obstacle, RID, p_map, rid_to_rid, rid_to_rid);
+RID FORWARD_1_C(obstacle_get_map, RID, p_obstacle, rid_to_rid);
+void FORWARD_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused, rid_to_rid, bool_to_bool);
+bool FORWARD_1_C(obstacle_get_paused, RID, p_obstacle, rid_to_rid);
+void FORWARD_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius, rid_to_rid, real_to_real);
+void FORWARD_2(obstacle_set_velocity, RID, p_obstacle, Vector2, p_velocity, rid_to_rid, v2_to_v3);
+void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3);
+void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32);
+
+void GodotNavigationServer2D::obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) {
+	NavigationServer3D::get_singleton()->obstacle_set_vertices(p_obstacle, vector_v2_to_v3(p_vertices));
+}
+
+void GodotNavigationServer2D::query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const {
+	ERR_FAIL_COND(!p_query_parameters.is_valid());
+	ERR_FAIL_COND(!p_query_result.is_valid());
+
+	const NavigationUtilities::PathQueryResult _query_result = NavigationServer3D::get_singleton()->_query_path(p_query_parameters->get_parameters());
+
+	p_query_result->set_path(vector_v3_to_v2(_query_result.path));
+	p_query_result->set_path_types(_query_result.path_types);
+	p_query_result->set_path_rids(_query_result.path_rids);
+	p_query_result->set_path_owner_ids(_query_result.path_owner_ids);
+}

+ 234 - 0
modules/navigation/godot_navigation_server_2d.h

@@ -0,0 +1,234 @@
+/**************************************************************************/
+/*  godot_navigation_server_2d.h                                          */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef GODOT_NAVIGATION_SERVER_2D_H
+#define GODOT_NAVIGATION_SERVER_2D_H
+
+#include "nav_agent.h"
+#include "nav_link.h"
+#include "nav_map.h"
+#include "nav_obstacle.h"
+#include "nav_region.h"
+
+#include "servers/navigation_server_2d.h"
+
+#ifdef CLIPPER2_ENABLED
+class NavMeshGenerator2D;
+#endif // CLIPPER2_ENABLED
+
+// This server exposes the `NavigationServer3D` features in the 2D world.
+class GodotNavigationServer2D : public NavigationServer2D {
+	GDCLASS(GodotNavigationServer2D, NavigationServer2D);
+
+#ifdef CLIPPER2_ENABLED
+	NavMeshGenerator2D *navmesh_generator_2d = nullptr;
+#endif // CLIPPER2_ENABLED
+
+public:
+	GodotNavigationServer2D();
+	virtual ~GodotNavigationServer2D();
+
+	virtual TypedArray<RID> get_maps() const override;
+
+	virtual RID map_create() override;
+	virtual void map_set_active(RID p_map, bool p_active) override;
+	virtual bool map_is_active(RID p_map) const override;
+	virtual void map_set_cell_size(RID p_map, real_t p_cell_size) override;
+	virtual real_t map_get_cell_size(RID p_map) const override;
+	virtual void map_set_use_edge_connections(RID p_map, bool p_enabled) override;
+	virtual bool map_get_use_edge_connections(RID p_map) const override;
+	virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) override;
+	virtual real_t map_get_edge_connection_margin(RID p_map) const override;
+	virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) override;
+	virtual real_t map_get_link_connection_radius(RID p_map) const override;
+	virtual Vector<Vector2> map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const override;
+	virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const override;
+	virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const override;
+	virtual TypedArray<RID> map_get_links(RID p_map) const override;
+	virtual TypedArray<RID> map_get_regions(RID p_map) const override;
+	virtual TypedArray<RID> map_get_agents(RID p_map) const override;
+	virtual TypedArray<RID> map_get_obstacles(RID p_map) const override;
+	virtual void map_force_update(RID p_map) override;
+
+	virtual RID region_create() override;
+	virtual void region_set_enabled(RID p_region, bool p_enabled) override;
+	virtual bool region_get_enabled(RID p_region) const override;
+	virtual void region_set_use_edge_connections(RID p_region, bool p_enabled) override;
+	virtual bool region_get_use_edge_connections(RID p_region) const override;
+	virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) override;
+	virtual real_t region_get_enter_cost(RID p_region) const override;
+	virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) override;
+	virtual real_t region_get_travel_cost(RID p_region) const override;
+	virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id) override;
+	virtual ObjectID region_get_owner_id(RID p_region) const override;
+	virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const override;
+	virtual void region_set_map(RID p_region, RID p_map) override;
+	virtual RID region_get_map(RID p_region) const override;
+	virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) override;
+	virtual uint32_t region_get_navigation_layers(RID p_region) const override;
+	virtual void region_set_transform(RID p_region, Transform2D p_transform) override;
+	virtual void region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) override;
+	virtual int region_get_connections_count(RID p_region) const override;
+	virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override;
+	virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
+
+	virtual RID link_create() override;
+
+	/// Set the map of this link.
+	virtual void link_set_map(RID p_link, RID p_map) override;
+	virtual RID link_get_map(RID p_link) const override;
+
+	virtual void link_set_enabled(RID p_link, bool p_enabled) override;
+	virtual bool link_get_enabled(RID p_link) const override;
+
+	/// Set whether this link travels in both directions.
+	virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) override;
+	virtual bool link_is_bidirectional(RID p_link) const override;
+
+	/// Set the link's layers.
+	virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) override;
+	virtual uint32_t link_get_navigation_layers(RID p_link) const override;
+
+	/// Set the start position of the link.
+	virtual void link_set_start_position(RID p_link, Vector2 p_position) override;
+	virtual Vector2 link_get_start_position(RID p_link) const override;
+
+	/// Set the end position of the link.
+	virtual void link_set_end_position(RID p_link, Vector2 p_position) override;
+	virtual Vector2 link_get_end_position(RID p_link) const override;
+
+	/// Set the enter cost of the link.
+	virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) override;
+	virtual real_t link_get_enter_cost(RID p_link) const override;
+
+	/// Set the travel cost of the link.
+	virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) override;
+	virtual real_t link_get_travel_cost(RID p_link) const override;
+
+	/// Set the node which manages this link.
+	virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id) override;
+	virtual ObjectID link_get_owner_id(RID p_link) const override;
+
+	/// Creates the agent.
+	virtual RID agent_create() override;
+
+	/// Put the agent in the map.
+	virtual void agent_set_map(RID p_agent, RID p_map) override;
+	virtual RID agent_get_map(RID p_agent) const override;
+
+	virtual void agent_set_paused(RID p_agent, bool p_paused) override;
+	virtual bool agent_get_paused(RID p_agent) const override;
+
+	virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) override;
+	virtual bool agent_get_avoidance_enabled(RID p_agent) const override;
+
+	/// The maximum distance (center point to
+	/// center point) to other agents this agent
+	/// takes into account in the navigation. The
+	/// larger this number, the longer the running
+	/// time of the simulation. If the number is too
+	/// low, the simulation will not be safe.
+	/// Must be non-negative.
+	virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance) override;
+
+	/// The maximum number of other agents this
+	/// agent takes into account in the navigation.
+	/// The larger this number, the longer the
+	/// running time of the simulation. If the
+	/// number is too low, the simulation will not
+	/// be safe.
+	virtual void agent_set_max_neighbors(RID p_agent, int p_count) override;
+
+	/// The minimal amount of time for which this
+	/// agent's velocities that are computed by the
+	/// simulation are safe with respect to other
+	/// agents. The larger this number, the sooner
+	/// this agent will respond to the presence of
+	/// other agents, but the less freedom this
+	/// agent has in choosing its velocities.
+	/// Must be positive.
+
+	virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) override;
+	virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override;
+
+	/// The radius of this agent.
+	/// Must be non-negative.
+	virtual void agent_set_radius(RID p_agent, real_t p_radius) override;
+
+	/// The maximum speed of this agent.
+	/// Must be non-negative.
+	virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed) override;
+
+	/// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly
+	virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) override;
+
+	/// The wanted velocity for the agent as a "suggestion" to the avoidance simulation.
+	/// The simulation will try to fulfill this velocity wish if possible but may change the velocity depending on other agent's and obstacles'.
+	virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity) override;
+
+	/// Position of the agent in world space.
+	virtual void agent_set_position(RID p_agent, Vector2 p_position) override;
+
+	/// Returns true if the map got changed the previous frame.
+	virtual bool agent_is_map_changed(RID p_agent) const override;
+
+	/// Callback called at the end of the RVO process
+	virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback) override;
+
+	virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override;
+	virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override;
+	virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override;
+
+	virtual RID obstacle_create() override;
+	virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) override;
+	virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const override;
+	virtual void obstacle_set_map(RID p_obstacle, RID p_map) override;
+	virtual RID obstacle_get_map(RID p_obstacle) const override;
+	virtual void obstacle_set_paused(RID p_obstacle, bool p_paused) override;
+	virtual bool obstacle_get_paused(RID p_obstacle) const override;
+	virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius) override;
+	virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) override;
+	virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position) override;
+	virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) override;
+	virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override;
+
+	virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const override;
+
+	virtual void init() override;
+	virtual void sync() override;
+	virtual void finish() override;
+	virtual void free(RID p_object) override;
+
+	virtual void parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
+	virtual void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+	virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+};
+
+#endif // GODOT_NAVIGATION_SERVER_2D_H

+ 830 - 0
modules/navigation/nav_mesh_generator_2d.cpp

@@ -0,0 +1,830 @@
+/**************************************************************************/
+/*  nav_mesh_generator_2d.cpp                                             */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "nav_mesh_generator_2d.h"
+
+#include "core/config/project_settings.h"
+#include "scene/2d/mesh_instance_2d.h"
+#include "scene/2d/multimesh_instance_2d.h"
+#include "scene/2d/physics_body_2d.h"
+#include "scene/2d/polygon_2d.h"
+#include "scene/2d/tile_map.h"
+#include "scene/resources/capsule_shape_2d.h"
+#include "scene/resources/circle_shape_2d.h"
+#include "scene/resources/concave_polygon_shape_2d.h"
+#include "scene/resources/convex_polygon_shape_2d.h"
+#include "scene/resources/navigation_mesh_source_geometry_data_2d.h"
+#include "scene/resources/navigation_polygon.h"
+#include "scene/resources/rectangle_shape_2d.h"
+
+#include "thirdparty/clipper2/include/clipper2/clipper.h"
+#include "thirdparty/misc/polypartition.h"
+
+NavMeshGenerator2D *NavMeshGenerator2D::singleton = nullptr;
+Mutex NavMeshGenerator2D::baking_navmesh_mutex;
+Mutex NavMeshGenerator2D::generator_task_mutex;
+bool NavMeshGenerator2D::use_threads = true;
+bool NavMeshGenerator2D::baking_use_multiple_threads = true;
+bool NavMeshGenerator2D::baking_use_high_priority_threads = true;
+HashSet<Ref<NavigationPolygon>> NavMeshGenerator2D::baking_navmeshes;
+HashMap<WorkerThreadPool::TaskID, NavMeshGenerator2D::NavMeshGeneratorTask2D *> NavMeshGenerator2D::generator_tasks;
+
+NavMeshGenerator2D *NavMeshGenerator2D::get_singleton() {
+	return singleton;
+}
+
+NavMeshGenerator2D::NavMeshGenerator2D() {
+	ERR_FAIL_COND(singleton != nullptr);
+	singleton = this;
+
+	baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads");
+	baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads");
+
+	// Using threads might cause problems on certain exports or with the Editor on certain devices.
+	// This is the main switch to turn threaded navmesh baking off should the need arise.
+	use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint();
+}
+
+NavMeshGenerator2D::~NavMeshGenerator2D() {
+	cleanup();
+}
+
+void NavMeshGenerator2D::sync() {
+	if (generator_tasks.size() == 0) {
+		return;
+	}
+
+	baking_navmesh_mutex.lock();
+	generator_task_mutex.lock();
+
+	LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+
+	for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
+		if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+			WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+			finished_task_ids.push_back(E.key);
+
+			NavMeshGeneratorTask2D *generator_task = E.value;
+			DEV_ASSERT(generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED);
+
+			baking_navmeshes.erase(generator_task->navigation_mesh);
+			if (generator_task->callback.is_valid()) {
+				generator_emit_callback(generator_task->callback);
+			}
+			memdelete(generator_task);
+		}
+	}
+
+	for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+		generator_tasks.erase(finished_task_id);
+	}
+
+	generator_task_mutex.unlock();
+	baking_navmesh_mutex.unlock();
+}
+
+void NavMeshGenerator2D::cleanup() {
+	baking_navmesh_mutex.lock();
+	generator_task_mutex.lock();
+
+	baking_navmeshes.clear();
+
+	for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
+		WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+		NavMeshGeneratorTask2D *generator_task = E.value;
+		memdelete(generator_task);
+	}
+	generator_tasks.clear();
+
+	generator_task_mutex.unlock();
+	baking_navmesh_mutex.unlock();
+}
+
+void NavMeshGenerator2D::finish() {
+	cleanup();
+}
+
+void NavMeshGenerator2D::parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+	ERR_FAIL_COND(!Thread::is_main_thread());
+	ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+	ERR_FAIL_NULL(p_root_node);
+	ERR_FAIL_COND(!p_root_node->is_inside_tree());
+	ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+	generator_parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node);
+
+	if (p_callback.is_valid()) {
+		generator_emit_callback(p_callback);
+	}
+}
+
+void NavMeshGenerator2D::bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback) {
+	ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+	ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+	if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) {
+		p_navigation_mesh->clear();
+		if (p_callback.is_valid()) {
+			generator_emit_callback(p_callback);
+		}
+		return;
+	}
+
+	baking_navmesh_mutex.lock();
+	if (baking_navmeshes.has(p_navigation_mesh)) {
+		baking_navmesh_mutex.unlock();
+		ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish.");
+	}
+	baking_navmeshes.insert(p_navigation_mesh);
+	baking_navmesh_mutex.unlock();
+
+	generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data);
+
+	baking_navmesh_mutex.lock();
+	baking_navmeshes.erase(p_navigation_mesh);
+	baking_navmesh_mutex.unlock();
+
+	if (p_callback.is_valid()) {
+		generator_emit_callback(p_callback);
+	}
+}
+
+void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback) {
+	ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+	ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+	if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) {
+		p_navigation_mesh->clear();
+		if (p_callback.is_valid()) {
+			generator_emit_callback(p_callback);
+		}
+		return;
+	}
+
+	if (!use_threads) {
+		bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+		return;
+	}
+
+	baking_navmesh_mutex.lock();
+	if (baking_navmeshes.has(p_navigation_mesh)) {
+		baking_navmesh_mutex.unlock();
+		ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish.");
+	}
+	baking_navmeshes.insert(p_navigation_mesh);
+	baking_navmesh_mutex.unlock();
+
+	generator_task_mutex.lock();
+	NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D);
+	generator_task->navigation_mesh = p_navigation_mesh;
+	generator_task->source_geometry_data = p_source_geometry_data;
+	generator_task->callback = p_callback;
+	generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED;
+	generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D");
+	generator_tasks.insert(generator_task->thread_task_id, generator_task);
+	generator_task_mutex.unlock();
+}
+
+void NavMeshGenerator2D::generator_thread_bake(void *p_arg) {
+	NavMeshGeneratorTask2D *generator_task = static_cast<NavMeshGeneratorTask2D *>(p_arg);
+
+	generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data);
+
+	generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED;
+}
+
+void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node, bool p_recurse_children) {
+	generator_parse_meshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+	generator_parse_multimeshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+	generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+	generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+	generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node);
+
+	if (p_recurse_children) {
+		for (int i = 0; i < p_node->get_child_count(); i++) {
+			generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children);
+		}
+	}
+}
+
+void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+	MeshInstance2D *mesh_instance = Object::cast_to<MeshInstance2D>(p_node);
+
+	if (mesh_instance == nullptr) {
+		return;
+	}
+
+	NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+
+	if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) {
+		return;
+	}
+
+	Ref<Mesh> mesh = mesh_instance->get_mesh();
+	if (!mesh.is_valid()) {
+		return;
+	}
+
+	const Transform2D mesh_instance_xform = p_source_geometry_data->root_node_transform * mesh_instance->get_global_transform();
+
+	using namespace Clipper2Lib;
+
+	Paths64 subject_paths, dummy_clip_paths;
+
+	for (int i = 0; i < mesh->get_surface_count(); i++) {
+		if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
+			continue;
+		}
+
+		if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) {
+			continue;
+		}
+
+		Path64 subject_path;
+
+		int index_count = 0;
+		if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
+			index_count = mesh->surface_get_array_index_len(i);
+		} else {
+			index_count = mesh->surface_get_array_len(i);
+		}
+
+		ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
+
+		Array a = mesh->surface_get_arrays(i);
+
+		Vector<Vector2> mesh_vertices = a[Mesh::ARRAY_VERTEX];
+
+		if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
+			Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
+			for (int vertex_index : mesh_indices) {
+				const Vector2 &vertex = mesh_vertices[vertex_index];
+				const Point64 &point = Point64(vertex.x, vertex.y);
+				subject_path.push_back(point);
+			}
+		} else {
+			for (const Vector2 &vertex : mesh_vertices) {
+				const Point64 &point = Point64(vertex.x, vertex.y);
+				subject_path.push_back(point);
+			}
+		}
+		subject_paths.push_back(subject_path);
+	}
+
+	Paths64 path_solution;
+
+	path_solution = Union(subject_paths, dummy_clip_paths, FillRule::NonZero);
+
+	//path_solution = RamerDouglasPeucker(path_solution, 0.025);
+
+	Vector<Vector<Vector2>> polypaths;
+
+	for (const Path64 &scaled_path : path_solution) {
+		Vector<Vector2> shape_outline;
+		for (const Point64 &scaled_point : scaled_path) {
+			shape_outline.push_back(Point2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y)));
+		}
+
+		for (int i = 0; i < shape_outline.size(); i++) {
+			shape_outline.write[i] = mesh_instance_xform.xform(shape_outline[i]);
+		}
+
+		p_source_geometry_data->add_obstruction_outline(shape_outline);
+	}
+}
+
+void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+	MultiMeshInstance2D *multimesh_instance = Object::cast_to<MultiMeshInstance2D>(p_node);
+
+	if (multimesh_instance == nullptr) {
+		return;
+	}
+
+	NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+	if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) {
+		return;
+	}
+
+	Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh();
+	if (!(multimesh.is_valid() && multimesh->get_transform_format() == MultiMesh::TRANSFORM_2D)) {
+		return;
+	}
+
+	Ref<Mesh> mesh = multimesh->get_mesh();
+	if (!mesh.is_valid()) {
+		return;
+	}
+
+	using namespace Clipper2Lib;
+
+	Paths64 mesh_subject_paths, dummy_clip_paths;
+
+	for (int i = 0; i < mesh->get_surface_count(); i++) {
+		if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
+			continue;
+		}
+
+		if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) {
+			continue;
+		}
+
+		Path64 subject_path;
+
+		int index_count = 0;
+		if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
+			index_count = mesh->surface_get_array_index_len(i);
+		} else {
+			index_count = mesh->surface_get_array_len(i);
+		}
+
+		ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
+
+		Array a = mesh->surface_get_arrays(i);
+
+		Vector<Vector2> mesh_vertices = a[Mesh::ARRAY_VERTEX];
+
+		if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
+			Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
+			for (int vertex_index : mesh_indices) {
+				const Vector2 &vertex = mesh_vertices[vertex_index];
+				const Point64 &point = Point64(vertex.x, vertex.y);
+				subject_path.push_back(point);
+			}
+		} else {
+			for (const Vector2 &vertex : mesh_vertices) {
+				const Point64 &point = Point64(vertex.x, vertex.y);
+				subject_path.push_back(point);
+			}
+		}
+		mesh_subject_paths.push_back(subject_path);
+	}
+
+	Paths64 mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero);
+
+	//path_solution = RamerDouglasPeucker(path_solution, 0.025);
+
+	int multimesh_instance_count = multimesh->get_visible_instance_count();
+	if (multimesh_instance_count == -1) {
+		multimesh_instance_count = multimesh->get_instance_count();
+	}
+
+	const Transform2D multimesh_instance_xform = p_source_geometry_data->root_node_transform * multimesh_instance->get_global_transform();
+
+	for (int i = 0; i < multimesh_instance_count; i++) {
+		const Transform2D multimesh_instance_mesh_instance_xform = multimesh_instance_xform * multimesh->get_instance_transform_2d(i);
+
+		for (const Path64 &mesh_path : mesh_path_solution) {
+			Vector<Vector2> shape_outline;
+
+			for (const Point64 &mesh_path_point : mesh_path) {
+				shape_outline.push_back(Point2(static_cast<real_t>(mesh_path_point.x), static_cast<real_t>(mesh_path_point.y)));
+			}
+
+			for (int j = 0; j < shape_outline.size(); j++) {
+				shape_outline.write[j] = multimesh_instance_mesh_instance_xform.xform(shape_outline[j]);
+			}
+			p_source_geometry_data->add_obstruction_outline(shape_outline);
+		}
+	}
+}
+
+void NavMeshGenerator2D::generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+	Polygon2D *polygon_2d = Object::cast_to<Polygon2D>(p_node);
+
+	if (polygon_2d == nullptr) {
+		return;
+	}
+
+	NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+
+	if (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) {
+		const Transform2D polygon_2d_xform = p_source_geometry_data->root_node_transform * polygon_2d->get_global_transform();
+
+		Vector<Vector2> shape_outline = polygon_2d->get_polygon();
+		for (int i = 0; i < shape_outline.size(); i++) {
+			shape_outline.write[i] = polygon_2d_xform.xform(shape_outline[i]);
+		}
+
+		p_source_geometry_data->add_obstruction_outline(shape_outline);
+	}
+}
+
+void NavMeshGenerator2D::generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+	StaticBody2D *static_body = Object::cast_to<StaticBody2D>(p_node);
+
+	if (static_body == nullptr) {
+		return;
+	}
+
+	NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+	if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) {
+		return;
+	}
+
+	uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask();
+	if (!(static_body->get_collision_layer() & parsed_collision_mask)) {
+		return;
+	}
+
+	List<uint32_t> shape_owners;
+	static_body->get_shape_owners(&shape_owners);
+
+	for (uint32_t shape_owner : shape_owners) {
+		if (static_body->is_shape_owner_disabled(shape_owner)) {
+			continue;
+		}
+
+		const int shape_count = static_body->shape_owner_get_shape_count(shape_owner);
+
+		for (int shape_index = 0; shape_index < shape_count; shape_index++) {
+			Ref<Shape2D> s = static_body->shape_owner_get_shape(shape_owner, shape_index);
+
+			if (s.is_null()) {
+				continue;
+			}
+
+			const Transform2D static_body_xform = p_source_geometry_data->root_node_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner);
+
+			RectangleShape2D *rectangle_shape = Object::cast_to<RectangleShape2D>(*s);
+			if (rectangle_shape) {
+				Vector<Vector2> shape_outline;
+
+				const Vector2 &rectangle_size = rectangle_shape->get_size();
+
+				shape_outline.resize(5);
+				shape_outline.write[0] = static_body_xform.xform(-rectangle_size * 0.5);
+				shape_outline.write[1] = static_body_xform.xform(Vector2(rectangle_size.x, -rectangle_size.y) * 0.5);
+				shape_outline.write[2] = static_body_xform.xform(rectangle_size * 0.5);
+				shape_outline.write[3] = static_body_xform.xform(Vector2(-rectangle_size.x, rectangle_size.y) * 0.5);
+				shape_outline.write[4] = static_body_xform.xform(-rectangle_size * 0.5);
+
+				p_source_geometry_data->add_obstruction_outline(shape_outline);
+			}
+
+			CapsuleShape2D *capsule_shape = Object::cast_to<CapsuleShape2D>(*s);
+			if (capsule_shape) {
+				const real_t capsule_height = capsule_shape->get_height();
+				const real_t capsule_radius = capsule_shape->get_radius();
+
+				Vector<Vector2> shape_outline;
+				const real_t turn_step = Math_TAU / 12.0;
+				shape_outline.resize(14);
+				int shape_outline_inx = 0;
+				for (int i = 0; i < 12; i++) {
+					Vector2 ofs = Vector2(0, (i > 3 && i <= 9) ? -capsule_height * 0.5 + capsule_radius : capsule_height * 0.5 - capsule_radius);
+
+					shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius + ofs);
+					shape_outline_inx += 1;
+					if (i == 3 || i == 9) {
+						shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius - ofs);
+						shape_outline_inx += 1;
+					}
+				}
+
+				p_source_geometry_data->add_obstruction_outline(shape_outline);
+			}
+
+			CircleShape2D *circle_shape = Object::cast_to<CircleShape2D>(*s);
+			if (circle_shape) {
+				const real_t circle_radius = circle_shape->get_radius();
+
+				Vector<Vector2> shape_outline;
+				int circle_edge_count = 12;
+				shape_outline.resize(circle_edge_count);
+
+				const real_t turn_step = Math_TAU / real_t(circle_edge_count);
+				for (int i = 0; i < circle_edge_count; i++) {
+					shape_outline.write[i] = static_body_xform.xform(Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * circle_radius);
+				}
+
+				p_source_geometry_data->add_obstruction_outline(shape_outline);
+			}
+
+			ConcavePolygonShape2D *concave_polygon_shape = Object::cast_to<ConcavePolygonShape2D>(*s);
+			if (concave_polygon_shape) {
+				Vector<Vector2> shape_outline = concave_polygon_shape->get_segments();
+
+				for (int i = 0; i < shape_outline.size(); i++) {
+					shape_outline.write[i] = static_body_xform.xform(shape_outline[i]);
+				}
+
+				p_source_geometry_data->add_obstruction_outline(shape_outline);
+			}
+
+			ConvexPolygonShape2D *convex_polygon_shape = Object::cast_to<ConvexPolygonShape2D>(*s);
+			if (convex_polygon_shape) {
+				Vector<Vector2> shape_outline = convex_polygon_shape->get_points();
+
+				for (int i = 0; i < shape_outline.size(); i++) {
+					shape_outline.write[i] = static_body_xform.xform(shape_outline[i]);
+				}
+
+				p_source_geometry_data->add_obstruction_outline(shape_outline);
+			}
+		}
+	}
+}
+
+void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+	TileMap *tilemap = Object::cast_to<TileMap>(p_node);
+
+	if (tilemap == nullptr) {
+		return;
+	}
+
+	NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+	uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask();
+
+	if (tilemap->get_layers_count() <= 0) {
+		return;
+	}
+
+	int tilemap_layer = 0; // only main tile map layer is supported
+
+	Ref<TileSet> tile_set = tilemap->get_tileset();
+	if (!tile_set.is_valid()) {
+		return;
+	}
+
+	int physics_layers_count = tile_set->get_physics_layers_count();
+	int navigation_layers_count = tile_set->get_navigation_layers_count();
+
+	if (physics_layers_count <= 0 && navigation_layers_count <= 0) {
+		return;
+	}
+
+	const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tilemap->get_global_transform();
+	TypedArray<Vector2i> used_cells = tilemap->get_used_cells(tilemap_layer);
+
+	for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) {
+		const Vector2i &cell = used_cells[used_cell_index];
+
+		const TileData *tile_data = tilemap->get_cell_tile_data(tilemap_layer, cell, false);
+
+		Transform2D tile_transform;
+		tile_transform.set_origin(tilemap->map_to_local(cell));
+
+		const Transform2D tile_transform_offset = tilemap_xform * tile_transform;
+
+		if (navigation_layers_count > 0) {
+			Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(tilemap_layer);
+			if (navigation_polygon.is_valid()) {
+				for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) {
+					Vector<Vector2> traversable_outline = navigation_polygon->get_outline(outline_index);
+
+					for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) {
+						traversable_outline.write[traversable_outline_index] = tile_transform_offset.xform(traversable_outline[traversable_outline_index]);
+					}
+
+					p_source_geometry_data->_add_traversable_outline(traversable_outline);
+				}
+			}
+		}
+
+		if (physics_layers_count > 0 && (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) && (tile_set->get_physics_layer_collision_layer(tilemap_layer) & parsed_collision_mask)) {
+			for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(tilemap_layer); collision_polygon_index++) {
+				Vector<Vector2> obstruction_outline = tile_data->get_collision_polygon_points(tilemap_layer, collision_polygon_index);
+
+				for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) {
+					obstruction_outline.write[obstruction_outline_index] = tile_transform_offset.xform(obstruction_outline[obstruction_outline_index]);
+				}
+
+				p_source_geometry_data->_add_obstruction_outline(obstruction_outline);
+			}
+		}
+	}
+}
+
+void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node) {
+	List<Node *> parse_nodes;
+
+	if (p_navigation_mesh->get_source_geometry_mode() == NavigationPolygon::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
+		parse_nodes.push_back(p_root_node);
+	} else {
+		p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_geometry_group_name(), &parse_nodes);
+	}
+
+	Transform2D root_node_transform = Transform2D();
+	if (Object::cast_to<Node2D>(p_root_node)) {
+		root_node_transform = Object::cast_to<Node2D>(p_root_node)->get_global_transform().affine_inverse();
+	}
+
+	p_source_geometry_data->clear();
+	p_source_geometry_data->root_node_transform = root_node_transform;
+
+	bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationPolygon::SOURCE_GEOMETRY_GROUPS_EXPLICIT;
+
+	for (Node *E : parse_nodes) {
+		generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, E, recurse_children);
+	}
+};
+
+static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPath64 *p_polypath_item) {
+	using namespace Clipper2Lib;
+
+	Vector<Vector2> polygon_vertices;
+
+	for (const Point64 &polypath_point : p_polypath_item->Polygon()) {
+		polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y)));
+	}
+
+	TPPLPoly tp;
+	tp.Init(polygon_vertices.size());
+	for (int j = 0; j < polygon_vertices.size(); j++) {
+		tp[j] = polygon_vertices[j];
+	}
+
+	if (p_polypath_item->IsHole()) {
+		tp.SetOrientation(TPPL_ORIENTATION_CW);
+		tp.SetHole(true);
+	} else {
+		tp.SetOrientation(TPPL_ORIENTATION_CCW);
+	}
+	p_tppl_in_polygon.push_back(tp);
+
+	for (size_t i = 0; i < p_polypath_item->Count(); i++) {
+		const PolyPath64 *polypath_item = p_polypath_item->Child(i);
+		generator_recursive_process_polytree_items(p_tppl_in_polygon, polypath_item);
+	}
+}
+
+bool NavMeshGenerator2D::generator_emit_callback(const Callable &p_callback) {
+	ERR_FAIL_COND_V(!p_callback.is_valid(), false);
+
+	Callable::CallError ce;
+	Variant result;
+	p_callback.callp(nullptr, 0, result, ce);
+
+	return ce.error == Callable::CallError::CALL_OK;
+}
+
+void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data) {
+	if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) {
+		return;
+	}
+
+	if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) {
+		return;
+	}
+
+	int outline_count = p_navigation_mesh->get_outline_count();
+	const Vector<Vector<Vector2>> &traversable_outlines = p_source_geometry_data->_get_traversable_outlines();
+	const Vector<Vector<Vector2>> &obstruction_outlines = p_source_geometry_data->_get_obstruction_outlines();
+
+	if (outline_count == 0 && traversable_outlines.size() == 0) {
+		return;
+	}
+
+	using namespace Clipper2Lib;
+
+	Paths64 traversable_polygon_paths;
+	Paths64 obstruction_polygon_paths;
+
+	for (int i = 0; i < outline_count; i++) {
+		const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i);
+		Path64 subject_path;
+		for (const Vector2 &traversable_point : traversable_outline) {
+			const Point64 &point = Point64(traversable_point.x, traversable_point.y);
+			subject_path.push_back(point);
+		}
+		traversable_polygon_paths.push_back(subject_path);
+	}
+
+	for (const Vector<Vector2> &traversable_outline : traversable_outlines) {
+		Path64 subject_path;
+		for (const Vector2 &traversable_point : traversable_outline) {
+			const Point64 &point = Point64(traversable_point.x, traversable_point.y);
+			subject_path.push_back(point);
+		}
+		traversable_polygon_paths.push_back(subject_path);
+	}
+
+	for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) {
+		Path64 clip_path;
+		for (const Vector2 &obstruction_point : obstruction_outline) {
+			const Point64 &point = Point64(obstruction_point.x, obstruction_point.y);
+			clip_path.push_back(point);
+		}
+		obstruction_polygon_paths.push_back(clip_path);
+	}
+
+	Paths64 path_solution;
+
+	// first merge all traversable polygons according to user specified fill rule
+	Paths64 dummy_clip_path;
+	traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, FillRule::NonZero);
+	// merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry
+	obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero);
+
+	path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero);
+
+	real_t agent_radius_offset = p_navigation_mesh->get_agent_radius();
+	if (agent_radius_offset > 0.0) {
+		path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon);
+	}
+	//path_solution = RamerDouglasPeucker(path_solution, 0.025); //
+
+	Vector<Vector<Vector2>> new_baked_outlines;
+
+	for (const Path64 &scaled_path : path_solution) {
+		Vector<Vector2> polypath;
+		for (const Point64 &scaled_point : scaled_path) {
+			polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y)));
+		}
+		new_baked_outlines.push_back(polypath);
+	}
+
+	if (new_baked_outlines.size() == 0) {
+		p_navigation_mesh->set_vertices(Vector<Vector2>());
+		p_navigation_mesh->clear_polygons();
+		return;
+	}
+
+	Paths64 polygon_paths;
+
+	for (const Vector<Vector2> &baked_outline : new_baked_outlines) {
+		Path64 polygon_path;
+		for (const Vector2 &baked_outline_point : baked_outline) {
+			const Point64 &point = Point64(baked_outline_point.x, baked_outline_point.y);
+			polygon_path.push_back(point);
+		}
+		polygon_paths.push_back(polygon_path);
+	}
+
+	ClipType clipper_cliptype = ClipType::Union;
+
+	List<TPPLPoly> tppl_in_polygon, tppl_out_polygon;
+
+	PolyTree64 polytree;
+	Clipper64 clipper_64;
+
+	clipper_64.AddSubject(polygon_paths);
+	clipper_64.Execute(clipper_cliptype, FillRule::NonZero, polytree);
+
+	for (size_t i = 0; i < polytree.Count(); i++) {
+		const PolyPath64 *polypath_item = polytree[i];
+		generator_recursive_process_polytree_items(tppl_in_polygon, polypath_item);
+	}
+
+	TPPLPartition tpart;
+	if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed!
+		ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths.");
+		p_navigation_mesh->set_vertices(Vector<Vector2>());
+		p_navigation_mesh->clear_polygons();
+		return;
+	}
+
+	Vector<Vector2> new_vertices;
+	Vector<Vector<int>> new_polygons;
+
+	HashMap<Vector2, int> points;
+	for (List<TPPLPoly>::Element *I = tppl_out_polygon.front(); I; I = I->next()) {
+		TPPLPoly &tp = I->get();
+
+		Vector<int> new_polygon;
+
+		for (int64_t i = 0; i < tp.GetNumPoints(); i++) {
+			HashMap<Vector2, int>::Iterator E = points.find(tp[i]);
+			if (!E) {
+				E = points.insert(tp[i], new_vertices.size());
+				new_vertices.push_back(tp[i]);
+			}
+			new_polygon.push_back(E->value);
+		}
+
+		new_polygons.push_back(new_polygon);
+	}
+
+	p_navigation_mesh->set_vertices(new_vertices);
+	p_navigation_mesh->clear_polygons();
+	for (int i = 0; i < new_polygons.size(); i++) {
+		p_navigation_mesh->add_polygon(new_polygons[i]);
+	}
+}

+ 100 - 0
modules/navigation/nav_mesh_generator_2d.h

@@ -0,0 +1,100 @@
+/**************************************************************************/
+/*  nav_mesh_generator_2d.h                                               */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef NAV_MESH_GENERATOR_2D_H
+#define NAV_MESH_GENERATOR_2D_H
+
+#include "core/object/class_db.h"
+#include "core/object/worker_thread_pool.h"
+
+class Node;
+class NavigationPolygon;
+class NavigationMeshSourceGeometryData2D;
+
+class NavMeshGenerator2D : public Object {
+	static NavMeshGenerator2D *singleton;
+
+	static Mutex baking_navmesh_mutex;
+	static Mutex generator_task_mutex;
+
+	static bool use_threads;
+	static bool baking_use_multiple_threads;
+	static bool baking_use_high_priority_threads;
+
+	struct NavMeshGeneratorTask2D {
+		enum TaskStatus {
+			BAKING_STARTED,
+			BAKING_FINISHED,
+			BAKING_FAILED,
+			CALLBACK_DISPATCHED,
+			CALLBACK_FAILED,
+		};
+
+		Ref<NavigationPolygon> navigation_mesh;
+		Ref<NavigationMeshSourceGeometryData2D> source_geometry_data;
+		Callable callback;
+		WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID;
+		NavMeshGeneratorTask2D::TaskStatus status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED;
+	};
+
+	static HashMap<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> generator_tasks;
+
+	static void generator_thread_bake(void *p_arg);
+
+	static HashSet<Ref<NavigationPolygon>> baking_navmeshes;
+
+	static void generator_parse_geometry_node(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node, bool p_recurse_children);
+	static void generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node);
+	static void generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data);
+
+	static void generator_parse_meshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+	static void generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+	static void generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+	static void generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+	static void generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+
+	static bool generator_emit_callback(const Callable &p_callback);
+
+public:
+	static NavMeshGenerator2D *get_singleton();
+
+	static void sync();
+	static void cleanup();
+	static void finish();
+
+	static void parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
+	static void bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable());
+	static void bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable());
+
+	NavMeshGenerator2D();
+	~NavMeshGenerator2D();
+};
+
+#endif // NAV_MESH_GENERATOR_2D_H

+ 7 - 0
modules/navigation/register_types.cpp

@@ -31,6 +31,7 @@
 #include "register_types.h"
 
 #include "godot_navigation_server.h"
+#include "godot_navigation_server_2d.h"
 
 #ifndef DISABLE_DEPRECATED
 #ifndef _3D_DISABLED
@@ -43,6 +44,7 @@
 #endif
 
 #include "core/config/engine.h"
+#include "servers/navigation_server_2d.h"
 #include "servers/navigation_server_3d.h"
 
 #ifndef DISABLE_DEPRECATED
@@ -55,9 +57,14 @@ NavigationServer3D *new_server() {
 	return memnew(GodotNavigationServer);
 }
 
+NavigationServer2D *new_navigation_server_2d() {
+	return memnew(GodotNavigationServer2D);
+}
+
 void initialize_navigation_module(ModuleInitializationLevel p_level) {
 	if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
 		NavigationServer3DManager::set_default_server(new_server);
+		NavigationServer2DManager::set_default_server(new_navigation_server_2d);
 
 #ifndef DISABLE_DEPRECATED
 #ifndef _3D_DISABLED

+ 31 - 4
scene/2d/navigation_region_2d.cpp

@@ -179,10 +179,6 @@ void NavigationRegion2D::_notification(int p_what) {
 }
 
 void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon) {
-	if (p_navigation_polygon == navigation_polygon) {
-		return;
-	}
-
 	if (navigation_polygon.is_valid()) {
 		navigation_polygon->disconnect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed));
 	}
@@ -226,6 +222,32 @@ RID NavigationRegion2D::get_navigation_map() const {
 	return RID();
 }
 
+void NavigationRegion2D::bake_navigation_polygon(bool p_on_thread) {
+	ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
+	ERR_FAIL_COND_MSG(!navigation_polygon.is_valid(), "Baking the navigation polygon requires a valid `NavigationPolygon` resource.");
+
+	Ref<NavigationMeshSourceGeometryData2D> source_geometry_data;
+	source_geometry_data.instantiate();
+
+	NavigationServer2D::get_singleton()->parse_source_geometry_data(navigation_polygon, source_geometry_data, this);
+
+	if (p_on_thread) {
+		NavigationServer2D::get_singleton()->bake_from_source_geometry_data_async(navigation_polygon, source_geometry_data, callable_mp(this, &NavigationRegion2D::_bake_finished).bind(navigation_polygon));
+	} else {
+		NavigationServer2D::get_singleton()->bake_from_source_geometry_data(navigation_polygon, source_geometry_data, callable_mp(this, &NavigationRegion2D::_bake_finished).bind(navigation_polygon));
+	}
+}
+
+void NavigationRegion2D::_bake_finished(Ref<NavigationPolygon> p_navigation_polygon) {
+	if (!Thread::is_main_thread()) {
+		call_deferred(SNAME("_bake_finished"), p_navigation_polygon);
+		return;
+	}
+
+	set_navigation_polygon(p_navigation_polygon);
+	emit_signal(SNAME("bake_finished"));
+}
+
 void NavigationRegion2D::_navigation_polygon_changed() {
 	if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) {
 		queue_redraw();
@@ -290,6 +312,8 @@ void NavigationRegion2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationRegion2D::set_travel_cost);
 	ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationRegion2D::get_travel_cost);
 
+	ClassDB::bind_method(D_METHOD("bake_navigation_polygon", "on_thread"), &NavigationRegion2D::bake_navigation_polygon, DEFVAL(true));
+
 	ClassDB::bind_method(D_METHOD("_navigation_polygon_changed"), &NavigationRegion2D::_navigation_polygon_changed);
 
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_polygon", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon");
@@ -300,6 +324,9 @@ void NavigationRegion2D::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constrain_avoidance"), "set_constrain_avoidance", "get_constrain_avoidance");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers");
+
+	ADD_SIGNAL(MethodInfo("navigation_polygon_changed"));
+	ADD_SIGNAL(MethodInfo("bake_finished"));
 }
 
 #ifndef DISABLE_DEPRECATED

+ 3 - 0
scene/2d/navigation_region_2d.h

@@ -114,6 +114,9 @@ public:
 
 	PackedStringArray get_configuration_warnings() const override;
 
+	void bake_navigation_polygon(bool p_on_thread);
+	void _bake_finished(Ref<NavigationPolygon> p_navigation_polygon);
+
 	NavigationRegion2D();
 	~NavigationRegion2D();
 

+ 2 - 0
scene/register_scene_types.cpp

@@ -172,6 +172,7 @@
 #include "scene/resources/mesh_texture.h"
 #include "scene/resources/multimesh.h"
 #include "scene/resources/navigation_mesh.h"
+#include "scene/resources/navigation_mesh_source_geometry_data_2d.h"
 #include "scene/resources/navigation_mesh_source_geometry_data_3d.h"
 #include "scene/resources/navigation_polygon.h"
 #include "scene/resources/packed_scene.h"
@@ -956,6 +957,7 @@ void register_scene_types() {
 	GDREGISTER_CLASS(PathFollow2D);
 
 	GDREGISTER_CLASS(NavigationMesh);
+	GDREGISTER_CLASS(NavigationMeshSourceGeometryData2D);
 	GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D);
 	GDREGISTER_CLASS(NavigationPolygon);
 	GDREGISTER_CLASS(NavigationRegion2D);

+ 138 - 0
scene/resources/navigation_mesh_source_geometry_data_2d.cpp

@@ -0,0 +1,138 @@
+/**************************************************************************/
+/*  navigation_mesh_source_geometry_data_2d.cpp                           */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "navigation_mesh_source_geometry_data_2d.h"
+
+#include "scene/resources/mesh.h"
+
+void NavigationMeshSourceGeometryData2D::clear() {
+	traversable_outlines.clear();
+	obstruction_outlines.clear();
+}
+
+void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines) {
+	traversable_outlines = p_traversable_outlines;
+}
+
+void NavigationMeshSourceGeometryData2D::_set_obstruction_outlines(const Vector<Vector<Vector2>> &p_obstruction_outlines) {
+	obstruction_outlines = p_obstruction_outlines;
+}
+
+void NavigationMeshSourceGeometryData2D::_add_traversable_outline(const Vector<Vector2> &p_shape_outline) {
+	if (p_shape_outline.size() > 1) {
+		traversable_outlines.push_back(p_shape_outline);
+	}
+}
+
+void NavigationMeshSourceGeometryData2D::_add_obstruction_outline(const Vector<Vector2> &p_shape_outline) {
+	if (p_shape_outline.size() > 1) {
+		obstruction_outlines.push_back(p_shape_outline);
+	}
+}
+
+void NavigationMeshSourceGeometryData2D::set_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines) {
+	traversable_outlines.resize(p_traversable_outlines.size());
+	for (int i = 0; i < p_traversable_outlines.size(); i++) {
+		traversable_outlines.write[i] = p_traversable_outlines[i];
+	}
+}
+
+TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_traversable_outlines() const {
+	TypedArray<Vector<Vector2>> typed_array_traversable_outlines;
+	typed_array_traversable_outlines.resize(traversable_outlines.size());
+	for (int i = 0; i < typed_array_traversable_outlines.size(); i++) {
+		typed_array_traversable_outlines[i] = traversable_outlines[i];
+	}
+
+	return typed_array_traversable_outlines;
+}
+
+void NavigationMeshSourceGeometryData2D::set_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) {
+	obstruction_outlines.resize(p_obstruction_outlines.size());
+	for (int i = 0; i < p_obstruction_outlines.size(); i++) {
+		obstruction_outlines.write[i] = p_obstruction_outlines[i];
+	}
+}
+
+TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_obstruction_outlines() const {
+	TypedArray<Vector<Vector2>> typed_array_obstruction_outlines;
+	typed_array_obstruction_outlines.resize(obstruction_outlines.size());
+	for (int i = 0; i < typed_array_obstruction_outlines.size(); i++) {
+		typed_array_obstruction_outlines[i] = obstruction_outlines[i];
+	}
+
+	return typed_array_obstruction_outlines;
+}
+
+void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) {
+	if (p_shape_outline.size() > 1) {
+		Vector<Vector2> traversable_outline;
+		traversable_outline.resize(p_shape_outline.size());
+		for (int i = 0; i < p_shape_outline.size(); i++) {
+			traversable_outline.write[i] = p_shape_outline[i];
+		}
+		traversable_outlines.push_back(traversable_outline);
+	}
+}
+
+void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVector2Array &p_shape_outline) {
+	if (p_shape_outline.size() > 1) {
+		Vector<Vector2> obstruction_outline;
+		obstruction_outline.resize(p_shape_outline.size());
+		for (int i = 0; i < p_shape_outline.size(); i++) {
+			obstruction_outline.write[i] = p_shape_outline[i];
+		}
+		obstruction_outlines.push_back(obstruction_outline);
+	}
+}
+
+void NavigationMeshSourceGeometryData2D::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData2D::clear);
+	ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData2D::has_data);
+
+	ClassDB::bind_method(D_METHOD("set_traversable_outlines", "traversable_outlines"), &NavigationMeshSourceGeometryData2D::set_traversable_outlines);
+	ClassDB::bind_method(D_METHOD("get_traversable_outlines"), &NavigationMeshSourceGeometryData2D::get_traversable_outlines);
+
+	ClassDB::bind_method(D_METHOD("set_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::set_obstruction_outlines);
+	ClassDB::bind_method(D_METHOD("get_obstruction_outlines"), &NavigationMeshSourceGeometryData2D::get_obstruction_outlines);
+
+	ClassDB::bind_method(D_METHOD("add_traversable_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_traversable_outline);
+	ClassDB::bind_method(D_METHOD("add_obstruction_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_obstruction_outline);
+
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines");
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines");
+}
+
+NavigationMeshSourceGeometryData2D::NavigationMeshSourceGeometryData2D() {
+}
+
+NavigationMeshSourceGeometryData2D::~NavigationMeshSourceGeometryData2D() {
+	clear();
+}

+ 78 - 0
scene/resources/navigation_mesh_source_geometry_data_2d.h

@@ -0,0 +1,78 @@
+/**************************************************************************/
+/*  navigation_mesh_source_geometry_data_2d.h                             */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H
+#define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H
+
+#include "scene/2d/node_2d.h"
+#include "scene/resources/navigation_polygon.h"
+
+class NavigationMeshSourceGeometryData2D : public Resource {
+	GDCLASS(NavigationMeshSourceGeometryData2D, Resource);
+
+	Vector<Vector<Vector2>> traversable_outlines;
+	Vector<Vector<Vector2>> obstruction_outlines;
+
+protected:
+	static void _bind_methods();
+
+public:
+	void _set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines);
+	const Vector<Vector<Vector2>> &_get_traversable_outlines() const { return traversable_outlines; }
+
+	void _set_obstruction_outlines(const Vector<Vector<Vector2>> &p_obstruction_outlines);
+	const Vector<Vector<Vector2>> &_get_obstruction_outlines() const { return obstruction_outlines; }
+
+	void _add_traversable_outline(const Vector<Vector2> &p_shape_outline);
+	void _add_obstruction_outline(const Vector<Vector2> &p_shape_outline);
+
+	// kept root node transform here on the geometry data
+	// if we add this transform to all exposed functions we need to break comp on all functions later
+	// when navmesh changes from global transform to relative to navregion
+	// but if it stays here we can just remove it and change the internal functions only
+	Transform2D root_node_transform;
+
+	void set_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines);
+	TypedArray<Vector<Vector2>> get_traversable_outlines() const;
+
+	void set_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines);
+	TypedArray<Vector<Vector2>> get_obstruction_outlines() const;
+
+	void add_traversable_outline(const PackedVector2Array &p_shape_outline);
+	void add_obstruction_outline(const PackedVector2Array &p_shape_outline);
+
+	bool has_data() { return traversable_outlines.size(); };
+	void clear();
+
+	NavigationMeshSourceGeometryData2D();
+	~NavigationMeshSourceGeometryData2D();
+};
+
+#endif // NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H

+ 131 - 1
scene/resources/navigation_polygon.cpp

@@ -32,6 +32,7 @@
 
 #include "core/math/geometry_2d.h"
 #include "core/os/mutex.h"
+#include "servers/navigation_server_2d.h"
 
 #include "thirdparty/misc/polypartition.h"
 
@@ -229,7 +230,11 @@ void NavigationPolygon::clear_outlines() {
 	rect_cache_dirty = true;
 }
 
+#ifndef DISABLE_DEPRECATED
 void NavigationPolygon::make_polygons_from_outlines() {
+	WARN_PRINT("Function make_polygons_from_outlines() is deprecated."
+			   "\nUse NavigationServer2D.parse_source_geometry_data() and NavigationServer2D.bake_from_source_geometry_data() instead.");
+
 	{
 		MutexLock lock(navigation_mesh_generation);
 		navigation_mesh.unref();
@@ -331,6 +336,7 @@ void NavigationPolygon::make_polygons_from_outlines() {
 
 	emit_changed();
 }
+#endif // DISABLE_DEPRECATED
 
 void NavigationPolygon::set_cell_size(real_t p_cell_size) {
 	cell_size = p_cell_size;
@@ -341,6 +347,69 @@ real_t NavigationPolygon::get_cell_size() const {
 	return cell_size;
 }
 
+void NavigationPolygon::set_parsed_geometry_type(ParsedGeometryType p_geometry_type) {
+	ERR_FAIL_INDEX(p_geometry_type, PARSED_GEOMETRY_MAX);
+	parsed_geometry_type = p_geometry_type;
+	notify_property_list_changed();
+}
+
+NavigationPolygon::ParsedGeometryType NavigationPolygon::get_parsed_geometry_type() const {
+	return parsed_geometry_type;
+}
+
+void NavigationPolygon::set_parsed_collision_mask(uint32_t p_mask) {
+	parsed_collision_mask = p_mask;
+}
+
+uint32_t NavigationPolygon::get_parsed_collision_mask() const {
+	return parsed_collision_mask;
+}
+
+void NavigationPolygon::set_parsed_collision_mask_value(int p_layer_number, bool p_value) {
+	ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
+	ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
+	uint32_t mask = get_parsed_collision_mask();
+	if (p_value) {
+		mask |= 1 << (p_layer_number - 1);
+	} else {
+		mask &= ~(1 << (p_layer_number - 1));
+	}
+	set_parsed_collision_mask(mask);
+}
+
+bool NavigationPolygon::get_parsed_collision_mask_value(int p_layer_number) const {
+	ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive.");
+	ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive.");
+	return get_parsed_collision_mask() & (1 << (p_layer_number - 1));
+}
+
+void NavigationPolygon::set_source_geometry_mode(SourceGeometryMode p_geometry_mode) {
+	ERR_FAIL_INDEX(p_geometry_mode, SOURCE_GEOMETRY_MAX);
+	source_geometry_mode = p_geometry_mode;
+	notify_property_list_changed();
+}
+
+NavigationPolygon::SourceGeometryMode NavigationPolygon::get_source_geometry_mode() const {
+	return source_geometry_mode;
+}
+
+void NavigationPolygon::set_source_geometry_group_name(StringName p_group_name) {
+	source_geometry_group_name = p_group_name;
+}
+
+StringName NavigationPolygon::get_source_geometry_group_name() const {
+	return source_geometry_group_name;
+}
+
+void NavigationPolygon::set_agent_radius(real_t p_value) {
+	ERR_FAIL_COND(p_value < 0);
+	agent_radius = p_value;
+}
+
+real_t NavigationPolygon::get_agent_radius() const {
+	return agent_radius;
+}
+
 void NavigationPolygon::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationPolygon::set_vertices);
 	ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationPolygon::get_vertices);
@@ -358,7 +427,9 @@ void NavigationPolygon::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_outline", "idx"), &NavigationPolygon::get_outline);
 	ClassDB::bind_method(D_METHOD("remove_outline", "idx"), &NavigationPolygon::remove_outline);
 	ClassDB::bind_method(D_METHOD("clear_outlines"), &NavigationPolygon::clear_outlines);
+#ifndef DISABLE_DEPRECATED
 	ClassDB::bind_method(D_METHOD("make_polygons_from_outlines"), &NavigationPolygon::make_polygons_from_outlines);
+#endif // DISABLE_DEPRECATED
 
 	ClassDB::bind_method(D_METHOD("_set_polygons", "polygons"), &NavigationPolygon::_set_polygons);
 	ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationPolygon::_get_polygons);
@@ -369,10 +440,69 @@ void NavigationPolygon::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &NavigationPolygon::set_cell_size);
 	ClassDB::bind_method(D_METHOD("get_cell_size"), &NavigationPolygon::get_cell_size);
 
+	ClassDB::bind_method(D_METHOD("set_parsed_geometry_type", "geometry_type"), &NavigationPolygon::set_parsed_geometry_type);
+	ClassDB::bind_method(D_METHOD("get_parsed_geometry_type"), &NavigationPolygon::get_parsed_geometry_type);
+
+	ClassDB::bind_method(D_METHOD("set_parsed_collision_mask", "mask"), &NavigationPolygon::set_parsed_collision_mask);
+	ClassDB::bind_method(D_METHOD("get_parsed_collision_mask"), &NavigationPolygon::get_parsed_collision_mask);
+
+	ClassDB::bind_method(D_METHOD("set_parsed_collision_mask_value", "layer_number", "value"), &NavigationPolygon::set_parsed_collision_mask_value);
+	ClassDB::bind_method(D_METHOD("get_parsed_collision_mask_value", "layer_number"), &NavigationPolygon::get_parsed_collision_mask_value);
+
+	ClassDB::bind_method(D_METHOD("set_source_geometry_mode", "geometry_mode"), &NavigationPolygon::set_source_geometry_mode);
+	ClassDB::bind_method(D_METHOD("get_source_geometry_mode"), &NavigationPolygon::get_source_geometry_mode);
+
+	ClassDB::bind_method(D_METHOD("set_source_geometry_group_name", "group_name"), &NavigationPolygon::set_source_geometry_group_name);
+	ClassDB::bind_method(D_METHOD("get_source_geometry_group_name"), &NavigationPolygon::get_source_geometry_group_name);
+
+	ClassDB::bind_method(D_METHOD("set_agent_radius", "agent_radius"), &NavigationPolygon::set_agent_radius);
+	ClassDB::bind_method(D_METHOD("get_agent_radius"), &NavigationPolygon::get_agent_radius);
+
 	ClassDB::bind_method(D_METHOD("clear"), &NavigationPolygon::clear);
 
 	ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines");
-	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "0.01,500.0,0.01,or_greater,suffix:px"), "set_cell_size", "get_cell_size");
+
+	ADD_GROUP("Geometry", "");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Meshes and Static Colliders"), "set_parsed_geometry_type", "get_parsed_geometry_type");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_parsed_collision_mask", "get_parsed_collision_mask");
+	ADD_PROPERTY_DEFAULT("parsed_collision_mask", 0xFFFFFFFF);
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "source_geometry_mode", PROPERTY_HINT_ENUM, "Root Node Children,Group With Children,Group Explicit"), "set_source_geometry_mode", "get_source_geometry_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_geometry_group_name"), "set_source_geometry_group_name", "get_source_geometry_group_name");
+	ADD_PROPERTY_DEFAULT("source_geometry_group_name", StringName("navigation_polygon_source_geometry_group"));
+	ADD_GROUP("Cells", "");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "1.0,50.0,1.0,or_greater,suffix:px"), "set_cell_size", "get_cell_size");
+	ADD_GROUP("Agents", "agent_");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_radius", PROPERTY_HINT_RANGE, "0.0,500.0,0.01,or_greater,suffix:px"), "set_agent_radius", "get_agent_radius");
+
+	BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES);
+	BIND_ENUM_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS);
+	BIND_ENUM_CONSTANT(PARSED_GEOMETRY_BOTH);
+	BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MAX);
+
+	BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_ROOT_NODE_CHILDREN);
+	BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN);
+	BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_EXPLICIT);
+	BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_MAX);
+}
+
+void NavigationPolygon::_validate_property(PropertyInfo &p_property) const {
+	if (p_property.name == "parsed_collision_mask") {
+		if (parsed_geometry_type == PARSED_GEOMETRY_MESH_INSTANCES) {
+			p_property.usage = PROPERTY_USAGE_NONE;
+			return;
+		}
+	}
+
+	if (p_property.name == "parsed_source_group_name") {
+		if (source_geometry_mode == SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
+			p_property.usage = PROPERTY_USAGE_NONE;
+			return;
+		}
+	}
+}
+
+NavigationPolygon::NavigationPolygon() {
+	navigation_mesh.instantiate();
 }

+ 50 - 1
scene/resources/navigation_polygon.h

@@ -43,6 +43,7 @@ class NavigationPolygon : public Resource {
 	};
 	Vector<Polygon> polygons;
 	Vector<Vector<Vector2>> outlines;
+	Vector<Vector<Vector2>> baked_outlines;
 
 	mutable Rect2 item_rect;
 	mutable bool rect_cache_dirty = true;
@@ -55,6 +56,7 @@ class NavigationPolygon : public Resource {
 
 protected:
 	static void _bind_methods();
+	void _validate_property(PropertyInfo &p_property) const;
 
 	void _set_polygons(const TypedArray<Vector<int32_t>> &p_array);
 	TypedArray<Vector<int32_t>> _get_polygons() const;
@@ -68,6 +70,28 @@ public:
 	bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
 #endif
 
+	enum ParsedGeometryType {
+		PARSED_GEOMETRY_MESH_INSTANCES = 0,
+		PARSED_GEOMETRY_STATIC_COLLIDERS,
+		PARSED_GEOMETRY_BOTH,
+		PARSED_GEOMETRY_MAX
+	};
+
+	enum SourceGeometryMode {
+		SOURCE_GEOMETRY_ROOT_NODE_CHILDREN = 0,
+		SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN,
+		SOURCE_GEOMETRY_GROUPS_EXPLICIT,
+		SOURCE_GEOMETRY_MAX
+	};
+
+	real_t agent_radius = 10.0f;
+
+	ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_BOTH;
+	uint32_t parsed_collision_mask = 0xFFFFFFFF;
+
+	SourceGeometryMode source_geometry_mode = SOURCE_GEOMETRY_ROOT_NODE_CHILDREN;
+	StringName source_geometry_group_name = "navigation_polygon_source_group";
+
 	void set_vertices(const Vector<Vector2> &p_vertices);
 	Vector<Vector2> get_vertices() const;
 
@@ -82,11 +106,33 @@ public:
 	int get_outline_count() const;
 
 	void clear_outlines();
+#ifndef DISABLE_DEPRECATED
 	void make_polygons_from_outlines();
+#endif // DISABLE_DEPRECATED
 
+	void set_polygons(const Vector<Vector<int>> &p_polygons);
+	const Vector<Vector<int>> &get_polygons() const;
 	Vector<int> get_polygon(int p_idx);
 	void clear_polygons();
 
+	void set_parsed_geometry_type(ParsedGeometryType p_geometry_type);
+	ParsedGeometryType get_parsed_geometry_type() const;
+
+	void set_parsed_collision_mask(uint32_t p_mask);
+	uint32_t get_parsed_collision_mask() const;
+
+	void set_parsed_collision_mask_value(int p_layer_number, bool p_value);
+	bool get_parsed_collision_mask_value(int p_layer_number) const;
+
+	void set_source_geometry_mode(SourceGeometryMode p_geometry_mode);
+	SourceGeometryMode get_source_geometry_mode() const;
+
+	void set_source_geometry_group_name(StringName p_group_name);
+	StringName get_source_geometry_group_name() const;
+
+	void set_agent_radius(real_t p_value);
+	real_t get_agent_radius() const;
+
 	Ref<NavigationMesh> get_navigation_mesh();
 
 	void set_cell_size(real_t p_cell_size);
@@ -94,8 +140,11 @@ public:
 
 	void clear();
 
-	NavigationPolygon() {}
+	NavigationPolygon();
 	~NavigationPolygon() {}
 };
 
+VARIANT_ENUM_CAST(NavigationPolygon::ParsedGeometryType);
+VARIANT_ENUM_CAST(NavigationPolygon::SourceGeometryMode);
+
 #endif // NAVIGATION_POLYGON_H

+ 129 - 377
servers/navigation_server_2d.cpp

@@ -28,133 +28,153 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#include "servers/navigation_server_2d.h"
+#include "navigation_server_2d.h"
 
-#include "core/math/transform_2d.h"
-#include "core/math/transform_3d.h"
 #include "servers/navigation_server_3d.h"
 
 NavigationServer2D *NavigationServer2D::singleton = nullptr;
 
-#define FORWARD_0(FUNC_NAME)                                     \
-	NavigationServer2D::FUNC_NAME() {                            \
-		return NavigationServer3D::get_singleton()->FUNC_NAME(); \
-	}
-
-#define FORWARD_0_C(FUNC_NAME)                                   \
-	NavigationServer2D::FUNC_NAME()                              \
-			const {                                              \
-		return NavigationServer3D::get_singleton()->FUNC_NAME(); \
-	}
-
-#define FORWARD_1(FUNC_NAME, T_0, D_0, CONV_0)                              \
-	NavigationServer2D::FUNC_NAME(T_0 D_0) {                                \
-		return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \
-	}
-
-#define FORWARD_1_C(FUNC_NAME, T_0, D_0, CONV_0)                            \
-	NavigationServer2D::FUNC_NAME(T_0 D_0)                                  \
-			const {                                                         \
-		return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \
-	}
+void NavigationServer2D::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("get_maps"), &NavigationServer2D::get_maps);
 
-#define FORWARD_1_R_C(CONV_R, FUNC_NAME, T_0, D_0, CONV_0)                          \
-	NavigationServer2D::FUNC_NAME(T_0 D_0)                                          \
-			const {                                                                 \
-		return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0))); \
-	}
+	ClassDB::bind_method(D_METHOD("map_create"), &NavigationServer2D::map_create);
+	ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &NavigationServer2D::map_set_active);
+	ClassDB::bind_method(D_METHOD("map_is_active", "map"), &NavigationServer2D::map_is_active);
+	ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &NavigationServer2D::map_set_cell_size);
+	ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &NavigationServer2D::map_get_cell_size);
+	ClassDB::bind_method(D_METHOD("map_set_use_edge_connections", "map", "enabled"), &NavigationServer2D::map_set_use_edge_connections);
+	ClassDB::bind_method(D_METHOD("map_get_use_edge_connections", "map"), &NavigationServer2D::map_get_use_edge_connections);
+	ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &NavigationServer2D::map_set_edge_connection_margin);
+	ClassDB::bind_method(D_METHOD("map_get_edge_connection_margin", "map"), &NavigationServer2D::map_get_edge_connection_margin);
+	ClassDB::bind_method(D_METHOD("map_set_link_connection_radius", "map", "radius"), &NavigationServer2D::map_set_link_connection_radius);
+	ClassDB::bind_method(D_METHOD("map_get_link_connection_radius", "map"), &NavigationServer2D::map_get_link_connection_radius);
+	ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize", "navigation_layers"), &NavigationServer2D::map_get_path, DEFVAL(1));
+	ClassDB::bind_method(D_METHOD("map_get_closest_point", "map", "to_point"), &NavigationServer2D::map_get_closest_point);
+	ClassDB::bind_method(D_METHOD("map_get_closest_point_owner", "map", "to_point"), &NavigationServer2D::map_get_closest_point_owner);
 
-#define FORWARD_2(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1)                         \
-	NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) {                                    \
-		return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \
-	}
+	ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer2D::map_get_links);
+	ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer2D::map_get_regions);
+	ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer2D::map_get_agents);
+	ClassDB::bind_method(D_METHOD("map_get_obstacles", "map"), &NavigationServer2D::map_get_obstacles);
 
-#define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1)                       \
-	NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1)                                      \
-			const {                                                                      \
-		return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \
-	}
+	ClassDB::bind_method(D_METHOD("map_force_update", "map"), &NavigationServer2D::map_force_update);
 
-#define FORWARD_2_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1)                     \
-	NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1)                                              \
-			const {                                                                              \
-		return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \
-	}
+	ClassDB::bind_method(D_METHOD("query_path", "parameters", "result"), &NavigationServer2D::query_path);
 
-#define FORWARD_5_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4)      \
-	NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4)                                                          \
-			const {                                                                                                                     \
-		return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \
-	}
+	ClassDB::bind_method(D_METHOD("region_create"), &NavigationServer2D::region_create);
+	ClassDB::bind_method(D_METHOD("region_set_enabled", "region", "enabled"), &NavigationServer2D::region_set_enabled);
+	ClassDB::bind_method(D_METHOD("region_get_enabled", "region"), &NavigationServer2D::region_get_enabled);
+	ClassDB::bind_method(D_METHOD("region_set_use_edge_connections", "region", "enabled"), &NavigationServer2D::region_set_use_edge_connections);
+	ClassDB::bind_method(D_METHOD("region_get_use_edge_connections", "region"), &NavigationServer2D::region_get_use_edge_connections);
+	ClassDB::bind_method(D_METHOD("region_set_enter_cost", "region", "enter_cost"), &NavigationServer2D::region_set_enter_cost);
+	ClassDB::bind_method(D_METHOD("region_get_enter_cost", "region"), &NavigationServer2D::region_get_enter_cost);
+	ClassDB::bind_method(D_METHOD("region_set_travel_cost", "region", "travel_cost"), &NavigationServer2D::region_set_travel_cost);
+	ClassDB::bind_method(D_METHOD("region_get_travel_cost", "region"), &NavigationServer2D::region_get_travel_cost);
+	ClassDB::bind_method(D_METHOD("region_set_owner_id", "region", "owner_id"), &NavigationServer2D::region_set_owner_id);
+	ClassDB::bind_method(D_METHOD("region_get_owner_id", "region"), &NavigationServer2D::region_get_owner_id);
+	ClassDB::bind_method(D_METHOD("region_owns_point", "region", "point"), &NavigationServer2D::region_owns_point);
+	ClassDB::bind_method(D_METHOD("region_set_map", "region", "map"), &NavigationServer2D::region_set_map);
+	ClassDB::bind_method(D_METHOD("region_get_map", "region"), &NavigationServer2D::region_get_map);
+	ClassDB::bind_method(D_METHOD("region_set_navigation_layers", "region", "navigation_layers"), &NavigationServer2D::region_set_navigation_layers);
+	ClassDB::bind_method(D_METHOD("region_get_navigation_layers", "region"), &NavigationServer2D::region_get_navigation_layers);
+	ClassDB::bind_method(D_METHOD("region_set_transform", "region", "transform"), &NavigationServer2D::region_set_transform);
+	ClassDB::bind_method(D_METHOD("region_set_navigation_polygon", "region", "navigation_polygon"), &NavigationServer2D::region_set_navigation_polygon);
+	ClassDB::bind_method(D_METHOD("region_get_connections_count", "region"), &NavigationServer2D::region_get_connections_count);
+	ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_start);
+	ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_end);
 
-static RID rid_to_rid(const RID d) {
-	return d;
-}
+	ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create);
+	ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map);
+	ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer2D::link_get_map);
+	ClassDB::bind_method(D_METHOD("link_set_enabled", "link", "enabled"), &NavigationServer2D::link_set_enabled);
+	ClassDB::bind_method(D_METHOD("link_get_enabled", "link"), &NavigationServer2D::link_get_enabled);
+	ClassDB::bind_method(D_METHOD("link_set_bidirectional", "link", "bidirectional"), &NavigationServer2D::link_set_bidirectional);
+	ClassDB::bind_method(D_METHOD("link_is_bidirectional", "link"), &NavigationServer2D::link_is_bidirectional);
+	ClassDB::bind_method(D_METHOD("link_set_navigation_layers", "link", "navigation_layers"), &NavigationServer2D::link_set_navigation_layers);
+	ClassDB::bind_method(D_METHOD("link_get_navigation_layers", "link"), &NavigationServer2D::link_get_navigation_layers);
+	ClassDB::bind_method(D_METHOD("link_set_start_position", "link", "position"), &NavigationServer2D::link_set_start_position);
+	ClassDB::bind_method(D_METHOD("link_get_start_position", "link"), &NavigationServer2D::link_get_start_position);
+	ClassDB::bind_method(D_METHOD("link_set_end_position", "link", "position"), &NavigationServer2D::link_set_end_position);
+	ClassDB::bind_method(D_METHOD("link_get_end_position", "link"), &NavigationServer2D::link_get_end_position);
+	ClassDB::bind_method(D_METHOD("link_set_enter_cost", "link", "enter_cost"), &NavigationServer2D::link_set_enter_cost);
+	ClassDB::bind_method(D_METHOD("link_get_enter_cost", "link"), &NavigationServer2D::link_get_enter_cost);
+	ClassDB::bind_method(D_METHOD("link_set_travel_cost", "link", "travel_cost"), &NavigationServer2D::link_set_travel_cost);
+	ClassDB::bind_method(D_METHOD("link_get_travel_cost", "link"), &NavigationServer2D::link_get_travel_cost);
+	ClassDB::bind_method(D_METHOD("link_set_owner_id", "link", "owner_id"), &NavigationServer2D::link_set_owner_id);
+	ClassDB::bind_method(D_METHOD("link_get_owner_id", "link"), &NavigationServer2D::link_get_owner_id);
 
-static bool bool_to_bool(const bool d) {
-	return d;
-}
+	ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer2D::agent_create);
+	ClassDB::bind_method(D_METHOD("agent_set_avoidance_enabled", "agent", "enabled"), &NavigationServer2D::agent_set_avoidance_enabled);
+	ClassDB::bind_method(D_METHOD("agent_get_avoidance_enabled", "agent"), &NavigationServer2D::agent_get_avoidance_enabled);
+	ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer2D::agent_set_map);
+	ClassDB::bind_method(D_METHOD("agent_get_map", "agent"), &NavigationServer2D::agent_get_map);
+	ClassDB::bind_method(D_METHOD("agent_set_paused", "agent", "paused"), &NavigationServer2D::agent_set_paused);
+	ClassDB::bind_method(D_METHOD("agent_get_paused", "agent"), &NavigationServer2D::agent_get_paused);
+	ClassDB::bind_method(D_METHOD("agent_set_neighbor_distance", "agent", "distance"), &NavigationServer2D::agent_set_neighbor_distance);
+	ClassDB::bind_method(D_METHOD("agent_set_max_neighbors", "agent", "count"), &NavigationServer2D::agent_set_max_neighbors);
+	ClassDB::bind_method(D_METHOD("agent_set_time_horizon_agents", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_agents);
+	ClassDB::bind_method(D_METHOD("agent_set_time_horizon_obstacles", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_obstacles);
+	ClassDB::bind_method(D_METHOD("agent_set_radius", "agent", "radius"), &NavigationServer2D::agent_set_radius);
+	ClassDB::bind_method(D_METHOD("agent_set_max_speed", "agent", "max_speed"), &NavigationServer2D::agent_set_max_speed);
+	ClassDB::bind_method(D_METHOD("agent_set_velocity_forced", "agent", "velocity"), &NavigationServer2D::agent_set_velocity_forced);
+	ClassDB::bind_method(D_METHOD("agent_set_velocity", "agent", "velocity"), &NavigationServer2D::agent_set_velocity);
+	ClassDB::bind_method(D_METHOD("agent_set_position", "agent", "position"), &NavigationServer2D::agent_set_position);
+	ClassDB::bind_method(D_METHOD("agent_is_map_changed", "agent"), &NavigationServer2D::agent_is_map_changed);
+	ClassDB::bind_method(D_METHOD("agent_set_avoidance_callback", "agent", "callback"), &NavigationServer2D::agent_set_avoidance_callback);
+	ClassDB::bind_method(D_METHOD("agent_set_avoidance_layers", "agent", "layers"), &NavigationServer2D::agent_set_avoidance_layers);
+	ClassDB::bind_method(D_METHOD("agent_set_avoidance_mask", "agent", "mask"), &NavigationServer2D::agent_set_avoidance_mask);
+	ClassDB::bind_method(D_METHOD("agent_set_avoidance_priority", "agent", "priority"), &NavigationServer2D::agent_set_avoidance_priority);
 
-static int int_to_int(const int d) {
-	return d;
-}
+	ClassDB::bind_method(D_METHOD("obstacle_create"), &NavigationServer2D::obstacle_create);
+	ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_enabled", "obstacle", "enabled"), &NavigationServer2D::obstacle_set_avoidance_enabled);
+	ClassDB::bind_method(D_METHOD("obstacle_get_avoidance_enabled", "obstacle"), &NavigationServer2D::obstacle_get_avoidance_enabled);
+	ClassDB::bind_method(D_METHOD("obstacle_set_map", "obstacle", "map"), &NavigationServer2D::obstacle_set_map);
+	ClassDB::bind_method(D_METHOD("obstacle_get_map", "obstacle"), &NavigationServer2D::obstacle_get_map);
+	ClassDB::bind_method(D_METHOD("obstacle_set_paused", "obstacle", "paused"), &NavigationServer2D::obstacle_set_paused);
+	ClassDB::bind_method(D_METHOD("obstacle_get_paused", "obstacle"), &NavigationServer2D::obstacle_get_paused);
+	ClassDB::bind_method(D_METHOD("obstacle_set_radius", "obstacle", "radius"), &NavigationServer2D::obstacle_set_radius);
+	ClassDB::bind_method(D_METHOD("obstacle_set_velocity", "obstacle", "velocity"), &NavigationServer2D::obstacle_set_velocity);
+	ClassDB::bind_method(D_METHOD("obstacle_set_position", "obstacle", "position"), &NavigationServer2D::obstacle_set_position);
+	ClassDB::bind_method(D_METHOD("obstacle_set_vertices", "obstacle", "vertices"), &NavigationServer2D::obstacle_set_vertices);
+	ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_layers", "obstacle", "layers"), &NavigationServer2D::obstacle_set_avoidance_layers);
 
-static uint32_t uint32_to_uint32(const uint32_t d) {
-	return d;
-}
+	ClassDB::bind_method(D_METHOD("parse_source_geometry_data", "navigation_polygon", "source_geometry_data", "root_node", "callback"), &NavigationServer2D::parse_source_geometry_data, DEFVAL(Callable()));
+	ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data", "navigation_polygon", "source_geometry_data", "callback"), &NavigationServer2D::bake_from_source_geometry_data, DEFVAL(Callable()));
+	ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data_async", "navigation_polygon", "source_geometry_data", "callback"), &NavigationServer2D::bake_from_source_geometry_data_async, DEFVAL(Callable()));
 
-static real_t real_to_real(const real_t d) {
-	return d;
-}
+	ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer2D::free);
 
-static Vector3 v2_to_v3(const Vector2 d) {
-	return Vector3(d.x, 0.0, d.y);
-}
+	ClassDB::bind_method(D_METHOD("set_debug_enabled", "enabled"), &NavigationServer2D::set_debug_enabled);
+	ClassDB::bind_method(D_METHOD("get_debug_enabled"), &NavigationServer2D::get_debug_enabled);
 
-static Vector2 v3_to_v2(const Vector3 &d) {
-	return Vector2(d.x, d.z);
-}
+	ADD_SIGNAL(MethodInfo("map_changed", PropertyInfo(Variant::RID, "map")));
 
-static Vector<Vector3> vector_v2_to_v3(const Vector<Vector2> &d) {
-	Vector<Vector3> nd;
-	nd.resize(d.size());
-	for (int i(0); i < nd.size(); i++) {
-		nd.write[i] = v2_to_v3(d[i]);
-	}
-	return nd;
+	ADD_SIGNAL(MethodInfo("navigation_debug_changed"));
 }
 
-static Vector<Vector2> vector_v3_to_v2(const Vector<Vector3> &d) {
-	Vector<Vector2> nd;
-	nd.resize(d.size());
-	for (int i(0); i < nd.size(); i++) {
-		nd.write[i] = v3_to_v2(d[i]);
-	}
-	return nd;
+NavigationServer2D *NavigationServer2D::get_singleton() {
+	return singleton;
 }
 
-static Transform3D trf2_to_trf3(const Transform2D &d) {
-	Vector3 o(v2_to_v3(d.get_origin()));
-	Basis b;
-	b.rotate(Vector3(0, -1, 0), d.get_rotation());
-	b.scale(v2_to_v3(d.get_scale()));
-	return Transform3D(b, o);
-}
+NavigationServer2D::NavigationServer2D() {
+	ERR_FAIL_COND(singleton != nullptr);
+	singleton = this;
+	ERR_FAIL_NULL_MSG(NavigationServer3D::get_singleton(), "The Navigation3D singleton should be initialized before the 2D one.");
+	NavigationServer3D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationServer2D::_emit_map_changed));
 
-static ObjectID id_to_id(const ObjectID &id) {
-	return id;
+#ifdef DEBUG_ENABLED
+	NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationServer2D::_emit_navigation_debug_changed_signal));
+#endif // DEBUG_ENABLED
 }
 
-static Callable callable_to_callable(const Callable &c) {
-	return c;
+#ifdef DEBUG_ENABLED
+void NavigationServer2D::_emit_navigation_debug_changed_signal() {
+	emit_signal(SNAME("navigation_debug_changed"));
 }
+#endif // DEBUG_ENABLED
 
-static Ref<NavigationMesh> poly_to_mesh(Ref<NavigationPolygon> d) {
-	if (d.is_valid()) {
-		return d->get_navigation_mesh();
-	} else {
-		return Ref<NavigationMesh>();
-	}
+NavigationServer2D::~NavigationServer2D() {
+	singleton = nullptr;
 }
 
 void NavigationServer2D::_emit_map_changed(RID p_map) {
@@ -363,286 +383,18 @@ bool NavigationServer2D::get_debug_navigation_avoidance_enable_obstacles_static(
 }
 #endif // DEBUG_ENABLED
 
-void NavigationServer2D::_bind_methods() {
-	ClassDB::bind_method(D_METHOD("get_maps"), &NavigationServer2D::get_maps);
+///////////////////////////////////////////////////////
 
-	ClassDB::bind_method(D_METHOD("map_create"), &NavigationServer2D::map_create);
-	ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &NavigationServer2D::map_set_active);
-	ClassDB::bind_method(D_METHOD("map_is_active", "map"), &NavigationServer2D::map_is_active);
-	ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &NavigationServer2D::map_set_cell_size);
-	ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &NavigationServer2D::map_get_cell_size);
-	ClassDB::bind_method(D_METHOD("map_set_use_edge_connections", "map", "enabled"), &NavigationServer2D::map_set_use_edge_connections);
-	ClassDB::bind_method(D_METHOD("map_get_use_edge_connections", "map"), &NavigationServer2D::map_get_use_edge_connections);
-	ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &NavigationServer2D::map_set_edge_connection_margin);
-	ClassDB::bind_method(D_METHOD("map_get_edge_connection_margin", "map"), &NavigationServer2D::map_get_edge_connection_margin);
-	ClassDB::bind_method(D_METHOD("map_set_link_connection_radius", "map", "radius"), &NavigationServer2D::map_set_link_connection_radius);
-	ClassDB::bind_method(D_METHOD("map_get_link_connection_radius", "map"), &NavigationServer2D::map_get_link_connection_radius);
-	ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize", "navigation_layers"), &NavigationServer2D::map_get_path, DEFVAL(1));
-	ClassDB::bind_method(D_METHOD("map_get_closest_point", "map", "to_point"), &NavigationServer2D::map_get_closest_point);
-	ClassDB::bind_method(D_METHOD("map_get_closest_point_owner", "map", "to_point"), &NavigationServer2D::map_get_closest_point_owner);
-
-	ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer2D::map_get_links);
-	ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer2D::map_get_regions);
-	ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer2D::map_get_agents);
-	ClassDB::bind_method(D_METHOD("map_get_obstacles", "map"), &NavigationServer2D::map_get_obstacles);
-
-	ClassDB::bind_method(D_METHOD("map_force_update", "map"), &NavigationServer2D::map_force_update);
-
-	ClassDB::bind_method(D_METHOD("query_path", "parameters", "result"), &NavigationServer2D::query_path);
-
-	ClassDB::bind_method(D_METHOD("region_create"), &NavigationServer2D::region_create);
-	ClassDB::bind_method(D_METHOD("region_set_enabled", "region", "enabled"), &NavigationServer2D::region_set_enabled);
-	ClassDB::bind_method(D_METHOD("region_get_enabled", "region"), &NavigationServer2D::region_get_enabled);
-	ClassDB::bind_method(D_METHOD("region_set_use_edge_connections", "region", "enabled"), &NavigationServer2D::region_set_use_edge_connections);
-	ClassDB::bind_method(D_METHOD("region_get_use_edge_connections", "region"), &NavigationServer2D::region_get_use_edge_connections);
-	ClassDB::bind_method(D_METHOD("region_set_enter_cost", "region", "enter_cost"), &NavigationServer2D::region_set_enter_cost);
-	ClassDB::bind_method(D_METHOD("region_get_enter_cost", "region"), &NavigationServer2D::region_get_enter_cost);
-	ClassDB::bind_method(D_METHOD("region_set_travel_cost", "region", "travel_cost"), &NavigationServer2D::region_set_travel_cost);
-	ClassDB::bind_method(D_METHOD("region_get_travel_cost", "region"), &NavigationServer2D::region_get_travel_cost);
-	ClassDB::bind_method(D_METHOD("region_set_owner_id", "region", "owner_id"), &NavigationServer2D::region_set_owner_id);
-	ClassDB::bind_method(D_METHOD("region_get_owner_id", "region"), &NavigationServer2D::region_get_owner_id);
-	ClassDB::bind_method(D_METHOD("region_owns_point", "region", "point"), &NavigationServer2D::region_owns_point);
-	ClassDB::bind_method(D_METHOD("region_set_map", "region", "map"), &NavigationServer2D::region_set_map);
-	ClassDB::bind_method(D_METHOD("region_get_map", "region"), &NavigationServer2D::region_get_map);
-	ClassDB::bind_method(D_METHOD("region_set_navigation_layers", "region", "navigation_layers"), &NavigationServer2D::region_set_navigation_layers);
-	ClassDB::bind_method(D_METHOD("region_get_navigation_layers", "region"), &NavigationServer2D::region_get_navigation_layers);
-	ClassDB::bind_method(D_METHOD("region_set_transform", "region", "transform"), &NavigationServer2D::region_set_transform);
-	ClassDB::bind_method(D_METHOD("region_set_navigation_polygon", "region", "navigation_polygon"), &NavigationServer2D::region_set_navigation_polygon);
-	ClassDB::bind_method(D_METHOD("region_get_connections_count", "region"), &NavigationServer2D::region_get_connections_count);
-	ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_start);
-	ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_end);
-
-	ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create);
-	ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map);
-	ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer2D::link_get_map);
-	ClassDB::bind_method(D_METHOD("link_set_enabled", "link", "enabled"), &NavigationServer2D::link_set_enabled);
-	ClassDB::bind_method(D_METHOD("link_get_enabled", "link"), &NavigationServer2D::link_get_enabled);
-	ClassDB::bind_method(D_METHOD("link_set_bidirectional", "link", "bidirectional"), &NavigationServer2D::link_set_bidirectional);
-	ClassDB::bind_method(D_METHOD("link_is_bidirectional", "link"), &NavigationServer2D::link_is_bidirectional);
-	ClassDB::bind_method(D_METHOD("link_set_navigation_layers", "link", "navigation_layers"), &NavigationServer2D::link_set_navigation_layers);
-	ClassDB::bind_method(D_METHOD("link_get_navigation_layers", "link"), &NavigationServer2D::link_get_navigation_layers);
-	ClassDB::bind_method(D_METHOD("link_set_start_position", "link", "position"), &NavigationServer2D::link_set_start_position);
-	ClassDB::bind_method(D_METHOD("link_get_start_position", "link"), &NavigationServer2D::link_get_start_position);
-	ClassDB::bind_method(D_METHOD("link_set_end_position", "link", "position"), &NavigationServer2D::link_set_end_position);
-	ClassDB::bind_method(D_METHOD("link_get_end_position", "link"), &NavigationServer2D::link_get_end_position);
-	ClassDB::bind_method(D_METHOD("link_set_enter_cost", "link", "enter_cost"), &NavigationServer2D::link_set_enter_cost);
-	ClassDB::bind_method(D_METHOD("link_get_enter_cost", "link"), &NavigationServer2D::link_get_enter_cost);
-	ClassDB::bind_method(D_METHOD("link_set_travel_cost", "link", "travel_cost"), &NavigationServer2D::link_set_travel_cost);
-	ClassDB::bind_method(D_METHOD("link_get_travel_cost", "link"), &NavigationServer2D::link_get_travel_cost);
-	ClassDB::bind_method(D_METHOD("link_set_owner_id", "link", "owner_id"), &NavigationServer2D::link_set_owner_id);
-	ClassDB::bind_method(D_METHOD("link_get_owner_id", "link"), &NavigationServer2D::link_get_owner_id);
+NavigationServer2DCallback NavigationServer2DManager::create_callback = nullptr;
 
-	ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer2D::agent_create);
-	ClassDB::bind_method(D_METHOD("agent_set_avoidance_enabled", "agent", "enabled"), &NavigationServer2D::agent_set_avoidance_enabled);
-	ClassDB::bind_method(D_METHOD("agent_get_avoidance_enabled", "agent"), &NavigationServer2D::agent_get_avoidance_enabled);
-	ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer2D::agent_set_map);
-	ClassDB::bind_method(D_METHOD("agent_get_map", "agent"), &NavigationServer2D::agent_get_map);
-	ClassDB::bind_method(D_METHOD("agent_set_paused", "agent", "paused"), &NavigationServer2D::agent_set_paused);
-	ClassDB::bind_method(D_METHOD("agent_get_paused", "agent"), &NavigationServer2D::agent_get_paused);
-	ClassDB::bind_method(D_METHOD("agent_set_neighbor_distance", "agent", "distance"), &NavigationServer2D::agent_set_neighbor_distance);
-	ClassDB::bind_method(D_METHOD("agent_set_max_neighbors", "agent", "count"), &NavigationServer2D::agent_set_max_neighbors);
-	ClassDB::bind_method(D_METHOD("agent_set_time_horizon_agents", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_agents);
-	ClassDB::bind_method(D_METHOD("agent_set_time_horizon_obstacles", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_obstacles);
-	ClassDB::bind_method(D_METHOD("agent_set_radius", "agent", "radius"), &NavigationServer2D::agent_set_radius);
-	ClassDB::bind_method(D_METHOD("agent_set_max_speed", "agent", "max_speed"), &NavigationServer2D::agent_set_max_speed);
-	ClassDB::bind_method(D_METHOD("agent_set_velocity_forced", "agent", "velocity"), &NavigationServer2D::agent_set_velocity_forced);
-	ClassDB::bind_method(D_METHOD("agent_set_velocity", "agent", "velocity"), &NavigationServer2D::agent_set_velocity);
-	ClassDB::bind_method(D_METHOD("agent_set_position", "agent", "position"), &NavigationServer2D::agent_set_position);
-	ClassDB::bind_method(D_METHOD("agent_is_map_changed", "agent"), &NavigationServer2D::agent_is_map_changed);
-	ClassDB::bind_method(D_METHOD("agent_set_avoidance_callback", "agent", "callback"), &NavigationServer2D::agent_set_avoidance_callback);
-	ClassDB::bind_method(D_METHOD("agent_set_avoidance_layers", "agent", "layers"), &NavigationServer2D::agent_set_avoidance_layers);
-	ClassDB::bind_method(D_METHOD("agent_set_avoidance_mask", "agent", "mask"), &NavigationServer2D::agent_set_avoidance_mask);
-	ClassDB::bind_method(D_METHOD("agent_set_avoidance_priority", "agent", "priority"), &NavigationServer2D::agent_set_avoidance_priority);
-
-	ClassDB::bind_method(D_METHOD("obstacle_create"), &NavigationServer2D::obstacle_create);
-	ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_enabled", "obstacle", "enabled"), &NavigationServer2D::obstacle_set_avoidance_enabled);
-	ClassDB::bind_method(D_METHOD("obstacle_get_avoidance_enabled", "obstacle"), &NavigationServer2D::obstacle_get_avoidance_enabled);
-	ClassDB::bind_method(D_METHOD("obstacle_set_map", "obstacle", "map"), &NavigationServer2D::obstacle_set_map);
-	ClassDB::bind_method(D_METHOD("obstacle_get_map", "obstacle"), &NavigationServer2D::obstacle_get_map);
-	ClassDB::bind_method(D_METHOD("obstacle_set_paused", "obstacle", "paused"), &NavigationServer2D::obstacle_set_paused);
-	ClassDB::bind_method(D_METHOD("obstacle_get_paused", "obstacle"), &NavigationServer2D::obstacle_get_paused);
-	ClassDB::bind_method(D_METHOD("obstacle_set_radius", "obstacle", "radius"), &NavigationServer2D::obstacle_set_radius);
-	ClassDB::bind_method(D_METHOD("obstacle_set_velocity", "obstacle", "velocity"), &NavigationServer2D::obstacle_set_velocity);
-	ClassDB::bind_method(D_METHOD("obstacle_set_position", "obstacle", "position"), &NavigationServer2D::obstacle_set_position);
-	ClassDB::bind_method(D_METHOD("obstacle_set_vertices", "obstacle", "vertices"), &NavigationServer2D::obstacle_set_vertices);
-	ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_layers", "obstacle", "layers"), &NavigationServer2D::obstacle_set_avoidance_layers);
-
-	ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer2D::free);
-
-	ClassDB::bind_method(D_METHOD("set_debug_enabled", "enabled"), &NavigationServer2D::set_debug_enabled);
-	ClassDB::bind_method(D_METHOD("get_debug_enabled"), &NavigationServer2D::get_debug_enabled);
-
-	ADD_SIGNAL(MethodInfo("map_changed", PropertyInfo(Variant::RID, "map")));
-
-	ADD_SIGNAL(MethodInfo("navigation_debug_changed"));
-}
-
-NavigationServer2D::NavigationServer2D() {
-	singleton = this;
-	ERR_FAIL_COND_MSG(!NavigationServer3D::get_singleton(), "The Navigation3D singleton should be initialized before the 2D one.");
-	NavigationServer3D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationServer2D::_emit_map_changed));
-
-#ifdef DEBUG_ENABLED
-	NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationServer2D::_emit_navigation_debug_changed_signal));
-#endif // DEBUG_ENABLED
+void NavigationServer2DManager::set_default_server(NavigationServer2DCallback p_callback) {
+	create_callback = p_callback;
 }
 
-#ifdef DEBUG_ENABLED
-void NavigationServer2D::_emit_navigation_debug_changed_signal() {
-	emit_signal(SNAME("navigation_debug_changed"));
-}
-#endif // DEBUG_ENABLED
-
-NavigationServer2D::~NavigationServer2D() {
-	singleton = nullptr;
-}
-
-TypedArray<RID> FORWARD_0_C(get_maps);
-
-TypedArray<RID> FORWARD_1_C(map_get_links, RID, p_map, rid_to_rid);
-
-TypedArray<RID> FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid);
-
-TypedArray<RID> FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid);
-
-TypedArray<RID> FORWARD_1_C(map_get_obstacles, RID, p_map, rid_to_rid);
-
-RID FORWARD_1_C(region_get_map, RID, p_region, rid_to_rid);
-
-RID FORWARD_1_C(agent_get_map, RID, p_agent, rid_to_rid);
-
-RID FORWARD_0(map_create);
-
-void FORWARD_2(map_set_active, RID, p_map, bool, p_active, rid_to_rid, bool_to_bool);
-
-bool FORWARD_1_C(map_is_active, RID, p_map, rid_to_rid);
-
-void NavigationServer2D::map_force_update(RID p_map) {
-	NavigationServer3D::get_singleton()->map_force_update(p_map);
-}
-
-void FORWARD_2(map_set_cell_size, RID, p_map, real_t, p_cell_size, rid_to_rid, real_to_real);
-real_t FORWARD_1_C(map_get_cell_size, RID, p_map, rid_to_rid);
-
-void FORWARD_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(map_get_use_edge_connections, RID, p_map, rid_to_rid);
-
-void FORWARD_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin, rid_to_rid, real_to_real);
-real_t FORWARD_1_C(map_get_edge_connection_margin, RID, p_map, rid_to_rid);
-
-void FORWARD_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius, rid_to_rid, real_to_real);
-real_t FORWARD_1_C(map_get_link_connection_radius, RID, p_map, rid_to_rid);
-
-Vector<Vector2> FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, uint32_t, p_layers, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool, uint32_to_uint32);
-
-Vector2 FORWARD_2_R_C(v3_to_v2, map_get_closest_point, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
-RID FORWARD_2_C(map_get_closest_point_owner, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
-
-RID FORWARD_0(region_create);
-
-void FORWARD_2(region_set_enabled, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(region_get_enabled, RID, p_region, rid_to_rid);
-void FORWARD_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(region_get_use_edge_connections, RID, p_region, rid_to_rid);
-
-void FORWARD_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost, rid_to_rid, real_to_real);
-real_t FORWARD_1_C(region_get_enter_cost, RID, p_region, rid_to_rid);
-void FORWARD_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost, rid_to_rid, real_to_real);
-real_t FORWARD_1_C(region_get_travel_cost, RID, p_region, rid_to_rid);
-void FORWARD_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id, rid_to_rid, id_to_id);
-ObjectID FORWARD_1_C(region_get_owner_id, RID, p_region, rid_to_rid);
-bool FORWARD_2_C(region_owns_point, RID, p_region, const Vector2 &, p_point, rid_to_rid, v2_to_v3);
-
-void FORWARD_2(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid);
-void FORWARD_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32);
-uint32_t FORWARD_1_C(region_get_navigation_layers, RID, p_region, rid_to_rid);
-void FORWARD_2(region_set_transform, RID, p_region, Transform2D, p_transform, rid_to_rid, trf2_to_trf3);
-
-void NavigationServer2D::region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) {
-	NavigationServer3D::get_singleton()->region_set_navigation_mesh(p_region, poly_to_mesh(p_navigation_polygon));
-}
-
-int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid);
-Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
-Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
-
-RID FORWARD_0(link_create);
-
-void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid);
-RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid);
-void FORWARD_2(link_set_enabled, RID, p_link, bool, p_enabled, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(link_get_enabled, RID, p_link, rid_to_rid);
-void FORWARD_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid);
-void FORWARD_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32);
-uint32_t FORWARD_1_C(link_get_navigation_layers, RID, p_link, rid_to_rid);
-void FORWARD_2(link_set_start_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3);
-Vector2 FORWARD_1_R_C(v3_to_v2, link_get_start_position, RID, p_link, rid_to_rid);
-void FORWARD_2(link_set_end_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3);
-Vector2 FORWARD_1_R_C(v3_to_v2, link_get_end_position, RID, p_link, rid_to_rid);
-void FORWARD_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost, rid_to_rid, real_to_real);
-real_t FORWARD_1_C(link_get_enter_cost, RID, p_link, rid_to_rid);
-void FORWARD_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost, rid_to_rid, real_to_real);
-real_t FORWARD_1_C(link_get_travel_cost, RID, p_link, rid_to_rid);
-void FORWARD_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id, rid_to_rid, id_to_id);
-ObjectID FORWARD_1_C(link_get_owner_id, RID, p_link, rid_to_rid);
-
-RID NavigationServer2D::agent_create() {
-	RID agent = NavigationServer3D::get_singleton()->agent_create();
-	return agent;
-}
-
-void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(agent_get_avoidance_enabled, RID, p_agent, rid_to_rid);
-void FORWARD_2(agent_set_map, RID, p_agent, RID, p_map, rid_to_rid, rid_to_rid);
-void FORWARD_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_dist, rid_to_rid, real_to_real);
-void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int);
-void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real);
-void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real);
-void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real);
-void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real);
-void FORWARD_2(agent_set_velocity_forced, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3);
-void FORWARD_2(agent_set_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3);
-void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3);
-
-bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid);
-void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid);
+NavigationServer2D *NavigationServer2DManager::new_default_server() {
+	if (create_callback == nullptr) {
+		return nullptr;
+	}
 
-void FORWARD_1(free, RID, p_object, rid_to_rid);
-void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable);
-
-void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32);
-void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32);
-void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real);
-
-RID NavigationServer2D::obstacle_create() {
-	RID obstacle = NavigationServer3D::get_singleton()->obstacle_create();
-	return obstacle;
-}
-void FORWARD_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(obstacle_get_avoidance_enabled, RID, p_obstacle, rid_to_rid);
-void FORWARD_2(obstacle_set_map, RID, p_obstacle, RID, p_map, rid_to_rid, rid_to_rid);
-RID FORWARD_1_C(obstacle_get_map, RID, p_obstacle, rid_to_rid);
-void FORWARD_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused, rid_to_rid, bool_to_bool);
-bool FORWARD_1_C(obstacle_get_paused, RID, p_obstacle, rid_to_rid);
-void FORWARD_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius, rid_to_rid, real_to_real);
-void FORWARD_2(obstacle_set_velocity, RID, p_obstacle, Vector2, p_velocity, rid_to_rid, v2_to_v3);
-void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3);
-void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32);
-
-void NavigationServer2D::obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) {
-	NavigationServer3D::get_singleton()->obstacle_set_vertices(p_obstacle, vector_v2_to_v3(p_vertices));
-}
-
-void NavigationServer2D::query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const {
-	ERR_FAIL_COND(!p_query_parameters.is_valid());
-	ERR_FAIL_COND(!p_query_result.is_valid());
-
-	const NavigationUtilities::PathQueryResult _query_result = NavigationServer3D::get_singleton()->_query_path(p_query_parameters->get_parameters());
-
-	p_query_result->set_path(vector_v3_to_v2(_query_result.path));
-	p_query_result->set_path_types(_query_result.path_types);
-	p_query_result->set_path_rids(_query_result.path_rids);
-	p_query_result->set_path_owner_ids(_query_result.path_owner_ids);
+	return create_callback();
 }

+ 121 - 97
servers/navigation_server_2d.h

@@ -34,10 +34,15 @@
 #include "core/object/class_db.h"
 #include "core/templates/rid.h"
 
+#include "scene/resources/navigation_mesh_source_geometry_data_2d.h"
 #include "scene/resources/navigation_polygon.h"
 #include "servers/navigation/navigation_path_query_parameters_2d.h"
 #include "servers/navigation/navigation_path_query_result_2d.h"
 
+#ifdef CLIPPER2_ENABLED
+class NavMeshGenerator2D;
+#endif // CLIPPER2_ENABLED
+
 // This server exposes the `NavigationServer3D` features in the 2D world.
 class NavigationServer2D : public Object {
 	GDCLASS(NavigationServer2D, Object);
@@ -51,145 +56,145 @@ protected:
 
 public:
 	/// Thread safe, can be used across many threads.
-	static NavigationServer2D *get_singleton() { return singleton; }
+	static NavigationServer2D *get_singleton();
 
-	virtual TypedArray<RID> get_maps() const;
+	virtual TypedArray<RID> get_maps() const = 0;
 
 	/// Create a new map.
-	virtual RID map_create();
+	virtual RID map_create() = 0;
 
 	/// Set map active.
-	virtual void map_set_active(RID p_map, bool p_active);
+	virtual void map_set_active(RID p_map, bool p_active) = 0;
 
 	/// Returns true if the map is active.
-	virtual bool map_is_active(RID p_map) const;
+	virtual bool map_is_active(RID p_map) const = 0;
 
 	/// Set the map cell size used to weld the navigation mesh polygons.
-	virtual void map_set_cell_size(RID p_map, real_t p_cell_size);
+	virtual void map_set_cell_size(RID p_map, real_t p_cell_size) = 0;
 
 	/// Returns the map cell size.
-	virtual real_t map_get_cell_size(RID p_map) const;
+	virtual real_t map_get_cell_size(RID p_map) const = 0;
 
-	virtual void map_set_use_edge_connections(RID p_map, bool p_enabled);
-	virtual bool map_get_use_edge_connections(RID p_map) const;
+	virtual void map_set_use_edge_connections(RID p_map, bool p_enabled) = 0;
+	virtual bool map_get_use_edge_connections(RID p_map) const = 0;
 
 	/// Set the map edge connection margin used to weld the compatible region edges.
-	virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin);
+	virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) = 0;
 
 	/// Returns the edge connection margin of this map.
-	virtual real_t map_get_edge_connection_margin(RID p_map) const;
+	virtual real_t map_get_edge_connection_margin(RID p_map) const = 0;
 
 	/// Set the map link connection radius used to attach links to the nav mesh.
-	virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius);
+	virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) = 0;
 
 	/// Returns the link connection radius of this map.
-	virtual real_t map_get_link_connection_radius(RID p_map) const;
+	virtual real_t map_get_link_connection_radius(RID p_map) const = 0;
 
 	/// Returns the navigation path to reach the destination from the origin.
-	virtual Vector<Vector2> map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const;
+	virtual Vector<Vector2> map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const = 0;
 
-	virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const;
-	virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const;
+	virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const = 0;
+	virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const = 0;
 
-	virtual TypedArray<RID> map_get_links(RID p_map) const;
-	virtual TypedArray<RID> map_get_regions(RID p_map) const;
-	virtual TypedArray<RID> map_get_agents(RID p_map) const;
-	virtual TypedArray<RID> map_get_obstacles(RID p_map) const;
+	virtual TypedArray<RID> map_get_links(RID p_map) const = 0;
+	virtual TypedArray<RID> map_get_regions(RID p_map) const = 0;
+	virtual TypedArray<RID> map_get_agents(RID p_map) const = 0;
+	virtual TypedArray<RID> map_get_obstacles(RID p_map) const = 0;
 
-	virtual void map_force_update(RID p_map);
+	virtual void map_force_update(RID p_map) = 0;
 
 	/// Creates a new region.
-	virtual RID region_create();
+	virtual RID region_create() = 0;
 
-	virtual void region_set_enabled(RID p_region, bool p_enabled);
-	virtual bool region_get_enabled(RID p_region) const;
+	virtual void region_set_enabled(RID p_region, bool p_enabled) = 0;
+	virtual bool region_get_enabled(RID p_region) const = 0;
 
-	virtual void region_set_use_edge_connections(RID p_region, bool p_enabled);
-	virtual bool region_get_use_edge_connections(RID p_region) const;
+	virtual void region_set_use_edge_connections(RID p_region, bool p_enabled) = 0;
+	virtual bool region_get_use_edge_connections(RID p_region) const = 0;
 
 	/// Set the enter_cost of a region
-	virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost);
-	virtual real_t region_get_enter_cost(RID p_region) const;
+	virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) = 0;
+	virtual real_t region_get_enter_cost(RID p_region) const = 0;
 
 	/// Set the travel_cost of a region
-	virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost);
-	virtual real_t region_get_travel_cost(RID p_region) const;
+	virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) = 0;
+	virtual real_t region_get_travel_cost(RID p_region) const = 0;
 
 	/// Set the node which manages this region.
-	virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id);
-	virtual ObjectID region_get_owner_id(RID p_region) const;
+	virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id) = 0;
+	virtual ObjectID region_get_owner_id(RID p_region) const = 0;
 
-	virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const;
+	virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const = 0;
 
 	/// Set the map of this region.
-	virtual void region_set_map(RID p_region, RID p_map);
-	virtual RID region_get_map(RID p_region) const;
+	virtual void region_set_map(RID p_region, RID p_map) = 0;
+	virtual RID region_get_map(RID p_region) const = 0;
 
 	/// Set the region's layers
-	virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers);
-	virtual uint32_t region_get_navigation_layers(RID p_region) const;
+	virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) = 0;
+	virtual uint32_t region_get_navigation_layers(RID p_region) const = 0;
 
 	/// Set the global transformation of this region.
-	virtual void region_set_transform(RID p_region, Transform2D p_transform);
+	virtual void region_set_transform(RID p_region, Transform2D p_transform) = 0;
 
 	/// Set the navigation poly of this region.
-	virtual void region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon);
+	virtual void region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) = 0;
 
 	/// Get a list of a region's connection to other regions.
-	virtual int region_get_connections_count(RID p_region) const;
-	virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const;
-	virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const;
+	virtual int region_get_connections_count(RID p_region) const = 0;
+	virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const = 0;
+	virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const = 0;
 
 	/// Creates a new link between positions in the nav map.
-	virtual RID link_create();
+	virtual RID link_create() = 0;
 
 	/// Set the map of this link.
-	virtual void link_set_map(RID p_link, RID p_map);
-	virtual RID link_get_map(RID p_link) const;
+	virtual void link_set_map(RID p_link, RID p_map) = 0;
+	virtual RID link_get_map(RID p_link) const = 0;
 
-	virtual void link_set_enabled(RID p_link, bool p_enabled);
-	virtual bool link_get_enabled(RID p_link) const;
+	virtual void link_set_enabled(RID p_link, bool p_enabled) = 0;
+	virtual bool link_get_enabled(RID p_link) const = 0;
 
 	/// Set whether this link travels in both directions.
-	virtual void link_set_bidirectional(RID p_link, bool p_bidirectional);
-	virtual bool link_is_bidirectional(RID p_link) const;
+	virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) = 0;
+	virtual bool link_is_bidirectional(RID p_link) const = 0;
 
 	/// Set the link's layers.
-	virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers);
-	virtual uint32_t link_get_navigation_layers(RID p_link) const;
+	virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) = 0;
+	virtual uint32_t link_get_navigation_layers(RID p_link) const = 0;
 
 	/// Set the start position of the link.
-	virtual void link_set_start_position(RID p_link, Vector2 p_position);
-	virtual Vector2 link_get_start_position(RID p_link) const;
+	virtual void link_set_start_position(RID p_link, Vector2 p_position) = 0;
+	virtual Vector2 link_get_start_position(RID p_link) const = 0;
 
 	/// Set the end position of the link.
-	virtual void link_set_end_position(RID p_link, Vector2 p_position);
-	virtual Vector2 link_get_end_position(RID p_link) const;
+	virtual void link_set_end_position(RID p_link, Vector2 p_position) = 0;
+	virtual Vector2 link_get_end_position(RID p_link) const = 0;
 
 	/// Set the enter cost of the link.
-	virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost);
-	virtual real_t link_get_enter_cost(RID p_link) const;
+	virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) = 0;
+	virtual real_t link_get_enter_cost(RID p_link) const = 0;
 
 	/// Set the travel cost of the link.
-	virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost);
-	virtual real_t link_get_travel_cost(RID p_link) const;
+	virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) = 0;
+	virtual real_t link_get_travel_cost(RID p_link) const = 0;
 
 	/// Set the node which manages this link.
-	virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id);
-	virtual ObjectID link_get_owner_id(RID p_link) const;
+	virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id) = 0;
+	virtual ObjectID link_get_owner_id(RID p_link) const = 0;
 
 	/// Creates the agent.
-	virtual RID agent_create();
+	virtual RID agent_create() = 0;
 
 	/// Put the agent in the map.
-	virtual void agent_set_map(RID p_agent, RID p_map);
-	virtual RID agent_get_map(RID p_agent) const;
+	virtual void agent_set_map(RID p_agent, RID p_map) = 0;
+	virtual RID agent_get_map(RID p_agent) const = 0;
 
-	virtual void agent_set_paused(RID p_agent, bool p_paused);
-	virtual bool agent_get_paused(RID p_agent) const;
+	virtual void agent_set_paused(RID p_agent, bool p_paused) = 0;
+	virtual bool agent_get_paused(RID p_agent) const = 0;
 
-	virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled);
-	virtual bool agent_get_avoidance_enabled(RID p_agent) const;
+	virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) = 0;
+	virtual bool agent_get_avoidance_enabled(RID p_agent) const = 0;
 
 	/// The maximum distance (center point to
 	/// center point) to other agents this agent
@@ -198,7 +203,7 @@ public:
 	/// time of the simulation. If the number is too
 	/// low, the simulation will not be safe.
 	/// Must be non-negative.
-	virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance);
+	virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance) = 0;
 
 	/// The maximum number of other agents this
 	/// agent takes into account in the navigation.
@@ -206,7 +211,7 @@ public:
 	/// running time of the simulation. If the
 	/// number is too low, the simulation will not
 	/// be safe.
-	virtual void agent_set_max_neighbors(RID p_agent, int p_count);
+	virtual void agent_set_max_neighbors(RID p_agent, int p_count) = 0;
 
 	/// The minimal amount of time for which this
 	/// agent's velocities that are computed by the
@@ -217,59 +222,67 @@ public:
 	/// agent has in choosing its velocities.
 	/// Must be positive.
 
-	virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon);
-	virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon);
+	virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) = 0;
+	virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) = 0;
 
 	/// The radius of this agent.
 	/// Must be non-negative.
-	virtual void agent_set_radius(RID p_agent, real_t p_radius);
+	virtual void agent_set_radius(RID p_agent, real_t p_radius) = 0;
 
 	/// The maximum speed of this agent.
 	/// Must be non-negative.
-	virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed);
+	virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed) = 0;
 
 	/// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly
-	virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity);
+	virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) = 0;
 
 	/// The wanted velocity for the agent as a "suggestion" to the avoidance simulation.
 	/// The simulation will try to fulfill this velocity wish if possible but may change the velocity depending on other agent's and obstacles'.
-	virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity);
+	virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity) = 0;
 
 	/// Position of the agent in world space.
-	virtual void agent_set_position(RID p_agent, Vector2 p_position);
+	virtual void agent_set_position(RID p_agent, Vector2 p_position) = 0;
 
 	/// Returns true if the map got changed the previous frame.
-	virtual bool agent_is_map_changed(RID p_agent) const;
+	virtual bool agent_is_map_changed(RID p_agent) const = 0;
 
 	/// Callback called at the end of the RVO process
-	virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback);
+	virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback) = 0;
 
-	virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers);
-	virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask);
-	virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority);
+	virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) = 0;
+	virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) = 0;
+	virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) = 0;
 
 	/// Creates the obstacle.
-	virtual RID obstacle_create();
-	virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled);
-	virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const;
-	virtual void obstacle_set_map(RID p_obstacle, RID p_map);
-	virtual RID obstacle_get_map(RID p_obstacle) const;
-	virtual void obstacle_set_paused(RID p_obstacle, bool p_paused);
-	virtual bool obstacle_get_paused(RID p_obstacle) const;
-	virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius);
-	virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity);
-	virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position);
-	virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices);
-	virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers);
+	virtual RID obstacle_create() = 0;
+	virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) = 0;
+	virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const = 0;
+	virtual void obstacle_set_map(RID p_obstacle, RID p_map) = 0;
+	virtual RID obstacle_get_map(RID p_obstacle) const = 0;
+	virtual void obstacle_set_paused(RID p_obstacle, bool p_paused) = 0;
+	virtual bool obstacle_get_paused(RID p_obstacle) const = 0;
+	virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius) = 0;
+	virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) = 0;
+	virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position) = 0;
+	virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) = 0;
+	virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) = 0;
 
 	/// Returns a customized navigation path using a query parameters object
-	virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const;
+	virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const = 0;
+
+	virtual void init() = 0;
+	virtual void sync() = 0;
+	virtual void finish() = 0;
 
 	/// Destroy the `RID`
-	virtual void free(RID p_object);
+	virtual void free(RID p_object) = 0;
+
+	virtual void parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) = 0;
+	virtual void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0;
+	virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0;
 
 	NavigationServer2D();
-	virtual ~NavigationServer2D();
+	~NavigationServer2D() override;
 
 	void set_debug_enabled(bool p_enabled);
 	bool get_debug_enabled() const;
@@ -354,4 +367,15 @@ private:
 #endif // DEBUG_ENABLED
 };
 
+typedef NavigationServer2D *(*NavigationServer2DCallback)();
+
+/// Manager used for the server singleton registration
+class NavigationServer2DManager {
+	static NavigationServer2DCallback create_callback;
+
+public:
+	static void set_default_server(NavigationServer2DCallback p_callback);
+	static NavigationServer2D *new_default_server();
+};
+
 #endif // NAVIGATION_SERVER_2D_H

+ 155 - 0
servers/navigation_server_2d_dummy.h

@@ -0,0 +1,155 @@
+/**************************************************************************/
+/*  navigation_server_2d_dummy.h                                          */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef NAVIGATION_SERVER_2D_DUMMY_H
+#define NAVIGATION_SERVER_2D_DUMMY_H
+
+#include "servers/navigation_server_2d.h"
+
+class NavigationServer2DDummy : public NavigationServer2D {
+	GDCLASS(NavigationServer2DDummy, NavigationServer2D);
+
+public:
+	TypedArray<RID> get_maps() const override { return TypedArray<RID>(); }
+
+	RID map_create() override { return RID(); }
+	void map_set_active(RID p_map, bool p_active) override {}
+	bool map_is_active(RID p_map) const override { return false; }
+	void map_set_cell_size(RID p_map, real_t p_cell_size) override {}
+	real_t map_get_cell_size(RID p_map) const override { return 0; }
+	void map_set_use_edge_connections(RID p_map, bool p_enabled) override {}
+	bool map_get_use_edge_connections(RID p_map) const override { return false; }
+	void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) override {}
+	real_t map_get_edge_connection_margin(RID p_map) const override { return 0; }
+	void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) override {}
+	real_t map_get_link_connection_radius(RID p_map) const override { return 0; }
+	Vector<Vector2> map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const override { return Vector<Vector2>(); }
+	Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const override { return Vector2(); }
+	RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const override { return RID(); }
+	TypedArray<RID> map_get_links(RID p_map) const override { return TypedArray<RID>(); }
+	TypedArray<RID> map_get_regions(RID p_map) const override { return TypedArray<RID>(); }
+	TypedArray<RID> map_get_agents(RID p_map) const override { return TypedArray<RID>(); }
+	TypedArray<RID> map_get_obstacles(RID p_map) const override { return TypedArray<RID>(); }
+	void map_force_update(RID p_map) override {}
+
+	RID region_create() override { return RID(); }
+	void region_set_enabled(RID p_region, bool p_enabled) override {}
+	bool region_get_enabled(RID p_region) const override { return false; }
+	void region_set_use_edge_connections(RID p_region, bool p_enabled) override {}
+	bool region_get_use_edge_connections(RID p_region) const override { return false; }
+	void region_set_enter_cost(RID p_region, real_t p_enter_cost) override {}
+	real_t region_get_enter_cost(RID p_region) const override { return 0; }
+	void region_set_travel_cost(RID p_region, real_t p_travel_cost) override {}
+	real_t region_get_travel_cost(RID p_region) const override { return 0; }
+	void region_set_owner_id(RID p_region, ObjectID p_owner_id) override {}
+	ObjectID region_get_owner_id(RID p_region) const override { return ObjectID(); }
+	bool region_owns_point(RID p_region, const Vector2 &p_point) const override { return false; }
+	void region_set_map(RID p_region, RID p_map) override {}
+	RID region_get_map(RID p_region) const override { return RID(); }
+	void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) override {}
+	uint32_t region_get_navigation_layers(RID p_region) const override { return 0; }
+	void region_set_transform(RID p_region, Transform2D p_transform) override {}
+	void region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) override {}
+	int region_get_connections_count(RID p_region) const override { return 0; }
+	Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override { return Vector2(); }
+	Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override { return Vector2(); }
+
+	RID link_create() override { return RID(); }
+	void link_set_map(RID p_link, RID p_map) override {}
+	RID link_get_map(RID p_link) const override { return RID(); }
+	void link_set_enabled(RID p_link, bool p_enabled) override {}
+	bool link_get_enabled(RID p_link) const override { return false; }
+	void link_set_bidirectional(RID p_link, bool p_bidirectional) override {}
+	bool link_is_bidirectional(RID p_link) const override { return false; }
+	void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) override {}
+	uint32_t link_get_navigation_layers(RID p_link) const override { return 0; }
+	void link_set_start_position(RID p_link, Vector2 p_position) override {}
+	Vector2 link_get_start_position(RID p_link) const override { return Vector2(); }
+	void link_set_end_position(RID p_link, Vector2 p_position) override {}
+	Vector2 link_get_end_position(RID p_link) const override { return Vector2(); }
+	void link_set_enter_cost(RID p_link, real_t p_enter_cost) override {}
+	real_t link_get_enter_cost(RID p_link) const override { return 0; }
+	void link_set_travel_cost(RID p_link, real_t p_travel_cost) override {}
+	real_t link_get_travel_cost(RID p_link) const override { return 0; }
+	void link_set_owner_id(RID p_link, ObjectID p_owner_id) override {}
+	ObjectID link_get_owner_id(RID p_link) const override { return ObjectID(); }
+
+	RID agent_create() override { return RID(); }
+	void agent_set_map(RID p_agent, RID p_map) override {}
+	RID agent_get_map(RID p_agent) const override { return RID(); }
+	void agent_set_paused(RID p_agent, bool p_paused) override {}
+	bool agent_get_paused(RID p_agent) const override { return false; }
+	void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) override {}
+	bool agent_get_avoidance_enabled(RID p_agent) const override { return false; }
+	void agent_set_neighbor_distance(RID p_agent, real_t p_distance) override {}
+	void agent_set_max_neighbors(RID p_agent, int p_count) override {}
+	void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) override {}
+	void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override {}
+	void agent_set_radius(RID p_agent, real_t p_radius) override {}
+	void agent_set_max_speed(RID p_agent, real_t p_max_speed) override {}
+	void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) override {}
+	void agent_set_velocity(RID p_agent, Vector2 p_velocity) override {}
+	void agent_set_position(RID p_agent, Vector2 p_position) override {}
+	bool agent_is_map_changed(RID p_agent) const override { return false; }
+	void agent_set_avoidance_callback(RID p_agent, Callable p_callback) override {}
+	void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override {}
+	void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override {}
+	void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override {}
+
+	RID obstacle_create() override { return RID(); }
+	void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) override {}
+	bool obstacle_get_avoidance_enabled(RID p_obstacle) const override { return false; }
+	void obstacle_set_map(RID p_obstacle, RID p_map) override {}
+	RID obstacle_get_map(RID p_obstacle) const override { return RID(); }
+	void obstacle_set_paused(RID p_obstacle, bool p_paused) override {}
+	bool obstacle_get_paused(RID p_obstacle) const override { return false; }
+	void obstacle_set_radius(RID p_obstacle, real_t p_radius) override {}
+	void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) override {}
+	void obstacle_set_position(RID p_obstacle, Vector2 p_position) override {}
+	void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) override {}
+	void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override {}
+
+	void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const override {}
+
+	void init() override {}
+	void sync() override {}
+	void finish() override {}
+
+	void free(RID p_object) override {}
+
+	void parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override {}
+	void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {}
+	void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {}
+
+	void set_debug_enabled(bool p_enabled) {}
+	bool get_debug_enabled() const { return false; }
+};
+
+#endif // NAVIGATION_SERVER_2D_DUMMY_H

+ 1 - 0
servers/navigation_server_3d.h

@@ -302,6 +302,7 @@ public:
 	/// Note: This function is not thread safe.
 	virtual void process(real_t delta_time) = 0;
 	virtual void init() = 0;
+	virtual void sync() = 0;
 	virtual void finish() = 0;
 
 	/// Returns a customized navigation path using a query parameters object

+ 1 - 0
servers/navigation_server_3d_dummy.h

@@ -152,6 +152,7 @@ public:
 	void set_active(bool p_active) override {}
 	void process(real_t delta_time) override {}
 	void init() override {}
+	void sync() override {}
 	void finish() override {}
 	NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override { return NavigationUtilities::PathQueryResult(); }
 	int get_process_info(ProcessInfo p_info) const override { return 0; }

+ 2 - 2
tests/test_main.cpp

@@ -252,7 +252,7 @@ struct GodotTestCaseListener : public doctest::IReporter {
 
 			ERR_PRINT_OFF;
 			navigation_server_3d = NavigationServer3DManager::new_default_server();
-			navigation_server_2d = memnew(NavigationServer2D);
+			navigation_server_2d = NavigationServer2DManager::new_default_server();
 			ERR_PRINT_ON;
 
 			memnew(InputMap);
@@ -278,7 +278,7 @@ struct GodotTestCaseListener : public doctest::IReporter {
 		if (suite_name.find("[Navigation]") != -1 && navigation_server_2d == nullptr && navigation_server_3d == nullptr) {
 			ERR_PRINT_OFF;
 			navigation_server_3d = NavigationServer3DManager::new_default_server();
-			navigation_server_2d = memnew(NavigationServer2D);
+			navigation_server_2d = NavigationServer2DManager::new_default_server();
 			ERR_PRINT_ON;
 			return;
 		}

+ 12 - 0
thirdparty/README.md

@@ -91,6 +91,18 @@ Files extracted from upstream source:
 - License: MPL 2.0
 
 
+## clipper2
+
+- Upstream: https://github.com/AngusJohnson/Clipper2
+- Version: 1.2.2  (756c5079aacab5837e812a143c59dc48a09f22e7, 2023)
+- License: Boost Software License 1.0
+
+Files extracted from upstream source:
+
+- `CPP/Clipper2Lib` folder
+- `LICENSE`
+
+
 ## cvtt
 
 - Upstream: https://github.com/elasota/ConvectionKernels

+ 23 - 0
thirdparty/clipper2/LICENSE

@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.

+ 35 - 0
thirdparty/clipper2/clipper2-exceptions.patch

@@ -0,0 +1,35 @@
+diff --git a/thirdparty/clipper2/include/clipper2/clipper.core.h b/thirdparty/clipper2/include/clipper2/clipper.core.h
+index c7522cb900..086d1b659c 100644
+--- a/thirdparty/clipper2/include/clipper2/clipper.core.h
++++ b/thirdparty/clipper2/include/clipper2/clipper.core.h
+@@ -20,6 +20,8 @@
+ #include <climits>
+ #include <numeric>
+ 
++#define CLIPPER2_THROW(exception) std::abort()
++
+ namespace Clipper2Lib
+ {
+ 
+@@ -65,16 +67,16 @@ namespace Clipper2Lib
+     switch (error_code)
+     {
+     case precision_error_i:
+-      throw Clipper2Exception(precision_error);
++      CLIPPER2_THROW(Clipper2Exception(precision_error));
+     case scale_error_i:
+-      throw Clipper2Exception(scale_error);
++      CLIPPER2_THROW(Clipper2Exception(scale_error));
+     case non_pair_error_i:
+-      throw Clipper2Exception(non_pair_error);
++      CLIPPER2_THROW(Clipper2Exception(non_pair_error));
+     case range_error_i:
+-      throw Clipper2Exception(range_error);
++      CLIPPER2_THROW(Clipper2Exception(range_error));
+     }
+ #else
+-    ++error_code; // only to stop compiler warning
++    if(error_code) {}; // only to stop compiler 'parameter not used' warning
+ #endif
+   }
+ 

+ 846 - 0
thirdparty/clipper2/include/clipper2/clipper.core.h

@@ -0,0 +1,846 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  22 March 2023                                                   *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* 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 <cstdint>
+#include <cstdlib>
+#include <cmath>
+#include <vector>
+#include <string>
+#include <iostream>
+#include <algorithm>
+#include <climits>
+#include <numeric>
+
+#define CLIPPER2_THROW(exception) std::abort()
+
+namespace Clipper2Lib
+{
+
+#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS)
+
+  class Clipper2Exception : public std::exception {
+  public:
+    explicit Clipper2Exception(const char* description) :
+      m_descr(description) {}
+    virtual const char* what() const throw() override { return m_descr.c_str(); }
+  private:
+    std::string m_descr;
+  };
+
+  static const char* precision_error =
+    "Precision exceeds the permitted range";
+  static const char* range_error =
+    "Values exceed permitted range";
+  static const char* scale_error =
+    "Invalid scale (either 0 or too large)";
+  static const char* non_pair_error =
+    "There must be 2 values for each coordinate";
+#endif
+
+  // error codes (2^n)
+  const int precision_error_i   = 1; // non-fatal
+  const int scale_error_i       = 2; // non-fatal 
+  const int non_pair_error_i    = 4; // non-fatal 
+  const int range_error_i = 64;
+
+  static const double PI = 3.141592653589793238;
+  static const int64_t MAX_COORD = INT64_MAX >> 2;
+  static const int64_t MIN_COORD = -MAX_COORD;
+  static const int64_t INVALID = INT64_MAX;
+  const double max_coord = static_cast<double>(MAX_COORD);
+  const double min_coord = static_cast<double>(MIN_COORD);
+
+  static const double MAX_DBL = (std::numeric_limits<double>::max)();
+
+  static void DoError(int error_code)
+  {
+#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS)
+    switch (error_code)
+    {
+    case precision_error_i:
+      CLIPPER2_THROW(Clipper2Exception(precision_error));
+    case scale_error_i:
+      CLIPPER2_THROW(Clipper2Exception(scale_error));
+    case non_pair_error_i:
+      CLIPPER2_THROW(Clipper2Exception(non_pair_error));
+    case range_error_i:
+      CLIPPER2_THROW(Clipper2Exception(range_error));
+    }
+#else
+    if(error_code) {}; // only to stop compiler 'parameter not used' warning
+#endif
+  }
+
+  //By far the most widely used filling rules for polygons are EvenOdd
+  //and NonZero, sometimes called Alternate and Winding respectively.
+  //https://en.wikipedia.org/wiki/Nonzero-rule
+  enum class FillRule { EvenOdd, NonZero, Positive, Negative };
+
+  // Point ------------------------------------------------------------------------
+
+  template <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>;
+
+  // 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) {}
+
+    Rect(bool is_valid)
+    {
+      if (is_valid)
+      {
+        left = right = top = bottom = 0;
+      }
+      else
+      {
+        left = top = std::numeric_limits<T>::max();
+        right = bottom = -std::numeric_limits<int64_t>::max();
+      }
+    }
+
+    T Width() const { return right - left; }
+    T Height() const { return bottom - top; }
+    void Width(T width) { right = left + width; }
+    void Height(T height) { bottom = top + height; }
+
+    Point<T> MidPoint() const
+    {
+      return Point<T>((left + right) / 2, (top + bottom) / 2);
+    }
+
+    Path<T> AsPath() const
+    {
+      Path<T> result;
+      result.reserve(4);
+      result.push_back(Point<T>(left, top));
+      result.push_back(Point<T>(right, top));
+      result.push_back(Point<T>(right, bottom));
+      result.push_back(Point<T>(left, bottom));
+      return result;
+    }
+
+    bool Contains(const Point<T>& pt) const
+    {
+      return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom;
+    }
+
+    bool Contains(const Rect<T>& rec) const
+    {
+      return rec.left >= left && rec.right <= right &&
+        rec.top >= top && rec.bottom <= bottom;
+    }
+
+    void Scale(double scale) {
+      left *= scale;
+      top *= scale;
+      right *= scale;
+      bottom *= scale;
+    }
+
+    bool IsEmpty() const { return bottom <= top || right <= left; };
+
+    bool Intersects(const Rect<T>& rec) const
+    {
+      return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) &&
+        ((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom));
+    };
+
+    friend std::ostream& operator<<(std::ostream& os, const Rect<T>& rect) {
+      os << "("
+        << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom
+        << ")";
+      return os;
+    }
+  };
+
+  template <typename T1, typename T2>
+  inline Rect<T1> ScaleRect(const Rect<T2>& rect, double scale)
+  {
+    Rect<T1> result;
+
+    if constexpr (std::numeric_limits<T1>::is_integer &&
+      !std::numeric_limits<T2>::is_integer)
+    {
+      result.left = static_cast<T1>(std::round(rect.left * scale));
+      result.top = static_cast<T1>(std::round(rect.top * scale));
+      result.right = static_cast<T1>(std::round(rect.right * scale));
+      result.bottom = static_cast<T1>(std::round(rect.bottom * scale));
+    }
+    else
+    {
+      result.left = rect.left * scale;
+      result.top = rect.top * scale;
+      result.right = rect.right * scale;
+      result.bottom = rect.bottom * scale;
+    }
+    return result;
+  }
+
+  static const Rect64 MaxInvalidRect64 = Rect64(
+    INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN);
+  static const RectD MaxInvalidRectD = RectD(
+    MAX_DBL, MAX_DBL, -MAX_DBL, -MAX_DBL);
+
+  template <typename T>
+  Rect<T> GetBounds(const Path<T>& path)
+  {
+    auto xmin = std::numeric_limits<T>::max();
+    auto ymin = std::numeric_limits<T>::max();
+    auto xmax = std::numeric_limits<T>::lowest();
+    auto ymax = std::numeric_limits<T>::lowest();
+    for (const auto& p : path)
+    {
+      if (p.x < xmin) xmin = p.x;
+      if (p.x > xmax) xmax = p.x;
+      if (p.y < ymin) ymin = p.y;
+      if (p.y > ymax) ymax = p.y;
+    }
+    return Rect<T>(xmin, ymin, xmax, ymax);
+  }
+
+  template <typename T>
+  Rect<T> GetBounds(const Paths<T>& paths)
+  {
+    auto xmin = std::numeric_limits<T>::max();
+    auto ymin = std::numeric_limits<T>::max();
+    auto xmax = std::numeric_limits<T>::lowest();
+    auto ymax = std::numeric_limits<T>::lowest();
+    for (const Path<T>& path : paths)
+      for (const Point<T>& p : path)
+      {
+      if (p.x < xmin) xmin = p.x;
+      if (p.x > xmax) xmax = p.x;
+      if (p.y < ymin) ymin = p.y;
+      if (p.y > ymax) ymax = p.y;
+      }
+    return Rect<T>(xmin, ymin, xmax, ymax);
+  }
+
+  template <typename T>
+  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, int& error_code)
+  {
+    Path<T1> result;
+    if (scale_x == 0 || scale_y == 0)
+    {
+      error_code |= scale_error_i;
+      DoError(scale_error_i);
+      // if no exception, treat as non-fatal error
+      if (scale_x == 0) scale_x = 1.0;
+      if (scale_y == 0) scale_y = 1.0;
+    }
+
+    result.reserve(path.size());
+#ifdef USINGZ
+    std::transform(path.begin(), path.end(), back_inserter(result),
+      [scale_x, scale_y](const auto& pt) 
+      { return Point<T1>(pt.x * scale_x, pt.y * scale_y, pt.z); });
+#else
+    std::transform(path.begin(), path.end(), back_inserter(result),
+      [scale_x, scale_y](const auto& pt) 
+      { return Point<T1>(pt.x * scale_x, pt.y * scale_y); });
+#endif
+    return result;
+  }
+
+  template <typename T1, typename T2>
+  inline Path<T1> ScalePath(const Path<T2>& path, 
+    double scale, int& error_code)
+  {
+    return ScalePath<T1, T2>(path, scale, scale, error_code);
+  }
+
+  template <typename T1, typename T2>
+  inline Paths<T1> ScalePaths(const Paths<T2>& paths, 
+    double scale_x, double scale_y, int& error_code)
+  {
+    Paths<T1> result;
+
+    if constexpr (std::numeric_limits<T1>::is_integer &&
+      !std::numeric_limits<T2>::is_integer)
+    {
+      RectD r = GetBounds(paths);
+      if ((r.left * scale_x) < min_coord ||
+        (r.right * scale_x) > max_coord ||
+        (r.top * scale_y) < min_coord ||
+        (r.bottom * scale_y) > max_coord)
+      { 
+        error_code |= range_error_i;
+        DoError(range_error_i);
+        return result; // empty path
+      }
+    }
+
+    result.reserve(paths.size());
+    std::transform(paths.begin(), paths.end(), back_inserter(result),
+      [=, &error_code](const auto& path)
+      { return ScalePath<T1, T2>(path, scale_x, scale_y, error_code); });
+    return result;
+  }
+
+  template <typename T1, typename T2>
+  inline Paths<T1> ScalePaths(const Paths<T2>& paths, 
+    double scale, int& error_code)
+  {
+    return ScalePaths<T1, T2>(paths, scale, scale, error_code);
+  }
+
+  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;
+  }
+
+  // Miscellaneous ------------------------------------------------------------
+
+  inline void CheckPrecision(int& precision, int& error_code)
+  {
+    if (precision >= -8 && precision <= 8) return;
+    error_code |= precision_error_i; // non-fatal error
+    DoError(precision_error_i);      // unless exceptions enabled
+    precision = precision > 8 ? 8 : -8;
+  }
+
+  inline void CheckPrecision(int& precision)
+  {
+    int error_code = 0;
+    CheckPrecision(precision, error_code);
+  }
+
+  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;
+  }
+
+  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 cbegin = polygon.cbegin(), first = cbegin, curr, prev;
+    typename Path<T>::const_iterator cend = polygon.cend();
+
+    while (first != cend && first->y == pt.y) ++first;
+    if (first == cend) // not a proper polygon
+      return PointInPolygonResult::IsOutside;
+
+    bool is_above = first->y < pt.y, starting_above = is_above;
+    curr = first +1; 
+    while (true)
+    {
+      if (curr == cend)
+      {
+        if (cend == first || first == cbegin) break;
+        cend = first;
+        curr = cbegin;
+      }
+      
+      if (is_above)
+      {
+        while (curr != cend && curr->y < pt.y) ++curr;
+        if (curr == cend) continue;
+      }
+      else
+      {
+        while (curr != cend && curr->y > pt.y) ++curr;
+        if (curr == cend) continue;
+      }
+
+      if (curr == cbegin) 
+        prev = polygon.cend() - 1; //nb: NOT cend (since might equal first)
+      else  
+        prev = curr - 1;
+
+      if (curr->y == pt.y)
+      {
+        if (curr->x == pt.x || 
+          (curr->y == prev->y &&
+            ((pt.x < prev->x) != (pt.x < curr->x))))
+              return PointInPolygonResult::IsOn;
+        ++curr;
+        if (curr == first) break;
+        continue;
+      }
+
+      if (pt.x < curr->x && pt.x < prev->x)
+      {
+        // we're only interested in edges crossing on the left
+      }
+      else if (pt.x > prev->x && pt.x > curr->x)
+        val = 1 - val; // toggle val
+      else
+      {
+        double d = CrossProduct(*prev, *curr, pt);
+        if (d == 0) return PointInPolygonResult::IsOn;
+        if ((d < 0) == is_above) val = 1 - val;
+      }
+      is_above = !is_above;
+      ++curr;
+    }
+    
+    if (is_above != starting_above)
+    {
+      cend = polygon.cend();
+      if (curr == cend) curr = cbegin;
+      if (curr == cbegin) prev = cend - 1;
+      else prev = curr - 1;
+      double d = CrossProduct(*prev, *curr, pt);
+      if (d == 0) return PointInPolygonResult::IsOn;
+      if ((d < 0) == is_above) val = 1 - val;
+    }
+
+    return (val == 0) ?
+      PointInPolygonResult::IsOutside :
+      PointInPolygonResult::IsInside;
+  }
+
+}  // namespace
+
+#endif  // CLIPPER_CORE_H

+ 610 - 0
thirdparty/clipper2/include/clipper2/clipper.engine.h

@@ -0,0 +1,610 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  26 March 2023                                                   *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* 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.2.2";
+
+#include <cstdlib>
+#include <iostream>
+#include <queue>
+#include <vector>
+#include <functional>
+#include <numeric>
+#include <memory>
+
+#include "clipper.core.h"
+
+namespace Clipper2Lib {
+
+	struct Scanline;
+	struct IntersectNode;
+	struct Active;
+	struct Vertex;
+	struct LocalMinima;
+	struct OutRec;
+	struct HorzSegment;
+
+	//Note: all clipping operations except for Difference are commutative.
+	enum class ClipType { None, Intersection, Union, Difference, Xor };
+	
+	enum class PathType { Subject, Clip };
+	enum class JoinWith { None, Left, Right };
+
+	enum class VertexFlags : uint32_t {
+		None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
+	};
+
+	constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b) 
+	{
+		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;
+		HorzSegment* horz = 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;
+		Active* front_edge = nullptr;
+		Active* back_edge = nullptr;
+		OutPt* pts = nullptr;
+		PolyPath* polypath = nullptr;
+		OutRecList* splits = nullptr;
+		Rect64 bounds = {};
+		Path64 path;
+		bool is_open = false;
+		bool horz_done = false;
+		~OutRec() { 
+			if (splits) delete splits;
+			// nb: don't delete the split pointers
+			// as these are owned by ClipperBase's outrec_list_
+		};
+	};
+
+	///////////////////////////////////////////////////////////////////
+	//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;
+		JoinWith join_with = JoinWith::None;
+	};
+
+	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) {}
+	};
+
+	struct HorzSegment {
+		OutPt* left_op;
+		OutPt* right_op = nullptr;
+		bool left_to_right = true;
+		HorzSegment() : left_op(nullptr) { }
+		explicit HorzSegment(OutPt* op) : left_op(op) { }
+	};
+
+	struct HorzJoin {
+		OutPt* op1 = nullptr;
+		OutPt* op2 = nullptr;
+		HorzJoin() {};
+		explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
+	};
+
+#ifdef USINGZ
+		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
+
+	typedef std::vector<HorzSegment> HorzSegmentList;
+	typedef std::unique_ptr<LocalMinima> LocalMinima_ptr;
+	typedef std::vector<LocalMinima_ptr> LocalMinimaList;
+	typedef std::vector<IntersectNode> IntersectNodeList;
+
+	// 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;
+		LocalMinimaList minima_list_;		//pointers in case of memory reallocs
+		LocalMinimaList::iterator current_locmin_iter_;
+		std::vector<Vertex*> vertex_lists_;
+		std::priority_queue<int64_t> scanline_list_;
+		IntersectNodeList intersect_nodes_;
+    HorzSegmentList horz_seg_list_;
+		std::vector<HorzJoin> horz_join_list_;
+		void Reset();
+		inline void InsertScanline(int64_t y);
+		inline bool PopScanline(int64_t &y);
+		inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima);
+		void DisposeAllOutRecs();
+		void DisposeVerticesAndLocalMinima();
+		void DeleteEdges(Active*& e);
+		inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
+		bool IsContributingClosed(const Active &e) const;
+		inline bool IsContributingOpen(const Active &e) const;
+		void SetWindCountForClosedPathEdge(Active &edge);
+		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);
+		OutRec* NewOutRec();
+		OutPt* AddOutPt(const Active &e, const Point64& pt);
+		OutPt* AddLocalMinPoly(Active &e1, Active &e2, 
+			const Point64& pt, bool is_new = false);
+		OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
+		void DoHorizontal(Active &horz);
+		bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex,
+			int64_t &horz_left, int64_t &horz_right);
+		void DoTopOfScanbeam(const int64_t top_y);
+		Active *DoMaxima(Active &e);
+		void JoinOutrecPaths(Active &e1, Active &e2);
+		void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec);
+		void FixSelfIntersects(OutRec* outrec);
+		void DoSplitOp(OutRec* outRec, OutPt* splitOp);
+		
+		inline void AddTrialHorzJoin(OutPt* op);
+		void ConvertHorzSegsToJoins();
+		void ProcessHorzJoins();
+
+		void Split(Active& e, const Point64& pt);
+		inline void CheckJoinLeft(Active& e, 
+			const Point64& pt, bool check_curr_x = false);
+		inline void CheckJoinRight(Active& e,
+			const Point64& pt, bool check_curr_x = false);
+	protected:
+		int error_code_ = 0;
+		bool has_open_paths_ = false;
+		bool succeeded_ = true;
+		OutRecList outrec_list_; //pointers in case list memory reallocated
+		bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
+		void CleanCollinear(OutRec* outrec);
+		bool CheckBounds(OutRec* outrec);
+		void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
+		void DeepCheckOwners(OutRec* outrec, PolyPath* polypath);
+#ifdef USINGZ
+		ZCallback64 zCallback_ = nullptr;
+		void SetZ(const Active& e1, const Active& e2, Point64& pt);
+#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();
+		int ErrorCode() { return error_code_; };
+		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() {};
+		//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() = 0;
+		virtual size_t Count() const { return 0; }
+
+		const PolyPath* Parent() const { return parent_; }
+
+		bool IsHole() const 
+		{
+			unsigned lvl = Level();
+			//Even levels except level 0
+			return lvl && !(lvl & 1);
+		}		
+	};
+
+	typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
+	typedef typename std::vector<std::unique_ptr<PolyPathD>>  PolyPathDList;
+
+	class PolyPath64 : public PolyPath {
+	private:
+		PolyPath64List childs_;
+		Path64 polygon_;
+	public:
+		explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
+
+		~PolyPath64() {
+			childs_.resize(0);
+		}
+
+		const PolyPath64* operator [] (size_t index) const
+		{ 
+			return childs_[index].get(); 
+		} 
+
+		const PolyPath64* Child(size_t index) const
+		{
+			return childs_[index].get();
+		}
+
+		PolyPath64List::const_iterator begin() const { return childs_.cbegin(); }
+		PolyPath64List::const_iterator end() const { return childs_.cend(); }
+
+		PolyPath64* AddChild(const Path64& path) override
+		{
+			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
+		{
+			return std::accumulate(childs_.cbegin(), childs_.cend(),
+				Clipper2Lib::Area<int64_t>(polygon_),
+				[](double a, const auto& child) {return a + child->Area(); });
+		}
+
+	};
+
+	class PolyPathD : public PolyPath {
+	private:
+		PolyPathDList childs_;
+		double inv_scale_;
+		PathD polygon_;
+	public:
+		explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
+		{
+			inv_scale_ = parent ? parent->inv_scale_ : 1.0;
+		}
+
+		~PolyPathD() {
+			childs_.resize(0);
+		}
+
+		const PolyPathD* operator [] (size_t index) const
+		{ 
+			return childs_[index].get();
+		}
+
+		const PolyPathD* Child(size_t index) const
+		{
+			return childs_[index].get();
+		}
+
+		PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
+		PolyPathDList::const_iterator end() const { return childs_.cend(); }
+
+		void SetInvScale(double value) { inv_scale_ = value; }
+		double InvScale() { return inv_scale_; }
+		PolyPathD* AddChild(const Path64& path) override
+		{
+			int error_code = 0;
+			auto p = std::make_unique<PolyPathD>(this);
+			PolyPathD* result = childs_.emplace_back(std::move(p)).get();
+			result->polygon_ = ScalePath<double, int64_t>(path, inv_scale_, error_code);
+			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
+		{
+			return std::accumulate(childs_.begin(), childs_.end(),
+				Clipper2Lib::Area<double>(polygon_),
+				[](double a, const auto& child) {return a + child->Area(); });
+		}
+	};
+
+	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 zCallbackD_ = nullptr;
+#endif
+		void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
+		void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
+	public:
+		explicit ClipperD(int precision = 2) : ClipperBase()
+		{
+			CheckPrecision(precision, error_code_);
+			// to optimize scaling / descaling precision
+			// set the scale to a power of double's radix (2) (#25)
+			scale_ = std::pow(std::numeric_limits<double>::radix,
+				std::ilogb(std::pow(10, precision)) + 1);
+			invScale_ = 1 / scale_;
+		}
+
+#ifdef USINGZ
+		void SetZCallback(ZCallbackD cb) { zCallbackD_ = 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_;
+			zCallbackD_(e1b,e1t, e2b, e2t, tmp);
+			pt.z = tmp.z; // only update 'z'
+		};
+
+		void CheckCallback()
+		{
+			if(zCallbackD_)
+				// 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_, error_code_), PathType::Subject, false);
+		}
+
+		void AddOpenSubject(const PathsD& open_subjects)
+		{
+			AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_, error_code_), PathType::Subject, true);
+		}
+
+		void AddClip(const PathsD& clips)
+		{
+			AddPaths(ScalePaths<int64_t, double>(clips, scale_, error_code_), 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

+ 774 - 0
thirdparty/clipper2/include/clipper2/clipper.export.h

@@ -0,0 +1,774 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  23 March 2023                                                   *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* Purpose   :  This module exports the Clipper2 Library (ie DLL/so)            *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+// The exported functions below refer to simple structures that
+// can be understood across multiple languages. Consequently
+// Path64, PathD, Polytree64 etc are converted from C++ classes
+// (std::vector<> etc) into the following data structures:
+//
+// CPath64 (int64_t*) & CPathD (double_t*):
+// Path64 and PathD are converted into arrays of x,y coordinates.
+// However in these arrays the first x,y coordinate pair is a
+// counter with 'x' containing the number of following coordinate
+// pairs. ('y' should be 0, with one exception explained below.)
+// __________________________________
+// |counter|coord1|coord2|...|coordN|
+// |N ,0   |x1, y1|x2, y2|...|xN, yN|
+// __________________________________
+//
+// CPaths64 (int64_t**) & CPathsD (double_t**):
+// These are arrays of pointers to CPath64 and CPathD where
+// the first pointer is to a 'counter path'. This 'counter
+// path' has a single x,y coord pair with 'y' (not 'x')
+// containing the number of paths that follow. ('x' = 0).
+// _______________________________
+// |counter|path1|path2|...|pathN|
+// |addr0  |addr1|addr2|...|addrN| (*addr0[0]=0; *addr0[1]=N)
+// _______________________________
+//
+// The structures of CPolytree64 and CPolytreeD are defined
+// below and these structures don't need to be explained here.
+
+#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);
+
+// ExecuteRectClip & ExecuteRectClipLines:
+EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect,
+  const CPaths64 paths, bool convex_only = false);
+EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect,
+  const CPathsD paths, int precision = 2, bool convex_only = false);
+EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect,
+  const CPaths64 paths);
+EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(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, result);
+  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, result);
+  return CreateCPathsD(result, 1/scale);
+}
+
+EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect,
+  const CPaths64 paths, bool convex_only)
+{
+  if (CRectIsEmpty(rect) || !paths) return nullptr;
+  Rect64 r64 = CRectToRect(rect);
+  class RectClip rc(r64);
+  Paths64 pp = ConvertCPaths64(paths);
+  Paths64 result = rc.Execute(pp, convex_only);
+  return CreateCPaths64(result);
+}
+
+EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect,
+  const CPathsD paths, int precision, bool convex_only)
+{
+  if (CRectIsEmpty(rect) || !paths) return nullptr;
+  if (precision < -8 || precision > 8) return nullptr;
+  const double scale = std::pow(10, precision);
+
+  RectD r = CRectToRect(rect);
+  Rect64 rec = ScaleRect<int64_t, double>(r, scale);
+  Paths64 pp = ConvertCPathsD(paths, scale);
+  class RectClip rc(rec);
+  Paths64 result = rc.Execute(pp, convex_only);
+  return CreateCPathsD(result, 1/scale);
+}
+
+EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(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 = rcl.Execute(pp);
+  return CreateCPaths64(result);
+}
+
+EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect,
+  const CPathsD paths, int precision)
+{
+  if (CRectIsEmpty(rect) || !paths) return nullptr;
+  if (precision < -8 || precision > 8) return nullptr;
+  const double scale = std::pow(10, precision);
+  Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
+  class RectClipLines rcl(r);
+  Paths64 pp = ConvertCPathsD(paths, scale);
+  Paths64 result = rcl.Execute(pp);
+  return CreateCPathsD(result, 1/scale);
+}
+
+inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2)
+{
+  // allocates memory for CPath64, fills in the counter, and
+  // returns the structure ready to be filled with path data
+  CPath64 result = new int64_t[2 + cnt1 *2];
+  result[0] = cnt1;
+  result[1] = cnt2;
+  return result;
+}
+
+inline CPath64 CreateCPath64(const Path64& p)
+{
+  // allocates memory for CPath64, fills the counter
+  // and returns the memory filled with path data
+  size_t cnt = p.size();
+  if (!cnt) return nullptr;
+  CPath64 result = CreateCPath64(cnt, 0);
+  CPath64 v = result;
+  v += 2; // skip counters
+  for (const Point64& pt : p)
+  {
+    *v++ = pt.x;
+    *v++ = pt.y;
+  }
+  return result;
+}
+
+inline Path64 ConvertCPath64(const CPath64& p)
+{
+  Path64 result;
+  if (p && *p)
+  {
+    CPath64 v = p;
+    const size_t cnt = static_cast<size_t>(p[0]);
+    v += 2; // skip counters
+    result.reserve(cnt);
+    for (size_t i = 0; i < cnt; ++i)
+    {
+      // x,y here avoids right to left function evaluation
+      // result.push_back(Point64(*v++, *v++));
+      int64_t x = *v++;
+      int64_t y = *v++;
+      result.push_back(Point64(x, y));
+    }
+  }
+  return result;
+}
+
+inline CPaths64 CreateCPaths64(const Paths64& pp)
+{
+  // allocates memory for multiple CPath64 and
+  // and returns this memory filled with path data
+  size_t cnt = pp.size(), cnt2 = cnt;
+
+  // don't allocate space for empty paths
+  for (size_t i = 0; i < cnt; ++i)
+    if (!pp[i].size()) --cnt2;
+  if (!cnt2) return nullptr;
+
+  CPaths64 result = new int64_t* [cnt2 + 1];
+  CPaths64 v = result;
+  *v++ = CreateCPath64(0, cnt2); // assign a counter path
+  for (const Path64& p : pp)
+  {
+    *v = CreateCPath64(p);
+    if (*v) ++v;
+  }
+  return result;
+}
+
+inline Paths64 ConvertCPaths64(const CPaths64& pp)
+{
+  Paths64 result;
+  if (pp) 
+  {
+    CPaths64 v = pp;
+    CPath64 cnts = pp[0];
+    const size_t cnt = static_cast<size_t>(cnts[1]); // nb 2nd cnt
+    ++v; // skip cnts
+    result.reserve(cnt);
+    for (size_t i = 0; i < cnt; ++i)
+      result.push_back(ConvertCPath64(*v++));
+  }
+  return result;
+}
+
+inline CPathD CreateCPathD(size_t cnt1, size_t cnt2)
+{
+  // allocates memory for CPathD, fills in the counter, and
+  // returns the structure ready to be filled with path data
+  CPathD result = new double[2 + cnt1 * 2];
+  result[0] = static_cast<double>(cnt1);
+  result[1] = static_cast<double>(cnt2);
+  return result;
+}
+
+inline CPathD CreateCPathD(const PathD& p)
+{
+  // allocates memory for CPath, fills the counter
+  // and returns the memory fills with path data
+  size_t cnt = p.size();
+  if (!cnt) return nullptr; 
+  CPathD result = CreateCPathD(cnt, 0);
+  CPathD v = result;
+  v += 2; // skip counters
+  for (const PointD& pt : p)
+  {
+    *v++ = pt.x;
+    *v++ = pt.y;
+  }
+  return result;
+}
+
+inline PathD ConvertCPathD(const CPathD& p)
+{
+  PathD result;
+  if (p)
+  {
+    CPathD v = p;
+    size_t cnt = static_cast<size_t>(v[0]);
+    v += 2; // skip counters
+    result.reserve(cnt);
+    for (size_t i = 0; i < cnt; ++i)
+    {
+      // x,y here avoids right to left function evaluation
+      // result.push_back(PointD(*v++, *v++));
+      double x = *v++;
+      double y = *v++;
+      result.push_back(PointD(x, y));
+    }
+  }
+  return result;
+}
+
+inline CPathsD CreateCPathsD(const PathsD& pp)
+{
+  size_t cnt = pp.size(), cnt2 = cnt;
+  // don't allocate space for empty paths
+  for (size_t i = 0; i < cnt; ++i)
+    if (!pp[i].size()) --cnt2;
+  if (!cnt2) return nullptr;
+  CPathsD result = new double * [cnt2 + 1];
+  CPathsD v = result;
+  *v++ = CreateCPathD(0, cnt2); // assign counter path
+  for (const PathD& p : pp)
+  {
+    *v = CreateCPathD(p);
+    if (*v) { ++v; }
+  }
+  return result;
+}
+
+inline PathsD ConvertCPathsD(const CPathsD& pp)
+{
+  PathsD result;
+  if (pp)
+  {
+    CPathsD v = pp;
+    CPathD cnts = v[0];
+    size_t cnt = static_cast<size_t>(cnts[1]);
+    ++v; // skip cnts path
+    result.reserve(cnt);
+    for (size_t i = 0; i < cnt; ++i)
+      result.push_back(ConvertCPathD(*v++));
+  }
+  return result;
+}
+
+inline Path64 ConvertCPathD(const CPathD& p, double scale)
+{
+  Path64 result;
+  if (p)
+  {
+    CPathD v = p;
+    size_t cnt = static_cast<size_t>(*v);
+    v += 2; // skip counters
+    result.reserve(cnt);
+    for (size_t i = 0; i < cnt; ++i)
+    {
+      // x,y here avoids right to left function evaluation
+      // result.push_back(PointD(*v++, *v++));
+      double x = *v++ * scale;
+      double y = *v++ * scale;
+      result.push_back(Point64(x, y));
+    }
+  }
+  return result;
+}
+
+inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale)
+{
+  Paths64 result;
+  if (pp)
+  {
+    CPathsD v = pp;
+    CPathD cnts = v[0];
+    size_t cnt = static_cast<size_t>(cnts[1]);
+    result.reserve(cnt);
+    ++v; // skip cnts path
+    for (size_t i = 0; i < cnt; ++i)
+      result.push_back(ConvertCPathD(*v++, scale));
+  }
+  return result;
+}
+
+inline CPathD CreateCPathD(const Path64& p, double scale)
+{
+  // allocates memory for CPathD, fills in the counter, and
+  // returns the structure filled with *scaled* path data
+  size_t cnt = p.size();
+  if (!cnt) return nullptr;
+  CPathD result = CreateCPathD(cnt, 0);
+  CPathD v = result;
+  v += 2; // skip cnts 
+  for (const Point64& pt : p)
+  {
+    *v++ = pt.x * scale;
+    *v++ = pt.y * scale;
+  }
+  return result;
+}
+
+inline CPathsD CreateCPathsD(const Paths64& pp, double scale)
+{
+  // allocates memory for *multiple* CPathD, and
+  // returns the structure filled with scaled path data
+  size_t cnt = pp.size(), cnt2 = cnt;
+  // don't allocate space for empty paths
+  for (size_t i = 0; i < cnt; ++i)
+    if (!pp[i].size()) --cnt2;
+  if (!cnt2) return nullptr;
+  CPathsD result = new double* [cnt2 + 1];
+  CPathsD v = result;
+  *v++ = CreateCPathD(0, cnt2);
+  for (const Path64& p : pp)
+  {
+    *v = CreateCPathD(p, scale);
+    if (*v) ++v;
+  }
+  return result;
+}
+
+inline void InitCPolyPath64(CPolyTree64* cpt, 
+  bool is_hole, const std::unique_ptr <PolyPath64>& pp)
+{
+  cpt->polygon = CreateCPath64(pp->Polygon());
+  cpt->is_hole = is_hole;
+  size_t child_cnt = pp->Count();
+  cpt->child_count = static_cast<uint32_t>(child_cnt);
+  cpt->childs = nullptr;
+  if (!child_cnt) return;
+  cpt->childs = new CPolyPath64[child_cnt];
+  CPolyPath64* child = cpt->childs;
+  for (const std::unique_ptr <PolyPath64>& pp_child : *pp)
+    InitCPolyPath64(child++, !is_hole, pp_child);  
+}
+
+inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt)
+{
+  CPolyTree64* result = new CPolyTree64();
+  result->polygon = nullptr;
+  result->is_hole = false;
+  size_t child_cnt = pt.Count();
+  result->childs = nullptr;
+  result->child_count = static_cast<uint32_t>(child_cnt);
+  if (!child_cnt) return result;
+  result->childs = new CPolyPath64[child_cnt];
+  CPolyPath64* child = result->childs;
+  for (const std::unique_ptr <PolyPath64>& pp : pt)
+    InitCPolyPath64(child++, true, pp);
+  return result;
+}
+
+inline void DisposeCPolyPath64(CPolyPath64* cpp) 
+{
+  if (!cpp->child_count) return;
+  CPolyPath64* child = cpp->childs;
+  for (size_t i = 0; i < cpp->child_count; ++i)
+    DisposeCPolyPath64(child);
+  delete[] cpp->childs;
+}
+
+EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt)
+{
+  if (!cpt) return;
+  DisposeCPolyPath64(cpt);
+  delete cpt;
+  cpt = nullptr;
+}
+
+inline void InitCPolyPathD(CPolyTreeD* cpt,
+  bool is_hole, const std::unique_ptr <PolyPath64>& pp, double scale)
+{
+  cpt->polygon = CreateCPathD(pp->Polygon(), scale);
+  cpt->is_hole = is_hole;
+  size_t child_cnt = pp->Count();
+  cpt->child_count = static_cast<uint32_t>(child_cnt);
+  cpt->childs = nullptr;
+  if (!child_cnt) return;
+  cpt->childs = new CPolyPathD[child_cnt];
+  CPolyPathD* child = cpt->childs;
+  for (const std::unique_ptr <PolyPath64>& pp_child : *pp)
+    InitCPolyPathD(child++, !is_hole, pp_child, scale);
+}
+
+inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale)
+{
+  CPolyTreeD* result = new CPolyTreeD();
+  result->polygon = nullptr;
+  result->is_hole = false;
+  size_t child_cnt = pt.Count();
+  result->child_count = static_cast<uint32_t>(child_cnt);
+  result->childs = nullptr;
+  if (!child_cnt) return result;
+  result->childs = new CPolyPathD[child_cnt];
+  CPolyPathD* child = result->childs;
+  for (const std::unique_ptr <PolyPath64>& pp : pt)
+    InitCPolyPathD(child++, true, pp, scale);
+  return result;
+}
+
+inline void DisposeCPolyPathD(CPolyPathD* cpp)
+{
+  if (!cpp->child_count) return;
+  CPolyPathD* child = cpp->childs;
+  for (size_t i = 0; i < cpp->child_count; ++i)
+    DisposeCPolyPathD(child++);
+  delete[] cpp->childs;
+}
+
+EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt)
+{
+  if (!cpt) return;
+  DisposeCPolyPathD(cpt);
+  delete cpt;
+  cpt = nullptr;
+}
+
+}  // end Clipper2Lib namespace
+  
+#endif  // CLIPPER2_EXPORT_H

+ 776 - 0
thirdparty/clipper2/include/clipper2/clipper.h

@@ -0,0 +1,776 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  23 March 2023                                                   *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* Purpose   :  This module provides a simple interface to the Clipper Library  *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#ifndef CLIPPER_H
+#define CLIPPER_H
+
+#include <cstdlib>
+#include <type_traits>
+#include <vector>
+
+#include "clipper.core.h"
+#include "clipper.engine.h"
+#include "clipper.offset.h"
+#include "clipper.minkowski.h"
+#include "clipper.rectclip.h"
+
+namespace Clipper2Lib {
+
+  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 precision = 2)
+  {
+    int error_code = 0;
+    CheckPrecision(precision, error_code);
+    PathsD result;
+    if (error_code) return result;
+    ClipperD clipper(precision);
+    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 precision = 2)
+  {
+    polytree.Clear();
+    int error_code = 0;
+    CheckPrecision(precision, error_code);
+    if (error_code) return;
+    ClipperD clipper(precision);
+    clipper.AddSubject(subjects);
+    clipper.AddClip(clips);
+    clipper.Execute(cliptype, fillrule, polytree);
+  }
+
+  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 precision = 2)
+  {
+    PathsD result;
+    int error_code = 0;
+    CheckPrecision(precision, error_code);
+    if (error_code) return result;
+    ClipperD clipper(precision);
+    clipper.AddSubject(subjects);
+    clipper.Execute(ClipType::Union, fillrule, result);
+    return result;
+  }
+
+  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,
+    double arc_tolerance = 0.0)
+  {
+    if (!delta) return paths;
+    ClipperOffset clip_offset(miter_limit, arc_tolerance);
+    clip_offset.AddPaths(paths, jt, et);
+    Paths64 solution;
+    clip_offset.Execute(delta, solution);
+    return solution;
+  }
+
+  inline PathsD InflatePaths(const PathsD& paths, double delta,
+    JoinType jt, EndType et, double miter_limit = 2.0, 
+    int precision = 2, double arc_tolerance = 0.0)
+  {
+    int error_code = 0;
+    CheckPrecision(precision, error_code);
+    if (!delta) return paths;
+    if (error_code) return PathsD();
+    const double scale = std::pow(10, precision);
+    ClipperOffset clip_offset(miter_limit, arc_tolerance);
+    clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
+    if (error_code) return PathsD();
+    Paths64 solution;
+    clip_offset.Execute(delta * scale, solution);
+    return ScalePaths<double, int64_t>(solution, 1 / scale, error_code);
+  }
+
+  inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
+  {
+    Path64 result;
+    result.reserve(path.size());
+    std::transform(path.begin(), path.end(), back_inserter(result),
+      [dx, dy](const auto& pt) { return Point64(pt.x + dx, pt.y +dy); });
+    return result;
+  }
+
+  inline PathD TranslatePath(const PathD& path, double dx, double dy)
+  {
+    PathD result;
+    result.reserve(path.size());
+    std::transform(path.begin(), path.end(), back_inserter(result),
+      [dx, dy](const auto& pt) { return PointD(pt.x + dx, pt.y + dy); });
+    return result;
+  }
+
+  inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
+  {
+    Paths64 result;
+    result.reserve(paths.size());
+    std::transform(paths.begin(), paths.end(), back_inserter(result),
+      [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
+    return result;
+  }
+
+  inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
+  {
+    PathsD result;
+    result.reserve(paths.size());
+    std::transform(paths.begin(), paths.end(), back_inserter(result),
+      [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
+    return result;
+  }
+
+  inline Paths64 ExecuteRectClip(const Rect64& rect, 
+    const Paths64& paths, bool convex_only = false)
+  {
+    if (rect.IsEmpty() || paths.empty()) return Paths64();
+    RectClip rc(rect);
+    return rc.Execute(paths, convex_only);
+  }
+
+  inline Paths64 ExecuteRectClip(const Rect64& rect,
+    const Path64& path, bool convex_only = false)
+  {
+    if (rect.IsEmpty() || path.empty()) return Paths64();
+    RectClip rc(rect);
+    return rc.Execute(Paths64{ path }, convex_only);
+  }
+
+  inline PathsD ExecuteRectClip(const RectD& rect,
+    const PathsD& paths, bool convex_only = false, int precision = 2)
+  {
+    if (rect.IsEmpty() || paths.empty()) return PathsD();
+    int error_code = 0;
+    CheckPrecision(precision, error_code);
+    if (error_code) return PathsD();
+    const double scale = std::pow(10, precision);
+    Rect64 r = ScaleRect<int64_t, double>(rect, scale);
+    RectClip rc(r);
+    Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code);
+    if (error_code) return PathsD(); // ie: error_code result is lost 
+    return ScalePaths<double, int64_t>(
+      rc.Execute(pp, convex_only), 1 / scale, error_code);
+  }
+
+  inline PathsD ExecuteRectClip(const RectD& rect,
+    const PathD& path, bool convex_only = false, int precision = 2)
+  {
+    return ExecuteRectClip(rect, PathsD{ path }, convex_only, precision);
+  }
+
+  inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Paths64& lines)
+  {
+    if (rect.IsEmpty() || lines.empty()) return Paths64();
+    RectClipLines rcl(rect);
+    return rcl.Execute(lines);
+  }
+
+  inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Path64& line)
+  {
+    return ExecuteRectClipLines(rect, Paths64{ line });
+  }
+
+  inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2)
+  {
+    return ExecuteRectClip(rect, PathsD{ line }, precision);
+  }
+
+  inline PathsD ExecuteRectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
+  {
+    if (rect.IsEmpty() || lines.empty()) return PathsD();
+    int error_code = 0;
+    CheckPrecision(precision, error_code);
+    if (error_code) return PathsD();
+    const double scale = std::pow(10, precision);
+    Rect64 r = ScaleRect<int64_t, double>(rect, scale);
+    RectClipLines rcl(r);
+    Paths64 p = ScalePaths<int64_t, double>(lines, scale, error_code);
+    if (error_code) return PathsD();
+    p = rcl.Execute(p);
+    return ScalePaths<double, int64_t>(p, 1 / scale, error_code);
+  }
+
+  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)
+      {
+        // return false if this child isn't fully contained by its parent
+
+        // the following algorithm is a bit too crude, and doesn't account
+        // for rounding errors. A better algorithm is to return false when
+        // consecutive vertices are found outside the parent's polygon.
+
+        //const Path64& path = pp.Polygon();
+        //if (std::any_of(child->Polygon().cbegin(), child->Polygon().cend(),
+        //  [path](const auto& pt) {return (PointInPolygon(pt, path) ==
+        //    PointInPolygonResult::IsOutside); })) return false;
+
+        int outsideCnt = 0;
+        for (const Point64& pt : child->Polygon())
+        {
+          PointInPolygonResult result = PointInPolygon(pt, pp.Polygon());
+          if (result == PointInPolygonResult::IsInside) --outsideCnt;
+          else if (result == PointInPolygonResult::IsOutside) ++outsideCnt;
+          if (outsideCnt > 1) return false;
+          else if (outsideCnt < -1) break;
+        }
+
+        // now check any nested children too
+        if (child->Count() > 0 && !PolyPath64ContainsChildren(*child))
+          return false;
+      }
+      return true;
+    }
+
+    static void OutlinePolyPath(std::ostream& os, 
+      bool isHole, size_t count, const std::string& preamble)
+    {
+      std::string plural = (count == 1) ? "." : "s.";
+      if (isHole)
+      {
+        if (count)
+          os << preamble << "+- Hole with " << count <<
+          " nested polygon" << plural << std::endl;
+        else
+          os << preamble << "+- Hole" << std::endl;
+      }
+      else
+      {
+        if (count)
+          os << preamble << "+- Polygon with " << count <<
+          " hole" << plural << std::endl;
+        else
+          os << preamble << "+- Polygon" << std::endl;
+      }
+    }
+
+    static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp,
+      std::string preamble, bool last_child)
+    {
+      OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble);
+      preamble += (!last_child) ? "|  " : "   ";
+      if (pp.Count())
+      {
+        PolyPath64List::const_iterator it = pp.begin();
+        for (; it < pp.end() - 1; ++it)
+          OutlinePolyPath64(os, **it, preamble, false);
+        OutlinePolyPath64(os, **it, preamble, true);
+      }
+    }
+
+    static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp,
+      std::string preamble, bool last_child)
+    {
+      OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble);
+      preamble += (!last_child) ? "|  " : "   ";
+      if (pp.Count())
+      {
+        PolyPathDList::const_iterator it = pp.begin();
+        for (; it < pp.end() - 1; ++it)
+          OutlinePolyPathD(os, **it, preamble, false);
+        OutlinePolyPathD(os, **it, preamble, true);
+      }
+    }
+
+  } // end details namespace 
+
+  inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
+  {
+    PolyPath64List::const_iterator it = pp.begin();
+    for (; it < pp.end() - 1; ++it)
+      details::OutlinePolyPath64(os, **it, "   ", false);
+    details::OutlinePolyPath64(os, **it, "   ", true);
+    os << std::endl << std::endl;
+    if (!pp.Level()) os << std::endl;
+    return os;
+  }
+
+  inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp)
+  {
+    PolyPathDList::const_iterator it = pp.begin();
+    for (; it < pp.end() - 1; ++it)
+      details::OutlinePolyPathD(os, **it, "   ", false);
+    details::OutlinePolyPathD(os, **it, "   ", true);
+    os << std::endl << std::endl;
+    if (!pp.Level()) os << std::endl;
+    return os;
+  }
+
+  inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
+  {
+    Paths64 result;
+    for (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;
+  }
+
+  namespace details {
+
+    template<typename T, typename U>
+    inline constexpr void MakePathGeneric(const T list, size_t size,
+      std::vector<U>& result)
+    {
+      for (size_t i = 0; i < size; ++i)
+#ifdef USINGZ
+        result[i / 2] = U{list[i], list[++i], 0};
+#else
+        result[i / 2] = U{list[i], list[++i]};
+#endif
+    }
+
+  } // end details namespace
+
+  template<typename T,
+    typename std::enable_if<
+      std::is_integral<T>::value &&
+      !std::is_same<char, T>::value, bool
+    >::type = true>
+  inline Path64 MakePath(const std::vector<T>& list)
+  {
+    const auto size = list.size() - list.size() % 2;
+    if (list.size() != size)
+      DoError(non_pair_error_i);  // non-fatal without exception handling
+    Path64 result(size / 2);      // else ignores unpaired value
+    details::MakePathGeneric(list, size, result);
+    return result;
+  }
+
+  template<typename T, std::size_t N,
+    typename std::enable_if<
+      std::is_integral<T>::value &&
+      !std::is_same<char, T>::value, bool
+    >::type = true>
+  inline Path64 MakePath(const T(&list)[N])
+  {
+    // Make the compiler error on unpaired value (i.e. no runtime effects).
+    static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
+    Path64 result(N / 2);
+    details::MakePathGeneric(list, N, result);
+    return result;
+  }
+
+  template<typename T,
+    typename std::enable_if<
+      std::is_arithmetic<T>::value &&
+      !std::is_same<char, T>::value, bool
+    >::type = true>
+  inline PathD MakePathD(const std::vector<T>& list)
+  {
+    const auto size = list.size() - list.size() % 2;
+    if (list.size() != size)
+      DoError(non_pair_error_i);  // non-fatal without exception handling
+    PathD result(size / 2);       // else ignores unpaired value
+    details::MakePathGeneric(list, size, result);
+    return result;
+  }
+
+  template<typename T, std::size_t N,
+    typename std::enable_if<
+      std::is_arithmetic<T>::value &&
+      !std::is_same<char, T>::value, bool
+    >::type = true>
+  inline PathD MakePathD(const T(&list)[N])
+  {
+    // Make the compiler error on unpaired value (i.e. no runtime effects).
+    static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
+    PathD result(N / 2);
+    details::MakePathGeneric(list, N, result);
+    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)
+  {
+    int error_code = 0;
+    CheckPrecision(precision, error_code);
+    if (error_code) return PathD();
+    const double scale = std::pow(10, precision);
+    Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
+    if (error_code) return PathD();
+    p = TrimCollinear(p, is_open_path);
+    return ScalePath<double, int64_t>(p, 1/scale, error_code);
+  }
+
+  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);
+  }
+
+  inline size_t GetNext(size_t current, size_t high, 
+    const std::vector<bool>& flags)
+  {
+    ++current;
+    while (current <= high && flags[current]) ++current;
+    if (current <= high) return current;
+    current = 0;
+    while (flags[current]) ++current;
+    return current;
+  }
+
+  inline size_t GetPrior(size_t current, size_t high, 
+    const std::vector<bool>& flags)
+  {
+    if (current == 0) current = high;
+    else --current;
+    while (current > 0 && flags[current]) --current;
+    if (!flags[current]) return current;
+    current = high;
+    while (flags[current]) --current;
+    return current;
+  }
+
+  template <typename T>
+  inline Path<T> SimplifyPath(const Path<T> path, 
+    double epsilon, bool isOpenPath = false)
+  {
+    const size_t len = path.size(), high = len -1;
+    const double epsSqr = Sqr(epsilon);
+    if (len < 4) return Path<T>(path);
+
+    std::vector<bool> flags(len);
+    std::vector<double> distSqr(len);
+    size_t prior = high, curr = 0, start, next, prior2, next2;
+    if (isOpenPath)
+    {
+      distSqr[0] = MAX_DBL;
+      distSqr[high] = MAX_DBL;
+    }
+    else 
+    {
+      distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
+      distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
+    }
+    for (size_t i = 1; i < high; ++i)
+      distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
+
+    for (;;)
+    {
+      if (distSqr[curr] > epsSqr)
+      {
+        start = curr;
+        do
+        {
+          curr = GetNext(curr, high, flags);
+        } while (curr != start && distSqr[curr] > epsSqr);
+        if (curr == start) break;
+      }
+      
+      prior = GetPrior(curr, high, flags);
+      next = GetNext(curr, high, flags);
+      if (next == prior) break;
+
+      if (distSqr[next] < distSqr[curr])
+      {
+        flags[next] = true;
+        next = GetNext(next, high, flags);
+        next2 = GetNext(next, high, flags);
+        distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
+        if (next != high || !isOpenPath)
+          distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]);
+        curr = next;
+      }
+      else
+      {
+        flags[curr] = true;
+        curr = next;
+        next = GetNext(next, high, flags);
+        prior2 = GetPrior(prior, high, flags);
+        distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
+        if (prior != 0 || !isOpenPath)
+          distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
+      }
+    }
+    Path<T> result;
+    result.reserve(len);
+    for (typename Path<T>::size_type i = 0; i < len; ++i)
+      if (!flags[i]) result.push_back(path[i]);
+    return result;
+  }
+
+  template <typename T>
+  inline Paths<T> SimplifyPaths(const Paths<T> paths, 
+    double epsilon, bool isOpenPath = false)
+  {
+    Paths<T> result;
+    result.reserve(paths.size());
+    for (const auto& path : paths)
+      result.push_back(SimplifyPath(path, epsilon, isOpenPath));
+    return result;
+  }
+
+  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());
+    std::transform(paths.begin(), paths.end(), back_inserter(result),
+      [epsilon](const auto& path)
+      { return RamerDouglasPeucker<T>(path, epsilon); });
+    return result;
+  }
+
+}  // end Clipper2Lib namespace
+
+#endif  // CLIPPER_H

+ 120 - 0
thirdparty/clipper2/include/clipper2/clipper.minkowski.h

@@ -0,0 +1,120 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  28 January 2023                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* 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)
+  {
+    int error_code = 0;
+    double scale = pow(10, decimalPlaces);
+    Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
+    Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
+    Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
+    return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
+  }
+
+  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)
+  {
+    int error_code = 0;
+    double scale = pow(10, decimalPlaces);
+    Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
+    Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
+    Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
+    return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
+  }
+
+} // Clipper2Lib namespace
+
+#endif  // CLIPPER_MINKOWSKI_H

+ 114 - 0
thirdparty/clipper2/include/clipper2/clipper.offset.h

@@ -0,0 +1,114 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  22 March 2023                                                   *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* 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"
+#include "clipper.engine.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) {}
+	};
+
+	int   error_code_ = 0;
+	double delta_ = 0.0;
+	double group_delta_ = 0.0;
+	double abs_group_delta_ = 0.0;
+	double temp_lim_ = 0.0;
+	double steps_per_rad_ = 0.0;
+	double step_sin_ = 0.0;
+	double step_cos_ = 0.0;
+	PathD norms;
+	Paths64 solution;
+	std::vector<Group> groups_;
+	JoinType join_type_ = JoinType::Square;
+	EndType end_type_ = EndType::Polygon;
+
+	double miter_limit_ = 0.0;
+	double arc_tolerance_ = 0.0;
+	bool preserve_collinear_ = false;
+	bool reverse_solution_ = false;
+
+#ifdef USINGZ
+	ZCallback64 zCallback64_ = nullptr;
+#endif
+
+	void DoSquare(Group& group, const Path64& path, size_t j, size_t k);
+	void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a);
+	void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle);
+	void BuildNormals(const Path64& path);
+	void OffsetPolygon(Group& group, Path64& path);
+	void OffsetOpenJoined(Group& group, Path64& path);
+	void OffsetOpenPath(Group& group, Path64& path);
+	void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k);
+	void DoGroupOffset(Group &group);
+	void ExecuteInternal(double delta);
+public:
+	explicit 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(); };
+
+	int ErrorCode() { return error_code_; };
+	void AddPath(const Path64& path, JoinType jt_, EndType et_);
+	void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
+	void Clear() { groups_.clear(); norms.clear(); };
+	
+	void Execute(double delta, Paths64& paths);
+	void Execute(double delta, PolyTree64& polytree);
+
+	double MiterLimit() const { return miter_limit_; }
+	void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
+
+	//ArcTolerance: needed for rounded offsets (See offset_triginometry2.svg)
+	double ArcTolerance() const { return arc_tolerance_; }
+	void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; }
+
+	bool PreserveCollinear() const { return preserve_collinear_; }
+	void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
+	
+	bool ReverseSolution() const { return reverse_solution_; }
+	void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
+
+#ifdef USINGZ
+	void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; }
+#endif
+};
+
+}
+#endif /* CLIPPER_OFFSET_H_ */

+ 82 - 0
thirdparty/clipper2/include/clipper2/clipper.rectclip.h

@@ -0,0 +1,82 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  9 February 2023                                                 *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* 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 <queue>
+#include "clipper.h"
+#include "clipper.core.h"
+
+namespace Clipper2Lib
+{
+
+  enum class Location { Left, Top, Right, Bottom, Inside };
+
+  class OutPt2;
+  typedef std::vector<OutPt2*> OutPt2List;
+
+  class OutPt2 {
+  public:
+    Point64 pt;
+    size_t owner_idx;
+    OutPt2List* edge;
+    OutPt2* next;
+    OutPt2* prev;
+  };
+
+  //------------------------------------------------------------------------------
+  // RectClip
+  //------------------------------------------------------------------------------
+
+  class RectClip {
+  private:
+    void ExecuteInternal(const Path64& path);
+    Path64 GetPath(OutPt2*& op);
+  protected:
+    const Rect64 rect_;
+    const Path64 rect_as_path_;
+    const Point64 rect_mp_;
+    Rect64 path_bounds_;
+    std::deque<OutPt2> op_container_;
+    OutPt2List results_;  // each path can be broken into multiples
+    OutPt2List edges_[8]; // clockwise and counter-clockwise
+    std::vector<Location> start_locs_;
+    void CheckEdges();
+    void TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw);
+    void GetNextLocation(const Path64& path,
+      Location& loc, int& i, int highI);
+    OutPt2* Add(Point64 pt, bool start_new = false);
+    void AddCorner(Location prev, Location curr);
+    void AddCorner(Location& loc, bool isClockwise);
+  public:
+    explicit RectClip(const Rect64& rect) :
+      rect_(rect),
+      rect_as_path_(rect.AsPath()),
+      rect_mp_(rect.MidPoint()) {}
+    Paths64 Execute(const Paths64& paths, bool convex_only = false);
+  };
+
+  //------------------------------------------------------------------------------
+  // RectClipLines
+  //------------------------------------------------------------------------------
+
+  class RectClipLines : public RectClip {
+  private:
+    void ExecuteInternal(const Path64& path);
+    Path64 GetPath(OutPt2*& op);
+  public:
+    explicit RectClipLines(const Rect64& rect) : RectClip(rect) {};
+    Paths64 Execute(const Paths64& paths);
+  };
+
+} // Clipper2Lib namespace
+#endif  // CLIPPER_RECTCLIP_H

+ 2979 - 0
thirdparty/clipper2/src/clipper.engine.cpp

@@ -0,0 +1,2979 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  19 March 2023                                                   *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* Purpose   :  This is the main polygon clipping module                        *
+* License   :  http://www.boost.org/LICENSE_1_0.txt                            *
+*******************************************************************************/
+
+#include <cstdlib>
+#include <cmath>
+#include <stdexcept>
+#include <vector>
+#include <numeric>
+#include <algorithm>
+
+#include "clipper2/clipper.engine.h"
+
+// https://github.com/AngusJohnson/Clipper2/discussions/334
+// #discussioncomment-4248602
+#if defined(_MSC_VER) && ( defined(_M_AMD64) || defined(_M_X64) )
+#include <xmmintrin.h>
+#include <emmintrin.h>
+#define fmin(a,b) _mm_cvtsd_f64(_mm_min_sd(_mm_set_sd(a),_mm_set_sd(b)))
+#define fmax(a,b) _mm_cvtsd_f64(_mm_max_sd(_mm_set_sd(a),_mm_set_sd(b)))
+#define nearbyint(a) _mm_cvtsd_si64(_mm_set_sd(a)) /* Note: expression type is (int64_t) */
+#endif
+
+namespace Clipper2Lib {
+
+  static const Rect64 invalid_rect = Rect64(false);
+
+  // Every closed path (or polygon) is made up of a series of vertices forming
+  // edges that alternate between going up (relative to the Y-axis) and going
+  // down. Edges consecutively going up or consecutively going down are called
+  // 'bounds' (ie sides if they're simple polygons). 'Local Minima' refer to
+  // vertices where descending bounds become ascending ones.
+
+  struct Scanline {
+    int64_t y = 0;
+    Scanline* next = nullptr;
+
+    explicit Scanline(int64_t y_) : y(y_) {}
+  };
+
+  struct HorzSegSorter {
+    inline bool operator()(const HorzSegment& hs1, const HorzSegment& hs2)
+    {
+      if (!hs1.right_op || !hs2.right_op) return (hs1.right_op);
+      return  hs2.left_op->pt.x > hs1.left_op->pt.x;
+    }
+  };
+
+  struct LocMinSorter {
+    inline bool operator()(const LocalMinima_ptr& locMin1,
+      const LocalMinima_ptr& locMin2)
+    {
+      if (locMin2->vertex->pt.y != locMin1->vertex->pt.y)
+        return locMin2->vertex->pt.y < locMin1->vertex->pt.y;
+      else
+        return locMin2->vertex->pt.x > locMin1->vertex->pt.x;
+    }
+  };
+
+  inline bool IsOdd(int val)
+  {
+    return (val & 1) ? true : false;
+  }
+
+
+  inline bool IsHotEdge(const Active& e)
+  {
+    return (e.outrec);
+  }
+
+
+  inline bool IsOpen(const Active& e)
+  {
+    return (e.local_min->is_open);
+  }
+
+
+  inline bool IsOpenEnd(const Vertex& v)
+  {
+    return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) !=
+      VertexFlags::None;
+  }
+
+
+  inline bool IsOpenEnd(const Active& ae)
+  {
+    return IsOpenEnd(*ae.vertex_top);
+  }
+
+
+  inline Active* GetPrevHotEdge(const Active& e)
+  {
+    Active* prev = e.prev_in_ael;
+    while (prev && (IsOpen(*prev) || !IsHotEdge(*prev)))
+      prev = prev->prev_in_ael;
+    return prev;
+  }
+
+  inline bool IsFront(const Active& e)
+  {
+    return (&e == e.outrec->front_edge);
+  }
+
+  inline bool IsInvalidPath(OutPt* op)
+  {
+    return (!op || op->next == op);
+  }
+
+  /*******************************************************************************
+    *  Dx:                             0(90deg)                                    *
+    *                                  |                                           *
+    *               +inf (180deg) <--- o ---> -inf (0deg)                          *
+    *******************************************************************************/
+
+  inline double GetDx(const Point64& pt1, const Point64& pt2)
+  {
+    double dy = double(pt2.y - pt1.y);
+    if (dy != 0)
+      return double(pt2.x - pt1.x) / dy;
+    else if (pt2.x > pt1.x)
+      return -std::numeric_limits<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>(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);
+  }
+
+  inline Vertex* GetCurrYMaximaVertex_Open(const Active& e)
+  {
+    Vertex* result = e.vertex_top;
+    if (e.wind_dx > 0)
+      while ((result->next->pt.y == result->pt.y) &&
+        ((result->flags & (VertexFlags::OpenEnd | 
+          VertexFlags::LocalMax)) == VertexFlags::None))
+            result = result->next;
+    else
+      while (result->prev->pt.y == result->pt.y &&
+        ((result->flags & (VertexFlags::OpenEnd | 
+          VertexFlags::LocalMax)) == VertexFlags::None))
+          result = result->prev;
+    if (!IsMaxima(*result)) result = nullptr; // not a maxima   
+    return result;
+  }
+
+    inline Vertex* GetCurrYMaximaVertex(const Active& e)
+  {
+    Vertex* result = e.vertex_top;
+    if (e.wind_dx > 0)
+      while (result->next->pt.y == result->pt.y) result = result->next;
+    else
+      while (result->prev->pt.y == result->pt.y) result = result->prev;
+    if (!IsMaxima(*result)) result = nullptr; // not a maxima   
+    return result;
+  }
+
+  Active* GetMaximaPair(const Active& e)
+  {
+    Active* e2;
+    e2 = e.next_in_ael;
+    while (e2)
+    {
+      if (e2->vertex_top == e.vertex_top) return e2;  // Found!
+      e2 = e2->next_in_ael;
+    }
+    return nullptr;
+  }
+
+  inline int PointCount(OutPt* op)
+  {
+    OutPt* op2 = op;
+    int cnt = 0;
+    do
+    {
+      op2 = op2->next;
+      ++cnt;
+    } while (op2 != op);
+    return cnt;
+  }
+
+  inline OutPt* DuplicateOp(OutPt* op, bool insert_after)
+  {
+    OutPt* result = new OutPt(op->pt, op->outrec);
+    if (insert_after)
+    {
+      result->next = op->next;
+      result->next->prev = result;
+      result->prev = op;
+      op->next = result;
+    }
+    else
+    {
+      result->prev = op->prev;
+      result->prev->next = result;
+      result->next = op;
+      op->prev = result;
+    }
+    return result;
+  }
+
+  inline OutPt* DisposeOutPt(OutPt* op)
+  {
+    OutPt* result = op->next;
+    op->prev->next = op->next;
+    op->next->prev = op->prev;
+    delete op;
+    return result;
+  }
+
+
+  inline void DisposeOutPts(OutRec* outrec)
+  {
+    OutPt* op = outrec->pts;
+    op->prev->next = nullptr;
+    while (op)
+    {
+      OutPt* tmp = op;
+      op = op->next;
+      delete tmp;
+    };
+    outrec->pts = nullptr;
+  }
+
+
+  bool IntersectListSort(const IntersectNode& a, const IntersectNode& b)
+  {
+    //note different inequality tests ...
+    return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y);
+  }
+
+
+  inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge)
+  {
+    outrec.front_edge = &start_edge;
+    outrec.back_edge = &end_edge;
+  }
+
+
+  void SwapOutrecs(Active& e1, Active& e2)
+  {
+    OutRec* or1 = e1.outrec;
+    OutRec* or2 = e2.outrec;
+    if (or1 == or2)
+    {
+      Active* e = or1->front_edge;
+      or1->front_edge = or1->back_edge;
+      or1->back_edge = e;
+      return;
+    }
+    if (or1)
+    {
+      if (&e1 == or1->front_edge)
+        or1->front_edge = &e2;
+      else
+        or1->back_edge = &e2;
+    }
+    if (or2)
+    {
+      if (&e2 == or2->front_edge)
+        or2->front_edge = &e1;
+      else
+        or2->back_edge = &e1;
+    }
+    e1.outrec = or2;
+    e2.outrec = or1;
+  }
+
+
+  double Area(OutPt* op)
+  {
+    //https://en.wikipedia.org/wiki/Shoelace_formula
+    double result = 0.0;
+    OutPt* op2 = op;
+    do
+    {
+      result += static_cast<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 IsJoined(const Active& e)
+  {
+    return e.join_with != JoinWith::None;
+  }
+
+  inline void SetOwner(OutRec* outrec, OutRec* new_owner)
+  {
+    //precondition1: new_owner is never null
+    while (new_owner->owner && !new_owner->owner->pts)
+      new_owner->owner = new_owner->owner->owner;
+    OutRec* tmp = new_owner;
+    while (tmp && tmp != outrec) tmp = tmp->owner;
+    if (tmp) new_owner->owner = outrec->owner;
+    outrec->owner = new_owner;
+  }
+
+  //------------------------------------------------------------------------------
+  // ClipperBase methods ...
+  //------------------------------------------------------------------------------
+
+  ClipperBase::~ClipperBase()
+  {
+    Clear();
+  }
+
+  void ClipperBase::DeleteEdges(Active*& e)
+  {
+    while (e)
+    {
+      Active* e2 = e;
+      e = e->next_in_ael;
+      delete e2;
+    }
+  }
+
+  void ClipperBase::CleanUp()
+  {
+    DeleteEdges(actives_);
+    scanline_list_ = std::priority_queue<int64_t>();
+    intersect_nodes_.clear();
+    DisposeAllOutRecs();
+    horz_seg_list_.clear();
+    horz_join_list_.clear();
+  }
+
+
+  void ClipperBase::Clear()
+  {
+    CleanUp();
+    DisposeVerticesAndLocalMinima();
+    current_locmin_iter_ = minima_list_.begin();
+    minima_list_sorted_ = false;
+    has_open_paths_ = false;
+  }
+
+
+  void ClipperBase::Reset()
+  {
+    if (!minima_list_sorted_)
+    {
+      std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter());
+      minima_list_sorted_ = true;
+    }
+    LocalMinimaList::const_reverse_iterator i;
+    for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i)
+      InsertScanline((*i)->vertex->pt.y);
+
+    current_locmin_iter_ = minima_list_.begin();
+    actives_ = nullptr;
+    sel_ = nullptr;
+    succeeded_ = true;
+  }
+
+
+#ifdef USINGZ
+  void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip)
+  {
+    if (!zCallback_) return;
+    // prioritize subject over clip vertices by passing 
+    // subject vertices before clip vertices in the callback
+    if (GetPolyType(e1) == PathType::Subject)
+    {
+      if (ip == e1.bot) ip.z = e1.bot.z;
+      else if (ip == e1.top) ip.z = e1.top.z;
+      else if (ip == e2.bot) ip.z = e2.bot.z;
+      else if (ip == e2.top) ip.z = e2.top.z;
+      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;
+
+    const auto total_vertex_count =
+      std::accumulate(paths.begin(), paths.end(), 0, 
+        [](const auto& a, const Path64& path) 
+        {return a + static_cast<unsigned>(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;
+
+      if (path.empty())
+        continue;
+
+      v->prev = nullptr;
+      int cnt = 0;
+      for (const Point64& pt : path)
+      {
+        if (prev_v)
+        {
+          if (prev_v->pt == pt) continue; // ie skips duplicates
+          prev_v->next = curr_v;
+        }
+        curr_v->prev = prev_v;
+        curr_v->pt = pt;
+        curr_v->flags = VertexFlags::None;
+        prev_v = curr_v++;
+        cnt++;
+      }
+      if (!prev_v || !prev_v->prev) continue;
+      if (!is_open && prev_v->pt == v0->pt)
+        prev_v = prev_v->prev;
+      prev_v->next = v0;
+      v0->prev = prev_v;
+      v = curr_v; // ie get ready for next path
+      if (cnt < 2 || (cnt == 2 && !is_open)) continue;
+
+      //now find and assign local minima
+      bool going_up, going_up0;
+      if (is_open)
+      {
+        curr_v = v0->next;
+        while (curr_v != v0 && curr_v->pt.y == v0->pt.y)
+          curr_v = curr_v->next;
+        going_up = curr_v->pt.y <= v0->pt.y;
+        if (going_up)
+        {
+          v0->flags = VertexFlags::OpenStart;
+          AddLocMin(*v0, polytype, true);
+        }
+        else
+          v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax;
+      }
+      else // closed path
+      {
+        prev_v = v0->prev;
+        while (prev_v != v0 && prev_v->pt.y == v0->pt.y)
+          prev_v = prev_v->prev;
+        if (prev_v == v0)
+          continue; // only open paths can be completely flat
+        going_up = prev_v->pt.y > v0->pt.y;
+      }
+
+      going_up0 = going_up;
+      prev_v = v0;
+      curr_v = v0->next;
+      while (curr_v != v0)
+      {
+        if (curr_v->pt.y > prev_v->pt.y && going_up)
+        {
+          prev_v->flags = (prev_v->flags | VertexFlags::LocalMax);
+          going_up = false;
+        }
+        else if (curr_v->pt.y < prev_v->pt.y && !going_up)
+        {
+          going_up = true;
+          AddLocMin(*prev_v, polytype, is_open);
+        }
+        prev_v = curr_v;
+        curr_v = curr_v->next;
+      }
+
+      if (is_open)
+      {
+        prev_v->flags = prev_v->flags | VertexFlags::OpenEnd;
+        if (going_up)
+          prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
+        else
+          AddLocMin(*prev_v, polytype, is_open);
+      }
+      else if (going_up != going_up0)
+      {
+        if (going_up0) AddLocMin(*prev_v, polytype, false);
+        else prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
+      }
+    } // end processing current path
+
+    vertex_lists_.emplace_back(vertices);
+  } // end AddPaths
+
+
+  void ClipperBase::InsertScanline(int64_t y)
+  {
+    scanline_list_.push(y);
+  }
+
+
+  bool ClipperBase::PopScanline(int64_t& y)
+  {
+    if (scanline_list_.empty()) return false;
+    y = scanline_list_.top();
+    scanline_list_.pop();
+    while (!scanline_list_.empty() && y == scanline_list_.top())
+      scanline_list_.pop();  // Pop duplicates.
+    return true;
+  }
+
+
+  bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima)
+  {
+    if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false;
+    local_minima = (current_locmin_iter_++)->get();
+    return true;
+  }
+
+  void ClipperBase::DisposeAllOutRecs()
+  {
+    for (auto outrec : outrec_list_)
+    {
+      if (outrec->pts) DisposeOutPts(outrec);
+      delete outrec;
+    }
+    outrec_list_.resize(0);
+  }
+
+  void ClipperBase::DisposeVerticesAndLocalMinima()
+  {
+    minima_list_.clear();
+    for (auto v : vertex_lists_) delete[] v;
+    vertex_lists_.clear();
+  }
+
+
+  void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open)
+  {
+    //make sure the vertex is added only once ...
+    if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return;
+
+    vert.flags = (vert.flags | VertexFlags::LocalMin);
+    minima_list_.push_back(std::make_unique <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;
+      if (e2->join_with == JoinWith::Right)
+        e2 = e2->next_in_ael;
+      if (!e2) return; // should never happen and stops compiler warning :)
+      e.next_in_ael = e2->next_in_ael;
+      if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e;
+      e.prev_in_ael = e2;
+      e2->next_in_ael = &e;
+    }
+  }
+
+
+  void InsertRightEdge(Active& e, Active& e2)
+  {
+    e2.next_in_ael = e.next_in_ael;
+    if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2;
+    e2.prev_in_ael = &e;
+    e.next_in_ael = &e2;
+  }
+
+
+  void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y)
+  {
+    LocalMinima* local_minima;
+    Active* left_bound, * right_bound;
+    //Add any local minima (if any) at BotY ...
+    //nb: horizontal local minima edges should contain locMin.vertex.prev
+
+    while (PopLocalMinima(bot_y, local_minima))
+    {
+      if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::None)
+      {
+        left_bound = nullptr;
+      }
+      else
+      {
+        left_bound = new Active();
+        left_bound->bot = local_minima->vertex->pt;
+        left_bound->curr_x = left_bound->bot.x;
+        left_bound->wind_dx = -1;
+        left_bound->vertex_top = local_minima->vertex->prev;  // ie descending
+        left_bound->top = left_bound->vertex_top->pt;
+        left_bound->local_min = local_minima;
+        SetDx(*left_bound);
+      }
+
+      if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::None)
+      {
+        right_bound = nullptr;
+      }
+      else
+      {
+        right_bound = new Active();
+        right_bound->bot = local_minima->vertex->pt;
+        right_bound->curr_x = right_bound->bot.x;
+        right_bound->wind_dx = 1;
+        right_bound->vertex_top = local_minima->vertex->next;  // ie ascending
+        right_bound->top = right_bound->vertex_top->pt;
+        right_bound->local_min = local_minima;
+        SetDx(*right_bound);
+      }
+
+      //Currently LeftB is just the descending bound and RightB is the ascending.
+      //Now if the LeftB isn't on the left of RightB then we need swap them.
+      if (left_bound && right_bound)
+      {
+        if (IsHorizontal(*left_bound))
+        {
+          if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound);
+        }
+        else if (IsHorizontal(*right_bound))
+        {
+          if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound);
+        }
+        else if (left_bound->dx < right_bound->dx)
+          SwapActives(left_bound, right_bound);
+      }
+      else if (!left_bound)
+      {
+        left_bound = right_bound;
+        right_bound = nullptr;
+      }
+
+      bool contributing;
+      left_bound->is_left_bound = true;
+      InsertLeftEdge(*left_bound);
+
+      if (IsOpen(*left_bound))
+      {
+        SetWindCountForOpenPathEdge(*left_bound);
+        contributing = IsContributingOpen(*left_bound);
+      }
+      else
+      {
+        SetWindCountForClosedPathEdge(*left_bound);
+        contributing = IsContributingClosed(*left_bound);
+      }
+
+      if (right_bound)
+      {
+        right_bound->is_left_bound = false;
+        right_bound->wind_cnt = left_bound->wind_cnt;
+        right_bound->wind_cnt2 = left_bound->wind_cnt2;
+        InsertRightEdge(*left_bound, *right_bound);  ///////
+        if (contributing)
+        {
+          AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true);
+          if (!IsHorizontal(*left_bound))
+            CheckJoinLeft(*left_bound, left_bound->bot);
+        }
+
+        while (right_bound->next_in_ael &&
+          IsValidAelOrder(*right_bound->next_in_ael, *right_bound))
+        {
+          IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot);
+          SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael);
+        }
+
+        if (IsHorizontal(*right_bound))
+          PushHorz(*right_bound);
+        else
+        {
+          CheckJoinRight(*right_bound, right_bound->bot);
+          InsertScanline(right_bound->top.y);
+        }
+      }
+      else if (contributing)
+      {
+        StartOpenPath(*left_bound, left_bound->bot);
+      }
+
+      if (IsHorizontal(*left_bound))
+        PushHorz(*left_bound);
+      else
+        InsertScanline(left_bound->top.y);
+    }  // while (PopLocalMinima())
+  }
+
+
+  inline void ClipperBase::PushHorz(Active& e)
+  {
+    e.next_in_sel = (sel_ ? sel_ : nullptr);
+    sel_ = &e;
+  }
+
+
+  inline bool ClipperBase::PopHorz(Active*& e)
+  {
+    e = sel_;
+    if (!e) return false;
+    sel_ = sel_->next_in_sel;
+    return true;
+  }
+
+
+  OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2,
+    const Point64& pt, bool is_new)
+  {
+    OutRec* outrec = NewOutRec();
+    e1.outrec = outrec;
+    e2.outrec = outrec;
+
+    if (IsOpen(e1))
+    {
+      outrec->owner = nullptr;
+      outrec->is_open = true;
+      if (e1.wind_dx > 0)
+        SetSides(*outrec, e1, e2);
+      else
+        SetSides(*outrec, e2, e1);
+    }
+    else
+    {
+      Active* prevHotEdge = GetPrevHotEdge(e1);
+      //e.windDx is the winding direction of the **input** paths
+      //and unrelated to the winding direction of output polygons.
+      //Output orientation is determined by e.outrec.frontE which is
+      //the ascending edge (see AddLocalMinPoly).
+      if (prevHotEdge)
+      {
+        if (using_polytree_)
+          SetOwner(outrec, prevHotEdge->outrec);
+        if (OutrecIsAscending(prevHotEdge) == is_new)
+          SetSides(*outrec, e2, e1);
+        else
+          SetSides(*outrec, e1, e2);
+      }
+      else
+      {
+        outrec->owner = nullptr;
+        if (is_new)
+          SetSides(*outrec, e1, e2);
+        else
+          SetSides(*outrec, e2, e1);
+      }
+    }
+
+    OutPt* op = new OutPt(pt, outrec);
+    outrec->pts = op;
+    return op;
+  }
+
+
+  OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt)
+  {
+    if (IsJoined(e1)) Split(e1, pt);
+    if (IsJoined(e2)) Split(e2, pt);
+    
+    if (IsFront(e1) == IsFront(e2))
+    {
+      if (IsOpenEnd(e1))
+        SwapFrontBackSides(*e1.outrec);
+      else if (IsOpenEnd(e2))
+        SwapFrontBackSides(*e2.outrec);
+      else
+      {
+        succeeded_ = false;
+        return nullptr;
+      }
+    }
+
+    OutPt* result = AddOutPt(e1, pt);
+    if (e1.outrec == e2.outrec)
+    {
+      OutRec& outrec = *e1.outrec;
+      outrec.pts = result;
+
+      if (using_polytree_)
+      {
+        Active* e = GetPrevHotEdge(e1);
+        if (!e)
+          outrec.owner = nullptr; 
+        else
+          SetOwner(&outrec, e->outrec);
+        // nb: outRec.owner here is likely NOT the real
+        // owner but this will be checked in DeepCheckOwner()
+      }
+
+      UncoupleOutRec(e1);
+      result = outrec.pts;
+      if (outrec.owner && !outrec.owner->front_edge)
+        outrec.owner = GetRealOutRec(outrec.owner);
+    }
+    //and to preserve the winding orientation of outrec ...
+    else if (IsOpen(e1))
+    {
+      if (e1.wind_dx < 0)
+        JoinOutrecPaths(e1, e2);
+      else
+        JoinOutrecPaths(e2, e1);
+    }
+    else if (e1.outrec->idx < e2.outrec->idx)
+      JoinOutrecPaths(e1, e2);
+    else
+      JoinOutrecPaths(e2, e1);
+    return result;
+  }
+
+  void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2)
+  {
+    //join e2 outrec path onto e1 outrec path and then delete e2 outrec path
+    //pointers. (NB Only very rarely do the joining ends share the same coords.)
+    OutPt* p1_st = e1.outrec->pts;
+    OutPt* p2_st = e2.outrec->pts;
+    OutPt* p1_end = p1_st->next;
+    OutPt* p2_end = p2_st->next;
+    if (IsFront(e1))
+    {
+      p2_end->prev = p1_st;
+      p1_st->next = p2_end;
+      p2_st->next = p1_end;
+      p1_end->prev = p2_st;
+      e1.outrec->pts = p2_st;
+      e1.outrec->front_edge = e2.outrec->front_edge;
+      if (e1.outrec->front_edge)
+        e1.outrec->front_edge->outrec = e1.outrec;
+    }
+    else
+    {
+      p1_end->prev = p2_st;
+      p2_st->next = p1_end;
+      p1_st->next = p2_end;
+      p2_end->prev = p1_st;
+      e1.outrec->back_edge = e2.outrec->back_edge;
+      if (e1.outrec->back_edge)
+        e1.outrec->back_edge->outrec = e1.outrec;
+    }
+
+    //after joining, the e2.OutRec must contains no vertices ...
+    e2.outrec->front_edge = nullptr;
+    e2.outrec->back_edge = nullptr;
+    e2.outrec->pts = nullptr;
+    SetOwner(e2.outrec, e1.outrec);
+
+    if (IsOpenEnd(e1))
+    {
+      e2.outrec->pts = e1.outrec->pts;
+      e1.outrec->pts = nullptr;
+    }
+
+    //and e1 and e2 are maxima and are about to be dropped from the Actives list.
+    e1.outrec = nullptr;
+    e2.outrec = nullptr;
+  }
+
+  OutRec* ClipperBase::NewOutRec()
+  {
+    OutRec* result = new OutRec();
+    result->idx = outrec_list_.size();
+    outrec_list_.push_back(result);
+    result->pts = nullptr;
+    result->owner = nullptr;
+    result->polypath = nullptr;
+    result->is_open = false;
+    return result;
+  }
+
+
+  OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt)
+  {
+    OutPt* new_op = nullptr;
+
+    //Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
+    //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next
+    OutRec* outrec = e.outrec;
+    bool to_front = IsFront(e);
+    OutPt* op_front = outrec->pts;
+    OutPt* op_back = op_front->next;
+
+    if (to_front)
+    {
+      if (pt == op_front->pt)
+        return op_front;
+    }
+    else if (pt == op_back->pt)
+      return op_back;
+
+    new_op = new OutPt(pt, outrec);
+    op_back->prev = new_op;
+    new_op->prev = op_front;
+    new_op->next = op_back;
+    op_front->next = new_op;
+    if (to_front) outrec->pts = new_op;
+    return new_op;
+  }
+
+
+  void ClipperBase::CleanCollinear(OutRec* outrec)
+  {
+    outrec = GetRealOutRec(outrec);
+    if (!outrec || outrec->is_open) return;
+    if (!IsValidClosedPath(outrec->pts))
+    {
+      DisposeOutPts(outrec);
+      return;
+    }
+
+    OutPt* startOp = outrec->pts, * op2 = startOp;
+    for (; ; )
+    {
+      //NB if preserveCollinear == true, then only remove 180 deg. spikes
+      if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) &&
+        (op2->pt == op2->prev->pt ||
+          op2->pt == op2->next->pt || !PreserveCollinear ||
+          DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0))
+      {
+
+        if (op2 == outrec->pts) outrec->pts = op2->prev;
+
+        op2 = DisposeOutPt(op2);
+        if (!IsValidClosedPath(op2))
+        {
+          DisposeOutPts(outrec);
+          return;
+        }
+        startOp = op2;
+        continue;
+      }
+      op2 = op2->next;
+      if (op2 == startOp) break;
+    }
+    FixSelfIntersects(outrec);
+  }
+
+  void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp)
+  {
+    // splitOp.prev -> splitOp && 
+    // splitOp.next -> splitOp.next.next are intersecting
+    OutPt* prevOp = splitOp->prev;
+    OutPt* nextNextOp = splitOp->next->next;
+    outrec->pts = prevOp;
+
+    Point64 ip;
+    GetIntersectPoint(prevOp->pt, splitOp->pt,
+      splitOp->next->pt, nextNextOp->pt, ip);
+
+#ifdef USINGZ
+    if (zCallback_) zCallback_(prevOp->pt, splitOp->pt,
+      splitOp->next->pt, nextNextOp->pt, ip);
+#endif
+    double area1 = Area(outrec->pts);
+    double absArea1 = std::fabs(area1);
+    if (absArea1 < 2)
+    {
+      DisposeOutPts(outrec);
+      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;
+    }
+
+    if (absArea2 >= 1 &&
+      (absArea2 > absArea1 || (area2 > 0) == (area1 > 0)))
+    {
+      OutRec* newOr = NewOutRec();
+      newOr->owner = outrec->owner;
+      
+      if (using_polytree_)
+      {
+        if (!outrec->splits) outrec->splits = new OutRecList();
+        outrec->splits->push_back(newOr);
+      }
+
+      splitOp->outrec = newOr;
+      splitOp->next->outrec = newOr;
+      OutPt* newOp = new OutPt(ip, newOr);
+      newOp->prev = splitOp->next;
+      newOp->next = splitOp;
+      newOr->pts = newOp;
+      splitOp->prev = newOp;
+      splitOp->next->next = newOp;
+    }
+    else
+    {
+      delete splitOp->next;
+      delete splitOp;
+    }
+  }
+
+  void ClipperBase::FixSelfIntersects(OutRec* outrec)
+  {
+    OutPt* op2 = outrec->pts;
+    for (; ; )
+    {
+      // triangles can't self-intersect
+      if (op2->prev == op2->next->next) break;
+      if (SegmentsIntersect(op2->prev->pt,
+        op2->pt, op2->next->pt, op2->next->next->pt))
+      {
+        if (op2 == outrec->pts || op2->next == outrec->pts)
+          outrec->pts = outrec->pts->prev;
+        DoSplitOp(outrec, op2);
+        if (!outrec->pts) break;
+        op2 = outrec->pts;
+        continue;
+      }
+      else
+        op2 = op2->next;
+
+      if (op2 == outrec->pts) break;
+    }
+  }
+
+
+  inline void UpdateOutrecOwner(OutRec* outrec)
+  {
+    OutPt* opCurr = outrec->pts;
+    for (; ; )
+    {
+      opCurr->outrec = outrec;
+      opCurr = opCurr->next;
+      if (opCurr == outrec->pts) return;
+    }
+  }
+
+
+  OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt)
+  {
+    OutRec* outrec = NewOutRec();
+    outrec->is_open = true;
+
+    if (e.wind_dx > 0)
+    {
+      outrec->front_edge = &e;
+      outrec->back_edge = nullptr;
+    }
+    else
+    {
+      outrec->front_edge = nullptr;
+      outrec->back_edge = &e;
+    }
+
+    e.outrec = outrec;
+
+    OutPt* op = new OutPt(pt, outrec);
+    outrec->pts = op;
+    return op;
+  }
+
+
+  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 (IsJoined(*e)) Split(*e, e->bot);
+
+    if (IsHorizontal(*e)) return;
+    InsertScanline(e->top.y);
+
+    CheckJoinLeft(*e, e->bot);
+    CheckJoinRight(*e, e->bot);
+  }
+
+  Active* FindEdgeWithMatchingLocMin(Active* e)
+  {
+    Active* result = e->next_in_ael;
+    while (result)
+    {
+      if (result->local_min == e->local_min) return result;
+      else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr;
+      else result = result->next_in_ael;
+    }
+    result = e->prev_in_ael;
+    while (result)
+    {
+      if (result->local_min == e->local_min) return result;
+      else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr;
+      else result = result->prev_in_ael;
+    }
+    return result;
+  }
+
+
+  OutPt* ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt)
+  {
+    //MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
+    if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2)))
+    {
+      if (IsOpen(e1) && IsOpen(e2)) return nullptr;
+      Active* edge_o, * edge_c;
+      if (IsOpen(e1))
+      {
+        edge_o = &e1;
+        edge_c = &e2;
+      }
+      else
+      {
+        edge_o = &e2;
+        edge_c = &e1;
+      }
+      if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety
+
+      if (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
+
+    if (IsJoined(e1)) Split(e1, pt);
+    if (IsJoined(e2)) Split(e2, pt);
+
+    //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);
+#ifdef USINGZ
+        OutPt* op2 = AddLocalMinPoly(e1, e2, pt);
+        if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);
+        if (zCallback_) SetZ(e1, e2, op2->pt);
+#else
+        AddLocalMinPoly(e1, e2, pt);
+#endif
+      }
+      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;
+      if (e->join_with == JoinWith::Left)
+        e->curr_x = e->prev_in_ael->curr_x; // also avoids complications      
+      else
+        e->curr_x = TopX(*e, top_y);
+      e = e->next_in_ael;
+    }
+  }
+
+  bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees)
+  {
+    cliptype_ = ct;
+    fillrule_ = fillrule;
+    using_polytree_ = use_polytrees;
+    Reset();
+    int64_t y;
+    if (ct == ClipType::None || !PopScanline(y)) return true;
+
+    while (succeeded_)
+    {
+      InsertLocalMinimaIntoAEL(y);
+      Active* e;
+      while (PopHorz(e)) DoHorizontal(*e);
+      if (horz_seg_list_.size() > 0)
+      {
+        ConvertHorzSegsToJoins();
+        horz_seg_list_.clear();
+      }
+      bot_y_ = y;  // bot_y_ == bottom of scanbeam
+      if (!PopScanline(y)) break;  // y new top of scanbeam
+      DoIntersections(y);
+      DoTopOfScanbeam(y);
+      while (PopHorz(e)) DoHorizontal(*e);
+    }
+    if (succeeded_) ProcessHorzJoins();
+    return succeeded_;
+  }
+
+  inline void FixOutRecPts(OutRec* outrec)
+  {
+    OutPt* op = outrec->pts;
+    do {
+      op->outrec = outrec;
+      op = op->next;
+    } while (op != outrec->pts);
+  }
+
+  inline Rect64 GetBounds(OutPt* op)
+  {
+    Rect64 result(op->pt.x, op->pt.y, op->pt.x, op->pt.y);
+    OutPt* op2 = op->next;
+    while (op2 != op)
+    {
+      if (op2->pt.x < result.left) result.left = op2->pt.x;
+      else if (op2->pt.x > result.right) result.right = op2->pt.x;
+      if (op2->pt.y < result.top) result.top = op2->pt.y;
+      else if (op2->pt.y > result.bottom) result.bottom = op2->pt.y;
+      op2 = op2->next;
+    }
+    return result;
+  }
+
+  static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op)
+  {
+    if (op == op->next || op->prev == op->next)
+      return PointInPolygonResult::IsOutside;
+  
+    OutPt* op2 = op;
+    do
+    {
+      if (op->pt.y != pt.y) break;
+      op = op->next;
+    } while (op != op2);
+    if (op->pt.y == pt.y) // not a proper polygon
+      return PointInPolygonResult::IsOutside;
+
+    bool is_above = op->pt.y < pt.y, starting_above = is_above;
+    int val = 0;
+    op2 = op->next;
+    while (op2 != op)
+    {
+      if (is_above)
+        while (op2 != op && op2->pt.y < pt.y) op2 = op2->next;
+      else
+        while (op2 != op && op2->pt.y > pt.y) op2 = op2->next;
+      if (op2 == op) break;
+
+      // must have touched or crossed the pt.Y horizonal
+      // and this must happen an even number of times
+
+      if (op2->pt.y == pt.y) // touching the horizontal
+      {
+        if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y &&
+          (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x)))
+          return PointInPolygonResult::IsOn;
+
+        op2 = op2->next;
+        if (op2 == op) break;
+        continue;
+      }
+
+      if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x);
+      // do nothing because
+      // we're only interested in edges crossing on the left
+      else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x))
+        val = 1 - val; // toggle val
+      else
+      {
+        double d = CrossProduct(op2->prev->pt, op2->pt, pt);
+        if (d == 0) return PointInPolygonResult::IsOn;
+        if ((d < 0) == is_above) val = 1 - val;
+      }
+      is_above = !is_above;
+      op2 = op2->next;
+    }
+
+    if (is_above != starting_above)
+    {
+      double d = CrossProduct(op2->prev->pt, op2->pt, pt);
+      if (d == 0) return PointInPolygonResult::IsOn;
+      if ((d < 0) == is_above) val = 1 - val;
+    }
+
+    if (val == 0) return PointInPolygonResult::IsOutside;
+    else return PointInPolygonResult::IsInside;
+  }
+
+  inline bool Path1InsidePath2(OutPt* op1, OutPt* op2)
+  {
+    // we need to make some accommodation for rounding errors
+    // so we won't jump if the first vertex is found outside
+    int outside_cnt = 0;
+    OutPt* op = op1;
+    do
+    {
+      PointInPolygonResult result = PointInOpPolygon(op->pt, op2);
+      if (result == PointInPolygonResult::IsOutside) ++outside_cnt;
+      else if (result == PointInPolygonResult::IsInside) --outside_cnt;
+      op = op->next;
+    } while (op != op1 && std::abs(outside_cnt) < 2);
+    if (std::abs(outside_cnt) > 1) return (outside_cnt < 0);
+    // since path1's location is still equivocal, check its midpoint
+    Point64 mp = GetBounds(op).MidPoint();
+    return  PointInOpPolygon(mp, op2) == PointInPolygonResult::IsInside;
+  }
+
+  inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN)
+  {
+    if (opP->pt.x == opN->pt.x) return false;
+    if (opP->pt.x < opN->pt.x)
+    {
+      hs.left_op = opP;
+      hs.right_op = opN;
+      hs.left_to_right = true;
+    }
+    else
+    {
+      hs.left_op = opN;
+      hs.right_op = opP;
+      hs.left_to_right = false;
+    }
+    return true;
+  }
+
+  inline bool UpdateHorzSegment(HorzSegment& hs)
+  {
+    OutPt* op = hs.left_op;
+    OutRec* outrec = GetRealOutRec(op->outrec);
+    bool outrecHasEdges = outrec->front_edge;
+    int64_t curr_y = op->pt.y;
+    OutPt* opP = op, * opN = op;
+    if (outrecHasEdges)
+    {
+      OutPt* opA = outrec->pts, * opZ = opA->next;
+      while (opP != opZ && opP->prev->pt.y == curr_y) 
+        opP = opP->prev;
+      while (opN != opA && opN->next->pt.y == curr_y)
+        opN = opN->next;
+    }
+    else
+    {
+      while (opP->prev != opN && opP->prev->pt.y == curr_y)
+        opP = opP->prev;
+      while (opN->next != opP && opN->next->pt.y == curr_y)
+        opN = opN->next;
+    }
+    bool result = 
+      SetHorzSegHeadingForward(hs, opP, opN) &&
+      !hs.left_op->horz;
+
+    if (result)
+      hs.left_op->horz = &hs;
+    else
+      hs.right_op = nullptr; // (for sorting)
+    return result;
+  }
+  
+  void ClipperBase::ConvertHorzSegsToJoins()
+  {
+    auto j = std::count_if(horz_seg_list_.begin(), 
+      horz_seg_list_.end(),
+      [](HorzSegment& hs) { return UpdateHorzSegment(hs); });
+    if (j < 2) return;
+    std::sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter());
+
+    HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2;
+    HorzSegmentList::iterator hs_end = hs1 +j;
+    HorzSegmentList::iterator hs_end1 = hs_end - 1;
+
+    for (; hs1 != hs_end1; ++hs1)
+    {
+      for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2)
+      {
+        if (hs2->left_op->pt.x >= hs1->right_op->pt.x) break;
+        if (hs2->left_to_right == hs1->left_to_right ||
+          (hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue;
+        int64_t curr_y = hs1->left_op->pt.y;
+        if (hs1->left_to_right)
+        {
+          while (hs1->left_op->next->pt.y == curr_y &&
+            hs1->left_op->next->pt.x <= hs2->left_op->pt.x)
+            hs1->left_op = hs1->left_op->next;
+          while (hs2->left_op->prev->pt.y == curr_y &&
+            hs2->left_op->prev->pt.x <= hs1->left_op->pt.x)
+            hs2->left_op = hs2->left_op->prev;
+          HorzJoin join = HorzJoin(
+            DuplicateOp(hs1->left_op, true),
+            DuplicateOp(hs2->left_op, false));
+          horz_join_list_.push_back(join);
+        }
+        else
+        {
+          while (hs1->left_op->prev->pt.y == curr_y &&
+            hs1->left_op->prev->pt.x <= hs2->left_op->pt.x)
+            hs1->left_op = hs1->left_op->prev;
+          while (hs2->left_op->next->pt.y == curr_y &&
+            hs2->left_op->next->pt.x <= hs1->left_op->pt.x)
+            hs2->left_op = hs2->left_op->next;
+          HorzJoin join = HorzJoin(
+            DuplicateOp(hs2->left_op, true),
+            DuplicateOp(hs1->left_op, false));
+          horz_join_list_.push_back(join);
+        }
+      } 
+    } 
+  }
+
+  void ClipperBase::ProcessHorzJoins()
+  {
+    for (const HorzJoin& j : horz_join_list_)
+    {
+      OutRec* or1 = GetRealOutRec(j.op1->outrec);
+      OutRec* or2 = GetRealOutRec(j.op2->outrec);
+
+      OutPt* op1b = j.op1->next;
+      OutPt* op2b = j.op2->prev;
+      j.op1->next = j.op2;
+      j.op2->prev = j.op1;
+      op1b->prev = op2b;
+      op2b->next = op1b;
+
+      if (or1 == or2)
+      {
+        or2 = new OutRec(); 
+        or2->pts = op1b;
+        FixOutRecPts(or2);
+        if (or1->pts->outrec == or2)
+        {
+          or1->pts = j.op1;
+          or1->pts->outrec = or1;
+        }
+
+        if (using_polytree_)
+        {
+          if (Path1InsidePath2(or2->pts, or1->pts))
+            SetOwner(or2, or1);
+          else if (Path1InsidePath2(or1->pts, or2->pts))
+            SetOwner(or1, or2);
+          else
+            or2->owner = or1;
+        }
+        else
+          or2->owner = or1;
+
+        outrec_list_.push_back(or2);
+      }
+      else
+      {
+        or2->pts = nullptr;
+        if (using_polytree_)
+          SetOwner(or2, or1);
+        else
+          or2->owner = or1;
+      }
+    }
+  }
+
+  void ClipperBase::DoIntersections(const int64_t top_y)
+  {
+    if (BuildIntersectList(top_y))
+    {
+      ProcessIntersectList();
+      intersect_nodes_.clear();
+    }
+  }
+
+  void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
+  {
+    Point64 ip;
+    if (!GetIntersectPoint(e1.bot, e1.top, e2.bot, e2.top, ip))
+      ip = Point64(e1.curr_x, top_y); //parallel edges
+
+    //rounding errors can occasionally place the calculated intersection
+    //point either below or above the scanbeam, so check and correct ...
+    if (ip.y > bot_y_ || ip.y < top_y)
+    {
+      double abs_dx1 = std::fabs(e1.dx);
+      double abs_dx2 = std::fabs(e2.dx);
+      if (abs_dx1 > 100 && abs_dx2 > 100)
+      {
+        if (abs_dx1 > abs_dx2)
+          ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);
+        else
+          ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);
+      }
+      else if (abs_dx1 > 100)
+        ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);
+      else if (abs_dx2 > 100)
+        ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);
+      else 
+      {
+        if (ip.y < top_y) ip.y = top_y;
+        else ip.y = bot_y_;
+        if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y);
+        else ip.x = TopX(e2, ip.y);
+      }
+    }
+    intersect_nodes_.push_back(IntersectNode(&e1, &e2, ip));
+  }
+
+  bool ClipperBase::BuildIntersectList(const int64_t top_y)
+  {
+    if (!actives_ || !actives_->next_in_ael) return false;
+
+    //Calculate edge positions at the top of the current scanbeam, and from this
+    //we will determine the intersections required to reach these new positions.
+    AdjustCurrXAndCopyToSEL(top_y);
+    //Find all edge intersections in the current scanbeam using a stable merge
+    //sort that ensures only adjacent edges are intersecting. Intersect info is
+    //stored in FIntersectList ready to be processed in ProcessIntersectList.
+    //Re merge sorts see https://stackoverflow.com/a/46319131/359538
+
+    Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp;
+
+    while (left && left->jump)
+    {
+      Active* prev_base = nullptr;
+      while (left && left->jump)
+      {
+        curr_base = left;
+        right = left->jump;
+        l_end = right;
+        r_end = right->jump;
+        left->jump = r_end;
+        while (left != l_end && right != r_end)
+        {
+          if (right->curr_x < left->curr_x)
+          {
+            tmp = right->prev_in_sel;
+            for (; ; )
+            {
+              AddNewIntersectNode(*tmp, *right, top_y);
+              if (tmp == left) break;
+              tmp = tmp->prev_in_sel;
+            }
+
+            tmp = right;
+            right = ExtractFromSEL(tmp);
+            l_end = right;
+            Insert1Before2InSEL(tmp, left);
+            if (left == curr_base)
+            {
+              curr_base = tmp;
+              curr_base->jump = r_end;
+              if (!prev_base) sel_ = curr_base;
+              else prev_base->jump = curr_base;
+            }
+          }
+          else left = left->next_in_sel;
+        }
+        prev_base = curr_base;
+        left = r_end;
+      }
+      left = sel_;
+    }
+    return intersect_nodes_.size() > 0;
+  }
+
+  void ClipperBase::ProcessIntersectList()
+  {
+    //We now have a list of intersections required so that edges will be
+    //correctly positioned at the top of the scanbeam. However, it's important
+    //that edge intersections are processed from the bottom up, but it's also
+    //crucial that intersections only occur between adjacent edges.
+
+    //First we do a quicksort so intersections proceed in a bottom up order ...
+    std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort);
+    //Now as we process these intersections, we must sometimes adjust the order
+    //to ensure that intersecting edges are always adjacent ...
+
+    IntersectNodeList::iterator node_iter, node_iter2;
+    for (node_iter = intersect_nodes_.begin();
+      node_iter != intersect_nodes_.end();  ++node_iter)
+    {
+      if (!EdgesAdjacentInAEL(*node_iter))
+      {
+        node_iter2 = node_iter + 1;
+        while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2;
+        std::swap(*node_iter, *node_iter2);
+      }
+
+      IntersectNode& node = *node_iter;
+      IntersectEdges(*node.edge1, *node.edge2, node.pt);
+      SwapPositionsInAEL(*node.edge1, *node.edge2);
+
+      node.edge1->curr_x = node.pt.x;
+      node.edge2->curr_x = node.pt.x;
+      CheckJoinLeft(*node.edge2, node.pt, true);
+      CheckJoinRight(*node.edge1, node.pt, true);
+    }
+  }
+
+  void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2)
+  {
+    //preconditon: e1 must be immediately to the left of e2
+    Active* next = e2.next_in_ael;
+    if (next) next->prev_in_ael = &e1;
+    Active* prev = e1.prev_in_ael;
+    if (prev) prev->next_in_ael = &e2;
+    e2.prev_in_ael = prev;
+    e2.next_in_ael = &e1;
+    e1.prev_in_ael = &e2;
+    e1.next_in_ael = next;
+    if (!e2.prev_in_ael) actives_ = &e2;
+  }
+
+  inline OutPt* GetLastOp(const Active& hot_edge)
+  {
+    OutRec* outrec = hot_edge.outrec;
+    OutPt* result = outrec->pts;
+    if (&hot_edge != outrec->front_edge)
+      result = result->next;
+    return result;
+  }
+
+  void ClipperBase::AddTrialHorzJoin(OutPt* op)
+  {
+    if (op->outrec->is_open) return;
+    horz_seg_list_.push_back(HorzSegment(op));
+  }
+
+  bool ClipperBase::ResetHorzDirection(const Active& horz, 
+    const Vertex* max_vertex, int64_t& horz_left, int64_t& horz_right)
+  {
+    if (horz.bot.x == horz.top.x)
+    {
+      //the horizontal edge is going nowhere ...
+      horz_left = horz.curr_x;
+      horz_right = horz.curr_x;
+      Active* e = horz.next_in_ael;
+      while (e && e->vertex_top != max_vertex) e = e->next_in_ael;
+      return e != nullptr;
+    }
+    else if (horz.curr_x < horz.top.x)
+    {
+      horz_left = horz.curr_x;
+      horz_right = horz.top.x;
+      return true;
+    }
+    else
+    {
+      horz_left = horz.top.x;
+      horz_right = horz.curr_x;
+      return false;  // right to left
+    }
+  }
+
+  inline bool HorzIsSpike(const Active& horzEdge)
+  {
+    Point64 nextPt = NextVertex(horzEdge)->pt;
+    return (nextPt.y == horzEdge.bot.y) &&
+      (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x);
+  }
+
+  inline void TrimHorz(Active& horzEdge, bool preserveCollinear)
+  {
+    bool wasTrimmed = false;
+    Point64 pt = NextVertex(horzEdge)->pt;
+    while (pt.y == horzEdge.top.y)
+    {
+      //always trim 180 deg. spikes (in closed paths)
+      //but otherwise break if preserveCollinear = true
+      if (preserveCollinear &&
+        ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x)))
+        break;
+
+      horzEdge.vertex_top = NextVertex(horzEdge);
+      horzEdge.top = pt;
+      wasTrimmed = true;
+      if (IsMaxima(horzEdge)) break;
+      pt = NextVertex(horzEdge)->pt;
+    }
+
+    if (wasTrimmed) SetDx(horzEdge); // +/-infinity
+  }
+
+  void ClipperBase::DoHorizontal(Active& horz)
+    /*******************************************************************************
+        * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or    *
+        * bottom of a scanbeam) are processed as if layered.The order in which HEs     *
+        * are processed doesn't matter. HEs intersect with the bottom vertices of      *
+        * other HEs[#] and with non-horizontal edges [*]. Once these intersections     *
+        * are completed, intermediate HEs are 'promoted' to the next edge in their     *
+        * bounds, and they in turn may be intersected[%] by other HEs.                 *
+        *                                                                              *
+        * eg: 3 horizontals at a scanline:    /   |                     /           /  *
+        *              |                     /    |     (HE3)o ========%========== o   *
+        *              o ======= o(HE2)     /     |         /         /                *
+        *          o ============#=========*======*========#=========o (HE1)           *
+        *         /              |        /       |       /                            *
+        *******************************************************************************/
+  {
+    Point64 pt;
+    bool horzIsOpen = IsOpen(horz);
+    int64_t y = horz.bot.y;
+    Vertex* vertex_max;
+    if (horzIsOpen)
+      vertex_max = GetCurrYMaximaVertex_Open(horz);
+    else
+      vertex_max = GetCurrYMaximaVertex(horz);
+
+    // remove 180 deg.spikes and also simplify
+    // consecutive horizontals when PreserveCollinear = true
+    if (vertex_max && !horzIsOpen && vertex_max != horz.vertex_top)
+      TrimHorz(horz, PreserveCollinear);
+
+    int64_t horz_left, horz_right;
+    bool is_left_to_right =
+      ResetHorzDirection(horz, vertex_max, horz_left, horz_right);
+
+    if (IsHotEdge(horz))
+    {
+#ifdef USINGZ
+      OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z));
+#else
+      OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y));
+#endif
+      AddTrialHorzJoin(op);
+    }
+    OutRec* currHorzOutrec = horz.outrec;
+
+    while (true) // loop through consec. horizontal edges
+    {
+      Active* e;
+      if (is_left_to_right) e = horz.next_in_ael;
+      else e = horz.prev_in_ael;
+
+      while (e)
+      {
+        if (e->vertex_top == vertex_max)
+        {
+          if (IsHotEdge(horz) && IsJoined(*e))
+            Split(*e, e->top);
+
+          if (IsHotEdge(horz))
+          {
+            while (horz.vertex_top != vertex_max)
+            {
+              AddOutPt(horz, horz.top);
+              UpdateEdgeIntoAEL(&horz);
+            }
+            if (is_left_to_right)
+              AddLocalMaxPoly(horz, *e, horz.top);
+            else
+              AddLocalMaxPoly(*e, horz, horz.top);
+          }
+          DeleteFromAEL(*e);
+          DeleteFromAEL(horz);
+          return;
+        }
+
+        //if horzEdge is a maxima, keep going until we reach
+        //its maxima pair, otherwise check for break conditions
+        if (vertex_max != horz.vertex_top || IsOpenEnd(horz))
+        {
+          //otherwise stop when 'ae' is beyond the end of the horizontal line
+          if ((is_left_to_right && e->curr_x > horz_right) ||
+            (!is_left_to_right && e->curr_x < horz_left)) break;
+
+          if (e->curr_x == horz.top.x && !IsHorizontal(*e))
+          {
+            pt = NextVertex(horz)->pt;
+            if (is_left_to_right)
+            {
+              //with open paths we'll only break once past horz's end
+              if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e))
+              {
+                if (TopX(*e, pt.y) > pt.x) break;
+              }
+              //otherwise we'll only break when horz's outslope is greater than e's
+              else if (TopX(*e, pt.y) >= pt.x) break;
+            }
+            else
+            {
+              if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e))
+              {
+                if (TopX(*e, pt.y) < pt.x) break;
+              }
+              else if (TopX(*e, pt.y) <= pt.x) break;
+            }
+          }
+        }
+
+        pt = Point64(e->curr_x, horz.bot.y);
+        if (is_left_to_right)
+        {
+          IntersectEdges(horz, *e, pt);
+          SwapPositionsInAEL(horz, *e);
+          horz.curr_x = e->curr_x;
+          e = horz.next_in_ael;
+        }
+        else
+        {
+          IntersectEdges(*e, horz, pt);
+          SwapPositionsInAEL(*e, horz);
+          horz.curr_x = e->curr_x;
+          e = horz.prev_in_ael;
+        }
+
+        if (horz.outrec && horz.outrec != currHorzOutrec)
+        {
+          currHorzOutrec = horz.outrec;
+          //nb: The outrec containining the op returned by IntersectEdges
+          //above may no longer be associated with horzEdge.
+          AddTrialHorzJoin(GetLastOp(horz));
+        }
+      }
+
+      //check if we've finished with (consecutive) horizontals ...
+      if (horzIsOpen && IsOpenEnd(horz)) // ie open at top
+      {
+        if (IsHotEdge(horz))
+        {
+          AddOutPt(horz, horz.top);
+          if (IsFront(horz))
+            horz.outrec->front_edge = nullptr;
+          else
+            horz.outrec->back_edge = nullptr;
+          horz.outrec = nullptr;
+        }
+        DeleteFromAEL(horz);
+        return;
+      }
+      else if (NextVertex(horz)->pt.y != horz.top.y)
+        break;
+
+      //still more horizontals in bound to process ...
+      if (IsHotEdge(horz))
+        AddOutPt(horz, horz.top);
+      UpdateEdgeIntoAEL(&horz);
+
+      if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz))
+        TrimHorz(horz, true);
+
+      is_left_to_right =
+        ResetHorzDirection(horz, vertex_max, horz_left, horz_right);
+    }
+
+    if (IsHotEdge(horz)) AddOutPt(horz, horz.top);
+    UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz.
+  }
+
+  void ClipperBase::DoTopOfScanbeam(const int64_t y)
+  {
+    sel_ = nullptr;  // sel_ is reused to flag horizontals (see PushHorz below)
+    Active* e = actives_;
+    while (e)
+    {
+      //nb: 'e' will never be horizontal here
+      if (e->top.y == y)
+      {
+        e->curr_x = e->top.x;
+        if (IsMaxima(*e))
+        {
+          e = DoMaxima(*e);  // TOP OF BOUND (MAXIMA)
+          continue;
+        }
+        else
+        {
+          //INTERMEDIATE VERTEX ...
+          if (IsHotEdge(*e)) AddOutPt(*e, e->top);
+          UpdateEdgeIntoAEL(e);
+          if (IsHorizontal(*e))
+            PushHorz(*e);  // horizontals are processed later
+        }
+      }
+      else // i.e. not the top of the edge
+        e->curr_x = TopX(*e, y);
+
+      e = e->next_in_ael;
+    }
+  }
+
+
+  Active* ClipperBase::DoMaxima(Active& e)
+  {
+    Active* next_e, * prev_e, * max_pair;
+    prev_e = e.prev_in_ael;
+    next_e = e.next_in_ael;
+    if (IsOpenEnd(e))
+    {
+      if (IsHotEdge(e)) AddOutPt(e, e.top);
+      if (!IsHorizontal(e))
+      {
+        if (IsHotEdge(e))
+        {
+          if (IsFront(e))
+            e.outrec->front_edge = nullptr;
+          else
+            e.outrec->back_edge = nullptr;
+          e.outrec = nullptr;
+        }
+        DeleteFromAEL(e);
+      }
+      return next_e;
+    }
+
+    max_pair = GetMaximaPair(e);
+    if (!max_pair) return next_e;  // eMaxPair is horizontal
+
+    if (IsJoined(e)) Split(e, e.top);
+    if (IsJoined(*max_pair)) Split(*max_pair, max_pair->top);
+
+    //only non-horizontal maxima here.
+    //process any edges between maxima pair ...
+    while (next_e != max_pair)
+    {
+      IntersectEdges(e, *next_e, e.top);
+      SwapPositionsInAEL(e, *next_e);
+      next_e = e.next_in_ael;
+    }
+
+    if (IsOpen(e))
+    {
+      if (IsHotEdge(e))
+        AddLocalMaxPoly(e, *max_pair, e.top);
+      DeleteFromAEL(*max_pair);
+      DeleteFromAEL(e);
+      return (prev_e ? prev_e->next_in_ael : actives_);
+    }
+
+    // e.next_in_ael== max_pair ...
+    if (IsHotEdge(e))
+      AddLocalMaxPoly(e, *max_pair, e.top);
+
+    DeleteFromAEL(e);
+    DeleteFromAEL(*max_pair);
+    return (prev_e ? prev_e->next_in_ael : actives_);
+  }
+
+  void ClipperBase::Split(Active& e, const Point64& pt)
+  {
+    if (e.join_with == JoinWith::Right)
+    {
+      e.join_with = JoinWith::None;
+      e.next_in_ael->join_with = JoinWith::None;
+      AddLocalMinPoly(e, *e.next_in_ael, pt, true);
+    }
+    else
+    {
+      e.join_with = JoinWith::None;
+      e.prev_in_ael->join_with = JoinWith::None;
+      AddLocalMinPoly(*e.prev_in_ael, e, pt, true);
+    }
+  }
+
+  void ClipperBase::CheckJoinLeft(Active& e, 
+    const Point64& pt, bool check_curr_x)
+  {
+    Active* prev = e.prev_in_ael;
+    if (IsOpen(e) || !IsHotEdge(e) || !prev || 
+      IsOpen(*prev) || !IsHotEdge(*prev) ||
+      pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) // avoid trivial joins
+        return;
+
+    if (check_curr_x)
+    {
+      if (DistanceFromLineSqrd(pt, prev->bot, prev->top) > 0.25) return;
+    }
+    else if (e.curr_x != prev->curr_x) return;
+    if (CrossProduct(e.top, pt, prev->top)) return;
+
+    if (e.outrec->idx == prev->outrec->idx)
+      AddLocalMaxPoly(*prev, e, pt);
+    else if (e.outrec->idx < prev->outrec->idx)
+      JoinOutrecPaths(e, *prev);
+    else
+      JoinOutrecPaths(*prev, e);
+    prev->join_with = JoinWith::Right;
+    e.join_with = JoinWith::Left;
+  }
+
+  void ClipperBase::CheckJoinRight(Active& e, 
+    const Point64& pt, bool check_curr_x)
+  {
+    Active* next = e.next_in_ael;
+    if (IsOpen(e) || !IsHotEdge(e) || 
+      !next || IsOpen(*next) || !IsHotEdge(*next) ||
+      pt.y < e.top.y +2 || pt.y < next->top.y +2) // avoids trivial joins
+        return;      
+
+    if (check_curr_x)
+    {
+      if (DistanceFromLineSqrd(pt, next->bot, next->top) > 0.35) return;
+    }
+    else if (e.curr_x != next->curr_x) return;
+    if (CrossProduct(e.top, pt, next->top)) return;
+      
+    if (e.outrec->idx == next->outrec->idx)
+      AddLocalMaxPoly(e, *next, pt);
+    else if (e.outrec->idx < next->outrec->idx)
+      JoinOutrecPaths(e, *next);
+    else
+      JoinOutrecPaths(*next, e);
+    e.join_with = JoinWith::Right;
+    next->join_with = JoinWith::Left;
+  }
+
+  inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2)
+  {
+    OutRec* outrec = GetRealOutRec(op->outrec);
+    op2 = op;
+    if (outrec->front_edge)
+    {
+      while (op->prev != outrec->pts &&
+        op->prev->pt.y == op->pt.y) op = op->prev;
+      while (op2 != outrec->pts &&
+        op2->next->pt.y == op2->pt.y) op2 = op2->next;
+      return op2 != op;
+    }
+    else
+    {
+      while (op->prev != op2 && op->prev->pt.y == op->pt.y)
+        op = op->prev;
+      while (op2->next != op && op2->next->pt.y == op2->pt.y)
+        op2 = op2->next;
+      return op2 != op && op2->next != op;
+    }
+  }
+
+  bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path)
+  {
+    if (!op || op->next == op || (!isOpen && op->next == op->prev))
+      return false;
+
+    path.resize(0);
+    Point64 lastPt;
+    OutPt* op2;
+    if (reverse)
+    {
+      lastPt = op->pt;
+      op2 = op->prev;
+    }
+    else
+    {
+      op = op->next;
+      lastPt = op->pt;
+      op2 = op->next;
+    }
+    path.push_back(lastPt);
+
+    while (op2 != op)
+    {
+      if (op2->pt != lastPt)
+      {
+        lastPt = op2->pt;
+        path.push_back(lastPt);
+      }
+      if (reverse)
+        op2 = op2->prev;
+      else
+        op2 = op2->next;
+    }
+
+    if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false;
+    else return true;
+  }
+
+  bool ClipperBase::CheckBounds(OutRec* outrec)
+  {
+    if (!outrec->pts) return false;
+    if (!outrec->bounds.IsEmpty()) return true;
+    CleanCollinear(outrec);
+    if (!outrec->pts || 
+      !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path))
+        return false;
+    outrec->bounds = GetBounds(outrec->path);
+    return true;
+  }
+
+  void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath)
+  {
+    // pre-condition: outrec will have valid bounds
+    // post-condition: if a valid path, outrec will have a polypath
+
+    if (outrec->polypath || outrec->bounds.IsEmpty()) return;
+
+    while (outrec->owner &&
+      (!outrec->owner->pts || !CheckBounds(outrec->owner)))
+        outrec->owner = outrec->owner->owner;
+
+    if (outrec->owner && !outrec->owner->polypath) 
+      RecursiveCheckOwners(outrec->owner, polypath);
+
+    while (outrec->owner)
+      if (outrec->owner->bounds.Contains(outrec->bounds) &&
+        Path1InsidePath2(outrec->pts, outrec->owner->pts))
+        break; // found - owner contain outrec!
+      else
+        outrec->owner = outrec->owner->owner;
+
+    if (outrec->owner)
+      outrec->polypath = outrec->owner->polypath->AddChild(outrec->path);
+    else
+      outrec->polypath = polypath->AddChild(outrec->path);
+  }
+
+  void ClipperBase::DeepCheckOwners(OutRec* outrec, PolyPath* polypath)
+  {
+    RecursiveCheckOwners(outrec, polypath);
+
+    while (outrec->owner && outrec->owner->splits)
+    {
+      OutRec* split = nullptr;
+      for (auto s : *outrec->owner->splits)
+      {
+        split = GetRealOutRec(s);
+        if (split && split != outrec &&
+          split != outrec->owner && CheckBounds(split) &&
+          split->bounds.Contains(outrec->bounds) &&
+            Path1InsidePath2(outrec->pts, split->pts)) 
+        {
+          RecursiveCheckOwners(split, polypath);
+          outrec->owner = split; //found in split
+          break; // inner 'for' loop
+        }
+        else
+          split = nullptr;
+      }
+      if (!split) break;
+    }
+  }
+
+  void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen)
+  {
+    solutionClosed.resize(0);
+    solutionClosed.reserve(outrec_list_.size());
+    if (solutionOpen)
+    {
+      solutionOpen->resize(0);
+      solutionOpen->reserve(outrec_list_.size());
+    }
+
+    // nb: outrec_list_.size() may change in the following
+    // while loop because polygons may be split during
+    // calls to CleanCollinear which calls FixSelfIntersects
+    for (size_t i = 0; i < outrec_list_.size(); ++i)
+    {
+      OutRec* outrec = outrec_list_[i];
+      if (outrec->pts == nullptr) continue;
+
+      Path64 path;
+      if (solutionOpen && outrec->is_open)
+      {
+        if (BuildPath64(outrec->pts, ReverseSolution, true, path))
+          solutionOpen->emplace_back(std::move(path));
+      }
+      else
+      {
+        // nb: CleanCollinear can add to outrec_list_
+        CleanCollinear(outrec);
+        //closed paths should always return a Positive orientation
+        if (BuildPath64(outrec->pts, ReverseSolution, false, path))
+          solutionClosed.emplace_back(std::move(path));
+      }
+    }
+  }
+
+  void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths)
+  {
+    polytree.Clear();
+    open_paths.resize(0);
+    if (has_open_paths_)
+      open_paths.reserve(outrec_list_.size());
+    
+    // outrec_list_.size() is not static here because
+    // CheckBounds below can indirectly add additional
+    // OutRec (via FixOutRecPts & CleanCollinear)
+    for (size_t i = 0; i < outrec_list_.size(); ++i)
+    {
+      OutRec* outrec = outrec_list_[i];
+      if (!outrec || !outrec->pts) continue;
+      if (outrec->is_open)
+      {
+        Path64 path;
+        if (BuildPath64(outrec->pts, ReverseSolution, true, path))
+          open_paths.push_back(path);
+        continue;
+      }
+
+      if (CheckBounds(outrec))
+        DeepCheckOwners(outrec, &polytree);
+    }
+  }
+
+  bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale)
+  {
+    if (!op || op->next == op || (!isOpen && op->next == op->prev)) 
+      return false;
+
+    path.resize(0);
+    Point64 lastPt;
+    OutPt* op2;
+    if (reverse)
+    {
+      lastPt = op->pt;
+      op2 = op->prev;
+    }
+    else
+    {
+      op = op->next;
+      lastPt = op->pt;
+      op2 = op->next;
+    }
+#ifdef USINGZ
+    path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z));
+#else
+    path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale));
+#endif
+
+    while (op2 != op)
+    {
+      if (op2->pt != lastPt)
+      {
+        lastPt = op2->pt;
+#ifdef USINGZ
+        path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z));
+#else
+        path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale));
+#endif
+        
+      }
+      if (reverse)
+        op2 = op2->prev;
+      else
+        op2 = op2->next;
+    }
+    if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false;
+    return true;
+  }
+
+  void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen)
+  {
+    solutionClosed.resize(0);
+    solutionClosed.reserve(outrec_list_.size());
+    if (solutionOpen)
+    {
+      solutionOpen->resize(0);
+      solutionOpen->reserve(outrec_list_.size());
+    }
+
+    // outrec_list_.size() is not static here because
+    // CleanCollinear below can indirectly add additional
+    // OutRec (via FixOutRecPts)
+    for (std::size_t i = 0; i < outrec_list_.size(); ++i)
+    {
+      OutRec* outrec = outrec_list_[i];
+      if (outrec->pts == nullptr) continue;
+
+      PathD path;
+      if (solutionOpen && outrec->is_open)
+      {
+        if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_))
+          solutionOpen->emplace_back(std::move(path));
+      }
+      else
+      {
+        CleanCollinear(outrec);
+        //closed paths should always return a Positive orientation
+        if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_))
+          solutionClosed.emplace_back(std::move(path));
+      }
+    }
+  }
+
+  void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths)
+  {
+    polytree.Clear();
+    open_paths.resize(0);
+    if (has_open_paths_)
+      open_paths.reserve(outrec_list_.size());
+
+    for (OutRec* outrec : outrec_list_)
+    {
+      if (!outrec || !outrec->pts) continue;
+      if (outrec->is_open)
+      {
+        PathD path;
+        if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_))
+          open_paths.push_back(path);
+        continue;
+      }
+
+      if (CheckBounds(outrec))
+        DeepCheckOwners(outrec, &polytree);
+    }
+  }
+
+}  // namespace clipper2lib

+ 618 - 0
thirdparty/clipper2/src/clipper.offset.cpp

@@ -0,0 +1,618 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  22 March 2023                                                   *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* 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
+//------------------------------------------------------------------------------
+
+void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx)
+{
+	idx = -1;
+	r = MaxInvalidRect64;
+	int64_t lpx = 0;
+	for (int i = 0; i < static_cast<int>(paths.size()); ++i)
+		for (const Point64& p : paths[i])
+		{
+			if (p.y >= r.bottom)
+			{
+				if (p.y > r.bottom || p.x < lpx)
+				{
+					idx = i;
+					lpx = p.x;
+					r.bottom = p.y;
+				}
+			}
+			else if (p.y < r.top) r.top = p.y;
+			if (p.x > r.right) r.right = p.x;
+			else if (p.x < r.left) r.left = p.x;
+		}
+	//if (idx < 0) r = Rect64(0, 0, 0, 0);
+	//if (r.top == INT64_MIN) r.bottom = r.top;
+	//if (r.left == INT64_MIN) r.left = r.right;
+}
+
+bool IsSafeOffset(const Rect64& r, double abs_delta)
+{
+	return r.left > min_coord + abs_delta &&
+		r.right < max_coord - abs_delta &&
+		r.top > min_coord + abs_delta &&
+		r.bottom < max_coord - abs_delta;
+}
+
+PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
+{
+	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)
+{
+#ifdef USINGZ
+	return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
+#else
+	return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
+#endif
+}
+
+inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
+{
+#ifdef USINGZ
+	return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
+#else
+	return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
+#endif
+}
+
+inline void NegatePath(PathD& path)
+{
+	for (PointD& pt : path)
+	{
+		pt.x = -pt.x;
+		pt.y = -pt.y;
+#ifdef USINGZ
+		pt.z = pt.z;
+#endif
+	}
+}
+
+//------------------------------------------------------------------------------
+// ClipperOffset 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::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)
+{
+#ifdef USINGZ
+	return PointD(pt.x + dx, pt.y + dy, pt.z);
+#else
+	return PointD(pt.x + dx, pt.y + dy);
+#endif
+}
+
+inline PointD ReflectPoint(const PointD& pt, const PointD& pivot)
+{
+#ifdef USINGZ
+	return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z);
+#else
+	return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
+#endif
+}
+
+PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
+	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);
+#ifdef USINGZ
+		pt.z = ptQ.z;
+#endif
+		//get the second intersect point through reflecion
+		group.path.push_back(Point64(ReflectPoint(pt, ptQ)));
+		group.path.push_back(Point64(pt));
+	}
+	else
+	{
+		PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
+		PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
+#ifdef USINGZ
+		pt.z = ptQ.z;
+#endif
+		group.path.push_back(Point64(pt));
+		//get the second intersect point through reflecion
+		group.path.push_back(Point64(ReflectPoint(pt, ptQ)));
+	}
+}
+
+void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a)
+{
+	double q = group_delta_ / (cos_a + 1);
+#ifdef USINGZ
+	group.path.push_back(Point64(
+		path[j].x + (norms[k].x + norms[j].x) * q,
+		path[j].y + (norms[k].y + norms[j].y) * q,
+		path[j].z));
+#else
+	group.path.push_back(Point64(
+		path[j].x + (norms[k].x + norms[j].x) * q,
+		path[j].y + (norms[k].y + norms[j].y) * q));
+#endif
+}
+
+void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle)
+{
+	Point64 pt = path[j];
+	PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
+
+	if (j == k) offsetVec.Negate();
+#ifdef USINGZ
+	group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
+#else
+	group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
+#endif
+	if (angle > -PI + 0.01)	// avoid 180deg concave
+	{
+		int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
+		for (int i = 1; i < steps; ++i) // ie 1 less than steps
+		{
+			offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
+				offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
+#ifdef USINGZ
+			group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
+#else
+			group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
+#endif
+
+		}
+	}
+	group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
+}
+
+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;
+
+	if (cos_a > 0.99) // almost straight - less than 8 degrees
+	{
+		group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
+		if (cos_a < 0.9998) // greater than 1 degree (#424)
+			group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); // (#418)
+	}
+	else if (cos_a > -0.99 && (sin_a * group_delta_ < 0))
+	{
+		// is concave
+		group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
+		// this extra point is the only (simple) way to ensure that
+		// path reversals are fully cleaned with the trailing clipper
+		group.path.push_back(path[j]); // (#405)
+		group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
+	}	
+	else if (join_type_ == JoinType::Round)
+		DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
+	else if (join_type_ == JoinType::Miter)
+	{
+		// miter unless the angle is so acute the miter would exceeds ML
+		if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
+		else DoSquare(group, path, j, k);
+	}
+	// don't bother squaring angles that deviate < ~20 degrees because
+	// squaring will be indistinguishable from mitering and just be a lot slower
+	else if (cos_a > 0.9)
+		DoMiter(group, path, j, k, cos_a);
+	else
+		DoSquare(group, path, j, k);
+
+	k = j;
+}
+
+void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
+{
+	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());
+	
+	//rebuild normals // BuildNormals(path);
+	std::reverse(norms.begin(), norms.end());
+	norms.push_back(norms[0]);
+	norms.erase(norms.begin());
+	NegatePath(norms);
+
+	group.path.clear();
+	OffsetPolygon(group, path);
+}
+
+void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
+{
+	// do the line start cap
+	switch (end_type_)
+	{
+	case EndType::Butt:
+#ifdef USINGZ
+		group.path.push_back(Point64(
+			path[0].x - norms[0].x * group_delta_,
+			path[0].y - norms[0].y * group_delta_,
+			path[0].z));
+#else
+		group.path.push_back(Point64(
+			path[0].x - norms[0].x * group_delta_,
+			path[0].y - norms[0].y * group_delta_));
+#endif
+		group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_));
+		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:
+#ifdef USINGZ
+		group.path.push_back(Point64(
+			path[highI].x - norms[highI].x * group_delta_,
+			path[highI].y - norms[highI].y * group_delta_,
+			path[highI].z));
+#else
+		group.path.push_back(Point64(
+			path[highI].x - norms[highI].x * group_delta_,
+			path[highI].y - norms[highI].y * group_delta_));
+#endif
+		group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_));
+		break;
+	case EndType::Round:
+		DoRound(group, path, highI, highI, PI);
+		break;
+	default:
+		DoSquare(group, path, highI, highI);
+		break;
+	}
+
+	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)
+{
+	Rect64 r;
+	int idx = -1;
+	//the lowermost polygon must be an outer polygon. So we can use that as the
+	//designated orientation for outer polygons (needed for tidy-up clipping)
+	GetBoundsAndLowestPolyIdx(group.paths_in, r, idx);
+	if (idx < 0) return;
+
+	if (group.end_type == EndType::Polygon)
+	{
+		double area = Area(group.paths_in[idx]);
+		//if (area == 0) return; // probably unhelpful (#430)
+		group.is_reversed = (area < 0);
+		if (group.is_reversed) group_delta_ = -delta_;
+		else group_delta_ = delta_;
+	} 
+	else
+	{
+		group.is_reversed = false;
+		group_delta_ = std::abs(delta_) * 0.5;
+	}
+	abs_group_delta_ = std::fabs(group_delta_);
+
+	// do range checking
+	if (!IsSafeOffset(r, abs_group_delta_))
+	{
+		DoError(range_error_i);
+		error_code_ |= range_error_i;
+		return;
+	}
+
+	join_type_	= group.join_type;
+	end_type_ = group.end_type;
+
+	//calculate a sensible number of steps (for 360 deg for the given offset
+	if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
+	{
+		// arcTol - when arc_tolerance_ is undefined (0), the amount of 
+		// curve imprecision that's allowed is based on the size of the 
+		// offset (delta). Obviously very large offsets will almost always 
+		// require much less precision. See also offset_triginometry2.svg
+		double arcTol = (arc_tolerance_ > floating_point_tolerance ?
+			std::min(abs_group_delta_, arc_tolerance_) :
+			std::log10(2 + abs_group_delta_) * default_arc_tolerance); 
+		double steps_per_360 = PI / std::acos(1 - arcTol / abs_group_delta_);
+		if (steps_per_360 > abs_group_delta_ * PI)
+			steps_per_360 = abs_group_delta_ * PI;  //ie avoids excessive precision
+
+		step_sin_ = std::sin(2 * PI / steps_per_360);
+		step_cos_ = std::cos(2 * PI / steps_per_360);
+		if (group_delta_ < 0.0) step_sin_ = -step_sin_;		
+		steps_per_rad_ = steps_per_360 / (2 *PI);
+	}
+
+	bool is_joined =
+		(end_type_ == EndType::Polygon) ||
+		(end_type_ == EndType::Joined);
+	Paths64::const_iterator path_iter;
+	for(path_iter = group.paths_in.cbegin(); path_iter != group.paths_in.cend(); ++path_iter)
+	{
+		Path64 path = StripDuplicates(*path_iter, is_joined);
+		Path64::size_type cnt = path.size();
+		if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) 
+			continue;
+
+		group.path.clear();
+		if (cnt == 1) // single point - only valid with open paths
+		{
+			if (group_delta_ < 1) continue;
+			//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);
+#ifdef USINGZ
+				for (auto& p : group.path) p.z = path[0].z;
+#endif
+			}
+			else
+			{
+				int d = (int)std::ceil(abs_group_delta_);
+				r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
+				group.path = r.AsPath();
+#ifdef USINGZ
+				for (auto& p : group.path) p.z = path[0].z;
+#endif
+			}
+			group.paths_out.push_back(group.path);
+		}
+		else
+		{
+			if ((cnt == 2) && (group.end_type == EndType::Joined))
+			{
+				if (group.join_type == JoinType::Round)
+					end_type_ = EndType::Round;
+				else
+					end_type_ = EndType::Square;
+			}
+
+			BuildNormals(path);
+			if (end_type_ == EndType::Polygon) OffsetPolygon(group, path);
+			else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
+			else OffsetOpenPath(group, path);
+		}
+	}
+	solution.reserve(solution.size() + group.paths_out.size());
+	copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution));
+	group.paths_out.clear();
+}
+
+void ClipperOffset::ExecuteInternal(double delta)
+{
+	error_code_ = 0;
+	solution.clear();
+	if (groups_.size() == 0) return;
+
+	if (std::abs(delta) < 0.5)
+	{
+		for (const Group& group : groups_)
+		{
+			solution.reserve(solution.size() + group.paths_in.size());
+			copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution));
+		}
+	} 
+	else
+	{
+		temp_lim_ = (miter_limit_ <= 1) ?
+			2.0 :
+			2.0 / (miter_limit_ * miter_limit_);
+
+		delta_ = delta;
+		std::vector<Group>::iterator git;
+		for (git = groups_.begin(); git != groups_.end(); ++git)
+		{
+			DoGroupOffset(*git);
+			if (!error_code_) continue; // all OK
+			solution.clear();
+		}
+	}
+}
+
+void ClipperOffset::Execute(double delta, Paths64& paths)
+{
+	paths.clear();
+
+	ExecuteInternal(delta);
+	if (!solution.size()) return;
+
+	paths = solution;
+	//clean up self-intersections ...
+	Clipper64 c;
+	c.PreserveCollinear = false;
+	//the solution should retain the orientation of the input
+	c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed;
+#ifdef USINGZ
+	if (zCallback64_) {
+		c.SetZCallback(zCallback64_);
+	}
+#endif
+	c.AddSubject(solution);
+	if (groups_[0].is_reversed)
+		c.Execute(ClipType::Union, FillRule::Negative, paths);
+	else
+		c.Execute(ClipType::Union, FillRule::Positive, paths);
+}
+
+
+void ClipperOffset::Execute(double delta, PolyTree64& polytree)
+{
+	polytree.Clear();
+
+	ExecuteInternal(delta);
+	if (!solution.size()) return;
+
+	//clean up self-intersections ...
+	Clipper64 c;
+	c.PreserveCollinear = false;
+	//the solution should retain the orientation of the input
+	c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed;
+#ifdef USINGZ
+	if (zCallback64_) {
+		c.SetZCallback(zCallback64_);
+	}
+#endif
+	c.AddSubject(solution);
+	if (groups_[0].is_reversed)
+		c.Execute(ClipType::Union, FillRule::Negative, polytree);
+	else
+		c.Execute(ClipType::Union, FillRule::Positive, polytree);
+}
+
+} // namespace

+ 976 - 0
thirdparty/clipper2/src/clipper.rectclip.cpp

@@ -0,0 +1,976 @@
+/*******************************************************************************
+* Author    :  Angus Johnson                                                   *
+* Date      :  14 February 2023                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2023                                         *
+* 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 bool Path1ContainsPath2(const Path64& path1, const Path64& path2)
+  {
+    int io_count = 0;
+    // precondition: no (significant) overlap
+    for (const Point64& pt : path2)
+    {
+      PointInPolygonResult pip = PointInPolygon(pt, path1);
+      switch (pip) 
+      {
+      case PointInPolygonResult::IsOutside: ++io_count; break;
+        case PointInPolygonResult::IsInside: --io_count; break;
+        default: continue;
+      }
+      if (std::abs(io_count) > 1) break;
+    }
+    return io_count <= 0;
+  }
+
+  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,
+    const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
+  {
+    if (AreOpposites(prev, curr))
+      return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
+    else
+      return HeadingClockwise(prev, curr);
+  }
+
+  inline OutPt2* UnlinkOp(OutPt2* op)
+  {
+    if (op->next == op) return nullptr;
+    op->prev->next = op->next;
+    op->next->prev = op->prev;
+    return op->next;
+  }
+
+  inline OutPt2* UnlinkOpBack(OutPt2* op)
+  {
+    if (op->next == op) return nullptr;
+    op->prev->next = op->next;
+    op->next->prev = op->prev;
+    return op->prev;
+  }
+
+  inline uint32_t GetEdgesForPt(const Point64& pt, const Rect64& rec)
+  {
+    uint32_t result = 0;
+    if (pt.x == rec.left) result = 1;
+    else if (pt.x == rec.right) result = 4;
+    if (pt.y == rec.top) result += 2;
+    else if (pt.y == rec.bottom) result += 8;
+    return result;
+  }
+
+  inline bool IsHeadingClockwise(const Point64& pt1, const Point64& pt2, int edgeIdx)
+  {
+    switch (edgeIdx)
+    {
+    case 0: return pt2.y < pt1.y;
+    case 1: return pt2.x > pt1.x;
+    case 2: return pt2.y > pt1.y;
+    default: return pt2.x < pt1.x;
+    }
+  }
+
+  inline bool HasHorzOverlap(const Point64& left1, const Point64& right1,
+    const Point64& left2, const Point64& right2)
+  {
+    return (left1.x < right2.x) && (right1.x > left2.x);
+  }
+
+  inline bool HasVertOverlap(const Point64& top1, const Point64& bottom1,
+    const Point64& top2, const Point64& bottom2)
+  {
+    return (top1.y < bottom2.y) && (bottom1.y > top2.y);
+  }
+
+  inline void AddToEdge(OutPt2List& edge, OutPt2* op)
+  {
+    if (op->edge) return;
+    op->edge = &edge;
+    edge.push_back(op);
+  }
+
+  inline void UncoupleEdge(OutPt2* op)
+  {
+    if (!op->edge) return;
+    for (size_t i = 0; i < op->edge->size(); ++i)
+    {
+      OutPt2* op2 = (*op->edge)[i];
+      if (op2 == op)
+      {
+        (*op->edge)[i] = nullptr;
+        break;
+      }
+    }
+    op->edge = nullptr;
+  }
+
+  inline void SetNewOwner(OutPt2* op, size_t new_idx)
+  {
+    op->owner_idx = new_idx;
+    OutPt2* op2 = op->next;
+    while (op2 != op)
+    {
+      op2->owner_idx = new_idx;
+      op2 = op2->next;
+    }
+  }
+
+  //----------------------------------------------------------------------------
+  // RectClip64
+  //----------------------------------------------------------------------------
+
+  OutPt2* RectClip::Add(Point64 pt, bool start_new)
+  {
+    // this method is only called by InternalExecute.
+    // Later splitting & rejoining won't create additional op's,
+    // though they will change the (non-storage) results_ count.
+    int curr_idx = static_cast<int>(results_.size()) - 1;
+    OutPt2* result;
+    if (curr_idx < 0 || start_new)
+    {
+      result = &op_container_.emplace_back(OutPt2());
+      result->pt = pt;
+      result->next = result;
+      result->prev = result;
+      results_.push_back(result);
+    }
+    else
+    {
+      OutPt2* prevOp = results_[curr_idx];
+      if (prevOp->pt == pt)  return prevOp;
+      result = &op_container_.emplace_back(OutPt2());
+      result->owner_idx = curr_idx;
+      result->pt = pt;
+      result->next = prevOp->next;
+      prevOp->next->prev = result;
+      prevOp->next = result;
+      result->prev = prevOp;
+      results_[curr_idx] = result;
+    }
+    return result;
+  }
+
+  void RectClip::AddCorner(Location prev, Location curr)
+  {
+    if (HeadingClockwise(prev, curr))
+      Add(rect_as_path_[static_cast<int>(prev)]);
+    else
+      Add(rect_as_path_[static_cast<int>(curr)]);
+  }
+
+  void RectClip::AddCorner(Location& loc, bool isClockwise)
+  {
+    if (isClockwise)
+    {
+      Add(rect_as_path_[static_cast<int>(loc)]);
+      loc = GetAdjacentLocation(loc, true);
+    }
+    else
+    {
+      loc = GetAdjacentLocation(loc, false);
+      Add(rect_as_path_[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 { Add(path[i]); ++i; continue; }
+        break; //inner loop
+      }
+      break;
+    } //switch          
+  }
+
+  void RectClip::ExecuteInternal(const Path64& path)
+  {
+    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) 
+      {
+        // all of path must be inside fRect
+        for (const auto& pt : path) Add(pt);
+        return;
+      }
+      if (prev == Location::Inside) loc = Location::Inside;
+      i = 0;
+    }
+    Location startingLoc = 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(rect_as_path_, 
+        path[i], prev_pt, crossing_loc, ip))
+      {
+        // ie remaining outside
+        if (crossing_prev == Location::Inside)
+        {
+          bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_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], rect_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], rect_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(rect_as_path_, 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;
+        Add(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;
+      }
+
+      Add(ip);
+
+    } //while i <= highI
+    ///////////////////////////////////////////////////
+
+    if (first_cross_ == Location::Inside)
+    {
+      // path never intersects
+      if (startingLoc != Location::Inside)
+      {
+        // path is outside rect
+        // but being outside, it still may not contain rect
+        if (path_bounds_.Contains(rect_) &&
+          Path1ContainsPath2(path, rect_as_path_))
+        {
+          // yep, the path does fully contain rect
+          // so add rect to the solution
+          for (size_t j = 0; j < 4; ++j)
+          {
+            Add(rect_as_path_[j]);
+            // we may well need to do some splitting later, so
+            AddToEdge(edges_[j * 2], results_[0]);
+          }
+        }
+      }
+    }
+    else 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_));
+    }
+  }
+
+  void RectClip::CheckEdges()
+  {
+    for (size_t i = 0; i < results_.size(); ++i)
+    {
+      OutPt2* op = results_[i];
+      if (!op) continue;
+      OutPt2* op2 = op;
+      do
+      {
+        if (!CrossProduct(op2->prev->pt,
+          op2->pt, op2->next->pt))
+        {
+          if (op2 == op)
+          {
+            op2 = UnlinkOpBack(op2);
+            if (!op2) break;
+            op = op2->prev;
+          }
+          else
+          {
+            op2 = UnlinkOpBack(op2);
+            if (!op2) break;
+          }
+        }
+        else
+          op2 = op2->next;
+      } while (op2 != op);
+
+      if (!op2)
+      {
+        results_[i] = nullptr;
+        continue;
+      }
+      results_[i] = op; // safety first
+
+      uint32_t edgeSet1 = GetEdgesForPt(op->prev->pt, rect_);
+      op2 = op;
+      do
+      {
+        uint32_t edgeSet2 = GetEdgesForPt(op2->pt, rect_);
+        if (edgeSet2 && !op2->edge)
+        {
+          uint32_t combinedSet = (edgeSet1 & edgeSet2);
+          for (int j = 0; j < 4; ++j)
+          {
+            if (combinedSet & (1 << j))
+            {
+              if (IsHeadingClockwise(op2->prev->pt, op2->pt, j))
+                AddToEdge(edges_[j * 2], op2);
+              else
+                AddToEdge(edges_[j * 2 + 1], op2);
+            }
+          }
+        }
+        edgeSet1 = edgeSet2;
+        op2 = op2->next;
+      } while (op2 != op);
+    }
+  }
+
+  void RectClip::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw)
+  {
+    if (ccw.empty()) return;
+    bool isHorz = ((idx == 1) || (idx == 3));
+    bool cwIsTowardLarger = ((idx == 1) || (idx == 2));
+    size_t i = 0, j = 0;
+    OutPt2* p1, * p2, * p1a, * p2a, * op, * op2;
+
+    while (i < cw.size()) 
+    {
+      p1 = cw[i];
+      if (!p1 || p1->next == p1->prev)
+      {
+        cw[i++]->edge = nullptr;
+        j = 0;
+        continue;
+      }
+
+      size_t jLim = ccw.size();
+      while (j < jLim &&
+        (!ccw[j] || ccw[j]->next == ccw[j]->prev)) ++j;
+
+      if (j == jLim)
+      {
+        ++i;
+        j = 0;
+        continue;
+      }
+
+      if (cwIsTowardLarger)
+      {
+        // p1 >>>> p1a;
+        // p2 <<<< p2a;
+        p1 = cw[i]->prev;
+        p1a = cw[i];
+        p2 = ccw[j];
+        p2a = ccw[j]->prev;
+      }
+      else
+      {
+        // p1 <<<< p1a;
+        // p2 >>>> p2a;
+        p1 = cw[i];
+        p1a = cw[i]->prev;
+        p2 = ccw[j]->prev;
+        p2a = ccw[j];
+      }
+
+      if ((isHorz && !HasHorzOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)) ||
+        (!isHorz && !HasVertOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)))
+      {
+        ++j;
+        continue;
+      }
+
+      // to get here we're either splitting or rejoining
+      bool isRejoining = cw[i]->owner_idx != ccw[j]->owner_idx;
+
+      if (isRejoining)
+      {
+        results_[p2->owner_idx] = nullptr;
+        SetNewOwner(p2, p1->owner_idx);
+      }
+
+      // do the split or re-join
+      if (cwIsTowardLarger)
+      {
+        // p1 >> | >> p1a;
+        // p2 << | << p2a;
+        p1->next = p2;
+        p2->prev = p1;
+        p1a->prev = p2a;
+        p2a->next = p1a;
+      }
+      else
+      {
+        // p1 << | << p1a;
+        // p2 >> | >> p2a;
+        p1->prev = p2;
+        p2->next = p1;
+        p1a->next = p2a;
+        p2a->prev = p1a;
+      }
+
+      if (!isRejoining)
+      {
+        size_t new_idx = results_.size();
+        results_.push_back(p1a);
+        SetNewOwner(p1a, new_idx);
+      }
+
+      if (cwIsTowardLarger)
+      {
+        op = p2;
+        op2 = p1a;
+      }
+      else
+      {
+        op = p1;
+        op2 = p2a;
+      }
+      results_[op->owner_idx] = op;
+      results_[op2->owner_idx] = op2;
+
+      // and now lots of work to get ready for the next loop
+
+      bool opIsLarger, op2IsLarger;
+      if (isHorz) // X
+      {
+        opIsLarger = op->pt.x > op->prev->pt.x;
+        op2IsLarger = op2->pt.x > op2->prev->pt.x;
+      }
+      else       // Y
+      {
+        opIsLarger = op->pt.y > op->prev->pt.y;
+        op2IsLarger = op2->pt.y > op2->prev->pt.y;
+      }
+
+      if ((op->next == op->prev) ||
+        (op->pt == op->prev->pt))
+      {
+        if (op2IsLarger == cwIsTowardLarger)
+        {
+          cw[i] = op2;
+          ccw[j++] = nullptr;
+        }
+        else
+        {
+          ccw[j] = op2;
+          cw[i++] = nullptr;
+        }
+      }
+      else if ((op2->next == op2->prev) ||
+        (op2->pt == op2->prev->pt))
+      {
+        if (opIsLarger == cwIsTowardLarger)
+        {
+          cw[i] = op;
+          ccw[j++] = nullptr;
+        }
+        else
+        {
+          ccw[j] = op;
+          cw[i++] = nullptr;
+        }
+      }
+      else if (opIsLarger == op2IsLarger)
+      {
+        if (opIsLarger == cwIsTowardLarger)
+        {
+          cw[i] = op;
+          UncoupleEdge(op2);
+          AddToEdge(cw, op2);
+          ccw[j++] = nullptr;
+        }
+        else
+        {
+          cw[i++] = nullptr;
+          ccw[j] = op2;
+          UncoupleEdge(op);
+          AddToEdge(ccw, op);
+          j = 0;
+        }
+      }
+      else
+      {
+        if (opIsLarger == cwIsTowardLarger)
+          cw[i] = op;
+        else
+          ccw[j] = op;
+        if (op2IsLarger == cwIsTowardLarger)
+          cw[i] = op2;
+        else
+          ccw[j] = op2;
+      }
+    }
+  }
+
+  Path64 RectClip::GetPath(OutPt2*& op)
+  {
+    if (!op || op->next == op->prev) return Path64();
+
+    OutPt2* op2 = op->next;
+    while (op2 && op2 != op)
+    {
+      if (CrossProduct(op2->prev->pt, 
+        op2->pt, op2->next->pt) == 0)
+      {
+        op = op2->prev;
+        op2 = UnlinkOp(op2);
+      }
+      else
+        op2 = op2->next;
+    }
+    op = op2; // needed for op cleanup
+    if (!op2) return Path64();
+
+    Path64 result;
+    result.push_back(op->pt);
+    op2 = op->next;
+    while (op2 != op)
+    {
+      result.push_back(op2->pt);
+      op2 = op2->next;
+    }
+    return result;
+  }
+
+  Paths64 RectClip::Execute(const Paths64& paths, bool convex_only)
+  {
+    Paths64 result;
+    if (rect_.IsEmpty()) return result;
+
+    for (const auto& path : paths)
+    {
+      if (path.size() < 3) continue;
+      path_bounds_ = GetBounds(path);
+      if (!rect_.Intersects(path_bounds_))
+        continue; // the path must be completely outside rect_
+      else if (rect_.Contains(path_bounds_))
+      {
+        // the path must be completely inside rect_
+        result.push_back(path);
+        continue;
+      }
+
+      ExecuteInternal(path);
+      if (!convex_only)
+      {
+        CheckEdges();
+        for (int i = 0; i < 4; ++i)
+          TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]);
+      }
+
+      for (OutPt2*& op :  results_)
+      {
+        Path64 tmp = GetPath(op);
+        if (!tmp.empty())
+          result.emplace_back(tmp);
+      }
+
+      //clean up after every loop
+      op_container_ = std::deque<OutPt2>();
+      results_.clear();
+      for (OutPt2List edge : edges_) edge.clear();
+      start_locs_.clear();
+    }
+    return result;
+  }
+
+  //------------------------------------------------------------------------------
+  // RectClipLines
+  //------------------------------------------------------------------------------
+
+  Paths64 RectClipLines::Execute(const Paths64& paths)
+  {
+    Paths64 result;
+    if (rect_.IsEmpty()) return result;
+
+    for (const auto& path : paths)
+    {
+      if (path.size() < 2) continue;
+      Rect64 pathrec = GetBounds(path);
+
+      if (!rect_.Intersects(pathrec)) continue;
+
+      ExecuteInternal(path);
+
+      for (OutPt2*& op : results_)
+      {
+        Path64 tmp = GetPath(op);
+        if (!tmp.empty())
+          result.emplace_back(tmp);
+      }
+      results_.clear();
+
+      op_container_ = std::deque<OutPt2>();
+      start_locs_.clear();
+    }
+    return result;
+  }
+
+  void RectClipLines::ExecuteInternal(const Path64& path)
+  {
+    if (rect_.IsEmpty() || path.size() < 2) return;
+
+    results_.clear();
+    op_container_ = std::deque<OutPt2>();
+    start_locs_.clear();
+
+    int i = 1, highI = static_cast<int>(path.size()) - 1;
+
+    Location prev = Location::Inside, loc;
+    Location crossing_loc;
+    if (!GetLocation(rect_, path[0], loc))
+    {
+      while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i;
+      if (i > highI) 
+      {
+        // all of path must be inside fRect
+        for (const auto& pt : path) Add(pt);
+        return;
+      }
+      if (prev == Location::Inside) loc = Location::Inside;
+      i = 1;
+    }
+    if (loc == Location::Inside) Add(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(rect_as_path_, 
+        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
+      {
+        Add(ip, true);
+      }
+      else if (prev != Location::Inside)
+      {
+        // passing right through rect. 'ip' here will be the second 
+        // intersect pt but we'll also need the first intersect pt (ip2)
+        crossing_loc = prev;
+        GetIntersection(rect_as_path_, 
+          prev_pt, path[i], crossing_loc, ip2);
+        Add(ip2, true);
+        Add(ip);
+      }
+      else // path must be exiting rect
+      {
+        Add(ip);
+      }
+    } //while i <= highI
+    ///////////////////////////////////////////////////
+  }
+
+  Path64 RectClipLines::GetPath(OutPt2*& op)
+  {
+    Path64 result;
+    if (!op || op == op->next) return result;
+    op = op->next; // starting at path beginning 
+    result.push_back(op->pt);
+    OutPt2 *op2 = op->next;
+    while (op2 != op)
+    {
+      result.push_back(op2->pt);
+      op2 = op2->next;
+    }        
+    return result;
+  }
+
+} // namespace