using System.Collections.Generic; using Pathfinding.Serialization; using UnityEngine; namespace Pathfinding { /// Node used for the GridGraph public class GridNode : GridNodeBase { public GridNode (AstarPath astar) : base(astar) { } #if !ASTAR_NO_GRID_GRAPH private static GridGraph[] _gridGraphs = new GridGraph[0]; public static GridGraph GetGridGraph (uint graphIndex) { return _gridGraphs[(int)graphIndex]; } public static void SetGridGraph (int graphIndex, GridGraph graph) { if (_gridGraphs.Length <= graphIndex) { var gg = new GridGraph[graphIndex+1]; for (int i = 0; i < _gridGraphs.Length; i++) gg[i] = _gridGraphs[i]; _gridGraphs = gg; } _gridGraphs[graphIndex] = graph; } public static void ClearGridGraph (int graphIndex, GridGraph graph) { if (graphIndex < _gridGraphs.Length && _gridGraphs[graphIndex] == graph) { _gridGraphs[graphIndex] = null; } } /// Internal use only internal ushort InternalGridFlags { get { return gridFlags; } set { gridFlags = value; } } const int GridFlagsConnectionOffset = 0; const int GridFlagsConnectionBit0 = 1 << GridFlagsConnectionOffset; const int GridFlagsConnectionMask = 0xFF << GridFlagsConnectionOffset; const int GridFlagsEdgeNodeOffset = 10; const int GridFlagsEdgeNodeMask = 1 << GridFlagsEdgeNodeOffset; public override bool HasConnectionsToAllEightNeighbours { get { return (InternalGridFlags & GridFlagsConnectionMask) == GridFlagsConnectionMask; } } /// /// True if the node has a connection in the specified direction. /// The dir parameter corresponds to directions in the grid as: /// /// Z /// | /// | /// /// 6 2 5 /// \ | / /// -- 3 - X - 1 ----- X /// / | \ /// 7 0 4 /// /// | /// | /// /// /// See: SetConnectionInternal /// public override bool HasConnectionInDirection (int dir) { return (gridFlags >> dir & GridFlagsConnectionBit0) != 0; } /// /// True if the node has a connection in the specified direction. /// Deprecated: Use HasConnectionInDirection /// [System.Obsolete("Use HasConnectionInDirection")] public bool GetConnectionInternal (int dir) { return HasConnectionInDirection(dir); } /// /// Enables or disables a connection in a specified direction on the graph. /// See: HasConnectionInDirection /// public void SetConnectionInternal (int dir, bool value) { // Set bit number #dir to 1 or 0 depending on #value unchecked { gridFlags = (ushort)(gridFlags & ~((ushort)1 << GridFlagsConnectionOffset << dir) | (value ? (ushort)1 : (ushort)0) << GridFlagsConnectionOffset << dir); } AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } /// /// Sets the state of all grid connections. /// /// See: SetConnectionInternal /// /// a bitmask of the connections (bit 0 is the first connection, bit 1 the second connection, etc.). public void SetAllConnectionInternal (int connections) { unchecked { gridFlags = (ushort)((gridFlags & ~GridFlagsConnectionMask) | (connections << GridFlagsConnectionOffset)); } AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } /// /// Disables all grid connections from this node. /// Note: Other nodes might still be able to get to this node. /// Therefore it is recommended to also disable the relevant connections on adjacent nodes. /// public void ResetConnectionsInternal () { unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsConnectionMask); } AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } /// /// Work in progress for a feature that required info about which nodes were at the border of the graph. /// Note: This property is not functional at the moment. /// public bool EdgeNode { get { return (gridFlags & GridFlagsEdgeNodeMask) != 0; } set { unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsEdgeNodeMask | (value ? GridFlagsEdgeNodeMask : 0)); } } } public override GridNodeBase GetNeighbourAlongDirection (int direction) { if (HasConnectionInDirection(direction)) { GridGraph gg = GetGridGraph(GraphIndex); return gg.nodes[NodeInGridIndex+gg.neighbourOffsets[direction]]; } return null; } public override void ClearConnections (bool alsoReverse) { if (alsoReverse) { // Note: This assumes that all connections are bidirectional // which should hold for all grid graphs unless some custom code has been added for (int i = 0; i < 8; i++) { var other = GetNeighbourAlongDirection(i) as GridNode; if (other != null) { // Remove reverse connection. See doc for GridGraph.neighbourOffsets to see which indices are used for what. other.SetConnectionInternal(i < 4 ? ((i + 2) % 4) : (((i-2) % 4) + 4), false); } } } ResetConnectionsInternal(); #if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS base.ClearConnections(alsoReverse); #endif } public override void GetConnections (System.Action action) { GridGraph gg = GetGridGraph(GraphIndex); int[] neighbourOffsets = gg.neighbourOffsets; var nodes = gg.nodes; for (int i = 0; i < 8; i++) { if (HasConnectionInDirection(i)) { var other = nodes[NodeInGridIndex + neighbourOffsets[i]]; if (other != null) action(other); } } #if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS base.GetConnections(action); #endif } public override Vector3 ClosestPointOnNode (Vector3 p) { var gg = GetGridGraph(GraphIndex); // Convert to graph space p = gg.transform.InverseTransform(p); // Calculate graph position of this node int x = NodeInGridIndex % gg.width; int z = NodeInGridIndex / gg.width; // Handle the y coordinate separately float y = gg.transform.InverseTransform((Vector3)position).y; var closestInGraphSpace = new Vector3(Mathf.Clamp(p.x, x, x+1f), y, Mathf.Clamp(p.z, z, z+1f)); // Convert to world space return gg.transform.Transform(closestInGraphSpace); } public override bool GetPortal (GraphNode other, List left, List right, bool backwards) { if (backwards) return true; GridGraph gg = GetGridGraph(GraphIndex); int[] neighbourOffsets = gg.neighbourOffsets; var nodes = gg.nodes; for (int i = 0; i < 4; i++) { if (HasConnectionInDirection(i) && other == nodes[NodeInGridIndex + neighbourOffsets[i]]) { Vector3 middle = ((Vector3)(position + other.position))*0.5f; Vector3 cross = Vector3.Cross(gg.collision.up, (Vector3)(other.position-position)); cross.Normalize(); cross *= gg.nodeSize*0.5f; left.Add(middle - cross); right.Add(middle + cross); return true; } } for (int i = 4; i < 8; i++) { if (HasConnectionInDirection(i) && other == nodes[NodeInGridIndex + neighbourOffsets[i]]) { bool rClear = false; bool lClear = false; if (HasConnectionInDirection(i-4)) { var n2 = nodes[NodeInGridIndex + neighbourOffsets[i-4]]; if (n2.Walkable && n2.HasConnectionInDirection((i-4+1)%4)) { rClear = true; } } if (HasConnectionInDirection((i-4+1)%4)) { var n2 = nodes[NodeInGridIndex + neighbourOffsets[(i-4+1)%4]]; if (n2.Walkable && n2.HasConnectionInDirection(i-4)) { lClear = true; } } Vector3 middle = ((Vector3)(position + other.position))*0.5f; Vector3 cross = Vector3.Cross(gg.collision.up, (Vector3)(other.position-position)); cross.Normalize(); cross *= gg.nodeSize*1.4142f; left.Add(middle - (lClear ? cross : Vector3.zero)); right.Add(middle + (rClear ? cross : Vector3.zero)); return true; } } return false; } public override void UpdateRecursiveG (Path path, PathNode pathNode, PathHandler handler) { GridGraph gg = GetGridGraph(GraphIndex); int[] neighbourOffsets = gg.neighbourOffsets; var nodes = gg.nodes; pathNode.UpdateG(path); handler.heap.Add(pathNode); ushort pid = handler.PathID; var index = NodeInGridIndex; for (int i = 0; i < 8; i++) { if (HasConnectionInDirection(i)) { var other = nodes[index + neighbourOffsets[i]]; PathNode otherPN = handler.GetPathNode(other); if (otherPN.parent == pathNode && otherPN.pathID == pid) other.UpdateRecursiveG(path, otherPN, handler); } } #if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS base.UpdateRecursiveG(path, pathNode, handler); #endif } #if ASTAR_JPS /* * Non Cyclic * [0] = -Y * [1] = +X * [2] = +Y * [3] = -X * [4] = -Y+X * [5] = +Y+X * [6] = +Y-X * [7] = -Y-X * * Cyclic * [0] = -X * [1] = -X+Y * [2] = +Y * [3] = +X+Y * [4] = +X * [5] = +X-Y * [6] = -Y * [7] = -Y-X */ static byte[] JPSForced = { 0xFF, // Shouldn't really happen 0, (1<<3), 0, 0, 0, (1<<5), 0 }; static byte[] JPSForcedDiagonal = { 0xFF, // Shouldn't really happen (1<<2), 0, 0, 0, 0, 0, (1<<6) }; /// /// Permutation of the standard grid node neighbour order to put them on a clockwise cycle around the node. /// Enables easier math in some cases /// static int[] JPSCyclic = { 6, 4, 2, 0, 5, 3, 1, 7 }; /// Inverse permutation of static int[] JPSInverseCyclic = { 3, // 0 6, // 1 2, // 2 5, // 3 1, // 4 4, // 5 0, // 6 7 // 7 }; const int JPSNaturalStraightNeighbours = 1 << 4; const int JPSNaturalDiagonalNeighbours = (1 << 5) | (1 << 4) | (1 << 3); /// Memoization of what results to return from the Jump method. GridNodeBase[] JPSCache; /// /// Each byte is a bitfield where each bit indicates if direction number i should return null from the Jump method. /// Used as a cache. /// I would like to make this a ulong instead, but that could run into data races. /// byte[] JPSDead; ushort[] JPSLastCacheID; /// /// Executes a straight jump search. /// See: http://en.wikipedia.org/wiki/Jump_point_search /// static GridNodeBase JPSJumpStraight (GridNode node, Path path, PathHandler handler, int parentDir, int depth = 0) { GridGraph gg = GetGridGraph(node.GraphIndex); int[] neighbourOffsets = gg.neighbourOffsets; var nodes = gg.nodes; var origin = node; // Indexing into the cache arrays from multiple threads like this should cause // a lot of false sharing and cache trashing, but after profiling it seems // that this is not a major concern int threadID = handler.threadID; int threadOffset = 8*handler.threadID; int cyclicParentDir = JPSCyclic[parentDir]; GridNodeBase result = null; // Rotate 180 degrees const int forwardDir = 4; int forwardOffset = neighbourOffsets[JPSInverseCyclic[(forwardDir + cyclicParentDir) % 8]]; // Move forwards in the same direction // until a node is encountered which we either // * know the result for (memoization) // * is a special node (flag2 set) // * has custom connections // * the node has a forced neighbour // Then break out of the loop // and start another loop which goes through the same nodes and sets the // memoization caches to avoid expensive calls in the future while (true) { // This is needed to make sure different threads don't overwrite each others results // It doesn't matter if we throw away some caching done by other threads as this will only // happen during the first few path requests if (node.JPSLastCacheID == null || node.JPSLastCacheID.Length < handler.totalThreadCount) { lock (node) { // Check again in case another thread has already created the array if (node.JPSLastCacheID == null || node.JPSLastCacheID.Length < handler.totalThreadCount) { node.JPSCache = new GridNode[8*handler.totalThreadCount]; node.JPSDead = new byte[handler.totalThreadCount]; node.JPSLastCacheID = new ushort[handler.totalThreadCount]; } } } if (node.JPSLastCacheID[threadID] != path.pathID) { for (int i = 0; i < 8; i++) node.JPSCache[i + threadOffset] = null; node.JPSLastCacheID[threadID] = path.pathID; node.JPSDead[threadID] = 0; } // Cache earlier results, major optimization // It is important to read from it once and then return the same result, // if we read from it twice, we might get different results due to other threads clearing the array sometimes var cachedResult = node.JPSCache[parentDir + threadOffset]; if (cachedResult != null) { result = cachedResult; break; } if (((node.JPSDead[threadID] >> parentDir)&1) != 0) return null; // Special node (e.g end node), take care of if (handler.GetPathNode(node).flag2) { //Debug.Log ("Found end Node!"); //Debug.DrawRay ((Vector3)position, Vector3.up*2, Color.green); result = node; break; } #if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS // Special node which has custom connections, take care of if (node.connections != null && node.connections.Length > 0) { result = node; break; } #endif // These are the nodes this node is connected to, one bit for each of the 8 directions int noncyclic = node.gridFlags;//We don't actually need to & with this because we don't use the other bits. & 0xFF; int cyclic = 0; for (int i = 0; i < 8; i++) cyclic |= ((noncyclic >> i)&0x1) << JPSCyclic[i]; int forced = 0; // Loop around to be able to assume -X is where we came from cyclic = ((cyclic >> cyclicParentDir) | ((cyclic << 8) >> cyclicParentDir)) & 0xFF; //for ( int i = 0; i < 8; i++ ) if ( ((cyclic >> i)&1) == 0 ) forced |= JPSForced[i]; if ((cyclic & (1 << 2)) == 0) forced |= (1<<3); if ((cyclic & (1 << 6)) == 0) forced |= (1<<5); int natural = JPSNaturalStraightNeighbours; // Check if there are any forced neighbours which we can reach that are not natural neighbours //if ( ((forced & cyclic) & (~(natural & cyclic))) != 0 ) { if ((forced & (~natural) & cyclic) != 0) { // Some of the neighbour nodes are forced result = node; break; } // Make sure we can reach the next node if ((cyclic & (1 << forwardDir)) != 0) { node = nodes[node.nodeInGridIndex + forwardOffset] as GridNode; //Debug.DrawLine ( (Vector3)position + Vector3.up*0.2f*(depth), (Vector3)other.position + Vector3.up*0.2f*(depth+1), Color.magenta); } else { result = null; break; } } if (result == null) { while (origin != node) { origin.JPSDead[threadID] |= (byte)(1 << parentDir); origin = nodes[origin.nodeInGridIndex + forwardOffset] as GridNode; } } else { while (origin != node) { origin.JPSCache[parentDir + threadOffset] = result; origin = nodes[origin.nodeInGridIndex + forwardOffset] as GridNode; } } return result; } /// /// Executes a diagonal jump search. /// See: http://en.wikipedia.org/wiki/Jump_point_search /// GridNodeBase JPSJumpDiagonal (Path path, PathHandler handler, int parentDir, int depth = 0) { // Indexing into the cache arrays from multiple threads like this should cause // a lot of false sharing and cache trashing, but after profiling it seems // that this is not a major concern int threadID = handler.threadID; int threadOffset = 8*handler.threadID; // This is needed to make sure different threads don't overwrite each others results // It doesn't matter if we throw away some caching done by other threads as this will only // happen during the first few path requests if (JPSLastCacheID == null || JPSLastCacheID.Length < handler.totalThreadCount) { lock (this) { // Check again in case another thread has already created the array if (JPSLastCacheID == null || JPSLastCacheID.Length < handler.totalThreadCount) { JPSCache = new GridNode[8*handler.totalThreadCount]; JPSDead = new byte[handler.totalThreadCount]; JPSLastCacheID = new ushort[handler.totalThreadCount]; } } } if (JPSLastCacheID[threadID] != path.pathID) { for (int i = 0; i < 8; i++) JPSCache[i + threadOffset] = null; JPSLastCacheID[threadID] = path.pathID; JPSDead[threadID] = 0; } // Cache earlier results, major optimization // It is important to read from it once and then return the same result, // if we read from it twice, we might get different results due to other threads clearing the array sometimes var cachedResult = JPSCache[parentDir + threadOffset]; if (cachedResult != null) { //return cachedResult; } //if ( ((JPSDead[threadID] >> parentDir)&1) != 0 ) return null; // Special node (e.g end node), take care of if (handler.GetPathNode(this).flag2) { //Debug.Log ("Found end Node!"); //Debug.DrawRay ((Vector3)position, Vector3.up*2, Color.green); JPSCache[parentDir + threadOffset] = this; return this; } #if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS // Special node which has custom connections, take care of if (connections != null && connections.Length > 0) { JPSCache[parentDir] = this; return this; } #endif int noncyclic = gridFlags;//We don't actually need to & with this because we don't use the other bits. & 0xFF; int cyclic = 0; for (int i = 0; i < 8; i++) cyclic |= ((noncyclic >> i)&0x1) << JPSCyclic[i]; int forced = 0; int cyclicParentDir = JPSCyclic[parentDir]; // Loop around to be able to assume -X is where we came from cyclic = ((cyclic >> cyclicParentDir) | ((cyclic << 8) >> cyclicParentDir)) & 0xFF; int natural; for (int i = 0; i < 8; i++) if (((cyclic >> i)&1) == 0) forced |= JPSForcedDiagonal[i]; natural = JPSNaturalDiagonalNeighbours; /* * if ( ((Vector3)position - new Vector3(1.5f,0,-1.5f)).magnitude < 0.5f ) { * Debug.Log (noncyclic + " " + parentDir + " " + cyclicParentDir); * Debug.Log (System.Convert.ToString (cyclic, 2)+"\n"+System.Convert.ToString (noncyclic, 2)+"\n"+System.Convert.ToString (natural, 2)+"\n"+System.Convert.ToString (forced, 2)); * }*/ // Don't force nodes we cannot reach anyway forced &= cyclic; natural &= cyclic; if ((forced & (~natural)) != 0) { // Some of the neighbour nodes are forced JPSCache[parentDir+threadOffset] = this; return this; } int forwardDir; GridGraph gg = GetGridGraph(GraphIndex); int[] neighbourOffsets = gg.neighbourOffsets; var nodes = gg.nodes; { // Rotate 180 degrees - 1 node forwardDir = 3; if (((cyclic >> forwardDir)&1) != 0) { int oi = JPSInverseCyclic[(forwardDir + cyclicParentDir) % 8]; var other = nodes[nodeInGridIndex + neighbourOffsets[oi]]; //Debug.DrawLine ( (Vector3)position + Vector3.up*0.2f*(depth), (Vector3)other.position + Vector3.up*0.2f*(depth+1), Color.black); GridNodeBase v; if (oi < 4) { v = JPSJumpStraight(other as GridNode, path, handler, JPSInverseCyclic[(cyclicParentDir-1+8)%8], depth+1); } else { v = (other as GridNode).JPSJumpDiagonal(path, handler, JPSInverseCyclic[(cyclicParentDir-1+8)%8], depth+1); } if (v != null) { JPSCache[parentDir+threadOffset] = this; return this; } } // Rotate 180 degrees + 1 node forwardDir = 5; if (((cyclic >> forwardDir)&1) != 0) { int oi = JPSInverseCyclic[(forwardDir + cyclicParentDir) % 8]; var other = nodes[nodeInGridIndex + neighbourOffsets[oi]]; //Debug.DrawLine ( (Vector3)position + Vector3.up*0.2f*(depth), (Vector3)other.position + Vector3.up*0.2f*(depth+1), Color.grey); GridNodeBase v; if (oi < 4) { v = JPSJumpStraight(other as GridNode, path, handler, JPSInverseCyclic[(cyclicParentDir+1+8)%8], depth+1); } else { v = (other as GridNode).JPSJumpDiagonal(path, handler, JPSInverseCyclic[(cyclicParentDir+1+8)%8], depth+1); } if (v != null) { JPSCache[parentDir+threadOffset] = this; return this; } } } // Rotate 180 degrees forwardDir = 4; if (((cyclic >> forwardDir)&1) != 0) { int oi = JPSInverseCyclic[(forwardDir + cyclicParentDir) % 8]; var other = nodes[nodeInGridIndex + neighbourOffsets[oi]] as GridNode; //Debug.DrawLine ( (Vector3)position + Vector3.up*0.2f*(depth), (Vector3)other.position + Vector3.up*0.2f*(depth+1), Color.magenta); var v = other.JPSJumpDiagonal(path, handler, parentDir, depth+1); if (v != null) { JPSCache[parentDir+threadOffset] = v; return v; } } JPSDead[threadID] |= (byte)(1 << parentDir); return null; } /// /// Opens a node using Jump Point Search. /// See: http://en.wikipedia.org/wiki/Jump_point_search /// public void JPSOpen (Path path, PathNode pathNode, PathHandler handler) { GridGraph gg = GetGridGraph(GraphIndex); int[] neighbourOffsets = gg.neighbourOffsets; var nodes = gg.nodes; ushort pid = handler.PathID; int noncyclic = gridFlags & 0xFF; int cyclic = 0; for (int i = 0; i < 8; i++) cyclic |= ((noncyclic >> i)&0x1) << JPSCyclic[i]; var parent = pathNode.parent != null ? pathNode.parent.node as GridNode : null; int parentDir = -1; if (parent != null) { int diff = parent != null ? parent.nodeInGridIndex - nodeInGridIndex : 0; int x2 = nodeInGridIndex % gg.width; int x1 = parent.nodeInGridIndex % gg.width; if (diff < 0) { if (x1 == x2) { parentDir = 0; } else if (x1 < x2) { parentDir = 7; } else { parentDir = 4; } } else { if (x1 == x2) { parentDir = 1; } else if (x1 < x2) { parentDir = 6; } else { parentDir = 5; } } } int cyclicParentDir = 0; // Check for -1 int forced = 0; if (parentDir != -1) { cyclicParentDir = JPSCyclic[parentDir]; // Loop around to be able to assume -X is where we came from cyclic = ((cyclic >> cyclicParentDir) | ((cyclic << 8) >> cyclicParentDir)) & 0xFF; } else { forced = 0xFF; //parentDir = 0; } bool diagonal = parentDir >= 4; int natural; if (diagonal) { for (int i = 0; i < 8; i++) if (((cyclic >> i)&1) == 0) forced |= JPSForcedDiagonal[i]; natural = JPSNaturalDiagonalNeighbours; } else { for (int i = 0; i < 8; i++) if (((cyclic >> i)&1) == 0) forced |= JPSForced[i]; natural = JPSNaturalStraightNeighbours; } // Don't force nodes we cannot reach anyway forced &= cyclic; natural &= cyclic; int nb = forced | natural; /*if ( ((Vector3)position - new Vector3(0.5f,0,3.5f)).magnitude < 0.5f ) { * Debug.Log (noncyclic + " " + parentDir + " " + cyclicParentDir); * Debug.Log (System.Convert.ToString (cyclic, 2)+"\n"+System.Convert.ToString (noncyclic, 2)+"\n"+System.Convert.ToString (natural, 2)+"\n"+System.Convert.ToString (forced, 2)); * }*/ for (int i = 0; i < 8; i++) { if (((nb >> i)&1) != 0) { int oi = JPSInverseCyclic[(i + cyclicParentDir) % 8]; var other = nodes[nodeInGridIndex + neighbourOffsets[oi]]; #if ASTARDEBUG if (((forced >> i)&1) != 0) { Debug.DrawLine((Vector3)position, Vector3.Lerp((Vector3)other.position, (Vector3)position, 0.6f), Color.red); } if (((natural >> i)&1) != 0) { Debug.DrawLine((Vector3)position + Vector3.up*0.2f, Vector3.Lerp((Vector3)other.position, (Vector3)position, 0.6f) + Vector3.up*0.2f, Color.green); } #endif if (oi < 4) { other = JPSJumpStraight(other as GridNode, path, handler, JPSInverseCyclic[(i + 4 + cyclicParentDir) % 8]); } else { other = (other as GridNode).JPSJumpDiagonal(path, handler, JPSInverseCyclic[(i + 4 + cyclicParentDir) % 8]); } if (other != null) { //Debug.DrawLine ( (Vector3)position + Vector3.up*0.0f, (Vector3)other.position + Vector3.up*0.3f, Color.cyan); //Debug.DrawRay ( (Vector3)other.position, Vector3.up, Color.cyan); //GridNode other = nodes[nodeInGridIndex + neighbourOffsets[i]]; //if (!path.CanTraverse (other)) continue; PathNode otherPN = handler.GetPathNode(other); if (otherPN.pathID != pid) { otherPN.parent = pathNode; otherPN.pathID = pid; otherPN.cost = (uint)(other.position - position).costMagnitude;//neighbourCosts[i]; otherPN.H = path.CalculateHScore(other); otherPN.UpdateG(path); //Debug.Log ("G " + otherPN.G + " F " + otherPN.F); handler.heap.Add(otherPN); //Debug.DrawRay ((Vector3)otherPN.node.Position, Vector3.up,Color.blue); } else { //If not we can test if the path from the current node to this one is a better one then the one already used uint tmpCost = (uint)(other.position - position).costMagnitude;//neighbourCosts[i]; if (pathNode.G+tmpCost+path.GetTraversalCost(other) < otherPN.G) { //Debug.Log ("Path better from " + NodeIndex + " to " + otherPN.node.NodeIndex + " " + (pathNode.G+tmpCost+path.GetTraversalCost(other)) + " < " + otherPN.G); otherPN.cost = tmpCost; otherPN.parent = pathNode; other.UpdateRecursiveG(path, otherPN, handler); } } } } #if ASTARDEBUG if (i == 0 && parentDir != -1 && this.nodeInGridIndex > 10) { int oi = JPSInverseCyclic[(i + cyclicParentDir) % 8]; if (nodeInGridIndex + neighbourOffsets[oi] < 0 || nodeInGridIndex + neighbourOffsets[oi] >= nodes.Length) { //Debug.LogError ("ERR: " + (nodeInGridIndex + neighbourOffsets[oi]) + " " + cyclicParentDir + " " + parentDir + " Reverted " + oi); //Debug.DrawRay ((Vector3)position, Vector3.up, Color.red); } else { var other = nodes[nodeInGridIndex + neighbourOffsets[oi]]; Debug.DrawLine((Vector3)position - Vector3.up*0.2f, Vector3.Lerp((Vector3)other.position, (Vector3)position, 0.6f) - Vector3.up*0.2f, Color.blue); } } #endif } } #endif public override void Open (Path path, PathNode pathNode, PathHandler handler) { GridGraph gg = GetGridGraph(GraphIndex); ushort pid = handler.PathID; #if ASTAR_JPS if (gg.useJumpPointSearch && !path.FloodingPath) { JPSOpen(path, pathNode, handler); } else #endif { int[] neighbourOffsets = gg.neighbourOffsets; uint[] neighbourCosts = gg.neighbourCosts; GridNodeBase[] nodes = gg.nodes; var index = NodeInGridIndex; for (int i = 0; i < 8; i++) { if (HasConnectionInDirection(i)) { GridNodeBase other = nodes[index + neighbourOffsets[i]]; if (!path.CanTraverse(other)) continue; PathNode otherPN = handler.GetPathNode(other); uint tmpCost = neighbourCosts[i]; // Check if the other node has not yet been visited by this path if (otherPN.pathID != pid) { otherPN.parent = pathNode; otherPN.pathID = pid; otherPN.cost = tmpCost; otherPN.H = path.CalculateHScore(other); otherPN.UpdateG(path); handler.heap.Add(otherPN); } else { // Sorry for the huge number of #ifs //If not we can test if the path from the current node to this one is a better one then the one already used #if ASTAR_NO_TRAVERSAL_COST if (pathNode.G+tmpCost < otherPN.G) #else if (pathNode.G+tmpCost+path.GetTraversalCost(other) < otherPN.G) #endif { //Debug.Log ("Path better from " + NodeIndex + " to " + otherPN.node.NodeIndex + " " + (pathNode.G+tmpCost+path.GetTraversalCost(other)) + " < " + otherPN.G); otherPN.cost = tmpCost; otherPN.parent = pathNode; other.UpdateRecursiveG(path, otherPN, handler); } } } } } #if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS base.Open(path, pathNode, handler); #endif } public override void SerializeNode (GraphSerializationContext ctx) { base.SerializeNode(ctx); ctx.SerializeInt3(position); ctx.writer.Write(gridFlags); } public override void DeserializeNode (GraphSerializationContext ctx) { base.DeserializeNode(ctx); position = ctx.DeserializeInt3(); gridFlags = ctx.reader.ReadUInt16(); } public override void AddConnection (GraphNode node, uint cost) { // In case the node was already added as an internal grid connection, // we need to remove that connection before we insert it as a custom connection. // Using a custom connection is necessary because it has a custom cost. if (node is GridNode gn && gn.GraphIndex == GraphIndex) { RemoveGridConnection(gn); } base.AddConnection(node, cost); } public override void RemoveConnection (GraphNode node) { base.RemoveConnection(node); // If the node is a grid node on the same graph, it might be added as an internal connection and not a custom one. if (node is GridNode gn && gn.GraphIndex == GraphIndex) { RemoveGridConnection(gn); } } /// /// Removes a connection from the internal grid connections. /// See: SetConnectionInternal /// protected void RemoveGridConnection (GridNode node) { var nodeIndex = NodeInGridIndex; var gg = GetGridGraph(GraphIndex); for (int i = 0; i < 8; i++) { if (nodeIndex + gg.neighbourOffsets[i] == node.NodeInGridIndex && GetNeighbourAlongDirection(i) == node) { SetConnectionInternal(i, false); break; } } } #else public override void AddConnection (GraphNode node, uint cost) { throw new System.NotImplementedException(); } public override void ClearConnections (bool alsoReverse) { throw new System.NotImplementedException(); } public override void GetConnections (GraphNodeDelegate del) { throw new System.NotImplementedException(); } public override void Open (Path path, PathNode pathNode, PathHandler handler) { throw new System.NotImplementedException(); } public override void RemoveConnection (GraphNode node) { throw new System.NotImplementedException(); } #endif } }