Fix HierarchicalPathFinder pathing from inaccessible source locations.
When a search is initiated from an inaccessible source location, a path is still allowed if there is an adjacent, accessible location the unit can move into. The local pathfinder and HierarchicalPathFinder already account for this logic, but HPF has some bugs. Firstly, when the HierarchicalPathFinder heuristic is being evaluated, it assumes all cells being explored are accessible - this is important for performance as it avoids constantly rechecking the accessibility of cells. Although this fact holds true for cells explored by the path search, it does not hold true for cells being added as the initial starting points of the search. Secondly, when checking for adjacent locations to an inaccessible source cell, we checked only if these were still on the map. This is insufficient - we need to know if movement between the source cell and the adjacent cell is possible. The fixes resolve this by: - Teaching the heuristic an extra parameter to know if the location is known to be accessible. This allow an accessibility check to be performed for starting locations which stops HPF mistakenly assuming the abstract node for that location is the one we need to consider, and to correctly check the adjacent locations and their abstract nodes instead. The parameter means will can still skip the accessibility check in the typical case where the path search is being expanded, preserving performance. - When adjacent cells are considered we now check if movement to them is possible. This stops HPF from allowing jumps over height discontinuities (i.e. no magically jumping up or down cliffs) and thinking a path is possible when it is not.
This commit is contained in:
committed by
Matthias Mailänder
parent
0788e5ff3e
commit
2fe7e1bff9
@@ -44,7 +44,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
IRecorder recorder = null)
|
||||
{
|
||||
var graph = new MapPathGraph(LayerPoolForWorld(world), locomotor, self, world, check, customCost, ignoreActor, laneBias, false);
|
||||
var search = new PathSearch(graph, loc => 0, 0, targetPredicate, recorder);
|
||||
var search = new PathSearch(graph, (_, _) => 0, 0, targetPredicate, recorder);
|
||||
|
||||
AddInitialCells(world, locomotor, self, froms, check, customCost, ignoreActor, false, search);
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
Actor ignoreActor = null,
|
||||
bool laneBias = true,
|
||||
bool inReverse = false,
|
||||
Func<CPos, int> heuristic = null,
|
||||
Func<CPos, bool, int> heuristic = null,
|
||||
Grid? grid = null,
|
||||
IRecorder recorder = null)
|
||||
{
|
||||
@@ -129,10 +129,10 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
/// <param name="locomotor">Locomotor used to provide terrain costs.</param>
|
||||
/// <param name="destination">The cell for which costs are to be given by the estimation function.</param>
|
||||
/// <returns>A delegate that calculates the cost estimation between the <paramref name="destination"/> and the given cell.</returns>
|
||||
public static Func<CPos, int> DefaultCostEstimator(Locomotor locomotor, CPos destination)
|
||||
public static Func<CPos, bool, int> DefaultCostEstimator(Locomotor locomotor, CPos destination)
|
||||
{
|
||||
var estimator = DefaultCostEstimator(locomotor);
|
||||
return here => estimator(here, destination);
|
||||
return (here, _) => estimator(here, destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -162,7 +162,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
|
||||
public IPathGraph Graph { get; }
|
||||
public Func<CPos, bool> TargetPredicate { get; set; }
|
||||
readonly Func<CPos, int> heuristic;
|
||||
readonly Func<CPos, bool, int> heuristic;
|
||||
readonly int heuristicWeightPercentage;
|
||||
readonly IRecorder recorder;
|
||||
readonly IPriorityQueue<GraphConnection> openQueue;
|
||||
@@ -171,7 +171,10 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
/// Initialize a new search.
|
||||
/// </summary>
|
||||
/// <param name="graph">Graph over which the search is conducted.</param>
|
||||
/// <param name="heuristic">Provides an estimation of the distance between the given cell and the target.</param>
|
||||
/// <param name="heuristic">Provides an estimation of the distance between the given cell and the target.
|
||||
/// The Boolean parameter indicates if the cell is known to be accessible.
|
||||
/// When true, it is known accessible as it is being explored by the search.
|
||||
/// When false, the cell is being considered as a starting location and might not be accessible.</param>
|
||||
/// <param name="heuristicWeightPercentage">
|
||||
/// The search will aim for the shortest path when given a weight of 100%.
|
||||
/// We can allow the search to find paths that aren't optimal by changing the weight.
|
||||
@@ -182,7 +185,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
/// </param>
|
||||
/// <param name="targetPredicate">Determines if the given cell is the target.</param>
|
||||
/// <param name="recorder">If provided, will record all nodes explored by searches performed.</param>
|
||||
PathSearch(IPathGraph graph, Func<CPos, int> heuristic, int heuristicWeightPercentage, Func<CPos, bool> targetPredicate, IRecorder recorder)
|
||||
PathSearch(IPathGraph graph, Func<CPos, bool, int> heuristic, int heuristicWeightPercentage, Func<CPos, bool> targetPredicate, IRecorder recorder)
|
||||
{
|
||||
Graph = graph;
|
||||
this.heuristic = heuristic;
|
||||
@@ -202,7 +205,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
return;
|
||||
}
|
||||
|
||||
var heuristicCost = heuristic(location);
|
||||
var heuristicCost = heuristic(location, false);
|
||||
if (heuristicCost == PathGraph.PathCostForInvalidPath)
|
||||
return;
|
||||
|
||||
@@ -272,7 +275,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
else
|
||||
{
|
||||
// If the heuristic reports the cell is unreachable, we won't consider it.
|
||||
var heuristicCost = heuristic(neighbor);
|
||||
var heuristicCost = heuristic(neighbor, true);
|
||||
if (heuristicCost == PathGraph.PathCostForInvalidPath)
|
||||
continue;
|
||||
estimatedRemainingCostToTarget = heuristicCost * heuristicWeightPercentage / 100;
|
||||
|
||||
Reference in New Issue
Block a user