#if !ASTAR_NO_GRID_GRAPH #if !ASTAR_LEVELGRIDNODE_MORE_LAYERS #define ASTAR_LEVELGRIDNODE_FEW_LAYERS #endif using UnityEngine; using System.Collections.Generic; using Pathfinding.Serialization; namespace Pathfinding { /// /// Describes a single node for the LayerGridGraph. /// Works almost the same as a grid node, except that it also stores to which layer the connections go to /// public class LevelGridNode : GridNodeBase { public LevelGridNode() { } public LevelGridNode (AstarPath astar) { astar.InitializeNode(this); } private static LayerGridGraph[] _gridGraphs = new LayerGridGraph[0]; public static LayerGridGraph GetGridGraph (uint graphIndex) { return _gridGraphs[(int)graphIndex]; } public static void SetGridGraph (int graphIndex, LayerGridGraph graph) { // LayeredGridGraphs also show up in the grid graph list // This is required by e.g the XCoordinateInGrid properties GridNode.SetGridGraph(graphIndex, graph); if (_gridGraphs.Length <= graphIndex) { var newGraphs = new LayerGridGraph[graphIndex+1]; for (int i = 0; i < _gridGraphs.Length; i++) newGraphs[i] = _gridGraphs[i]; _gridGraphs = newGraphs; } _gridGraphs[graphIndex] = graph; } public static void ClearGridGraph (int graphIndex, LayerGridGraph graph) { if (graphIndex < _gridGraphs.Length && _gridGraphs[graphIndex] == graph) { _gridGraphs[graphIndex] = null; } } #if ASTAR_LEVELGRIDNODE_FEW_LAYERS public uint gridConnections; #else public ulong gridConnections; #endif protected static LayerGridGraph[] gridGraphs; const int MaxNeighbours = 8; #if ASTAR_LEVELGRIDNODE_FEW_LAYERS public const int ConnectionMask = 0xF; public const int ConnectionStride = 4; public const int AxisAlignedConnectionsMask = 0xFFFF; public const uint AllConnectionsMask = 0xFFFFFFFF; #else public const int ConnectionMask = 0xFF; public const int ConnectionStride = 8; public const ulong AxisAlignedConnectionsMask = 0xFFFFFFFF; public const ulong AllConnectionsMask = 0xFFFFFFFFFFFFFFFF; #endif public const int NoConnection = ConnectionMask; internal const ulong DiagonalConnectionsMask = ((ulong)NoConnection << 4*ConnectionStride) | ((ulong)NoConnection << 5*ConnectionStride) | ((ulong)NoConnection << 6*ConnectionStride) | ((ulong)NoConnection << 7*ConnectionStride); /// /// Maximum number of layers the layered grid graph supports. /// /// This can be changed in the A* Inspector -> Optimizations tab by enabling or disabling the ASTAR_LEVELGRIDNODE_MORE_LAYERS option. /// public const int MaxLayerCount = ConnectionMask; /// /// Removes all grid connections from this node. /// /// Warning: Using this method can make the graph data inconsistent. It's recommended to use other ways to update the graph, instead. /// public override void ResetConnectionsInternal () { #if ASTAR_LEVELGRIDNODE_FEW_LAYERS gridConnections = unchecked ((uint)-1); #else gridConnections = unchecked ((ulong)-1); #endif AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } #if ASTAR_LEVELGRIDNODE_FEW_LAYERS public override bool HasAnyGridConnections => gridConnections != unchecked ((uint)-1); #else public override bool HasAnyGridConnections => gridConnections != unchecked ((ulong)-1); #endif public override bool HasConnectionsToAllEightNeighbours { get { for (int i = 0; i < 8; i++) { if (!HasConnectionInDirection(i)) return false; } return true; } } public override bool HasConnectionsToAllAxisAlignedNeighbours { get { return (gridConnections & AxisAlignedConnectionsMask) == AxisAlignedConnectionsMask; } } /// /// Layer coordinate of the node in the grid. /// If there are multiple nodes in the same (x,z) cell, then they will be stored in different layers. /// Together with NodeInGridIndex, you can look up the node in the nodes array /// /// int index = node.NodeInGridIndex + node.LayerCoordinateInGrid * graph.width * graph.depth; /// Assert(node == graph.nodes[index]); /// /// /// See: XCoordInGrid /// See: ZCoordInGrid /// See: NodeInGridIndex /// public int LayerCoordinateInGrid { get { return nodeInGridIndex >> NodeInGridIndexLayerOffset; } set { nodeInGridIndex = (nodeInGridIndex & NodeInGridIndexMask) | (value << NodeInGridIndexLayerOffset); } } public void SetPosition (Int3 position) { this.position = position; } public override int GetGizmoHashCode () { return base.GetGizmoHashCode() ^ (int)((805306457UL * gridConnections) ^ (402653189UL * (gridConnections >> 32))); } public override GridNodeBase GetNeighbourAlongDirection (int direction) { int conn = GetConnectionValue(direction); if (conn != NoConnection) { LayerGridGraph graph = GetGridGraph(GraphIndex); return graph.nodes[NodeInGridIndex+graph.neighbourOffsets[direction] + graph.lastScannedWidth*graph.lastScannedDepth*conn]; } return null; } public override void ClearConnections (bool alsoReverse) { if (alsoReverse) { LayerGridGraph graph = GetGridGraph(GraphIndex); int[] neighbourOffsets = graph.neighbourOffsets; var nodes = graph.nodes; for (int i = 0; i < MaxNeighbours; i++) { int conn = GetConnectionValue(i); if (conn != LevelGridNode.NoConnection) { var other = nodes[NodeInGridIndex+neighbourOffsets[i] + graph.lastScannedWidth*graph.lastScannedDepth*conn] as LevelGridNode; if (other != null) { // Remove reverse connection other.SetConnectionValue((i + 2) % 4, NoConnection); } } } } ResetConnectionsInternal(); #if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS base.ClearConnections(alsoReverse); #endif } public override void GetConnections(GetConnectionsWithData action, ref T data, int connectionFilter) { if ((connectionFilter & (Connection.IncomingConnection | Connection.OutgoingConnection)) == 0) return; LayerGridGraph graph = GetGridGraph(GraphIndex); int[] neighbourOffsets = graph.neighbourOffsets; var nodes = graph.nodes; int index = NodeInGridIndex; for (int i = 0; i < MaxNeighbours; i++) { int conn = GetConnectionValue(i); if (conn != LevelGridNode.NoConnection) { var other = nodes[index+neighbourOffsets[i] + graph.lastScannedWidth*graph.lastScannedDepth*conn]; if (other != null) action(other, ref data); } } #if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS base.GetConnections(action, ref data, connectionFilter); #endif } public override bool HasConnectionInDirection (int direction) { return ((gridConnections >> direction*ConnectionStride) & ConnectionMask) != NoConnection; } /// /// Set which layer a grid connection goes to. /// /// Warning: Using this method can make the graph data inconsistent. It's recommended to use other ways to update the graph, instead. /// /// Direction for the connection. /// The layer of the connected node or #NoConnection if there should be no connection in that direction. public void SetConnectionValue (int dir, int value) { #if ASTAR_LEVELGRIDNODE_FEW_LAYERS gridConnections = gridConnections & ~(((uint)NoConnection << dir*ConnectionStride)) | ((uint)value << dir*ConnectionStride); #else gridConnections = gridConnections & ~(((ulong)NoConnection << dir*ConnectionStride)) | ((ulong)value << dir*ConnectionStride); #endif AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } #if ASTAR_LEVELGRIDNODE_FEW_LAYERS public void SetAllConnectionInternal (ulong value) { gridConnections = (uint)value; } #else public void SetAllConnectionInternal (ulong value) { gridConnections = value; } #endif /// /// Which layer a grid connection goes to. /// Returns: The layer of the connected node or if there is no connection in that direction. /// /// Direction for the connection. public int GetConnectionValue (int dir) { return (int)((gridConnections >> dir*ConnectionStride) & ConnectionMask); } public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) { // 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 LevelGridNode gn && gn.GraphIndex == GraphIndex) { RemoveGridConnection(gn); } base.AddPartialConnection(node, cost, isOutgoing, isIncoming); } public override void RemovePartialConnection (GraphNode node) { base.RemovePartialConnection(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 LevelGridNode gn && gn.GraphIndex == GraphIndex) { RemoveGridConnection(gn); } } /// /// Removes a connection from the internal grid connections, not the list of custom connections. /// See: SetConnectionValue /// protected void RemoveGridConnection (LevelGridNode 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) { SetConnectionValue(i, NoConnection); break; } } } public override bool GetPortal (GraphNode other, out Vector3 left, out Vector3 right) { LayerGridGraph graph = GetGridGraph(GraphIndex); int[] neighbourOffsets = graph.neighbourOffsets; var nodes = graph.nodes; int index = NodeInGridIndex; for (int i = 0; i < MaxNeighbours; i++) { int conn = GetConnectionValue(i); if (conn != LevelGridNode.NoConnection) { if (other == nodes[index+neighbourOffsets[i] + graph.lastScannedWidth*graph.lastScannedDepth*conn]) { Vector3 middle = ((Vector3)(position + other.position))*0.5f; Vector3 cross = Vector3.Cross(graph.collision.up, (Vector3)(other.position-position)); cross.Normalize(); cross *= graph.nodeSize*0.5f; left = middle - cross; right = middle + cross; return true; } } } left = Vector3.zero; right = Vector3.zero; return false; } public override void Open (Path path, uint pathNodeIndex, uint gScore) { LayerGridGraph graph = GetGridGraph(GraphIndex); int[] neighbourOffsets = graph.neighbourOffsets; uint[] neighbourCosts = graph.neighbourCosts; var nodes = graph.nodes; int index = NodeInGridIndex; // Bitmask of the 8 connections out of this node. // Each bit represents one connection. // We only use this to be able to dynamically handle // things like cutCorners and other diagonal connection filtering // based on things like the tags or ITraversalProvider set for just this path. // It starts off with all connections enabled but then in the following loop // we will remove connections which are not traversable. // When we get to the first diagonal connection we run a pass to // filter out any diagonal connections which shouldn't be enabled. // See the documentation for FilterDiagonalConnections for more info. // The regular grid graph does a similar thing. var conns = 0xFF; for (int dir = 0; dir < MaxNeighbours; dir++) { if (dir == 4 && (path.traversalProvider == null || path.traversalProvider.filterDiagonalGridConnections)) { conns = GridNode.FilterDiagonalConnections(conns, graph.neighbours, graph.cutCorners); } int conn = GetConnectionValue(dir); if (conn != LevelGridNode.NoConnection && ((conns >> dir) & 0x1) != 0) { GraphNode other = nodes[index+neighbourOffsets[dir] + graph.lastScannedWidth*graph.lastScannedDepth*conn]; if (!path.CanTraverse(this, other)) { conns &= ~(1 << dir); continue; } path.OpenCandidateConnection(pathNodeIndex, other.NodeIndex, gScore, neighbourCosts[dir], 0, other.position); } else { conns &= ~(1 << dir); } } base.Open(path, pathNodeIndex, gScore); } public override void SerializeNode (GraphSerializationContext ctx) { base.SerializeNode(ctx); ctx.SerializeInt3(position); ctx.writer.Write(gridFlags); // gridConnections are now always serialized as 64 bits for easier compatibility handling #if ASTAR_LEVELGRIDNODE_FEW_LAYERS // Convert from 32 bits to 64-bits ulong connectionsLong = 0; for (int i = 0; i < 8; i++) connectionsLong |= (ulong)GetConnectionValue(i) << (i*8); #else ulong connectionsLong = gridConnections; #endif ctx.writer.Write(connectionsLong); } public override void DeserializeNode (GraphSerializationContext ctx) { base.DeserializeNode(ctx); position = ctx.DeserializeInt3(); gridFlags = ctx.reader.ReadUInt16(); if (ctx.meta.version < AstarSerializer.V4_3_12) { // Note: assumes ASTAR_LEVELGRIDNODE_FEW_LAYERS was false when saving, which was the default // This info not saved with the graph unfortunately and in 4.3.12 the default changed. ulong conns; if (ctx.meta.version < AstarSerializer.V3_9_0) { // Set the upper 32 bits for compatibility conns = ctx.reader.ReadUInt32() | (((ulong)NoConnection << 56) | ((ulong)NoConnection << 48) | ((ulong)NoConnection << 40) | ((ulong)NoConnection << 32)); } else { conns = ctx.reader.ReadUInt64(); } const int stride = 8; const int mask = (1 << stride) - 1; gridConnections = 0; for (int i = 0; i < 8; i++) { var y = (conns >> (i*stride)) & mask; // 4.3.12 by default only supports 15 layers. So we may have to disable some connections when loading from earlier versions. if ((y & ConnectionMask) != y) y = NoConnection; SetConnectionValue(i, (int)y); } } else { var gridConnectionsLong = ctx.reader.ReadUInt64(); #if ASTAR_LEVELGRIDNODE_FEW_LAYERS uint c = 0; if (ctx.meta.version < AstarSerializer.V4_3_83) { // The default during 4.3.12..4.3.83 was that ASTAR_LEVELGRIDNODE_FEW_LAYERS was enabled, but it was serialized just as 32-bits zero-extended to 64 bits c = (uint)gridConnectionsLong; } else { // Convert from 64 bits to 32-bits for (int i = 0; i < 8; i++) { c |= ((uint)(gridConnectionsLong >> (i*8)) & LevelGridNode.ConnectionMask) << (LevelGridNode.ConnectionStride*i); } } gridConnections = c; #else gridConnections = gridConnectionsLong; #endif } } } } #endif