diff --git a/core/global_constants.cpp b/core/global_constants.cpp index 56d6ac92afe0..ab0730131298 100644 --- a/core/global_constants.cpp +++ b/core/global_constants.cpp @@ -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); diff --git a/core/object.h b/core/object.h index 6fbb349f3c83..3e025771b3aa 100644 --- a/core/object.h +++ b/core/object.h @@ -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," diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index f95af77cf906..01286332d77d 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -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]. - + 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. @@ -1443,40 +1443,46 @@ Hints that an integer property is a bitmask using the optionally named 2D physics layers. - + + Hints that an integer property is a bitmask using the optionally named 2D navigation layers. + + Hints that an integer property is a bitmask using the optionally named 3D render layers. - + Hints that an integer property is a bitmask using the optionally named 3D physics layers. - + + Hints that an integer property is a bitmask using the optionally named 2D navigation layers. + + 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]. - + Hints that a string property is a path to a directory. Editing it will show a file dialog for picking the path. - + 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]. - + 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. - + 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. - + Hints that a string property is text with line breaks. Editing it will show a text input field where line breaks can be typed. - + 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. - + Hints that a color property should be edited without changing its alpha component, i.e. only R, G and B channels are edited. - + Hints that an image is compressed using lossy compression. - + Hints that an image is compressed using lossless compression. diff --git a/doc/classes/Navigation.xml b/doc/classes/Navigation.xml index 7ebe799365d3..36a3757d8e06 100644 --- a/doc/classes/Navigation.xml +++ b/doc/classes/Navigation.xml @@ -43,7 +43,7 @@ - Returns the object's [RID]. + Returns the [RID] of the navigation map on the [NavigationServer]. @@ -57,19 +57,30 @@ - + The cell height to use for fields. - + The XZ plane cell size to use for fields. - + This value is used to detect the near edges to connect compatible regions. + + A bitfield determining all navigation map layers the navigation can use on a [method Navigation.get_simple_path] path query. + Defines which direction is up. By default, this is [code](0, 1, 0)[/code], which is the world's "up" direction. + + + + + Emitted when a navigation map is updated, when a region moves or is modified. + + + diff --git a/doc/classes/Navigation2D.xml b/doc/classes/Navigation2D.xml index a0d5d6e40381..c78ffcf1f4e8 100644 --- a/doc/classes/Navigation2D.xml +++ b/doc/classes/Navigation2D.xml @@ -47,6 +47,9 @@ This value is used to detect the near edges to connect compatible regions. + + A bitfield determining all navigation map layers the navigation can use on a [method Navigation2D.get_simple_path] path query. + diff --git a/doc/classes/Navigation2DServer.xml b/doc/classes/Navigation2DServer.xml index 1669d6f3e79e..77116012ec76 100644 --- a/doc/classes/Navigation2DServer.xml +++ b/doc/classes/Navigation2DServer.xml @@ -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]. + + + + + Returns the map cell height. [b]Note:[/b] Currently not implemented. + + @@ -174,8 +182,9 @@ + - 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. @@ -200,6 +209,14 @@ Sets the map active. + + + + + + Set the map cell height used to weld the navigation mesh polygons. [b]Note:[/b] Currently not implemented. + + @@ -222,6 +239,36 @@ Creates a new region. + + + + + + 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]. + + + + + + + + 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]. + + + + + + + Returns how many connections this [code]region[/code] has with other regions in the map. + + + + + + + Returns the [code]enter_cost[/code] of this [code]region[/code]. + + @@ -229,6 +276,28 @@ Returns the navigation map [RID] the requested [code]region[/code] is currently assigned to. + + + + + Returns the region's navigation layers. + + + + + + + Returns the [code]travel_cost[/code] of this [code]region[/code]. + + + + + + + + Sets the [code]enter_cost[/code] for this [code]region[/code]. + + @@ -237,6 +306,14 @@ Sets the map for the region. + + + + + + Set the region's navigation layers. This allows selecting regions from a path request (when using [method Navigation2DServer.map_get_path]). + + @@ -253,7 +330,23 @@ Sets the global transformation for the region. + + + + + + Sets the [code]travel_cost[/code] for this [code]region[/code]. + + + + + + + Emitted when a navigation map is updated, when a region moves or is modified. + + + diff --git a/doc/classes/NavigationAgent.xml b/doc/classes/NavigationAgent.xml index e4c849ee189b..ae65dfd6f96b 100644 --- a/doc/classes/NavigationAgent.xml +++ b/doc/classes/NavigationAgent.xml @@ -4,7 +4,7 @@ 3D agent used in navigation for collision avoidance. - 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. @@ -114,6 +114,9 @@ The maximum speed that an agent can move. + + 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. + The distance to search for other agents. diff --git a/doc/classes/NavigationAgent2D.xml b/doc/classes/NavigationAgent2D.xml index cbdc09fd12f3..1fcdacd35c57 100644 --- a/doc/classes/NavigationAgent2D.xml +++ b/doc/classes/NavigationAgent2D.xml @@ -4,7 +4,7 @@ 2D agent used in navigation for collision avoidance. - 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. @@ -108,6 +108,9 @@ The maximum speed that an agent can move. + + 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. + The distance to search for other agents. diff --git a/doc/classes/NavigationMesh.xml b/doc/classes/NavigationMesh.xml index 4107254f65d4..160060f70d22 100644 --- a/doc/classes/NavigationMesh.xml +++ b/doc/classes/NavigationMesh.xml @@ -98,7 +98,7 @@ The sampling distance to use when generating the detail mesh, in cell unit. - + The maximum distance the detail mesh surface should deviate from heightfield, in cell unit. diff --git a/doc/classes/NavigationMeshInstance.xml b/doc/classes/NavigationMeshInstance.xml index b74ea4b48527..25ee04fc7a6e 100644 --- a/doc/classes/NavigationMeshInstance.xml +++ b/doc/classes/NavigationMeshInstance.xml @@ -4,7 +4,13 @@ An instance of a [NavigationMesh]. - 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. @@ -27,9 +33,18 @@ Determines if the [NavigationMeshInstance] is enabled or disabled. + + 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. + + + 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. + The [NavigationMesh] resource to use. + + When pathfinding moves inside this regions navmesh the traveled distances are multiplied with [code]travel_cost[/code] for determining the shortest path. + diff --git a/doc/classes/NavigationPolygonInstance.xml b/doc/classes/NavigationPolygonInstance.xml index 667074c2950c..efe281d5dc5b 100644 --- a/doc/classes/NavigationPolygonInstance.xml +++ b/doc/classes/NavigationPolygonInstance.xml @@ -1,8 +1,16 @@ + A region of the 2D navigation map. + 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. @@ -16,8 +24,19 @@ + Determines if the [NavigationPolygonInstance] is enabled or disabled. + + + 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. + + + 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. + The [NavigationPolygon] resource to use. + + + When pathfinding moves inside this regions navmesh the traveled distances are multiplied with [code]travel_cost[/code] for determining the shortest path. diff --git a/doc/classes/NavigationServer.xml b/doc/classes/NavigationServer.xml index 7de76134ef15..032e8e4876a5 100644 --- a/doc/classes/NavigationServer.xml +++ b/doc/classes/NavigationServer.xml @@ -199,8 +199,9 @@ + - 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. @@ -287,6 +288,36 @@ Creates a new region. + + + + + + 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]. + + + + + + + + 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]. + + + + + + + Returns how many connections this [code]region[/code] has with other regions in the map. + + + + + + + Returns the [code]enter_cost[/code] of this [code]region[/code]. + + @@ -294,6 +325,28 @@ Returns the navigation map [RID] the requested [code]region[/code] is currently assigned to. + + + + + Returns the region's navigation layers. + + + + + + + Returns the [code]travel_cost[/code] of this [code]region[/code]. + + + + + + + + Sets the [code]enter_cost[/code] for this [code]region[/code]. + + @@ -302,6 +355,14 @@ Sets the map for the region. + + + + + + Set the region's navigation layers. This allows selecting regions from a path request (when using [method NavigationServer.map_get_path]). + + @@ -318,6 +379,14 @@ Sets the global transformation for the region. + + + + + + Sets the [code]travel_cost[/code] for this [code]region[/code]. + + @@ -326,6 +395,14 @@ + + + + + Emitted when a navigation map is updated, when a region moves or is modified. + + + diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 4530c6c58679..4995c6ab2b93 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -638,6 +638,102 @@ Default delay for touch events. This only affects iOS devices. + + Optional name for the 2D navigation layer 1. If left empty, the layer will display as "Layer 1". + + + Optional name for the 2D navigation layer 10. If left empty, the layer will display as "Layer 10". + + + Optional name for the 2D navigation layer 11. If left empty, the layer will display as "Layer 11". + + + Optional name for the 2D navigation layer 12. If left empty, the layer will display as "Layer 12". + + + Optional name for the 2D navigation layer 13. If left empty, the layer will display as "Layer 13". + + + Optional name for the 2D navigation layer 14. If left empty, the layer will display as "Layer 14". + + + Optional name for the 2D navigation layer 15. If left empty, the layer will display as "Layer 15". + + + Optional name for the 2D navigation layer 16. If left empty, the layer will display as "Layer 16". + + + Optional name for the 2D navigation layer 17. If left empty, the layer will display as "Layer 17". + + + Optional name for the 2D navigation layer 18. If left empty, the layer will display as "Layer 18". + + + Optional name for the 2D navigation layer 19. If left empty, the layer will display as "Layer 19". + + + Optional name for the 2D navigation layer 2. If left empty, the layer will display as "Layer 2". + + + Optional name for the 2D navigation layer 20. If left empty, the layer will display as "Layer 20". + + + Optional name for the 2D navigation layer 21. If left empty, the layer will display as "Layer 21". + + + Optional name for the 2D navigation layer 22. If left empty, the layer will display as "Layer 22". + + + Optional name for the 2D navigation layer 23. If left empty, the layer will display as "Layer 23". + + + Optional name for the 2D navigation layer 24. If left empty, the layer will display as "Layer 24". + + + Optional name for the 2D navigation layer 25. If left empty, the layer will display as "Layer 25". + + + Optional name for the 2D navigation layer 26. If left empty, the layer will display as "Layer 26". + + + Optional name for the 2D navigation layer 27. If left empty, the layer will display as "Layer 27". + + + Optional name for the 2D navigation layer 28. If left empty, the layer will display as "Layer 28". + + + Optional name for the 2D navigation layer 29. If left empty, the layer will display as "Layer 29". + + + Optional name for the 2D navigation layer 3. If left empty, the layer will display as "Layer 3". + + + Optional name for the 2D navigation layer 30. If left empty, the layer will display as "Layer 30". + + + Optional name for the 2D navigation layer 31. If left empty, the layer will display as "Layer 31". + + + Optional name for the 2D navigation layer 32. If left empty, the layer will display as "Layer 32". + + + Optional name for the 2D navigation layer 4. If left empty, the layer will display as "Layer 4". + + + Optional name for the 2D navigation layer 5. If left empty, the layer will display as "Layer 5". + + + Optional name for the 2D navigation layer 6. If left empty, the layer will display as "Layer 6". + + + Optional name for the 2D navigation layer 7. If left empty, the layer will display as "Layer 7". + + + Optional name for the 2D navigation layer 8. If left empty, the layer will display as "Layer 8". + + + Optional name for the 2D navigation layer 9. If left empty, the layer will display as "Layer 9". + Optional name for the 2D physics layer 1. @@ -794,6 +890,102 @@ Optional name for the 2D render layer 9. + + Optional name for the 3D navigation layer 1. If left empty, the layer will display as "Layer 1". + + + Optional name for the 3D navigation layer 10. If left empty, the layer will display as "Layer 10". + + + Optional name for the 3D navigation layer 11. If left empty, the layer will display as "Layer 11". + + + Optional name for the 3D navigation layer 12. If left empty, the layer will display as "Layer 12". + + + Optional name for the 3D navigation layer 13. If left empty, the layer will display as "Layer 13". + + + Optional name for the 3D navigation layer 14. If left empty, the layer will display as "Layer 14". + + + Optional name for the 3D navigation layer 15. If left empty, the layer will display as "Layer 15". + + + Optional name for the 3D navigation layer 16. If left empty, the layer will display as "Layer 16". + + + Optional name for the 3D navigation layer 17. If left empty, the layer will display as "Layer 17". + + + Optional name for the 3D navigation layer 18. If left empty, the layer will display as "Layer 18". + + + Optional name for the 3D navigation layer 19. If left empty, the layer will display as "Layer 19". + + + Optional name for the 3D navigation layer 2. If left empty, the layer will display as "Layer 2". + + + Optional name for the 3D navigation layer 20. If left empty, the layer will display as "Layer 20". + + + Optional name for the 3D navigation layer 21. If left empty, the layer will display as "Layer 21". + + + Optional name for the 3D navigation layer 22. If left empty, the layer will display as "Layer 22". + + + Optional name for the 3D navigation layer 23. If left empty, the layer will display as "Layer 23". + + + Optional name for the 3D navigation layer 24. If left empty, the layer will display as "Layer 24". + + + Optional name for the 3D navigation layer 25. If left empty, the layer will display as "Layer 25". + + + Optional name for the 3D navigation layer 26. If left empty, the layer will display as "Layer 26". + + + Optional name for the 3D navigation layer 27. If left empty, the layer will display as "Layer 27". + + + Optional name for the 3D navigation layer 28. If left empty, the layer will display as "Layer 28". + + + Optional name for the 3D navigation layer 29. If left empty, the layer will display as "Layer 29". + + + Optional name for the 3D navigation layer 3. If left empty, the layer will display as "Layer 3". + + + Optional name for the 3D navigation layer 30. If left empty, the layer will display as "Layer 30". + + + Optional name for the 3D navigation layer 31. If left empty, the layer will display as "Layer 31". + + + Optional name for the 3D navigation layer 32. If left empty, the layer will display as "Layer 32". + + + Optional name for the 3D navigation layer 4. If left empty, the layer will display as "Layer 4". + + + Optional name for the 3D navigation layer 5. If left empty, the layer will display as "Layer 5". + + + Optional name for the 3D navigation layer 6. If left empty, the layer will display as "Layer 6". + + + Optional name for the 3D navigation layer 7. If left empty, the layer will display as "Layer 7". + + + Optional name for the 3D navigation layer 8. If left empty, the layer will display as "Layer 8". + + + Optional name for the 3D navigation layer 9. If left empty, the layer will display as "Layer 9". + Optional name for the 3D physics layer 1. @@ -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. - + + Default cell height for 2D navigation maps. See [method Navigation2DServer.map_set_cell_height]. + [b]Note:[/b] Currently not implemented. + + Default cell size for 2D navigation maps. See [method Navigation2DServer.map_set_cell_size]. - + Default edge connection margin for 2D navigation maps. See [method Navigation2DServer.map_set_edge_connection_margin]. + + Default cell height for 3D navigation maps. See [method NavigationServer.map_set_cell_height]. + Default cell size for 3D navigation maps. See [method NavigationServer.map_set_cell_size]. Default edge connection margin for 3D navigation maps. See [method NavigationServer.map_set_edge_connection_margin]. + + Default map up vector for 3D navigation maps. See [method NavigationServer.map_set_up]. + 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. diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 3bd7f0f1e81f..cec9b86cdafc 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -215,6 +215,9 @@ + + If [code]true[/code], this TileMap bakes a navigation region. + If [code]true[/code], the cell's UVs will be clipped. @@ -266,6 +269,9 @@ The TileMap orientation mode. See [enum Mode] for possible values. + + The navigation layers the TileMap generates its navigation regions in. + 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). diff --git a/doc/classes/World.xml b/doc/classes/World.xml index c8111587b60e..e3130276e6a9 100644 --- a/doc/classes/World.xml +++ b/doc/classes/World.xml @@ -4,7 +4,7 @@ Class that has everything pertaining to a world. - 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. $DOCS_URL/tutorials/physics/ray-casting.html diff --git a/doc/classes/World2D.xml b/doc/classes/World2D.xml index 3940ab8e18c5..bbb30c62500c 100644 --- a/doc/classes/World2D.xml +++ b/doc/classes/World2D.xml @@ -4,7 +4,7 @@ Class that has everything pertaining to a 2D world. - 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. $DOCS_URL/tutorials/physics/ray-casting.html diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index be381ec62cf0..37350f64dc59 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -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 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 } diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 8a0b54786520..383af9067b22 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -256,8 +256,10 @@ class EditorPropertyLayers : public EditorProperty { enum LayerType { LAYER_PHYSICS_2D, LAYER_RENDER_2D, + LAYER_NAVIGATION_2D, LAYER_PHYSICS_3D, LAYER_RENDER_3D, + LAYER_NAVIGATION_3D, }; private: diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index e504cb9321d0..ec254d5cb16e 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -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); diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index eb8575fdd3cc..2e5d923a6af5 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -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," diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index b0b41132c7e7..735bc3f5898d 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -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; diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 99e3c88d50a2..8270712f196b 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -160,6 +160,9 @@ + + If [code]true[/code], this GridMap uses cell navmesh resources to create navigation regions. + If [code]true[/code], grid items are centered on the X axis. @@ -190,6 +193,9 @@ The assigned [MeshLibrary]. + + The navigation layers the GridMap generates its navigation regions in. + Overrides the default friction and bounce physics properties for the whole [GridMap]. diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index f0dec03d005a..b0a2689e0dac 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -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 &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::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::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::Element *F = g.navmesh_ids.front(); F; F = F->next()) { - if (cell_map.has(F->key()) && F->get().region.is_valid() == false) { - Ref nm = mesh_library->get_item_navmesh(cell_map[F->key()].item); + if (bake_navigation && mesh_library.is_valid()) { + for (Map::Element *E = g.navmesh_ids.front(); E; E = E->next()) { + if (cell_map.has(E->key()) && E->get().region.is_valid() == false) { + Ref 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::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::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::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); diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index be0637f937e0..b0ebe18bbca4 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -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 physics_material; + bool bake_navigation = false; + uint32_t navigation_layers = 1; Transform last_transform; @@ -227,6 +230,12 @@ class GridMap : public Spatial { 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 &p_mesh_library); Ref get_mesh_library() const; diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index 088e6ad8ee63..c421b1b727f5 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -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(this); + GodotNavigationServer *mut_this = const_cast(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(this); + GodotNavigationServer *mut_this = const_cast(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 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 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()); - 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(this); + GodotNavigationServer *mut_this = const_cast(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, 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 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(this); + GodotNavigationServer *mut_this = const_cast(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 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(this); + GodotNavigationServer *mut_this = const_cast(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; + } } } diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index 64af7fdb7d44..d777c74b198c 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -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 region_owner; mutable RID_Owner agent_owner; - bool active; - Vector active_maps; + bool active = true; + LocalVector active_maps; + LocalVector active_maps_update_id; public: GodotNavigationServer(); @@ -101,7 +99,7 @@ class GodotNavigationServer : public NavigationServer { 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 map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize) const; + virtual Vector 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 @@ class GodotNavigationServer : public NavigationServer { 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, p_nav_mesh); virtual void region_bake_navmesh(Ref 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 @@ class GodotNavigationServer : public NavigationServer { 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); }; diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 795e116f93c2..102577553f8b 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -32,28 +32,10 @@ #include "nav_region.h" #include "rvo_agent.h" -#include - -/** - @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) {} +#include -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 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 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 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(); } @@ -135,92 +123,102 @@ Vector NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p return path; } + // List of all reachable navigation polys. std::vector navigation_polys; navigation_polys.reserve(polygons.size() * 0.75); - // The elements indices in the `navigation_polys`. - int least_cost_id(-1); - List open_list; - bool found_route = false; + // 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); - 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; - } + // List of polygon IDs to visit. + List to_visit; + to_visit.push_back(0); - open_list.push_back(0); + // This is an implementation of the A* algorithm. + int least_cost_id = 0; + bool found_route = false; - 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; + + 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]; - const gd::Edge &edge = least_cost_poly->poly->edges[i]; - if (!edge.other_polygon) + // 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::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 - - 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 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); + + // 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 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::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 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 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 path; - if (p_optimize) { - // String pulling + // If we did not find a route, return an empty path. + if (!found_route) { + return Vector(); + } - 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 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; - } 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; - - //if (CLOCK_TANGENT(apex_point,left,(left+right)*0.5).dot(up) < 0){ - if (p->poly->clockwise) { - SWAP(left, right); - } - } + 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 (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; - } + 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 { + clip_path(navigation_polys, path, apex_poly, right_portal, right_poly); + + 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; } + } - 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 (!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); + + 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); } - - 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); - - 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; + // 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; } + } + // 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); - path.invert(); + // 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; } - return path; + path.push_back(begin_point); + path.invert(); } - return Vector(); + + 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::iterator it = std::find(regions.begin(), regions.end(), p_region); + const std::vector::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::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::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 connections; - + // Group all edges per key. + Map> 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::Element *connection = connections.find(ek); + Map>::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(); + } + 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 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 free_edges; + for (Map>::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 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; + + // 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 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; - - 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; + 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); } } - } - if (regenerate_links) { - map_update_id = map_update_id + 1 % 9999999; + // Update the update ID. + map_update_id = (map_update_id + 1) % 9999999; } + // Update agents tree. if (agents_dirty) { std::vector 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 &p_navigation_polys, Vector &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(); +} diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index 196ceb6c7d72..4377a4c48826 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -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 -/** - @author AndreaCatania -*/ +#include 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 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 agents; @@ -79,10 +77,10 @@ class NavMap : public NavRid { std::vector 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 @@ class NavMap : public NavRid { gd::PointKey get_point_key(const Vector3 &p_pos) const; - Vector get_path(Vector3 p_origin, Vector3 p_destination, bool p_optimize) const; + Vector 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; diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index b4f62891dbe2..103214ba97b2 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -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 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 vertices = mesh->get_vertices(); int len = vertices.size(); - if (len == 0) + if (len == 0) { return; + } PoolVector::Read vertices_r = vertices.read(); @@ -132,3 +155,7 @@ void NavRegion::update_polygons() { } } } + +NavRegion::NavRegion() {} + +NavRegion::~NavRegion() {} diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h index 59effd403ed8..14318f07acc0 100644 --- a/modules/navigation/nav_region.h +++ b/modules/navigation/nav_region.h @@ -31,32 +31,31 @@ #ifndef NAV_REGION_H #define NAV_REGION_H -#include "nav_rid.h" +#include "scene/3d/navigation.h" +#include "nav_rid.h" #include "nav_utils.h" -#include "scene/3d/navigation.h" -#include -/** - @author AndreaCatania -*/ +#include class NavMap; class NavRegion; class NavRegion : public NavRid { - NavMap *map; + NavMap *map = nullptr; Transform transform; Ref mesh; + uint32_t navigation_layers = 1; + float enter_cost = 0.0; + float travel_cost = 1.0; + Vector connections; - bool polygons_dirty; + bool polygons_dirty = true; /// Cache std::vector polygons; public: - NavRegion(); - void scratch_polygons() { polygons_dirty = true; } @@ -66,6 +65,15 @@ class NavRegion : public NavRid { 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 @@ class NavRegion : public NavRid { return mesh; } + Vector &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 const &get_polygons() const { return polygons; } bool sync(); + NavRegion(); + ~NavRegion(); + private: void update_polygons(); }; diff --git a/modules/navigation/nav_rid.h b/modules/navigation/nav_rid.h index 35189f92f460..91a2af50d09d 100644 --- a/modules/navigation/nav_rid.h +++ b/modules/navigation/nav_rid.h @@ -33,10 +33,6 @@ #include "core/rid.h" -/** - @author AndreaCatania -*/ - class NavRid : public RID_Data { RID self; diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h index 6563822c9a1e..d348730593c2 100644 --- a/modules/navigation/nav_utils.h +++ b/modules/navigation/nav_utils.h @@ -32,11 +32,8 @@ #define NAV_UTILS_H #include "core/math/vector3.h" -#include -/** - @author AndreaCatania -*/ +#include 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; - - /// 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; - } + int this_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 connections; }; struct Polygon { - NavRegion *owner; + NavRegion *owner = nullptr; /// The points of this `Polygon` std::vector 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; diff --git a/modules/navigation/register_types.cpp b/modules/navigation/register_types.cpp index f47c426d2a54..f3ce7bc7d19b 100644 --- a/modules/navigation/register_types.cpp +++ b/modules/navigation/register_types.cpp @@ -42,10 +42,6 @@ #include "navigation_mesh_editor_plugin.h" #endif -/** - @author AndreaCatania -*/ - #ifndef _3D_DISABLED NavigationMeshGenerator *_nav_mesh_generator = nullptr; #endif diff --git a/modules/navigation/register_types.h b/modules/navigation/register_types.h index 619ac4e3a657..37d9de90e147 100644 --- a/modules/navigation/register_types.h +++ b/modules/navigation/register_types.h @@ -28,9 +28,5 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -/** - @author AndreaCatania -*/ - void register_navigation_types(); void unregister_navigation_types(); diff --git a/modules/navigation/rvo_agent.cpp b/modules/navigation/rvo_agent.cpp index 8186751673ee..a295bd56f364 100644 --- a/modules/navigation/rvo_agent.cpp +++ b/modules/navigation/rvo_agent.cpp @@ -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); } diff --git a/modules/navigation/rvo_agent.h b/modules/navigation/rvo_agent.h index b00f4f773a70..102043ecfd0a 100644 --- a/modules/navigation/rvo_agent.h +++ b/modules/navigation/rvo_agent.h @@ -33,11 +33,8 @@ #include "core/object.h" #include "nav_rid.h" -#include -/** - @author AndreaCatania -*/ +#include 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(); diff --git a/scene/2d/navigation_2d.cpp b/scene/2d/navigation_2d.cpp index 87fe05bce45b..6b952bf97ab7 100644 --- a/scene/2d/navigation_2d.cpp +++ b/scene/2d/navigation_2d.cpp @@ -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 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() { diff --git a/scene/2d/navigation_2d.h b/scene/2d/navigation_2d.h index f4d916e1da6a..1e881d6670d9 100644 --- a/scene/2d/navigation_2d.h +++ b/scene/2d/navigation_2d.h @@ -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 @@ class Navigation2D : public Node2D { 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; diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index a126704215b7..b35b39674349 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -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(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(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(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(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) { diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h index 93d12dc6cdf8..a6a7885777c7 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -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 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 @@ class NavigationAgent2D : public Node { 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 @@ class NavigationAgent2D : public Node { private: void update_navigation(); + void _request_repath(); void _check_distance_to_target(); }; diff --git a/scene/2d/navigation_polygon.cpp b/scene/2d/navigation_polygon.cpp index db3b18dd26de..33bf691b8c7f 100644 --- a/scene/2d/navigation_polygon.cpp +++ b/scene/2d/navigation_polygon.cpp @@ -151,10 +151,12 @@ void NavigationPolygon::add_outline_at_index(const PoolVector &p_outlin int NavigationPolygon::get_polygon_count() const { return polygons.size(); } + Vector NavigationPolygon::get_polygon(int p_idx) { ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector()); return polygons[p_idx].indices; } + void NavigationPolygon::clear_polygons() { polygons.clear(); { @@ -165,6 +167,7 @@ void NavigationPolygon::clear_polygons() { Ref NavigationPolygon::get_mesh() { MutexLock lock(navmesh_generation); + if (navmesh.is_null()) { navmesh.instance(); PoolVector verts; @@ -184,6 +187,7 @@ Ref 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(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 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 colors; - Vector vertices; - vertices.resize(vsize); - colors.resize(vsize); - { - PoolVector::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 indices; + RandomPCG rand; for (int i = 0; i < navpoly->get_polygon_count(); i++) { + // An array of vertices for this polygon. Vector 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 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 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(c)) { - return warning; - } - - c = Object::cast_to(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() { diff --git a/scene/2d/navigation_polygon.h b/scene/2d/navigation_polygon.h index 1194c1438838..f5b091805185 100644 --- a/scene/2d/navigation_polygon.h +++ b/scene/2d/navigation_polygon.h @@ -45,7 +45,7 @@ class NavigationPolygon : public Resource { Vector> 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 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 @@ class NavigationPolygonInstance : public Node2D { 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 &p_navpoly); Ref get_navigation_polygon() const; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 7fb4e03abdd6..4113ab68243d 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -62,6 +62,8 @@ void TileMap::_notification(int p_what) { while (c) { navigation = Object::cast_to(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::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::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 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::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::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::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"); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index bda68b5425e5..a0c8e97ed119 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -79,6 +79,8 @@ class TileMap : public Node2D { 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 @@ class TileMap : public Node2D { 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; diff --git a/scene/3d/navigation.cpp b/scene/3d/navigation.cpp index a7f2f3cccfea..3ea67bcb6be4 100644 --- a/scene/3d/navigation.cpp +++ b/scene/3d/navigation.cpp @@ -33,7 +33,7 @@ #include "servers/navigation_server.h" Vector 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() { diff --git a/scene/3d/navigation.h b/scene/3d/navigation.h index 9779bd05b623..1009b482173d 100644 --- a/scene/3d/navigation.h +++ b/scene/3d/navigation.h @@ -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 @@ class Navigation : public Spatial { 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; diff --git a/scene/3d/navigation_agent.cpp b/scene/3d/navigation_agent.cpp index 92f5528d595b..e1354ab010c7 100644 --- a/scene/3d/navigation_agent.cpp +++ b/scene/3d/navigation_agent.cpp @@ -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(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(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(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(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) { diff --git a/scene/3d/navigation_agent.h b/scene/3d/navigation_agent.h index b2c57326f444..0820b8872f01 100644 --- a/scene/3d/navigation_agent.h +++ b/scene/3d/navigation_agent.h @@ -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 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 @@ class NavigationAgent : public Node { 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 @@ class NavigationAgent : public Node { private: void update_navigation(); + void _request_repath(); void _check_distance_to_target(); }; diff --git a/scene/3d/navigation_mesh_instance.cpp b/scene/3d/navigation_mesh_instance.cpp index eff89ef679d3..297002733362 100644 --- a/scene/3d/navigation_mesh_instance.cpp +++ b/scene/3d/navigation_mesh_instance.cpp @@ -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 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(c)) { - return String(); - } - - c = Object::cast_to(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; } diff --git a/scene/3d/navigation_mesh_instance.h b/scene/3d/navigation_mesh_instance.h index 5c274931d058..9da715afae10 100644 --- a/scene/3d/navigation_mesh_instance.h +++ b/scene/3d/navigation_mesh_instance.h @@ -40,10 +40,14 @@ class Navigation; class NavigationMeshInstance : public Spatial { GDCLASS(NavigationMeshInstance, Spatial); - bool enabled; + bool enabled = true; RID region; Ref 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 @@ class NavigationMeshInstance : public Spatial { 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 &p_navmesh); Ref get_navigation_mesh() const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index e9b94deffb47..2f300f2b7640 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -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() { diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index 6dad1142f642..d5ce6cb605e5 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -31,6 +31,7 @@ #include "navigation_mesh.h" void NavigationMesh::create_from_mesh(const Ref &p_mesh) { + ERR_FAIL_COND(p_mesh.is_null()); vertices = PoolVector(); clear_polygons(); @@ -40,6 +41,7 @@ void NavigationMesh::create_from_mesh(const Ref &p_mesh) { continue; } Array arr = p_mesh->surface_get_arrays(i); + ERR_CONTINUE(arr.size() != Mesh::ARRAY_MAX); PoolVector varr = arr[Mesh::ARRAY_VERTEX]; PoolVector iarr = arr[Mesh::ARRAY_INDEX]; if (varr.size() == 0 || iarr.size() == 0) { @@ -299,13 +301,16 @@ void NavigationMesh::add_polygon(const Vector &p_polygon) { polygons.push_back(polygon); _change_notify(); } + int NavigationMesh::get_polygon_count() const { return polygons.size(); } + Vector NavigationMesh::get_polygon(int p_idx) { ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector()); 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() {} diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h index 4f7ae05d63ee..7d444b765bb1 100644 --- a/scene/resources/navigation_mesh.h +++ b/scene/resources/navigation_mesh.h @@ -90,30 +90,30 @@ class NavigationMesh : public Resource { }; 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; - - SamplePartitionType partition_type; - ParsedGeometryType parsed_geometry_type; - uint32_t collision_mask; - - SourceGeometryMode source_geometry_mode; - StringName source_group_name; - - bool filter_low_hanging_obstacles; - bool filter_ledge_spans; - bool filter_walkable_low_height_spans; + 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 = SAMPLE_PARTITION_WATERSHED; + ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_MESH_INSTANCES; + uint32_t collision_mask = 0xFFFFFFFF; + + SourceGeometryMode source_geometry_mode = SOURCE_GEOMETRY_NAVMESH_CHILDREN; + StringName source_group_name = "navmesh"; + + bool filter_low_hanging_obstacles = false; + bool filter_ledge_spans = false; + bool filter_walkable_low_height_spans = false; public: // Recast settings diff --git a/scene/resources/world.cpp b/scene/resources/world.cpp index 32c4f72ee7bd..2b94271c66a1 100644 --- a/scene/resources/world.cpp +++ b/scene/resources/world.cpp @@ -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 diff --git a/scene/resources/world_2d.cpp b/scene/resources/world_2d.cpp index 63ab21b51bc6..ac9382340276 100644 --- a/scene/resources/world_2d.cpp +++ b/scene/resources/world_2d.cpp @@ -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); } diff --git a/scene/resources/world_2d.h b/scene/resources/world_2d.h index b771206dd3f2..27ca401fb226 100644 --- a/scene/resources/world_2d.h +++ b/scene/resources/world_2d.h @@ -66,7 +66,7 @@ class World2D : public Resource { public: RID get_canvas(); RID get_space(); - RID get_navigation_map(); + RID get_navigation_map() const; Physics2DDirectSpaceState *get_direct_space_state(); diff --git a/servers/navigation_2d_server.cpp b/servers/navigation_2d_server.cpp index 326738f2a893..209ad4a07aaa 100644 --- a/servers/navigation_2d_server.cpp +++ b/servers/navigation_2d_server.cpp @@ -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 vector_v3_to_v2(const Vector &d) { Vector nd; nd.resize(d.size()); @@ -107,21 +137,27 @@ Vector vector_v3_to_v2(const Vector &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 poly_to_mesh(Ref d) { if (d.is_valid()) { return d->get_mesh(); @@ -130,15 +166,21 @@ Ref poly_to_mesh(Ref 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 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 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 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); diff --git a/servers/navigation_2d_server.h b/servers/navigation_2d_server.h index ad1015b3c7e7..8e06ae2763c6 100644 --- a/servers/navigation_2d_server.h +++ b/servers/navigation_2d_server.h @@ -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 @@ class Navigation2DServer : public Object { /// 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 @@ class Navigation2DServer : public Object { 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 map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize) const; + virtual Vector 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 @@ class Navigation2DServer : public Object { /// 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 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 @@ class Navigation2DServer : public Object { virtual ~Navigation2DServer(); }; -#endif +#endif // NAVIGATION_2D_SERVER_H diff --git a/servers/navigation_server.cpp b/servers/navigation_server.cpp index 3adda285c58f..f071e0143814 100644 --- a/servers/navigation_server.cpp +++ b/servers/navigation_server.cpp @@ -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() { diff --git a/servers/navigation_server.h b/servers/navigation_server.h index d99bba7a88dc..321f99be0199 100644 --- a/servers/navigation_server.h +++ b/servers/navigation_server.h @@ -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 @@ class NavigationServer : public Object { 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 map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize) const = 0; + virtual Vector 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 @@ class NavigationServer : public Object { /// 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 p_nav_mesh) const = 0; - /// Bake the navigation mesh + /// Bake the navigation mesh. virtual void region_bake_navmesh(Ref 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 @@ class NavigationServerManager { static NavigationServer *new_default_server(); }; -#endif +#endif // NAVIGATION_SERVER_H