#define PREALLOCATE_NODES
using UnityEngine;
using Pathfinding.Serialization;
namespace Pathfinding {
/// Base class for GridNode and LevelGridNode
public abstract class GridNodeBase : GraphNode {
const int GridFlagsWalkableErosionOffset = 8;
const int GridFlagsWalkableErosionMask = 1 << GridFlagsWalkableErosionOffset;
const int GridFlagsWalkableTmpOffset = 9;
const int GridFlagsWalkableTmpMask = 1 << GridFlagsWalkableTmpOffset;
public const int NodeInGridIndexLayerOffset = 24;
protected const int NodeInGridIndexMask = 0xFFFFFF;
///
/// Bitfield containing the x and z coordinates of the node as well as the layer (for layered grid graphs).
/// See: NodeInGridIndex
///
protected int nodeInGridIndex;
protected ushort gridFlags;
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
///
/// Custon non-grid connections from this node.
/// See:
/// See:
///
/// This field is removed if the ASTAR_GRID_NO_CUSTOM_CONNECTIONS compiler directive is used.
/// Removing it can save a tiny bit of memory. You can enable the define in the Optimizations tab in the A* inspector.
/// See: compiler-directives (view in online documentation for working links)
///
/// Note: If you modify this array or the contents of it you must call .
///
public Connection[] connections;
#endif
///
/// The index of the node in the grid.
/// This is x + z*graph.width
/// So you can get the X and Z indices using
///
/// int index = node.NodeInGridIndex;
/// int x = index % graph.width;
/// int z = index / graph.width;
/// // where graph is GridNode.GetGridGraph (node.graphIndex), i.e the graph the nodes are contained in.
///
///
/// See:
///
public int NodeInGridIndex { get { return nodeInGridIndex & NodeInGridIndexMask; } set { nodeInGridIndex = (nodeInGridIndex & ~NodeInGridIndexMask) | value; } }
///
/// X coordinate of the node in the grid.
/// The node in the bottom left corner has (x,z) = (0,0) and the one in the opposite
/// corner has (x,z) = (width-1, depth-1)
///
/// See:
/// See:
///
public int XCoordinateInGrid => NodeInGridIndex % GridNode.GetGridGraph(GraphIndex).width;
///
/// Z coordinate of the node in the grid.
/// The node in the bottom left corner has (x,z) = (0,0) and the one in the opposite
/// corner has (x,z) = (width-1, depth-1)
///
/// See:
/// See:
///
public int ZCoordinateInGrid => NodeInGridIndex / GridNode.GetGridGraph(GraphIndex).width;
///
/// The X and Z coordinates of the node in the grid.
///
/// The node in the bottom left corner has (x,z) = (0,0) and the one in the opposite
/// corner has (x,z) = (width-1, depth-1)
///
/// See:
/// See:
/// See:
///
public Vector2Int CoordinatesInGrid {
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get {
var width = GridNode.GetGridGraph(GraphIndex).width;
var index = NodeInGridIndex;
var z = index / width;
var x = index - z * width;
return new Vector2Int(x, z);
}
}
///
/// Stores walkability before erosion is applied.
/// Used internally when updating the graph.
///
public bool WalkableErosion {
get {
return (gridFlags & GridFlagsWalkableErosionMask) != 0;
}
set {
unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsWalkableErosionMask | (value ? (ushort)GridFlagsWalkableErosionMask : (ushort)0)); }
}
}
/// Temporary variable used internally when updating the graph.
public bool TmpWalkable {
get {
return (gridFlags & GridFlagsWalkableTmpMask) != 0;
}
set {
unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsWalkableTmpMask | (value ? (ushort)GridFlagsWalkableTmpMask : (ushort)0)); }
}
}
///
/// True if the node has grid connections to all its 8 neighbours.
/// Note: This will always return false if GridGraph.neighbours is set to anything other than Eight.
/// See: GetNeighbourAlongDirection
/// See:
///
public abstract bool HasConnectionsToAllEightNeighbours { get; }
///
/// True if the node has grid connections to all its 4 axis-aligned neighbours.
/// See: GetNeighbourAlongDirection
/// See:
///
public abstract bool HasConnectionsToAllAxisAlignedNeighbours { get; }
///
/// The connection opposite the given one.
///
///
/// Z
/// |
/// |
///
/// 6 2 5
/// \ | /
/// -- 3 - X - 1 ----- X
/// / | \
/// 7 0 4
///
/// |
/// |
///
///
/// For example, dir=1 outputs 3, dir=6 outputs 4 and so on.
///
/// See:
///
public static int OppositeConnectionDirection (int dir) {
return dir < 4 ? ((dir + 2) % 4) : (((dir-2) % 4) + 4);
}
///
/// Converts from dx + 3*dz to a neighbour direction.
///
/// Used by .
///
/// Assumes that dx and dz are both in the range [0,2].
/// See:
///
internal static readonly int[] offsetToDirection = { 7, 0, 4, 3, -1, 1, 6, 2, 5 };
///
/// Converts from a delta (dx, dz) to a neighbour direction.
///
/// For example, if dx=1 and dz=0, the return value will be 1, which is the direction to the right of a grid coordinate.
///
/// If dx=0 and dz=0, the return value will be -1.
///
/// See:
///
///
/// Z
/// |
/// |
///
/// 6 2 5
/// \ | /
/// -- 3 - X - 1 ----- X
/// / | \
/// 7 0 4
///
/// |
/// |
///
///
/// See:
///
/// X coordinate delta. Should be in the range [-1, 1]. Values outside this range will cause -1 to be returned.
/// Z coordinate delta. Should be in the range [-1, 1]. Values outside this range will cause -1 to be returned.
public static int OffsetToConnectionDirection (int dx, int dz) {
dx++; dz++;
if ((uint)dx > 2 || (uint)dz > 2) return -1;
return offsetToDirection[3*dz + dx];
}
///
/// Projects the given point onto the plane of this node's surface.
///
/// The point will be projected down to a plane that contains the surface of the node.
/// If the point is not contained inside the node, it is projected down onto this plane anyway.
///
public Vector3 ProjectOnSurface (Vector3 point) {
var gg = GridNode.GetGridGraph(GraphIndex);
var nodeCenter = (Vector3)position;
var up = gg.transform.WorldUpAtGraphPosition(nodeCenter);
return point - up * Vector3.Dot(up, point - nodeCenter);
}
public override Vector3 ClosestPointOnNode (Vector3 p) {
var gg = GridNode.GetGridGraph(GraphIndex);
// Convert to graph space
var nodeCenter = (Vector3)position;
// Calculate the offset from the node's center to the given point in graph space
var offsetInGraphSpace = gg.transform.InverseTransformVector(p - nodeCenter);
// Project onto the node's surface
offsetInGraphSpace.y = 0;
// Clamp to the node's extents
offsetInGraphSpace.x = Mathf.Clamp(offsetInGraphSpace.x, -0.5f, 0.5f);
offsetInGraphSpace.z = Mathf.Clamp(offsetInGraphSpace.z, -0.5f, 0.5f);
// Convert back to world space
return nodeCenter + gg.transform.TransformVector(offsetInGraphSpace);
}
///
/// Checks if point is inside the node when seen from above.
///
/// The borders of a node are considered to be inside the node.
///
/// Note that is faster than this method as it avoids
/// some coordinate transformations. If you are repeatedly calling this method
/// on many different nodes but with the same point then you should consider
/// transforming the point first and then calling ContainsPointInGraphSpace.
///
/// Int3 p = (Int3)graph.transform.InverseTransform(point);
///
/// node.ContainsPointInGraphSpace(p);
///
///
public override bool ContainsPoint (Vector3 point) {
var gg = Graph as GridGraph;
// Convert to graph space
return ContainsPointInGraphSpace((Int3)gg.transform.InverseTransform(point));
}
///
/// Checks if point is inside the node in graph space.
///
/// The borders of a node are considered to be inside the node.
///
/// The y coordinate of the point is ignored.
///
public override bool ContainsPointInGraphSpace (Int3 point) {
// Calculate graph position of this node
var x = XCoordinateInGrid*Int3.Precision;
var z = ZCoordinateInGrid*Int3.Precision;
return point.x >= x && point.x <= x + Int3.Precision && point.z >= z && point.z <= z + Int3.Precision;
}
public override float SurfaceArea () {
GridGraph gg = GridNode.GetGridGraph(GraphIndex);
return gg.nodeSize*gg.nodeSize;
}
public override Vector3 RandomPointOnSurface () {
GridGraph gg = GridNode.GetGridGraph(GraphIndex);
var graphSpacePosition = gg.transform.InverseTransform((Vector3)position);
var r = AstarMath.ThreadSafeRandomFloat2();
return gg.transform.Transform(graphSpacePosition + new Vector3(r.x - 0.5f, 0, r.y - 0.5f));
}
///
/// Transforms a world space point to a normalized point on this node's surface.
/// (0.5,0.5) represents the node's center. (0,0), (1,0), (1,1) and (0,1) each represent the corners of the node.
///
/// See:
///
public Vector2 NormalizePoint (Vector3 worldPoint) {
GridGraph gg = GridNode.GetGridGraph(GraphIndex);
var graphSpacePosition = gg.transform.InverseTransform(worldPoint);
return new Vector2(graphSpacePosition.x - this.XCoordinateInGrid, graphSpacePosition.z - this.ZCoordinateInGrid);
}
///
/// Transforms a normalized point on this node's surface to a world space point.
/// (0.5,0.5) represents the node's center. (0,0), (1,0), (1,1) and (0,1) each represent the corners of the node.
///
/// See:
///
public Vector3 UnNormalizePoint (Vector2 normalizedPointOnSurface) {
GridGraph gg = GridNode.GetGridGraph(GraphIndex);
return (Vector3)this.position + gg.transform.TransformVector(new Vector3(normalizedPointOnSurface.x - 0.5f, 0, normalizedPointOnSurface.y - 0.5f));
}
public override int GetGizmoHashCode () {
var hash = base.GetGizmoHashCode();
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
if (connections != null) {
for (int i = 0; i < connections.Length; i++) {
hash ^= 17 * connections[i].GetHashCode();
}
}
#endif
hash ^= 109 * gridFlags;
return hash;
}
///
/// Adjacent grid node in the specified direction.
/// This will return null if the node does not have a connection to a node
/// in that direction.
///
/// The dir parameter corresponds to directions in the grid as:
///
/// Z
/// |
/// |
///
/// 6 2 5
/// \ | /
/// -- 3 - X - 1 ----- X
/// / | \
/// 7 0 4
///
/// |
/// |
///
///
/// See:
/// See:
///
/// Note: This method only takes grid connections into account, not custom connections (i.e. those added using or using node links).
///
public abstract GridNodeBase GetNeighbourAlongDirection(int direction);
///
/// True if the node has a connection to an adjecent node 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:
/// See:
/// See:
///
/// Note: This method only takes grid connections into account, not custom connections (i.e. those added using or using node links).
///
public virtual bool HasConnectionInDirection (int direction) {
// TODO: Can be optimized if overriden in each subclass
return GetNeighbourAlongDirection(direction) != null;
}
/// True if this node has any grid connections
public abstract bool HasAnyGridConnections { get; }
public override bool ContainsOutgoingConnection (GraphNode node) {
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
if (connections != null) {
for (int i = 0; i < connections.Length; i++) {
if (connections[i].node == node && connections[i].isOutgoing) {
return true;
}
}
}
#endif
for (int i = 0; i < 8; i++) {
if (node == GetNeighbourAlongDirection(i)) {
return true;
}
}
return false;
}
///
/// 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 abstract void ResetConnectionsInternal();
public override void OpenAtPoint (Path path, uint pathNodeIndex, Int3 pos, uint gScore) {
path.OpenCandidateConnectionsToEndNode(pos, pathNodeIndex, pathNodeIndex, gScore);
path.OpenCandidateConnection(pathNodeIndex, NodeIndex, gScore, 0, 0, position);
}
public override void Open (Path path, uint pathNodeIndex, uint gScore) {
path.OpenCandidateConnectionsToEndNode(position, pathNodeIndex, pathNodeIndex, gScore);
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
if (connections != null) {
for (int i = 0; i < connections.Length; i++) {
GraphNode other = connections[i].node;
if (!connections[i].isOutgoing || !path.CanTraverse(this, other)) continue;
path.OpenCandidateConnection(pathNodeIndex, other.NodeIndex, gScore, connections[i].cost, 0, other.position);
}
}
#endif
}
#if ASTAR_GRID_NO_CUSTOM_CONNECTIONS
public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) {
throw new System.NotImplementedException("GridNodes do not have support for adding manual connections with your current settings."+
"\nPlease disable ASTAR_GRID_NO_CUSTOM_CONNECTIONS in the Optimizations tab in the A* Inspector");
}
public override void RemovePartialConnection (GraphNode node) {
// Nothing to do because ASTAR_GRID_NO_CUSTOM_CONNECTIONS is enabled
}
public void ClearCustomConnections (bool alsoReverse) {
}
#else
/// Same as , but does not clear grid connections, only custom ones (e.g added by or a NodeLink component)
public void ClearCustomConnections (bool alsoReverse) {
if (connections != null) {
for (int i = 0; i < connections.Length; i++) connections[i].node.RemovePartialConnection(this);
connections = null;
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
}
public override void ClearConnections (bool alsoReverse) {
ClearCustomConnections(alsoReverse);
}
public override void GetConnections(GetConnectionsWithData action, ref T data, int connectionFilter) {
if (connections == null) return;
for (int i = 0; i < connections.Length; i++) if ((connections[i].shapeEdgeInfo & connectionFilter) != 0) action(connections[i].node, ref data);
}
public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) {
if (node == null) throw new System.ArgumentNullException();
if (connections != null) {
for (int i = 0; i < connections.Length; i++) {
if (connections[i].node == node) {
connections[i].cost = cost;
connections[i].shapeEdgeInfo = Connection.PackShapeEdgeInfo(isOutgoing, isIncoming);
return;
}
}
}
int connLength = connections != null ? connections.Length : 0;
var newconns = new Connection[connLength+1];
for (int i = 0; i < connLength; i++) {
newconns[i] = connections[i];
}
newconns[connLength] = new Connection(node, cost, isOutgoing, isIncoming);
connections = newconns;
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
///
/// Removes any connection from this node to the specified node.
/// If no such connection exists, nothing will be done.
///
/// Note: This only removes the connection from this node to the other node.
/// You may want to call the same function on the other node to remove its eventual connection
/// to this node.
///
/// Version: Before 4.3.48 This method only handled custom connections (those added using link components or the AddConnection method).
/// Regular grid connections had to be added or removed using . Starting with 4.3.48 this method
/// can remove all types of connections.
///
public override void RemovePartialConnection (GraphNode node) {
if (connections == null) return;
for (int i = 0; i < connections.Length; i++) {
if (connections[i].node == node) {
int connLength = connections.Length;
var newconns = new Connection[connLength-1];
for (int j = 0; j < i; j++) {
newconns[j] = connections[j];
}
for (int j = i+1; j < connLength; j++) {
newconns[j-1] = connections[j];
}
connections = newconns;
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
return;
}
}
}
public override void SerializeReferences (GraphSerializationContext ctx) {
ctx.SerializeConnections(connections, true);
}
public override void DeserializeReferences (GraphSerializationContext ctx) {
// Grid nodes didn't serialize references before 3.8.3
if (ctx.meta.version < AstarSerializer.V3_8_3)
return;
connections = ctx.DeserializeConnections(ctx.meta.version >= AstarSerializer.V4_3_85);
}
#endif
}
}