Skip to content

Commit fcca9b3

Browse files
committed
Merge pull request godotengine#90434 from smix8/path_simplify
Add navigation path simplification
2 parents da0e993 + 1c134f4 commit fcca9b3

25 files changed

+330
-0
lines changed

doc/classes/NavigationAgent2D.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@
198198
The radius of the avoidance agent. This is the "body" of the avoidance agent and not the avoidance maneuver starting radius (which is controlled by [member neighbor_distance]).
199199
Does not affect normal pathfinding. To change an actor's pathfinding radius bake [NavigationMesh] resources with a different [member NavigationMesh.agent_radius] property and use different navigation maps for each actor size.
200200
</member>
201+
<member name="simplify_epsilon" type="float" setter="set_simplify_epsilon" getter="get_simplify_epsilon" default="0.0">
202+
The path simplification amount in worlds units.
203+
</member>
204+
<member name="simplify_path" type="bool" setter="set_simplify_path" getter="get_simplify_path" default="false">
205+
If [code]true[/code] a simplified version of the path will be returned with less critical path points removed. The simplification amount is controlled by [member simplify_epsilon]. The simplification uses a variant of Ramer-Douglas-Peucker algorithm for curve point decimation.
206+
Path simplification can be helpful to mitigate various path following issues that can arise with certain agent types and script behaviors. E.g. "steering" agents or avoidance in "open fields".
207+
</member>
201208
<member name="target_desired_distance" type="float" setter="set_target_desired_distance" getter="get_target_desired_distance" default="10.0">
202209
The distance threshold before the target is considered to be reached. On reaching the target, [signal target_reached] is emitted and navigation ends (see [method is_navigation_finished] and [signal navigation_finished]).
203210
You can make navigation end early by setting this property to a value greater than [member path_desired_distance] (navigation will end before reaching the last waypoint).

doc/classes/NavigationAgent3D.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@
204204
The radius of the avoidance agent. This is the "body" of the avoidance agent and not the avoidance maneuver starting radius (which is controlled by [member neighbor_distance]).
205205
Does not affect normal pathfinding. To change an actor's pathfinding radius bake [NavigationMesh] resources with a different [member NavigationMesh.agent_radius] property and use different navigation maps for each actor size.
206206
</member>
207+
<member name="simplify_epsilon" type="float" setter="set_simplify_epsilon" getter="get_simplify_epsilon" default="0.0">
208+
The path simplification amount in worlds units.
209+
</member>
210+
<member name="simplify_path" type="bool" setter="set_simplify_path" getter="get_simplify_path" default="false">
211+
If [code]true[/code] a simplified version of the path will be returned with less critical path points removed. The simplification amount is controlled by [member simplify_epsilon]. The simplification uses a variant of Ramer-Douglas-Peucker algorithm for curve point decimation.
212+
Path simplification can be helpful to mitigate various path following issues that can arise with certain agent types and script behaviors. E.g. "steering" agents or avoidance in "open fields".
213+
</member>
207214
<member name="target_desired_distance" type="float" setter="set_target_desired_distance" getter="get_target_desired_distance" default="1.0">
208215
The distance threshold before the target is considered to be reached. On reaching the target, [signal target_reached] is emitted and navigation ends (see [method is_navigation_finished] and [signal navigation_finished]).
209216
You can make navigation end early by setting this property to a value greater than [member path_desired_distance] (navigation will end before reaching the last waypoint).

doc/classes/NavigationPathQueryParameters2D.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
<member name="pathfinding_algorithm" type="int" setter="set_pathfinding_algorithm" getter="get_pathfinding_algorithm" enum="NavigationPathQueryParameters2D.PathfindingAlgorithm" default="0">
2626
The pathfinding algorithm used in the path query.
2727
</member>
28+
<member name="simplify_epsilon" type="float" setter="set_simplify_epsilon" getter="get_simplify_epsilon" default="0.0">
29+
The path simplification amount in worlds units.
30+
</member>
31+
<member name="simplify_path" type="bool" setter="set_simplify_path" getter="get_simplify_path" default="false">
32+
If [code]true[/code] a simplified version of the path will be returned with less critical path points removed. The simplification amount is controlled by [member simplify_epsilon]. The simplification uses a variant of Ramer-Douglas-Peucker algorithm for curve point decimation.
33+
Path simplification can be helpful to mitigate various path following issues that can arise with certain agent types and script behaviors. E.g. "steering" agents or avoidance in "open fields".
34+
</member>
2835
<member name="start_position" type="Vector2" setter="set_start_position" getter="get_start_position" default="Vector2(0, 0)">
2936
The pathfinding start position in global coordinates.
3037
</member>

