Skip to content

Commit 5a1831d

Browse files
committed
Update NavigationPathQueryObjects to explain all (new) options
Updates NavigationPathQueryObjects to explain all (new) options.
1 parent bf4294b commit 5a1831d

File tree

5 files changed

+245
-8
lines changed

5 files changed

+245
-8
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: 241 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@
33
Using NavigationPathQueryObjects
44
================================
55

6+
.. tip::
7+
8+
Path query parameters expose various options to improve pathfinding performance or lower memory consumption.
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()``
7-
to obtain a heavily **customized** navigation path including optional **meta data** about the path.
15+
to obtain a heavily **customized** navigation path including optional **metadata** about the path.
816

917
This requires more setup compared to obtaining a normal NavigationPath but lets you tailor
1018
the pathfinding and provided path data to the different needs of a project.
1119

1220
NavigationPathQueryObjects consist of a pair of objects, a ``NavigationPathQueryParameters`` object holding the customization options
13-
for the query and a ``NavigationPathQueryResult`` that receives (regular) updates with the resulting path and meta data from the query.
21+
for the query and a ``NavigationPathQueryResult`` that receives (regular) updates with the resulting path and metadata from the query.
1422

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