Skip to content

Commit c3ee32f

Browse files
committed
Use a heap to store traversable polygons for pathfinding
1 parent fd7239c commit c3ee32f

File tree

3 files changed

+392
-85
lines changed

3 files changed

+392
-85
lines changed

modules/navigation/nav_map.cpp

+70-75
Original file line numberDiff line numberDiff line change
@@ -221,27 +221,27 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
221221

222222
// List of all reachable navigation polys.
223223
LocalVector<gd::NavigationPoly> navigation_polys;
224-
navigation_polys.reserve(polygons.size() * 0.75);
224+
navigation_polys.resize(polygons.size() + link_polygons.size());
225225

226-
// Add the start polygon to the reachable navigation polygons.
227-
gd::NavigationPoly begin_navigation_poly = gd::NavigationPoly(begin_poly);
228-
begin_navigation_poly.self_id = 0;
226+
// Initialize the matching navigation polygon.
227+
gd::NavigationPoly &begin_navigation_poly = navigation_polys[begin_poly->id];
228+
begin_navigation_poly.poly = begin_poly;
229229
begin_navigation_poly.entry = begin_point;
230230
begin_navigation_poly.back_navigation_edge_pathway_start = begin_point;
231231
begin_navigation_poly.back_navigation_edge_pathway_end = begin_point;
232-
navigation_polys.push_back(begin_navigation_poly);
233232

234-
// List of polygon IDs to visit.
235-
List<uint32_t> to_visit;
236-
to_visit.push_back(0);
233+
// Heap of polygons to travel next.
234+
gd::Heap<gd::NavigationPoly *, gd::NavPolyTravelCostGreaterThan, gd::NavPolyHeapIndexer>
235+
traversable_polys;
236+
traversable_polys.reserve(polygons.size() * 0.25);
237237

238238
// This is an implementation of the A* algorithm.
239-
int least_cost_id = 0;
239+
int least_cost_id = begin_poly->id;
240240
int prev_least_cost_id = -1;
241241
bool found_route = false;
242242

243243
const gd::Polygon *reachable_end = nullptr;
244-
real_t reachable_d = FLT_MAX;
244+
real_t distance_to_reachable_end = FLT_MAX;
245245
bool is_reachable = true;
246246

247247
while (true) {
@@ -260,51 +260,57 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
260260
real_t poly_enter_cost = 0.0;
261261
real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost();
262262

263-
if (prev_least_cost_id != -1 && (navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self())) {
263+
if (prev_least_cost_id != -1 && navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self()) {
264264
poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost();
265265
}
266266
prev_least_cost_id = least_cost_id;
267267

268268
Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end };
269269
const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway);
270-
const real_t new_distance = (least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost) + poly_enter_cost + least_cost_poly.traveled_distance;
271-
272-
int64_t already_visited_polygon_index = navigation_polys.find(gd::NavigationPoly(connection.polygon));
273-
274-
if (already_visited_polygon_index != -1) {
275-
// Polygon already visited, check if we can reduce the travel cost.
276-
gd::NavigationPoly &avp = navigation_polys[already_visited_polygon_index];
277-
if (new_distance < avp.traveled_distance) {
278-
avp.back_navigation_poly_id = least_cost_id;
279-
avp.back_navigation_edge = connection.edge;
280-
avp.back_navigation_edge_pathway_start = connection.pathway_start;
281-
avp.back_navigation_edge_pathway_end = connection.pathway_end;
282-
avp.traveled_distance = new_distance;
283-
avp.entry = new_entry;
270+
const real_t new_traveled_distance = least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost + poly_enter_cost + least_cost_poly.traveled_distance;
271+
272+
// Check if the neighbor polygon has already been processed.
273+
gd::NavigationPoly &neighbor_poly = navigation_polys[connection.polygon->id];
274+
if (neighbor_poly.poly != nullptr) {
275+
// If the neighbor polygon hasn't been traversed yet and the new path leading to
276+
// it is shorter, update the polygon.
277+
if (neighbor_poly.traversable_poly_index < traversable_polys.size() &&
278+
new_traveled_distance < neighbor_poly.traveled_distance) {
279+
neighbor_poly.back_navigation_poly_id = least_cost_id;
280+
neighbor_poly.back_navigation_edge = connection.edge;
281+
neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start;
282+
neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end;
283+
neighbor_poly.traveled_distance = new_traveled_distance;
284+
neighbor_poly.distance_to_destination =
285+
new_entry.distance_to(end_point) *
286+
neighbor_poly.poly->owner->get_travel_cost();
287+
neighbor_poly.entry = new_entry;
288+
289+
// Update the priority of the polygon in the heap.
290+
traversable_polys.shift(neighbor_poly.traversable_poly_index);
284291
}
285292
} else {
286-
// Add the neighbor polygon to the reachable ones.
287-
gd::NavigationPoly new_navigation_poly = gd::NavigationPoly(connection.polygon);
288-
new_navigation_poly.self_id = navigation_polys.size();
289-
new_navigation_poly.back_navigation_poly_id = least_cost_id;
290-
new_navigation_poly.back_navigation_edge = connection.edge;
291-
new_navigation_poly.back_navigation_edge_pathway_start = connection.pathway_start;
292-
new_navigation_poly.back_navigation_edge_pathway_end = connection.pathway_end;
293-
new_navigation_poly.traveled_distance = new_distance;
294-
new_navigation_poly.entry = new_entry;
295-
navigation_polys.push_back(new_navigation_poly);
296-
297-
// Add the neighbor polygon to the polygons to visit.
298-
to_visit.push_back(navigation_polys.size() - 1);
293+
// Initialize the matching navigation polygon.
294+
neighbor_poly.poly = connection.polygon;
295+
neighbor_poly.back_navigation_poly_id = least_cost_id;
296+
neighbor_poly.back_navigation_edge = connection.edge;
297+
neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start;
298+
neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end;
299+
neighbor_poly.traveled_distance = new_traveled_distance;
300+
neighbor_poly.distance_to_destination =
301+
new_entry.distance_to(end_point) *
302+
neighbor_poly.poly->owner->get_travel_cost();
303+
neighbor_poly.entry = new_entry;
304+
305+
// Add the polygon to the heap of polygons to traverse next.
306+
traversable_polys.push(&neighbor_poly);
299307
}
300308
}
301309
}
302310