doc/classes/NavigationPathQueryParameters3D.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
<member name="pathfinding_algorithm" type="int" setter="set_pathfinding_algorithm" getter="get_pathfinding_algorithm" enum="NavigationPathQueryParameters3D.PathfindingAlgorithm" default="0">
2626
The pathfinding algorithm used in the path query.
2727
</member>
28+
<member name="simplify_epsilon" type="float" setter="set_simplify_epsilon" getter="get_simplify_epsilon" default="0.0">
29+
The path simplification amount in worlds units.
30+
</member>
31+
<member name="simplify_path" type="bool" setter="set_simplify_path" getter="get_simplify_path" default="false">
32+
If [code]true[/code] a simplified version of the path will be returned with less critical path points removed. The simplification amount is controlled by [member simplify_epsilon]. The simplification uses a variant of Ramer-Douglas-Peucker algorithm for curve point decimation.
33+
Path simplification can be helpful to mitigate various path following issues that can arise with certain agent types and script behaviors. E.g. "steering" agents or avoidance in "open fields".
34+
</member>
2835
<member name="start_position" type="Vector3" setter="set_start_position" getter="get_start_position" default="Vector3(0, 0, 0)">
2936
The pathfinding start position in global coordinates.
3037
</member>

doc/classes/NavigationServer2D.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,15 @@
947947
If [code]true[/code] enables debug mode on the NavigationServer.
948948
</description>
949949
</method>
950+
<method name="simplify_path">
951+
<return type="PackedVector2Array" />
952+
<param index="0" name="path" type="PackedVector2Array" />
953+
<param index="1" name="epsilon" type="float" />
954+
<description>
955+
Returns a simplified version of [param path] with less critical path points removed. The simplification amount is in worlds units and controlled by [param epsilon]. The simplification uses a variant of Ramer-Douglas-Peucker algorithm for curve point decimation.
956+
Path simplification can be helpful to mitigate various path following issues that can arise with certain agent types and script behaviors. E.g. "steering" agents or avoidance in "open fields".
957+
</description>
958+
</method>
950959
</methods>
951960
<signals>
952961
<signal name="map_changed">

doc/classes/NavigationServer3D.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,15 @@
10941094
If [code]true[/code] enables debug mode on the NavigationServer.
10951095
</description>
10961096
</method>
1097+
<method name="simplify_path">
1098+
<return type="PackedVector3Array" />
1099+
<param index="0" name="path" type="PackedVector3Array" />
1100+
<param index="1" name="epsilon" type="float" />
1101+
<description>
1102+
Returns a simplified version of [param path] with less critical path points removed. The simplification amount is in worlds units and controlled by [param epsilon]. The simplification uses a variant of Ramer-Douglas-Peucker algorithm for curve point decimation.
1103+
Path simplification can be helpful to mitigate various path following issues that can arise with certain agent types and script behaviors. E.g. "steering" agents or avoidance in "open fields".
1104+
</description>
1105+
</method>
10971106
</methods>
10981107
<signals>
10991108
<signal name="avoidance_debug_changed">

modules/navigation/2d/godot_navigation_server_2d.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ bool GodotNavigationServer2D::is_baking_navigation_polygon(Ref<NavigationPolygon
229229
#endif
230230
}
231231

232+
Vector<Vector2> GodotNavigationServer2D::simplify_path(const Vector<Vector2> &p_path, real_t p_epsilon) {
233+
return vector_v3_to_v2(NavigationServer3D::get_singleton()->simplify_path(vector_v2_to_v3(p_path), p_epsilon));
234+
}
235+
232236
GodotNavigationServer2D::GodotNavigationServer2D() {}
233237

