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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
|
#define PREALLOCATE_NODES
using System.Collections.Generic;
using Pathfinding.Serialization;
using Pathfinding.Util;
using UnityEngine;
namespace Pathfinding {
/// <summary>Node used for the GridGraph</summary>
public class GridNode : GridNodeBase {
public GridNode() {
}
public GridNode (AstarPath astar) {
astar.InitializeNode(this);
}
#if !ASTAR_NO_GRID_GRAPH
private static GridGraph[] _gridGraphs = new GridGraph[0];
public static GridGraph GetGridGraph (uint graphIndex) { return _gridGraphs[(int)graphIndex]; }
public static void SetGridGraph (int graphIndex, GridGraph graph) {
if (_gridGraphs.Length <= graphIndex) {
var gg = new GridGraph[graphIndex+1];
for (int i = 0; i < _gridGraphs.Length; i++) gg[i] = _gridGraphs[i];
_gridGraphs = gg;
}
_gridGraphs[graphIndex] = graph;
}
public static void ClearGridGraph (int graphIndex, GridGraph graph) {
if (graphIndex < _gridGraphs.Length && _gridGraphs[graphIndex] == graph) {
_gridGraphs[graphIndex] = null;
}
}
/// <summary>Internal use only</summary>
internal ushort InternalGridFlags {
get { return gridFlags; }
set { gridFlags = value; }
}
const int GridFlagsConnectionOffset = 0;
const int GridFlagsConnectionBit0 = 1 << GridFlagsConnectionOffset;
const int GridFlagsConnectionMask = 0xFF << GridFlagsConnectionOffset;
const int GridFlagsAxisAlignedConnectionMask = 0xF << GridFlagsConnectionOffset;
const int GridFlagsEdgeNodeOffset = 10;
const int GridFlagsEdgeNodeMask = 1 << GridFlagsEdgeNodeOffset;
public override bool HasConnectionsToAllEightNeighbours {
get {
return (InternalGridFlags & GridFlagsConnectionMask) == GridFlagsConnectionMask;
}
}
public override bool HasConnectionsToAllAxisAlignedNeighbours {
get {
return (InternalGridFlags & GridFlagsAxisAlignedConnectionMask) == GridFlagsAxisAlignedConnectionMask;
}
}
/// <summary>
/// True if the node has a connection in the specified direction.
/// The dir parameter corresponds to directions in the grid as:
/// <code>
/// Z
/// |
/// |
///
/// 6 2 5
/// \ | /
/// -- 3 - X - 1 ----- X
/// / | \
/// 7 0 4
///
/// |
/// |
/// </code>
///
/// See: <see cref="SetConnectionInternal"/>
/// See: <see cref="GridGraph.neighbourXOffsets"/>
/// See: <see cref="GridGraph.neighbourZOffsets"/>
/// See: <see cref="GridGraph.neighbourOffsets"/>
/// See: <see cref="GridGraph.GetNeighbourIndices"/>
/// </summary>
public override bool HasConnectionInDirection (int dir) {
return (gridFlags >> dir & GridFlagsConnectionBit0) != 0;
}
/// <summary>
/// True if the node has a connection in the specified direction.
/// Deprecated: Use <see cref="HasConnectionInDirection"/>
/// </summary>
[System.Obsolete("Use HasConnectionInDirection")]
public bool GetConnectionInternal (int dir) {
return HasConnectionInDirection(dir);
}
/// <summary>
/// Enables or disables a connection in a specified direction on the graph.
///
/// Note: This only changes the connection from this node to the other node. You may also want to call the same method on the other node with the opposite direction.
///
/// See: <see cref="HasConnectionInDirection"/>
/// See: <see cref="OppositeConnectionDirection"/>
/// </summary>
public void SetConnection (int dir, bool value) {
SetConnectionInternal(dir, value);
var grid = GetGridGraph(GraphIndex);
grid.nodeDataRef.connections[NodeInGridIndex] = (ulong)GetAllConnectionInternal();
}
/// <summary>
/// Enables or disables a connection in a specified direction on the graph.
/// See: <see cref="HasConnectionInDirection"/>
///
/// Warning: Using this method can make the graph data inconsistent. It's recommended to use other ways to update the graph, instead, for example <see cref="SetConnection"/>.
/// </summary>
public void SetConnectionInternal (int dir, bool value) {
// Set bit number #dir to 1 or 0 depending on #value
unchecked { gridFlags = (ushort)(gridFlags & ~((ushort)1 << GridFlagsConnectionOffset << dir) | (value ? (ushort)1 : (ushort)0) << GridFlagsConnectionOffset << dir); }
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
/// <summary>
/// Sets the state of all grid connections.
///
/// See: SetConnectionInternal
///
/// Warning: Using this method can make the graph data inconsistent. It's recommended to use other ways to update the graph, instead.
/// </summary>
/// <param name="connections">a bitmask of the connections (bit 0 is the first connection, bit 1 the second connection, etc.).</param>
public void SetAllConnectionInternal (int connections) {
unchecked { gridFlags = (ushort)((gridFlags & ~GridFlagsConnectionMask) | (connections << GridFlagsConnectionOffset)); }
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
/// <summary>Bitpacked int containing all 8 grid connections</summary>
public int GetAllConnectionInternal () {
return (gridFlags & GridFlagsConnectionMask) >> GridFlagsConnectionOffset;
}
public override bool HasAnyGridConnections => GetAllConnectionInternal() != 0;
/// <summary>
/// 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.
///
/// Warning: Using this method can make the graph data inconsistent. It's recommended to use other ways to update the graph, instead.
/// </summary>
public override void ResetConnectionsInternal () {
unchecked {
gridFlags = (ushort)(gridFlags & ~GridFlagsConnectionMask);
}
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
/// <summary>
/// Work in progress for a feature that required info about which nodes were at the border of the graph.
/// Note: This property is not functional at the moment.
/// </summary>
public bool EdgeNode {
get {
return (gridFlags & GridFlagsEdgeNodeMask) != 0;
}
set {
unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsEdgeNodeMask | (value ? GridFlagsEdgeNodeMask : 0)); }
}
}
public override GridNodeBase GetNeighbourAlongDirection (int direction) {
if (HasConnectionInDirection(direction)) {
GridGraph gg = GetGridGraph(GraphIndex);
return gg.nodes[NodeInGridIndex+gg.neighbourOffsets[direction]];
}
return null;
}
public override void ClearConnections (bool alsoReverse) {
if (alsoReverse) {
// Note: This assumes that all connections are bidirectional
// which should hold for all grid graphs unless some custom code has been added
for (int i = 0; i < 8; i++) {
var other = GetNeighbourAlongDirection(i) as GridNode;
if (other != null) {
// Remove reverse connection. See doc for GridGraph.neighbourOffsets to see which indices are used for what.
other.SetConnectionInternal(OppositeConnectionDirection(i), false);
}
}
}
ResetConnectionsInternal();
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
base.ClearConnections(alsoReverse);
#endif
}
public override void GetConnections<T>(GetConnectionsWithData<T> action, ref T data, int connectionFilter) {
if ((connectionFilter & (Connection.IncomingConnection | Connection.OutgoingConnection)) == 0) return;
GridGraph gg = GetGridGraph(GraphIndex);
var neighbourOffsets = gg.neighbourOffsets;
var nodes = gg.nodes;
for (int i = 0; i < 8; i++) {
if ((gridFlags >> i & GridFlagsConnectionBit0) != 0) {
var other = nodes[NodeInGridIndex + neighbourOffsets[i]];
if (other != null) action(other, ref data);
}
}
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
base.GetConnections(action, ref data, connectionFilter);
#endif
}
public override bool GetPortal (GraphNode other, out Vector3 left, out Vector3 right) {
if (other.GraphIndex != GraphIndex) {
left = right = Vector3.zero;
return false;
}
GridGraph gg = GetGridGraph(GraphIndex);
var cellOffset = (other as GridNode).CoordinatesInGrid - CoordinatesInGrid;
var dir = OffsetToConnectionDirection(cellOffset.x, cellOffset.y);
if (dir == -1 || !HasConnectionInDirection(dir)) {
left = right = Vector3.zero;
return false;
}
UnityEngine.Assertions.Assert.AreEqual(other, gg.nodes[NodeInGridIndex + gg.neighbourOffsets[dir]]);
if (dir < 4) {
Vector3 middle = ((Vector3)(position + other.position))*0.5f;
Vector3 cross = Vector3.Cross(gg.collision.up, (Vector3)(other.position-position));
cross.Normalize();
cross *= gg.nodeSize*0.5f;
left = middle - cross;
right = middle + cross;
} else {
bool rClear = false;
bool lClear = false;
if (HasConnectionInDirection(dir-4)) {
var n2 = gg.nodes[NodeInGridIndex + gg.neighbourOffsets[dir-4]];
if (n2.Walkable && n2.HasConnectionInDirection((dir-4+1)%4)) {
rClear = true;
}
}
if (HasConnectionInDirection((dir-4+1)%4)) {
var n2 = gg.nodes[NodeInGridIndex + gg.neighbourOffsets[(dir-4+1)%4]];
if (n2.Walkable && n2.HasConnectionInDirection(dir-4)) {
lClear = true;
}
}
Vector3 middle = ((Vector3)(position + other.position))*0.5f;
Vector3 cross = Vector3.Cross(gg.collision.up, (Vector3)(other.position-position));
cross.Normalize();
cross *= gg.nodeSize*1.4142f;
left = middle - (lClear ? cross : Vector3.zero);
right = middle + (rClear ? cross : Vector3.zero);
}
return true;
}
/// <summary>
/// Filters diagonal connections based on the non-diagonal ones to prevent corner cutting and similar things.
///
/// This involves some complicated bitshifting to calculate which diagonal connections
/// should be active based on the non-diagonal ones.
/// For example a path should not be able to pass from A to B if the \<see cref="s"/> represent nodes
/// that we cannot traverse.
///
/// <code>
/// # B
/// A #
/// </code>
///
/// Additionally if corner cutting is disabled we will also prevent a connection from A to B in this case:
///
/// <code>
/// B
/// A #
/// </code>
///
/// If neighbours = 4 then only the 4 axis aligned connections will be enabled.
///
/// If neighbours = 6 then only the connections which are valid for hexagonal graphs will be enabled.
/// </summary>
public static int FilterDiagonalConnections (int conns, NumNeighbours neighbours, bool cutCorners) {
switch (neighbours) {
case NumNeighbours.Four:
// The first 4 bits are the axis aligned connections
return conns & 0xF;
// Default case exists only to make the compiler happy, it is never intended to be used.
default:
case NumNeighbours.Eight:
if (cutCorners) {
int axisConns = conns & 0xF;
// If at least one axis aligned connection
// is adjacent to this diagonal, then we can add a connection.
// Bitshifting is a lot faster than calling node.HasConnectionInDirection.
// We need to check if connection i and i+1 are enabled
// but i+1 may overflow 4 and in that case need to be wrapped around
// (so 3+1 = 4 goes to 0). We do that by checking both connection i+1
// and i+1-4 at the same time. Either i+1 or i+1-4 will be in the range
// from 0 to 4 (exclusive)
int diagConns = (axisConns | (axisConns >> 1 | axisConns << 3)) << 4;
// Filter out diagonal connections that are invalid
// This will also filter out some junk bits which may be set to true above bit 8
diagConns &= conns;
return axisConns | diagConns;
} else {
int axisConns = conns & 0xF;
// If exactly 2 axis aligned connections are adjacent to a diagonal connection
// then the diagonal connection is ok to use.
int diagConns = (axisConns & (axisConns >> 1 | axisConns << 3)) << 4;
// Filter out diagonal connections that are invalid
// This will also filter out some junk bits which may be set above bit 8
diagConns &= conns;
return axisConns | diagConns;
}
case NumNeighbours.Six:
// Hexagon layout
return conns & GridGraph.HexagonConnectionMask;
}
}
public override void Open (Path path, uint pathNodeIndex, uint gScore) {
GridGraph gg = GetGridGraph(GraphIndex);
int[] neighbourOffsets = gg.neighbourOffsets;
uint[] neighbourCosts = gg.neighbourCosts;
var nodes = gg.nodes;
var index = NodeInGridIndex;
// Bitmask of the 8 connections out of this node
// Each bit represents one connection.
var conns = gridFlags & GridFlagsConnectionMask;
// Loop over all connections, first the 4 axis aligned ones and then the 4 diagonal ones.
for (int dir = 0; dir < 8; dir++) {
// dir=4 is the first diagonal connection.
// At this point we know exactly which orthogonal (not diagonal) connections are actually traversable.
// So we do some filtering to determine which diagonals should be traversable.
//
// We do this dynamically because each path may use different tags or different
// ITraversalProviders that affect the result.
//
// When the grid graph is scanned this exact method is also run to pre-filter connections
// based on their walkability values.
// Doing pre-filtering is good because it allows users to use `HasConnectionInDirection`
// and it will return accurate values even for diagonals (even though it will of course not
// take into account any additional constraints such as tags or ITraversalProviders).
if (dir == 4 && (path.traversalProvider == null || path.traversalProvider.filterDiagonalGridConnections)) {
conns = FilterDiagonalConnections(conns, gg.neighbours, gg.cutCorners);
}
// Check if we have a connection in this direction
if (((conns >> dir) & GridFlagsConnectionBit0) != 0) {
var other = nodes[index + neighbourOffsets[dir]];
if (path.CanTraverse(this, other)) {
path.OpenCandidateConnection(pathNodeIndex, other.NodeIndex, gScore, neighbourCosts[dir], 0, other.position);
} else {
// Mark that connection as not valid
conns &= ~(GridFlagsConnectionBit0 << dir);
}
}
}
base.Open(path, pathNodeIndex, gScore);
}
public override void SerializeNode (GraphSerializationContext ctx) {
base.SerializeNode(ctx);
ctx.SerializeInt3(position);
ctx.writer.Write(gridFlags);
}
public override void DeserializeNode (GraphSerializationContext ctx) {
base.DeserializeNode(ctx);
position = ctx.DeserializeInt3();
gridFlags = ctx.reader.ReadUInt16();
}
public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) {
// In case the node was already added as an internal grid connection,
// we need to remove that connection before we insert it as a custom connection.
// Using a custom connection is necessary because it has a custom cost.
if (node is GridNode gn && gn.GraphIndex == GraphIndex) {
RemoveGridConnection(gn);
}
base.AddPartialConnection(node, cost, isOutgoing, isIncoming);
}
public override void RemovePartialConnection (GraphNode node) {
base.RemovePartialConnection(node);
// If the node is a grid node on the same graph, it might be added as an internal connection and not a custom one.
if (node is GridNode gn && gn.GraphIndex == GraphIndex) {
RemoveGridConnection(gn);
}
}
/// <summary>
/// Removes a connection from the internal grid connections.
/// See: SetConnectionInternal
/// </summary>
protected void RemoveGridConnection (GridNode node) {
var nodeIndex = NodeInGridIndex;
var gg = GetGridGraph(GraphIndex);
for (int i = 0; i < 8; i++) {
if (nodeIndex + gg.neighbourOffsets[i] == node.NodeInGridIndex && GetNeighbourAlongDirection(i) == node) {
SetConnectionInternal(i, false);
break;
}
}
}
#else
public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) {
throw new System.NotImplementedException();
}
public override void ClearConnections (bool alsoReverse) {
throw new System.NotImplementedException();
}
public override void GetConnections (GraphNodeDelegate del) {
throw new System.NotImplementedException();
}
public override void Open (Path path, PathNode pathNode, PathHandler handler) {
throw new System.NotImplementedException();
}
public override void AddPartialConnection (GraphNode node) {
throw new System.NotImplementedException();
}
#endif
}
}
|