#if !ASTAR_NO_GRID_GRAPH
using UnityEngine;
using System.Collections.Generic;
using Pathfinding.Serialization;
using Pathfinding.Graphs.Grid;
namespace Pathfinding {
///
/// Grid Graph, supports layered worlds.
/// [Open online documentation to see images]
/// The GridGraph is great in many ways, reliable, easily configured and updatable during runtime.
/// But it lacks support for worlds which have multiple layers, such as a building with multiple floors.
/// That's where this graph type comes in. It supports basically the same stuff as the grid graph, but also multiple layers.
/// It uses a bit more memory than a regular grid graph, but is otherwise equivalent.
///
/// \section layergridgraph-inspector Inspector
/// [Open online documentation to see images]
///
/// \inspectorField{Shape, inspectorGridMode}
/// \inspectorField{2D, is2D}
/// \inspectorField{Align to tilemap, AlignToTilemap}
/// \inspectorField{Width, width}
/// \inspectorField{Depth, depth}
/// \inspectorField{Node size, nodeSize}
/// \inspectorField{Aspect ratio (isometric/advanced shape), aspectRatio}
/// \inspectorField{Isometric angle (isometric/advanced shape), isometricAngle}
/// \inspectorField{Center, center}
/// \inspectorField{Rotation, rotation}
/// \inspectorField{Connections, neighbours}
/// \inspectorField{Cut corners, cutCorners}
/// \inspectorField{Max step height, maxStepHeight}
/// \inspectorField{Account for slopes, maxStepUsesSlope}
/// \inspectorField{Max slope, maxSlope}
/// \inspectorField{Erosion iterations, erodeIterations}
/// \inspectorField{Erosion → Erosion Uses Tags, erosionUseTags}
/// \inspectorField{Use 2D physics, collision.use2D}
///
/// Collision testing
/// \inspectorField{Enable Collision Testing, collision.collisionCheck}
/// \inspectorField{Collider type, collision.type}
/// \inspectorField{Diameter, collision.diameter}
/// \inspectorField{Height/length, collision.height}
/// \inspectorField{Offset, collision.collisionOffset}
/// \inspectorField{Obstacle layer mask, collision.mask}
/// \inspectorField{Preview, GridGraphEditor.collisionPreviewOpen}
///
/// Height testing
/// \inspectorField{Enable Height Testing, collision.heightCheck}
/// \inspectorField{Ray length, collision.fromHeight}
/// \inspectorField{Mask, collision.heightMask}
/// \inspectorField{Thick raycast, collision.thickRaycast}
/// \inspectorField{Unwalkable when no ground, collision.unwalkableWhenNoGround}
///
/// Rules
/// Take a look at grid-rules (view in online documentation for working links) for a list of available rules.
///
/// Other settings
/// \inspectorField{Show surface, showMeshSurface}
/// \inspectorField{Show outline, showMeshOutline}
/// \inspectorField{Show connections, showNodeConnections}
/// \inspectorField{Initial penalty, NavGraph.initialPenalty}
///
/// Note: The graph supports 16 layers by default, but it can be increased to 256 by enabling the ASTAR_LEVELGRIDNODE_MORE_LAYERS option in the A* Inspector → Settings → Optimizations tab.
///
/// See:
///
[Pathfinding.Util.Preserve]
public class LayerGridGraph : GridGraph, IUpdatableGraph {
// This function will be called when this graph is destroyed
protected override void DisposeUnmanagedData () {
base.DisposeUnmanagedData();
// Clean up a reference in a static variable which otherwise should point to this graph forever and stop the GC from collecting it
LevelGridNode.ClearGridGraph((int)graphIndex, this);
}
public LayerGridGraph () {
newGridNodeDelegate = () => new LevelGridNode();
}
protected override GridNodeBase[] AllocateNodesJob (int size, out Unity.Jobs.JobHandle dependency) {
var newNodes = new LevelGridNode[size];
dependency = active.AllocateNodes(newNodes, size, newGridNodeDelegate, 1);
return newNodes;
}
///
/// Number of layers.
/// Warning: Do not modify this variable
///
[JsonMember]
internal int layerCount;
/// Nodes with a short distance to the node above it will be set unwalkable
[JsonMember]
public float characterHeight = 0.4F;
internal int lastScannedWidth;
internal int lastScannedDepth;
public override int LayerCount {
get => layerCount;
protected set => layerCount = value;
}
public override int MaxLayers => LevelGridNode.MaxLayerCount;
public override int CountNodes () {
if (nodes == null) return 0;
int counter = 0;
for (int i = 0; i < nodes.Length; i++) {
if (nodes[i] != null) counter++;
}
return counter;
}
public override void GetNodes (System.Action action) {
if (nodes == null) return;
for (int i = 0; i < nodes.Length; i++) {
if (nodes[i] != null) action(nodes[i]);
}
}
///
/// Get all nodes in a rectangle.
/// Returns: The number of nodes written to the buffer.
///
/// Region in which to return nodes. It will be clamped to the grid.
/// Buffer in which the nodes will be stored. Should be at least as large as the number of nodes that can exist in that region.
public override int GetNodesInRegion (IntRect rect, GridNodeBase[] buffer) {
// Clamp the rect to the grid
// Rect which covers the whole grid
var gridRect = new IntRect(0, 0, width-1, depth-1);
rect = IntRect.Intersection(rect, gridRect);
if (nodes == null || !rect.IsValid() || nodes.Length != width*depth*layerCount) return 0;
int counter = 0;
try {
for (int l = 0; l < layerCount; l++) {
var lwd = l * Width * Depth;
for (int z = rect.ymin; z <= rect.ymax; z++) {
var offset = lwd + z*Width;
for (int x = rect.xmin; x <= rect.xmax; x++) {
var node = nodes[offset + x];
if (node != null) {
buffer[counter] = node;
counter++;
}
}
}
}
} catch (System.IndexOutOfRangeException) {
// Catch the exception which 'buffer[counter] = node' would throw if the buffer was too small
throw new System.ArgumentException("Buffer is too small");
}
return counter;
}
///
/// Node in the specified cell.
/// Returns null if the coordinate is outside the grid.
///
/// If you know the coordinate is inside the grid and you are looking to maximize performance then you
/// can look up the node in the internal array directly which is slightly faster.
/// See:
///
public GridNodeBase GetNode (int x, int z, int layer) {
if (x < 0 || z < 0 || x >= width || z >= depth || layer < 0 || layer >= layerCount) return null;
return nodes[x + z*width + layer*width*depth];
}
protected override IGraphUpdatePromise ScanInternal (bool async) {
LevelGridNode.SetGridGraph((int)graphIndex, this);
layerCount = 0;
lastScannedWidth = width;
lastScannedDepth = depth;
return base.ScanInternal(async);
}
protected override GridNodeBase GetNearestFromGraphSpace (Vector3 positionGraphSpace) {
if (nodes == null || depth*width*layerCount != nodes.Length) {
return null;
}
float xf = positionGraphSpace.x;
float zf = positionGraphSpace.z;
int x = Mathf.Clamp((int)xf, 0, width-1);
int z = Mathf.Clamp((int)zf, 0, depth-1);
var worldPos = transform.Transform(positionGraphSpace);
return GetNearestNode(worldPos, x, z, null);
}
private GridNodeBase GetNearestNode (Vector3 position, int x, int z, NNConstraint constraint) {
int index = width*z+x;
float minDist = float.PositiveInfinity;
GridNodeBase minNode = null;
for (int i = 0; i < layerCount; i++) {
var node = nodes[index + width*depth*i];
if (node != null) {
float dist = ((Vector3)node.position - position).sqrMagnitude;
if (dist < minDist && (constraint == null || constraint.Suitable(node))) {
minDist = dist;
minNode = node;
}
}
}
return minNode;
}
protected override void SerializeExtraInfo (GraphSerializationContext ctx) {
if (nodes == null) {
ctx.writer.Write(-1);
return;
}
ctx.writer.Write(nodes.Length);
for (int i = 0; i < nodes.Length; i++) {
if (nodes[i] == null) {
ctx.writer.Write(-1);
} else {
ctx.writer.Write(0);
nodes[i].SerializeNode(ctx);
}
}
SerializeNodeSurfaceNormals(ctx);
}
protected override void DeserializeExtraInfo (GraphSerializationContext ctx) {
int count = ctx.reader.ReadInt32();
if (count == -1) {
nodes = null;
return;
}
nodes = new LevelGridNode[count];
for (int i = 0; i < nodes.Length; i++) {
if (ctx.reader.ReadInt32() != -1) {
nodes[i] = newGridNodeDelegate();
active.InitializeNode(nodes[i]);
nodes[i].DeserializeNode(ctx);
} else {
nodes[i] = null;
}
}
DeserializeNativeData(ctx, ctx.meta.version >= AstarSerializer.V4_3_37);
}
protected override void PostDeserialization (GraphSerializationContext ctx) {
UpdateTransform();
lastScannedWidth = width;
lastScannedDepth = depth;
SetUpOffsetsAndCosts();
LevelGridNode.SetGridGraph((int)graphIndex, this);
if (nodes == null || nodes.Length == 0) return;
if (width*depth*layerCount != nodes.Length) {
Debug.LogError("Node data did not match with bounds data. Probably a change to the bounds/width/depth data was made after scanning the graph, just prior to saving it. Nodes will be discarded");
nodes = new GridNodeBase[0];
return;
}
for (int i = 0; i < layerCount; i++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
LevelGridNode node = nodes[z*width+x + width*depth*i] as LevelGridNode;
if (node == null) {
continue;
}
node.NodeInGridIndex = z*width+x;
node.LayerCoordinateInGrid = i;
}
}
}
}
}
}
#endif