234238
GodotNavigationServer2D::~GodotNavigationServer2D() {}

modules/navigation/2d/godot_navigation_server_2d.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ class GodotNavigationServer2D : public NavigationServer2D {
252252
virtual void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
253253
virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
254254
virtual bool is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const override;
255+
256+
virtual Vector<Vector2> simplify_path(const Vector<Vector2> &p_path, real_t p_epsilon) override;
255257
};
256258

257259
#endif // GODOT_NAVIGATION_SERVER_2D_H

modules/navigation/3d/godot_navigation_server_3d.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,11 +1381,140 @@ PathQueryResult GodotNavigationServer3D::_query_path(const PathQueryParameters &
13811381

13821382
// add path postprocessing
13831383

1384+
if (r_query_result.path.size() > 2 && p_parameters.simplify_path) {
1385+
const LocalVector<uint32_t> &simplified_path_indices = get_simplified_path_indices(r_query_result.path, p_parameters.simplify_epsilon);
1386+
1387+
uint32_t indices_count = simplified_path_indices.size();
1388+
1389+
{
1390+
Vector3 *w = r_query_result.path.ptrw();
1391+
const Vector3 *r = r_query_result.path.ptr();
1392+
for (uint32_t i = 0; i < indices_count; i++) {
1393+
w[i] = r[simplified_path_indices[i]];
1394+
}
1395+
r_query_result.path.resize(indices_count);
1396+
}
1397+
1398+
if (p_parameters.metadata_flags.has_flag(PathMetadataFlags::PATH_INCLUDE_TYPES)) {
1399+
int32_t *w = r_query_result.path_types.ptrw();
1400+
const int32_t *r = r_query_result.path_types.ptr();
1401+
for (uint32_t i = 0; i < indices_count; i++) {
1402+
w[i] = r[simplified_path_indices[i]];
1403+
}
1404+
r_query_result.path_types.resize(indices_count);
1405+
}
1406+
1407+
if (p_parameters.metadata_flags.has_flag(PathMetadataFlags::PATH_INCLUDE_RIDS)) {
1408+
TypedArray<RID> simplified_path_rids;
1409+
simplified_path_rids.resize(indices_count);
1410+
for (uint32_t i = 0; i < indices_count; i++) {
1411+
simplified_path_rids[i] = r_query_result.path_rids[i];
1412+
}
1413+
r_query_result.path_rids = simplified_path_rids;
1414+
}
1415+
1416+
if (p_parameters.metadata_flags.has_flag(PathMetadataFlags::PATH_INCLUDE_OWNERS)) {
1417+
int64_t *w = r_query_result.path_owner_ids.ptrw();
1418+
const int64_t *r = r_query_result.path_owner_ids.ptr();
1419+
for (uint32_t i = 0; i < indices_count; i++) {
1420+
w[i] = r[simplified_path_indices[i]];
1421+
}
1422+
r_query_result.path_owner_ids.resize(indices_count);
1423+
}
1424+
}
1425+
13841426
// add path stats
13851427

13861428
return r_query_result;
13871429
}
13881430

