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 {
///
/// 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.
///
[BurstCompile(FloatMode = FloatMode.Fast, CompileSynchronously = true)]
public struct JobCalculateGridConnections : IJobParallelForBatched {
public float maxStepHeight;
/// Normalized up direction
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 nodeWalkable;
[ReadOnly]
public UnsafeSpan nodeNormals;
[ReadOnly]
public UnsafeSpan nodePositions;
/// All bitpacked node connections
[WriteOnly]
public UnsafeSpan nodeConnections;
public bool allowBoundsChecks => false;
///
/// Check if a connection to node B is valid.
/// Node A is assumed to be walkable already
///
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 neighbourOffsets = new NativeArray(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();
// 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 neighbourOffsets = new NativeArray(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;
}
}
}
}
}
}