122 lines
4.6 KiB
C#

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);
}
}
}