1431+
Vector<Vector3> GodotNavigationServer3D::simplify_path(const Vector<Vector3> &p_path, real_t p_epsilon) {
1432+
if (p_path.size() <= 2) {
1433+
return p_path;
1434+
}
1435+
1436+
p_epsilon = MAX(0.0, p_epsilon);
1437+
1438+
LocalVector<uint32_t> simplified_path_indices = get_simplified_path_indices(p_path, p_epsilon);
1439+
1440+
uint32_t indices_count = simplified_path_indices.size();
1441+
1442+
Vector<Vector3> simplified_path;
1443+
simplified_path.resize(indices_count);
1444+
1445+
Vector3 *w = simplified_path.ptrw();
1446+
const Vector3 *r = p_path.ptr();
1447+
for (uint32_t i = 0; i < indices_count; i++) {
1448+
w[i] = r[simplified_path_indices[i]];
1449+
}
1450+
1451+
return simplified_path;
1452+
}
1453+
1454+
LocalVector<uint32_t> GodotNavigationServer3D::get_simplified_path_indices(const Vector<Vector3> &p_path, real_t p_epsilon) {
1455+
p_epsilon = MAX(0.0, p_epsilon);
1456+
real_t squared_epsilon = p_epsilon * p_epsilon;
1457+
1458+
LocalVector<bool> valid_points;
1459+
valid_points.resize(p_path.size());
1460+
for (uint32_t i = 0; i < valid_points.size(); i++) {
1461+
valid_points[i] = false;
1462+
}
1463+
1464+
simplify_path_segment(0, p_path.size() - 1, p_path, squared_epsilon, valid_points);
1465+
1466+
int valid_point_index = 0;
1467+
1468+
for (bool valid : valid_points) {
1469+
if (valid) {
1470+
valid_point_index += 1;
1471+
}
1472+
}
1473+
1474+
LocalVector<uint32_t> simplified_path_indices;
1475+
simplified_path_indices.resize(valid_point_index);
1476+
valid_point_index = 0;
1477+
1478+
for (uint32_t i = 0; i < valid_points.size(); i++) {
1479+
if (valid_points[i]) {
1480+
simplified_path_indices[valid_point_index] = i;
1481+
valid_point_index += 1;
1482+
}
1483+
}
1484+
1485+
return simplified_path_indices;
1486+
}
1487+
1488+
void GodotNavigationServer3D::simplify_path_segment(int p_start_inx, int p_end_inx, const Vector<Vector3> &p_points, real_t p_epsilon, LocalVector<bool> &r_valid_points) {
1489+
r_valid_points[p_start_inx] = true;
1490+
r_valid_points[p_end_inx] = true;
1491+
1492+
const Vector3 &start_point = p_points[p_start_inx];
1493+
const Vector3 &end_point = p_points[p_end_inx];
1494+
1495+
Vector3 path_segment[2] = { start_point, end_point };
1496+
1497+
real_t point_max_distance = 0.0;
1498+
int point_max_index = 0;
1499+
1500+
for (int i = p_start_inx; i < p_end_inx; i++) {
1501+
const Vector3 &checked_point = p_points[i];
1502+
1503+
const Vector3 closest_point = Geometry3D::get_closest_point_to_segment(checked_point, path_segment);
1504+
real_t distance_squared = closest_point.distance_squared_to(checked_point);
1505+
1506+
if (distance_squared > point_max_distance) {
1507+
point_max_index = i;
1508+
point_max_distance = distance_squared;
1509+
}
1510+
}
1511+
1512+
if (point_max_distance > p_epsilon) {
1513+
simplify_path_segment(p_start_inx, point_max_index, p_points, p_epsilon, r_valid_points);
1514+
simplify_path_segment(point_max_index, p_end_inx, p_points, p_epsilon, r_valid_points);
1515+
}
1516+
}
1517+
13891518
int GodotNavigationServer3D::get_process_info(ProcessInfo p_info) const {
13901519
switch (p_info) {
13911520
case INFO_ACTIVE_MAPS: {

modules/navigation/3d/godot_navigation_server_3d.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,13 @@ class GodotNavigationServer3D : public NavigationServer3D {
264264
virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
265265
virtual bool is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const override;
266266

267+
virtual Vector<Vector3> simplify_path(const Vector<Vector3> &p_path, real_t p_epsilon) override;
268+
269+
private:
270+
static void simplify_path_segment(int p_start_inx, int p_end_inx, const Vector<Vector3> &p_points, real_t p_epsilon, LocalVector<bool> &r_valid_points);
271+
static LocalVector<uint32_t> get_simplified_path_indices(const Vector<Vector3> &p_path, real_t p_epsilon);
272+
273+
public:
267274
COMMAND_1(free, RID, p_object);
268275

269276
virtual void set_active(bool p_active) override;

0 commit comments

Comments
 (0)