com.badlogic.gdx.ai.pfa.HierarchicalPathFinder Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2014 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.ai.pfa;
import com.badlogic.gdx.utils.TimeUtils;
/** A {@code HierarchicalPathFinder} can find a path in an arbitrary {@link HierarchicalGraph} using the given {@link PathFinder},
* known as level path finder, on each level of the hierarchy.
*
* Pathfinding on a hierarchical graph applies the level path finder algorithm several times, starting at a high level of the
* hierarchy and working down. The results at high levels are used to limit the work it needs to do at lower levels.
*
* Note that the hierarchical path finder calls the {@link HierarchicalGraph#setLevel(int)} method to switch the graph into a
* particular level. All future calls to the {@link HierarchicalGraph#getConnections(Object) getConnections} method of the
* hierarchical graph then act as if the graph was just a simple, non-hierarchical graph at that level. This way, the level path
* finder has no way of telling that it is working with a hierarchical graph and it doesn't need to, meaning that you can use any
* path finder implementation for the level path finder.
*
* @param Type of node
*
* @author davebaol */
public class HierarchicalPathFinder implements PathFinder {
public static boolean DEBUG = false;
HierarchicalGraph graph;
PathFinder levelPathFinder;
LevelPathFinderRequest levelRequest;
PathFinderRequestControl levelRequestControl;
public HierarchicalPathFinder (HierarchicalGraph graph, PathFinder levelPathFinder) {
this.graph = graph;
this.levelPathFinder = levelPathFinder;
this.levelRequest = null;
this.levelRequestControl = null;
}
@Override
public boolean searchNodePath (N startNode, N endNode, Heuristic heuristic, GraphPath outPath) {
// Check if we have no path to find
if (startNode == endNode) return true;
// Set up our initial pair of nodes
N currentStartNode = startNode;
N currentEndNode = endNode;
int levelOfNodes = 0;
// Descend through levels of the graph
int currentLevel = graph.getLevelCount() - 1;
while (currentLevel >= 0) {
// Find the start node at current level
currentStartNode = graph.convertNodeBetweenLevels(0, startNode, currentLevel);
// Find the end node at current level
// Note that if we're examining level 0 and the current end node, the end node and the
// start node have the same parent at level 1 then we can use the end node directly.
currentEndNode = graph.convertNodeBetweenLevels(levelOfNodes, currentEndNode, currentLevel);
if (currentLevel == 0) {
N currentEndNodeParent = graph.convertNodeBetweenLevels(0, currentEndNode, 1);
if (currentEndNodeParent == graph.convertNodeBetweenLevels(0, endNode, 1)
&& currentEndNodeParent == graph.convertNodeBetweenLevels(0, startNode, 1)) {
currentEndNode = endNode;
}
}
// Decrease current level and skip it if start and end node are the same
levelOfNodes = currentLevel;
currentLevel--;
if (currentStartNode == currentEndNode) continue;
// Otherwise we can perform the plan
graph.setLevel(levelOfNodes);
outPath.clear();
boolean pathFound = levelPathFinder.searchNodePath(currentStartNode, currentEndNode, heuristic, outPath);
if (!pathFound) return false;
// Now take the first move of this plan and use it for the next run through
currentEndNode = outPath.get(1);
}
// Return success.
// Note that outPath contains the last path we considered which is at level zero
return true;
}
@Override
public boolean searchConnectionPath (N startNode, N endNode, Heuristic heuristic, GraphPath> outPath) {
// Check if we have no path to find
if (startNode == endNode) return true;
// Set up our initial pair of nodes
N currentStartNode = startNode;
N currentEndNode = endNode;
int levelOfNodes = 0;
// Descend through levels of the graph
int currentLevel = graph.getLevelCount() - 1;
while (currentLevel >= 0) {
// Find the start node at current level
currentStartNode = graph.convertNodeBetweenLevels(0, startNode, currentLevel);
// Find the end node at current level
// Note that if we're examining level 0 and the current end node, the end node and the
// start node have the same parent at level 1 then we can use the end node directly.
currentEndNode = graph.convertNodeBetweenLevels(levelOfNodes, currentEndNode, currentLevel);
if (currentLevel == 0) {
N currentEndNodeParent = graph.convertNodeBetweenLevels(0, currentEndNode, 1);
if (currentEndNodeParent == graph.convertNodeBetweenLevels(0, endNode, 1)
&& currentEndNodeParent == graph.convertNodeBetweenLevels(0, startNode, 1)) {
currentEndNode = endNode;
}
}
// Decrease current level and skip it if start and end node are the same
levelOfNodes = currentLevel;
currentLevel--;
if (currentStartNode == currentEndNode) continue;
// Otherwise we can perform the plan
graph.setLevel(levelOfNodes);
outPath.clear();
boolean pathFound = levelPathFinder.searchConnectionPath(currentStartNode, currentEndNode, heuristic, outPath);
if (!pathFound) return false;
// Now take the first move of this plan and use it for the next run through
currentEndNode = outPath.get(0).getToNode();
}
// Return success.
// Note that outPath contains the last path we considered which is at level zero
return true;
}
@Override
public boolean search (PathFinderRequest request, long timeToRun) {
if (DEBUG) System.out.println("Enter interruptible HPF; request.status = " + request.status);
// Make sure the level request and its control are instantiated
if (levelRequest == null) {
levelRequest = new LevelPathFinderRequest();
levelRequestControl = new PathFinderRequestControl();
}
// We have to initialize the search if the status has just changed
if (request.statusChanged) {
if (DEBUG) System.out.println("-- statusChanged");
// Check if we have no path to find
if (request.startNode == request.endNode) return true;
// Prepare the the level request control
levelRequestControl.lastTime = TimeUtils.nanoTime(); // Keep track of the current time
levelRequestControl.timeToRun = timeToRun;
levelRequestControl.timeTolerance = PathFinderQueue.TIME_TOLERANCE;
levelRequestControl.server = null;
levelRequestControl.pathFinder = levelPathFinder;
// Prepare the level request
levelRequest.hpf = this;
levelRequest.hpfRequest = request;
levelRequest.status = PathFinderRequest.SEARCH_NEW;
levelRequest.statusChanged = true;
levelRequest.heuristic = request.heuristic;
levelRequest.resultPath = request.resultPath;
levelRequest.startNode = request.startNode;
levelRequest.endNode = request.endNode;
levelRequest.levelOfNodes = 0;
levelRequest.currentLevel = graph.getLevelCount() - 1;
}
while (levelRequest.currentLevel >= 0) {
// if (DEBUG) System.out.println("currentLevel = "+levelRequest.currentLevel);
boolean finished = levelRequestControl.execute(levelRequest);
// if (DEBUG) System.out.println("finished = "+finished);
// if (DEBUG) System.out.println("pathFound = "+levelRequest.pathFound);
// if (finished && !levelRequest.pathFound) return true;
if (!finished) {
return false;
}
else {
levelRequest.executionFrames = 0;
// levelRequest.pathFound = false;
levelRequest.status = PathFinderRequest.SEARCH_NEW;
levelRequest.statusChanged = true;
if (!levelRequest.pathFound) return true;
}
}
if (DEBUG) System.out.println("-- before exit");
// If we're here we have finished
return true;
}
static class LevelPathFinderRequest extends PathFinderRequest {
HierarchicalPathFinder hpf;
PathFinderRequest hpfRequest;
int levelOfNodes;
int currentLevel;
@Override
public boolean initializeSearch (long timeToRun) {
// Reset the status
// We can do it here because we know this method completes during this frame,
// meaning that it is executed once per request
this.executionFrames = 0;
this.pathFound = false;
this.status = SEARCH_NEW;
this.statusChanged = false;
do {
// Find the start node at current level
startNode = hpf.graph.convertNodeBetweenLevels(0, hpfRequest.startNode, currentLevel);
// Find the end node at current level
// Note that if we're examining level 0 and the current end node, the end node and the
// start node have the same parent at level 1 then we can use the end node directly.
endNode = hpf.graph.convertNodeBetweenLevels(levelOfNodes, endNode, currentLevel);
if (currentLevel == 0) {
N currentEndNodeParent = hpf.graph.convertNodeBetweenLevels(0, endNode, 1);
if (currentEndNodeParent == hpf.graph.convertNodeBetweenLevels(0, hpfRequest.endNode, 1)
&& currentEndNodeParent == hpf.graph.convertNodeBetweenLevels(0, hpfRequest.startNode, 1)) {
endNode = hpfRequest.endNode;
}
}
// Decrease current level and skip it if start and end node are the same
// FIXME the break below is wrong
if (DEBUG) System.out.println("LevelPathFinder initializeSearch");
levelOfNodes = currentLevel;
currentLevel--;
if (startNode != endNode) break;
} while (currentLevel >= 0);
// Otherwise we can perform the plan
hpf.graph.setLevel(levelOfNodes);
resultPath.clear();
return true;
}
@Override
public boolean search (PathFinder pathFinder, long timeToRun) {
if (DEBUG) System.out.println("LevelPathFinder search; status: " + status);
return super.search(pathFinder, timeToRun);
}
@Override
public boolean finalizeSearch (long timeToRun) {
hpfRequest.pathFound = pathFound;
if (pathFound) {
// Take the first move of this plan and use it for the next run through
endNode = resultPath.get(1);
}
if (DEBUG) System.out.println("LevelPathFinder finalizeSearch; status: " + status);
return true;
}
}
}