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
|
using UnityEngine;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using Pathfinding.Jobs;
using Pathfinding.Util;
using System.Data;
using UnityEngine.Assertions;
namespace Pathfinding.Graphs.Grid.Jobs {
/// <summary>
/// Calculates the grid connections for all nodes.
///
/// This is a IJobParallelForBatch job. Calculating the connections in multiple threads is faster,
/// but due to hyperthreading (used on most intel processors) the individual threads will become slower.
/// It is still worth it though.
/// </summary>
[BurstCompile(FloatMode = FloatMode.Fast, CompileSynchronously = true)]
public struct JobCalculateGridConnections : IJobParallelForBatched {
public float maxStepHeight;
/// <summary>Normalized up direction</summary>
public Vector3 up;
public IntBounds bounds;
public int3 arrayBounds;
public NumNeighbours neighbours;
public bool use2D;
public bool cutCorners;
public bool maxStepUsesSlope;
public float characterHeight;
public bool layeredDataLayout;
[ReadOnly]
public UnsafeSpan<bool> nodeWalkable;
[ReadOnly]
public UnsafeSpan<float4> nodeNormals;
[ReadOnly]
public UnsafeSpan<Vector3> nodePositions;
/// <summary>All bitpacked node connections</summary>
[WriteOnly]
public UnsafeSpan<ulong> nodeConnections;
public bool allowBoundsChecks => false;
/// <summary>
/// Check if a connection to node B is valid.
/// Node A is assumed to be walkable already
/// </summary>
public static bool IsValidConnection (float4 nodePosA, float4 nodeNormalA, bool nodeWalkableB, float4 nodePosB, float4 nodeNormalB, bool maxStepUsesSlope, float maxStepHeight, float4 up) {
if (!nodeWalkableB) return false;
if (!maxStepUsesSlope) {
// Check their differences along the Y coordinate (well, the up direction really. It is not necessarily the Y axis).
return math.abs(math.dot(up, nodePosB - nodePosA)) <= maxStepHeight;
} else {
float4 v = nodePosB - nodePosA;
float heightDifference = math.dot(v, up);
// Check if the step is small enough.
// This is a fast path for the common case.
if (math.abs(heightDifference) <= maxStepHeight) return true;
float4 v_flat = (v - heightDifference * up) * 0.5f;
// Math!
// Calculates the approximate offset along the up direction
// that the ground will have moved at the midpoint between the
// nodes compared to the nodes' center points.
float NDotU = math.dot(nodeNormalA, up);
float offsetA = -math.dot(nodeNormalA - NDotU * up, v_flat);
NDotU = math.dot(nodeNormalB, up);
float offsetB = math.dot(nodeNormalB - NDotU * up, v_flat);
// Check the height difference with slopes taken into account.
// Note that since we also do the heightDifference check above we will ensure slope offsets do not increase the height difference.
// If we allowed this then some connections might not be valid near the start of steep slopes.
return math.abs(heightDifference + offsetB - offsetA) <= maxStepHeight;
}
}
public void Execute (int start, int count) {
if (layeredDataLayout) ExecuteLayered(start, count);
else ExecuteFlat(start, count);
}
public void ExecuteFlat (int start, int count) {
if (maxStepHeight <= 0 || use2D) maxStepHeight = float.PositiveInfinity;
float4 up = new float4(this.up.x, this.up.y, this.up.z, 0);
NativeArray<int> neighbourOffsets = new NativeArray<int>(8, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * arrayBounds.x + GridGraph.neighbourXOffsets[i];
var nodePositions = this.nodePositions.Reinterpret<float3>();
// The loop is parallelized over z coordinates
start += bounds.min.z;
for (int z = start; z < start + count; z++) {
var initialConnections = 0xFF;
// Disable connections to out-of-bounds nodes
// See GridNode.HasConnectionInDirection
if (z == 0) initialConnections &= ~((1 << 0) | (1 << 7) | (1 << 4));
if (z == arrayBounds.z - 1) initialConnections &= ~((1 << 2) | (1 << 5) | (1 << 6));
for (int x = bounds.min.x; x < bounds.max.x; x++) {
int nodeIndex = z * arrayBounds.x + x;
if (!nodeWalkable[nodeIndex]) {
nodeConnections[nodeIndex] = 0;
continue;
}
// Bitpacked connections
// bit 0 is set if connection 0 is enabled
// bit 1 is set if connection 1 is enabled etc.
int conns = initialConnections;
// Disable connections to out-of-bounds nodes
if (x == 0) conns &= ~((1 << 3) | (1 << 6) | (1 << 7));
if (x == arrayBounds.x - 1) conns &= ~((1 << 1) | (1 << 4) | (1 << 5));
float4 pos = new float4(nodePositions[nodeIndex], 0);
float4 normal = nodeNormals[nodeIndex];
for (int i = 0; i < 8; i++) {
int neighbourIndex = nodeIndex + neighbourOffsets[i];
if ((conns & (1 << i)) != 0 && !IsValidConnection(pos, normal, nodeWalkable[neighbourIndex], new float4(nodePositions[neighbourIndex], 0), nodeNormals[neighbourIndex], maxStepUsesSlope, maxStepHeight, up)) {
// Enable connection i
conns &= ~(1 << i);
}
}
nodeConnections[nodeIndex] = (ulong)GridNode.FilterDiagonalConnections(conns, neighbours, cutCorners);
}
}
}
public void ExecuteLayered (int start, int count) {
if (maxStepHeight <= 0 || use2D) maxStepHeight = float.PositiveInfinity;
float4 up = new float4(this.up.x, this.up.y, this.up.z, 0);
NativeArray<int> neighbourOffsets = new NativeArray<int>(8, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * arrayBounds.x + GridGraph.neighbourXOffsets[i];
var layerStride = arrayBounds.z*arrayBounds.x;
start += bounds.min.z;
for (int y = bounds.min.y; y < bounds.max.y; y++) {
// The loop is parallelized over z coordinates
for (int z = start; z < start + count; z++) {
for (int x = bounds.min.x; x < bounds.max.x; x++) {
// Bitpacked connections
ulong conns = 0;
int nodeIndexXZ = z * arrayBounds.x + x;
int nodeIndex = nodeIndexXZ + y * layerStride;
float4 pos = new float4(nodePositions[nodeIndex], 0);
float4 normal = nodeNormals[nodeIndex];
if (nodeWalkable[nodeIndex]) {
var ourY = math.dot(up, pos);
float ourHeight;
if (y == arrayBounds.y-1 || !math.any(nodeNormals[nodeIndex + layerStride])) {
ourHeight = float.PositiveInfinity;
} else {
var nodeAboveNeighbourPos = new float4(nodePositions[nodeIndex + layerStride], 0);
ourHeight = math.max(0, math.dot(up, nodeAboveNeighbourPos) - ourY);
}
for (int i = 0; i < 8; i++) {
int nx = x + GridGraph.neighbourXOffsets[i];
int nz = z + GridGraph.neighbourZOffsets[i];
// Check if the new position is inside the grid
int conn = LevelGridNode.NoConnection;
if (nx >= 0 && nz >= 0 && nx < arrayBounds.x && nz < arrayBounds.z) {
int neighbourStartIndex = nodeIndexXZ + neighbourOffsets[i];
for (int y2 = 0; y2 < arrayBounds.y; y2++) {
var neighbourIndex = neighbourStartIndex + y2 * layerStride;
float4 nodePosB = new float4(nodePositions[neighbourIndex], 0);
var neighbourY = math.dot(up, nodePosB);
// Is there a node above this one
float neighbourHeight;
if (y2 == arrayBounds.y-1 || !math.any(nodeNormals[neighbourIndex + layerStride])) {
neighbourHeight = float.PositiveInfinity;
} else {
var nodeAboveNeighbourPos = new float4(nodePositions[neighbourIndex + layerStride], 0);
neighbourHeight = math.max(0, math.dot(up, nodeAboveNeighbourPos) - neighbourY);
}
float bottom = math.max(neighbourY, ourY);
float top = math.min(neighbourY + neighbourHeight, ourY + ourHeight);
float dist = top-bottom;
if (dist >= characterHeight && IsValidConnection(pos, normal, nodeWalkable[neighbourIndex], new float4(nodePositions[neighbourIndex], 0), nodeNormals[neighbourIndex], maxStepUsesSlope, maxStepHeight, up)) {
conn = y2;
}
}
}
conns |= (ulong)conn << LevelGridNode.ConnectionStride*i;
}
} else {
conns = LevelGridNode.AllConnectionsMask;
}
nodeConnections[nodeIndex] = conns;
}
}
}
}
}
}
|