303-
// Removes the least cost polygon from the list of polygons to visit so we can advance.
304-
to_visit.erase(least_cost_id);
305-
306-
// When the list of polygons to visit is empty at this point it means the End Polygon is not reachable
307-
if (to_visit.size() == 0) {
311+
// When the heap of traversable polygons is empty at this point it means the end polygon is
312+
// unreachable.
313+
if (traversable_polys.is_empty()) {
308314
// Thus use the further reachable polygon
309315
ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons");
310316
is_reachable = false;
@@ -366,40 +372,27 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
366372
return path;
367373
}
368374

369-
// Reset open and navigation_polys
370-
gd::NavigationPoly np = navigation_polys[0];
371-
navigation_polys.clear();
372-
navigation_polys.push_back(np);
373-
to_visit.clear();
374-
to_visit.push_back(0);
375-
least_cost_id = 0;
375+
for (gd::NavigationPoly &nav_poly : navigation_polys) {
376+
nav_poly.poly = nullptr;
377+
}
378+
navigation_polys[begin_poly->id].poly = begin_poly;
379+
380+
least_cost_id = begin_poly->id;
376381
prev_least_cost_id = -1;
377382

378383
reachable_end = nullptr;
379384

380385
continue;
381386
}
382387

383-
// Find the polygon with the minimum cost from the list of polygons to visit.
384-
least_cost_id = -1;
385-
real_t least_cost = FLT_MAX;
386-
for (List<uint32_t>::Element *element = to_visit.front(); element != nullptr; element = element->next()) {
387-
gd::NavigationPoly *np = &navigation_polys[element->get()];
388-
real_t cost = np->traveled_distance;
389-
cost += (np->entry.distance_to(end_point) * np->poly->owner->get_travel_cost());
390-
if (cost < least_cost) {
391-
least_cost_id = np->self_id;
392-
least_cost = cost;
393-
}
394-
}
395-
396-
ERR_BREAK(least_cost_id == -1);
388+
// Pop the polygon with the lowest travel cost from the heap of traversable polygons.
389+
least_cost_id = traversable_polys.pop()->poly->id;
397390

398-
// Stores the further reachable end polygon, in case our goal is not reachable.
391+
// Store the farthest reachable end polygon in case our goal is not reachable.
399392
if (is_reachable) {
400-
real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination);
401-
if (reachable_d > d) {
402-
reachable_d = d;
393+
real_t distance = navigation_polys[least_cost_id].entry.distance_to(p_destination);
394+
if (distance_to_reachable_end > distance) {
395+
distance_to_reachable_end = distance;
403396
reachable_end = navigation_polys[least_cost_id].poly;
404397
}
405398
}
@@ -943,29 +936,30 @@ void NavMap::sync() {
943936
}
944937

945938
// Resize the polygon count.
946-
int count = 0;
939+
int polygon_count = 0;
947940
for (const NavRegion *region : regions) {
948941
if (!region->get_enabled()) {
949942
continue;
950943
}
951-
count += region->get_polygons().size();
944+
polygon_count += region->get_polygons().size();
952945
}
953-
polygons.resize(count);
946+
polygons.resize(polygon_count);
954947

955948
// Copy all region polygons in the map.
956-
count = 0;
949+
polygon_count = 0;
957950
for (const NavRegion *region : regions) {
958951
if (!region->get_enabled()) {
959952
continue;
960953
}
961954
const LocalVector<gd::Polygon> &polygons_source = region->get_polygons();
962955
for (uint32_t n = 0; n < polygons_source.size(); n++) {
963-
polygons[count + n] = polygons_source[n];
956+
polygons[polygon_count] = polygons_source[n];
957+
polygons[polygon_count].id = polygon_count;
958+
polygon_count++;
964959
}
965-
count += region->get_polygons().size();
966960
}
967961

968-
_new_pm_polygon_count = polygons.size();
962+
_new_pm_polygon_count = polygon_count;
969963

970964
// Group all edges per key.
971965
HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections;
@@ -1136,6 +1130,7 @@ void NavMap::sync() {
11361130
// If we have both a start and end point, then create a synthetic polygon to route through.
11371131
if (closest_start_polygon && closest_end_polygon) {
11381132
gd::Polygon &new_polygon = link_polygons[link_poly_idx++];
1133+
new_polygon.id = polygon_count++;
11391134
new_polygon.owner = link;
11401135

11411136
new_polygon.edges.clear();

0 commit comments

Comments
 (0)