1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
#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.
///
/// [Open online documentation to see images]
///
/// 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
|