#if !ASTAR_NO_GRID_GRAPH
using UnityEngine;
using System.Collections.Generic;
using Pathfinding.Serialization;
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.
///
/// [Open online documentation to see images]
///
/// See:
///
[Pathfinding.Util.Preserve]
public class LayerGridGraph : GridGraph, IUpdatableGraph {
// This function will be called when this graph is destroyed
protected override void OnDestroy () {
base.OnDestroy();
// Clean up a reference in a static variable which otherwise should point to this graph forever and stop the GC from collecting it
RemoveGridGraphFromStatic();
}
void RemoveGridGraphFromStatic () {
LevelGridNode.SetGridGraph(active.data.GetGraphIndex(this), null);
}
///
/// Number of layers.
/// Warning: Do not modify this variable
///
[JsonMember]
internal int layerCount;
/// If two layered nodes are too close, they will be merged
[JsonMember]
public float mergeSpanRange = 0.5F;
/// 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 bool uniformWidthDepthGrid {
get {
return false;
}
}
public override int LayerCount {
get {
return layerCount;
}
}
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]);
}
}
protected override List GetNodesInRegion (Bounds b, GraphUpdateShape shape) {
var rect = GetRectFromBounds(b);
if (nodes == null || !rect.IsValid() || nodes.Length != width*depth*layerCount) {
return Pathfinding.Util.ListPool.Claim();
}
// Get a buffer we can use
var inArea = Pathfinding.Util.ListPool.Claim(rect.Width*rect.Height*layerCount);
// Loop through all nodes in the rectangle
for (int l = 0; l < layerCount; l++) {
var lwd = l * width * depth;
for (int x = rect.xmin; x <= rect.xmax; x++) {
for (int z = rect.ymin; z <= rect.ymax; z++) {
int index = lwd + z*width + x;
GraphNode node = nodes[index];
// If it is contained in the bounds (and optionally the shape)
// then add it to the buffer
if (node != null && b.Contains((Vector3)node.position) && (shape == null || shape.Contains((Vector3)node.position))) {
inArea.Add(node);
}
}
}
}
return inArea;
}
public override List GetNodesInRegion (IntRect rect) {
// Get a buffer we can use
var inArea = Pathfinding.Util.ListPool.Claim();
// Rect which covers the whole grid
var gridRect = new IntRect(0, 0, width-1, depth-1);
// Clamp the rect to the grid
rect = IntRect.Intersection(rect, gridRect);
if (nodes == null || !rect.IsValid() || nodes.Length != width*depth*layerCount) return inArea;
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) {
inArea.Add(node);
}
}
}
}
return inArea;
}
///
/// 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 in the first layer.
/// Returns null if the coordinate is outside the grid.
///
///
/// var gg = AstarPath.active.data.gridGraph;
/// int x = 5;
/// int z = 8;
/// GridNodeBase node = gg.GetNode(x, z);
///
///
/// 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 override GridNodeBase GetNode (int x, int z) {
if (x < 0 || z < 0 || x >= width || z >= depth) return null;
return nodes[x + z*width];
}
///
/// 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];
}
void IUpdatableGraph.UpdateArea (GraphUpdateObject o) {
if (nodes == null || nodes.Length != width*depth*layerCount) {
Debug.LogWarning("The Grid Graph is not scanned, cannot update area ");
//Not scanned
return;
}
IntRect originalRect, affectRect, physicsRect;
bool willChangeWalkability;
int erosion;
CalculateAffectedRegions(o, out originalRect, out affectRect, out physicsRect, out willChangeWalkability, out erosion);
bool willChangeNodeInstances = (o is LayerGridGraphUpdate && ((LayerGridGraphUpdate)o).recalculateNodes);
bool preserveExistingNodes = (o is LayerGridGraphUpdate ? ((LayerGridGraphUpdate)o).preserveExistingNodes : !o.resetPenaltyOnPhysics);
if (o.trackChangedNodes && willChangeNodeInstances) {
Debug.LogError("Cannot track changed nodes when creating or deleting nodes.\nWill not update LayerGridGraph");
return;
}
// Rect which covers the whole grid
var gridRect = new IntRect(0, 0, width-1, depth-1);
IntRect clampedRect = IntRect.Intersection(affectRect, gridRect);
// Mark nodes that might be changed
if (!willChangeNodeInstances) {
for (int x = clampedRect.xmin; x <= clampedRect.xmax; x++) {
for (int z = clampedRect.ymin; z <= clampedRect.ymax; z++) {
for (int y = 0; y < layerCount; y++) {
o.WillUpdateNode(nodes[y*width*depth + z*width+x]);
}
}
}
}
// Update Physics
if (o.updatePhysics && !o.modifyWalkability) {
collision.Initialize(transform, nodeSize);
clampedRect = IntRect.Intersection(physicsRect, gridRect);
for (int x = clampedRect.xmin; x <= clampedRect.xmax; x++) {
for (int z = clampedRect.ymin; z <= clampedRect.ymax; z++) {
RecalculateCell(x, z, !preserveExistingNodes, false);
}
}
for (int x = clampedRect.xmin; x <= clampedRect.xmax; x++) {
for (int z = clampedRect.ymin; z <= clampedRect.ymax; z++) {
CalculateConnections(x, z);
}
}
}
// Apply GUO
clampedRect = IntRect.Intersection(originalRect, gridRect);
for (int x = clampedRect.xmin; x <= clampedRect.xmax; x++) {
for (int z = clampedRect.ymin; z <= clampedRect.ymax; z++) {
for (int y = 0; y < layerCount; y++) {
int index = y*width*depth + z*width+x;
var node = nodes[index];
if (node == null) continue;
if (willChangeWalkability) {
node.Walkable = node.WalkableErosion;
if (o.bounds.Contains((Vector3)node.position)) o.Apply(node);
node.WalkableErosion = node.Walkable;
} else {
if (o.bounds.Contains((Vector3)node.position)) o.Apply(node);
}
}
}
}
// Recalculate connections
if (willChangeWalkability && erosion == 0) {
clampedRect = IntRect.Intersection(affectRect, gridRect);
for (int x = clampedRect.xmin; x <= clampedRect.xmax; x++) {
for (int z = clampedRect.ymin; z <= clampedRect.ymax; z++) {
CalculateConnections(x, z);
}
}
} else if (willChangeWalkability && erosion > 0) {
clampedRect = IntRect.Union(originalRect, physicsRect);
IntRect erosionRect1 = clampedRect.Expand(erosion);
IntRect erosionRect2 = erosionRect1.Expand(erosion);
erosionRect1 = IntRect.Intersection(erosionRect1, gridRect);
erosionRect2 = IntRect.Intersection(erosionRect2, gridRect);
/*
* all nodes inside clampedRect might have had their walkability changed
* all nodes inside erosionRect1 might get affected by erosion from clampedRect and erosionRect2
* all nodes inside erosionRect2 (but outside erosionRect1) will be reset to previous walkability
* after calculation since their erosion might not be correctly calculated (nodes outside erosionRect2 would maybe have effect)
*/
for (int x = erosionRect2.xmin; x <= erosionRect2.xmax; x++) {
for (int z = erosionRect2.ymin; z <= erosionRect2.ymax; z++) {
for (int y = 0; y < layerCount; y++) {
int index = y*width*depth + z*width+x;
var node = nodes[index];
if (node == null) continue;
bool tmp = node.Walkable;
node.Walkable = node.WalkableErosion;
if (!erosionRect1.Contains(x, z)) {
//Save the border's walkabilty data in bit 16 (will be reset later)
node.TmpWalkable = tmp;
}
}
}
}
for (int x = erosionRect2.xmin; x <= erosionRect2.xmax; x++) {
for (int z = erosionRect2.ymin; z <= erosionRect2.ymax; z++) {
CalculateConnections(x, z);
}
}
// Erode the walkable area
ErodeWalkableArea(erosionRect2.xmin, erosionRect2.ymin, erosionRect2.xmax+1, erosionRect2.ymax+1);
for (int x = erosionRect2.xmin; x <= erosionRect2.xmax; x++) {
for (int z = erosionRect2.ymin; z <= erosionRect2.ymax; z++) {
if (erosionRect1.Contains(x, z)) continue;
for (int y = 0; y < layerCount; y++) {
int index = y*width*depth + z*width+x;
var node = nodes[index];
if (node == null) continue;
// Restore temporarily stored data
node.Walkable = node.TmpWalkable;
}
}
}
// Recalculate connections of all affected nodes
for (int x = erosionRect2.xmin; x <= erosionRect2.xmax; x++) {
for (int z = erosionRect2.ymin; z <= erosionRect2.ymax; z++) {
CalculateConnections(x, z);
}
}
}
}
protected override IEnumerable