namespace Pathfinding.Jobs {
using UnityEngine;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine.Assertions;
using Pathfinding.Graphs.Grid;
using Pathfinding.Collections;
///
/// Slice of a 3D array.
///
/// This is a helper struct used in many jobs to make them work on a part of the data.
///
/// The outer array has the size .x * .y * .z, laid out as if the coordinates were sorted by the tuple (Y,Z,X).
/// The inner array has the size .x * .y * .z, also laid out as if the coordinates were sorted by the tuple (Y,Z,X).
///
public readonly struct Slice3D {
public readonly int3 outerSize;
public readonly IntBounds slice;
public Slice3D (IntBounds outer, IntBounds slice) : this(outer.size, slice.Offset(-outer.min)) {}
public Slice3D (int3 outerSize, IntBounds slice) {
this.outerSize = outerSize;
this.slice = slice;
Assert.IsTrue(slice.min.x >= 0 && slice.min.y >= 0 && slice.min.z >= 0);
Assert.IsTrue(slice.max.x <= outerSize.x && slice.max.y <= outerSize.y && slice.max.z <= outerSize.z);
Assert.IsTrue(slice.size.x > 0 && slice.size.y > 0 && slice.size.z > 0);
}
public void AssertMatchesOuter(UnsafeSpan values) where T : unmanaged {
Assert.AreEqual(outerSize.x * outerSize.y * outerSize.z, values.Length);
}
public void AssertMatchesOuter(NativeArray values) where T : struct {
Assert.AreEqual(outerSize.x * outerSize.y * outerSize.z, values.Length);
}
public void AssertMatchesInner(NativeArray values) where T : struct {
Assert.AreEqual(slice.size.x * slice.size.y * slice.size.z, values.Length);
}
public void AssertSameSize (Slice3D other) {
Assert.AreEqual(slice.size, other.slice.size);
}
public int InnerCoordinateToOuterIndex (int x, int y, int z) {
var(dx, dy, dz) = outerStrides;
return (x + slice.min.x) * dx + (y + slice.min.y) * dy + (z + slice.min.z) * dz;
}
public int length => slice.size.x * slice.size.y * slice.size.z;
public (int, int, int)outerStrides => (1, outerSize.x * outerSize.z, outerSize.x);
public (int, int, int)innerStrides => (1, slice.size.x * slice.size.z, slice.size.x);
public int outerStartIndex {
get {
var(dx, dy, dz) = outerStrides;
return slice.min.x * dx + slice.min.y * dy + slice.min.z * dz;
}
}
/// True if the slice covers the whole outer array
public bool coversEverything => math.all(slice.size == outerSize);
}
/// Helpers for scheduling simple NativeArray jobs
static class NativeArrayExtensions {
/// this[i] = value
public static JobMemSet MemSet(this NativeArray self, T value) where T : unmanaged {
return new JobMemSet {
data = self,
value = value,
};
}
/// this[i] &= other[i]
public static JobAND BitwiseAndWith (this NativeArray self, NativeArray other) {
return new JobAND {
result = self,
data = other,
};
}
/// to[i] = from[i]
public static JobCopy CopyToJob(this NativeArray from, NativeArray to) where T : struct {
return new JobCopy {
from = from,
to = to,
};
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static SliceActionJob WithSlice(this T action, Slice3D slice) where T : struct, GridIterationUtilities.ISliceAction {
return new SliceActionJob {
action = action,
slice = slice,
};
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static IndexActionJob WithLength(this T action, int length) where T : struct, GridIterationUtilities.ISliceAction {
return new IndexActionJob {
action = action,
length = length,
};
}
public static JobRotate3DArray Rotate3D(this NativeArray arr, int3 size, int dx, int dz) where T : unmanaged {
return new JobRotate3DArray {
arr = arr,
size = size,
dx = dx,
dz = dz,
};
}
}
///
/// Treats input as a 3-dimensional array and copies it into the output at the specified position.
///
/// The is a 3D array, and refers to a rectangular slice of this array.
/// The is defined similarly.
///
/// The two slices must naturally have the same shape.
///
[BurstCompile]
public struct JobCopyRectangle : IJob where T : struct {
[ReadOnly]
[DisableUninitializedReadCheck] // TODO: Fix so that job doesn't run instead
public NativeArray input;
[WriteOnly]
public NativeArray output;
public Slice3D inputSlice;
public Slice3D outputSlice;
public void Execute () {
Copy(input, output, inputSlice, outputSlice);
}
///
/// Treats input as a 3-dimensional array and copies it into the output at the specified position.
///
/// The input is a 3D array, and inputSlice refers to a rectangular slice of this array.
/// The output is defined similarly.
///
/// The two slices must naturally have the same shape.
///
public static void Copy (NativeArray input, NativeArray output, Slice3D inputSlice, Slice3D outputSlice) {
inputSlice.AssertMatchesOuter(input);
outputSlice.AssertMatchesOuter(output);
inputSlice.AssertSameSize(outputSlice);
if (inputSlice.coversEverything && outputSlice.coversEverything) {
// One contiguous chunk
// TODO: Check can be made better by only checking if it is a contiguous chunk instead of covering the whole arrays
input.CopyTo(output);
} else {
// Copy row-by-row
for (int y = 0; y < outputSlice.slice.size.y; y++) {
for (int z = 0; z < outputSlice.slice.size.z; z++) {
var rowOffsetInput = inputSlice.InnerCoordinateToOuterIndex(0, y, z);
var rowOffsetOutput = outputSlice.InnerCoordinateToOuterIndex(0, y, z);
// Using a raw MemCpy call is a bit faster, but that requires unsafe code
// Using a for loop is *a lot* slower (except for very small arrays, in which case it is about the same or very slightly faster).
NativeArray.Copy(input, rowOffsetInput, output, rowOffsetOutput, outputSlice.slice.size.x);
}
}
}
}
}
/// result[i] = value
[BurstCompile]
public struct JobMemSet : IJob where T : unmanaged {
[WriteOnly]
public NativeArray data;
public T value;
public void Execute() => data.AsUnsafeSpan().Fill(value);
}
/// to[i] = from[i]
[BurstCompile]
public struct JobCopy : IJob where T : struct {
[ReadOnly]
public NativeArray from;
[WriteOnly]
public NativeArray to;
public void Execute () {
from.CopyTo(to);
}
}
[BurstCompile]
public struct IndexActionJob : IJob where T : struct, GridIterationUtilities.ISliceAction {
public T action;
public int length;
public void Execute () {
for (int i = 0; i < length; i++) action.Execute((uint)i, (uint)i);
}
}
[BurstCompile]
public struct SliceActionJob : IJob where T : struct, GridIterationUtilities.ISliceAction {
public T action;
public Slice3D slice;
public void Execute () {
GridIterationUtilities.ForEachCellIn3DSlice(slice, ref action);
}
}
/// result[i] &= data[i]
public struct JobAND : GridIterationUtilities.ISliceAction {
public NativeArray result;
[ReadOnly]
public NativeArray data;
public void Execute (uint outerIdx, uint innerIdx) {
result[(int)outerIdx] &= data[(int)outerIdx];
}
}
[BurstCompile]
public struct JobMaxHitCount : IJob {
[ReadOnly]
public NativeArray hits;
public int maxHits;
public int layerStride;
[WriteOnly]
public NativeArray maxHitCount;
public void Execute () {
int maxHit = 0;
for (; maxHit < maxHits; maxHit++) {
int offset = maxHit * layerStride;
bool any = false;
for (int i = offset; i < offset + layerStride; i++) {
if (math.any(hits[i].normal)) {
any = true;
break;
}
}
if (!any) break;
}
maxHitCount[0] = math.max(1, maxHit);
}
}
///
/// Copies hit points and normals.
/// points[i] = hits[i].point (if anything was hit), normals[i] = hits[i].normal.normalized.
///
[BurstCompile(FloatMode = FloatMode.Fast)]
public struct JobCopyHits : IJob, GridIterationUtilities.ISliceAction {
[ReadOnly]
public NativeArray hits;
[WriteOnly]
public NativeArray points;
[WriteOnly]
public NativeArray normals;
public Slice3D slice;
public void Execute () {
// The number of hits may be larger than the number of points. The remaining hits are not actually hits.
Assert.IsTrue(hits.Length >= slice.length);
slice.AssertMatchesOuter(points);
slice.AssertMatchesOuter(normals);
GridIterationUtilities.ForEachCellIn3DSlice(slice, ref this);
}
public void Execute (uint outerIdx, uint innerIdx) {
Unity.Burst.CompilerServices.Aliasing.ExpectNotAliased(points, normals);
var normal = hits[(int)innerIdx].normal;
var normalV4 = new float4(normal.x, normal.y, normal.z, 0);
normals[(int)outerIdx] = math.normalizesafe(normalV4);
// Check if anything was hit. The normal will be zero otherwise
// If nothing was hit then the existing data in the points array is reused
if (math.lengthsq(normalV4) > math.FLT_MIN_NORMAL) {
points[(int)outerIdx] = hits[(int)innerIdx].point;
}
}
}
[BurstCompile]
public struct JobRotate3DArray: IJob where T : unmanaged {
public NativeArray arr;
public int3 size;
public int dx, dz;
public void Execute () {
int width = size.x;
int height = size.y;
int depth = size.z;
var span = arr.AsUnsafeSpan();
dx = dx % width;
dz = dz % depth;
if (dx != 0) {
if (dx < 0) dx = width + dx;
var tmp = new NativeArray(dx, Allocator.Temp);
var tmpSpan = tmp.AsUnsafeSpan();
for (int y = 0; y < height; y++) {
var offset = y * width * depth;
for (int z = 0; z < depth; z++) {
span.Slice(offset + z * width + width - dx, dx).CopyTo(tmpSpan);
span.Move(offset + z * width, offset + z * width + dx, width - dx);
tmpSpan.CopyTo(span.Slice(offset + z * width, dx));
}
}
}
if (dz != 0) {
if (dz < 0) dz = depth + dz;
var tmp = new NativeArray(dz * width, Allocator.Temp);
var tmpSpan = tmp.AsUnsafeSpan();
for (int y = 0; y < height; y++) {
var offset = y * width * depth;
span.Slice(offset + (depth - dz) * width, dz * width).CopyTo(tmpSpan);
span.Move(offset, offset + dz * width, (depth - dz) * width);
tmpSpan.CopyTo(span.Slice(offset, dz * width));
}
}
}
}
}