/// <summary> /// Calculates a path from start to end. When no path can be found in /// reasonable time the search is aborted and an incomplete path is returned. /// When refresh is not set to true a cached path is returned where possible. /// </summary> /// <param name="start">start position in 2d map space</param> /// <param name="end">end position in 2d map space</param> /// <param name="refresh">force to recalculate the path</param> /// <returns></returns> public TilePath CalculatePath(Point2D start, Point2D end, bool refresh) { // swap points to calculate the path backwards (from end to start) Point2D temp = end; end = start; start = temp; // Check whether the requested path is already known var request = new TilePathRequest(start, end); if (!refresh && mPathCache.ContainsKey(request)) { return(mPathCache[request].Copy()); } // priority queue of nodes that yet have to be explored sorted in // ascending order by node costs (F) var open = new AutoPriorityQueue <TilePathNode>(); // List of nodes that have already been explored var closed = new LinkedList <ITileCell>(); // Start is to be explored first var startNode = new TilePathNode(null, mLayer.GetCell(start), end); open.Enqueue(startNode); var steps = 0; do { // Examine the cheapest node among the yet to be explored var current = open.Dequeue(); // Finish? if (current.Cell.Matches(end) || ++steps > mStepLimit) { // Paths which lead to the requested goal are cached for reuse var path = new TilePath(current); if (mPathCache.ContainsKey(request)) { mPathCache[request] = path.Copy(); } else { mPathCache.Add(request, path.Copy()); } return(path); } // Explore all neighbours of the current cell var neighbours = mLayer.GetNeighbourCells(current.Cell); foreach (var cell in neighbours) { // Discard nodes that are not of interest var flag = mCollisionLayer[cell.X, cell.Y]; if (closed.Contains(cell) || (cell.Matches(end) == false && (flag & ECollisionType.NotMoveable) != ECollisionType.NotMoveable)) { continue; } // Successor is one of current's neighbours var successor = new TilePathNode(current, cell, end); var contained = open.Find(successor); if (contained != null && successor.CostTotal >= contained.CostTotal) { // This cell is already in the open list represented by // another node that is cheaper continue; } if (contained != null && successor.CostTotal < contained.CostTotal) { // This cell is already in the open list but on a more expensive // path -> "integrate" the node into the current path contained.Predecessor = current; contained.Update(); open.Update(contained); } else { // The cell is not in the open list and therefore still has to // be explored open.Enqueue(successor); } } // Add current to the list of the already explored nodes closed.AddLast(current.Cell); } while (open.Peek() != null); return(null); }