Add 2D navigation mesh baking
Adds 2D navigation mesh baking.
This commit is contained in:
parent
82f6e9be5e
commit
0ee7e3102b
|
@ -171,6 +171,11 @@ Comment: CA certificates
|
||||||
Copyright: Mozilla Contributors
|
Copyright: Mozilla Contributors
|
||||||
License: MPL-2.0
|
License: MPL-2.0
|
||||||
|
|
||||||
|
Files: ./thirdparty/clipper2/
|
||||||
|
Comment: Clipper2
|
||||||
|
Copyright: 2010-2013, Angus Johnson
|
||||||
|
License: BSL-1.0
|
||||||
|
|
||||||
Files: ./thirdparty/cvtt/
|
Files: ./thirdparty/cvtt/
|
||||||
Comment: Convection Texture Tools Stand-Alone Kernels
|
Comment: Convection Texture Tools Stand-Alone Kernels
|
||||||
Copyright: 2018, Eric Lasota
|
Copyright: 2018, Eric Lasota
|
||||||
|
|
|
@ -226,6 +226,7 @@ opts.Add("scu_limit", "Max includes per SCU file when using scu_build (determine
|
||||||
# Thirdparty libraries
|
# Thirdparty libraries
|
||||||
opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True))
|
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_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_embree", "Use the built-in Embree library", True))
|
||||||
opts.Add(BoolVariable("builtin_enet", "Use the built-in ENet 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))
|
opts.Add(BoolVariable("builtin_freetype", "Use the built-in FreeType library", True))
|
||||||
|
|
18
core/SCsub
18
core/SCsub
|
@ -89,6 +89,24 @@ if env["brotli"] and env["builtin_brotli"]:
|
||||||
|
|
||||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_brotli_sources)
|
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
|
# Zlib library, can be unbundled
|
||||||
if env["builtin_zlib"]:
|
if env["builtin_zlib"]:
|
||||||
thirdparty_zlib_dir = "#thirdparty/zlib/"
|
thirdparty_zlib_dir = "#thirdparty/zlib/"
|
||||||
|
|
|
@ -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>
|
|
@ -1,44 +1,44 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?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">
|
<class name="NavigationPolygon" inherits="Resource" is_experimental="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
|
||||||
<brief_description>
|
<brief_description>
|
||||||
A navigation polygon that defines traversable areas and obstacles.
|
A 2D navigation mesh that describes a traversable surface for pathfinding.
|
||||||
</brief_description>
|
</brief_description>
|
||||||
<description>
|
<description>
|
||||||
There are two ways to create polygons. Either by using the [method add_outline] method, or using the [method add_polygon] method.
|
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.
|
||||||
Using [method add_outline]:
|
To bake a navigation mesh at least one outline needs to be added that defines the outer bounds of the baked area.
|
||||||
[codeblocks]
|
[codeblocks]
|
||||||
[gdscript]
|
[gdscript]
|
||||||
var polygon = NavigationPolygon.new()
|
var new_navigation_mesh = NavigationPolygon.new()
|
||||||
var outline = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
|
var bounding_outline = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
|
||||||
polygon.add_outline(outline)
|
new_navigation_mesh.add_outline(bounding_outline)
|
||||||
polygon.make_polygons_from_outlines()
|
NavigationServer2D.bake_from_source_geometry_data(new_navigation_mesh, NavigationMeshSourceGeometryData2D.new());
|
||||||
$NavigationRegion2D.navigation_polygon = polygon
|
$NavigationRegion2D.navigation_polygon = new_navigation_mesh
|
||||||
[/gdscript]
|
[/gdscript]
|
||||||
[csharp]
|
[csharp]
|
||||||
var polygon = new NavigationPolygon();
|
var newNavigationMesh = new NavigationPolygon();
|
||||||
var outline = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
|
var boundingOutline = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
|
||||||
polygon.AddOutline(outline);
|
newNavigationMesh.AddOutline(boundingOutline);
|
||||||
polygon.MakePolygonsFromOutlines();
|
NavigationServer2D.BakeFromSourceGeometryData(newNavigationMesh, new NavigationMeshSourceGeometryData2D());
|
||||||
GetNode<NavigationRegion2D>("NavigationRegion2D").NavigationPolygon = polygon;
|
GetNode<NavigationRegion2D>("NavigationRegion2D").NavigationPolygon = newNavigationMesh;
|
||||||
[/csharp]
|
[/csharp]
|
||||||
[/codeblocks]
|
[/codeblocks]
|
||||||
Using [method add_polygon] and indices of the vertices array.
|
Adding vertices and polygon indices manually.
|
||||||
[codeblocks]
|
[codeblocks]
|
||||||
[gdscript]
|
[gdscript]
|
||||||
var polygon = NavigationPolygon.new()
|
var new_navigation_mesh = NavigationPolygon.new()
|
||||||
var vertices = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
|
var new_vertices = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
|
||||||
polygon.vertices = vertices
|
new_navigation_mesh.vertices = new_vertices
|
||||||
var indices = PackedInt32Array([0, 1, 2, 3])
|
var new_polygon_indices = PackedInt32Array([0, 1, 2, 3])
|
||||||
polygon.add_polygon(indices)
|
new_navigation_mesh.add_polygon(new_polygon_indices)
|
||||||
$NavigationRegion2D.navigation_polygon = polygon
|
$NavigationRegion2D.navigation_polygon = new_navigation_mesh
|
||||||
[/gdscript]
|
[/gdscript]
|
||||||
[csharp]
|
[csharp]
|
||||||
var polygon = new NavigationPolygon();
|
var newNavigationMesh = new NavigationPolygon();
|
||||||
var vertices = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
|
var newVertices = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
|
||||||
polygon.Vertices = vertices;
|
newNavigationMesh.Vertices = newVertices;
|
||||||
var indices = new int[] { 0, 1, 2, 3 };
|
var newPolygonIndices = new int[] { 0, 1, 2, 3 };
|
||||||
polygon.AddPolygon(indices);
|
newNavigationMesh.AddPolygon(newPolygonIndices);
|
||||||
GetNode<NavigationRegion2D>("NavigationRegion2D").NavigationPolygon = polygon;
|
GetNode<NavigationRegion2D>("NavigationRegion2D").NavigationPolygon = newNavigationMesh;
|
||||||
[/csharp]
|
[/csharp]
|
||||||
[/codeblocks]
|
[/codeblocks]
|
||||||
</description>
|
</description>
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<param index="0" name="outline" type="PackedVector2Array" />
|
<param index="0" name="outline" type="PackedVector2Array" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="add_outline_at_index">
|
<method name="add_outline_at_index">
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
<param index="0" name="outline" type="PackedVector2Array" />
|
<param index="0" name="outline" type="PackedVector2Array" />
|
||||||
<param index="1" name="index" type="int" />
|
<param index="1" name="index" type="int" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="add_polygon">
|
<method name="add_polygon">
|
||||||
|
@ -106,6 +106,13 @@
|
||||||
Returns the number of outlines that were created in the editor or by script.
|
Returns the number of outlines that were created in the editor or by script.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="get_polygon">
|
||||||
<return type="PackedInt32Array" />
|
<return type="PackedInt32Array" />
|
||||||
<param index="0" name="idx" type="int" />
|
<param index="0" name="idx" type="int" />
|
||||||
|
@ -125,10 +132,11 @@
|
||||||
Returns a [PackedVector2Array] containing all the vertices being used to create the polygons.
|
Returns a [PackedVector2Array] containing all the vertices being used to create the polygons.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="make_polygons_from_outlines">
|
<method name="make_polygons_from_outlines" is_deprecated="true">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
Creates polygons from the outlines added in the editor or by script.
|
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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="remove_outline">
|
<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.
|
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>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="set_vertices">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<param index="0" name="vertices" type="PackedVector2Array" />
|
<param index="0" name="vertices" type="PackedVector2Array" />
|
||||||
|
@ -155,8 +171,52 @@
|
||||||
</method>
|
</method>
|
||||||
</methods>
|
</methods>
|
||||||
<members>
|
<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">
|
<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.
|
The cell size used to rasterize the navigation mesh vertices. Must match with the cell size on the navigation map.
|
||||||
</member>
|
</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="&"navigation_polygon_source_geometry_group"">
|
||||||
|
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>
|
</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>
|
</class>
|
||||||
|
|
|
@ -16,6 +16,13 @@
|
||||||
<link title="Using NavigationRegions">$DOCS_URL/tutorials/navigation/navigation_using_navigationregions.html</link>
|
<link title="Using NavigationRegions">$DOCS_URL/tutorials/navigation/navigation_using_navigationregions.html</link>
|
||||||
</tutorials>
|
</tutorials>
|
||||||
<methods>
|
<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">
|
<method name="get_avoidance_layer_value" qualifiers="const">
|
||||||
<return type="bool" />
|
<return type="bool" />
|
||||||
<param index="0" name="layer_number" type="int" />
|
<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.
|
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>
|
</member>
|
||||||
</members>
|
</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>
|
</class>
|
||||||
|
|
|
@ -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.
|
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>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="free_rid">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<param index="0" name="rid" type="RID" />
|
<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.
|
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>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="query_path" qualifiers="const">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<param index="0" name="parameters" type="NavigationPathQueryParameters2D" />
|
<param index="0" name="parameters" type="NavigationPathQueryParameters2D" />
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
#include "editor/editor_node.h"
|
#include "editor/editor_node.h"
|
||||||
#include "editor/editor_undo_redo_manager.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> NavigationPolygonEditor::_ensure_navpoly() const {
|
||||||
Ref<NavigationPolygon> navpoly = node->get_navigation_polygon();
|
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 {
|
void NavigationPolygonEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
|
||||||
Ref<NavigationPolygon> navpoly = _ensure_navpoly();
|
Ref<NavigationPolygon> navpoly = _ensure_navpoly();
|
||||||
navpoly->set_outline(p_idx, p_polygon);
|
navpoly->set_outline(p_idx, p_polygon);
|
||||||
navpoly->make_polygons_from_outlines();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) {
|
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();
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||||
undo_redo->add_do_method(navpoly.ptr(), "add_outline", p_polygon);
|
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_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) {
|
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();
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||||
undo_redo->add_do_method(navpoly.ptr(), "remove_outline", p_idx);
|
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_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) {
|
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();
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||||
undo_redo->add_do_method(navpoly.ptr(), "set_outline", p_idx, p_polygon);
|
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_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 {
|
bool NavigationPolygonEditor::_has_resource() const {
|
||||||
|
@ -119,7 +114,84 @@ void NavigationPolygonEditor::_create_resource() {
|
||||||
_menu_option(MODE_CREATE);
|
_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() :
|
NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin() :
|
||||||
AbstractPolygon2DEditorPlugin(memnew(NavigationPolygonEditor), "NavigationRegion2D") {
|
AbstractPolygon2DEditorPlugin(memnew(NavigationPolygonEditor), "NavigationRegion2D") {
|
||||||
|
|
|
@ -32,16 +32,38 @@
|
||||||
#define NAVIGATION_POLYGON_EDITOR_PLUGIN_H
|
#define NAVIGATION_POLYGON_EDITOR_PLUGIN_H
|
||||||
|
|
||||||
#include "editor/plugins/abstract_polygon_2d_editor.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 {
|
class NavigationPolygonEditor : public AbstractPolygon2DEditor {
|
||||||
|
friend class NavigationPolygonEditorPlugin;
|
||||||
|
|
||||||
GDCLASS(NavigationPolygonEditor, AbstractPolygon2DEditor);
|
GDCLASS(NavigationPolygonEditor, AbstractPolygon2DEditor);
|
||||||
|
|
||||||
NavigationRegion2D *node = nullptr;
|
NavigationRegion2D *node = nullptr;
|
||||||
|
|
||||||
Ref<NavigationPolygon> _ensure_navpoly() const;
|
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:
|
protected:
|
||||||
|
void _notification(int p_what);
|
||||||
|
|
||||||
virtual Node2D *_get_node() const override;
|
virtual Node2D *_get_node() const override;
|
||||||
virtual void _set_node(Node *p_polygon) override;
|
virtual void _set_node(Node *p_polygon) override;
|
||||||
|
|
||||||
|
@ -63,6 +85,8 @@ public:
|
||||||
class NavigationPolygonEditorPlugin : public AbstractPolygon2DEditorPlugin {
|
class NavigationPolygonEditorPlugin : public AbstractPolygon2DEditorPlugin {
|
||||||
GDCLASS(NavigationPolygonEditorPlugin, AbstractPolygon2DEditorPlugin);
|
GDCLASS(NavigationPolygonEditorPlugin, AbstractPolygon2DEditorPlugin);
|
||||||
|
|
||||||
|
NavigationPolygonEditor *navigation_polygon_editor = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NavigationPolygonEditorPlugin();
|
NavigationPolygonEditorPlugin();
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
#include "servers/movie_writer/movie_writer.h"
|
#include "servers/movie_writer/movie_writer.h"
|
||||||
#include "servers/movie_writer/movie_writer_mjpeg.h"
|
#include "servers/movie_writer/movie_writer_mjpeg.h"
|
||||||
#include "servers/navigation_server_2d.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.h"
|
||||||
#include "servers/navigation_server_3d_dummy.h"
|
#include "servers/navigation_server_3d_dummy.h"
|
||||||
#include "servers/physics_server_2d.h"
|
#include "servers/physics_server_2d.h"
|
||||||
|
@ -339,8 +340,14 @@ void initialize_navigation_server() {
|
||||||
navigation_server_3d->init();
|
navigation_server_3d->init();
|
||||||
|
|
||||||
// Init 2D Navigation Server
|
// 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.");
|
ERR_FAIL_NULL_MSG(navigation_server_2d, "Failed to initialize NavigationServer2D.");
|
||||||
|
navigation_server_2d->init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void finalize_navigation_server() {
|
void finalize_navigation_server() {
|
||||||
|
@ -350,6 +357,7 @@ void finalize_navigation_server() {
|
||||||
navigation_server_3d = nullptr;
|
navigation_server_3d = nullptr;
|
||||||
|
|
||||||
ERR_FAIL_NULL(navigation_server_2d);
|
ERR_FAIL_NULL(navigation_server_2d);
|
||||||
|
navigation_server_2d->finish();
|
||||||
memdelete(navigation_server_2d);
|
memdelete(navigation_server_2d);
|
||||||
navigation_server_2d = nullptr;
|
navigation_server_2d = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -3490,6 +3498,9 @@ bool Main::iteration() {
|
||||||
// process all our active interfaces
|
// process all our active interfaces
|
||||||
XRServer::get_singleton()->_process();
|
XRServer::get_singleton()->_process();
|
||||||
|
|
||||||
|
NavigationServer2D::get_singleton()->sync();
|
||||||
|
NavigationServer3D::get_singleton()->sync();
|
||||||
|
|
||||||
for (int iters = 0; iters < advance.physics_steps; ++iters) {
|
for (int iters = 0; iters < advance.physics_steps; ++iters) {
|
||||||
if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) {
|
if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) {
|
||||||
Input::get_singleton()->flush_buffered_events();
|
Input::get_singleton()->flush_buffered_events();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
def can_build(env, platform):
|
def can_build(env, platform):
|
||||||
return True
|
return not env["disable_3d"]
|
||||||
|
|
||||||
|
|
||||||
def configure(env):
|
def configure(env):
|
||||||
|
|
|
@ -1086,6 +1086,14 @@ void GodotNavigationServer::map_force_update(RID p_map) {
|
||||||
map->sync();
|
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) {
|
void GodotNavigationServer::process(real_t p_delta_time) {
|
||||||
flush_queries();
|
flush_queries();
|
||||||
|
|
||||||
|
@ -1093,16 +1101,6 @@ void GodotNavigationServer::process(real_t p_delta_time) {
|
||||||
return;
|
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_region_count = 0;
|
||||||
int _new_pm_agent_count = 0;
|
int _new_pm_agent_count = 0;
|
||||||
int _new_pm_link_count = 0;
|
int _new_pm_link_count = 0;
|
||||||
|
|
|
@ -243,6 +243,7 @@ public:
|
||||||
void flush_queries();
|
void flush_queries();
|
||||||
virtual void process(real_t p_delta_time) override;
|
virtual void process(real_t p_delta_time) override;
|
||||||
virtual void init() override;
|
virtual void init() override;
|
||||||
|
virtual void sync() override;
|
||||||
virtual void finish() override;
|
virtual void finish() override;
|
||||||
|
|
||||||
virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override;
|
virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -31,6 +31,7 @@
|
||||||
#include "register_types.h"
|
#include "register_types.h"
|
||||||
|
|
||||||
#include "godot_navigation_server.h"
|
#include "godot_navigation_server.h"
|
||||||
|
#include "godot_navigation_server_2d.h"
|
||||||
|
|
||||||
#ifndef DISABLE_DEPRECATED
|
#ifndef DISABLE_DEPRECATED
|
||||||
#ifndef _3D_DISABLED
|
#ifndef _3D_DISABLED
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "core/config/engine.h"
|
#include "core/config/engine.h"
|
||||||
|
#include "servers/navigation_server_2d.h"
|
||||||
#include "servers/navigation_server_3d.h"
|
#include "servers/navigation_server_3d.h"
|
||||||
|
|
||||||
#ifndef DISABLE_DEPRECATED
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
@ -55,9 +57,14 @@ NavigationServer3D *new_server() {
|
||||||
return memnew(GodotNavigationServer);
|
return memnew(GodotNavigationServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NavigationServer2D *new_navigation_server_2d() {
|
||||||
|
return memnew(GodotNavigationServer2D);
|
||||||
|
}
|
||||||
|
|
||||||
void initialize_navigation_module(ModuleInitializationLevel p_level) {
|
void initialize_navigation_module(ModuleInitializationLevel p_level) {
|
||||||
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
|
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
|
||||||
NavigationServer3DManager::set_default_server(new_server);
|
NavigationServer3DManager::set_default_server(new_server);
|
||||||
|
NavigationServer2DManager::set_default_server(new_navigation_server_2d);
|
||||||
|
|
||||||
#ifndef DISABLE_DEPRECATED
|
#ifndef DISABLE_DEPRECATED
|
||||||
#ifndef _3D_DISABLED
|
#ifndef _3D_DISABLED
|
||||||
|
|
|
@ -179,10 +179,6 @@ void NavigationRegion2D::_notification(int p_what) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon) {
|
void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon) {
|
||||||
if (p_navigation_polygon == navigation_polygon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (navigation_polygon.is_valid()) {
|
if (navigation_polygon.is_valid()) {
|
||||||
navigation_polygon->disconnect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed));
|
navigation_polygon->disconnect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed));
|
||||||
}
|
}
|
||||||
|
@ -226,6 +222,32 @@ RID NavigationRegion2D::get_navigation_map() const {
|
||||||
return RID();
|
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() {
|
void NavigationRegion2D::_navigation_polygon_changed() {
|
||||||
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) {
|
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) {
|
||||||
queue_redraw();
|
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("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("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);
|
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");
|
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::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::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_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
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
|
|
@ -114,6 +114,9 @@ public:
|
||||||
|
|
||||||
PackedStringArray get_configuration_warnings() const override;
|
PackedStringArray get_configuration_warnings() const override;
|
||||||
|
|
||||||
|
void bake_navigation_polygon(bool p_on_thread);
|
||||||
|
void _bake_finished(Ref<NavigationPolygon> p_navigation_polygon);
|
||||||
|
|
||||||
NavigationRegion2D();
|
NavigationRegion2D();
|
||||||
~NavigationRegion2D();
|
~NavigationRegion2D();
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,7 @@
|
||||||
#include "scene/resources/mesh_texture.h"
|
#include "scene/resources/mesh_texture.h"
|
||||||
#include "scene/resources/multimesh.h"
|
#include "scene/resources/multimesh.h"
|
||||||
#include "scene/resources/navigation_mesh.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_mesh_source_geometry_data_3d.h"
|
||||||
#include "scene/resources/navigation_polygon.h"
|
#include "scene/resources/navigation_polygon.h"
|
||||||
#include "scene/resources/packed_scene.h"
|
#include "scene/resources/packed_scene.h"
|
||||||
|
@ -956,6 +957,7 @@ void register_scene_types() {
|
||||||
GDREGISTER_CLASS(PathFollow2D);
|
GDREGISTER_CLASS(PathFollow2D);
|
||||||
|
|
||||||
GDREGISTER_CLASS(NavigationMesh);
|
GDREGISTER_CLASS(NavigationMesh);
|
||||||
|
GDREGISTER_CLASS(NavigationMeshSourceGeometryData2D);
|
||||||
GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D);
|
GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D);
|
||||||
GDREGISTER_CLASS(NavigationPolygon);
|
GDREGISTER_CLASS(NavigationPolygon);
|
||||||
GDREGISTER_CLASS(NavigationRegion2D);
|
GDREGISTER_CLASS(NavigationRegion2D);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
#include "core/math/geometry_2d.h"
|
#include "core/math/geometry_2d.h"
|
||||||
#include "core/os/mutex.h"
|
#include "core/os/mutex.h"
|
||||||
|
#include "servers/navigation_server_2d.h"
|
||||||
|
|
||||||
#include "thirdparty/misc/polypartition.h"
|
#include "thirdparty/misc/polypartition.h"
|
||||||
|
|
||||||
|
@ -229,7 +230,11 @@ void NavigationPolygon::clear_outlines() {
|
||||||
rect_cache_dirty = true;
|
rect_cache_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
void NavigationPolygon::make_polygons_from_outlines() {
|
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);
|
MutexLock lock(navigation_mesh_generation);
|
||||||
navigation_mesh.unref();
|
navigation_mesh.unref();
|
||||||
|
@ -331,6 +336,7 @@ void NavigationPolygon::make_polygons_from_outlines() {
|
||||||
|
|
||||||
emit_changed();
|
emit_changed();
|
||||||
}
|
}
|
||||||
|
#endif // DISABLE_DEPRECATED
|
||||||
|
|
||||||
void NavigationPolygon::set_cell_size(real_t p_cell_size) {
|
void NavigationPolygon::set_cell_size(real_t p_cell_size) {
|
||||||
cell_size = p_cell_size;
|
cell_size = p_cell_size;
|
||||||
|
@ -341,6 +347,69 @@ real_t NavigationPolygon::get_cell_size() const {
|
||||||
return cell_size;
|
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() {
|
void NavigationPolygon::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationPolygon::set_vertices);
|
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationPolygon::set_vertices);
|
||||||
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationPolygon::get_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("get_outline", "idx"), &NavigationPolygon::get_outline);
|
||||||
ClassDB::bind_method(D_METHOD("remove_outline", "idx"), &NavigationPolygon::remove_outline);
|
ClassDB::bind_method(D_METHOD("remove_outline", "idx"), &NavigationPolygon::remove_outline);
|
||||||
ClassDB::bind_method(D_METHOD("clear_outlines"), &NavigationPolygon::clear_outlines);
|
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);
|
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("_set_polygons", "polygons"), &NavigationPolygon::_set_polygons);
|
||||||
ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationPolygon::_get_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("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("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);
|
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::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, "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::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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ class NavigationPolygon : public Resource {
|
||||||
};
|
};
|
||||||
Vector<Polygon> polygons;
|
Vector<Polygon> polygons;
|
||||||
Vector<Vector<Vector2>> outlines;
|
Vector<Vector<Vector2>> outlines;
|
||||||
|
Vector<Vector<Vector2>> baked_outlines;
|
||||||
|
|
||||||
mutable Rect2 item_rect;
|
mutable Rect2 item_rect;
|
||||||
mutable bool rect_cache_dirty = true;
|
mutable bool rect_cache_dirty = true;
|
||||||
|
@ -55,6 +56,7 @@ class NavigationPolygon : public Resource {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
void _validate_property(PropertyInfo &p_property) const;
|
||||||
|
|
||||||
void _set_polygons(const TypedArray<Vector<int32_t>> &p_array);
|
void _set_polygons(const TypedArray<Vector<int32_t>> &p_array);
|
||||||
TypedArray<Vector<int32_t>> _get_polygons() const;
|
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;
|
bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
|
||||||
#endif
|
#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);
|
void set_vertices(const Vector<Vector2> &p_vertices);
|
||||||
Vector<Vector2> get_vertices() const;
|
Vector<Vector2> get_vertices() const;
|
||||||
|
|
||||||
|
@ -82,11 +106,33 @@ public:
|
||||||
int get_outline_count() const;
|
int get_outline_count() const;
|
||||||
|
|
||||||
void clear_outlines();
|
void clear_outlines();
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
void make_polygons_from_outlines();
|
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);
|
Vector<int> get_polygon(int p_idx);
|
||||||
void clear_polygons();
|
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();
|
Ref<NavigationMesh> get_navigation_mesh();
|
||||||
|
|
||||||
void set_cell_size(real_t p_cell_size);
|
void set_cell_size(real_t p_cell_size);
|
||||||
|
@ -94,8 +140,11 @@ public:
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
NavigationPolygon() {}
|
NavigationPolygon();
|
||||||
~NavigationPolygon() {}
|
~NavigationPolygon() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
VARIANT_ENUM_CAST(NavigationPolygon::ParsedGeometryType);
|
||||||
|
VARIANT_ENUM_CAST(NavigationPolygon::SourceGeometryMode);
|
||||||
|
|
||||||
#endif // NAVIGATION_POLYGON_H
|
#endif // NAVIGATION_POLYGON_H
|
||||||
|
|
|
@ -28,133 +28,153 @@
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
/* 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"
|
#include "servers/navigation_server_3d.h"
|
||||||
|
|
||||||
NavigationServer2D *NavigationServer2D::singleton = nullptr;
|
NavigationServer2D *NavigationServer2D::singleton = nullptr;
|
||||||
|
|
||||||
#define FORWARD_0(FUNC_NAME) \
|
void NavigationServer2D::_bind_methods() {
|
||||||
NavigationServer2D::FUNC_NAME() { \
|
ClassDB::bind_method(D_METHOD("get_maps"), &NavigationServer2D::get_maps);
|
||||||
return NavigationServer3D::get_singleton()->FUNC_NAME(); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define FORWARD_0_C(FUNC_NAME) \
|
ClassDB::bind_method(D_METHOD("map_create"), &NavigationServer2D::map_create);
|
||||||
NavigationServer2D::FUNC_NAME() \
|
ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &NavigationServer2D::map_set_active);
|
||||||
const { \
|
ClassDB::bind_method(D_METHOD("map_is_active", "map"), &NavigationServer2D::map_is_active);
|
||||||
return NavigationServer3D::get_singleton()->FUNC_NAME(); \
|
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_1(FUNC_NAME, T_0, D_0, CONV_0) \
|
ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer2D::map_get_links);
|
||||||
NavigationServer2D::FUNC_NAME(T_0 D_0) { \
|
ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer2D::map_get_regions);
|
||||||
return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \
|
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_1_C(FUNC_NAME, T_0, D_0, CONV_0) \
|
ClassDB::bind_method(D_METHOD("map_force_update", "map"), &NavigationServer2D::map_force_update);
|
||||||
NavigationServer2D::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) \
|
ClassDB::bind_method(D_METHOD("query_path", "parameters", "result"), &NavigationServer2D::query_path);
|
||||||
NavigationServer2D::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) \
|
ClassDB::bind_method(D_METHOD("region_create"), &NavigationServer2D::region_create);
|
||||||
NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) { \
|
ClassDB::bind_method(D_METHOD("region_set_enabled", "region", "enabled"), &NavigationServer2D::region_set_enabled);
|
||||||
return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \
|
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);
|
||||||
|
|
||||||
#define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \
|
ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create);
|
||||||
NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \
|
ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map);
|
||||||
const { \
|
ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer2D::link_get_map);
|
||||||
return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \
|
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);
|
||||||
|
|
||||||
#define FORWARD_2_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \
|
ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer2D::agent_create);
|
||||||
NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \
|
ClassDB::bind_method(D_METHOD("agent_set_avoidance_enabled", "agent", "enabled"), &NavigationServer2D::agent_set_avoidance_enabled);
|
||||||
const { \
|
ClassDB::bind_method(D_METHOD("agent_get_avoidance_enabled", "agent"), &NavigationServer2D::agent_get_avoidance_enabled);
|
||||||
return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \
|
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);
|
||||||
|
|
||||||
#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) \
|
ClassDB::bind_method(D_METHOD("obstacle_create"), &NavigationServer2D::obstacle_create);
|
||||||
NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \
|
ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_enabled", "obstacle", "enabled"), &NavigationServer2D::obstacle_set_avoidance_enabled);
|
||||||
const { \
|
ClassDB::bind_method(D_METHOD("obstacle_get_avoidance_enabled", "obstacle"), &NavigationServer2D::obstacle_get_avoidance_enabled);
|
||||||
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("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 RID rid_to_rid(const RID 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()));
|
||||||
return d;
|
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()));
|
||||||
|
|
||||||
|
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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool bool_to_bool(const bool d) {
|
NavigationServer2D *NavigationServer2D::get_singleton() {
|
||||||
return d;
|
return singleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int int_to_int(const int d) {
|
NavigationServer2D::NavigationServer2D() {
|
||||||
return d;
|
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));
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationServer2D::_emit_navigation_debug_changed_signal));
|
||||||
|
#endif // DEBUG_ENABLED
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t uint32_to_uint32(const uint32_t d) {
|
#ifdef DEBUG_ENABLED
|
||||||
return d;
|
void NavigationServer2D::_emit_navigation_debug_changed_signal() {
|
||||||
|
emit_signal(SNAME("navigation_debug_changed"));
|
||||||
}
|
}
|
||||||
|
#endif // DEBUG_ENABLED
|
||||||
|
|
||||||
static real_t real_to_real(const real_t d) {
|
NavigationServer2D::~NavigationServer2D() {
|
||||||
return d;
|
singleton = nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
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 NavigationServer2D::_emit_map_changed(RID p_map) {
|
void NavigationServer2D::_emit_map_changed(RID p_map) {
|
||||||
|
@ -363,286 +383,18 @@ bool NavigationServer2D::get_debug_navigation_avoidance_enable_obstacles_static(
|
||||||
}
|
}
|
||||||
#endif // DEBUG_ENABLED
|
#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);
|
NavigationServer2DCallback NavigationServer2DManager::create_callback = nullptr;
|
||||||
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);
|
void NavigationServer2DManager::set_default_server(NavigationServer2DCallback p_callback) {
|
||||||
ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer2D::map_get_regions);
|
create_callback = p_callback;
|
||||||
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);
|
|
||||||
|
|
||||||
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() {
|
NavigationServer2D *NavigationServer2DManager::new_default_server() {
|
||||||
singleton = this;
|
if (create_callback == nullptr) {
|
||||||
ERR_FAIL_COND_MSG(!NavigationServer3D::get_singleton(), "The Navigation3D singleton should be initialized before the 2D one.");
|
return nullptr;
|
||||||
NavigationServer3D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationServer2D::_emit_map_changed));
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
return create_callback();
|
||||||
NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationServer2D::_emit_navigation_debug_changed_signal));
|
|
||||||
#endif // DEBUG_ENABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
#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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,15 @@
|
||||||
#include "core/object/class_db.h"
|
#include "core/object/class_db.h"
|
||||||
#include "core/templates/rid.h"
|
#include "core/templates/rid.h"
|
||||||
|
|
||||||
|
#include "scene/resources/navigation_mesh_source_geometry_data_2d.h"
|
||||||
#include "scene/resources/navigation_polygon.h"
|
#include "scene/resources/navigation_polygon.h"
|
||||||
#include "servers/navigation/navigation_path_query_parameters_2d.h"
|
#include "servers/navigation/navigation_path_query_parameters_2d.h"
|
||||||
#include "servers/navigation/navigation_path_query_result_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.
|
// This server exposes the `NavigationServer3D` features in the 2D world.
|
||||||
class NavigationServer2D : public Object {
|
class NavigationServer2D : public Object {
|
||||||
GDCLASS(NavigationServer2D, Object);
|
GDCLASS(NavigationServer2D, Object);
|
||||||
|
@ -51,145 +56,145 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Thread safe, can be used across many threads.
|
/// 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.
|
/// Create a new map.
|
||||||
virtual RID map_create();
|
virtual RID map_create() = 0;
|
||||||
|
|
||||||
/// Set map active.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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 void map_set_use_edge_connections(RID p_map, bool p_enabled) = 0;
|
||||||
virtual bool map_get_use_edge_connections(RID p_map) const;
|
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.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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 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;
|
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_links(RID p_map) const = 0;
|
||||||
virtual TypedArray<RID> map_get_regions(RID p_map) const;
|
virtual TypedArray<RID> map_get_regions(RID p_map) const = 0;
|
||||||
virtual TypedArray<RID> map_get_agents(RID p_map) const;
|
virtual TypedArray<RID> map_get_agents(RID p_map) const = 0;
|
||||||
virtual TypedArray<RID> map_get_obstacles(RID p_map) const;
|
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.
|
/// 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 void region_set_enabled(RID p_region, bool p_enabled) = 0;
|
||||||
virtual bool region_get_enabled(RID p_region) const;
|
virtual bool region_get_enabled(RID p_region) const = 0;
|
||||||
|
|
||||||
virtual void region_set_use_edge_connections(RID p_region, bool p_enabled);
|
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;
|
virtual bool region_get_use_edge_connections(RID p_region) const = 0;
|
||||||
|
|
||||||
/// Set the enter_cost of a region
|
/// Set the enter_cost of a region
|
||||||
virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost);
|
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;
|
virtual real_t region_get_enter_cost(RID p_region) const = 0;
|
||||||
|
|
||||||
/// Set the travel_cost of a region
|
/// Set the travel_cost of a region
|
||||||
virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost);
|
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;
|
virtual real_t region_get_travel_cost(RID p_region) const = 0;
|
||||||
|
|
||||||
/// Set the node which manages this region.
|
/// Set the node which manages this region.
|
||||||
virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id);
|
virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id) = 0;
|
||||||
virtual ObjectID region_get_owner_id(RID p_region) const;
|
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.
|
/// Set the map of this region.
|
||||||
virtual void region_set_map(RID p_region, RID p_map);
|
virtual void region_set_map(RID p_region, RID p_map) = 0;
|
||||||
virtual RID region_get_map(RID p_region) const;
|
virtual RID region_get_map(RID p_region) const = 0;
|
||||||
|
|
||||||
/// Set the region's layers
|
/// Set the region's layers
|
||||||
virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers);
|
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;
|
virtual uint32_t region_get_navigation_layers(RID p_region) const = 0;
|
||||||
|
|
||||||
/// Set the global transformation of this region.
|
/// 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.
|
/// 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.
|
/// Get a list of a region's connection to other regions.
|
||||||
virtual int region_get_connections_count(RID p_region) 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;
|
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;
|
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.
|
/// 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.
|
/// Set the map of this link.
|
||||||
virtual void link_set_map(RID p_link, RID p_map);
|
virtual void link_set_map(RID p_link, RID p_map) = 0;
|
||||||
virtual RID link_get_map(RID p_link) const;
|
virtual RID link_get_map(RID p_link) const = 0;
|
||||||
|
|
||||||
virtual void link_set_enabled(RID p_link, bool p_enabled);
|
virtual void link_set_enabled(RID p_link, bool p_enabled) = 0;
|
||||||
virtual bool link_get_enabled(RID p_link) const;
|
virtual bool link_get_enabled(RID p_link) const = 0;
|
||||||
|
|
||||||
/// Set whether this link travels in both directions.
|
/// Set whether this link travels in both directions.
|
||||||
virtual void link_set_bidirectional(RID p_link, bool p_bidirectional);
|
virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) = 0;
|
||||||
virtual bool link_is_bidirectional(RID p_link) const;
|
virtual bool link_is_bidirectional(RID p_link) const = 0;
|
||||||
|
|
||||||
/// Set the link's layers.
|
/// Set the link's layers.
|
||||||
virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers);
|
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;
|
virtual uint32_t link_get_navigation_layers(RID p_link) const = 0;
|
||||||
|
|
||||||
/// Set the start position of the link.
|
/// Set the start position of the link.
|
||||||
virtual void link_set_start_position(RID p_link, Vector2 p_position);
|
virtual void link_set_start_position(RID p_link, Vector2 p_position) = 0;
|
||||||
virtual Vector2 link_get_start_position(RID p_link) const;
|
virtual Vector2 link_get_start_position(RID p_link) const = 0;
|
||||||
|
|
||||||
/// Set the end position of the link.
|
/// Set the end position of the link.
|
||||||
virtual void link_set_end_position(RID p_link, Vector2 p_position);
|
virtual void link_set_end_position(RID p_link, Vector2 p_position) = 0;
|
||||||
virtual Vector2 link_get_end_position(RID p_link) const;
|
virtual Vector2 link_get_end_position(RID p_link) const = 0;
|
||||||
|
|
||||||
/// Set the enter cost of the link.
|
/// Set the enter cost of the link.
|
||||||
virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost);
|
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;
|
virtual real_t link_get_enter_cost(RID p_link) const = 0;
|
||||||
|
|
||||||
/// Set the travel cost of the link.
|
/// Set the travel cost of the link.
|
||||||
virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost);
|
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;
|
virtual real_t link_get_travel_cost(RID p_link) const = 0;
|
||||||
|
|
||||||
/// Set the node which manages this link.
|
/// Set the node which manages this link.
|
||||||
virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id);
|
virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id) = 0;
|
||||||
virtual ObjectID link_get_owner_id(RID p_link) const;
|
virtual ObjectID link_get_owner_id(RID p_link) const = 0;
|
||||||
|
|
||||||
/// Creates the agent.
|
/// Creates the agent.
|
||||||
virtual RID agent_create();
|
virtual RID agent_create() = 0;
|
||||||
|
|
||||||
/// Put the agent in the map.
|
/// Put the agent in the map.
|
||||||
virtual void agent_set_map(RID p_agent, RID p_map);
|
virtual void agent_set_map(RID p_agent, RID p_map) = 0;
|
||||||
virtual RID agent_get_map(RID p_agent) const;
|
virtual RID agent_get_map(RID p_agent) const = 0;
|
||||||
|
|
||||||
virtual void agent_set_paused(RID p_agent, bool p_paused);
|
virtual void agent_set_paused(RID p_agent, bool p_paused) = 0;
|
||||||
virtual bool agent_get_paused(RID p_agent) const;
|
virtual bool agent_get_paused(RID p_agent) const = 0;
|
||||||
|
|
||||||
virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled);
|
virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) = 0;
|
||||||
virtual bool agent_get_avoidance_enabled(RID p_agent) const;
|
virtual bool agent_get_avoidance_enabled(RID p_agent) const = 0;
|
||||||
|
|
||||||
/// The maximum distance (center point to
|
/// The maximum distance (center point to
|
||||||
/// center point) to other agents this agent
|
/// center point) to other agents this agent
|
||||||
|
@ -198,7 +203,7 @@ public:
|
||||||
/// time of the simulation. If the number is too
|
/// time of the simulation. If the number is too
|
||||||
/// low, the simulation will not be safe.
|
/// low, the simulation will not be safe.
|
||||||
/// Must be non-negative.
|
/// 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
|
/// The maximum number of other agents this
|
||||||
/// agent takes into account in the navigation.
|
/// agent takes into account in the navigation.
|
||||||
|
@ -206,7 +211,7 @@ public:
|
||||||
/// running time of the simulation. If the
|
/// running time of the simulation. If the
|
||||||
/// number is too low, the simulation will not
|
/// number is too low, the simulation will not
|
||||||
/// be safe.
|
/// 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
|
/// The minimal amount of time for which this
|
||||||
/// agent's velocities that are computed by the
|
/// agent's velocities that are computed by the
|
||||||
|
@ -217,59 +222,67 @@ public:
|
||||||
/// agent has in choosing its velocities.
|
/// agent has in choosing its velocities.
|
||||||
/// Must be positive.
|
/// Must be positive.
|
||||||
|
|
||||||
virtual void agent_set_time_horizon_agents(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);
|
virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) = 0;
|
||||||
|
|
||||||
/// The radius of this agent.
|
/// The radius of this agent.
|
||||||
/// Must be non-negative.
|
/// 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.
|
/// The maximum speed of this agent.
|
||||||
/// Must be non-negative.
|
/// 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
|
/// 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 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'.
|
/// 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.
|
/// 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.
|
/// 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
|
/// 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_layers(RID p_agent, uint32_t p_layers) = 0;
|
||||||
virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask);
|
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);
|
virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) = 0;
|
||||||
|
|
||||||
/// Creates the obstacle.
|
/// Creates the obstacle.
|
||||||
virtual RID obstacle_create();
|
virtual RID obstacle_create() = 0;
|
||||||
virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled);
|
virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) = 0;
|
||||||
virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const;
|
virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const = 0;
|
||||||
virtual void obstacle_set_map(RID p_obstacle, RID p_map);
|
virtual void obstacle_set_map(RID p_obstacle, RID p_map) = 0;
|
||||||
virtual RID obstacle_get_map(RID p_obstacle) const;
|
virtual RID obstacle_get_map(RID p_obstacle) const = 0;
|
||||||
virtual void obstacle_set_paused(RID p_obstacle, bool p_paused);
|
virtual void obstacle_set_paused(RID p_obstacle, bool p_paused) = 0;
|
||||||
virtual bool obstacle_get_paused(RID p_obstacle) const;
|
virtual bool obstacle_get_paused(RID p_obstacle) const = 0;
|
||||||
virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius);
|
virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius) = 0;
|
||||||
virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity);
|
virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) = 0;
|
||||||
virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position);
|
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);
|
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);
|
virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) = 0;
|
||||||
|
|
||||||
/// Returns a customized navigation path using a query parameters object
|
/// 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`
|
/// 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();
|
NavigationServer2D();
|
||||||
virtual ~NavigationServer2D();
|
~NavigationServer2D() override;
|
||||||
|
|
||||||
void set_debug_enabled(bool p_enabled);
|
void set_debug_enabled(bool p_enabled);
|
||||||
bool get_debug_enabled() const;
|
bool get_debug_enabled() const;
|
||||||
|
@ -354,4 +367,15 @@ private:
|
||||||
#endif // DEBUG_ENABLED
|
#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
|
#endif // NAVIGATION_SERVER_2D_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
|
|
@ -302,6 +302,7 @@ public:
|
||||||
/// Note: This function is not thread safe.
|
/// Note: This function is not thread safe.
|
||||||
virtual void process(real_t delta_time) = 0;
|
virtual void process(real_t delta_time) = 0;
|
||||||
virtual void init() = 0;
|
virtual void init() = 0;
|
||||||
|
virtual void sync() = 0;
|
||||||
virtual void finish() = 0;
|
virtual void finish() = 0;
|
||||||
|
|
||||||
/// Returns a customized navigation path using a query parameters object
|
/// Returns a customized navigation path using a query parameters object
|
||||||
|
|
|
@ -152,6 +152,7 @@ public:
|
||||||
void set_active(bool p_active) override {}
|
void set_active(bool p_active) override {}
|
||||||
void process(real_t delta_time) override {}
|
void process(real_t delta_time) override {}
|
||||||
void init() override {}
|
void init() override {}
|
||||||
|
void sync() override {}
|
||||||
void finish() override {}
|
void finish() override {}
|
||||||
NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override { return NavigationUtilities::PathQueryResult(); }
|
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; }
|
int get_process_info(ProcessInfo p_info) const override { return 0; }
|
||||||
|
|
|
@ -252,7 +252,7 @@ struct GodotTestCaseListener : public doctest::IReporter {
|
||||||
|
|
||||||
ERR_PRINT_OFF;
|
ERR_PRINT_OFF;
|
||||||
navigation_server_3d = NavigationServer3DManager::new_default_server();
|
navigation_server_3d = NavigationServer3DManager::new_default_server();
|
||||||
navigation_server_2d = memnew(NavigationServer2D);
|
navigation_server_2d = NavigationServer2DManager::new_default_server();
|
||||||
ERR_PRINT_ON;
|
ERR_PRINT_ON;
|
||||||
|
|
||||||
memnew(InputMap);
|
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) {
|
if (suite_name.find("[Navigation]") != -1 && navigation_server_2d == nullptr && navigation_server_3d == nullptr) {
|
||||||
ERR_PRINT_OFF;
|
ERR_PRINT_OFF;
|
||||||
navigation_server_3d = NavigationServer3DManager::new_default_server();
|
navigation_server_3d = NavigationServer3DManager::new_default_server();
|
||||||
navigation_server_2d = memnew(NavigationServer2D);
|
navigation_server_2d = NavigationServer2DManager::new_default_server();
|
||||||
ERR_PRINT_ON;
|
ERR_PRINT_ON;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,18 @@ Files extracted from upstream source:
|
||||||
- License: MPL 2.0
|
- 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
|
## cvtt
|
||||||
|
|
||||||
- Upstream: https://github.com/elasota/ConvectionKernels
|
- Upstream: https://github.com/elasota/ConvectionKernels
|
||||||
|
|
|
@ -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.
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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_ */
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue