summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs
diff options
context:
space:
mode:
authorchai <215380520@qq.com>2024-05-23 10:08:29 +0800
committerchai <215380520@qq.com>2024-05-23 10:08:29 +0800
commit8722a9920c1f6119bf6e769cba270e63097f8e25 (patch)
tree2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs
parent3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff)
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs57
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs219
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs33
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs28
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs50
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs190
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs128
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs31
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs36
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs73
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs45
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs61
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs51
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs42
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs121
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs93
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs91
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta11
34 files changed, 1536 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs
new file mode 100644
index 0000000..d301d44
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs
@@ -0,0 +1,57 @@
+using Pathfinding.Util;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Allocates and deallocates nodes in a grid graph.
+ ///
+ /// This will inspect every cell in the dataBounds and allocate or deallocate the node depending on if that slot should have a node or not according to the nodeNormals array (pure zeroes means no node).
+ ///
+ /// This is only used for incremental updates of grid graphs.
+ /// The initial layer of the grid graph (which is always filled with nodes) is allocated in the <see cref="GridGraph.AllocateNodesJob"/> method.
+ /// </summary>
+ public struct JobAllocateNodes : IJob {
+ public AstarPath active;
+ [ReadOnly]
+ public NativeArray<float4> nodeNormals;
+ public IntBounds dataBounds;
+ public int3 nodeArrayBounds;
+ public GridNodeBase[] nodes;
+ public System.Func<GridNodeBase> newGridNodeDelegate;
+
+ public void Execute () {
+ var size = dataBounds.size;
+
+ // Start at y=1 because all nodes at y=0 are guaranteed to already be allocated (they are always allocated in a layered grid graph).
+ var nodeNormalsSpan = nodeNormals.AsUnsafeReadOnlySpan();
+ for (int y = 1; y < size.y; y++) {
+ for (int z = 0; z < size.z; z++) {
+ var rowOffset = ((y + dataBounds.min.y) * nodeArrayBounds.z + (z + dataBounds.min.z)) * nodeArrayBounds.x + dataBounds.min.x;
+ for (int x = 0; x < size.x; x++) {
+ var nodeIndex = rowOffset + x;
+ var shouldHaveNode = math.any(nodeNormalsSpan[nodeIndex]);
+ var node = nodes[nodeIndex];
+ var hasNode = node != null;
+ if (shouldHaveNode != hasNode) {
+ if (shouldHaveNode) {
+ node = nodes[nodeIndex] = newGridNodeDelegate();
+ active.InitializeNode(node);
+ } else {
+ // Clear custom connections first and clear connections from other nodes to this one
+ node.ClearCustomConnections(true);
+ // Clear grid connections without clearing the connections from other nodes to this one (a bit slow)
+ // Since this is inside a graph update we guarantee that the grid connections will be correct at the end
+ // of the update anyway
+ node.ResetConnectionsInternal();
+ node.Destroy();
+ nodes[nodeIndex] = null;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta
new file mode 100644
index 0000000..a2391b4
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 773a80d74b04a904faae186478d396c4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs
new file mode 100644
index 0000000..0a14596
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs
@@ -0,0 +1,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;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta
new file mode 100644
index 0000000..089d203
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 63dd791a75e95424ea05940b4c155c25
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs
new file mode 100644
index 0000000..71a3d2c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs
@@ -0,0 +1,33 @@
+using UnityEngine;
+using Unity.Collections;
+using Pathfinding.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Checks if nodes are obstructed by obstacles or not.
+ ///
+ /// See: <see cref="GraphCollision"/>
+ /// </summary>
+ struct JobCheckCollisions : IJobTimeSliced {
+ [ReadOnly]
+ public NativeArray<Vector3> nodePositions;
+ public NativeArray<bool> collisionResult;
+ public GraphCollision collision;
+ int startIndex;
+
+ public void Execute () {
+ Execute(TimeSlice.Infinite);
+ }
+
+ public bool Execute (TimeSlice timeSlice) {
+ for (int i = startIndex; i < nodePositions.Length; i++) {
+ collisionResult[i] = collisionResult[i] && collision.Check(nodePositions[i]);
+ if ((i & 127) == 0 && timeSlice.expired) {
+ startIndex = i + 1;
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta
new file mode 100644
index 0000000..9311878
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6977ffeeb891185449eae9f827667558
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs
new file mode 100644
index 0000000..84ae59e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs
@@ -0,0 +1,28 @@
+#if UNITY_2022_2_OR_NEWER
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Fills the output with true or false depending on if the collider hit was a hit.
+ ///
+ /// result[i] = false if hits[i] is a valid hit, otherwise true.
+ /// </summary>
+ [BurstCompile]
+ public struct JobColliderHitsToBooleans : IJob {
+ [ReadOnly]
+ public NativeArray<ColliderHit> hits;
+
+ [WriteOnly]
+ public NativeArray<bool> result;
+
+ public void Execute () {
+ for (int i = 0; i < hits.Length; i++) {
+ result[i] = hits[i].instanceID == 0;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta
new file mode 100644
index 0000000..39e8bb7
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0e741e2fc3248564998e707ba25fe69c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs
new file mode 100644
index 0000000..690448f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs
@@ -0,0 +1,50 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Copies 3D arrays with grid data from one shape to another.
+ ///
+ /// Only the data for the nodes that exist in both buffers will be copied.
+ ///
+ /// This essentially is several <see cref="JobCopyRectangle"/> jobs in one (to avoid scheduling overhead).
+ /// See that job for more documentation.
+ /// </summary>
+ [BurstCompile]
+ public struct JobCopyBuffers : IJob {
+ [ReadOnly]
+ [DisableUninitializedReadCheck]
+ public GridGraphNodeData input;
+
+ [WriteOnly]
+ public GridGraphNodeData output;
+ public IntBounds bounds;
+
+ public bool copyPenaltyAndTags;
+
+ public void Execute () {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (!input.bounds.Contains(bounds)) throw new System.ArgumentException("Bounds are outside the source buffer");
+ if (!output.bounds.Contains(bounds)) throw new System.ArgumentException("Bounds are outside the destination buffer");
+#endif
+ var inputSlice = new Slice3D(input.bounds, bounds);
+ var outputSlice = new Slice3D(output.bounds, bounds);
+ // Note: Having a single job that copies all of the buffers avoids a lot of scheduling overhead.
+ // We do miss out on parallelization, however for this job it is not that significant.
+ JobCopyRectangle<Vector3>.Copy(input.positions, output.positions, inputSlice, outputSlice);
+ JobCopyRectangle<float4>.Copy(input.normals, output.normals, inputSlice, outputSlice);
+ JobCopyRectangle<ulong>.Copy(input.connections, output.connections, inputSlice, outputSlice);
+ if (copyPenaltyAndTags) {
+ JobCopyRectangle<uint>.Copy(input.penalties, output.penalties, inputSlice, outputSlice);
+ JobCopyRectangle<int>.Copy(input.tags, output.tags, inputSlice, outputSlice);
+ }
+ JobCopyRectangle<bool>.Copy(input.walkable, output.walkable, inputSlice, outputSlice);
+ JobCopyRectangle<bool>.Copy(input.walkableWithErosion, output.walkableWithErosion, inputSlice, outputSlice);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta
new file mode 100644
index 0000000..9c60956
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a3c4dcf30e4497e44a9c7421803ec902
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs
new file mode 100644
index 0000000..5b5ba35
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs
@@ -0,0 +1,190 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Calculates erosion.
+ /// Note that to ensure that connections are completely up to date after updating a node you
+ /// have to calculate the connections for both the changed node and its neighbours.
+ ///
+ /// In a layered grid graph, this will recalculate the connections for all nodes
+ /// in the (x,z) cell (it may have multiple layers of nodes).
+ ///
+ /// See: CalculateConnections(GridNodeBase)
+ /// </summary>
+ [BurstCompile]
+ public struct JobErosion<AdjacencyMapper> : IJob where AdjacencyMapper : GridAdjacencyMapper, new() {
+ public IntBounds bounds;
+ public IntBounds writeMask;
+ public NumNeighbours neighbours;
+ public int erosion;
+ public bool erosionUsesTags;
+ public int erosionStartTag;
+
+ [ReadOnly]
+ public NativeArray<ulong> nodeConnections;
+
+ [ReadOnly]
+ public NativeArray<bool> nodeWalkable;
+
+ [WriteOnly]
+ public NativeArray<bool> outNodeWalkable;
+
+ public NativeArray<int> nodeTags;
+ public int erosionTagsPrecedenceMask;
+
+ // Note: the first 3 connections are to nodes with a higher x or z coordinate
+ // The last 3 connections are to nodes with a lower x or z coordinate
+ // This is required for the grassfire transform to work properly
+ // This is a permutation of GridGraph.hexagonNeighbourIndices
+ static readonly int[] hexagonNeighbourIndices = { 1, 2, 5, 0, 3, 7 };
+
+ public void Execute () {
+ var slice = new Slice3D(bounds, bounds);
+ var size = slice.slice.size;
+ slice.AssertMatchesOuter(nodeConnections);
+ slice.AssertMatchesOuter(nodeWalkable);
+ slice.AssertMatchesOuter(outNodeWalkable);
+ slice.AssertMatchesOuter(nodeTags);
+ Assert.IsTrue(bounds.Contains(writeMask));
+
+ var(outerStrideX, outerStrideY, outerStrideZ) = slice.outerStrides;
+ var(innerStrideX, innerStrideY, innerStrideZ) = slice.innerStrides;
+ NativeArray<int> neighbourOffsets = new NativeArray<int>(8, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * innerStrideZ + GridGraph.neighbourXOffsets[i] * innerStrideX;
+
+ var erosionDistances = new NativeArray<int>(slice.length, Allocator.Temp, NativeArrayOptions.ClearMemory);
+ var adjacencyMapper = new AdjacencyMapper();
+ var layers = adjacencyMapper.LayerCount(slice.slice);
+ var outerOffset = slice.outerStartIndex;
+ if (neighbours == NumNeighbours.Six) {
+ // Use the grassfire transform: https://en.wikipedia.org/wiki/Grassfire_transform extended to hexagonal graphs
+ for (int z = 1; z < size.z - 1; z++) {
+ for (int x = 1; x < size.x - 1; x++) {
+ for (int y = 0; y < layers; y++) {
+ // Note: This is significantly faster than using csum, because burst can optimize it better
+ int outerIndex = z * outerStrideZ + x * outerStrideX + y * outerStrideY + outerOffset;
+ var innerIndexXZ = z * innerStrideZ + x * innerStrideX;
+ int innerIndex = innerIndexXZ + y * innerStrideY;
+ int v = int.MaxValue;
+ for (int i = 3; i < 6; i++) {
+ int connection = hexagonNeighbourIndices[i];
+ if (!adjacencyMapper.HasConnection(outerIndex, connection, nodeConnections)) v = -1;
+ else v = math.min(v, erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, connection, nodeConnections, neighbourOffsets, innerStrideY)]);
+ }
+
+ erosionDistances[innerIndex] = v + 1;
+ }
+ }
+ }
+
+ for (int z = size.z - 2; z > 0; z--) {
+ for (int x = size.x - 2; x > 0; x--) {
+ for (int y = 0; y < layers; y++) {
+ int outerIndex = z * outerStrideZ + x * outerStrideX + y * outerStrideY + outerOffset;
+ var innerIndexXZ = z * innerStrideZ + x * innerStrideX;
+ int innerIndex = innerIndexXZ + y * innerStrideY;
+ int v = int.MaxValue;
+ for (int i = 3; i < 6; i++) {
+ int connection = hexagonNeighbourIndices[i];
+ if (!adjacencyMapper.HasConnection(outerIndex, connection, nodeConnections)) v = -1;
+ else v = math.min(v, erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, connection, nodeConnections, neighbourOffsets, innerStrideY)]);
+ }
+
+ erosionDistances[innerIndex] = math.min(erosionDistances[innerIndex], v + 1);
+ }
+ }
+ }
+ } else {
+ /* Index offset to get neighbour nodes. Added to a node's index to get a neighbour node index.
+ *
+ * \code
+ * Z
+ * |
+ * |
+ *
+ * 6 2 5
+ * \ | /
+ * -- 3 - X - 1 ----- X
+ * / | \
+ * 7 0 4
+ *
+ * |
+ * |
+ * \endcode
+ */
+ const int DirectionDown = 0;
+ const int DirectionRight = 1;
+ const int DirectionUp = 2;
+ const int DirectionLeft = 3;
+
+ // Use the grassfire transform: https://en.wikipedia.org/wiki/Grassfire_transform
+ for (int z = 1; z < size.z - 1; z++) {
+ for (int x = 1; x < size.x - 1; x++) {
+ for (int y = 0; y < layers; y++) {
+ int outerIndex = z * outerStrideZ + x * outerStrideX + y * outerStrideY + outerOffset;
+ var innerIndexXZ = z * innerStrideZ + x * innerStrideX;
+ int innerIndex = innerIndexXZ + y * innerStrideY;
+ var v1 = -1;
+ if (adjacencyMapper.HasConnection(outerIndex, DirectionDown, nodeConnections)) v1 = erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, DirectionDown, nodeConnections, neighbourOffsets, innerStrideY)];
+ var v2 = -1;
+ if (adjacencyMapper.HasConnection(outerIndex, DirectionLeft, nodeConnections)) v2 = erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, DirectionLeft, nodeConnections, neighbourOffsets, innerStrideY)];
+
+ erosionDistances[innerIndex] = math.min(v1, v2) + 1;
+ }
+ }
+ }
+
+ for (int z = size.z - 2; z > 0; z--) {
+ for (int x = size.x - 2; x > 0; x--) {
+ for (int y = 0; y < layers; y++) {
+ int outerIndex = z * outerStrideZ + x * outerStrideX + y * outerStrideY + outerOffset;
+ var innerIndexXZ = z * innerStrideZ + x * innerStrideX;
+ int innerIndex = innerIndexXZ + y * innerStrideY;
+ var v1 = -1;
+ if (adjacencyMapper.HasConnection(outerIndex, DirectionUp, nodeConnections)) v1 = erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, DirectionUp, nodeConnections, neighbourOffsets, innerStrideY)];
+ var v2 = -1;
+ if (adjacencyMapper.HasConnection(outerIndex, DirectionRight, nodeConnections)) v2 = erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, DirectionRight, nodeConnections, neighbourOffsets, innerStrideY)];
+
+ erosionDistances[innerIndex] = math.min(erosionDistances[outerIndex], math.min(v1, v2) + 1);
+ }
+ }
+ }
+ }
+
+ var relativeWriteMask = writeMask.Offset(-bounds.min);
+
+ // Erosion tags are allowed to overwrite the ones the user specifies, as well as the ones that are already reserved for erosion.
+ for (int i = erosionStartTag; i < erosionStartTag + erosion; i++) erosionTagsPrecedenceMask |= 1 << i;
+
+ for (int y = relativeWriteMask.min.y; y < relativeWriteMask.max.y; y++) {
+ for (int z = relativeWriteMask.min.z; z < relativeWriteMask.max.z; z++) {
+ for (int x = relativeWriteMask.min.x; x < relativeWriteMask.max.x; x++) {
+ int outerIndex = x * outerStrideX + y * outerStrideY + z * outerStrideZ + outerOffset;
+ int innerIndex = x * innerStrideX + y * innerStrideY + z * innerStrideZ;
+ if (erosionUsesTags) {
+ var prevTag = nodeTags[outerIndex];
+ outNodeWalkable[outerIndex] = nodeWalkable[outerIndex];
+
+ if (erosionDistances[innerIndex] < erosion) {
+ if (((erosionTagsPrecedenceMask >> prevTag) & 0x1) != 0) {
+ nodeTags[outerIndex] = nodeWalkable[outerIndex] ? math.min(GraphNode.MaxTagIndex, erosionDistances[innerIndex] + erosionStartTag) : 0;
+ }
+ } else if (prevTag >= erosionStartTag && prevTag < erosionStartTag + erosion) {
+ // If the node already had a tag that was reserved for erosion, but it shouldn't have that tag, then we remove it.
+ nodeTags[outerIndex] = 0;
+ }
+ } else {
+ outNodeWalkable[outerIndex] = nodeWalkable[outerIndex] & (erosionDistances[innerIndex] >= erosion);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs.meta
new file mode 100644
index 0000000..09484b2
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 12ba2e44c6c183644a4ad6186f4ab21e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs
new file mode 100644
index 0000000..6b40a5f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs
@@ -0,0 +1,128 @@
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Filters out diagonal connections that are not allowed in layered grid graphs.
+ ///
+ /// This is a IJobParallelForBatched job which is parallelelized over the z coordinate of the <see cref="slice"/>.
+ ///
+ /// The <see cref="JobCalculateGridConnections"/> job will run first, and calculate the connections for all nodes.
+ /// However, for layered grid graphs, the connections for diagonal nodes may be incorrect, and this
+ /// post-processing pass is needed to validate the diagonal connections.
+ /// </summary>
+ [BurstCompile]
+ public struct JobFilterDiagonalConnections : IJobParallelForBatched {
+ public Slice3D slice;
+ public NumNeighbours neighbours;
+ public bool cutCorners;
+
+ /// <summary>All bitpacked node connections</summary>
+ public UnsafeSpan<ulong> nodeConnections;
+
+ public bool allowBoundsChecks => false;
+
+ public void Execute (int start, int count) {
+ slice.AssertMatchesOuter(nodeConnections);
+
+ // For single layer graphs this will have already been done in the JobCalculateGridConnections job
+ // but for layered grid graphs we need to handle things differently because the data layout is different
+
+ int3 size = slice.outerSize;
+ NativeArray<int> neighbourOffsets = new NativeArray<int>(8, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+
+ for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * size.x + GridGraph.neighbourXOffsets[i];
+
+ ulong hexagonConnectionMask = 0;
+ for (int i = 0; i < GridGraph.hexagonNeighbourIndices.Length; i++) hexagonConnectionMask |= (ulong)LevelGridNode.ConnectionMask << (LevelGridNode.ConnectionStride*GridGraph.hexagonNeighbourIndices[i]);
+
+ int adjacencyThreshold = cutCorners ? 1 : 2;
+ int layerStride = size.x * size.z;
+ start += slice.slice.min.z;
+ for (int y = slice.slice.min.y; y < slice.slice.max.y; y++) {
+ // The loop is parallelized over z coordinates
+ for (int z = start; z < start + count; z++) {
+ for (int x = slice.slice.min.x; x < slice.slice.max.x; x++) {
+ int nodeIndexXZ = z * size.x + x;
+ int nodeIndex = nodeIndexXZ + y * layerStride;
+
+ switch (neighbours) {
+ case NumNeighbours.Four:
+ // Mask out all the diagonal connections
+ nodeConnections[nodeIndex] = nodeConnections[nodeIndex] | LevelGridNode.DiagonalConnectionsMask;
+ break;
+ case NumNeighbours.Eight:
+ var conns = nodeConnections[nodeIndex];
+
+ // Skip node if no connections are enabled already
+ if (conns == LevelGridNode.AllConnectionsMask) continue;
+
+ // When cutCorners is enabled then the diagonal connection is allowed
+ // if at least one axis aligned connection is adjacent to this diagonal.
+ // Otherwise both axis aligned connections must be present.
+ //
+ // X ----- axis2
+ // | \
+ // | \
+ // | \
+ // axis1 diagonal
+ //
+ // Z
+ // |
+ // |
+ //
+ // 6 2 5
+ // \ | /
+ // -- 3 - X - 1 ----- X
+ // / | \
+ // 7 0 4
+ //
+ // |
+ // |
+ //
+ for (int dir = 0; dir < 4; dir++) {
+ int adjacent = 0;
+ var axis1 = (conns >> dir*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask;
+ var axis2 = (conns >> ((dir+1) % 4)*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask;
+ var diagonal = (conns >> (dir+4)*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask;
+
+ // Check if the diagonal connection is present at all.
+ // The JobCalculateGridConnections calculated this.
+ if (diagonal == LevelGridNode.NoConnection) continue;
+
+ if (axis1 != LevelGridNode.NoConnection) {
+ // We also check that the neighbour node is also connected to the diagonal node
+ var neighbourDir = (dir + 1) % 4;
+ var neighbourIndex = nodeIndexXZ + neighbourOffsets[dir] + (int)axis1 * layerStride;
+ if (((nodeConnections[neighbourIndex] >> neighbourDir*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask) == diagonal) {
+ adjacent++;
+ }
+ }
+ if (axis2 != LevelGridNode.NoConnection) {
+ var neighbourDir = dir;
+ var neighbourIndex = nodeIndexXZ + neighbourOffsets[(dir+1)%4] + (int)axis2 * layerStride;
+ if (((nodeConnections[neighbourIndex] >> neighbourDir*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask) == diagonal) {
+ adjacent++;
+ }
+ }
+
+ if (adjacent < adjacencyThreshold) conns |= (ulong)LevelGridNode.NoConnection << (dir + 4)*LevelGridNode.ConnectionStride;
+ }
+ nodeConnections[nodeIndex] = conns;
+ break;
+ case NumNeighbours.Six:
+ // Hexagon layout
+ // Note that for layered nodes NoConnection is all bits set (see LevelGridNode.NoConnection)
+ // So in contrast to the non-layered grid graph we do a bitwise OR here
+ nodeConnections[nodeIndex] = (nodeConnections[nodeIndex] | ~hexagonConnectionMask) & LevelGridNode.AllConnectionsMask;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta
new file mode 100644
index 0000000..a3d3d9b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e737565f0f6416f4ab1470d4f194ed25
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs
new file mode 100644
index 0000000..61707dd
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs
@@ -0,0 +1,31 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Calculates if either of the two input hits actually hit something.
+ ///
+ /// result[i] = true if hit1[i] or hit2[i] is a valid hit.
+ ///
+ /// A valid hit will always have a non-zero normal.
+ /// </summary>
+ [BurstCompile]
+ public struct JobMergeRaycastCollisionHits : IJob {
+ [ReadOnly]
+ public NativeArray<RaycastHit> hit1;
+
+ [ReadOnly]
+ public NativeArray<RaycastHit> hit2;
+
+ [WriteOnly]
+ public NativeArray<bool> result;
+
+ public void Execute () {
+ for (int i = 0; i < hit1.Length; i++) {
+ result[i] = hit1[i].normal == Vector3.zero && hit2[i].normal == Vector3.zero;
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta
new file mode 100644
index 0000000..73957ca
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: be7b697c92431a74a8db8ae716c3f0b6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs
new file mode 100644
index 0000000..e9bdaf3
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs
@@ -0,0 +1,36 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Calculates the default node positions for a grid graph.
+ ///
+ /// The node positions will lie on the base plane of the grid graph.
+ ///
+ /// See: <see cref="GridGraph.CalculateTransform"/>
+ /// </summary>
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ public struct JobNodeGridLayout : IJob, GridIterationUtilities.ICellAction {
+ public Matrix4x4 graphToWorld;
+ public IntBounds bounds;
+
+ [WriteOnly]
+ public NativeArray<Vector3> nodePositions;
+
+ public static Vector3 NodePosition (Matrix4x4 graphToWorld, int x, int z) {
+ return graphToWorld.MultiplyPoint3x4(new Vector3(x + 0.5f, 0, z + 0.5f));
+ }
+
+ public void Execute () {
+ GridIterationUtilities.ForEachCellIn3DArray(bounds.size, ref this);
+ }
+
+ public void Execute (uint innerIndex, int x, int y, int z) {
+ nodePositions[(int)innerIndex] = NodePosition(graphToWorld, x + bounds.min.x, z + bounds.min.z);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta
new file mode 100644
index 0000000..4a82431
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7dba760f9d6d81e458c5122c0d28b2df
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs
new file mode 100644
index 0000000..7629df7
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs
@@ -0,0 +1,73 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>Calculates for each grid node if it should be walkable or not</summary>
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ public struct JobNodeWalkability : IJob {
+ /// <summary>
+ /// If true, use the normal of the raycast hit to check if the ground is flat enough to stand on.
+ ///
+ /// Any nodes with a steeper slope than <see cref="maxSlope"/> will be made unwalkable.
+ /// </summary>
+ public bool useRaycastNormal;
+ /// <summary>Max slope in degrees</summary>
+ public float maxSlope;
+ /// <summary>Normalized up direction of the graph</summary>
+ public Vector3 up;
+ /// <summary>If true, nodes will be made unwalkable if no ground was found under them</summary>
+ public bool unwalkableWhenNoGround;
+ /// <summary>For layered grid graphs, if there's a node above another node closer than this distance, the lower node will be made unwalkable</summary>
+ public float characterHeight;
+ /// <summary>Number of nodes in each layer</summary>
+ public int layerStride;
+
+ [ReadOnly]
+ public NativeArray<float3> nodePositions;
+
+ public NativeArray<float4> nodeNormals;
+
+ [WriteOnly]
+ public NativeArray<bool> nodeWalkable;
+
+ public void Execute () {
+ // Cosinus of the max slope
+ float cosMaxSlopeAngle = math.cos(math.radians(maxSlope));
+ float4 upNative = new float4(up.x, up.y, up.z, 0);
+ float3 upNative3 = upNative.xyz;
+
+ for (int i = 0; i < nodeNormals.Length; i++) {
+ // walkable will be set to false if no ground was found (unless that setting has been disabled)
+ // The normal will only be non-zero if something was hit.
+ bool didHit = math.any(nodeNormals[i]);
+ var walkable = didHit;
+ if (!didHit && !unwalkableWhenNoGround && i < layerStride) {
+ walkable = true;
+ // If there was no hit, but we still want to make the node walkable, then we set the normal to the up direction
+ nodeNormals[i] = upNative;
+ }
+
+ // Check if the node is on a slope steeper than permitted
+ if (walkable && useRaycastNormal && didHit) {
+ // Take the dot product to find out the cosine of the angle it has (faster than Vector3.Angle)
+ float angle = math.dot(nodeNormals[i], upNative);
+
+ // Check if the ground is flat enough to stand on
+ if (angle < cosMaxSlopeAngle) {
+ walkable = false;
+ }
+ }
+
+ // Check if there is a node above this one (layered grid graph only)
+ if (walkable && i + layerStride < nodeNormals.Length && math.any(nodeNormals[i + layerStride])) {
+ walkable = math.dot(upNative3, nodePositions[i + layerStride] - nodePositions[i]) >= characterHeight;
+ }
+
+ nodeWalkable[i] = walkable;
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta
new file mode 100644
index 0000000..2a3d418
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a4ace8275be3d2f4b990df3672acb302
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs
new file mode 100644
index 0000000..4644787
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs
@@ -0,0 +1,45 @@
+#if UNITY_2022_2_OR_NEWER
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Collections.LowLevel.Unsafe;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Prepares a set of capsule commands for collision checking in a grid graph.
+ ///
+ /// See: <see cref="GraphCollision"/>
+ /// </summary>
+ [BurstCompile]
+ public struct JobPrepareCapsuleCommands : IJob {
+ public Vector3 direction;
+ public Vector3 originOffset;
+ public float radius;
+ public LayerMask mask;
+ public PhysicsScene physicsScene;
+
+ [ReadOnly]
+ public NativeArray<Vector3> origins;
+
+ [WriteOnly]
+ public NativeArray<OverlapCapsuleCommand> commands;
+
+ public void Execute () {
+ var commandSpan = commands.AsUnsafeSpan();
+ // It turns out it is faster to set all commands to the same value using MemCpyReplicate and then patch point0 and point1,
+ // rather than setting each command individually (about 30% faster even).
+ // MemCpy is very fast.
+ var queryParameters = new QueryParameters(mask, false, QueryTriggerInteraction.Ignore, false);
+ commandSpan.Fill(new OverlapCapsuleCommand(physicsScene, Vector3.zero, Vector3.zero, radius, queryParameters));
+
+ for (int i = 0; i < commandSpan.Length; i++) {
+ var origin = origins[i] + originOffset;
+ commandSpan[i].point0 = origin;
+ commandSpan[i].point1 = origin + direction;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta
new file mode 100644
index 0000000..414cc20
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: af594897c5b7f094585ba27f8be57d45
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs
new file mode 100644
index 0000000..e05347d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs
@@ -0,0 +1,61 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Collections.LowLevel.Unsafe;
+using Pathfinding.Util;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Prepares a set of raycast commands for a grid graph.
+ ///
+ /// Each ray will start at <see cref="raycastOffset"/> from the node's position. The end point of the raycast will be the start point + <see cref="raycastDirection"/>.
+ ///
+ /// See: <see cref="GraphCollision"/>
+ /// </summary>
+ [BurstCompile]
+ public struct JobPrepareGridRaycast : IJob {
+ public Matrix4x4 graphToWorld;
+ public IntBounds bounds;
+ public Vector3 raycastOffset;
+ public Vector3 raycastDirection;
+ public LayerMask raycastMask;
+ public PhysicsScene physicsScene;
+
+ [WriteOnly]
+ public NativeArray<RaycastCommand> raycastCommands;
+
+ public void Execute () {
+ float raycastLength = raycastDirection.magnitude;
+ var size = bounds.size;
+
+ // In particular Unity 2022.2 seems to assert that RaycastCommands use normalized directions
+ var direction = raycastDirection.normalized;
+ var commands = raycastCommands.AsUnsafeSpan();
+
+ Assert.AreEqual(commands.Length, size.x * size.z);
+
+#if UNITY_2022_2_OR_NEWER
+ var queryParameters = new QueryParameters(raycastMask, false, QueryTriggerInteraction.Ignore, false);
+ // This is about 30% faster than setting each command individually. MemCpy is fast.
+ commands.Fill(new RaycastCommand(physicsScene, Vector3.zero, direction, queryParameters, raycastLength));
+#else
+ const int RaycastMaxHits = 1;
+#endif
+
+ for (int z = 0; z < size.z; z++) {
+ int zw = z * size.x;
+ for (int x = 0; x < size.x; x++) {
+ int idx = zw + x;
+ var pos = JobNodeGridLayout.NodePosition(graphToWorld, x + bounds.min.x, z + bounds.min.z);
+#if UNITY_2022_2_OR_NEWER
+ commands[idx].from = pos + raycastOffset;
+#else
+ commands[idx] = new RaycastCommand(pos + raycastOffset, direction, raycastLength, raycastMask, RaycastMaxHits);
+#endif
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta
new file mode 100644
index 0000000..3800b46
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f081267224f64d44a99b9962d3d300a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs
new file mode 100644
index 0000000..8a7fc7a
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs
@@ -0,0 +1,51 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Collections.LowLevel.Unsafe;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Prepares a set of raycast commands for a grid graph.
+ ///
+ /// This is very similar to <see cref="JobPrepareGridRaycast"/> but it uses an array of origin points instead of a grid pattern.
+ ///
+ /// See: <see cref="GraphCollision"/>
+ /// </summary>
+ [BurstCompile]
+ public struct JobPrepareRaycasts : IJob {
+ public Vector3 direction;
+ public Vector3 originOffset;
+ public float distance;
+ public LayerMask mask;
+ public PhysicsScene physicsScene;
+
+ [ReadOnly]
+ public NativeArray<Vector3> origins;
+
+ [WriteOnly]
+ public NativeArray<RaycastCommand> raycastCommands;
+
+ public void Execute () {
+ // In particular Unity 2022.2 seems to assert that RaycastCommands use normalized directions
+ var direction = this.direction.normalized;
+ var commands = raycastCommands.AsUnsafeSpan();
+
+#if UNITY_2022_2_OR_NEWER
+ var queryParameters = new QueryParameters(mask, false, QueryTriggerInteraction.Ignore, false);
+ var defaultCommand = new RaycastCommand(physicsScene, Vector3.zero, direction, queryParameters, distance);
+ // This is about 30% faster than setting each command individually. MemCpy is fast.
+ commands.Fill(defaultCommand);
+#endif
+
+ for (int i = 0; i < raycastCommands.Length; i++) {
+#if UNITY_2022_2_OR_NEWER
+ commands[i].from = origins[i] + originOffset;
+#else
+ raycastCommands[i] = new RaycastCommand(origins[i] + originOffset, direction, distance, mask, 1);
+#endif
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta
new file mode 100644
index 0000000..c998e6b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 41ad6b54690bf4949a2974c3ca5d2503
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs
new file mode 100644
index 0000000..50562a2
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs
@@ -0,0 +1,42 @@
+#if UNITY_2022_2_OR_NEWER
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Collections.LowLevel.Unsafe;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Prepares a set of sphere commands for collision checking in a grid graph.
+ ///
+ /// See: <see cref="GraphCollision"/>
+ /// </summary>
+ [BurstCompile]
+ public struct JobPrepareSphereCommands : IJob {
+ public Vector3 originOffset;
+ public float radius;
+ public LayerMask mask;
+ public PhysicsScene physicsScene;
+
+ [ReadOnly]
+ public NativeArray<Vector3> origins;
+
+ [WriteOnly]
+ public NativeArray<OverlapSphereCommand> commands;
+
+ public void Execute () {
+ var commandSpan = commands.AsUnsafeSpan();
+ // It turns out it is faster to set all commands to the same value using MemCpyReplicate and then patch point,
+ // rather than setting each command individually.
+ var queryParameters = new QueryParameters(mask, false, QueryTriggerInteraction.Ignore, false);
+ commandSpan.Fill(new OverlapSphereCommand(physicsScene, Vector3.zero, radius, queryParameters));
+
+ for (int i = 0; i < commandSpan.Length; i++) {
+ var origin = origins[i] + originOffset;
+ commandSpan[i].point = origin;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta
new file mode 100644
index 0000000..f5fbad1
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: db154518b4b8b044d946bb6a9770bdbf
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs
new file mode 100644
index 0000000..fd34a1d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs
@@ -0,0 +1,121 @@
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using UnityEngine;
+using Pathfinding.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Jobs {
+ public struct JobRaycastAll {
+ int maxHits;
+ public readonly float minStep;
+
+ NativeArray<RaycastHit> results;
+ NativeArray<RaycastHit> semiResults;
+ NativeArray<RaycastCommand> commands;
+ public PhysicsScene physicsScene;
+
+ [BurstCompile]
+ private struct JobCreateCommands : IJobParallelFor {
+ public NativeArray<RaycastCommand> commands;
+ [ReadOnly]
+ public NativeArray<RaycastHit> raycastHits;
+
+ public float minStep;
+ public PhysicsScene physicsScene;
+
+ public void Execute (int index) {
+ var rayHit = raycastHits[index];
+
+ if (rayHit.normal != default(Vector3)) {
+ var previousCommand = commands[index];
+ // Little hack to bypass same collider hit in specific cases
+ var point = rayHit.point + previousCommand.direction.normalized * minStep;
+ var distance = previousCommand.distance - (point - previousCommand.from).magnitude;
+#if UNITY_2022_2_OR_NEWER
+ // TODO: In 2022.2 with the 'hit multiple faces' option, this whole class might be redundant.
+ var queryParameters = new QueryParameters(previousCommand.queryParameters.layerMask, false, QueryTriggerInteraction.Ignore, false);
+ commands[index] = new RaycastCommand(physicsScene, point, previousCommand.direction, queryParameters, distance);
+#else
+ commands[index] = new RaycastCommand(point, previousCommand.direction, distance, previousCommand.layerMask, 1);
+#endif
+ } else {
+#if UNITY_2022_2_OR_NEWER
+ // Note: Using a default RaycastCommand may cause Unity to crash.
+ // This seems to be primarily because it assumes a non-zero direction.
+ commands[index] = new RaycastCommand(physicsScene, Vector3.zero, Vector3.up, new QueryParameters(0, false, QueryTriggerInteraction.Ignore, false), 1);
+#else
+ commands[index] = new RaycastCommand(Vector3.zero, Vector3.up, 1, 0, 1);
+#endif
+ }
+ }
+ }
+
+ [BurstCompile]
+ private struct JobCombineResults : IJob {
+ public int maxHits;
+ [ReadOnly]
+ public NativeArray<RaycastHit> semiResults;
+ public NativeArray<RaycastHit> results;
+
+ public void Execute () {
+ int layerStride = semiResults.Length / maxHits;
+
+ for (int i = 0; i < layerStride; i++) {
+ int layerOffset = 0;
+
+ for (int j = maxHits - 1; j >= 0; j--) {
+ if (math.any(semiResults[i + j*layerStride].normal)) {
+ results[i + layerOffset] = semiResults[i + j*layerStride];
+ layerOffset += layerStride;
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>Jobified version of Physics.RaycastNonAlloc.</summary>
+ /// <param name="commands">Array of commands to perform.</param>
+ /// <param name="results">Array to store results in.</param>
+ /// <param name="physicsScene">PhysicsScene to use for the raycasts. Only used in Unity 2022.2 or later.</param>
+ /// <param name="maxHits">Max hits count per command.</param>
+ /// <param name="allocator">Allocator to use for the results array.</param>
+ /// <param name="dependencyTracker">Tracker to use for dependencies.</param>
+ /// <param name="minStep">Minimal distance each Raycast should progress.</param>
+ public JobRaycastAll(NativeArray<RaycastCommand> commands, NativeArray<RaycastHit> results, PhysicsScene physicsScene, int maxHits, Allocator allocator, JobDependencyTracker dependencyTracker, float minStep = 0.0001f) {
+ if (maxHits <= 0) throw new System.ArgumentException("maxHits should be greater than zero");
+ if (results.Length < commands.Length * maxHits) throw new System.ArgumentException("Results array length does not match maxHits count");
+ if (minStep < 0f) throw new System.ArgumentException("minStep should be more or equal to zero");
+
+ this.results = results;
+ this.maxHits = maxHits;
+ this.minStep = minStep;
+ this.commands = commands;
+ this.physicsScene = physicsScene;
+
+ semiResults = dependencyTracker.NewNativeArray<RaycastHit>(maxHits * commands.Length, allocator);
+ }
+
+ public JobHandle Schedule (JobHandle dependency) {
+ for (int i = 0; i < maxHits; i++) {
+ var semiResultsPart = semiResults.GetSubArray(i*commands.Length, commands.Length);
+ dependency = RaycastCommand.ScheduleBatch(commands, semiResultsPart, 128, dependency);
+ if (i < maxHits - 1) {
+ var filter = new JobCreateCommands {
+ commands = commands,
+ raycastHits = semiResultsPart,
+ minStep = minStep,
+ physicsScene = physicsScene,
+ };
+ dependency = filter.Schedule(commands.Length, 256, dependency);
+ }
+ }
+
+ return new JobCombineResults {
+ semiResults = semiResults,
+ maxHits = maxHits,
+ results = results
+ }.Schedule(dependency);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs.meta
new file mode 100644
index 0000000..aed1b42
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f8eb8ba655e2d2dc88958d073ff52f3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs
new file mode 100644
index 0000000..a94b652
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs
@@ -0,0 +1,93 @@
+using UnityEngine;
+using Unity.Collections;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Reads node data from managed <see cref="GridNodeBase"/> objects into unmanaged arrays.
+ ///
+ /// This is done so that burst jobs can later access this data directly.
+ ///
+ /// Later, data will be written back to the managed objects using the <see cref="JobWriteNodeData"/> job.
+ /// </summary>
+ public struct JobReadNodeData : IJobParallelForBatched {
+ public System.Runtime.InteropServices.GCHandle nodesHandle;
+ public uint graphIndex;
+
+ public Slice3D slice;
+
+ [WriteOnly]
+ public NativeArray<Vector3> nodePositions;
+
+ [WriteOnly]
+ public NativeArray<uint> nodePenalties;
+
+ [WriteOnly]
+ public NativeArray<int> nodeTags;
+
+ [WriteOnly]
+ public NativeArray<ulong> nodeConnections;
+
+ [WriteOnly]
+ public NativeArray<bool> nodeWalkableWithErosion;
+
+ [WriteOnly]
+ public NativeArray<bool> nodeWalkable;
+
+ public bool allowBoundsChecks => false;
+
+ struct Reader : GridIterationUtilities.ISliceAction {
+ public GridNodeBase[] nodes;
+ public NativeArray<Vector3> nodePositions;
+ public NativeArray<uint> nodePenalties;
+ public NativeArray<int> nodeTags;
+ public NativeArray<ulong> nodeConnections;
+ public NativeArray<bool> nodeWalkableWithErosion;
+ public NativeArray<bool> nodeWalkable;
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ public void Execute (uint outerIdx, uint innerIdx) {
+ var dataIdx = (int)innerIdx;
+ // The data bounds may have more layers than the existing nodes if a new layer is being added.
+ // We can only copy from the nodes that exist.
+ if (outerIdx < nodes.Length) {
+ var node = nodes[outerIdx];
+ if (node != null) {
+ nodePositions[dataIdx] = (Vector3)node.position;
+ nodePenalties[dataIdx] = node.Penalty;
+ nodeTags[dataIdx] = (int)node.Tag;
+ nodeConnections[dataIdx] = node is GridNode gn ? (ulong)gn.GetAllConnectionInternal() : (node as LevelGridNode).gridConnections;
+ nodeWalkableWithErosion[dataIdx] = node.Walkable;
+ nodeWalkable[dataIdx] = node.WalkableErosion;
+ return;
+ }
+ }
+
+ // Fallback in case the node was null (only happens for layered grid graphs),
+ // or if we are adding more layers to the graph, in which case we are outside
+ // the bounds of the nodes array.
+ nodePositions[dataIdx] = Vector3.zero;
+ nodePenalties[dataIdx] = 0;
+ nodeTags[dataIdx] = 0;
+ nodeConnections[dataIdx] = 0;
+ nodeWalkableWithErosion[dataIdx] = false;
+ nodeWalkable[dataIdx] = false;
+ }
+ }
+
+ public void Execute (int startIndex, int count) {
+ var reader = new Reader {
+ // This is a managed type, we need to trick Unity to allow this inside of a job
+ nodes = (GridNodeBase[])nodesHandle.Target,
+ nodePositions = nodePositions,
+ nodePenalties = nodePenalties,
+ nodeTags = nodeTags,
+ nodeConnections = nodeConnections,
+ nodeWalkableWithErosion = nodeWalkableWithErosion,
+ nodeWalkable = nodeWalkable
+ };
+ GridIterationUtilities.ForEachCellIn3DSlice(slice, ref reader);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs.meta
new file mode 100644
index 0000000..2092692
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 203c976aeb6f3d84caeab084738a53c8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs
new file mode 100644
index 0000000..96a58a8
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs
@@ -0,0 +1,91 @@
+using UnityEngine;
+using Unity.Collections;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// <summary>
+ /// Writes node data from unmanaged arrays into managed <see cref="GridNodeBase"/> objects.
+ ///
+ /// This is done after burst jobs have been working on graph data, as they cannot access the managed objects directly.
+ ///
+ /// Earlier, data will have been either calculated from scratch, or read from the managed objects using the <see cref="JobReadNodeData"/> job.
+ /// </summary>
+ public struct JobWriteNodeData : IJobParallelForBatched {
+ public System.Runtime.InteropServices.GCHandle nodesHandle;
+ public uint graphIndex;
+
+ /// <summary>(width, depth) of the array that the <see cref="nodesHandle"/> refers to</summary>
+ public int3 nodeArrayBounds;
+ public IntBounds dataBounds;
+ public IntBounds writeMask;
+
+ [ReadOnly]
+ public NativeArray<Vector3> nodePositions;
+
+ [ReadOnly]
+ public NativeArray<uint> nodePenalties;
+
+ [ReadOnly]
+ public NativeArray<int> nodeTags;
+
+ [ReadOnly]
+ public NativeArray<ulong> nodeConnections;
+
+ [ReadOnly]
+ public NativeArray<bool> nodeWalkableWithErosion;
+
+ [ReadOnly]
+ public NativeArray<bool> nodeWalkable;
+
+ public bool allowBoundsChecks => false;
+
+ public void Execute (int startIndex, int count) {
+ // This is a managed type, we need to trick Unity to allow this inside of a job
+ var nodes = (GridNodeBase[])nodesHandle.Target;
+
+ var relativeMask = writeMask.Offset(-dataBounds.min);
+
+ // Determinstically convert the indices to rows. It is much easier to process a number of whole rows.
+ var writeSize = writeMask.size;
+ var zstart = startIndex / (writeSize.x*writeSize.y);
+ var zend = (startIndex+count) / (writeSize.x*writeSize.y);
+
+ Assert.IsTrue(zstart >= 0 && zstart <= writeSize.z);
+ Assert.IsTrue(zend >= 0 && zend <= writeSize.z);
+ relativeMask.min.z = writeMask.min.z + zstart - dataBounds.min.z;
+ relativeMask.max.z = writeMask.min.z + zend - dataBounds.min.z;
+
+ var dataSize = dataBounds.size;
+ for (int y = relativeMask.min.y; y < relativeMask.max.y; y++) {
+ for (int z = relativeMask.min.z; z < relativeMask.max.z; z++) {
+ var rowOffset1 = (y*dataSize.z + z)*dataSize.x;
+ var rowOffset2 = (z + dataBounds.min.z)*nodeArrayBounds.x + dataBounds.min.x;
+ var rowOffset3 = (y + dataBounds.min.y)*nodeArrayBounds.z*nodeArrayBounds.x + rowOffset2;
+ for (int x = relativeMask.min.x; x < relativeMask.max.x; x++) {
+ int dataIndex = rowOffset1 + x;
+ int nodeIndex = rowOffset3 + x;
+ var node = nodes[nodeIndex];
+ if (node != null) {
+ node.GraphIndex = graphIndex;
+ node.NodeInGridIndex = rowOffset2 + x;
+ // TODO: Use UnsafeSpan
+ node.position = (Int3)nodePositions[dataIndex];
+ node.Penalty = nodePenalties[dataIndex];
+ node.Tag = (uint)nodeTags[dataIndex];
+ if (node is GridNode gridNode) {
+ gridNode.SetAllConnectionInternal((int)nodeConnections[dataIndex]);
+ } else if (node is LevelGridNode levelGridNode) {
+ levelGridNode.LayerCoordinateInGrid = y + dataBounds.min.y;
+ levelGridNode.SetAllConnectionInternal(nodeConnections[dataIndex]);
+ }
+ node.Walkable = nodeWalkableWithErosion[dataIndex];
+ node.WalkableErosion = nodeWalkable[dataIndex];
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta
new file mode 100644
index 0000000..503743b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 86fcb7ce1d754024290197052acf39d5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: