292 lines
9.7 KiB
C#

#if !ASTAR_NO_GRID_GRAPH
using UnityEngine;
using System.Collections.Generic;
using Pathfinding.Serialization;
using Pathfinding.Graphs.Grid;
namespace Pathfinding {
/// <summary>
/// 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}
///
/// <b>Collision testing</b>
/// \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}
///
/// <b>Height testing</b>
/// \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}
///
/// <b>Rules</b>
/// Take a look at grid-rules (view in online documentation for working links) for a list of available rules.
///
/// <b>Other settings</b>
/// \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: <see cref="GridGraph"/>
/// </summary>
[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;
}
/// <summary>
/// Number of layers.
/// Warning: Do not modify this variable
/// </summary>
[JsonMember]
internal int layerCount;
/// <summary>Nodes with a short distance to the node above it will be set unwalkable</summary>
[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<GraphNode> action) {
if (nodes == null) return;
for (int i = 0; i < nodes.Length; i++) {
if (nodes[i] != null) action(nodes[i]);
}
}
/// <summary>
/// Get all nodes in a rectangle.
/// Returns: The number of nodes written to the buffer.
/// </summary>
/// <param name="rect">Region in which to return nodes. It will be clamped to the grid.</param>
/// <param name="buffer">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.</param>
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;
}
/// <summary>
/// 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: <see cref="nodes"/>
/// </summary>
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