[3.5] Update NavigationServer backport

Backports features and bugfixes from current Godot 4.0 to 3.5 and brings functions and codebase of both version largely in sync to make tutorials more compatible and future backports easier.
This commit is contained in:
smix8 2022-06-13 15:51:23 +02:00
parent 0d2be435ea
commit 8bd7c6188b
60 changed files with 1859 additions and 749 deletions

View file

@ -592,8 +592,10 @@ void register_global_constants() {
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_2D_RENDER);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_2D_PHYSICS);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_2D_NAVIGATION);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_RENDER);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_PHYSICS);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_NAVIGATION);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_FILE);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_DIR);

View file

@ -67,8 +67,10 @@ enum PropertyHint {
PROPERTY_HINT_FLAGS, ///< hint_text= "flag1,flag2,etc" (as bit flags)
PROPERTY_HINT_LAYERS_2D_RENDER,
PROPERTY_HINT_LAYERS_2D_PHYSICS,
PROPERTY_HINT_LAYERS_2D_NAVIGATION,
PROPERTY_HINT_LAYERS_3D_RENDER,
PROPERTY_HINT_LAYERS_3D_PHYSICS,
PROPERTY_HINT_LAYERS_3D_NAVIGATION,
PROPERTY_HINT_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,"
PROPERTY_HINT_DIR, ///< a directory path must be passed
PROPERTY_HINT_GLOBAL_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,"

View file

@ -1421,7 +1421,7 @@
Hints that an integer, float or string property is an enumerated value to pick in a list specified via a hint string.
The hint string is a comma separated list of names such as [code]"Hello,Something,Else"[/code]. For integer and float properties, the first name in the list has value 0, the next 1, and so on. Explicit values can also be specified by appending [code]:integer[/code] to the name, e.g. [code]"Zero,One,Three:3,Four,Six:6"[/code].
</constant>
<constant name="PROPERTY_HINT_ENUM_SUGGESTION" value="37" enum="PropertyHint">
<constant name="PROPERTY_HINT_ENUM_SUGGESTION" value="39" enum="PropertyHint">
Hints that a string property can be an enumerated value to pick in a list specified via a hint string such as [code]"Hello,Something,Else"[/code].
Unlike [constant PROPERTY_HINT_ENUM] a property with this hint still accepts arbitrary values and can be empty. The list of values serves to suggest possible values.
</constant>
@ -1443,40 +1443,46 @@
<constant name="PROPERTY_HINT_LAYERS_2D_PHYSICS" value="10" enum="PropertyHint">
Hints that an integer property is a bitmask using the optionally named 2D physics layers.
</constant>
<constant name="PROPERTY_HINT_LAYERS_3D_RENDER" value="11" enum="PropertyHint">
<constant name="PROPERTY_HINT_LAYERS_2D_NAVIGATION" value="11" enum="PropertyHint">
Hints that an integer property is a bitmask using the optionally named 2D navigation layers.
</constant>
<constant name="PROPERTY_HINT_LAYERS_3D_RENDER" value="12" enum="PropertyHint">
Hints that an integer property is a bitmask using the optionally named 3D render layers.
</constant>
<constant name="PROPERTY_HINT_LAYERS_3D_PHYSICS" value="12" enum="PropertyHint">
<constant name="PROPERTY_HINT_LAYERS_3D_PHYSICS" value="13" enum="PropertyHint">
Hints that an integer property is a bitmask using the optionally named 3D physics layers.
</constant>
<constant name="PROPERTY_HINT_FILE" value="13" enum="PropertyHint">
<constant name="PROPERTY_HINT_LAYERS_3D_NAVIGATION" value="14" enum="PropertyHint">
Hints that an integer property is a bitmask using the optionally named 2D navigation layers.
</constant>
<constant name="PROPERTY_HINT_FILE" value="15" enum="PropertyHint">
Hints that a string property is a path to a file. Editing it will show a file dialog for picking the path. The hint string can be a set of filters with wildcards like [code]"*.png,*.jpg"[/code].
</constant>
<constant name="PROPERTY_HINT_DIR" value="14" enum="PropertyHint">
<constant name="PROPERTY_HINT_DIR" value="16" enum="PropertyHint">
Hints that a string property is a path to a directory. Editing it will show a file dialog for picking the path.
</constant>
<constant name="PROPERTY_HINT_GLOBAL_FILE" value="15" enum="PropertyHint">
<constant name="PROPERTY_HINT_GLOBAL_FILE" value="17" enum="PropertyHint">
Hints that a string property is an absolute path to a file outside the project folder. Editing it will show a file dialog for picking the path. The hint string can be a set of filters with wildcards like [code]"*.png,*.jpg"[/code].
</constant>
<constant name="PROPERTY_HINT_GLOBAL_DIR" value="16" enum="PropertyHint">
<constant name="PROPERTY_HINT_GLOBAL_DIR" value="18" enum="PropertyHint">
Hints that a string property is an absolute path to a directory outside the project folder. Editing it will show a file dialog for picking the path.
</constant>
<constant name="PROPERTY_HINT_RESOURCE_TYPE" value="17" enum="PropertyHint">
<constant name="PROPERTY_HINT_RESOURCE_TYPE" value="19" enum="PropertyHint">
Hints that a property is an instance of a [Resource]-derived type, optionally specified via the hint string (e.g. [code]"Texture"[/code]). Editing it will show a popup menu of valid resource types to instantiate.
</constant>
<constant name="PROPERTY_HINT_MULTILINE_TEXT" value="18" enum="PropertyHint">
<constant name="PROPERTY_HINT_MULTILINE_TEXT" value="20" enum="PropertyHint">
Hints that a string property is text with line breaks. Editing it will show a text input field where line breaks can be typed.
</constant>
<constant name="PROPERTY_HINT_PLACEHOLDER_TEXT" value="19" enum="PropertyHint">
<constant name="PROPERTY_HINT_PLACEHOLDER_TEXT" value="21" enum="PropertyHint">
Hints that a string property should have a placeholder text visible on its input field, whenever the property is empty. The hint string is the placeholder text to use.
</constant>
<constant name="PROPERTY_HINT_COLOR_NO_ALPHA" value="20" enum="PropertyHint">
<constant name="PROPERTY_HINT_COLOR_NO_ALPHA" value="22" enum="PropertyHint">
Hints that a color property should be edited without changing its alpha component, i.e. only R, G and B channels are edited.
</constant>
<constant name="PROPERTY_HINT_IMAGE_COMPRESS_LOSSY" value="21" enum="PropertyHint">
<constant name="PROPERTY_HINT_IMAGE_COMPRESS_LOSSY" value="23" enum="PropertyHint">
Hints that an image is compressed using lossy compression.
</constant>
<constant name="PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS" value="22" enum="PropertyHint">
<constant name="PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS" value="24" enum="PropertyHint">
Hints that an image is compressed using lossless compression.
</constant>
<constant name="PROPERTY_USAGE_STORAGE" value="1" enum="PropertyUsageFlags">

View file

@ -43,7 +43,7 @@
<method name="get_rid" qualifiers="const">
<return type="RID" />
<description>
Returns the object's [RID].
Returns the [RID] of the navigation map on the [NavigationServer].
</description>
</method>
<method name="get_simple_path" qualifiers="const">
@ -57,19 +57,30 @@
</method>
</methods>
<members>
<member name="cell_height" type="float" setter="set_cell_height" getter="get_cell_height" default="0.2">
<member name="cell_height" type="float" setter="set_cell_height" getter="get_cell_height" default="0.25">
The cell height to use for fields.
</member>
<member name="cell_size" type="float" setter="set_cell_size" getter="get_cell_size" default="0.3">
<member name="cell_size" type="float" setter="set_cell_size" getter="get_cell_size" default="0.25">
The XZ plane cell size to use for fields.
</member>
<member name="edge_connection_margin" type="float" setter="set_edge_connection_margin" getter="get_edge_connection_margin" default="5.0">
<member name="edge_connection_margin" type="float" setter="set_edge_connection_margin" getter="get_edge_connection_margin" default="0.25">
This value is used to detect the near edges to connect compatible regions.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation map layers the navigation can use on a [method Navigation.get_simple_path] path query.
</member>
<member name="up_vector" type="Vector3" setter="set_up_vector" getter="get_up_vector" default="Vector3( 0, 1, 0 )">
Defines which direction is up. By default, this is [code](0, 1, 0)[/code], which is the world's "up" direction.
</member>
</members>
<signals>
<signal name="map_changed">
<argument index="0" name="map" type="RID" />
<description>
Emitted when a navigation map is updated, when a region moves or is modified.
</description>
</signal>
</signals>
<constants>
</constants>
</class>

View file

@ -47,6 +47,9 @@
<member name="edge_connection_margin" type="float" setter="set_edge_connection_margin" getter="get_edge_connection_margin" default="1.0">
This value is used to detect the near edges to connect compatible regions.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation map layers the navigation can use on a [method Navigation2D.get_simple_path] path query.
</member>
</members>
<constants>
</constants>

View file

@ -7,7 +7,8 @@
Navigation2DServer is the server responsible for all 2D navigation. It handles several objects, namely maps, regions and agents.
Maps are made up of regions, which are made of navigation polygons. Together, they define the navigable areas in the 2D world.
[b]Note:[/b] Most NavigationServer changes take effect after the next physics frame and not immediately. This includes all changes made to maps, regions or agents by navigation related Nodes in the SceneTree or made through scripts.
For two regions to be connected to each other, they must share a similar edge. An edge is considered connected to another if both of its two vertices are at a distance less than [member Navigation.edge_connection_margin] to the respective other edge's vertex.
For two regions to be connected to each other, they must share a similar edge. An edge is considered connected to another if both of its two vertices are at a distance less than navigation map [code]edge_connection_margin[/code] to the respective other edge's vertex.
You may assign navigation layers to regions with [method Navigation2DServer.region_set_navigation_layers], which then can be checked upon when requesting a path with [method Navigation2DServer.map_get_path]. This allows allowing or forbidding some areas to 2D objects.
To use the collision avoidance system, you may use agents. You can set an agent's target velocity, then the servers will emit a callback with a modified velocity.
[b]Note:[/b] The collision avoidance system ignores regions. Using the modified velocity as-is might lead to pushing and agent outside of a navigable area. This is a limitation of the collision avoidance system, any more complex situation may require the use of the physics engine.
This server keeps tracks of any call and executes them during the sync phase. This means that you can request any change to the map, using any thread, without worrying.
@ -138,6 +139,13 @@
Returns all navigation agents [RID]s that are currently assigned to the requested navigation [code]map[/code].
</description>
</method>
<method name="map_get_cell_height" qualifiers="const">
<return type="float" />
<argument index="0" name="map" type="RID" />
<description>
Returns the map cell height. [b]Note:[/b] Currently not implemented.
</description>
</method>
<method name="map_get_cell_size" qualifiers="const">
<return type="float" />
<argument index="0" name="map" type="RID" />
@ -174,8 +182,9 @@
<argument index="1" name="origin" type="Vector2" />
<argument index="2" name="destination" type="Vector2" />
<argument index="3" name="optimize" type="bool" />
<argument index="4" name="navigation_layers" type="int" default="1" />
<description>
Returns the navigation path to reach the destination from the origin.
Returns the navigation path to reach the destination from the origin. [code]navigation_layers[/code] is a bitmask of all region layers that are allowed to be in the path.
</description>
</method>
<method name="map_get_regions" qualifiers="const">
@ -200,6 +209,14 @@
Sets the map active.
</description>
</method>
<method name="map_set_cell_height" qualifiers="const">
<return type="void" />
<argument index="0" name="map" type="RID" />
<argument index="1" name="cell_height" type="float" />
<description>
Set the map cell height used to weld the navigation mesh polygons. [b]Note:[/b] Currently not implemented.
</description>
</method>
<method name="map_set_cell_size" qualifiers="const">
<return type="void" />
<argument index="0" name="map" type="RID" />
@ -222,6 +239,36 @@
Creates a new region.
</description>
</method>
<method name="region_get_connection_pathway_end" qualifiers="const">
<return type="Vector2" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="connection" type="int" />
<description>
Returns the ending point of a connection door. [code]connection[/code] is an index between 0 and the return value of [method region_get_connections_count].
</description>
</method>
<method name="region_get_connection_pathway_start" qualifiers="const">
<return type="Vector2" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="connection" type="int" />
<description>
Returns the starting point of a connection door. [code]connection[/code] is an index between 0 and the return value of [method region_get_connections_count].
</description>
</method>
<method name="region_get_connections_count" qualifiers="const">
<return type="int" />
<argument index="0" name="region" type="RID" />
<description>
Returns how many connections this [code]region[/code] has with other regions in the map.
</description>
</method>
<method name="region_get_enter_cost" qualifiers="const">
<return type="float" />
<argument index="0" name="region" type="RID" />
<description>
Returns the [code]enter_cost[/code] of this [code]region[/code].
</description>
</method>
<method name="region_get_map" qualifiers="const">
<return type="RID" />
<argument index="0" name="region" type="RID" />
@ -229,6 +276,28 @@
Returns the navigation map [RID] the requested [code]region[/code] is currently assigned to.
</description>
</method>
<method name="region_get_navigation_layers" qualifiers="const">
<return type="int" />
<argument index="0" name="region" type="RID" />
<description>
Returns the region's navigation layers.
</description>
</method>
<method name="region_get_travel_cost" qualifiers="const">
<return type="float" />
<argument index="0" name="region" type="RID" />
<description>
Returns the [code]travel_cost[/code] of this [code]region[/code].
</description>
</method>
<method name="region_set_enter_cost" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="enter_cost" type="float" />
<description>
Sets the [code]enter_cost[/code] for this [code]region[/code].
</description>
</method>
<method name="region_set_map" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
@ -237,6 +306,14 @@
Sets the map for the region.
</description>
</method>
<method name="region_set_navigation_layers" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="navigation_layers" type="int" />
<description>
Set the region's navigation layers. This allows selecting regions from a path request (when using [method Navigation2DServer.map_get_path]).
</description>
</method>
<method name="region_set_navpoly" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
@ -253,7 +330,23 @@
Sets the global transformation for the region.
</description>
</method>
<method name="region_set_travel_cost" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="travel_cost" type="float" />
<description>
Sets the [code]travel_cost[/code] for this [code]region[/code].
</description>
</method>
</methods>
<signals>
<signal name="map_changed">
<argument index="0" name="map" type="RID" />
<description>
Emitted when a navigation map is updated, when a region moves or is modified.
</description>
</signal>
</signals>
<constants>
</constants>
</class>

View file

@ -4,7 +4,7 @@
3D agent used in navigation for collision avoidance.
</brief_description>
<description>
3D agent that is used in navigation to reach a location while avoiding static and dynamic obstacles. The dynamic obstacles are avoided using RVO (Reciprocal Velocity Obstacles) collision avoidance. The agent needs navigation data to work correctly. This can be done by having the agent as a child of a [Navigation] node, or using [method set_navigation]. [NavigationAgent] is physics safe.
3D agent that is used in navigation to reach a location while avoiding static and dynamic obstacles. The dynamic obstacles are avoided using RVO (Reciprocal Velocity Obstacles) collision avoidance. The agent needs navigation data to work correctly. By default this node will register to the default [World] navigation map. If this node is a child of a [Navigation] node it will register to the navigation map of the navigation node or the function [method set_navigation] can be used to set the navigation node directly. [NavigationAgent] is physics safe.
[b]Note:[/b] After [method set_target_location] is used it is required to use the [method get_next_location] function once every physics frame to update the internal path logic of the NavigationAgent. The returned vector position from this function should be used as the next movement position for the agent's parent Node.
</description>
<tutorials>
@ -114,6 +114,9 @@
<member name="max_speed" type="float" setter="set_max_speed" getter="get_max_speed" default="10.0">
The maximum speed that an agent can move.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation map layers the [NavigationAgent] belongs to. On path requests the agent will ignore navmeshes without at least one matching layer.
</member>
<member name="neighbor_dist" type="float" setter="set_neighbor_dist" getter="get_neighbor_dist" default="50.0">
The distance to search for other agents.
</member>

View file

@ -4,7 +4,7 @@
2D agent used in navigation for collision avoidance.
</brief_description>
<description>
2D agent that is used in navigation to reach a location while avoiding static and dynamic obstacles. The dynamic obstacles are avoided using RVO (Reciprocal Velocity Obstacles) collision avoidance. The agent needs navigation data to work correctly. This can be done by having the agent as a child of a [Navigation2D] node, or using [method set_navigation]. [NavigationAgent2D] is physics safe.
2D agent that is used in navigation to reach a location while avoiding static and dynamic obstacles. The dynamic obstacles are avoided using RVO (Reciprocal Velocity Obstacles) collision avoidance. The agent needs navigation data to work correctly. By default this node will register to the default [World2D] navigation map. If this node is a child of a [Navigation2D] node it will register to the navigation map of the navigation node or the function [method set_navigation] can be used to set the navigation node directly. [NavigationAgent2D] is physics safe.
[b]Note:[/b] After [method set_target_location] is used it is required to use the [method get_next_location] function once every physics frame to update the internal path logic of the NavigationAgent. The returned vector position from this function should be used as the next movement position for the agent's parent Node.
</description>
<tutorials>
@ -108,6 +108,9 @@
<member name="max_speed" type="float" setter="set_max_speed" getter="get_max_speed" default="200.0">
The maximum speed that an agent can move.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation map layers the [NavigationAgent2D] belongs to. On path requests the agent will ignore navmeshes without at least one matching layer.
</member>
<member name="neighbor_dist" type="float" setter="set_neighbor_dist" getter="get_neighbor_dist" default="500.0">
The distance to search for other agents.
</member>

View file

@ -98,7 +98,7 @@
<member name="detail_sample_distance" type="float" setter="set_detail_sample_distance" getter="get_detail_sample_distance" default="6.0">
The sampling distance to use when generating the detail mesh, in cell unit.
</member>
<member name="detail_sample_max_error" type="float" setter="set_detail_sample_max_error" getter="get_detail_sample_max_error" default="5.0">
<member name="detail_sample_max_error" type="float" setter="set_detail_sample_max_error" getter="get_detail_sample_max_error" default="1.0">
The maximum distance the detail mesh surface should deviate from heightfield, in cell unit.
</member>
<member name="edge_max_error" type="float" setter="set_edge_max_error" getter="get_edge_max_error" default="1.3">

View file

@ -4,7 +4,13 @@
An instance of a [NavigationMesh].
</brief_description>
<description>
An instance of a [NavigationMesh]. It tells the [Navigation] node what can be navigated and what cannot, based on the [NavigationMesh] resource. This should be a child of a [Navigation] node.
An instance of a [NavigationMesh]. It tells the [Navigation] node what can be navigated and what cannot, based on the [NavigationMesh] resource.
By default this node will register to the default [World] navigation map. If this node is a child of a [Navigation] node it will register to the navigation map of the navigation node.
Two regions can be connected to each other if they share a similar edge. You can set the minimum distance between two vertices required to connect two edges by using [method NavigationServer.map_set_edge_connection_margin].
[b]Note:[/b] Overlapping two regions' navmeshes is not enough for connecting two regions. They must share a similar edge.
The cost of entering this region from another region can be controlled with the [member enter_cost] value.
[b]Note[/b]: This value is not added to the path cost when the start position is already inside this region.
The cost of traveling distances inside this region can be controlled with the [member travel_cost] multiplier.
</description>
<tutorials>
</tutorials>
@ -27,9 +33,18 @@
<member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true">
Determines if the [NavigationMeshInstance] is enabled or disabled.
</member>
<member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0">
When pathfinding enters this regions navmesh from another regions navmesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation map layers the [NavigationMesh] belongs to. On path requests with [method NavigationServer.map_get_path] navmeshes without matching layers will be ignored and the navigation map will only proximity merge different navmeshes with matching layers.
</member>
<member name="navmesh" type="NavigationMesh" setter="set_navigation_mesh" getter="get_navigation_mesh">
The [NavigationMesh] resource to use.
</member>
<member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0">
When pathfinding moves inside this regions navmesh the traveled distances are multiplied with [code]travel_cost[/code] for determining the shortest path.
</member>
</members>
<signals>
<signal name="bake_finished">

View file

@ -1,8 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="NavigationPolygonInstance" inherits="Node2D" version="3.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A region of the 2D navigation map.
</brief_description>
<description>
A region of the navigation map. It tells the [Navigation2DServer] what can be navigated and what cannot, based on its [NavigationPolygon] resource.
By default this node will register to the default [World2D] navigation map. If this node is a child of a [Navigation2D] node it will register to the navigation map of the navigation node.
Two regions can be connected to each other if they share a similar edge. You can set the minimum distance between two vertices required to connect two edges by using [method Navigation2DServer.map_set_edge_connection_margin].
[b]Note:[/b] Overlapping two regions' polygons is not enough for connecting two regions. They must share a similar edge.
The pathfinding cost of entering this region from another region can be controlled with the [member enter_cost] value.
[b]Note[/b]: This value is not added to the path cost when the start position is already inside this region.
The pathfinding cost of traveling distances inside this region can be controlled with the [member travel_cost] multiplier.
</description>
<tutorials>
</tutorials>
@ -16,8 +24,19 @@
</methods>
<members>
<member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true">
Determines if the [NavigationPolygonInstance] is enabled or disabled.
</member>
<member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0">
When pathfinding enters this regions navmesh from another regions navmesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation map layers the [NavigationPolygon] belongs to. On path requests with [method Navigation2DServer.map_get_path] navmeshes without matching layers will be ignored and the navigation map will only proximity merge different navmeshes with matching layers.
</member>
<member name="navpoly" type="NavigationPolygon" setter="set_navigation_polygon" getter="get_navigation_polygon">
The [NavigationPolygon] resource to use.
</member>
<member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0">
When pathfinding moves inside this regions navmesh the traveled distances are multiplied with [code]travel_cost[/code] for determining the shortest path.
</member>
</members>
<constants>

View file

@ -199,8 +199,9 @@
<argument index="1" name="origin" type="Vector3" />
<argument index="2" name="destination" type="Vector3" />
<argument index="3" name="optimize" type="bool" />
<argument index="4" name="navigation_layers" type="int" default="1" />
<description>
Returns the navigation path to reach the destination from the origin.
Returns the navigation path to reach the destination from the origin. [code]navigation_layers[/code] is a bitmask of all region layers that are allowed to be in the path.
</description>
</method>
<method name="map_get_regions" qualifiers="const">
@ -287,6 +288,36 @@
Creates a new region.
</description>
</method>
<method name="region_get_connection_pathway_end" qualifiers="const">
<return type="Vector3" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="connection" type="int" />
<description>
Returns the ending point of a connection door. [code]connection[/code] is an index between 0 and the return value of [method region_get_connections_count].
</description>
</method>
<method name="region_get_connection_pathway_start" qualifiers="const">
<return type="Vector3" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="connection" type="int" />
<description>
Returns the starting point of a connection door. [code]connection[/code] is an index between 0 and the return value of [method region_get_connections_count].
</description>
</method>
<method name="region_get_connections_count" qualifiers="const">
<return type="int" />
<argument index="0" name="region" type="RID" />
<description>
Returns how many connections this [code]region[/code] has with other regions in the map.
</description>
</method>
<method name="region_get_enter_cost" qualifiers="const">
<return type="float" />
<argument index="0" name="region" type="RID" />
<description>
Returns the [code]enter_cost[/code] of this [code]region[/code].
</description>
</method>
<method name="region_get_map" qualifiers="const">
<return type="RID" />
<argument index="0" name="region" type="RID" />
@ -294,6 +325,28 @@
Returns the navigation map [RID] the requested [code]region[/code] is currently assigned to.
</description>
</method>
<method name="region_get_navigation_layers" qualifiers="const">
<return type="int" />
<argument index="0" name="region" type="RID" />
<description>
Returns the region's navigation layers.
</description>
</method>
<method name="region_get_travel_cost" qualifiers="const">
<return type="float" />
<argument index="0" name="region" type="RID" />
<description>
Returns the [code]travel_cost[/code] of this [code]region[/code].
</description>
</method>
<method name="region_set_enter_cost" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="enter_cost" type="float" />
<description>
Sets the [code]enter_cost[/code] for this [code]region[/code].
</description>
</method>
<method name="region_set_map" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
@ -302,6 +355,14 @@
Sets the map for the region.
</description>
</method>
<method name="region_set_navigation_layers" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="navigation_layers" type="int" />
<description>
Set the region's navigation layers. This allows selecting regions from a path request (when using [method NavigationServer.map_get_path]).
</description>
</method>
<method name="region_set_navmesh" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
@ -318,6 +379,14 @@
Sets the global transformation for the region.
</description>
</method>
<method name="region_set_travel_cost" qualifiers="const">
<return type="void" />
<argument index="0" name="region" type="RID" />
<argument index="1" name="travel_cost" type="float" />
<description>
Sets the [code]travel_cost[/code] for this [code]region[/code].
</description>
</method>
<method name="set_active" qualifiers="const">
<return type="void" />
<argument index="0" name="active" type="bool" />
@ -326,6 +395,14 @@
</description>
</method>
</methods>
<signals>
<signal name="map_changed">
<argument index="0" name="map" type="RID" />
<description>
Emitted when a navigation map is updated, when a region moves or is modified.
</description>
</signal>
</signals>
<constants>
</constants>
</class>

View file

@ -638,6 +638,102 @@
<member name="input_devices/pointing/ios/touch_delay" type="float" setter="" getter="" default="0.15">
Default delay for touch events. This only affects iOS devices.
</member>
<member name="layer_names/2d_navigation/layer_1" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 1. If left empty, the layer will display as "Layer 1".
</member>
<member name="layer_names/2d_navigation/layer_10" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 10. If left empty, the layer will display as "Layer 10".
</member>
<member name="layer_names/2d_navigation/layer_11" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 11. If left empty, the layer will display as "Layer 11".
</member>
<member name="layer_names/2d_navigation/layer_12" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 12. If left empty, the layer will display as "Layer 12".
</member>
<member name="layer_names/2d_navigation/layer_13" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 13. If left empty, the layer will display as "Layer 13".
</member>
<member name="layer_names/2d_navigation/layer_14" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 14. If left empty, the layer will display as "Layer 14".
</member>
<member name="layer_names/2d_navigation/layer_15" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 15. If left empty, the layer will display as "Layer 15".
</member>
<member name="layer_names/2d_navigation/layer_16" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 16. If left empty, the layer will display as "Layer 16".
</member>
<member name="layer_names/2d_navigation/layer_17" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 17. If left empty, the layer will display as "Layer 17".
</member>
<member name="layer_names/2d_navigation/layer_18" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 18. If left empty, the layer will display as "Layer 18".
</member>
<member name="layer_names/2d_navigation/layer_19" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 19. If left empty, the layer will display as "Layer 19".
</member>
<member name="layer_names/2d_navigation/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 2. If left empty, the layer will display as "Layer 2".
</member>
<member name="layer_names/2d_navigation/layer_20" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 20. If left empty, the layer will display as "Layer 20".
</member>
<member name="layer_names/2d_navigation/layer_21" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 21. If left empty, the layer will display as "Layer 21".
</member>
<member name="layer_names/2d_navigation/layer_22" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 22. If left empty, the layer will display as "Layer 22".
</member>
<member name="layer_names/2d_navigation/layer_23" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 23. If left empty, the layer will display as "Layer 23".
</member>
<member name="layer_names/2d_navigation/layer_24" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 24. If left empty, the layer will display as "Layer 24".
</member>
<member name="layer_names/2d_navigation/layer_25" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 25. If left empty, the layer will display as "Layer 25".
</member>
<member name="layer_names/2d_navigation/layer_26" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 26. If left empty, the layer will display as "Layer 26".
</member>
<member name="layer_names/2d_navigation/layer_27" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 27. If left empty, the layer will display as "Layer 27".
</member>
<member name="layer_names/2d_navigation/layer_28" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 28. If left empty, the layer will display as "Layer 28".
</member>
<member name="layer_names/2d_navigation/layer_29" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 29. If left empty, the layer will display as "Layer 29".
</member>
<member name="layer_names/2d_navigation/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 3. If left empty, the layer will display as "Layer 3".
</member>
<member name="layer_names/2d_navigation/layer_30" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 30. If left empty, the layer will display as "Layer 30".
</member>
<member name="layer_names/2d_navigation/layer_31" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 31. If left empty, the layer will display as "Layer 31".
</member>
<member name="layer_names/2d_navigation/layer_32" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 32. If left empty, the layer will display as "Layer 32".
</member>
<member name="layer_names/2d_navigation/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 4. If left empty, the layer will display as "Layer 4".
</member>
<member name="layer_names/2d_navigation/layer_5" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 5. If left empty, the layer will display as "Layer 5".
</member>
<member name="layer_names/2d_navigation/layer_6" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 6. If left empty, the layer will display as "Layer 6".
</member>
<member name="layer_names/2d_navigation/layer_7" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 7. If left empty, the layer will display as "Layer 7".
</member>
<member name="layer_names/2d_navigation/layer_8" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 8. If left empty, the layer will display as "Layer 8".
</member>
<member name="layer_names/2d_navigation/layer_9" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D navigation layer 9. If left empty, the layer will display as "Layer 9".
</member>
<member name="layer_names/2d_physics/layer_1" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D physics layer 1.
</member>
@ -794,6 +890,102 @@
<member name="layer_names/2d_render/layer_9" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 2D render layer 9.
</member>
<member name="layer_names/3d_navigation/layer_1" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 1. If left empty, the layer will display as "Layer 1".
</member>
<member name="layer_names/3d_navigation/layer_10" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 10. If left empty, the layer will display as "Layer 10".
</member>
<member name="layer_names/3d_navigation/layer_11" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 11. If left empty, the layer will display as "Layer 11".
</member>
<member name="layer_names/3d_navigation/layer_12" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 12. If left empty, the layer will display as "Layer 12".
</member>
<member name="layer_names/3d_navigation/layer_13" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 13. If left empty, the layer will display as "Layer 13".
</member>
<member name="layer_names/3d_navigation/layer_14" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 14. If left empty, the layer will display as "Layer 14".
</member>
<member name="layer_names/3d_navigation/layer_15" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 15. If left empty, the layer will display as "Layer 15".
</member>
<member name="layer_names/3d_navigation/layer_16" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 16. If left empty, the layer will display as "Layer 16".
</member>
<member name="layer_names/3d_navigation/layer_17" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 17. If left empty, the layer will display as "Layer 17".
</member>
<member name="layer_names/3d_navigation/layer_18" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 18. If left empty, the layer will display as "Layer 18".
</member>
<member name="layer_names/3d_navigation/layer_19" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 19. If left empty, the layer will display as "Layer 19".
</member>
<member name="layer_names/3d_navigation/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 2. If left empty, the layer will display as "Layer 2".
</member>
<member name="layer_names/3d_navigation/layer_20" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 20. If left empty, the layer will display as "Layer 20".
</member>
<member name="layer_names/3d_navigation/layer_21" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 21. If left empty, the layer will display as "Layer 21".
</member>
<member name="layer_names/3d_navigation/layer_22" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 22. If left empty, the layer will display as "Layer 22".
</member>
<member name="layer_names/3d_navigation/layer_23" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 23. If left empty, the layer will display as "Layer 23".
</member>
<member name="layer_names/3d_navigation/layer_24" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 24. If left empty, the layer will display as "Layer 24".
</member>
<member name="layer_names/3d_navigation/layer_25" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 25. If left empty, the layer will display as "Layer 25".
</member>
<member name="layer_names/3d_navigation/layer_26" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 26. If left empty, the layer will display as "Layer 26".
</member>
<member name="layer_names/3d_navigation/layer_27" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 27. If left empty, the layer will display as "Layer 27".
</member>
<member name="layer_names/3d_navigation/layer_28" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 28. If left empty, the layer will display as "Layer 28".
</member>
<member name="layer_names/3d_navigation/layer_29" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 29. If left empty, the layer will display as "Layer 29".
</member>
<member name="layer_names/3d_navigation/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 3. If left empty, the layer will display as "Layer 3".
</member>
<member name="layer_names/3d_navigation/layer_30" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 30. If left empty, the layer will display as "Layer 30".
</member>
<member name="layer_names/3d_navigation/layer_31" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 31. If left empty, the layer will display as "Layer 31".
</member>
<member name="layer_names/3d_navigation/layer_32" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 32. If left empty, the layer will display as "Layer 32".
</member>
<member name="layer_names/3d_navigation/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 4. If left empty, the layer will display as "Layer 4".
</member>
<member name="layer_names/3d_navigation/layer_5" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 5. If left empty, the layer will display as "Layer 5".
</member>
<member name="layer_names/3d_navigation/layer_6" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 6. If left empty, the layer will display as "Layer 6".
</member>
<member name="layer_names/3d_navigation/layer_7" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 7. If left empty, the layer will display as "Layer 7".
</member>
<member name="layer_names/3d_navigation/layer_8" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 8. If left empty, the layer will display as "Layer 8".
</member>
<member name="layer_names/3d_navigation/layer_9" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D navigation layer 9. If left empty, the layer will display as "Layer 9".
</member>
<member name="layer_names/3d_physics/layer_1" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D physics layer 1.
</member>
@ -990,18 +1182,28 @@
The policy to use for unhandled Mono (C#) exceptions. The default "Terminate Application" exits the project as soon as an unhandled exception is thrown. "Log Error" logs an error message to the console instead, and will not interrupt the project execution when an unhandled exception is thrown.
[b]Note:[/b] The unhandled exception policy is always set to "Log Error" in the editor, which also includes C# [code]tool[/code] scripts running within the editor as well as editor plugin code.
</member>
<member name="navigation/2d/default_cell_size" type="int" setter="" getter="" default="1">
<member name="navigation/2d/default_cell_height" type="float" setter="" getter="" default="1.0">
Default cell height for 2D navigation maps. See [method Navigation2DServer.map_set_cell_height].
[b]Note:[/b] Currently not implemented.
</member>
<member name="navigation/2d/default_cell_size" type="float" setter="" getter="" default="1.0">
Default cell size for 2D navigation maps. See [method Navigation2DServer.map_set_cell_size].
</member>
<member name="navigation/2d/default_edge_connection_margin" type="int" setter="" getter="" default="1">
<member name="navigation/2d/default_edge_connection_margin" type="float" setter="" getter="" default="1.0">
Default edge connection margin for 2D navigation maps. See [method Navigation2DServer.map_set_edge_connection_margin].
</member>
<member name="navigation/3d/default_cell_height" type="float" setter="" getter="" default="0.25">
Default cell height for 3D navigation maps. See [method NavigationServer.map_set_cell_height].
</member>
<member name="navigation/3d/default_cell_size" type="float" setter="" getter="" default="0.25">
Default cell size for 3D navigation maps. See [method NavigationServer.map_set_cell_size].
</member>
<member name="navigation/3d/default_edge_connection_margin" type="float" setter="" getter="" default="0.25">
Default edge connection margin for 3D navigation maps. See [method NavigationServer.map_set_edge_connection_margin].
</member>
<member name="navigation/3d/default_map_up" type="Vector3" setter="" getter="" default="Vector3( 0, 1, 0 )">
Default map up vector for 3D navigation maps. See [method NavigationServer.map_set_up].
</member>
<member name="network/limits/debugger_stdout/max_chars_per_second" type="int" setter="" getter="" default="2048">
Maximum amount of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
</member>

View file

@ -215,6 +215,9 @@
</method>
</methods>
<members>
<member name="bake_navigation" type="bool" setter="set_bake_navigation" getter="is_baking_navigation" default="false">
If [code]true[/code], this TileMap bakes a navigation region.
</member>
<member name="cell_clip_uv" type="bool" setter="set_clip_uv" getter="get_clip_uv" default="false">
If [code]true[/code], the cell's UVs will be clipped.
</member>
@ -266,6 +269,9 @@
<member name="mode" type="int" setter="set_mode" getter="get_mode" enum="TileMap.Mode" default="0">
The TileMap orientation mode. See [enum Mode] for possible values.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
The navigation layers the TileMap generates its navigation regions in.
</member>
<member name="occluder_light_mask" type="int" setter="set_occluder_light_mask" getter="get_occluder_light_mask" default="1">
The light mask assigned to all light occluders in the TileMap. The TileSet's light occluders will cast shadows only from Light2D(s) that have the same light mask(s).
</member>

View file

@ -4,7 +4,7 @@
Class that has everything pertaining to a world.
</brief_description>
<description>
Class that has everything pertaining to a world. A physics space, a visual scenario and a sound space. Spatial nodes register their resources into the current world.
Class that has everything pertaining to a world. A physics space, a visual scenario, a navigation map and a sound space. Spatial nodes register their resources into the current world.
</description>
<tutorials>
<link>$DOCS_URL/tutorials/physics/ray-casting.html</link>

View file

@ -4,7 +4,7 @@
Class that has everything pertaining to a 2D world.
</brief_description>
<description>
Class that has everything pertaining to a 2D world. A physics space, a visual scenario and a sound space. 2D nodes register their resources into the current 2D world.
Class that has everything pertaining to a 2D world. A physics space, a visual scenario, a navigation map and a sound space. 2D nodes register their resources into the current 2D world.
</description>
<tutorials>
<link>$DOCS_URL/tutorials/physics/ray-casting.html</link>

View file

@ -971,6 +971,12 @@ void EditorPropertyLayers::setup(LayerType p_layer_type) {
layer_count = 32;
} break;
case LAYER_NAVIGATION_2D: {
basename = "layer_names/2d_navigation";
layer_group_size = 4;
layer_count = 32;
} break;
case LAYER_RENDER_3D: {
basename = "layer_names/3d_render";
layer_group_size = 5;
@ -982,6 +988,12 @@ void EditorPropertyLayers::setup(LayerType p_layer_type) {
layer_group_size = 4;
layer_count = 32;
} break;
case LAYER_NAVIGATION_3D: {
basename = "layer_names/3d_navigation";
layer_group_size = 4;
layer_count = 32;
} break;
}
Vector<String> names;
@ -2734,7 +2746,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ
editor->setup(options);
add_property_editor(p_path, editor);
} else if (p_hint == PROPERTY_HINT_LAYERS_2D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_2D_RENDER || p_hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_3D_RENDER) {
} else if (p_hint == PROPERTY_HINT_LAYERS_2D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_2D_RENDER || p_hint == PROPERTY_HINT_LAYERS_2D_NAVIGATION || p_hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_3D_RENDER || p_hint == PROPERTY_HINT_LAYERS_3D_NAVIGATION) {
EditorPropertyLayers::LayerType lt = EditorPropertyLayers::LAYER_RENDER_2D;
switch (p_hint) {
case PROPERTY_HINT_LAYERS_2D_RENDER:
@ -2743,12 +2755,18 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ
case PROPERTY_HINT_LAYERS_2D_PHYSICS:
lt = EditorPropertyLayers::LAYER_PHYSICS_2D;
break;
case PROPERTY_HINT_LAYERS_2D_NAVIGATION:
lt = EditorPropertyLayers::LAYER_NAVIGATION_2D;
break;
case PROPERTY_HINT_LAYERS_3D_RENDER:
lt = EditorPropertyLayers::LAYER_RENDER_3D;
break;
case PROPERTY_HINT_LAYERS_3D_PHYSICS:
lt = EditorPropertyLayers::LAYER_PHYSICS_3D;
break;
case PROPERTY_HINT_LAYERS_3D_NAVIGATION:
lt = EditorPropertyLayers::LAYER_NAVIGATION_3D;
break;
default: {
} //compiler could be smarter here and realize this can't happen
}

View file

@ -256,8 +256,10 @@ public:
enum LayerType {
LAYER_PHYSICS_2D,
LAYER_RENDER_2D,
LAYER_NAVIGATION_2D,
LAYER_PHYSICS_3D,
LAYER_RENDER_3D,
LAYER_NAVIGATION_3D,
};
private:

View file

@ -419,7 +419,7 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant::
updating = false;
return false;
} else if (hint == PROPERTY_HINT_LAYERS_2D_PHYSICS || hint == PROPERTY_HINT_LAYERS_2D_RENDER || hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || hint == PROPERTY_HINT_LAYERS_3D_RENDER) {
} else if (hint == PROPERTY_HINT_LAYERS_2D_PHYSICS || hint == PROPERTY_HINT_LAYERS_2D_RENDER || hint == PROPERTY_HINT_LAYERS_2D_NAVIGATION || hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || hint == PROPERTY_HINT_LAYERS_3D_RENDER || hint == PROPERTY_HINT_LAYERS_3D_NAVIGATION) {
String basename;
switch (hint) {
case PROPERTY_HINT_LAYERS_2D_RENDER:
@ -428,12 +428,18 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant::
case PROPERTY_HINT_LAYERS_2D_PHYSICS:
basename = "layer_names/2d_physics";
break;
case PROPERTY_HINT_LAYERS_2D_NAVIGATION:
basename = "layer_names/2d_navigation";
break;
case PROPERTY_HINT_LAYERS_3D_RENDER:
basename = "layer_names/3d_render";
break;
case PROPERTY_HINT_LAYERS_3D_PHYSICS:
basename = "layer_names/3d_physics";
break;
case PROPERTY_HINT_LAYERS_3D_NAVIGATION:
basename = "layer_names/3d_navigation";
break;
}
checks20gc->show();
@ -1166,7 +1172,7 @@ void CustomPropertyEditor::_action_pressed(int p_which) {
emit_signal("variant_changed");
} break;
case Variant::INT: {
if (hint == PROPERTY_HINT_LAYERS_2D_PHYSICS || hint == PROPERTY_HINT_LAYERS_2D_RENDER || hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || hint == PROPERTY_HINT_LAYERS_3D_RENDER) {
if (hint == PROPERTY_HINT_LAYERS_2D_PHYSICS || hint == PROPERTY_HINT_LAYERS_2D_RENDER || hint == PROPERTY_HINT_LAYERS_2D_NAVIGATION || hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || hint == PROPERTY_HINT_LAYERS_3D_RENDER || hint == PROPERTY_HINT_LAYERS_3D_NAVIGATION) {
uint32_t f = v;
if (checks20[p_which]->is_pressed()) {
f |= (1 << p_which);

View file

@ -61,8 +61,10 @@ typedef enum {
GODOT_PROPERTY_HINT_FLAGS, ///< hint_text= "flag1,flag2,etc" (as bit flags)
GODOT_PROPERTY_HINT_LAYERS_2D_RENDER,
GODOT_PROPERTY_HINT_LAYERS_2D_PHYSICS,
GODOT_PROPERTY_HINT_LAYERS_2D_NAVIGATION,
GODOT_PROPERTY_HINT_LAYERS_3D_RENDER,
GODOT_PROPERTY_HINT_LAYERS_3D_PHYSICS,
GODOT_PROPERTY_HINT_LAYERS_3D_NAVIGATION,
GODOT_PROPERTY_HINT_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,"
GODOT_PROPERTY_HINT_DIR, ///< a directory path must be passed
GODOT_PROPERTY_HINT_GLOBAL_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,"

View file

@ -4304,6 +4304,16 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
break;
}
if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_NAVIGATION") {
_ADVANCE_AND_CONSUME_NEWLINES;
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
_set_error("Expected \")\" in the layers 2D navigation hint.");
return;
}
current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS;
break;
}
if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_RENDER") {
_ADVANCE_AND_CONSUME_NEWLINES;
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
@ -4324,6 +4334,16 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
break;
}
if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_NAVIGATION") {
_ADVANCE_AND_CONSUME_NEWLINES;
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
_set_error("Expected \")\" in the layers 3D navigation hint.");
return;
}
current_export.hint = PROPERTY_HINT_LAYERS_3D_NAVIGATION;
break;
}
if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) {
//enumeration
current_export.hint = PROPERTY_HINT_ENUM;

View file

@ -160,6 +160,9 @@
</method>
</methods>
<members>
<member name="bake_navigation" type="bool" setter="set_bake_navigation" getter="is_baking_navigation" default="false">
If [code]true[/code], this GridMap uses cell navmesh resources to create navigation regions.
</member>
<member name="cell_center_x" type="bool" setter="set_center_x" getter="get_center_x" default="true">
If [code]true[/code], grid items are centered on the X axis.
</member>
@ -190,6 +193,9 @@
<member name="mesh_library" type="MeshLibrary" setter="set_mesh_library" getter="get_mesh_library">
The assigned [MeshLibrary].
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
The navigation layers the GridMap generates its navigation regions in.
</member>
<member name="physics_material" type="PhysicsMaterial" setter="set_physics_material" getter="get_physics_material">
Overrides the default friction and bounce physics properties for the whole [GridMap].
</member>

View file

@ -213,6 +213,24 @@ Array GridMap::get_collision_shapes() const {
return shapes;
}
void GridMap::set_bake_navigation(bool p_bake_navigation) {
bake_navigation = p_bake_navigation;
_recreate_octant_data();
}
bool GridMap::is_baking_navigation() {
return bake_navigation;
}
void GridMap::set_navigation_layers(uint32_t p_navigation_layers) {
navigation_layers = p_navigation_layers;
_recreate_octant_data();
}
uint32_t GridMap::get_navigation_layers() {
return navigation_layers;
}
void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) {
if (!mesh_library.is_null()) {
mesh_library->unregister_owner(this);
@ -421,6 +439,18 @@ void GridMap::_octant_transform(const OctantKey &p_key) {
VS::get_singleton()->instance_set_transform(g.collision_debug_instance, get_global_transform());
}
// update transform for NavigationServer regions and navigation debugmesh instances
if (bake_navigation) {
for (Map<IndexKey, Octant::NavMesh>::Element *E = g.navmesh_ids.front(); E; E = E->next()) {
if (E->get().region.is_valid()) {
NavigationServer::get_singleton()->region_set_transform(E->get().region, get_global_transform() * E->get().xform);
}
if (E->get().navmesh_debug_instance.is_valid()) {
VS::get_singleton()->instance_set_transform(E->get().navmesh_debug_instance, get_global_transform() * E->get().xform);
}
}
}
for (int i = 0; i < g.multimesh_instances.size(); i++) {
VS::get_singleton()->instance_set_transform(g.multimesh_instances[i].instance, get_global_transform());
}
@ -444,6 +474,9 @@ bool GridMap::_octant_update(const OctantKey &p_key) {
//erase navigation
for (Map<IndexKey, Octant::NavMesh>::Element *E = g.navmesh_ids.front(); E; E = E->next()) {
NavigationServer::get_singleton()->free(E->get().region);
if (E->get().navmesh_debug_instance.is_valid()) {
VS::get_singleton()->free(E->get().navmesh_debug_instance);
}
}
g.navmesh_ids.clear();
@ -523,12 +556,32 @@ bool GridMap::_octant_update(const OctantKey &p_key) {
Octant::NavMesh nm;
nm.xform = xform * mesh_library->get_item_navmesh_transform(c.item);
if (navigation) {
if (bake_navigation) {
RID region = NavigationServer::get_singleton()->region_create();
NavigationServer::get_singleton()->region_set_navigation_layers(region, navigation_layers);
NavigationServer::get_singleton()->region_set_navmesh(region, navmesh);
NavigationServer::get_singleton()->region_set_transform(region, navigation->get_global_transform() * nm.xform);
NavigationServer::get_singleton()->region_set_map(region, navigation->get_rid());
NavigationServer::get_singleton()->region_set_transform(region, get_global_transform() * nm.xform);
if (navigation) {
NavigationServer::get_singleton()->region_set_map(region, navigation->get_rid());
} else {
NavigationServer::get_singleton()->region_set_map(region, get_world()->get_navigation_map());
}
nm.region = region;
// add navigation debugmesh visual instances if debug is enabled
SceneTree *st = SceneTree::get_singleton();
if (st && st->is_debugging_navigation_hint()) {
if (!nm.navmesh_debug_instance.is_valid()) {
RID navmesh_debug_rid = navmesh->get_debug_mesh()->get_rid();
nm.navmesh_debug_instance = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(nm.navmesh_debug_instance, navmesh_debug_rid);
VS::get_singleton()->mesh_surface_set_material(navmesh_debug_rid, 0, st->get_debug_navigation_material()->get_rid());
}
if (is_inside_tree()) {
VS::get_singleton()->instance_set_scenario(nm.navmesh_debug_instance, get_world()->get_scenario());
VS::get_singleton()->instance_set_transform(nm.navmesh_debug_instance, get_global_transform() * nm.xform);
}
}
}
g.navmesh_ids[E->get()] = nm;
}
@ -614,16 +667,21 @@ void GridMap::_octant_enter_world(const OctantKey &p_key) {
VS::get_singleton()->instance_set_transform(g.multimesh_instances[i].instance, get_global_transform());
}
if (navigation && mesh_library.is_valid()) {
for (Map<IndexKey, Octant::NavMesh>::Element *F = g.navmesh_ids.front(); F; F = F->next()) {
if (cell_map.has(F->key()) && F->get().region.is_valid() == false) {
Ref<NavigationMesh> nm = mesh_library->get_item_navmesh(cell_map[F->key()].item);
if (bake_navigation && mesh_library.is_valid()) {
for (Map<IndexKey, Octant::NavMesh>::Element *E = g.navmesh_ids.front(); E; E = E->next()) {
if (cell_map.has(E->key()) && E->get().region.is_valid() == false) {
Ref<NavigationMesh> nm = mesh_library->get_item_navmesh(cell_map[E->key()].item);
if (nm.is_valid()) {
RID region = NavigationServer::get_singleton()->region_create();
NavigationServer::get_singleton()->region_set_navigation_layers(region, navigation_layers);
NavigationServer::get_singleton()->region_set_navmesh(region, nm);
NavigationServer::get_singleton()->region_set_transform(region, navigation->get_global_transform() * F->get().xform);
NavigationServer::get_singleton()->region_set_map(region, navigation->get_rid());
F->get().region = region;
NavigationServer::get_singleton()->region_set_transform(region, get_global_transform() * E->get().xform);
if (navigation) {
NavigationServer::get_singleton()->region_set_map(region, navigation->get_rid());
} else {
NavigationServer::get_singleton()->region_set_map(region, get_world()->get_navigation_map());
}
E->get().region = region;
}
}
}
@ -644,12 +702,14 @@ void GridMap::_octant_exit_world(const OctantKey &p_key) {
VS::get_singleton()->instance_set_scenario(g.multimesh_instances[i].instance, RID());
}
if (navigation) {
for (Map<IndexKey, Octant::NavMesh>::Element *F = g.navmesh_ids.front(); F; F = F->next()) {
if (F->get().region.is_valid()) {
NavigationServer::get_singleton()->free(F->get().region);
F->get().region = RID();
}
for (Map<IndexKey, Octant::NavMesh>::Element *E = g.navmesh_ids.front(); E; E = E->next()) {
if (E->get().region.is_valid()) {
NavigationServer::get_singleton()->free(E->get().region);
E->get().region = RID();
}
if (E->get().navmesh_debug_instance.is_valid()) {
VS::get_singleton()->free(E->get().navmesh_debug_instance);
E->get().navmesh_debug_instance = RID();
}
}
}
@ -675,7 +735,12 @@ void GridMap::_octant_clean_up(const OctantKey &p_key) {
// Erase navigation
for (Map<IndexKey, Octant::NavMesh>::Element *E = g.navmesh_ids.front(); E; E = E->next()) {
NavigationServer::get_singleton()->free(E->get().region);
if (E->get().region.is_valid()) {
NavigationServer::get_singleton()->free(E->get().region);
}
if (E->get().navmesh_debug_instance.is_valid()) {
VS::get_singleton()->free(E->get().navmesh_debug_instance);
}
}
g.navmesh_ids.clear();
@ -855,6 +920,12 @@ void GridMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physics_material", "material"), &GridMap::set_physics_material);
ClassDB::bind_method(D_METHOD("get_physics_material"), &GridMap::get_physics_material);
ClassDB::bind_method(D_METHOD("set_bake_navigation", "bake_navigation"), &GridMap::set_bake_navigation);
ClassDB::bind_method(D_METHOD("is_baking_navigation"), &GridMap::is_baking_navigation);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &GridMap::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &GridMap::get_navigation_layers);
ClassDB::bind_method(D_METHOD("set_mesh_library", "mesh_library"), &GridMap::set_mesh_library);
ClassDB::bind_method(D_METHOD("get_mesh_library"), &GridMap::get_mesh_library);
@ -914,6 +985,8 @@ void GridMap::_bind_methods() {
ADD_GROUP("Collision", "collision_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_layer", "get_collision_layer");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bake_navigation"), "set_bake_navigation", "is_baking_navigation");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
BIND_CONSTANT(INVALID_CELL_ITEM);

View file

@ -90,6 +90,7 @@ class GridMap : public Spatial {
struct NavMesh {
RID region;
Transform xform;
RID navmesh_debug_instance;
};
struct MultimeshInstance {
@ -135,6 +136,8 @@ class GridMap : public Spatial {
uint32_t collision_layer;
uint32_t collision_mask;
Ref<PhysicsMaterial> physics_material;
bool bake_navigation = false;
uint32_t navigation_layers = 1;
Transform last_transform;
@ -227,6 +230,12 @@ public:
Array get_collision_shapes() const;
void set_bake_navigation(bool p_bake_navigation);
bool is_baking_navigation();
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers();
void set_mesh_library(const Ref<MeshLibrary> &p_mesh_library);
Ref<MeshLibrary> get_mesh_library() const;

View file

@ -36,10 +36,6 @@
#include "navigation_mesh_generator.h"
#endif
/**
@author AndreaCatania
*/
/// Creates a struct for each function and a function that once called creates
/// an instance of that struct with the submitted parameters.
/// Then, that struct is stored in an array; the `sync` function consume that array.
@ -113,17 +109,14 @@
} \
void GodotNavigationServer::MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3)
GodotNavigationServer::GodotNavigationServer() :
NavigationServer(),
active(true) {
}
GodotNavigationServer::GodotNavigationServer() {}
GodotNavigationServer::~GodotNavigationServer() {
flush_queries();
}
void GodotNavigationServer::add_command(SetCommand *command) const {
auto mut_this = const_cast<GodotNavigationServer *>(this);
GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
{
MutexLock lock(commands_mutex);
mut_this->commands.push_back(command);
@ -131,7 +124,7 @@ void GodotNavigationServer::add_command(SetCommand *command) const {
}
RID GodotNavigationServer::map_create() const {
auto mut_this = const_cast<GodotNavigationServer *>(this);
GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
MutexLock lock(mut_this->operations_mutex);
NavMap *space = memnew(NavMap);
RID rid = map_owner.make_rid(space);
@ -140,27 +133,31 @@ RID GodotNavigationServer::map_create() const {
}
COMMAND_2(map_set_active, RID, p_map, bool, p_active) {
NavMap *map = map_owner.get(p_map);
NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND(map == nullptr);
if (p_active) {
if (!map_is_active(p_map)) {
active_maps.push_back(map);
active_maps_update_id.push_back(map->get_map_update_id());
}
} else {
active_maps.erase(map);
int map_index = active_maps.find(map);
ERR_FAIL_COND(map_index < 0);
active_maps.remove(map_index);
active_maps_update_id.remove(map_index);
}
}
bool GodotNavigationServer::map_is_active(RID p_map) const {
NavMap *map = map_owner.get(p_map);
NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND_V(map == nullptr, false);
return active_maps.find(map) >= 0;
}
COMMAND_2(map_set_up, RID, p_map, Vector3, p_up) {
NavMap *map = map_owner.get(p_map);
NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND(map == nullptr);
map->set_up(p_up);
@ -174,7 +171,7 @@ Vector3 GodotNavigationServer::map_get_up(RID p_map) const {
}
COMMAND_2(map_set_cell_size, RID, p_map, real_t, p_cell_size) {
NavMap *map = map_owner.get(p_map);
NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND(map == nullptr);
map->set_cell_size(p_cell_size);
@ -188,7 +185,7 @@ real_t GodotNavigationServer::map_get_cell_size(RID p_map) const {
}
COMMAND_2(map_set_cell_height, RID, p_map, real_t, p_cell_height) {
NavMap *map = map_owner.get(p_map);
NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND(map == nullptr);
map->set_cell_height(p_cell_height);
@ -202,7 +199,7 @@ real_t GodotNavigationServer::map_get_cell_height(RID p_map) const {
}
COMMAND_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin) {
NavMap *map = map_owner.get(p_map);
NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND(map == nullptr);
map->set_edge_connection_margin(p_connection_margin);
@ -215,11 +212,11 @@ real_t GodotNavigationServer::map_get_edge_connection_margin(RID p_map) const {
return map->get_edge_connection_margin();
}
Vector<Vector3> GodotNavigationServer::map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize) const {
NavMap *map = map_owner.get(p_map);
Vector<Vector3> GodotNavigationServer::map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_layers) const {
const NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND_V(map == nullptr, Vector<Vector3>());
return map->get_path(p_origin, p_destination, p_optimize);
return map->get_path(p_origin, p_destination, p_optimize, p_layers);
}
Vector3 GodotNavigationServer::map_get_closest_point_to_segment(RID p_map, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const {
@ -289,7 +286,7 @@ RID GodotNavigationServer::agent_get_map(RID p_agent) const {
}
RID GodotNavigationServer::region_create() const {
auto mut_this = const_cast<GodotNavigationServer *>(this);
GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
MutexLock lock(mut_this->operations_mutex);
NavRegion *reg = memnew(NavRegion);
RID rid = region_owner.make_rid(reg);
@ -298,7 +295,7 @@ RID GodotNavigationServer::region_create() const {
}
COMMAND_2(region_set_map, RID, p_region, RID, p_map) {
NavRegion *region = region_owner.get(p_region);
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND(region == nullptr);
if (region->get_map() != nullptr) {
@ -311,7 +308,7 @@ COMMAND_2(region_set_map, RID, p_region, RID, p_map) {
}
if (p_map.is_valid()) {
NavMap *map = map_owner.get(p_map);
NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND(map == nullptr);
map->add_region(region);
@ -320,14 +317,58 @@ COMMAND_2(region_set_map, RID, p_region, RID, p_map) {
}
COMMAND_2(region_set_transform, RID, p_region, Transform, p_transform) {
NavRegion *region = region_owner.get(p_region);
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND(region == nullptr);
region->set_transform(p_transform);
}
COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost) {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND(region == nullptr);
ERR_FAIL_COND(p_enter_cost < 0.0);
region->set_enter_cost(p_enter_cost);
}
real_t GodotNavigationServer::region_get_enter_cost(RID p_region) const {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND_V(region == nullptr, 0);
return region->get_enter_cost();
}
COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost) {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND(region == nullptr);
ERR_FAIL_COND(p_travel_cost < 0.0);
region->set_travel_cost(p_travel_cost);
}
real_t GodotNavigationServer::region_get_travel_cost(RID p_region) const {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND_V(region == nullptr, 0);
return region->get_travel_cost();
}
COMMAND_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers) {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND(region == nullptr);
region->set_navigation_layers(p_navigation_layers);
}
uint32_t GodotNavigationServer::region_get_navigation_layers(RID p_region) const {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND_V(region == nullptr, 0);
return region->get_navigation_layers();
}
COMMAND_2(region_set_navmesh, RID, p_region, Ref<NavigationMesh>, p_nav_mesh) {
NavRegion *region = region_owner.get(p_region);
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND(region == nullptr);
region->set_mesh(p_nav_mesh);
@ -343,8 +384,29 @@ void GodotNavigationServer::region_bake_navmesh(Ref<NavigationMesh> r_mesh, Node
#endif
}
int GodotNavigationServer::region_get_connections_count(RID p_region) const {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND_V(!region, 0);
return region->get_connections_count();
}
Vector3 GodotNavigationServer::region_get_connection_pathway_start(RID p_region, int p_connection_id) const {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND_V(!region, Vector3());
return region->get_connection_pathway_start(p_connection_id);
}
Vector3 GodotNavigationServer::region_get_connection_pathway_end(RID p_region, int p_connection_id) const {
NavRegion *region = region_owner.getornull(p_region);
ERR_FAIL_COND_V(!region, Vector3());
return region->get_connection_pathway_end(p_connection_id);
}
RID GodotNavigationServer::agent_create() const {
auto mut_this = const_cast<GodotNavigationServer *>(this);
GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
MutexLock lock(mut_this->operations_mutex);
RvoAgent *agent = memnew(RvoAgent());
RID rid = agent_owner.make_rid(agent);
@ -353,7 +415,7 @@ RID GodotNavigationServer::agent_create() const {
}
COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
if (agent->get_map()) {
@ -367,7 +429,7 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
agent->set_map(nullptr);
if (p_map.is_valid()) {
NavMap *map = map_owner.get(p_map);
NavMap *map = map_owner.getornull(p_map);
ERR_FAIL_COND(map == nullptr);
agent->set_map(map);
@ -380,77 +442,77 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
}
COMMAND_2(agent_set_neighbor_dist, RID, p_agent, real_t, p_dist) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->neighborDist_ = p_dist;
}
COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->maxNeighbors_ = p_count;
}
COMMAND_2(agent_set_time_horizon, RID, p_agent, real_t, p_time) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->timeHorizon_ = p_time;
}
COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->radius_ = p_radius;
}
COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->maxSpeed_ = p_max_speed;
}
COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->velocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z);
}
COMMAND_2(agent_set_target_velocity, RID, p_agent, Vector3, p_velocity) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->prefVelocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z);
}
COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->position_ = RVO::Vector3(p_position.x, p_position.y, p_position.z);
}
COMMAND_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->ignore_y_ = p_ignore;
}
bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND_V(agent == nullptr, false);
return agent->is_map_changed();
}
COMMAND_4(agent_set_callback, RID, p_agent, Object *, p_receiver, StringName, p_method, Variant, p_udata) {
RvoAgent *agent = agent_owner.get(p_agent);
RvoAgent *agent = agent_owner.getornull(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->set_callback(p_receiver == nullptr ? 0 : p_receiver->get_instance_id(), p_method, p_udata);
@ -466,7 +528,7 @@ COMMAND_4(agent_set_callback, RID, p_agent, Object *, p_receiver, StringName, p_
COMMAND_1(free, RID, p_object) {
if (map_owner.owns(p_object)) {
NavMap *map = map_owner.get(p_object);
NavMap *map = map_owner.getornull(p_object);
// Removes any assigned region
std::vector<NavRegion *> regions = map->get_regions();
@ -482,12 +544,14 @@ COMMAND_1(free, RID, p_object) {
agents[i]->set_map(nullptr);
}
active_maps.erase(map);
int map_index = active_maps.find(map);
active_maps.remove(map_index);
active_maps_update_id.remove(map_index);
map_owner.free(p_object);
memdelete(map);
} else if (region_owner.owns(p_object)) {
NavRegion *region = region_owner.get(p_object);
NavRegion *region = region_owner.getornull(p_object);
// Removes this region from the map if assigned
if (region->get_map() != nullptr) {
@ -499,7 +563,7 @@ COMMAND_1(free, RID, p_object) {
memdelete(region);
} else if (agent_owner.owns(p_object)) {
RvoAgent *agent = agent_owner.get(p_object);
RvoAgent *agent = agent_owner.getornull(p_object);
// Removes this agent from the map if assigned
if (agent->get_map() != nullptr) {
@ -516,7 +580,7 @@ COMMAND_1(free, RID, p_object) {
}
void GodotNavigationServer::set_active(bool p_active) const {
auto mut_this = const_cast<GodotNavigationServer *>(this);
GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
MutexLock lock(mut_this->operations_mutex);
mut_this->active = p_active;
}
@ -543,10 +607,18 @@ void GodotNavigationServer::process(real_t p_delta_time) {
// In c++ we can't be sure that this is performed in the main thread
// even with mutable functions.
MutexLock lock(operations_mutex);
for (int i(0); i < active_maps.size(); i++) {
for (uint32_t i(0); i < active_maps.size(); i++) {
active_maps[i]->sync();
active_maps[i]->step(p_delta_time);
active_maps[i]->dispatch_callbacks();
// Emit a signal if a map changed.
const uint32_t new_map_update_id = active_maps[i]->get_map_update_id();
if (new_map_update_id != active_maps_update_id[i]) {
emit_signal("map_changed", active_maps[i]->get_self());
active_maps_update_id[i] = new_map_update_id;
}
}
}

View file

@ -31,16 +31,13 @@
#ifndef GODOT_NAVIGATION_SERVER_H
#define GODOT_NAVIGATION_SERVER_H
#include "core/rid.h"
#include "servers/navigation_server.h"
#include "nav_map.h"
#include "nav_region.h"
#include "rvo_agent.h"
/**
@author AndreaCatania
*/
/// The commands are functions executed during the `sync` phase.
#define MERGE_INTERNAL(A, B) A##B
@ -76,8 +73,9 @@ class GodotNavigationServer : public NavigationServer {
mutable RID_Owner<NavRegion> region_owner;
mutable RID_Owner<RvoAgent> agent_owner;
bool active;
Vector<NavMap *> active_maps;
bool active = true;
LocalVector<NavMap *> active_maps;
LocalVector<uint32_t> active_maps_update_id;
public:
GodotNavigationServer();
@ -101,7 +99,7 @@ public:
COMMAND_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin);
virtual real_t map_get_edge_connection_margin(RID p_map) const;
virtual Vector<Vector3> map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize) const;
virtual Vector<Vector3> map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_layers = 1) const;
virtual Vector3 map_get_closest_point_to_segment(RID p_map, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision = false) const;
virtual Vector3 map_get_closest_point(RID p_map, const Vector3 &p_point) const;
@ -112,12 +110,24 @@ public:
virtual Array map_get_agents(RID p_map) const;
virtual RID region_create() const;
COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost);
virtual real_t region_get_enter_cost(RID p_region) const;
COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost);
virtual real_t region_get_travel_cost(RID p_region) const;
COMMAND_2(region_set_map, RID, p_region, RID, p_map);
virtual RID region_get_map(RID p_region) const;
COMMAND_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers);
virtual uint32_t region_get_navigation_layers(RID p_region) const;
COMMAND_2(region_set_transform, RID, p_region, Transform, p_transform);
COMMAND_2(region_set_navmesh, RID, p_region, Ref<NavigationMesh>, p_nav_mesh);
virtual void region_bake_navmesh(Ref<NavigationMesh> r_mesh, Node *p_node) const;
virtual int region_get_connections_count(RID p_region) const;
virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const;
virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const;
virtual RID agent_create() const;
COMMAND_2(agent_set_map, RID, p_agent, RID, p_map);
virtual RID agent_get_map(RID p_agent) const;
@ -136,6 +146,7 @@ public:
COMMAND_1(free, RID, p_object);
virtual void set_active(bool p_active) const;
void flush_queries();
virtual void process(real_t p_delta_time);
};

View file

@ -32,28 +32,10 @@
#include "nav_region.h"
#include "rvo_agent.h"
#include <algorithm>
/**
@author AndreaCatania
*/
#define USE_ENTRY_POINT
NavMap::NavMap() :
up(0, 1, 0),
cell_size(0.3),
cell_height(0.2),
edge_connection_margin(5.0),
regenerate_polygons(true),
regenerate_links(true),
agents_dirty(false),
deltatime(0.0),
map_update_id(0) {}
NavMap::~NavMap() {
step_work_pool.finish();
}
#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a)))
void NavMap::set_up(Vector3 p_up) {
up = p_up;
@ -88,9 +70,10 @@ gd::PointKey NavMap::get_point_key(const Vector3 &p_pos) const {
return p;
}
Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p_optimize) const {
const gd::Polygon *begin_poly = NULL;
const gd::Polygon *end_poly = NULL;
Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers) const {
// Find the start poly and the end poly on this map.
const gd::Polygon *begin_poly = nullptr;
const gd::Polygon *end_poly = nullptr;
Vector3 begin_point;
Vector3 end_point;
float begin_d = 1e20;
@ -100,30 +83,35 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
for (size_t i(0); i < polygons.size(); i++) {
const gd::Polygon &p = polygons[i];
// Only consider the polygon if it in a region with compatible layers.
if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) {
continue;
}
// For each face check the distance between the origin/destination
for (size_t point_id = 2; point_id < p.points.size(); point_id++) {
const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
Vector3 spoint = face.get_closest_point_to(p_origin);
float dpoint = spoint.distance_to(p_origin);
if (dpoint < begin_d) {
begin_d = dpoint;
Vector3 point = face.get_closest_point_to(p_origin);
float distance_to_point = point.distance_to(p_origin);
if (distance_to_point < begin_d) {
begin_d = distance_to_point;
begin_poly = &p;
begin_point = spoint;
begin_point = point;
}
spoint = face.get_closest_point_to(p_destination);
dpoint = spoint.distance_to(p_destination);
if (dpoint < end_d) {
end_d = dpoint;
point = face.get_closest_point_to(p_destination);
distance_to_point = point.distance_to(p_destination);
if (distance_to_point < end_d) {
end_d = distance_to_point;
end_poly = &p;
end_point = spoint;
end_point = point;
}
}
}
// Check for trivial cases
if (!begin_poly || !end_poly) {
// No path
return Vector<Vector3>();
}
@ -135,92 +123,102 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
return path;
}
// List of all reachable navigation polys.
std::vector<gd::NavigationPoly> navigation_polys;
navigation_polys.reserve(polygons.size() * 0.75);
// The elements indices in the `navigation_polys`.
int least_cost_id(-1);
List<uint32_t> open_list;
// Add the start polygon to the reachable navigation polygons.
gd::NavigationPoly begin_navigation_poly = gd::NavigationPoly(begin_poly);
begin_navigation_poly.self_id = 0;
begin_navigation_poly.entry = begin_point;
begin_navigation_poly.back_navigation_edge_pathway_start = begin_point;
begin_navigation_poly.back_navigation_edge_pathway_end = begin_point;
navigation_polys.push_back(begin_navigation_poly);
// List of polygon IDs to visit.
List<uint32_t> to_visit;
to_visit.push_back(0);
// This is an implementation of the A* algorithm.
int least_cost_id = 0;
bool found_route = false;
navigation_polys.push_back(gd::NavigationPoly(begin_poly));
{
least_cost_id = 0;
gd::NavigationPoly *least_cost_poly = &navigation_polys[least_cost_id];
least_cost_poly->self_id = least_cost_id;
least_cost_poly->entry = begin_point;
}
open_list.push_back(0);
const gd::Polygon *reachable_end = NULL;
const gd::Polygon *reachable_end = nullptr;
float reachable_d = 1e30;
bool is_reachable = true;
while (found_route == false) {
{
// Takes the current least_cost_poly neighbors and compute the traveled_distance of each
for (size_t i = 0; i < navigation_polys[least_cost_id].poly->edges.size(); i++) {
gd::NavigationPoly *least_cost_poly = &navigation_polys[least_cost_id];
gd::NavigationPoly *prev_least_cost_poly = nullptr;
const gd::Edge &edge = least_cost_poly->poly->edges[i];
if (!edge.other_polygon)
while (true) {
// Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance.
for (size_t i = 0; i < navigation_polys[least_cost_id].poly->edges.size(); i++) {
gd::NavigationPoly *least_cost_poly = &navigation_polys[least_cost_id];
const gd::Edge &edge = least_cost_poly->poly->edges[i];
// Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon.
for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) {
const gd::Edge::Connection &connection = edge.connections[connection_index];
// Only consider the connection to another polygon if this polygon is in a region with compatible layers.
if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) {
continue;
}
#ifdef USE_ENTRY_POINT
Vector3 edge_line[2] = {
least_cost_poly->poly->points[i].pos,
least_cost_poly->poly->points[(i + 1) % least_cost_poly->poly->points.size()].pos
};
float region_enter_cost = 0.0;
float region_travel_cost = least_cost_poly->poly->owner->get_travel_cost();
const Vector3 new_entry = Geometry::get_closest_point_to_segment(least_cost_poly->entry, edge_line);
const float new_distance = least_cost_poly->entry.distance_to(new_entry) + least_cost_poly->traveled_distance;
#else
const float new_distance = least_cost_poly->poly->center.distance_to(edge.other_polygon->center) + least_cost_poly->traveled_distance;
#endif
if (prev_least_cost_poly != nullptr && !(prev_least_cost_poly->poly->owner->get_self() == least_cost_poly->poly->owner->get_self())) {
region_enter_cost = least_cost_poly->poly->owner->get_enter_cost();
}
prev_least_cost_poly = least_cost_poly;
auto it = std::find(
Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end };
const Vector3 new_entry = Geometry::get_closest_point_to_segment(least_cost_poly->entry, pathway);
const float new_distance = (least_cost_poly->entry.distance_to(new_entry) * region_travel_cost) + region_enter_cost + least_cost_poly->traveled_distance;
const std::vector<gd::NavigationPoly>::iterator it = std::find(
navigation_polys.begin(),
navigation_polys.end(),
gd::NavigationPoly(edge.other_polygon));
gd::NavigationPoly(connection.polygon));
if (it != navigation_polys.end()) {
// Oh this was visited already, can we win the cost?
if (it->traveled_distance > new_distance) {
it->prev_navigation_poly_id = least_cost_id;
it->back_navigation_edge = edge.other_edge;
// Polygon already visited, check if we can reduce the travel cost.
if (new_distance < it->traveled_distance) {
it->back_navigation_poly_id = least_cost_id;
it->back_navigation_edge = connection.edge;
it->back_navigation_edge_pathway_start = connection.pathway_start;
it->back_navigation_edge_pathway_end = connection.pathway_end;
it->traveled_distance = new_distance;
#ifdef USE_ENTRY_POINT
it->entry = new_entry;
#endif
}
} else {
// Add to open neighbours
// Add the neighbour polygon to the reachable ones.
gd::NavigationPoly new_navigation_poly = gd::NavigationPoly(connection.polygon);
new_navigation_poly.self_id = navigation_polys.size();
new_navigation_poly.back_navigation_poly_id = least_cost_id;
new_navigation_poly.back_navigation_edge = connection.edge;
new_navigation_poly.back_navigation_edge_pathway_start = connection.pathway_start;
new_navigation_poly.back_navigation_edge_pathway_end = connection.pathway_end;
new_navigation_poly.traveled_distance = new_distance;
new_navigation_poly.entry = new_entry;
navigation_polys.push_back(new_navigation_poly);
navigation_polys.push_back(gd::NavigationPoly(edge.other_polygon));
gd::NavigationPoly *np = &navigation_polys[navigation_polys.size() - 1];
np->self_id = navigation_polys.size() - 1;
np->prev_navigation_poly_id = least_cost_id;
np->back_navigation_edge = edge.other_edge;
np->traveled_distance = new_distance;
#ifdef USE_ENTRY_POINT
np->entry = new_entry;
#endif
open_list.push_back(navigation_polys.size() - 1);
// Add the neighbour polygon to the polygons to visit.
to_visit.push_back(navigation_polys.size() - 1);
}
}
}
// Removes the least cost polygon from the open list so we can advance.
open_list.erase(least_cost_id);
// Removes the least cost polygon from the list of polygons to visit so we can advance.
to_visit.erase(least_cost_id);
if (open_list.size() == 0) {
// When the open list is empty at this point the End Polygon is not reachable
// so use the further reachable polygon
// When the list of polygons to visit is empty at this point it means the End Polygon is not reachable
if (to_visit.size() == 0) {
// Thus use the further reachable polygon
ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons");
is_reachable = false;
if (reachable_end == NULL) {
if (reachable_end == nullptr) {
// The path is not found and there is not a way out.
break;
}
@ -242,27 +240,22 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
gd::NavigationPoly np = navigation_polys[0];
navigation_polys.clear();
navigation_polys.push_back(np);
open_list.clear();
open_list.push_back(0);
to_visit.clear();
to_visit.push_back(0);
least_cost_id = 0;
reachable_end = NULL;
reachable_end = nullptr;
continue;
}
// Now take the new least_cost_poly from the open list.
// Find the polygon with the minimum cost from the list of polygons to visit.
least_cost_id = -1;
float least_cost = 1e30;
for (auto element = open_list.front(); element != NULL; element = element->next()) {
for (List<uint32_t>::Element *element = to_visit.front(); element != nullptr; element = element->next()) {
gd::NavigationPoly *np = &navigation_polys[element->get()];
float cost = np->traveled_distance;
#ifdef USE_ENTRY_POINT
cost += np->entry.distance_to(end_point);
#else
cost += np->poly->center.distance_to(end_point);
#endif
cost += (np->entry.distance_to(end_point) * np->poly->owner->get_travel_cost());
if (cost < least_cost) {
least_cost_id = np->self_id;
least_cost = cost;
@ -273,7 +266,7 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
// Stores the further reachable end polygon, in case our goal is not reachable.
if (is_reachable) {
float d = navigation_polys[least_cost_id].entry.distance_to(p_destination);
float d = navigation_polys[least_cost_id].entry.distance_to(p_destination) * navigation_polys[least_cost_id].poly->owner->get_travel_cost();
if (reachable_d > d) {
reachable_d = d;
reachable_end = navigation_polys[least_cost_id].poly;
@ -282,120 +275,112 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
// Check if we reached the end
if (navigation_polys[least_cost_id].poly == end_poly) {
// Yep, done!!
found_route = true;
break;
}
}
if (found_route) {
Vector<Vector3> path;
if (p_optimize) {
// String pulling
// If we did not find a route, return an empty path.
if (!found_route) {
return Vector<Vector3>();
}
gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id];
Vector3 apex_point = end_point;
Vector3 portal_left = apex_point;
Vector3 portal_right = apex_point;
gd::NavigationPoly *left_poly = apex_poly;
gd::NavigationPoly *right_poly = apex_poly;
gd::NavigationPoly *p = apex_poly;
Vector<Vector3> path;
// Optimize the path.
if (p_optimize) {
// Set the apex poly/point to the end point
gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id];
Vector3 apex_point = end_point;
path.push_back(end_point);
gd::NavigationPoly *left_poly = apex_poly;
Vector3 left_portal = apex_point;
gd::NavigationPoly *right_poly = apex_poly;
Vector3 right_portal = apex_point;
while (p) {
Vector3 left;
Vector3 right;
gd::NavigationPoly *p = apex_poly;
#define CLOCK_TANGENT(m_a, m_b, m_c) (((m_a) - (m_c)).cross((m_a) - (m_b)))
path.push_back(end_point);
if (p->poly == begin_poly) {
left = begin_point;
right = begin_point;
while (p) {
// Set left and right points of the pathway between polygons.
Vector3 left = p->back_navigation_edge_pathway_start;
Vector3 right = p->back_navigation_edge_pathway_end;
if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(up) < 0) {
SWAP(left, right);
}
bool skip = false;
if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(up) >= 0) {
//process
if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(up) > 0) {
left_poly = p;
left_portal = left;
} else {
int prev = p->back_navigation_edge;
int prev_n = (p->back_navigation_edge + 1) % p->poly->points.size();
left = p->poly->points[prev].pos;
right = p->poly->points[prev_n].pos;
clip_path(navigation_polys, path, apex_poly, right_portal, right_poly);
//if (CLOCK_TANGENT(apex_point,left,(left+right)*0.5).dot(up) < 0){
if (p->poly->clockwise) {
SWAP(left, right);
}
apex_point = right_portal;
p = right_poly;
left_poly = p;
apex_poly = p;
left_portal = apex_point;
right_portal = apex_point;
path.push_back(apex_point);
skip = true;
}
bool skip = false;
if (CLOCK_TANGENT(apex_point, portal_left, left).dot(up) >= 0) {
//process
if (portal_left == apex_point || CLOCK_TANGENT(apex_point, left, portal_right).dot(up) > 0) {
left_poly = p;
portal_left = left;
} else {
clip_path(navigation_polys, path, apex_poly, portal_right, right_poly);
apex_point = portal_right;
p = right_poly;
left_poly = p;
apex_poly = p;
portal_left = apex_point;
portal_right = apex_point;
path.push_back(apex_point);
skip = true;
}
}
if (!skip && CLOCK_TANGENT(apex_point, portal_right, right).dot(up) <= 0) {
//process
if (portal_right == apex_point || CLOCK_TANGENT(apex_point, right, portal_left).dot(up) < 0) {
right_poly = p;
portal_right = right;
} else {
clip_path(navigation_polys, path, apex_poly, portal_left, left_poly);
apex_point = portal_left;
p = left_poly;
right_poly = p;
apex_poly = p;
portal_right = apex_point;
portal_left = apex_point;
path.push_back(apex_point);
}
}
if (p->prev_navigation_poly_id != -1)
p = &navigation_polys[p->prev_navigation_poly_id];
else
// The end
p = NULL;
}
if (path[path.size() - 1] != begin_point)
path.push_back(begin_point);
if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(up) <= 0) {
//process
if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(up) < 0) {
right_poly = p;
right_portal = right;
} else {
clip_path(navigation_polys, path, apex_poly, left_portal, left_poly);
path.invert();
} else {
path.push_back(end_point);
// Add mid points
int np_id = least_cost_id;
while (np_id != -1 && navigation_polys[np_id].prev_navigation_poly_id != -1) {
int prev = navigation_polys[np_id].back_navigation_edge;
int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size();
Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5;
path.push_back(point);
np_id = navigation_polys[np_id].prev_navigation_poly_id;
apex_point = left_portal;
p = left_poly;
right_poly = p;
apex_poly = p;
right_portal = apex_point;
left_portal = apex_point;
path.push_back(apex_point);
}
}
path.push_back(begin_point);
path.invert();
// Go to the previous polygon.
if (p->back_navigation_poly_id != -1) {
p = &navigation_polys[p->back_navigation_poly_id];
} else {
// The end
p = nullptr;
}
}
return path;
// If the last point is not the begin point, add it to the list.
if (path[path.size() - 1] != begin_point) {
path.push_back(begin_point);
}
path.invert();
} else {
path.push_back(end_point);
// Add mid points
int np_id = least_cost_id;
while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) {
int prev = navigation_polys[np_id].back_navigation_edge;
int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size();
Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5;
path.push_back(point);
np_id = navigation_polys[np_id].back_navigation_poly_id;
}
path.push_back(begin_point);
path.invert();
}
return Vector<Vector3>();
return path;
}
Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const {
@ -492,7 +477,7 @@ void NavMap::add_region(NavRegion *p_region) {
}
void NavMap::remove_region(NavRegion *p_region) {
std::vector<NavRegion *>::iterator it = std::find(regions.begin(), regions.end(), p_region);
const std::vector<NavRegion *>::iterator it = std::find(regions.begin(), regions.end(), p_region);
if (it != regions.end()) {
regions.erase(it);
regenerate_links = true;
@ -512,7 +497,7 @@ void NavMap::add_agent(RvoAgent *agent) {
void NavMap::remove_agent(RvoAgent *agent) {
remove_agent_as_controlled(agent);
auto it = std::find(agents.begin(), agents.end(), agent);
const std::vector<RvoAgent *>::iterator it = std::find(agents.begin(), agents.end(), agent);
if (it != agents.end()) {
agents.erase(it);
agents_dirty = true;
@ -528,13 +513,14 @@ void NavMap::set_agent_as_controlled(RvoAgent *agent) {
}
void NavMap::remove_agent_as_controlled(RvoAgent *agent) {
auto it = std::find(controlled_agents.begin(), controlled_agents.end(), agent);
const std::vector<RvoAgent *>::iterator it = std::find(controlled_agents.begin(), controlled_agents.end(), agent);
if (it != controlled_agents.end()) {
controlled_agents.erase(it);
}
}
void NavMap::sync() {
// Check if we need to update the links.
if (regenerate_polygons) {
for (size_t r(0); r < regions.size(); r++) {
regions[r]->scratch_polygons();
@ -549,27 +535,30 @@ void NavMap::sync() {
}
if (regenerate_links) {
// Copy all region polygons in the map.
// Remove regions connections.
for (size_t r(0); r < regions.size(); r++) {
regions[r]->get_connections().clear();
}
// Resize the polygon count.
int count = 0;
for (size_t r(0); r < regions.size(); r++) {
count += regions[r]->get_polygons().size();
}
polygons.resize(count);
count = 0;
// Copy all region polygons in the map.
count = 0;
for (size_t r(0); r < regions.size(); r++) {
std::copy(
regions[r]->get_polygons().data(),
regions[r]->get_polygons().data() + regions[r]->get_polygons().size(),
polygons.begin() + count);
count += regions[r]->get_polygons().size();
}
// Connects the `Edges` of all the `Polygons` of all `Regions` each other.
Map<gd::EdgeKey, gd::Connection> connections;
// Group all edges per key.
Map<gd::EdgeKey, Vector<gd::Edge::Connection>> connections;
for (size_t poly_id(0); poly_id < polygons.size(); poly_id++) {
gd::Polygon &poly(polygons[poly_id]);
@ -577,69 +566,40 @@ void NavMap::sync() {
int next_point = (p + 1) % poly.points.size();
gd::EdgeKey ek(poly.points[p].key, poly.points[next_point].key);
Map<gd::EdgeKey, gd::Connection>::Element *connection = connections.find(ek);
Map<gd::EdgeKey, Vector<gd::Edge::Connection>>::Element *connection = connections.find(ek);
if (!connection) {
// Nothing yet
gd::Connection c;
c.A = &poly;
c.A_edge = p;
c.B = NULL;
c.B_edge = -1;
connections[ek] = c;
} else if (connection->get().B == NULL) {
CRASH_COND(connection->get().A == NULL); // Unreachable
// Connect the two Polygons by this edge
connection->get().B = &poly;
connection->get().B_edge = p;
connection->get().A->edges[connection->get().A_edge].this_edge = connection->get().A_edge;
connection->get().A->edges[connection->get().A_edge].other_polygon = connection->get().B;
connection->get().A->edges[connection->get().A_edge].other_edge = connection->get().B_edge;
connection->get().B->edges[connection->get().B_edge].this_edge = connection->get().B_edge;
connection->get().B->edges[connection->get().B_edge].other_polygon = connection->get().A;
connection->get().B->edges[connection->get().B_edge].other_edge = connection->get().A_edge;
connections[ek] = Vector<gd::Edge::Connection>();
}
if (connections[ek].size() <= 1) {
// Add the polygon/edge tuple to this key.
gd::Edge::Connection new_connection;
new_connection.polygon = &poly;
new_connection.edge = p;
new_connection.pathway_start = poly.points[p].pos;
new_connection.pathway_end = poly.points[next_point].pos;
connections[ek].push_back(new_connection);
} else {
// The edge is already connected with another edge, skip.
ERR_PRINT("Attempted to merge a navigation mesh triangle edge with another already-merged edge. Either the Navigation's `cell_size` is different from the one used to generate the navigation mesh or `detail/sample_max_error` is too small. This will cause navigation problem.");
ERR_PRINT("Attempted to merge a navigation mesh triangle edge with another already-merged edge. This happens when the current `cell_size` is different from the one used to generate the navigation mesh. This will cause navigation problem.");
}
}
}
// Takes all the free edges.
std::vector<gd::FreeEdge> free_edges;
free_edges.reserve(connections.size());
for (auto connection_element = connections.front(); connection_element; connection_element = connection_element->next()) {
if (connection_element->get().B == NULL) {
CRASH_COND(connection_element->get().A == NULL); // Unreachable
CRASH_COND(connection_element->get().A_edge < 0); // Unreachable
// This is a free edge
uint32_t id(free_edges.size());
free_edges.push_back(gd::FreeEdge());
free_edges[id].is_free = true;
free_edges[id].poly = connection_element->get().A;
free_edges[id].edge_id = connection_element->get().A_edge;
uint32_t point_0(free_edges[id].edge_id);
uint32_t point_1((free_edges[id].edge_id + 1) % free_edges[id].poly->points.size());
Vector3 pos_0 = free_edges[id].poly->points[point_0].pos;
Vector3 pos_1 = free_edges[id].poly->points[point_1].pos;
Vector3 relative = pos_1 - pos_0;
free_edges[id].edge_center = (pos_0 + pos_1) / 2.0;
free_edges[id].edge_dir = relative.normalized();
free_edges[id].edge_len_squared = relative.length_squared();
Vector<gd::Edge::Connection> free_edges;
for (Map<gd::EdgeKey, Vector<gd::Edge::Connection>>::Element *E = connections.front(); E; E = E->next()) {
if (E->get().size() == 2) {
// Connect edge that are shared in different polygons.
gd::Edge::Connection &c1 = E->get().write[0];
gd::Edge::Connection &c2 = E->get().write[1];
c1.polygon->edges[c1.edge].connections.push_back(c2);
c2.polygon->edges[c2.edge].connections.push_back(c1);
// Note: The pathway_start/end are full for those connection and do not need to be modified.
} else {
CRASH_COND_MSG(E->get().size() != 1, vformat("Number of connection != 1. Found: %d", E->get().size()));
free_edges.push_back(E->get()[0]);
}
}
const float ecm_squared(edge_connection_margin * edge_connection_margin);
#define LEN_TOLERANCE 0.1
#define DIR_TOLERANCE 0.9
// In front of tolerance
#define IFO_TOLERANCE 0.5
// Find the compatible near edges.
//
// Note:
@ -647,48 +607,73 @@ void NavMap::sync() {
// to be connected, create new polygons to remove that small gap is
// not really useful and would result in wasteful computation during
// connection, integration and path finding.
for (size_t i(0); i < free_edges.size(); i++) {
if (!free_edges[i].is_free) {
continue;
}
gd::FreeEdge &edge = free_edges[i];
for (size_t y(0); y < free_edges.size(); y++) {
gd::FreeEdge &other_edge = free_edges[y];
if (i == y || !other_edge.is_free || edge.poly->owner == other_edge.poly->owner) {
for (int i = 0; i < free_edges.size(); i++) {
const gd::Edge::Connection &free_edge = free_edges[i];
Vector3 edge_p1 = free_edge.polygon->points[free_edge.edge].pos;
Vector3 edge_p2 = free_edge.polygon->points[(free_edge.edge + 1) % free_edge.polygon->points.size()].pos;
for (int j = 0; j < free_edges.size(); j++) {
const gd::Edge::Connection &other_edge = free_edges[j];
if (i == j || free_edge.polygon->owner == other_edge.polygon->owner) {
continue;
}
Vector3 rel_centers = other_edge.edge_center - edge.edge_center;
if (ecm_squared > rel_centers.length_squared() // Are enough closer?
&& ABS(edge.edge_len_squared - other_edge.edge_len_squared) < LEN_TOLERANCE // Are the same length?
&& ABS(edge.edge_dir.dot(other_edge.edge_dir)) > DIR_TOLERANCE // Are aligned?
&& ABS(rel_centers.normalized().dot(edge.edge_dir)) < IFO_TOLERANCE // Are one in front the other?
) {
// The edges can be connected
edge.is_free = false;
other_edge.is_free = false;
Vector3 other_edge_p1 = other_edge.polygon->points[other_edge.edge].pos;
Vector3 other_edge_p2 = other_edge.polygon->points[(other_edge.edge + 1) % other_edge.polygon->points.size()].pos;
edge.poly->edges[edge.edge_id].this_edge = edge.edge_id;
edge.poly->edges[edge.edge_id].other_edge = other_edge.edge_id;
edge.poly->edges[edge.edge_id].other_polygon = other_edge.poly;
other_edge.poly->edges[other_edge.edge_id].this_edge = other_edge.edge_id;
other_edge.poly->edges[other_edge.edge_id].other_edge = edge.edge_id;
other_edge.poly->edges[other_edge.edge_id].other_polygon = edge.poly;
// Compute the projection of the opposite edge on the current one
Vector3 edge_vector = edge_p2 - edge_p1;
float projected_p1_ratio = edge_vector.dot(other_edge_p1 - edge_p1) / (edge_vector.length_squared());
float projected_p2_ratio = edge_vector.dot(other_edge_p2 - edge_p1) / (edge_vector.length_squared());
if ((projected_p1_ratio < 0.0 && projected_p2_ratio < 0.0) || (projected_p1_ratio > 1.0 && projected_p2_ratio > 1.0)) {
continue;
}
// Check if the two edges are close to each other enough and compute a pathway between the two regions.
Vector3 self1 = edge_vector * CLAMP(projected_p1_ratio, 0.0, 1.0) + edge_p1;
Vector3 other1;
if (projected_p1_ratio >= 0.0 && projected_p1_ratio <= 1.0) {
other1 = other_edge_p1;
} else {
other1 = other_edge_p1.linear_interpolate(other_edge_p2, (1.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio));
}
if (other1.distance_to(self1) > edge_connection_margin) {
continue;
}
Vector3 self2 = edge_vector * CLAMP(projected_p2_ratio, 0.0, 1.0) + edge_p1;
Vector3 other2;
if (projected_p2_ratio >= 0.0 && projected_p2_ratio <= 1.0) {
other2 = other_edge_p2;
} else {
other2 = other_edge_p1.linear_interpolate(other_edge_p2, (0.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio));
}
if (other2.distance_to(self2) > edge_connection_margin) {
continue;
}
// The edges can now be connected.
gd::Edge::Connection new_connection = other_edge;
new_connection.pathway_start = (self1 + other1) / 2.0;
new_connection.pathway_end = (self2 + other2) / 2.0;
free_edge.polygon->edges[free_edge.edge].connections.push_back(new_connection);
// Add the connection to the region_connection map.
free_edge.polygon->owner->get_connections().push_back(new_connection);
}
}
// Update the update ID.
map_update_id = (map_update_id + 1) % 9999999;
}
if (regenerate_links) {
map_update_id = map_update_id + 1 % 9999999;
}
// Update agents tree.
if (agents_dirty) {
std::vector<RVO::Agent *> raw_agents;
raw_agents.reserve(agents.size());
for (size_t i(0); i < agents.size(); i++)
for (size_t i(0); i < agents.size(); i++) {
raw_agents.push_back(agents[i]->get_agent());
}
rvo.buildAgentTree(raw_agents);
}
@ -725,30 +710,38 @@ void NavMap::dispatch_callbacks() {
void NavMap::clip_path(const std::vector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly) const {
Vector3 from = path[path.size() - 1];
if (from.distance_to(p_to_point) < CMP_EPSILON)
if (from.is_equal_approx(p_to_point)) {
return;
}
Plane cut_plane;
cut_plane.normal = (from - p_to_point).cross(up);
if (cut_plane.normal == Vector3())
if (cut_plane.normal == Vector3()) {
return;
}
cut_plane.normal.normalize();
cut_plane.d = cut_plane.normal.dot(from);
while (from_poly != p_to_poly) {
int back_nav_edge = from_poly->back_navigation_edge;
Vector3 a = from_poly->poly->points[back_nav_edge].pos;
Vector3 b = from_poly->poly->points[(back_nav_edge + 1) % from_poly->poly->points.size()].pos;
Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start;
Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end;
ERR_FAIL_COND(from_poly->prev_navigation_poly_id == -1);
from_poly = &p_navigation_polys[from_poly->prev_navigation_poly_id];
ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1);
from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id];
if (a.distance_to(b) > CMP_EPSILON) {
if (!pathway_start.is_equal_approx(pathway_end)) {
Vector3 inters;
if (cut_plane.intersects_segment(a, b, &inters)) {
if (inters.distance_to(p_to_point) > CMP_EPSILON && inters.distance_to(path[path.size() - 1]) > CMP_EPSILON) {
if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) {
if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) {
path.push_back(inters);
}
}
}
}
}
NavMap::NavMap() {
}
NavMap::~NavMap() {
step_work_pool.finish();
}

View file

@ -33,14 +33,12 @@
#include "nav_rid.h"
#include "core/map.h"
#include "core/math/math_defs.h"
#include "core/os/thread_work_pool.h"
#include "nav_utils.h"
#include <KdTree.h>
/**
@author AndreaCatania
*/
#include <KdTree.h>
class NavRegion;
class RvoAgent;
@ -48,18 +46,18 @@ class NavRegion;
class NavMap : public NavRid {
/// Map Up
Vector3 up;
Vector3 up = Vector3(0, 1, 0);
/// To find the polygons edges the vertices are displaced in a grid where
/// each cell has the following cell_size and cell_height.
real_t cell_size;
real_t cell_height;
real_t cell_size = 0.25;
real_t cell_height = 0.25;
/// This value is used to detect the near edges to connect.
real_t edge_connection_margin;
real_t edge_connection_margin = 0.25;
bool regenerate_polygons;
bool regenerate_links;
bool regenerate_polygons = true;
bool regenerate_links = true;
std::vector<NavRegion *> regions;
@ -70,7 +68,7 @@ class NavMap : public NavRid {
RVO::KdTree rvo;
/// Is agent array modified?
bool agents_dirty;
bool agents_dirty = false;
/// All the Agents (even the controlled one)
std::vector<RvoAgent *> agents;
@ -79,10 +77,10 @@ class NavMap : public NavRid {
std::vector<RvoAgent *> controlled_agents;
/// Physics delta time
real_t deltatime;
real_t deltatime = 0.0;
/// Change the id each time the map is updated.
uint32_t map_update_id;
uint32_t map_update_id = 0;
/// Pooled threads for computing steps
ThreadWorkPool step_work_pool;
@ -113,7 +111,7 @@ public:
gd::PointKey get_point_key(const Vector3 &p_pos) const;
Vector<Vector3> get_path(Vector3 p_origin, Vector3 p_destination, bool p_optimize) const;
Vector<Vector3> get_path(Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const;
Vector3 get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const;
Vector3 get_closest_point(const Vector3 &p_point) const;
Vector3 get_closest_point_normal(const Vector3 &p_point) const;

View file

@ -32,18 +32,20 @@
#include "nav_map.h"
/**
@author AndreaCatania
*/
NavRegion::NavRegion() :
map(NULL),
polygons_dirty(true) {
}
void NavRegion::set_map(NavMap *p_map) {
map = p_map;
polygons_dirty = true;
if (!map) {
connections.clear();
}
}
void NavRegion::set_navigation_layers(uint32_t p_navigation_layers) {
navigation_layers = p_navigation_layers;
}
uint32_t NavRegion::get_navigation_layers() const {
return navigation_layers;
}
void NavRegion::set_transform(Transform p_transform) {
@ -56,6 +58,25 @@ void NavRegion::set_mesh(Ref<NavigationMesh> p_mesh) {
polygons_dirty = true;
}
int NavRegion::get_connections_count() const {
if (!map) {
return 0;
}
return connections.size();
}
Vector3 NavRegion::get_connection_pathway_start(int p_connection_id) const {
ERR_FAIL_COND_V(!map, Vector3());
ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3());
return connections[p_connection_id].pathway_start;
}
Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const {
ERR_FAIL_COND_V(!map, Vector3());
ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3());
return connections[p_connection_id].pathway_end;
}
bool NavRegion::sync() {
bool something_changed = polygons_dirty /* || something_dirty? */;
@ -71,17 +92,19 @@ void NavRegion::update_polygons() {
polygons.clear();
polygons_dirty = false;
if (map == NULL) {
if (map == nullptr) {
return;
}
if (mesh.is_null())
if (mesh.is_null()) {
return;
}
PoolVector<Vector3> vertices = mesh->get_vertices();
int len = vertices.size();
if (len == 0)
if (len == 0) {
return;
}
PoolVector<Vector3>::Read vertices_r = vertices.read();
@ -132,3 +155,7 @@ void NavRegion::update_polygons() {
}
}
}
NavRegion::NavRegion() {}
NavRegion::~NavRegion() {}

View file

@ -31,32 +31,31 @@
#ifndef NAV_REGION_H
#define NAV_REGION_H
#include "nav_rid.h"
#include "nav_utils.h"
#include "scene/3d/navigation.h"
#include <vector>
/**
@author AndreaCatania
*/
#include "nav_rid.h"
#include "nav_utils.h"
#include <vector>
class NavMap;
class NavRegion;
class NavRegion : public NavRid {
NavMap *map;
NavMap *map = nullptr;
Transform transform;
Ref<NavigationMesh> mesh;
uint32_t navigation_layers = 1;
float enter_cost = 0.0;
float travel_cost = 1.0;
Vector<gd::Edge::Connection> connections;
bool polygons_dirty;
bool polygons_dirty = true;
/// Cache
std::vector<gd::Polygon> polygons;
public:
NavRegion();
void scratch_polygons() {
polygons_dirty = true;
}
@ -66,6 +65,15 @@ public:
return map;
}
void set_enter_cost(float p_enter_cost) { enter_cost = MAX(p_enter_cost, 0.0); }
float get_enter_cost() const { return enter_cost; }
void set_travel_cost(float p_travel_cost) { travel_cost = MAX(p_travel_cost, 0.0); }
float get_travel_cost() const { return travel_cost; }
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
void set_transform(Transform transform);
const Transform &get_transform() const {
return transform;
@ -76,12 +84,22 @@ public:
return mesh;
}
Vector<gd::Edge::Connection> &get_connections() {
return connections;
}
int get_connections_count() const;
Vector3 get_connection_pathway_start(int p_connection_id) const;
Vector3 get_connection_pathway_end(int p_connection_id) const;
std::vector<gd::Polygon> const &get_polygons() const {
return polygons;
}
bool sync();
NavRegion();
~NavRegion();
private:
void update_polygons();
};

View file

@ -33,10 +33,6 @@
#include "core/rid.h"
/**
@author AndreaCatania
*/
class NavRid : public RID_Data {
RID self;

View file

@ -32,11 +32,8 @@
#define NAV_UTILS_H
#include "core/math/vector3.h"
#include <vector>
/**
@author AndreaCatania
*/
#include <vector>
class NavRegion;
@ -50,8 +47,7 @@ union PointKey {
int64_t z : 21;
};
uint64_t key;
bool operator<(const PointKey &p_key) const { return key < p_key.key; }
uint64_t key = 0;
};
struct EdgeKey {
@ -78,23 +74,20 @@ struct Point {
struct Edge {
/// This edge ID
int this_edge;
int this_edge = -1;
/// Other Polygon
Polygon *other_polygon;
/// The other `Polygon` at this edge id has this `Polygon`.
int other_edge;
Edge() {
this_edge = -1;
other_polygon = NULL;
other_edge = -1;
}
/// The gateway in the edge, as, in some case, the whole edge might not be navigable.
struct Connection {
Polygon *polygon = nullptr;
int edge = -1;
Vector3 pathway_start;
Vector3 pathway_end;
};
Vector<Connection> connections;
};
struct Polygon {
NavRegion *owner;
NavRegion *owner = nullptr;
/// The points of this `Polygon`
std::vector<Point> points;
@ -109,40 +102,24 @@ struct Polygon {
Vector3 center;
};
struct Connection {
Polygon *A;
int A_edge;
Polygon *B;
int B_edge;
Connection() {
A = NULL;
B = NULL;
A_edge = -1;
B_edge = -1;
}
};
struct NavigationPoly {
uint32_t self_id;
uint32_t self_id = 0;
/// This poly.
const Polygon *poly;
/// The previous navigation poly (id in the `navigation_poly` array).
int prev_navigation_poly_id;
/// The edge id in this `Poly` to reach the `prev_navigation_poly_id`.
uint32_t back_navigation_edge;
/// Those 4 variables are used to travel the path backwards.
int back_navigation_poly_id = -1;
uint32_t back_navigation_edge = UINT32_MAX;
Vector3 back_navigation_edge_pathway_start;
Vector3 back_navigation_edge_pathway_end;
/// The entry location of this poly.
Vector3 entry;
/// The distance to the destination.
float traveled_distance;
float traveled_distance = 0.0;
NavigationPoly(const Polygon *p_poly) :
self_id(0),
poly(p_poly),
prev_navigation_poly_id(-1),
back_navigation_edge(0),
traveled_distance(0.0) {
}
poly(p_poly) {}
bool operator==(const NavigationPoly &other) const {
return this->poly == other.poly;
@ -153,15 +130,6 @@ struct NavigationPoly {
}
};
struct FreeEdge {
bool is_free;
Polygon *poly;
uint32_t edge_id;
Vector3 edge_center;
Vector3 edge_dir;
float edge_len_squared;
};
struct ClosestPointQueryResult {
Vector3 point;
Vector3 normal;

View file

@ -42,10 +42,6 @@
#include "navigation_mesh_editor_plugin.h"
#endif
/**
@author AndreaCatania
*/
#ifndef _3D_DISABLED
NavigationMeshGenerator *_nav_mesh_generator = nullptr;
#endif

View file

@ -28,9 +28,5 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
/**
@author AndreaCatania
*/
void register_navigation_types();
void unregister_navigation_types();

View file

@ -32,12 +32,7 @@
#include "nav_map.h"
/**
@author AndreaCatania
*/
RvoAgent::RvoAgent() :
map(NULL) {
RvoAgent::RvoAgent() {
callback.id = ObjectID(0);
}
@ -70,7 +65,7 @@ void RvoAgent::dispatch_callback() {
return;
}
Object *obj = ObjectDB::get_instance(callback.id);
if (obj == NULL) {
if (obj == nullptr) {
callback.id = ObjectID(0);
}

View file

@ -33,11 +33,8 @@
#include "core/object.h"
#include "nav_rid.h"
#include <Agent.h>
/**
@author AndreaCatania
*/
#include <Agent.h>
class NavMap;
@ -49,10 +46,10 @@ class RvoAgent : public NavRid {
Variant new_velocity;
};
NavMap *map;
NavMap *map = nullptr;
RVO::Agent agent;
AvoidanceComputedCallback callback;
uint32_t map_update_id;
uint32_t map_update_id = 0;
public:
RvoAgent();

View file

@ -45,8 +45,12 @@ void Navigation2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_edge_connection_margin", "margin"), &Navigation2D::set_edge_connection_margin);
ClassDB::bind_method(D_METHOD("get_edge_connection_margin"), &Navigation2D::get_edge_connection_margin);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &Navigation2D::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &Navigation2D::get_navigation_layers);
ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_size"), "set_cell_size", "get_cell_size");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "edge_connection_margin"), "set_edge_connection_margin", "get_edge_connection_margin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
}
void Navigation2D::_notification(int p_what) {
@ -54,9 +58,12 @@ void Navigation2D::_notification(int p_what) {
case NOTIFICATION_READY: {
Navigation2DServer::get_singleton()->map_set_active(map, true);
} break;
case NOTIFICATION_EXIT_TREE: {
Navigation2DServer::get_singleton()->map_set_active(map, false);
} break;
// FIXME 3.5 with this old navigation 2d node only
// if the node gets deleted this exit causes annoying error prints in debug
// It tries to deactive a map that itself has send a free command to the server.
//case NOTIFICATION_EXIT_TREE: {
// Navigation2DServer::get_singleton()->map_set_active(map, false);
//} break;
}
}
@ -71,7 +78,7 @@ void Navigation2D::set_edge_connection_margin(float p_edge_connection_margin) {
}
Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vector2 &p_end, bool p_optimize) const {
return Navigation2DServer::get_singleton()->map_get_path(map, p_start, p_end, p_optimize);
return Navigation2DServer::get_singleton()->map_get_path(map, p_start, p_end, p_optimize, navigation_layers);
}
Vector2 Navigation2D::get_closest_point(const Vector2 &p_point) const {
@ -82,10 +89,19 @@ RID Navigation2D::get_closest_point_owner(const Vector2 &p_point) const {
return Navigation2DServer::get_singleton()->map_get_closest_point_owner(map, p_point);
}
void Navigation2D::set_navigation_layers(uint32_t p_navigation_layers) {
navigation_layers = p_navigation_layers;
}
uint32_t Navigation2D::get_navigation_layers() const {
return navigation_layers;
}
Navigation2D::Navigation2D() {
map = Navigation2DServer::get_singleton()->map_create();
set_cell_size(1); // One pixel
set_edge_connection_margin(1);
Navigation2DServer::get_singleton()->map_set_active(map, true);
Navigation2DServer::get_singleton()->map_set_cell_size(map, get_cell_size());
Navigation2DServer::get_singleton()->map_set_edge_connection_margin(map, get_edge_connection_margin());
}
Navigation2D::~Navigation2D() {

View file

@ -38,8 +38,9 @@ class Navigation2D : public Node2D {
GDCLASS(Navigation2D, Node2D);
RID map;
real_t cell_size;
real_t edge_connection_margin;
real_t cell_size = 1.0;
real_t edge_connection_margin = 1.0;
uint32_t navigation_layers = 1;
protected:
static void _bind_methods();
@ -55,6 +56,9 @@ public:
return cell_size;
}
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
void set_edge_connection_margin(float p_edge_connection_margin);
float get_edge_connection_margin() const {
return edge_connection_margin;

View file

@ -64,6 +64,9 @@ void NavigationAgent2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_path_max_distance", "max_speed"), &NavigationAgent2D::set_path_max_distance);
ClassDB::bind_method(D_METHOD("get_path_max_distance"), &NavigationAgent2D::get_path_max_distance);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationAgent2D::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationAgent2D::get_navigation_layers);
ClassDB::bind_method(D_METHOD("set_target_location", "location"), &NavigationAgent2D::set_target_location);
ClassDB::bind_method(D_METHOD("get_target_location"), &NavigationAgent2D::get_target_location);
ClassDB::bind_method(D_METHOD("get_next_location"), &NavigationAgent2D::get_next_location);
@ -86,6 +89,7 @@ void NavigationAgent2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_speed", PROPERTY_HINT_RANGE, "0.1,100000,0.01"), "set_max_speed", "get_max_speed");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "path_max_distance", PROPERTY_HINT_RANGE, "10,100,1"), "set_path_max_distance", "get_path_max_distance");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_SIGNAL(MethodInfo("path_changed"));
ADD_SIGNAL(MethodInfo("target_reached"));
@ -95,11 +99,7 @@ void NavigationAgent2D::_bind_methods() {
void NavigationAgent2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
agent_parent = Object::cast_to<Node2D>(get_parent());
set_avoidance_enabled(avoidance_enabled);
case NOTIFICATION_POST_ENTER_TREE: {
// Search the navigation node and set it
{
Navigation2D *nav = nullptr;
@ -112,12 +112,31 @@ void NavigationAgent2D::_notification(int p_what) {
p = p->get_parent();
}
}
set_navigation(nav);
}
// need to use POST_ENTER_TREE cause with normal ENTER_TREE not all required Nodes are ready.
// cannot use READY as ready does not get called if Node is readded to SceneTree
set_agent_parent(get_parent());
set_physics_process_internal(true);
} break;
case NOTIFICATION_PARENTED: {
if (is_inside_tree() && (get_parent() != agent_parent)) {
// only react to PARENTED notifications when already inside_tree and parent changed, e.g. users switch nodes around
// PARENTED notification fires also when Node is added in scripts to a parent
// this would spam transforms fails and world fails while Node is outside SceneTree
// when node gets reparented when joining the tree POST_ENTER_TREE takes care of this
set_agent_parent(get_parent());
set_physics_process_internal(true);
}
} break;
case NOTIFICATION_UNPARENTED: {
// if agent has no parent no point in processing it until reparented
set_agent_parent(nullptr);
set_physics_process_internal(false);
} break;
case NOTIFICATION_PAUSED: {
if (agent_parent && !agent_parent->can_process()) {
map_before_pause = Navigation2DServer::get_singleton()->agent_get_map(get_rid());
@ -146,7 +165,11 @@ void NavigationAgent2D::_notification(int p_what) {
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (agent_parent) {
Navigation2DServer::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().get_origin());
if (avoidance_enabled) {
// agent_position on NavigationServer is avoidance only and has nothing to do with pathfinding
// no point in flooding NavigationServer queue with agent position updates that get send to the void if avoidance is not used
Navigation2DServer::get_singleton()->agent_set_position(agent, agent_parent->get_global_position());
}
_check_distance_to_target();
}
} break;
@ -189,6 +212,26 @@ bool NavigationAgent2D::get_avoidance_enabled() const {
return avoidance_enabled;
}
void NavigationAgent2D::set_agent_parent(Node *p_agent_parent) {
// remove agent from any avoidance map before changing parent or there will be leftovers on the RVO map
Navigation2DServer::get_singleton()->agent_set_callback(agent, nullptr, "_avoidance_done");
if (Object::cast_to<Node2D>(p_agent_parent) != nullptr) {
// place agent on navigation map first or else the RVO agent callback creation fails silently later
agent_parent = Object::cast_to<Node2D>(p_agent_parent);
if (navigation == nullptr) {
// no navigation node found in parent nodes, use default navigation map from world resource
Navigation2DServer::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world_2d()->get_navigation_map());
} else {
Navigation2DServer::get_singleton()->agent_set_map(get_rid(), navigation->get_rid());
}
// create new avoidance callback if enabled
set_avoidance_enabled(avoidance_enabled);
} else {
agent_parent = nullptr;
Navigation2DServer::get_singleton()->agent_set_map(get_rid(), RID());
}
}
void NavigationAgent2D::set_navigation(Navigation2D *p_nav) {
if (navigation == p_nav) {
return; // Pointless
@ -208,6 +251,18 @@ Node *NavigationAgent2D::get_navigation_node() const {
return Object::cast_to<Node>(navigation);
}
void NavigationAgent2D::set_navigation_layers(uint32_t p_navigation_layers) {
bool _navigation_layers_changed = navigation_layers != p_navigation_layers;
navigation_layers = p_navigation_layers;
if (_navigation_layers_changed) {
_request_repath();
}
}
uint32_t NavigationAgent2D::get_navigation_layers() const {
return navigation_layers;
}
void NavigationAgent2D::set_target_desired_distance(real_t p_dd) {
target_desired_distance = p_dd;
}
@ -247,10 +302,7 @@ real_t NavigationAgent2D::get_path_max_distance() {
void NavigationAgent2D::set_target_location(Vector2 p_location) {
target_location = p_location;
navigation_path.clear();
target_reached = false;
navigation_finished = false;
update_frame_id = 0;
_request_repath();
}
Vector2 NavigationAgent2D::get_target_location() const {
@ -357,7 +409,7 @@ void NavigationAgent2D::update_navigation() {
}
if (reload_path) {
navigation_path = Navigation2DServer::get_singleton()->map_get_path(navigation->get_rid(), o, target_location, true);
navigation_path = Navigation2DServer::get_singleton()->map_get_path(navigation->get_rid(), o, target_location, true, navigation_layers);
navigation_finished = false;
nav_path_index = 0;
emit_signal("path_changed");
@ -383,6 +435,13 @@ void NavigationAgent2D::update_navigation() {
}
}
void NavigationAgent2D::_request_repath() {
navigation_path.clear();
target_reached = false;
navigation_finished = false;
update_frame_id = 0;
}
void NavigationAgent2D::_check_distance_to_target() {
if (!target_reached) {
if (distance_to_target() < target_desired_distance) {

View file

@ -40,34 +40,35 @@ class Navigation2D;
class NavigationAgent2D : public Node {
GDCLASS(NavigationAgent2D, Node);
Node2D *agent_parent;
Navigation2D *navigation;
Node2D *agent_parent = nullptr;
Navigation2D *navigation = nullptr;
RID agent;
RID map_before_pause;
bool avoidance_enabled;
bool avoidance_enabled = false;
uint32_t navigation_layers = 1;
real_t target_desired_distance;
real_t radius;
real_t neighbor_dist;
int max_neighbors;
real_t time_horizon;
real_t max_speed;
real_t target_desired_distance = 1.0;
real_t radius = 0.0;
real_t neighbor_dist = 0.0;
int max_neighbors = 0;
real_t time_horizon = 0.0;
real_t max_speed = 0.0;
real_t path_max_distance;
real_t path_max_distance = 3.0;
Vector2 target_location;
Vector<Vector2> navigation_path;
int nav_path_index;
bool velocity_submitted;
int nav_path_index = 0;
bool velocity_submitted = false;
Vector2 prev_safe_velocity;
/// The submitted target velocity
Vector2 target_velocity;
bool target_reached;
bool navigation_finished;
bool target_reached = false;
bool navigation_finished = true;
// No initialized on purpose
uint32_t update_frame_id;
uint32_t update_frame_id = 0;
protected:
static void _bind_methods();
@ -92,6 +93,11 @@ public:
void set_avoidance_enabled(bool p_enabled);
bool get_avoidance_enabled() const;
void set_agent_parent(Node *p_agent_parent);
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
void set_target_desired_distance(real_t p_dd);
real_t get_target_desired_distance() const {
return target_desired_distance;
@ -151,6 +157,7 @@ public:
private:
void update_navigation();
void _request_repath();
void _check_distance_to_target();
};

View file

@ -151,10 +151,12 @@ void NavigationPolygon::add_outline_at_index(const PoolVector<Vector2> &p_outlin
int NavigationPolygon::get_polygon_count() const {
return polygons.size();
}
Vector<int> NavigationPolygon::get_polygon(int p_idx) {
ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector<int>());
return polygons[p_idx].indices;
}
void NavigationPolygon::clear_polygons() {
polygons.clear();
{
@ -165,6 +167,7 @@ void NavigationPolygon::clear_polygons() {
Ref<NavigationMesh> NavigationPolygon::get_mesh() {
MutexLock lock(navmesh_generation);
if (navmesh.is_null()) {
navmesh.instance();
PoolVector<Vector3> verts;
@ -184,6 +187,7 @@ Ref<NavigationMesh> NavigationPolygon::get_mesh() {
navmesh->add_polygon(get_polygon(i));
}
}
return navmesh;
}
@ -217,6 +221,7 @@ void NavigationPolygon::clear_outlines() {
outlines.clear();
rect_cache_dirty = true;
}
void NavigationPolygon::make_polygons_from_outlines() {
{
MutexLock lock(navmesh_generation);
@ -348,8 +353,7 @@ void NavigationPolygon::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines");
}
NavigationPolygon::NavigationPolygon() :
rect_cache_dirty(true) {
NavigationPolygon::NavigationPolygon() {
}
NavigationPolygon::~NavigationPolygon() {
@ -367,10 +371,14 @@ void NavigationPolygonInstance::set_enabled(bool p_enabled) {
if (!enabled) {
Navigation2DServer::get_singleton()->region_set_map(region, RID());
Navigation2DServer::get_singleton_mut()->disconnect("map_changed", this, "_map_changed");
} else {
if (navigation) {
if (navigation != nullptr) {
Navigation2DServer::get_singleton()->region_set_map(region, navigation->get_rid());
} else {
Navigation2DServer::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map());
}
Navigation2DServer::get_singleton_mut()->connect("map_changed", this, "_map_changed");
}
if (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint()) {
@ -382,6 +390,35 @@ bool NavigationPolygonInstance::is_enabled() const {
return enabled;
}
void NavigationPolygonInstance::set_navigation_layers(uint32_t p_navigation_layers) {
navigation_layers = p_navigation_layers;
Navigation2DServer::get_singleton()->region_set_navigation_layers(region, navigation_layers);
}
uint32_t NavigationPolygonInstance::get_navigation_layers() const {
return navigation_layers;
}
void NavigationPolygonInstance::set_enter_cost(real_t p_enter_cost) {
ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive.");
enter_cost = MAX(p_enter_cost, 0.0);
Navigation2DServer::get_singleton()->region_set_enter_cost(region, p_enter_cost);
}
real_t NavigationPolygonInstance::get_enter_cost() const {
return enter_cost;
}
void NavigationPolygonInstance::set_travel_cost(real_t p_travel_cost) {
ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive.");
travel_cost = MAX(p_travel_cost, 0.0);
Navigation2DServer::get_singleton()->region_set_enter_cost(region, travel_cost);
}
real_t NavigationPolygonInstance::get_travel_cost() const {
return travel_cost;
}
RID NavigationPolygonInstance::get_region_rid() const {
return region;
}
@ -409,26 +446,35 @@ void NavigationPolygonInstance::_notification(int p_what) {
}
break;
}
c = Object::cast_to<Node2D>(c->get_parent());
}
if (enabled && navigation == nullptr) {
// did not find a valid navigation node parent, fallback to default navigation map on world resource
Navigation2DServer::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map());
}
if (enabled) {
Navigation2DServer::get_singleton_mut()->connect("map_changed", this, "_map_changed");
}
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
Navigation2DServer::get_singleton()->region_set_transform(region, get_global_transform());
} break;
case NOTIFICATION_EXIT_TREE: {
if (navigation) {
Navigation2DServer::get_singleton()->region_set_map(region, RID());
}
navigation = nullptr;
if (enabled) {
Navigation2DServer::get_singleton_mut()->disconnect("map_changed", this, "_map_changed");
}
} break;
case NOTIFICATION_DRAW: {
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint()) && navpoly.is_valid()) {
PoolVector<Vector2> verts = navpoly->get_vertices();
int vsize = verts.size();
if (vsize < 3) {
if (verts.size() < 3) {
return;
}
@ -438,33 +484,54 @@ void NavigationPolygonInstance::_notification(int p_what) {
} else {
color = get_tree()->get_debug_navigation_disabled_color();
}
Vector<Color> colors;
Vector<Vector2> vertices;
vertices.resize(vsize);
colors.resize(vsize);
{
PoolVector<Vector2>::Read vr = verts.read();
for (int i = 0; i < vsize; i++) {
vertices.write[i] = vr[i];
colors.write[i] = color;
}
}
Color doors_color = color.lightened(0.2);
Vector<int> indices;
RandomPCG rand;
for (int i = 0; i < navpoly->get_polygon_count(); i++) {
// An array of vertices for this polygon.
Vector<int> polygon = navpoly->get_polygon(i);
for (int j = 2; j < polygon.size(); j++) {
int kofs[3] = { 0, j - 1, j };
for (int k = 0; k < 3; k++) {
int idx = polygon[kofs[k]];
ERR_FAIL_INDEX(idx, vsize);
indices.push_back(idx);
}
Vector<Vector2> vertices;
vertices.resize(polygon.size());
for (int j = 0; j < polygon.size(); j++) {
ERR_FAIL_INDEX(polygon[j], verts.size());
vertices.write[j] = verts[polygon[j]];
}
// Generate the polygon color, slightly randomly modified from the settings one.
Color random_variation_color;
random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1);
random_variation_color.a = color.a;
Vector<Color> colors;
colors.push_back(random_variation_color);
VS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), vertices, colors);
}
// Draw the region
Transform2D xform = get_global_transform();
const Navigation2DServer *ns = Navigation2DServer::get_singleton();
real_t radius = 1.0;
if (navigation != nullptr) {
radius = Navigation2DServer::get_singleton()->map_get_edge_connection_margin(navigation->get_rid());
} else {
radius = Navigation2DServer::get_singleton()->map_get_edge_connection_margin(get_world_2d()->get_navigation_map());
}
radius = radius * 0.5;
for (int i = 0; i < ns->region_get_connections_count(region); i++) {
// Two main points
Vector2 a = ns->region_get_connection_pathway_start(region, i);
a = xform.affine_inverse().xform(a);
Vector2 b = ns->region_get_connection_pathway_end(region, i);
b = xform.affine_inverse().xform(b);
draw_line(a, b, doors_color);
// Draw a circle to illustrate the margins.
real_t angle = a.angle_to_point(b);
draw_arc(a, radius, angle + Math_PI / 2.0, angle - Math_PI / 2.0 + Math_TAU, 10, doors_color);
draw_arc(b, radius, angle - Math_PI / 2.0, angle + Math_PI / 2.0, 10, doors_color);
}
VS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, vertices, colors);
}
} break;
}
@ -504,6 +571,14 @@ void NavigationPolygonInstance::_navpoly_changed() {
}
}
void NavigationPolygonInstance::_map_changed(RID p_map) {
if (navigation != nullptr && enabled && (navigation->get_rid() == p_map)) {
update();
} else if (is_inside_tree() && enabled && (get_world_2d()->get_navigation_map() == p_map)) {
update();
}
}
String NavigationPolygonInstance::get_configuration_warning() const {
if (!is_visible_in_tree() || !is_inside_tree()) {
return String();
@ -516,19 +591,6 @@ String NavigationPolygonInstance::get_configuration_warning() const {
}
warning += TTR("A NavigationPolygon resource must be set or created for this node to work. Please set a property or draw a polygon.");
}
const Node2D *c = this;
while (c) {
if (Object::cast_to<Navigation2D>(c)) {
return warning;
}
c = Object::cast_to<Node2D>(c->get_parent());
}
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("NavigationPolygonInstance must be a child or grandchild to a Navigation2D node. It only provides navigation data.");
return warning;
}
@ -540,20 +602,33 @@ void NavigationPolygonInstance::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationPolygonInstance::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationPolygonInstance::is_enabled);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationPolygonInstance::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationPolygonInstance::get_navigation_layers);
ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationPolygonInstance::get_region_rid);
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationPolygonInstance::set_enter_cost);
ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationPolygonInstance::get_enter_cost);
ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationPolygonInstance::set_travel_cost);
ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationPolygonInstance::get_travel_cost);
ClassDB::bind_method(D_METHOD("_navpoly_changed"), &NavigationPolygonInstance::_navpoly_changed);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navpoly", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "enter_cost"), "set_enter_cost", "get_enter_cost");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "travel_cost"), "set_travel_cost", "get_travel_cost");
ClassDB::bind_method(D_METHOD("_map_changed"), &NavigationPolygonInstance::_map_changed);
}
NavigationPolygonInstance::NavigationPolygonInstance() {
enabled = true;
set_notify_transform(true);
region = Navigation2DServer::get_singleton()->region_create();
navigation = nullptr;
Navigation2DServer::get_singleton()->region_set_enter_cost(region, get_enter_cost());
Navigation2DServer::get_singleton()->region_set_travel_cost(region, get_travel_cost());
}
NavigationPolygonInstance::~NavigationPolygonInstance() {

View file

@ -45,7 +45,7 @@ class NavigationPolygon : public Resource {
Vector<PoolVector<Vector2>> outlines;
mutable Rect2 item_rect;
mutable bool rect_cache_dirty;
mutable bool rect_cache_dirty = true;
Mutex navmesh_generation;
// Navigation mesh
@ -96,12 +96,18 @@ class Navigation2D;
class NavigationPolygonInstance : public Node2D {
GDCLASS(NavigationPolygonInstance, Node2D);
bool enabled;
bool enabled = true;
RID region;
Navigation2D *navigation;
Navigation2D *navigation = nullptr;
Ref<NavigationPolygon> navpoly;
real_t enter_cost = 0.0;
real_t travel_cost = 1.0;
uint32_t navigation_layers = 1;
void _navpoly_changed();
void _map_changed(RID p_map);
protected:
void _notification(int p_what);
@ -116,8 +122,17 @@ public:
void set_enabled(bool p_enabled);
bool is_enabled() const;
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
RID get_region_rid() const;
void set_enter_cost(real_t p_enter_cost);
real_t get_enter_cost() const;
void set_travel_cost(real_t p_travel_cost);
real_t get_travel_cost() const;
void set_navigation_polygon(const Ref<NavigationPolygon> &p_navpoly);
Ref<NavigationPolygon> get_navigation_polygon() const;

View file

@ -62,6 +62,8 @@ void TileMap::_notification(int p_what) {
while (c) {
navigation = Object::cast_to<Navigation2D>(c);
if (navigation) {
// only for <3.5 backward compatibility
bake_navigation = true;
break;
}
@ -87,7 +89,7 @@ void TileMap::_notification(int p_what) {
_update_quadrant_space(RID());
for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
Quadrant &q = E->get();
if (navigation) {
if (bake_navigation) {
q.clear_navpoly();
}
@ -154,7 +156,7 @@ void TileMap::_update_quadrant_transform() {
}
Transform2D nav_rel;
if (navigation) {
if (bake_navigation) {
nav_rel = get_relative_transform_to_parent(navigation);
}
@ -168,7 +170,7 @@ void TileMap::_update_quadrant_transform() {
Physics2DServer::get_singleton()->body_set_state(q.body, Physics2DServer::BODY_STATE_TRANSFORM, xform);
}
if (navigation) {
if (bake_navigation) {
for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) {
Navigation2DServer::get_singleton()->region_set_transform(F->get().region, nav_rel * F->get().xform);
}
@ -336,7 +338,7 @@ void TileMap::update_dirty_quadrants() {
Physics2DServer *ps = Physics2DServer::get_singleton();
Vector2 tofs = get_cell_draw_offset();
Transform2D nav_rel;
if (navigation) {
if (bake_navigation) {
nav_rel = get_relative_transform_to_parent(navigation);
}
@ -381,7 +383,7 @@ void TileMap::update_dirty_quadrants() {
}
int shape_idx = 0;
if (navigation) {
if (bake_navigation) {
q.clear_navpoly();
}
@ -604,7 +606,7 @@ void TileMap::update_dirty_quadrants() {
vs->canvas_item_add_set_transform(debug_canvas_item, Transform2D());
}
if (navigation) {
if (bake_navigation) {
Ref<NavigationPolygon> navpoly;
Vector2 npoly_ofs;
if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) {
@ -621,7 +623,12 @@ void TileMap::update_dirty_quadrants() {
_fix_cell_transform(xform, c, npoly_ofs, s);
RID region = Navigation2DServer::get_singleton()->region_create();
Navigation2DServer::get_singleton()->region_set_map(region, navigation->get_rid());
if (navigation) {
Navigation2DServer::get_singleton()->region_set_map(region, navigation->get_rid());
} else {
Navigation2DServer::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map());
}
Navigation2DServer::get_singleton()->region_set_navigation_layers(region, navigation_layers);
Navigation2DServer::get_singleton()->region_set_transform(region, nav_rel * xform);
Navigation2DServer::get_singleton()->region_set_navpoly(region, navpoly);
@ -818,7 +825,7 @@ void TileMap::_erase_quadrant(Map<PosKey, Quadrant>::Element *Q) {
dirty_quadrant_list.remove(&q.dirty_list);
}
if (navigation) {
if (bake_navigation) {
q.clear_navpoly();
}
@ -1394,6 +1401,28 @@ float TileMap::get_collision_bounce() const {
return bounce;
}
void TileMap::set_bake_navigation(bool p_bake_navigation) {
bake_navigation = p_bake_navigation;
for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
_make_quadrant_dirty(E);
}
}
bool TileMap::is_baking_navigation() {
return bake_navigation;
}
void TileMap::set_navigation_layers(uint32_t p_navigation_layers) {
navigation_layers = p_navigation_layers;
for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
_make_quadrant_dirty(E);
}
}
uint32_t TileMap::get_navigation_layers() {
return navigation_layers;
}
uint32_t TileMap::get_collision_layer() const {
return collision_layer;
}
@ -1811,6 +1840,12 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collision_bounce", "value"), &TileMap::set_collision_bounce);
ClassDB::bind_method(D_METHOD("get_collision_bounce"), &TileMap::get_collision_bounce);
ClassDB::bind_method(D_METHOD("set_bake_navigation", "bake_navigation"), &TileMap::set_bake_navigation);
ClassDB::bind_method(D_METHOD("is_baking_navigation"), &TileMap::is_baking_navigation);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &TileMap::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &TileMap::get_navigation_layers);
ClassDB::bind_method(D_METHOD("set_occluder_light_mask", "mask"), &TileMap::set_occluder_light_mask);
ClassDB::bind_method(D_METHOD("get_occluder_light_mask"), &TileMap::get_occluder_light_mask);
@ -1868,6 +1903,10 @@ void TileMap::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask");
ADD_GROUP("Navigation", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bake_navigation"), "set_bake_navigation", "is_baking_navigation");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_GROUP("Occluder", "occluder_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "occluder_light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask");

View file

@ -79,6 +79,8 @@ private:
CollisionObject2D *collision_parent;
bool use_kinematic;
Navigation2D *navigation;
bool bake_navigation = false;
uint32_t navigation_layers = 1;
bool show_collision = false;
union PosKey {
@ -303,6 +305,12 @@ public:
void set_collision_bounce(float p_bounce);
float get_collision_bounce() const;
void set_bake_navigation(bool p_bake_navigation);
bool is_baking_navigation();
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers();
void set_mode(Mode p_mode);
Mode get_mode() const;

View file

@ -33,7 +33,7 @@
#include "servers/navigation_server.h"
Vector<Vector3> Navigation::get_simple_path(const Vector3 &p_start, const Vector3 &p_end, bool p_optimize) const {
return NavigationServer::get_singleton()->map_get_path(map, p_start, p_end, p_optimize);
return NavigationServer::get_singleton()->map_get_path(map, p_start, p_end, p_optimize, navigation_layers);
}
Vector3 Navigation::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const {
@ -76,6 +76,14 @@ void Navigation::set_edge_connection_margin(float p_edge_connection_margin) {
NavigationServer::get_singleton()->map_set_edge_connection_margin(map, edge_connection_margin);
}
void Navigation::set_navigation_layers(uint32_t p_navigation_layers) {
navigation_layers = p_navigation_layers;
}
uint32_t Navigation::get_navigation_layers() const {
return navigation_layers;
}
void Navigation::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rid"), &Navigation::get_rid);
@ -97,10 +105,16 @@ void Navigation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_edge_connection_margin", "margin"), &Navigation::set_edge_connection_margin);
ClassDB::bind_method(D_METHOD("get_edge_connection_margin"), &Navigation::get_edge_connection_margin);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &Navigation::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &Navigation::get_navigation_layers);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_vector"), "set_up_vector", "get_up_vector");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_size"), "set_cell_size", "get_cell_size");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_height"), "set_cell_height", "get_cell_height");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "edge_connection_margin"), "set_edge_connection_margin", "get_edge_connection_margin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_SIGNAL(MethodInfo("map_changed", PropertyInfo(Variant::_RID, "map")));
}
void Navigation::_notification(int p_what) {
@ -109,19 +123,21 @@ void Navigation::_notification(int p_what) {
NavigationServer::get_singleton()->map_set_active(map, true);
} break;
case NOTIFICATION_EXIT_TREE: {
NavigationServer::get_singleton()->map_set_active(map, false);
// FIXME 3.5 with this old navigation node only
// if the node gets deleted this exit causes annoying error prints in debug
// It tries to deactive a map that itself has send a free command to the server.
//NavigationServer::get_singleton()->map_set_active(map, false);
} break;
}
}
Navigation::Navigation() {
map = NavigationServer::get_singleton()->map_create();
set_cell_size(0.3);
set_cell_height(0.2);
set_edge_connection_margin(5.0); // Five meters, depends a lot on the agents radius
up = Vector3(0, 1, 0);
NavigationServer::get_singleton()->map_set_active(map, true);
NavigationServer::get_singleton()->map_set_up(map, get_up_vector());
NavigationServer::get_singleton()->map_set_cell_size(map, get_cell_size());
NavigationServer::get_singleton()->map_set_cell_height(map, get_cell_height());
NavigationServer::get_singleton()->map_set_edge_connection_margin(map, get_edge_connection_margin());
}
Navigation::~Navigation() {

View file

@ -39,10 +39,12 @@ class Navigation : public Spatial {
RID map;
Vector3 up;
real_t cell_size;
real_t cell_height;
real_t edge_connection_margin;
Vector3 up = Vector3(0, 1, 0);
real_t cell_size = 0.25;
real_t cell_height = 0.25;
real_t edge_connection_margin = 0.25;
uint32_t navigation_layers = 1;
protected:
static void _bind_methods();
@ -66,6 +68,9 @@ public:
return cell_height;
}
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
void set_edge_connection_margin(float p_edge_connection_margin);
float get_edge_connection_margin() const {
return edge_connection_margin;

View file

@ -70,6 +70,9 @@ void NavigationAgent::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_path_max_distance", "max_speed"), &NavigationAgent::set_path_max_distance);
ClassDB::bind_method(D_METHOD("get_path_max_distance"), &NavigationAgent::get_path_max_distance);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationAgent::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationAgent::get_navigation_layers);
ClassDB::bind_method(D_METHOD("set_target_location", "location"), &NavigationAgent::set_target_location);
ClassDB::bind_method(D_METHOD("get_target_location"), &NavigationAgent::get_target_location);
ClassDB::bind_method(D_METHOD("get_next_location"), &NavigationAgent::get_next_location);
@ -94,6 +97,7 @@ void NavigationAgent::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::REAL, "path_max_distance", PROPERTY_HINT_RANGE, "0.01,100,0.1"), "set_path_max_distance", "get_path_max_distance");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_y"), "set_ignore_y", "get_ignore_y");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_SIGNAL(MethodInfo("path_changed"));
ADD_SIGNAL(MethodInfo("target_reached"));
@ -103,11 +107,7 @@ void NavigationAgent::_bind_methods() {
void NavigationAgent::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
agent_parent = Object::cast_to<Spatial>(get_parent());
set_avoidance_enabled(avoidance_enabled);
case NOTIFICATION_POST_ENTER_TREE: {
// Search the navigation node and set it
{
Navigation *nav = nullptr;
@ -120,20 +120,40 @@ void NavigationAgent::_notification(int p_what) {
p = p->get_parent();
}
}
set_navigation(nav);
}
// need to use POST_ENTER_TREE cause with normal ENTER_TREE not all required Nodes are ready.
// cannot use READY as ready does not get called if Node is readded to SceneTree
set_agent_parent(get_parent());
set_physics_process_internal(true);
} break;
case NOTIFICATION_PARENTED: {
if (is_inside_tree() && (get_parent() != agent_parent)) {
// only react to PARENTED notifications when already inside_tree and parent changed, e.g. users switch nodes around
// PARENTED notification fires also when Node is added in scripts to a parent
// this would spam transforms fails and world fails while Node is outside SceneTree
// when node gets reparented when joining the tree POST_ENTER_TREE takes care of this
set_agent_parent(get_parent());
set_physics_process_internal(true);
}
} break;
case NOTIFICATION_UNPARENTED: {
// if agent has no parent no point in processing it until reparented
set_agent_parent(nullptr);
set_physics_process_internal(false);
} break;
case NOTIFICATION_EXIT_TREE: {
agent_parent = nullptr;
set_agent_parent(nullptr);
set_navigation(nullptr);
set_physics_process_internal(false);
// Want to call ready again when the node enters the tree again. We're not using enter_tree notification because
// the navigation map may not be ready at that time. This fixes issues with taking the agent out of the scene tree.
request_ready();
} break;
case NOTIFICATION_PAUSED: {
if (agent_parent && !agent_parent->can_process()) {
map_before_pause = NavigationServer::get_singleton()->agent_get_map(get_rid());
@ -143,6 +163,7 @@ void NavigationAgent::_notification(int p_what) {
map_before_pause = RID();
}
} break;
case NOTIFICATION_UNPAUSED: {
if (agent_parent && !agent_parent->can_process()) {
map_before_pause = NavigationServer::get_singleton()->agent_get_map(get_rid());
@ -152,26 +173,21 @@ void NavigationAgent::_notification(int p_what) {
map_before_pause = RID();
}
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (agent_parent) {
NavigationServer::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().origin);
if (avoidance_enabled) {
// agent_position on NavigationServer is avoidance only and has nothing to do with pathfinding
// no point in flooding NavigationServer queue with agent position updates that get send to the void if avoidance is not used
NavigationServer::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().origin);
}
_check_distance_to_target();
}
} break;
}
}
NavigationAgent::NavigationAgent() :
agent_parent(nullptr),
navigation(nullptr),
agent(RID()),
avoidance_enabled(false),
target_desired_distance(1.0),
navigation_height_offset(0.0),
path_max_distance(3.0),
velocity_submitted(false),
target_reached(false),
navigation_finished(true) {
NavigationAgent::NavigationAgent() {
agent = NavigationServer::get_singleton()->agent_create();
set_neighbor_dist(50.0);
set_max_neighbors(10);
@ -218,6 +234,38 @@ Node *NavigationAgent::get_navigation_node() const {
return Object::cast_to<Node>(navigation);
}
void NavigationAgent::set_agent_parent(Node *p_agent_parent) {
// remove agent from any avoidance map before changing parent or there will be leftovers on the RVO map
NavigationServer::get_singleton()->agent_set_callback(agent, nullptr, "_avoidance_done");
if (Object::cast_to<Spatial>(p_agent_parent) != nullptr) {
// place agent on navigation map first or else the RVO agent callback creation fails silently later
agent_parent = Object::cast_to<Spatial>(p_agent_parent);
if (navigation == nullptr) {
// no navigation node found in parent nodes, use default navigation map from world resource
NavigationServer::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world()->get_navigation_map());
} else {
NavigationServer::get_singleton()->agent_set_map(get_rid(), navigation->get_rid());
}
// create new avoidance callback if enabled
set_avoidance_enabled(avoidance_enabled);
} else {
agent_parent = nullptr;
NavigationServer::get_singleton()->agent_set_map(get_rid(), RID());
}
}
void NavigationAgent::set_navigation_layers(uint32_t p_layers) {
bool _navigation_layers_changed = navigation_layers != p_layers;
navigation_layers = p_layers;
if (_navigation_layers_changed) {
_request_repath();
}
}
uint32_t NavigationAgent::get_navigation_layers() const {
return navigation_layers;
}
void NavigationAgent::set_target_desired_distance(real_t p_dd) {
target_desired_distance = p_dd;
}
@ -266,10 +314,7 @@ real_t NavigationAgent::get_path_max_distance() {
void NavigationAgent::set_target_location(Vector3 p_location) {
target_location = p_location;
navigation_path.clear();
target_reached = false;
navigation_finished = false;
update_frame_id = 0;
_request_repath();
}
Vector3 NavigationAgent::get_target_location() const {
@ -279,7 +324,7 @@ Vector3 NavigationAgent::get_target_location() const {
Vector3 NavigationAgent::get_next_location() {
update_navigation();
if (navigation_path.size() == 0) {
ERR_FAIL_COND_V(agent_parent == nullptr, Vector3());
ERR_FAIL_COND_V_MSG(agent_parent == nullptr, Vector3(), "The agent has no parent.");
return agent_parent->get_global_transform().origin;
} else {
return navigation_path[nav_path_index] - Vector3(0, navigation_height_offset, 0);
@ -287,7 +332,7 @@ Vector3 NavigationAgent::get_next_location() {
}
real_t NavigationAgent::distance_to_target() const {
ERR_FAIL_COND_V(agent_parent == nullptr, 0.0);
ERR_FAIL_COND_V_MSG(agent_parent == nullptr, 0.0, "The agent has no parent.");
return agent_parent->get_global_transform().origin.distance_to(target_location);
}
@ -343,7 +388,7 @@ void NavigationAgent::update_navigation() {
if (agent_parent == nullptr) {
return;
}
if (navigation == nullptr) {
if (!agent_parent->is_inside_tree()) {
return;
}
if (update_frame_id == Engine::get_singleton()->get_physics_frames()) {
@ -377,7 +422,11 @@ void NavigationAgent::update_navigation() {
}
if (reload_path) {
navigation_path = NavigationServer::get_singleton()->map_get_path(navigation->get_rid(), o, target_location, true);
if (navigation == nullptr) {
navigation_path = NavigationServer::get_singleton()->map_get_path(agent_parent->get_world()->get_navigation_map(), o, target_location, true, navigation_layers);
} else {
navigation_path = NavigationServer::get_singleton()->map_get_path(navigation->get_rid(), o, target_location, true, navigation_layers);
}
navigation_finished = false;
nav_path_index = 0;
emit_signal("path_changed");
@ -403,6 +452,13 @@ void NavigationAgent::update_navigation() {
}
}
void NavigationAgent::_request_repath() {
navigation_path.clear();
target_reached = false;
navigation_finished = false;
update_frame_id = 0;
}
void NavigationAgent::_check_distance_to_target() {
if (!target_reached) {
if (distance_to_target() < target_desired_distance) {

View file

@ -40,36 +40,37 @@ class Navigation;
class NavigationAgent : public Node {
GDCLASS(NavigationAgent, Node);
Spatial *agent_parent;
Navigation *navigation;
Spatial *agent_parent = nullptr;
Navigation *navigation = nullptr;
RID agent;
RID map_before_pause;
bool avoidance_enabled;
bool avoidance_enabled = false;
uint32_t navigation_layers = 1;
real_t target_desired_distance;
real_t radius;
real_t navigation_height_offset;
bool ignore_y;
real_t neighbor_dist;
int max_neighbors;
real_t time_horizon;
real_t max_speed;
real_t target_desired_distance = 1.0;
real_t radius = 0.0;
real_t navigation_height_offset = 0.0;
bool ignore_y = false;
real_t neighbor_dist = 0.0;
int max_neighbors = 0;
real_t time_horizon = 0.0;
real_t max_speed = 0.0;
real_t path_max_distance;
real_t path_max_distance = 3.0;
Vector3 target_location;
Vector<Vector3> navigation_path;
int nav_path_index;
bool velocity_submitted;
int nav_path_index = 0;
bool velocity_submitted = false;
Vector3 prev_safe_velocity;
/// The submitted target velocity
Vector3 target_velocity;
bool target_reached;
bool navigation_finished;
bool target_reached = false;
bool navigation_finished = true;
// No initialized on purpose
uint32_t update_frame_id;
uint32_t update_frame_id = 0;
protected:
static void _bind_methods();
@ -94,6 +95,11 @@ public:
void set_avoidance_enabled(bool p_enabled);
bool get_avoidance_enabled() const;
void set_agent_parent(Node *p_agent_parent);
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
void set_target_desired_distance(real_t p_dd);
real_t get_target_desired_distance() const {
return target_desired_distance;
@ -163,6 +169,7 @@ public:
private:
void update_navigation();
void _request_repath();
void _check_distance_to_target();
};

View file

@ -51,6 +51,8 @@ void NavigationMeshInstance::set_enabled(bool p_enabled) {
} else {
if (navigation) {
NavigationServer::get_singleton()->region_set_map(region, navigation->get_rid());
} else {
NavigationServer::get_singleton()->region_set_map(region, get_world()->get_navigation_map());
}
}
@ -70,6 +72,35 @@ bool NavigationMeshInstance::is_enabled() const {
return enabled;
}
void NavigationMeshInstance::set_navigation_layers(uint32_t p_navigation_layers) {
navigation_layers = p_navigation_layers;
NavigationServer::get_singleton()->region_set_navigation_layers(region, navigation_layers);
}
uint32_t NavigationMeshInstance::get_navigation_layers() const {
return navigation_layers;
}
void NavigationMeshInstance::set_enter_cost(real_t p_enter_cost) {
ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive.");
enter_cost = MAX(p_enter_cost, 0.0);
NavigationServer::get_singleton()->region_set_enter_cost(region, p_enter_cost);
}
real_t NavigationMeshInstance::get_enter_cost() const {
return enter_cost;
}
void NavigationMeshInstance::set_travel_cost(real_t p_travel_cost) {
ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive.");
travel_cost = MAX(p_travel_cost, 0.0);
NavigationServer::get_singleton()->region_set_enter_cost(region, travel_cost);
}
real_t NavigationMeshInstance::get_travel_cost() const {
return travel_cost;
}
RID NavigationMeshInstance::get_region_rid() const {
return region;
}
@ -92,6 +123,11 @@ void NavigationMeshInstance::_notification(int p_what) {
c = c->get_parent_spatial();
}
if (enabled && navigation == nullptr) {
// did not find a valid navigation node parent, fallback to default navigation map on world resource
NavigationServer::get_singleton()->region_set_map(region, get_world()->get_navigation_map());
}
if (navmesh.is_valid() && get_tree()->is_debugging_navigation_hint()) {
MeshInstance *dm = memnew(MeshInstance);
dm->set_mesh(navmesh->get_debug_mesh());
@ -165,7 +201,7 @@ Ref<NavigationMesh> NavigationMeshInstance::get_navigation_mesh() const {
}
struct BakeThreadsArgs {
NavigationMeshInstance *nav_region;
NavigationMeshInstance *nav_region = nullptr;
};
void _bake_navigation_mesh(void *p_user_data) {
@ -216,16 +252,8 @@ String NavigationMeshInstance::get_configuration_warning() const {
if (!navmesh.is_valid()) {
return TTR("A NavigationMesh resource must be set or created for this node to work.");
}
const Spatial *c = this;
while (c) {
if (Object::cast_to<Navigation>(c)) {
return String();
}
c = Object::cast_to<Spatial>(c->get_parent());
}
return TTR("NavigationMeshInstance must be a child or grandchild to a Navigation node. It only provides navigation data.");
return String();
}
void NavigationMeshInstance::_bind_methods() {
@ -235,13 +263,25 @@ void NavigationMeshInstance::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationMeshInstance::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationMeshInstance::is_enabled);
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationMeshInstance::set_navigation_layers);
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationMeshInstance::get_navigation_layers);
ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationMeshInstance::get_region_rid);
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationMeshInstance::set_enter_cost);
ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationMeshInstance::get_enter_cost);
ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationMeshInstance::set_travel_cost);
ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationMeshInstance::get_travel_cost);
ClassDB::bind_method(D_METHOD("bake_navigation_mesh", "on_thread"), &NavigationMeshInstance::bake_navigation_mesh, DEFVAL(true));
ClassDB::bind_method(D_METHOD("_bake_finished", "nav_mesh"), &NavigationMeshInstance::_bake_finished);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navmesh", PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh"), "set_navigation_mesh", "get_navigation_mesh");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "enter_cost"), "set_enter_cost", "get_enter_cost");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "travel_cost"), "set_travel_cost", "get_travel_cost");
ADD_SIGNAL(MethodInfo("navigation_mesh_changed"));
ADD_SIGNAL(MethodInfo("bake_finished"));
@ -255,6 +295,8 @@ void NavigationMeshInstance::_changed_callback(Object *p_changed, const char *p_
NavigationMeshInstance::NavigationMeshInstance() {
set_notify_transform(true);
region = NavigationServer::get_singleton()->region_create();
NavigationServer::get_singleton()->region_set_enter_cost(region, get_enter_cost());
NavigationServer::get_singleton()->region_set_travel_cost(region, get_travel_cost());
enabled = true;
}

View file

@ -40,10 +40,14 @@ class Navigation;
class NavigationMeshInstance : public Spatial {
GDCLASS(NavigationMeshInstance, Spatial);
bool enabled;
bool enabled = true;
RID region;
Ref<NavigationMesh> navmesh;
real_t enter_cost = 0.0;
real_t travel_cost = 1.0;
uint32_t navigation_layers = 1;
Navigation *navigation = nullptr;
Node *debug_view = nullptr;
Thread bake_thread;
@ -57,8 +61,17 @@ public:
void set_enabled(bool p_enabled);
bool is_enabled() const;
void set_navigation_layers(uint32_t p_navigation_layers);
uint32_t get_navigation_layers() const;
RID get_region_rid() const;
void set_enter_cost(real_t p_enter_cost);
real_t get_enter_cost() const;
void set_travel_cost(real_t p_travel_cost);
real_t get_travel_cost() const;
void set_navigation_mesh(const Ref<NavigationMesh> &p_navmesh);
Ref<NavigationMesh> get_navigation_mesh() const;

View file

@ -796,6 +796,11 @@ void register_scene_types() {
GLOBAL_DEF(vformat("%s/layer_%d", PNAME("layer_names/2d_physics"), i + 1), "");
GLOBAL_DEF(vformat("%s/layer_%d", PNAME("layer_names/3d_physics"), i + 1), "");
}
for (int i = 0; i < 32; i++) {
GLOBAL_DEF(vformat("%s/layer_%d", PNAME("layer_names/2d_navigation"), i + 1), "");
GLOBAL_DEF(vformat("%s/layer_%d", PNAME("layer_names/3d_navigation"), i + 1), "");
};
}
void initialize_theme() {

View file

@ -31,6 +31,7 @@
#include "navigation_mesh.h"
void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) {
ERR_FAIL_COND(p_mesh.is_null());
vertices = PoolVector<Vector3>();
clear_polygons();
@ -40,6 +41,7 @@ void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) {
continue;
}
Array arr = p_mesh->surface_get_arrays(i);
ERR_CONTINUE(arr.size() != Mesh::ARRAY_MAX);
PoolVector<Vector3> varr = arr[Mesh::ARRAY_VERTEX];
PoolVector<int> iarr = arr[Mesh::ARRAY_INDEX];
if (varr.size() == 0 || iarr.size() == 0) {
@ -299,13 +301,16 @@ void NavigationMesh::add_polygon(const Vector<int> &p_polygon) {
polygons.push_back(polygon);
_change_notify();
}
int NavigationMesh::get_polygon_count() const {
return polygons.size();
}
Vector<int> NavigationMesh::get_polygon(int p_idx) {
ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector<int>());
return polygons[p_idx].indices;
}
void NavigationMesh::clear_polygons() {
polygons.clear();
}
@ -579,27 +584,4 @@ bool NavigationMesh::_get(const StringName &p_name, Variant &r_ret) const {
}
#endif // DISABLE_DEPRECATED
NavigationMesh::NavigationMesh() {
cell_size = 0.25f;
cell_height = 0.25f;
agent_height = 1.5f;
agent_radius = 0.5f;
agent_max_climb = 0.25f;
agent_max_slope = 45.0f;
region_min_size = 2.0f;
region_merge_size = 20.0f;
edge_max_length = 12.0f;
edge_max_error = 1.3f;
verts_per_poly = 6.0f;
detail_sample_distance = 6.0f;
detail_sample_max_error = 5.0f;
partition_type = SAMPLE_PARTITION_WATERSHED;
parsed_geometry_type = PARSED_GEOMETRY_MESH_INSTANCES;
collision_mask = 0xFFFFFFFF;
source_geometry_mode = SOURCE_GEOMETRY_NAVMESH_CHILDREN;
source_group_name = "navmesh";
filter_low_hanging_obstacles = false;
filter_ledge_spans = false;
filter_walkable_low_height_spans = false;
}
NavigationMesh::NavigationMesh() {}

View file

@ -90,30 +90,30 @@ public:
};
protected:
float cell_size;
float cell_height;
float agent_height;
float agent_radius;
float agent_max_climb;
float agent_max_slope;
float region_min_size;
float region_merge_size;
float edge_max_length;
float edge_max_error;
float verts_per_poly;
float detail_sample_distance;
float detail_sample_max_error;
float cell_size = 0.25f;
float cell_height = 0.25f;
float agent_height = 1.5f;
float agent_radius = 0.5f;
float agent_max_climb = 0.25f;
float agent_max_slope = 45.0f;
float region_min_size = 2.0f;
float region_merge_size = 20.0f;
float edge_max_length = 12.0f;
float edge_max_error = 1.3f;
float verts_per_poly = 6.0f;
float detail_sample_distance = 6.0f;
float detail_sample_max_error = 1.0f;
SamplePartitionType partition_type;
ParsedGeometryType parsed_geometry_type;
uint32_t collision_mask;
SamplePartitionType partition_type = SAMPLE_PARTITION_WATERSHED;
ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_MESH_INSTANCES;
uint32_t collision_mask = 0xFFFFFFFF;
SourceGeometryMode source_geometry_mode;
StringName source_group_name;
SourceGeometryMode source_geometry_mode = SOURCE_GEOMETRY_NAVMESH_CHILDREN;
StringName source_group_name = "navmesh";
bool filter_low_hanging_obstacles;
bool filter_ledge_spans;
bool filter_walkable_low_height_spans;
bool filter_low_hanging_obstacles = false;
bool filter_ledge_spans = false;
bool filter_walkable_low_height_spans = false;
public:
// Recast settings

View file

@ -257,6 +257,7 @@ void World::_update(uint64_t p_frame) {
RID World::get_space() const {
return space;
}
RID World::get_scenario() const {
return scenario;
}
@ -342,9 +343,12 @@ World::World() {
PhysicsServer::get_singleton()->area_set_param(space, PhysicsServer::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF("physics/3d/default_angular_damp", 0.1));
ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/default_angular_damp", PropertyInfo(Variant::REAL, "physics/3d/default_angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"));
// Create default navigation map
navigation_map = NavigationServer::get_singleton()->map_create();
NavigationServer::get_singleton()->map_set_active(navigation_map, true);
NavigationServer::get_singleton()->map_set_up(navigation_map, GLOBAL_DEF("navigation/3d/default_map_up", Vector3(0, 1, 0)));
NavigationServer::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/3d/default_cell_size", 0.25));
NavigationServer::get_singleton()->map_set_cell_height(navigation_map, GLOBAL_DEF("navigation/3d/default_cell_height", 0.25));
NavigationServer::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/3d/default_edge_connection_margin", 0.25));
#ifdef _3D_DISABLED

View file

@ -327,7 +327,7 @@ RID World2D::get_space() {
return space;
}
RID World2D::get_navigation_map() {
RID World2D::get_navigation_map() const {
return navigation_map;
}
@ -367,11 +367,12 @@ World2D::World2D() {
Physics2DServer::get_singleton()->area_set_param(space, Physics2DServer::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF("physics/2d/default_angular_damp", 1.0));
ProjectSettings::get_singleton()->set_custom_property_info("physics/2d/default_angular_damp", PropertyInfo(Variant::REAL, "physics/2d/default_angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"));
// Create and configure the navigation_map to be more friendly with pixels than meters.
// Create default navigation map
navigation_map = Navigation2DServer::get_singleton()->map_create();
Navigation2DServer::get_singleton()->map_set_active(navigation_map, true);
Navigation2DServer::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/2d/default_cell_size", 1));
Navigation2DServer::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/2d/default_edge_connection_margin", 1));
Navigation2DServer::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/2d/default_cell_size", 1.0));
Navigation2DServer::get_singleton()->map_set_cell_height(navigation_map, GLOBAL_DEF("navigation/2d/default_cell_height", 1.0));
Navigation2DServer::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/2d/default_edge_connection_margin", 1.0));
indexer = memnew(SpatialIndexer2D);
}

View file

@ -66,7 +66,7 @@ protected:
public:
RID get_canvas();
RID get_space();
RID get_navigation_map();
RID get_navigation_map() const;
Physics2DDirectSpaceState *get_direct_space_state();

View file

@ -34,10 +34,6 @@
#include "core/math/transform_2d.h"
#include "servers/navigation_server.h"
/**
@author AndreaCatania
*/
Navigation2DServer *Navigation2DServer::singleton = nullptr;
#define FORWARD_0_C(FUNC_NAME) \
@ -69,6 +65,18 @@ Navigation2DServer *Navigation2DServer::singleton = nullptr;
return CONV_R(NavigationServer::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \
}
#define FORWARD_3_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, CONV_0, CONV_1, CONV_2) \
Navigation2DServer::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2) \
const { \
return CONV_R(NavigationServer::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2))); \
}
#define FORWARD_3_C(FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, CONV_0, CONV_1, CONV_2) \
Navigation2DServer::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2) \
const { \
return NavigationServer::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2)); \
}
#define FORWARD_4_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, CONV_0, CONV_1, CONV_2, CONV_3) \
Navigation2DServer::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3) \
const { \
@ -81,24 +89,46 @@ Navigation2DServer *Navigation2DServer::singleton = nullptr;
return NavigationServer::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3)); \
}
#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) \
Navigation2DServer::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(NavigationServer::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \
}
#define FORWARD_5_C(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) \
Navigation2DServer::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \
const { \
return NavigationServer::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4)); \
}
RID rid_to_rid(const RID d) {
return d;
}
bool bool_to_bool(const bool d) {
return d;
}
int int_to_int(const int d) {
return d;
}
uint32_t uint32_to_uint32(const uint32_t d) {
return d;
}
real_t real_to_real(const real_t d) {
return d;
}
Vector3 v2_to_v3(const Vector2 d) {
return Vector3(d.x, 0.0, d.y);
}
Vector2 v3_to_v2(const Vector3 &d) {
return Vector2(d.x, d.z);
}
Vector<Vector2> vector_v3_to_v2(const Vector<Vector3> &d) {
Vector<Vector2> nd;
nd.resize(d.size());
@ -107,21 +137,27 @@ Vector<Vector2> vector_v3_to_v2(const Vector<Vector3> &d) {
}
return nd;
}
Transform 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.rotate(Vector3(0, -1, 0), d.get_rotation());
b.scale(v2_to_v3(d.get_scale()));
return Transform(b, o);
}
Object *obj_to_obj(Object *d) {
return d;
}
StringName sn_to_sn(StringName &d) {
return d;
}
Variant var_to_var(Variant &d) {
return d;
}
Ref<NavigationMesh> poly_to_mesh(Ref<NavigationPolygon> d) {
if (d.is_valid()) {
return d->get_mesh();
@ -130,15 +166,21 @@ Ref<NavigationMesh> poly_to_mesh(Ref<NavigationPolygon> d) {
}
}
void Navigation2DServer::_emit_map_changed(RID p_map) {
emit_signal("map_changed", p_map);
}
void Navigation2DServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("map_create"), &Navigation2DServer::map_create);
ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &Navigation2DServer::map_set_active);
ClassDB::bind_method(D_METHOD("map_is_active", "nap"), &Navigation2DServer::map_is_active);
ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &Navigation2DServer::map_set_cell_size);
ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &Navigation2DServer::map_get_cell_size);
ClassDB::bind_method(D_METHOD("map_set_cell_height", "map", "cell_height"), &Navigation2DServer::map_set_cell_size);
ClassDB::bind_method(D_METHOD("map_get_cell_height", "map"), &Navigation2DServer::map_get_cell_size);
ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &Navigation2DServer::map_set_edge_connection_margin);
ClassDB::bind_method(D_METHOD("map_get_edge_connection_margin", "map"), &Navigation2DServer::map_get_edge_connection_margin);
ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize"), &Navigation2DServer::map_get_path);
ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize", "navigation_layers"), &Navigation2DServer::map_get_path, DEFVAL(1));
ClassDB::bind_method(D_METHOD("map_get_closest_point", "map", "to_point"), &Navigation2DServer::map_get_closest_point);
ClassDB::bind_method(D_METHOD("map_get_closest_point_owner", "map", "to_point"), &Navigation2DServer::map_get_closest_point_owner);
@ -146,10 +188,19 @@ void Navigation2DServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &Navigation2DServer::map_get_agents);
ClassDB::bind_method(D_METHOD("region_create"), &Navigation2DServer::region_create);
ClassDB::bind_method(D_METHOD("region_set_enter_cost", "region", "enter_cost"), &Navigation2DServer::region_set_enter_cost);
ClassDB::bind_method(D_METHOD("region_get_enter_cost", "region"), &Navigation2DServer::region_get_enter_cost);
ClassDB::bind_method(D_METHOD("region_set_travel_cost", "region", "travel_cost"), &Navigation2DServer::region_set_travel_cost);
ClassDB::bind_method(D_METHOD("region_get_travel_cost", "region"), &Navigation2DServer::region_get_travel_cost);
ClassDB::bind_method(D_METHOD("region_set_map", "region", "map"), &Navigation2DServer::region_set_map);
ClassDB::bind_method(D_METHOD("region_get_map", "region"), &Navigation2DServer::region_get_map);
ClassDB::bind_method(D_METHOD("region_set_navigation_layers", "region", "navigation_layers"), &Navigation2DServer::region_set_navigation_layers);
ClassDB::bind_method(D_METHOD("region_get_navigation_layers", "region"), &Navigation2DServer::region_get_navigation_layers);
ClassDB::bind_method(D_METHOD("region_set_transform", "region", "transform"), &Navigation2DServer::region_set_transform);
ClassDB::bind_method(D_METHOD("region_set_navpoly", "region", "nav_poly"), &Navigation2DServer::region_set_navpoly);
ClassDB::bind_method(D_METHOD("region_get_connections_count", "region"), &Navigation2DServer::region_get_connections_count);
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &Navigation2DServer::region_get_connection_pathway_start);
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &Navigation2DServer::region_get_connection_pathway_end);
ClassDB::bind_method(D_METHOD("agent_create"), &Navigation2DServer::agent_create);
ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &Navigation2DServer::agent_set_map);
@ -166,10 +217,15 @@ void Navigation2DServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("agent_set_callback", "agent", "receiver", "method", "userdata"), &Navigation2DServer::agent_set_callback, DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("free_rid", "rid"), &Navigation2DServer::free);
ClassDB::bind_method(D_METHOD("_emit_map_changed"), &Navigation2DServer::_emit_map_changed);
ADD_SIGNAL(MethodInfo("map_changed", PropertyInfo(Variant::_RID, "map")));
}
Navigation2DServer::Navigation2DServer() {
singleton = this;
ERR_FAIL_COND_MSG(!NavigationServer::get_singleton(), "The NavigationServer singleton should be initialized before the Navigation2DServer one.");
NavigationServer::get_singleton_mut()->connect("map_changed", this, "_emit_map_changed");
}
Navigation2DServer::~Navigation2DServer() {
@ -193,23 +249,37 @@ bool FORWARD_1_C(map_is_active, RID, p_map, rid_to_rid);
void FORWARD_2_C(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_C(map_set_cell_height, RID, p_map, real_t, p_cell_height, rid_to_rid, real_to_real);
real_t FORWARD_1_C(map_get_cell_height, RID, p_map, rid_to_rid);
void FORWARD_2_C(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);
Vector<Vector2> FORWARD_4_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool);
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_navigation_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_C(region_create);
void FORWARD_2_C(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid);
void FORWARD_2_C(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_C(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_C(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid);
void FORWARD_2_C(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_C(region_set_transform, RID, p_region, Transform2D, p_transform, rid_to_rid, trf2_to_trf3);
void Navigation2DServer::region_set_navpoly(RID p_region, Ref<NavigationPolygon> p_nav_mesh) const {
NavigationServer::get_singleton()->region_set_navmesh(p_region, poly_to_mesh(p_nav_mesh));
}
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 Navigation2DServer::agent_create() const {
RID agent = NavigationServer::get_singleton()->agent_create();
NavigationServer::get_singleton()->agent_set_ignore_y(agent, true);

View file

@ -28,10 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
/**
@author AndreaCatania
*/
#ifndef NAVIGATION_2D_SERVER_H
#define NAVIGATION_2D_SERVER_H
@ -45,6 +41,8 @@ class Navigation2DServer : public Object {
static Navigation2DServer *singleton;
void _emit_map_changed(RID p_map);
protected:
static void _bind_methods();
@ -70,6 +68,10 @@ public:
/// Returns the map cell size.
virtual real_t map_get_cell_size(RID p_map) const;
/// Set the map cell height used to weld the navigation mesh polygons.
virtual void map_set_cell_height(RID p_map, real_t p_cell_height) const;
virtual real_t map_get_cell_height(RID p_map) const;
/// 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) const;
@ -77,7 +79,7 @@ public:
virtual real_t map_get_edge_connection_margin(RID p_map) const;
/// 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) 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;
virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const;
virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const;
@ -88,16 +90,33 @@ public:
/// Creates a new region.
virtual RID region_create() const;
/// Set the enter_cost of a region
virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) const;
virtual real_t region_get_enter_cost(RID p_region) const;
/// Set the travel_cost of a region
virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) const;
virtual real_t region_get_travel_cost(RID p_region) const;
/// Set the map of this region.
virtual void region_set_map(RID p_region, RID p_map) const;
virtual RID region_get_map(RID p_region) const;
/// Set the region's layers
virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) const;
virtual uint32_t region_get_navigation_layers(RID p_region) const;
/// Set the global transformation of this region.
virtual void region_set_transform(RID p_region, Transform2D p_transform) const;
/// Set the navigation poly of this region.
virtual void region_set_navpoly(RID p_region, Ref<NavigationPolygon> p_nav_mesh) const;
/// Get a list of a region's connection to other regions.
virtual int region_get_connections_count(RID p_region) const;
virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const;
virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const;
/// Creates the agent.
virtual RID agent_create() const;
@ -165,4 +184,4 @@ public:
virtual ~Navigation2DServer();
};
#endif
#endif // NAVIGATION_2D_SERVER_H

View file

@ -28,10 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
/**
@author AndreaCatania
*/
#include "navigation_server.h"
NavigationServer *NavigationServer::singleton = nullptr;
@ -48,7 +44,7 @@ void NavigationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("map_get_cell_height", "map"), &NavigationServer::map_get_cell_height);
ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &NavigationServer::map_set_edge_connection_margin);
ClassDB::bind_method(D_METHOD("map_get_edge_connection_margin", "map"), &NavigationServer::map_get_edge_connection_margin);
ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize"), &NavigationServer::map_get_path);
ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize", "navigation_layers"), &NavigationServer::map_get_path, DEFVAL(1));
ClassDB::bind_method(D_METHOD("map_get_closest_point_to_segment", "map", "start", "end", "use_collision"), &NavigationServer::map_get_closest_point_to_segment, DEFVAL(false));
ClassDB::bind_method(D_METHOD("map_get_closest_point", "map", "to_point"), &NavigationServer::map_get_closest_point);
ClassDB::bind_method(D_METHOD("map_get_closest_point_normal", "map", "to_point"), &NavigationServer::map_get_closest_point_normal);
@ -58,11 +54,22 @@ void NavigationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer::map_get_agents);
ClassDB::bind_method(D_METHOD("region_create"), &NavigationServer::region_create);
ClassDB::bind_method(D_METHOD("region_set_enter_cost", "region", "enter_cost"), &NavigationServer::region_set_enter_cost);
ClassDB::bind_method(D_METHOD("region_get_enter_cost", "region"), &NavigationServer::region_get_enter_cost);
ClassDB::bind_method(D_METHOD("region_set_travel_cost", "region", "travel_cost"), &NavigationServer::region_set_travel_cost);
ClassDB::bind_method(D_METHOD("region_get_travel_cost", "region"), &NavigationServer::region_get_travel_cost);
ClassDB::bind_method(D_METHOD("region_set_map", "region", "map"), &NavigationServer::region_set_map);
ClassDB::bind_method(D_METHOD("region_get_map", "region"), &NavigationServer::region_get_map);
ClassDB::bind_method(D_METHOD("region_set_navigation_layers", "region", "navigation_layers"), &NavigationServer::region_set_navigation_layers);
ClassDB::bind_method(D_METHOD("region_get_navigation_layers", "region"), &NavigationServer::region_get_navigation_layers);
ClassDB::bind_method(D_METHOD("region_set_transform", "region", "transform"), &NavigationServer::region_set_transform);
ClassDB::bind_method(D_METHOD("region_set_navmesh", "region", "nav_mesh"), &NavigationServer::region_set_navmesh);
ClassDB::bind_method(D_METHOD("region_bake_navmesh", "mesh", "node"), &NavigationServer::region_bake_navmesh);
ClassDB::bind_method(D_METHOD("region_get_connections_count", "region"), &NavigationServer::region_get_connections_count);
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer::region_get_connection_pathway_start);
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer::region_get_connection_pathway_end);
ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer::agent_create);
ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer::agent_set_map);
@ -82,6 +89,8 @@ void NavigationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_active", "active"), &NavigationServer::set_active);
ClassDB::bind_method(D_METHOD("process", "delta_time"), &NavigationServer::process);
ADD_SIGNAL(MethodInfo("map_changed", PropertyInfo(Variant::_RID, "map")));
}
const NavigationServer *NavigationServer::get_singleton() {

View file

@ -28,10 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
/**
@author AndreaCatania
*/
#ifndef NAVIGATION_SERVER_H
#define NAVIGATION_SERVER_H
@ -94,7 +90,7 @@ public:
virtual real_t map_get_edge_connection_margin(RID p_map) const = 0;
/// Returns the navigation path to reach the destination from the origin.
virtual Vector<Vector3> map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize) const = 0;
virtual Vector<Vector3> map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const = 0;
virtual Vector3 map_get_closest_point_to_segment(RID p_map, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision = false) const = 0;
virtual Vector3 map_get_closest_point(RID p_map, const Vector3 &p_point) const = 0;
@ -107,19 +103,36 @@ public:
/// Creates a new region.
virtual RID region_create() const = 0;
/// Set the enter_cost of a region
virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) const = 0;
virtual real_t region_get_enter_cost(RID p_region) const = 0;
/// Set the travel_cost of a region
virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) const = 0;
virtual real_t region_get_travel_cost(RID p_region) const = 0;
/// Set the map of this region.
virtual void region_set_map(RID p_region, RID p_map) const = 0;
virtual RID region_get_map(RID p_region) const = 0;
/// Set the region's layers
virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) const = 0;
virtual uint32_t region_get_navigation_layers(RID p_region) const = 0;
/// Set the global transformation of this region.
virtual void region_set_transform(RID p_region, Transform p_transform) const = 0;
/// Set the navigation mesh of this region.
virtual void region_set_navmesh(RID p_region, Ref<NavigationMesh> p_nav_mesh) const = 0;
/// Bake the navigation mesh
/// Bake the navigation mesh.
virtual void region_bake_navmesh(Ref<NavigationMesh> r_mesh, Node *p_node) const = 0;
/// Get a list of a region's connection to other regions.
virtual int region_get_connections_count(RID p_region) const = 0;
virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const = 0;
virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const = 0;
/// Creates the agent.
virtual RID agent_create() const = 0;
@ -207,4 +220,4 @@ public:
static NavigationServer *new_default_server();
};
#endif
#endif // NAVIGATION_SERVER_H