package org.andengine.util.algorithm.path.astar;
import org.andengine.util.adt.list.ShiftList;
import org.andengine.util.adt.map.LongSparseArray;
import org.andengine.util.adt.queue.IQueue;
import org.andengine.util.adt.queue.SortedQueue;
import org.andengine.util.adt.spatial.bounds.util.IntBoundsUtils;
import org.andengine.util.algorithm.path.ICostFunction;
import org.andengine.util.algorithm.path.IPathFinder;
import org.andengine.util.algorithm.path.IPathFinderMap;
import org.andengine.util.algorithm.path.Path;
/**
* TODO Nodes could be recycle in a pool.
*
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
* @author Greg Haynes
* @since 23:16:17 - 16.08.2010
*/
public class AStarPathFinder<T> implements IPathFinder<T> {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
// ===========================================================
// Constructors
// ===========================================================
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
public Path findPath(final IPathFinderMap<T> pPathFinderMap, final int pXMin, final int pYMin, final int pXMax, final int pYMax, final T pEntity, final int pFromX, final int pFromY, final int pToX, final int pToY, final boolean pAllowDiagonal, final IAStarHeuristic<T> pAStarHeuristic, final ICostFunction<T> pCostFunction) {
return this.findPath(pPathFinderMap, pXMin, pYMin, pXMax, pYMax, pEntity, pFromX, pFromY, pToX, pToY, pAllowDiagonal, pAStarHeuristic, pCostFunction, Float.MAX_VALUE);
}
@Override
public Path findPath(final IPathFinderMap<T> pPathFinderMap, final int pXMin, final int pYMin, final int pXMax, final int pYMax, final T pEntity, final int pFromX, final int pFromY, final int pToX, final int pToY, final boolean pAllowDiagonal, final IAStarHeuristic<T> pAStarHeuristic, final ICostFunction<T> pCostFunction, final float pMaxCost) {
return this.findPath(pPathFinderMap, pXMin, pYMin, pXMax, pYMax, pEntity, pFromX, pFromY, pToX, pToY, pAllowDiagonal, pAStarHeuristic, pCostFunction, pMaxCost, null);
}
@Override
public Path findPath(final IPathFinderMap<T> pPathFinderMap, final int pXMin, final int pYMin, final int pXMax, final int pYMax, final T pEntity, final int pFromX, final int pFromY, final int pToX, final int pToY, final boolean pAllowDiagonal, final IAStarHeuristic<T> pAStarHeuristic, final ICostFunction<T> pCostFunction, final float pMaxCost, final IPathFinderListener<T> pPathFinderListener) {
if(((pFromX == pToX) && (pFromY == pToY)) || pPathFinderMap.isBlocked(pFromX, pFromY, pEntity) || pPathFinderMap.isBlocked(pToX, pToY, pEntity)) {
return null;
}
/* Drag some fields to local variables. */
final Node fromNode = new Node(pFromX, pFromY, pAStarHeuristic.getExpectedRestCost(pPathFinderMap, pEntity, pFromX, pFromY, pToX, pToY));
final long fromNodeID = fromNode.mID;
final long toNodeID = Node.calculateID(pToX, pToY);
final LongSparseArray<Node> visitedNodes = new LongSparseArray<Node>();
final LongSparseArray<Node> openNodes = new LongSparseArray<Node>();
final IQueue<Node> sortedOpenNodes = new SortedQueue<Node>(new ShiftList<Node>());
final boolean allowDiagonalMovement = pAllowDiagonal;
/* Initialize algorithm. */
openNodes.put(fromNodeID, fromNode);
sortedOpenNodes.enter(fromNode);
Node currentNode = null;
while(openNodes.size() > 0) {
/* The first Node in the open list is the one with the lowest cost. */
currentNode = sortedOpenNodes.poll();
final long currentNodeID = currentNode.mID;
if(currentNodeID == toNodeID) {
break;
}
visitedNodes.put(currentNodeID, currentNode);
/* Loop over all neighbors of this position. */
for(int dX = -1; dX <= 1; dX++) {
for(int dY = -1; dY <= 1; dY++) {
if((dX == 0) && (dY == 0)) {
continue;
}
if(!allowDiagonalMovement && (dX != 0) && (dY != 0)) {
continue;
}
final int neighborNodeX = dX + currentNode.mX;
final int neighborNodeY = dY + currentNode.mY;
final long neighborNodeID = Node.calculateID(neighborNodeX, neighborNodeY);
if(!IntBoundsUtils.contains(pXMin, pYMin, pXMax, pYMax, neighborNodeX, neighborNodeY) || pPathFinderMap.isBlocked(neighborNodeX, neighborNodeY, pEntity)) {
continue;
}
if(visitedNodes.indexOfKey(neighborNodeID) >= 0) {
continue;
}
Node neighborNode = openNodes.get(neighborNodeID);
final boolean neighborNodeIsNew;
/* Check if neighbor exists. */
if(neighborNode == null) {
neighborNodeIsNew = true;
neighborNode = new Node(neighborNodeX, neighborNodeY, pAStarHeuristic.getExpectedRestCost(pPathFinderMap, pEntity, neighborNodeX, neighborNodeY, pToX, pToY));
} else {
neighborNodeIsNew = false;
}
/* Update cost of neighbor as cost of current plus step from current to neighbor. */
final float costFromCurrentToNeigbor = pCostFunction.getCost(pPathFinderMap, currentNode.mX, currentNode.mY, neighborNodeX, neighborNodeY, pEntity);
final float neighborNodeCost = currentNode.mCost + costFromCurrentToNeigbor;
if(neighborNodeCost > pMaxCost) {
/* Too expensive -> remove if isn't a new node. */
if(!neighborNodeIsNew) {
openNodes.remove(neighborNodeID);
}
} else {
neighborNode.setParent(currentNode, costFromCurrentToNeigbor);
if(neighborNodeIsNew) {
openNodes.put(neighborNodeID, neighborNode);
} else {
/* Remove so that re-insertion puts it to the correct spot. */
sortedOpenNodes.remove(neighborNode);
}
sortedOpenNodes.enter(neighborNode);
if(pPathFinderListener != null) {
pPathFinderListener.onVisited(pEntity, neighborNodeX, neighborNodeY);
}
}
}
}
}
/* Cleanup. */
// TODO We could just let the GC do its work.
visitedNodes.clear();
openNodes.clear();
sortedOpenNodes.clear();
/* Check if a path was found. */
if(currentNode.mID != toNodeID) {
return null;
}
/* Calculate path length. */
int length = 1;
Node tmp = currentNode;
while(tmp.mID != fromNodeID) {
tmp = tmp.mParent;
length++;
}
/* Traceback path. */
final Path path = new Path(length);
int index = length - 1;
tmp = currentNode;
while(tmp.mID != fromNodeID) {
path.set(index, tmp.mX, tmp.mY);
tmp = tmp.mParent;
index--;
}
path.set(0, pFromX, pFromY);
return path;
}
// ===========================================================
// Methods
// ===========================================================
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
private static final class Node implements Comparable<Node> {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
/* package */ Node mParent;
/* package */ final int mX;
/* package */ final int mY;
/* package */ final long mID;
/* package */ final float mExpectedRestCost;
/* package */ float mCost;
/* package */ float mTotalCost;
// ===========================================================
// Constructors
// ===========================================================
public Node(final int pX, final int pY, final float pExpectedRestCost) {
this.mX = pX;
this.mY = pY;
this.mExpectedRestCost = pExpectedRestCost;
this.mID = Node.calculateID(pX, pY);
}
// ===========================================================
// Getter & Setter
// ===========================================================
public void setParent(final Node pParent, final float pCost) {
this.mParent = pParent;
this.mCost = pCost;
this.mTotalCost = pCost + this.mExpectedRestCost;
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
public int compareTo(final Node pNode) {
final float diff = this.mTotalCost - pNode.mTotalCost;
if(diff > 0) {
return 1;
} else if(diff < 0) {
return -1;
} else {
return 0;
}
}
@Override
public boolean equals(final Object pOther) {
if(this == pOther) {
return true;
} else if(pOther == null) {
return false;
} else if(this.getClass() != pOther.getClass()) {
return false;
}
return this.equals((Node)pOther);
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " [x=" + this.mX + ", y=" + this.mY + "]";
}
// ===========================================================
// Methods
// ===========================================================
public static long calculateID(final int pX, final int pY) {
return (((long)pX) << 32) | pY;
}
public boolean equals(final Node pNode) {
return this.mID == pNode.mID;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}
}