/// <summary> /// Check if this GraphNode can be passed; eg. is it solid of empty? /// </summary> /// <param name="startingNode">The node we are travelling from.</param> /// <returns>True if if the node can be travelled to.</returns> public override Boolean IsPassable(GraphNode startingNode) { // If this GraphNode is not storing a tile, or that Tile is not empty, thats // an instant fail. if (!IsEmpty()) { return false; } // We should not have mismatch GraphNode objects, so pData should be Level.Tile in this case. Level.Tile tile = startingNode.pData as Level.Tile; // Loop through all adjacent tiles to figure out which direction we are travelling in. // We need that data in order to determine if this is a legal diagonal move. for (Int32 i = 0; i < tile.mAdjecentTiles.Length; i++) { if (mTile == tile.mAdjecentTiles[i]) { if (Level.IsAttemptingInvalidDiagonalMove((Level.Tile.AdjacentTileDir)i, tile)) { return false; } else { // No need to continue searching. break; } } } return true; }
/// <summary> /// When finished using a Node which was retrieved from GetNode, pass it back to /// this function for clean up. /// </summary> /// <param name="node"></param> public void RecycleNode(GraphNode node) { node.Reset(); #if DEBUG node.mInUse = false; #endif // DEBUG mUnusedNavMeshTileGraphNodes.Enqueue(node as NavMeshTileGraphNode); }
/// <summary> /// These nodes get used over and over again, so we need to make sure they /// get completely reset between uses. /// </summary> public void Reset() { mGraphNode = null; mPrevious = null; mCostFromStart = 0; mCostToDestination = 0; mReached = false; mPathSolved = false; }
/// <summary> /// Renders a single GraphNode and optionally links to all it's neighbours. Derived Graph classes should try /// to use this function even if overriding DebugDraw so that there is a consistent look to Graph objects. /// </summary> /// <param name="node">The GraphNode to render.</param> /// <param name="showLinks">True to draw links to neighbouring GraphNode objects.</param> protected virtual void DrawNode(GraphNode node, Boolean showLinks) { // Draw the node a circle. DebugShapeDisplay.pInstance.AddCircle(node.pPosition, 4.0f, Color.DarkRed); // If requested show the links between this node and all his neighbours. if (showLinks) { for (Int32 j = 0; j < node.pNeighbours.Count; j++) { Color costColor = Color.Lerp(Color.White, Color.Red, (node.pNeighbours[j].mCostToTravel - 8.0f) / (3.0f * 11.134f)); DebugShapeDisplay.pInstance.AddSegment(node.pPosition, node.pNeighbours[j].mGraphNode.pPosition, costColor); } } }
/// <summary> /// Doesn't actually do anthing different in Release, but in Debug it does some checks for /// duplicate entries. Doing this for all Graph objects would be too expensive even for testing. /// </summary> /// <param name="node">The node to add.</param> public override void AddNode(GraphNode node) { /* for(Int32 i = 0; i < pNodes.Count; i++) { if (pNodes[i].pPosition == node.pPosition) { //System.Diagnostics.Debug.Assert(false, "Attempting to add GraphNode at dupe position."); } if (pNodes.Contains(node)) { //System.Diagnostics.Debug.Assert(false, "Attempting to add Dupe GraphNode."); } } */ base.AddNode(node); }
/// <summary> /// Update where this Planner is travelling from. The position is used to look up a tile at that /// position in the world. /// </summary> /// <param name="source">The position in the world that the planner will start at.</param> public Boolean SetSource(GraphNode source) { //System.Diagnostics.Debug.Assert(source != mEnd || source == null, "Source and Destination are the same."); //HPAStar.NavMesh.DebugCheckNode(source); if (mStart != source) { mStart = source; mPathInvalidated = true; mSolved = false; return true; } return false; }
/// <summary> /// Similar to SetDestination, except in this case the existing path is not invalidated. Instead /// the path searching continues as if the supplied destination were the original destination. /// </summary> /// <param name="destination">The node to try and reach.</param> /// <returns></returns> public Boolean ExtendDestination(GraphNode destination) { if (mEnd != destination) { // Make sure that we are not trying to extend the destination // to a Node that has already be Closed. If that is the case // then the path needs to be regenerated with fresh data. for (Int32 i = 0; i < mClosedNodes.Count; i++) { if (mClosedNodes[i].pGraphNode == destination) { //DebugMessageDisplay.pInstance.AddConstantMessage("Extending with closed node."); return SetDestination(destination); } } mEnd = destination; // If the path was previously solved, it is not any longer. mSolved = false; // If the path was already solved, it needs to be told otherwise. mBestPathEnd.pPathSolved = false; return true; } return false; }
/// <summary> /// Checks if a Node and it's neighbours are erronously flagged as being not in use or contain /// null data. /// </summary> /// <param name="node">The node to check. The node assumed to be in use.</param> static public void DebugCheckNode(GraphNode node) { if (node is NavMeshTileGraphNode) { System.Diagnostics.Debug.Assert(node.mInUse, "Node being used but flag as not in use."); System.Diagnostics.Debug.Assert(node.pData != null, "pData is null."); for (Int32 j = 0; j < node.pNeighbours.Count; j++) { System.Diagnostics.Debug.Assert(node.pNeighbours[j].mGraphNode.mInUse, "Node being used but flag as not in use."); System.Diagnostics.Debug.Assert(node.pNeighbours[j].mGraphNode.pData != null, "pData is null."); } } }
/// <summary> /// Take two GraphNode objects and remove any link they have as Neighbour. /// </summary> /// <param name="a"></param> /// <param name="b"></param> public void UnlinkGraphNodes(GraphNode a, GraphNode b) { a.RemoveNeighbour(b); b.RemoveNeighbour(a); //if (a != null) //{ //DebugWalkGraphForErrors(a as NavMeshTileGraphNode); //} //DebugCheckNode(a); //DebugCheckNode(b); }
/// <summary> /// Removes a Neighbour by looking at the GraphNode stored in each one. /// </summary> /// <param name="neighbour">The GraphNode in the Neighbour that should be removed.</param> public virtual void RemoveNeighbour(GraphNode neighbour) { for (Int32 i = 0; i < mNeighbours.Count; i++) { if (mNeighbours[i].mGraphNode == neighbour) { //HPAStar.NavMesh.DebugCheckNode(mNeighbours[i].mGraphNode); mNeighbours[i].Reset(); // Put this Neighbour back into the Queue so others can reuse it. mUnusedNeighbours.Enqueue(mNeighbours[i]); mNeighbours.RemoveAt(i); // Should be no dupes so return after finding one. return; } } }
/// <summary> /// Adds a neighbour to this GraphNode. /// </summary> /// <param name="node">The neighbouring GraphNode.</param> /// <param name="cost"> /// The cost to travel from this GraphNode to <paramref name="node"/>. If not supplied the cost will /// be the distance between the two GraphNode objects. /// </param> public virtual void AddNeighbour(GraphNode node, Single cost) { //HPAStar.NavMesh.DebugCheckNode(node); // Create a new neighbour to wrap the node passed in. Neighbour temp = mUnusedNeighbours.Dequeue(); // Put it back into a default state. temp.Reset(); temp.mGraphNode = node; if (cost >= 0) { temp.mCostToTravel = cost; } else { // This assumes that nodes never move. temp.mCostToTravel = Vector2.Distance(node.pPosition, pPosition); } System.Diagnostics.Debug.Assert(node.pData != null, "Uninitialized node set as neighbour"); mNeighbours.Add(temp); }
/// <summary> /// Adds a neighbour to this GraphNode. /// </summary> /// <param name="node">The neighbouring GraphNode.</param> /// <param name="cost"> /// The cost to travel from this GraphNode to <paramref name="node"/>. If not supplied the cost will /// be the distance between the two GraphNode objects. /// </param> public virtual void AddNeighbour(GraphNode node) { AddNeighbour(node, -1.0f); }
/// <summary> /// Removes a GraphNode from the Graph. /// </summary> /// <param name="node"></param> public virtual void RemoveNode(GraphNode node) { mNodes.Remove(node); }
/// <summary> /// Adds a GraphNode to the Graph. /// </summary> /// <param name="node">The node to add.</param> public virtual void AddNode(GraphNode node) { mNodes.Add(node); }
/// <summary> /// Clear out the destination that the planner is trying to reach. This doubles as a way /// to stop the planner from trying to advance. /// </summary> public void ClearDestination() { mEnd = null; mSolved = false; // Release the Nodes. We don't need them anymore. If a new destination // is set, we will need to start from scratch anyway. ClearNodeLists(); }
/// <summary> /// Helper method for checking if any of the Neighbours of this GraphNode contain a /// supplied GraphNode. /// </summary> /// <param name="neighbour"></param> /// <returns></returns> public virtual Boolean HasNeighbour(GraphNode neighbour) { for (Int32 i = 0; i < mNeighbours.Count; i++) { if (mNeighbours[i].mGraphNode == neighbour) { return true; } } return false; }
/// <summary> /// Return the planner to its original state. /// </summary> public void Reset() { mStart = null; ClearDestination(); ClearNodeLists(); }
/// <summary> /// Check if this GraphNode can be passed; eg. is it solid of empty? /// </summary> /// <param name="startingNode">The node we are travelling from.</param> /// <returns>True if if the node can be travelled to.</returns> public abstract Boolean IsPassable(GraphNode startingNode);
/// <summary> /// Links two GraphNode objects as Neighbour. Does the slightly expensive task of calculating actual /// A* path between the two, and caches that value as the cost between the two. /// </summary> /// <param name="a">A node to link to <paramref name="b"/>.</param> /// <param name="b">A node to link to <paramref name="a"/>.</param> /// <param name="cluster">The cluster containing both nodes.</param> private void LinkGraphNodes(GraphNode a, GraphNode b, Cluster cluster) { Boolean oneWay = false; mPlanner.Reset(); mPlanner.SetSource((a.pData as Level.Tile).mGraphNode); mPlanner.SetDestination((b.pData as Level.Tile).mGraphNode); // Do a standard A* search with the adde constraint of staying within the // bounds of this cluster. Planner.Result result = mPlanner.PlanPath(cluster.pBounds, false); // Keep searching unti we either fail or succeed. while(result == Planner.Result.InProgress) { result = mPlanner.PlanPath(cluster.pBounds, false); } // Only connect the nodes if they can be reached from one another within the same cluster. if (result == Planner.Result.Solved) { PathNode path = mPlanner.pCurrentBest; // Link the two neightbours. a.AddNeighbour(b, path.pFinalCost); if (!oneWay) { b.AddNeighbour(a, path.pFinalCost); } } mPlanner.ClearDestination(); //if (a != null) //{ //DebugWalkGraphForErrors(a as NavMeshTileGraphNode); //} //DebugCheckNode(a); //DebugCheckNode(b); }
/// <summary> /// Put this object into a default state. /// </summary> public void Reset() { mGraphNode = null; mCostToTravel = 0; }
/// <summary> /// Helper function for creating a Node for an entrace but also checking that it hasn't already been /// created, and if it has, just using that one instead to avoid multiple Nodes on top of the same /// tile. /// </summary> /// <param name="cluster">The custer which this node lives in.</param> /// <param name="tile">The tile which this node wraps.</param> /// <param name="node"> /// If a node already exists over <paramref name="tile"/> this will be that GraphNode. If not it will /// be a newly created GraphNode. /// </param> /// <returns>True if a new GraphNode was created.</returns> private Boolean CreateEntraceNode(Cluster cluster, Level.Tile tile, out GraphNode node) { Boolean created = false; // First see if this Tile is already managed by this cluster. If it is, it will return us // the node which contains it. node = cluster.GetNodeContaining(tile); // If the node isn't already being managed, we need to create a new one. if (null == node) { node = mNodeFactory.GetNode(); node.pData = tile; created = true; // New nodes need to be registers with the Graph. AddNode(node); } return created; }
/// <summary> /// Check if this GraphNode can be passed; eg. is it solid of empty? /// </summary> /// <param name="startingNode">The node we are travelling from.</param> /// <returns>True if if the node can be travelled to.</returns> public override Boolean IsPassable(GraphNode startingNode) { // This Node would never have been created if it weren't passable. return true; }
/// <summary> /// Removes a GraphNode from the Graph and also removes all the associated links to and from other /// GraphNode objects in this Graph. /// </summary> /// <param name="node">The GraphNode to remove.</param> public void RemoveTempNode(GraphNode node) { Cluster cluster = GetClusterAtPosition(node.pPosition); if (null != cluster) { for (Int32 i = 0; i < cluster.pNodes.Count; i++) { if (node != cluster.pNodes[i]) { UnlinkGraphNodes(node, cluster.pNodes[i]); } } cluster.RemoveNode(node); } //DebugCheckForReferences(node); mNodeFactory.RecycleNode(node); RemoveNode(node); //DebugCheckNodes(); }
/// <summary> /// Update the location that the Planner is attempting to reach. The position will be used /// to look up the tile at that position in the world. /// </summary> /// <param name="destination"></param> public Boolean SetDestination(GraphNode destination) { if (mEnd != destination) { mEnd = destination; mPathInvalidated = true; mSolved = false; return true; } return false; }
/// <summary> /// Checks if a node is reference by any Node in the Graph and their neighbours. /// </summary> /// <param name="node">The node to search for.</param> public void DebugCheckForReferences(GraphNode node) { for (Int32 i = 0; i < pNodes.Count; i++) { GraphNode next = pNodes[i]; for (Int32 j = 0; j < next.pNeighbours.Count; j++) { GraphNode nextNeighbour = next.pNeighbours[j].mGraphNode; System.Diagnostics.Debug.Assert(nextNeighbour != node, "Found node!"); } } }
/// <summary> /// Constructor. /// </summary> /// <param name="node">The GraphNode this PathNode will wrap.</param> public PathNode(GraphNode node) { mGraphNode = node; }