Skip to content

Commit 06d6dc7

Browse files
committed
Update NavigationPathQueryObjects to explain all (new) options
Updates NavigationPathQueryObjects to explain all (new) options.
1 parent dd72262 commit 06d6dc7

File tree

5 files changed

+233
-6
lines changed

5 files changed

+233
-6
lines changed
Loading
Binary file not shown.
Binary file not shown.

tutorials/navigation/navigation_using_navigationagents.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
Using NavigationAgents
44
======================
55

6+
.. tip::
7+
8+
For more advanced uses consider :ref:`doc_navigation_using_navigationpathqueryobjects` over NavigationAgent nodes.
9+
610
NavigationsAgents are helper nodes that combine functionality
711
for pathfinding, path following and agent avoidance for a Node2D/3D inheriting parent node.
812
They facilitate common calls to the NavigationServer API on

tutorials/navigation/navigation_using_navigationpathqueryobjects.rst

Lines changed: 229 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
Using NavigationPathQueryObjects
44
================================
55

6+
.. tip::
7+
8+
Path query parameters expose various options to improve pathfinding performance or lower memory consumptions.
9+
10+
They cater to more advanced pathfinding needs that the high-level nodes can not always cover.
11+
12+
See the respective option sections below.
13+
614
``NavigationPathQueryObjects`` can be used together with ``NavigationServer.query_path()``
715
to obtain a heavily **customized** navigation path including optional **meta data** about the path.
816

@@ -20,18 +28,26 @@ for the query and a ``NavigationPathQueryResult`` that receives (regular) update
2028
:ref:`NavigationPathQueryResult2D<class_NavigationPathQueryResult2D>` and
2129
:ref:`NavigationPathQueryResult3D<class_NavigationPathQueryResult3D>` respectively.
2230

31+
Creating a basic path query
32+
---------------------------
33+
2334
Both parameters and result are used as a pair with the ``NavigationServer.query_path()`` function.
2435

25-
For the available customization options and their use see the class doc of the parameters.
36+
For the available customization options, see further below. See also the descriptions for each parameter in the class reference.
2637

2738
While not a strict requirement, both objects are intended to be created once in advance, stored in a
2839
persistent variable for the agent and reused for every followup path query with updated parameters.
29-
This reuse avoids performance implications from frequent object creation if a project
30-
has a large quantity of simultaneous agents that regularly update their paths.
40+
41+
Reusing the same objects improves performance when frequently creating objects or allocating memory.
42+
43+
The following script creates the objects and provides a ``query_path()`` function to create new navigation paths.
44+
The resulting path is identical to using ``NavigationServer.map_get_path()`` while reusing the objects.
3145

3246
.. tabs::
3347
.. code-tab:: gdscript 2D GDScript
3448

49+
extends Node2D
50+
3551
# Prepare query objects.
3652
var query_parameters := NavigationPathQueryParameters2D.new()
3753
var query_result := NavigationPathQueryResult2D.new()
@@ -40,7 +56,13 @@ has a large quantity of simultaneous agents that regularly update their paths.
4056
if not is_inside_tree():
4157
return PackedVector2Array()
4258

43-
query_parameters.map = get_world_2d().get_navigation_map()
59+
var map: RID = get_world_2d().get_navigation_map()
60+
61+
if NavigationServer2D.map_get_iteration_id(map) == 0:
62+
# This map has never synced and is empty, no point in querying it.
63+
return PackedVector2Array()
64+
65+
query_parameters.map = map
4466
query_parameters.start_position = p_start_position
4567
query_parameters.target_position = p_target_position
4668
query_parameters.navigation_layers = p_navigation_layers
@@ -50,9 +72,10 @@ has a large quantity of simultaneous agents that regularly update their paths.
5072

5173
return path
5274

53-
5475
.. code-tab:: gdscript 3D GDScript
5576

77+
extends Node3D
78+
5679
# Prepare query objects.
5780
var query_parameters := NavigationPathQueryParameters3D.new()
5881
var query_result := NavigationPathQueryResult3D.new()
@@ -61,7 +84,13 @@ has a large quantity of simultaneous agents that regularly update their paths.
6184
if not is_inside_tree():
6285
return PackedVector3Array()
6386

64-
query_parameters.map = get_world_3d().get_navigation_map()
87+
var map: RID = get_world_3d().get_navigation_map()
88+
89+
if NavigationServer3D.map_get_iteration_id(map) == 0:
90+
# This map has never synced and is empty, no point in querying it.
91+
return PackedVector3Array()
92+
93+
query_parameters.map = map
6594
query_parameters.start_position = p_start_position
6695
query_parameters.target_position = p_target_position
6796
query_parameters.navigation_layers = p_navigation_layers
@@ -70,3 +99,197 @@ has a large quantity of simultaneous agents that regularly update their paths.
7099
var path: PackedVector3Array = query_result.get_path()
71100

72101
return path
102+
103+
Path postprocessing options
104+
---------------------------
105+
106+
.. figure:: img/path_postprocess_diff.webp
107+
:align: center
108+
:alt: Path post-processing differences depending on navigation mesh polygon layout
109+
110+
Path post-processing differences depending on navigation mesh polygon layout.
111+
112+
A path query search travels from closest navigation mesh polygon edge to closest edge along the available polygons.
113+
If possible it builds a polygon corridor towards the target position polygon.
114+
115+
This raw "search" polygon corridor path is not very optimized and usually a bad fit for agents to travel along.
116+
E.g. the closest edge point on a navigation mesh polygon might cause a huge detour for agents on larger polygons.
117+
In order to improve the quality of paths returned by the query various ``path_postprocessing`` options exist.
118+
119+
- The ``PATH_POSTPROCESSING_CORRIDORFUNNEL`` post processing shortens paths by funneling paths around corners **inside the available polygon corridor**.
120+
121+
This is the default post processing and usually also the most useful as it gives the shortest path result **inside the available polygon corridor**. If the polygon corridor is already suboptimal, e.g. due to a suboptimal navigation mesh layout, the funnel can snap to unexpected polygon corners causing detours.
122+
123+
- The ``PATH_POSTPROCESSING_EDGECENTERED`` post processing forces all path points to be placed in the middle of the crossed polygon edges **inside the available polygon corridor**.
124+
125+
This post processing is usually only useful when used with strictly tile-like navigation mesh polygons that are all evenly sized and where the excepted path following is also constrained to cell centers, e.g. typical grid game with movement constrained to grid cell centers.
126+
127+
- The ``PATH_POSTPROCESSING_NONE`` post processing returns the path as is how the pathfinding traveled **inside the available polygon corridor**.
128+
129+
This post processing is very useful for debug as it shows how the path search traveled from closest edge point to closet edge point and what polygons it picked.
130+
A lot of unexpected or suboptimal path results can be immediately explained by looking at this raw path and polygon corridor.
131+
132+
Path simplification
133+
-------------------
134+
135+
.. tip::
136+
137+
Path simplification can help steering agents or agents that jitter on thin polygon edges.
138+
139+
.. figure:: img/path_simplification_diff.webp
140+
:align: center
141+
:alt: Path point difference with or without path simplification
142+
143+
Path point difference with or without path simplification.
144+
145+
If ``simplify_path`` is enabled a variant of the Ramer-Douglas-Peucker path simplification algorithm is applied to the path.
146+
This algorithm straightens paths by removing less relevant path points depending on the used ``simplify_epsilon``.
147+
148+
Path simplification helps with all kinds of agent movement problems in "open fields" that are caused by having many unnecessary polygon edges.
149+
E.g. a terrain mesh when baked to a navigation mesh can cause an excessive polygon count due to all the small (but for pathfinding almost meaningless) height variations in the terrain.
150+
151+
Path simplification also helps with "steering" agents because they only have more critical corner path points to aim for.
152+
153+
.. Warning::
154+
155+
Path simplification is an additional final post-processing of the path. It adds extra performance cost the query so only enabled the simplification when actually needed.
156+
157+
.. note::
158+
159+
Path simplification is exposed on the NavigationServer as a generic function. It can be used outside of navigation queries for all kinds of position holding arrays as well.
160+
161+
Path meta data
162+
--------------
163+
164+
.. tip::
165+
166+
Disabling unneeded path meta data options can improve performance and lower memory consumption.
167+
168+
A path query can return additional meta data for every path point.
169+
170+
- Flag ``PATH_METADATA_INCLUDE_TYPES`` collects an array with the primitive information about the point owners, e.g. if a point belongs to a region or link.
171+
- Flag ``PATH_METADATA_INCLUDE_RIDS`` collects an array with the :ref:`RID<class_RID>` of the point owners. Depending on point owner primitive, these RIDs can be used with the various NavigationServer functions related to regions or links.
172+
- Flag ``PATH_METADATA_INCLUDE_RIDS`` collects an array with the ``ObjectIDs`` of the point owners. These object ids can be used with :ref:`@GlobalScope.instance_from_id()<class_@GlobalScope_method_instance_from_id>` to receive the node behind that object instance, e.g. a NavigationRegion or NavigationLink node.
173+
174+
By default all path meta data is collected as this meta data can be essential for more advanced navigation gameplay.
175+
176+
- E.g. to know what path point maps to what object or node owner inside the SceneTree.
177+
- E.g. to know if a path point is the start or end of a navigation link that requires scripted takeover.
178+
179+
For the most basic path uses meta data is not always needed.
180+
Path meta data collection can be selectively disabled to gain some performance and reduce memory consumption.
181+
182+
Excluding or including regions
183+
------------------------------
184+
185+
.. tip::
186+
187+
Region filters can greatly help with performance on large navigation maps that are region partitioned.
188+
189+
Query parameters allow to limit the pathfinding to specific region navigation meshes.
190+
191+
If a large navigation map is well partitioned into smaller regions this can greatly help with performance as the query can skip a large amount of polygons at one of the earliest checks in the path search.
192+
193+
- By default and if left empty all regions of the queried navigation map are included.
194+
- If a region :ref:`RID<class_RID>` is added to the ``excluded_regions`` array the region's navigation mesh will be ignored in the path search.
195+
- If a region :ref:`RID<class_RID>` is added to the ``included_regions`` array the region's navigation mesh will be considered in the path search and also all other regions not included will be ignored as well.
196+
- If a region ends up both included and excluded it is considered excluded.
197+
198+
Region filters are very effective for performance when paired with navigation region chunks that are aligned on a grid.
199+
This way the filter can be set to only include the start position chunk and surrounding chunks instead of the entire navigation map.
200+
201+
Even if the target might be outside these surrounding chunks (can always add more "rings") the pathfinding will try to create a path to the polygon closest to the target.
202+
This usually creates half-paths heading in the general direction that are good enough, all for a fraction of the performance that a full map search would cost.
203+
204+
The following addition to the basic path query script showcases the idea how to integrate a region chunk mapping with the region filters. This is not a full working example.
205+
206+
.. tabs::
207+
.. code-tab:: gdscript 2D GDScript
208+
209+
extends Node2D
210+
211+
# ...
212+
213+
var chunk_id_to_region_rid: Dictionary[Vector2i, RID] = {}
214+
215+
func query_path(p_start_position: Vector2, p_target_position: Vector2, p_navigation_layers: int = 1) -> PackedVector2Array:
216+
217+
# ...
218+
219+
var regions_around_start_position: Array[RID] = []
220+
221+
var chunk_rings: int = 1 # Increase for very small regions or more quality
222+
var start_chunk_id: Vector2i = floor(p_start_position / float(chunk_size))
223+
224+
for y: int in range(start_chunk_id.y - chunk_rings, start_chunk_id.y + chunk_rings):
225+
for x: int in range(start_chunk_id.x - chunk_rings, start_chunk_id.x + chunk_rings):
226+
var chunk_id: Vector2i = Vector2i(x, y)
227+
if chunk_id_to_region_rid.has(chunk_id):
228+
var region: RID = chunk_id_to_region_rid[chunk_id]
229+
regions_around_start_position.push_back(region)
230+
231+
query_parameters.included_regions = regions_around_start_position
232+
233+
# ...
234+
235+
.. code-tab:: gdscript 3D GDScript
236+
237+
extends Node3D
238+
239+
# ...
240+
241+
var chunk_id_to_region_rid: Dictionary[Vector3i, RID] = {}
242+
243+
func query_path(p_start_position: Vector3, p_target_position: Vector3, p_navigation_layers: int = 1) -> PackedVector3Array:
244+
245+
# ...
246+
247+
var regions_around_start_position: Array[RID] = []
248+
249+
var chunk_rings: int = 1 # Increase for very small regions or more quality
250+
var start_chunk_id: Vector3i = floor(p_start_position / float(chunk_size))
251+
var y: int = 0 # Assume a planar navigation map for simplicity.
252+
253+
for z: int in range(start_chunk_id.z - chunk_rings, start_chunk_id.z + chunk_rings):
254+
for x: int in range(start_chunk_id.x - chunk_rings, start_chunk_id.x + chunk_rings):
255+
var chunk_id: Vector3i = Vector3i(x, y, z)
256+
if chunk_id_to_region_rid.has(chunk_id):
257+
var region: RID = chunk_id_to_region_rid[chunk_id]
258+
regions_around_start_position.push_back(region)
259+
260+
query_parameters.included_regions = regions_around_start_position
261+
262+
# ...
263+
264+
Path clipping and limits
265+
------------------------
266+
267+
.. tip::
268+
269+
Sensible set limits can greatly help with performance on large navigation maps, especially when targets end up as not reachable.
270+
271+
.. figure:: img/path_clip_and_limits.gif
272+
:align: center
273+
:alt: Clipping returned paths to specific distances
274+
275+
Clipping returned paths to specific distances.
276+
277+
Query parameters allow to clip returned paths to specific lengths.
278+
These options clip the path as a post-processing. The path is still searched as if at full length, so will have the same quality as if returned at full length.
279+
Path length clips can be helpful to create paths that better fit constrained gameplay, e.g. tactical games with limited movement ranges.
280+
281+
- Property ``path_return_max_length`` can be used to clip the returned path to a specific max length.
282+
- Property ``path_return_max_radius`` can be used to clip the returned path inside a circle (2D) or sphere (3D) radius around the start position.
283+
284+
Query parameters allow to limit the path search to only searched up to a specific distance or a specific amount of searched polygons.
285+
These options are for performance and affect the path search directly.
286+
287+
- Property ``path_search_max_distance`` can be used to stop the path search when going over this distance from the start position.
288+
- Property ``path_search_max_polygons`` can be used to stop the path search when going over this searched polygon amount.
289+
290+
When the path search is stopped by reaching a limit the path resets and creates a path from the start position polygon to the so far found polygon that is closest to the target position.
291+
292+
.. warning::
293+
294+
While good for performance, if path search limit values are set too low they can affect the path quality very negatively.
295+
Depending on polygon layout and search pattern the returned paths might go into completely wrong directions instead of the direction of the target.

0 commit comments

Comments
 (0)