update
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
using Pathfinding.Jobs;
|
||||
using Pathfinding.Sync;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>
|
||||
/// Builds nodes and tiles and prepares them for pathfinding.
|
||||
///
|
||||
/// Takes input from a <see cref="TileBuilder"/> job and outputs a <see cref="BuildNodeTilesOutput"/>.
|
||||
///
|
||||
/// This job takes the following steps:
|
||||
/// - Calculate connections between nodes inside each tile
|
||||
/// - Create node and tile objects
|
||||
/// - Connect adjacent tiles together
|
||||
/// </summary>
|
||||
public struct JobBuildNodes {
|
||||
uint graphIndex;
|
||||
public uint initialPenalty;
|
||||
public bool recalculateNormals;
|
||||
public float maxTileConnectionEdgeDistance;
|
||||
Matrix4x4 graphToWorldSpace;
|
||||
TileLayout tileLayout;
|
||||
|
||||
public class BuildNodeTilesOutput : IProgress, System.IDisposable {
|
||||
public TileBuilder.TileBuilderOutput progressSource;
|
||||
public NavmeshTile[] tiles;
|
||||
|
||||
public float Progress => progressSource.Progress;
|
||||
|
||||
public void Dispose () {
|
||||
}
|
||||
}
|
||||
|
||||
internal JobBuildNodes(RecastGraph graph, TileLayout tileLayout) {
|
||||
this.tileLayout = tileLayout;
|
||||
this.graphIndex = graph.graphIndex;
|
||||
this.initialPenalty = graph.initialPenalty;
|
||||
this.recalculateNormals = graph.RecalculateNormals;
|
||||
this.maxTileConnectionEdgeDistance = graph.MaxTileConnectionEdgeDistance;
|
||||
this.graphToWorldSpace = tileLayout.transform.matrix;
|
||||
}
|
||||
|
||||
public Promise<BuildNodeTilesOutput> Schedule (DisposeArena arena, Promise<TileBuilder.TileBuilderOutput> preCutDependency, Promise<TileCutter.TileCutterOutput> postCutDependency) {
|
||||
var postCutInput = postCutDependency.GetValue();
|
||||
var preCutInput = preCutDependency.GetValue();
|
||||
var tileRect = preCutInput.tileMeshes.tileRect;
|
||||
|
||||
NativeArray<TileMesh.TileMeshUnsafe> finalTileMeshes;
|
||||
if (postCutInput.tileMeshes.tileMeshes.IsCreated) {
|
||||
UnityEngine.Assertions.Assert.AreEqual(postCutInput.tileMeshes.tileMeshes.Length, tileRect.Area);
|
||||
finalTileMeshes = postCutInput.tileMeshes.tileMeshes;
|
||||
} else {
|
||||
finalTileMeshes = preCutInput.tileMeshes.tileMeshes;
|
||||
}
|
||||
|
||||
UnityEngine.Assertions.Assert.AreEqual(preCutInput.tileMeshes.tileMeshes.Length, tileRect.Area);
|
||||
var tiles = new NavmeshTile[tileRect.Area];
|
||||
var tilesGCHandle = System.Runtime.InteropServices.GCHandle.Alloc(tiles);
|
||||
var nodeConnections = new NativeArray<JobCalculateTriangleConnections.TileNodeConnectionsUnsafe>(tileRect.Area, Allocator.Persistent);
|
||||
|
||||
var calculateConnectionsJob = new JobCalculateTriangleConnections {
|
||||
tileMeshes = finalTileMeshes,
|
||||
nodeConnections = nodeConnections,
|
||||
}.Schedule(postCutDependency.handle);
|
||||
|
||||
var tileWorldSize = new Vector2(tileLayout.TileWorldSizeX, tileLayout.TileWorldSizeZ);
|
||||
var createTilesJob = new JobCreateTiles {
|
||||
// If any cutting is done, we need to save the pre-cut data to be able to re-cut tiles later
|
||||
preCutTileMeshes = postCutInput.tileMeshes.tileMeshes.IsCreated ? preCutInput.tileMeshes.tileMeshes : default,
|
||||
tileMeshes = finalTileMeshes,
|
||||
tiles = tilesGCHandle,
|
||||
tileRect = tileRect,
|
||||
graphTileCount = tileLayout.tileCount,
|
||||
graphIndex = graphIndex,
|
||||
initialPenalty = initialPenalty,
|
||||
recalculateNormals = recalculateNormals,
|
||||
graphToWorldSpace = this.graphToWorldSpace,
|
||||
tileWorldSize = tileWorldSize,
|
||||
}.Schedule(postCutDependency.handle);
|
||||
|
||||
var applyConnectionsJob = new JobWriteNodeConnections {
|
||||
nodeConnections = nodeConnections,
|
||||
tiles = tilesGCHandle,
|
||||
}.Schedule(JobHandle.CombineDependencies(calculateConnectionsJob, createTilesJob));
|
||||
|
||||
Profiler.BeginSample("Scheduling ConnectTiles");
|
||||
var connectTilesDependency = JobConnectTiles.ScheduleBatch(tilesGCHandle, applyConnectionsJob, tileRect, tileWorldSize, maxTileConnectionEdgeDistance);
|
||||
Profiler.EndSample();
|
||||
|
||||
arena.Add(tilesGCHandle);
|
||||
arena.Add(nodeConnections);
|
||||
|
||||
return new Promise<BuildNodeTilesOutput>(connectTilesDependency, new BuildNodeTilesOutput {
|
||||
progressSource = preCutInput,
|
||||
tiles = tiles,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd51eca97d285874d997d22edd420a27
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
using Pathfinding.Util;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using Pathfinding.Collections;
|
||||
using Pathfinding.Sync;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>
|
||||
/// Builds tiles from raw mesh vertices and indices.
|
||||
///
|
||||
/// This job takes the following steps:
|
||||
/// - Transform all vertices using the <see cref="meshToGraph"/> matrix.
|
||||
/// - Remove duplicate vertices
|
||||
/// - If <see cref="recalculateNormals"/> is enabled: ensure all triangles are laid out in the clockwise direction.
|
||||
/// </summary>
|
||||
[BurstCompile(FloatMode = FloatMode.Default)]
|
||||
public struct JobBuildTileMeshFromVertices : IJob {
|
||||
public NativeArray<Vector3> vertices;
|
||||
public NativeArray<int> indices;
|
||||
public Matrix4x4 meshToGraph;
|
||||
public NativeArray<TileMesh.TileMeshUnsafe> outputBuffers;
|
||||
public bool recalculateNormals;
|
||||
|
||||
|
||||
[BurstCompile(FloatMode = FloatMode.Fast)]
|
||||
public struct JobTransformTileCoordinates : IJob {
|
||||
public NativeArray<Vector3> vertices;
|
||||
public NativeArray<Int3> outputVertices;
|
||||
public Matrix4x4 matrix;
|
||||
|
||||
public void Execute () {
|
||||
if (vertices.Length != outputVertices.Length) throw new System.ArgumentException("Input and output arrays must have the same length");
|
||||
for (int i = 0; i < vertices.Length; i++) {
|
||||
outputVertices[i] = (Int3)matrix.MultiplyPoint3x4(vertices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Promise<TileBuilder.TileBuilderOutput> Schedule (NativeArray<Vector3> vertices, NativeArray<int> indices, Matrix4x4 meshToGraph, bool recalculateNormals) {
|
||||
if (vertices.Length > NavmeshBase.VertexIndexMask) throw new System.ArgumentException("Too many vertices in the navmesh graph. Provided " + vertices.Length + ", but the maximum number of vertices per tile is " + NavmeshBase.VertexIndexMask + ". You can raise this limit by enabling ASTAR_RECAST_LARGER_TILES in the A* Inspector Optimizations tab");
|
||||
|
||||
var outputBuffers = new NativeArray<TileMesh.TileMeshUnsafe>(1, Allocator.Persistent);
|
||||
|
||||
var job = new JobBuildTileMeshFromVertices {
|
||||
vertices = vertices,
|
||||
indices = indices,
|
||||
meshToGraph = meshToGraph,
|
||||
outputBuffers = outputBuffers,
|
||||
recalculateNormals = recalculateNormals,
|
||||
}.Schedule();
|
||||
return new Promise<TileBuilder.TileBuilderOutput>(job, new TileBuilder.TileBuilderOutput {
|
||||
// TODO: Tile world size is wrong
|
||||
tileMeshes = new TileMeshesUnsafe(outputBuffers, new IntRect(0, 0, 0, 0), new Vector2(100000, 100000)),
|
||||
});
|
||||
}
|
||||
|
||||
public void Execute () {
|
||||
var int3vertices = new NativeList<Int3>(vertices.Length, Allocator.Temp);
|
||||
int3vertices.Length = vertices.Length;
|
||||
var tags = new NativeList<int>(indices.Length / 3, Allocator.Temp);
|
||||
tags.Length = indices.Length / 3;
|
||||
var triangles = new NativeList<int>(indices.Length, Allocator.Temp);
|
||||
triangles.AddRange(indices);
|
||||
|
||||
new JobTransformTileCoordinates {
|
||||
vertices = vertices,
|
||||
outputVertices = int3vertices.AsArray(),
|
||||
matrix = meshToGraph,
|
||||
}.Execute();
|
||||
|
||||
unsafe {
|
||||
UnityEngine.Assertions.Assert.IsTrue(this.outputBuffers.Length == 1);
|
||||
var tile = (TileMesh.TileMeshUnsafe*) this.outputBuffers.GetUnsafePtr();
|
||||
new MeshUtility.JobRemoveDuplicateVertices {
|
||||
vertices = int3vertices,
|
||||
triangles = triangles,
|
||||
tags = tags,
|
||||
}.Execute();
|
||||
|
||||
// Convert the buffers to spans that own their memory.
|
||||
// The spans may be smaller than the underlaying allocation,
|
||||
// but the whole allocation will be freed using the span's Free method.
|
||||
tile->verticesInTileSpace = int3vertices.AsUnsafeSpan<Int3>().Clone(Allocator.Persistent);
|
||||
tile->triangles = triangles.AsUnsafeSpan<int>().Clone(Allocator.Persistent);
|
||||
tile->tags = tags.AsUnsafeSpan().Reinterpret<uint>().Clone(Allocator.Persistent);
|
||||
|
||||
if (recalculateNormals) {
|
||||
MeshUtility.MakeTrianglesClockwise(ref tile->verticesInTileSpace, ref tile->triangles);
|
||||
}
|
||||
}
|
||||
|
||||
int3vertices.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a22b53fa064d9344988e2a86b73851b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,288 @@
|
||||
using Pathfinding.Jobs;
|
||||
using Pathfinding.Util;
|
||||
using Pathfinding.Graphs.Navmesh.Voxelization.Burst;
|
||||
using Pathfinding.Collections;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>
|
||||
/// Scratch space for building navmesh tiles using voxelization.
|
||||
///
|
||||
/// This uses quite a lot of memory, so it is used by a single worker thread for multiple tiles in order to minimize allocations.
|
||||
/// </summary>
|
||||
public struct TileBuilderBurst : IArenaDisposable {
|
||||
public LinkedVoxelField linkedVoxelField;
|
||||
public CompactVoxelField compactVoxelField;
|
||||
public NativeList<ushort> distanceField;
|
||||
public NativeQueue<Int3> tmpQueue1;
|
||||
public NativeQueue<Int3> tmpQueue2;
|
||||
public NativeList<VoxelContour> contours;
|
||||
public NativeList<int> contourVertices;
|
||||
public VoxelMesh voxelMesh;
|
||||
|
||||
public TileBuilderBurst (int width, int depth, int voxelWalkableHeight, int maximumVoxelYCoord) {
|
||||
linkedVoxelField = new LinkedVoxelField(width, depth, maximumVoxelYCoord);
|
||||
compactVoxelField = new CompactVoxelField(width, depth, voxelWalkableHeight, Allocator.Persistent);
|
||||
tmpQueue1 = new NativeQueue<Int3>(Allocator.Persistent);
|
||||
tmpQueue2 = new NativeQueue<Int3>(Allocator.Persistent);
|
||||
distanceField = new NativeList<ushort>(0, Allocator.Persistent);
|
||||
contours = new NativeList<VoxelContour>(Allocator.Persistent);
|
||||
contourVertices = new NativeList<int>(Allocator.Persistent);
|
||||
voxelMesh = new VoxelMesh {
|
||||
verts = new NativeList<Int3>(Allocator.Persistent),
|
||||
tris = new NativeList<int>(Allocator.Persistent),
|
||||
areas = new NativeList<int>(Allocator.Persistent),
|
||||
};
|
||||
}
|
||||
|
||||
void IArenaDisposable.DisposeWith (DisposeArena arena) {
|
||||
arena.Add(linkedVoxelField);
|
||||
arena.Add(compactVoxelField);
|
||||
arena.Add(distanceField);
|
||||
arena.Add(tmpQueue1);
|
||||
arena.Add(tmpQueue2);
|
||||
arena.Add(contours);
|
||||
arena.Add(contourVertices);
|
||||
arena.Add(voxelMesh);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds tiles from a polygon soup using voxelization.
|
||||
///
|
||||
/// This job takes the following steps:
|
||||
/// - Voxelize the input meshes
|
||||
/// - Filter and process the resulting voxelization in various ways to remove unwanted artifacts and make it better suited for pathfinding.
|
||||
/// - Extract a walkable surface from the voxelization.
|
||||
/// - Triangulate this surface and create navmesh tiles from it.
|
||||
///
|
||||
/// This job uses work stealing to distribute the work between threads. The communication happens using a shared queue and the <see cref="currentTileCounter"/> atomic variable.
|
||||
/// </summary>
|
||||
[BurstCompile(CompileSynchronously = true)]
|
||||
// TODO: [BurstCompile(FloatMode = FloatMode.Fast)]
|
||||
public struct JobBuildTileMeshFromVoxels : IJob {
|
||||
public TileBuilderBurst tileBuilder;
|
||||
[ReadOnly]
|
||||
public TileBuilder.BucketMapping inputMeshes;
|
||||
[ReadOnly]
|
||||
public NativeArray<Bounds> tileGraphSpaceBounds;
|
||||
public Matrix4x4 voxelToTileSpace;
|
||||
|
||||
/// <summary>
|
||||
/// Limits of the graph space bounds for the whole graph on the XZ plane.
|
||||
///
|
||||
/// Used to crop the border tiles to exactly the limits of the graph's bounding box.
|
||||
/// </summary>
|
||||
public Vector2 graphSpaceLimits;
|
||||
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
public unsafe TileMesh.TileMeshUnsafe* outputMeshes;
|
||||
|
||||
/// <summary>Max number of tiles to process in this job</summary>
|
||||
public int maxTiles;
|
||||
|
||||
public int voxelWalkableClimb;
|
||||
public uint voxelWalkableHeight;
|
||||
public float cellSize;
|
||||
public float cellHeight;
|
||||
public float maxSlope;
|
||||
public RecastGraph.DimensionMode dimensionMode;
|
||||
public RecastGraph.BackgroundTraversability backgroundTraversability;
|
||||
public Matrix4x4 graphToWorldSpace;
|
||||
public int characterRadiusInVoxels;
|
||||
public int tileBorderSizeInVoxels;
|
||||
public int minRegionSize;
|
||||
public float maxEdgeLength;
|
||||
public float contourMaxError;
|
||||
[ReadOnly]
|
||||
public NativeArray<JobBuildRegions.RelevantGraphSurfaceInfo> relevantGraphSurfaces;
|
||||
public RecastGraph.RelevantGraphSurfaceMode relevantGraphSurfaceMode;
|
||||
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
public unsafe int* currentTileCounter;
|
||||
|
||||
public void SetOutputMeshes (NativeArray<TileMesh.TileMeshUnsafe> arr) {
|
||||
unsafe {
|
||||
outputMeshes = (TileMesh.TileMeshUnsafe*)arr.GetUnsafeReadOnlyPtr();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCounter (NativeReference<int> counter) {
|
||||
unsafe {
|
||||
// Note: The pointer cast is only necessary when using early versions of the collections package.
|
||||
currentTileCounter = (int*)counter.GetUnsafePtr();
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ProfilerMarker MarkerVoxelize = new ProfilerMarker("Voxelize");
|
||||
private static readonly ProfilerMarker MarkerFilterLedges = new ProfilerMarker("FilterLedges");
|
||||
private static readonly ProfilerMarker MarkerFilterLowHeightSpans = new ProfilerMarker("FilterLowHeightSpans");
|
||||
private static readonly ProfilerMarker MarkerBuildCompactField = new ProfilerMarker("BuildCompactField");
|
||||
private static readonly ProfilerMarker MarkerBuildConnections = new ProfilerMarker("BuildConnections");
|
||||
private static readonly ProfilerMarker MarkerErodeWalkableArea = new ProfilerMarker("ErodeWalkableArea");
|
||||
private static readonly ProfilerMarker MarkerBuildDistanceField = new ProfilerMarker("BuildDistanceField");
|
||||
private static readonly ProfilerMarker MarkerBuildRegions = new ProfilerMarker("BuildRegions");
|
||||
private static readonly ProfilerMarker MarkerBuildContours = new ProfilerMarker("BuildContours");
|
||||
private static readonly ProfilerMarker MarkerBuildMesh = new ProfilerMarker("BuildMesh");
|
||||
private static readonly ProfilerMarker MarkerConvertAreasToTags = new ProfilerMarker("ConvertAreasToTags");
|
||||
private static readonly ProfilerMarker MarkerRemoveDuplicateVertices = new ProfilerMarker("RemoveDuplicateVertices");
|
||||
private static readonly ProfilerMarker MarkerTransformTileCoordinates = new ProfilerMarker("TransformTileCoordinates");
|
||||
|
||||
public void Execute () {
|
||||
for (int k = 0; k < maxTiles; k++) {
|
||||
// Grab the next tile index that we should calculate
|
||||
int i;
|
||||
unsafe {
|
||||
i = System.Threading.Interlocked.Increment(ref UnsafeUtility.AsRef<int>(currentTileCounter)) - 1;
|
||||
}
|
||||
if (i >= tileGraphSpaceBounds.Length) return;
|
||||
|
||||
tileBuilder.linkedVoxelField.ResetLinkedVoxelSpans();
|
||||
if (dimensionMode == RecastGraph.DimensionMode.Dimension2D && backgroundTraversability == RecastGraph.BackgroundTraversability.Walkable) {
|
||||
tileBuilder.linkedVoxelField.SetWalkableBackground();
|
||||
}
|
||||
|
||||
var bucketStart = i > 0 ? inputMeshes.bucketRanges[i-1] : 0;
|
||||
var bucketEnd = inputMeshes.bucketRanges[i];
|
||||
MarkerVoxelize.Begin();
|
||||
new JobVoxelize {
|
||||
inputMeshes = inputMeshes.meshes,
|
||||
bucket = inputMeshes.pointers.GetSubArray(bucketStart, bucketEnd - bucketStart),
|
||||
voxelWalkableClimb = voxelWalkableClimb,
|
||||
voxelWalkableHeight = voxelWalkableHeight,
|
||||
cellSize = cellSize,
|
||||
cellHeight = cellHeight,
|
||||
maxSlope = maxSlope,
|
||||
graphTransform = graphToWorldSpace,
|
||||
graphSpaceBounds = tileGraphSpaceBounds[i],
|
||||
graphSpaceLimits = graphSpaceLimits,
|
||||
voxelArea = tileBuilder.linkedVoxelField,
|
||||
}.Execute();
|
||||
MarkerVoxelize.End();
|
||||
|
||||
|
||||
|
||||
MarkerFilterLedges.Begin();
|
||||
new JobFilterLedges {
|
||||
field = tileBuilder.linkedVoxelField,
|
||||
voxelWalkableClimb = voxelWalkableClimb,
|
||||
voxelWalkableHeight = voxelWalkableHeight,
|
||||
cellSize = cellSize,
|
||||
cellHeight = cellHeight,
|
||||
}.Execute();
|
||||
MarkerFilterLedges.End();
|
||||
|
||||
MarkerFilterLowHeightSpans.Begin();
|
||||
new JobFilterLowHeightSpans {
|
||||
field = tileBuilder.linkedVoxelField,
|
||||
voxelWalkableHeight = voxelWalkableHeight,
|
||||
}.Execute();
|
||||
MarkerFilterLowHeightSpans.End();
|
||||
|
||||
MarkerBuildCompactField.Begin();
|
||||
new JobBuildCompactField {
|
||||
input = tileBuilder.linkedVoxelField,
|
||||
output = tileBuilder.compactVoxelField,
|
||||
}.Execute();
|
||||
MarkerBuildCompactField.End();
|
||||
|
||||
MarkerBuildConnections.Begin();
|
||||
new JobBuildConnections {
|
||||
field = tileBuilder.compactVoxelField,
|
||||
voxelWalkableHeight = (int)voxelWalkableHeight,
|
||||
voxelWalkableClimb = voxelWalkableClimb,
|
||||
}.Execute();
|
||||
MarkerBuildConnections.End();
|
||||
|
||||
MarkerErodeWalkableArea.Begin();
|
||||
new JobErodeWalkableArea {
|
||||
field = tileBuilder.compactVoxelField,
|
||||
radius = characterRadiusInVoxels,
|
||||
}.Execute();
|
||||
MarkerErodeWalkableArea.End();
|
||||
|
||||
MarkerBuildDistanceField.Begin();
|
||||
new JobBuildDistanceField {
|
||||
field = tileBuilder.compactVoxelField,
|
||||
output = tileBuilder.distanceField,
|
||||
}.Execute();
|
||||
MarkerBuildDistanceField.End();
|
||||
|
||||
MarkerBuildRegions.Begin();
|
||||
new JobBuildRegions {
|
||||
field = tileBuilder.compactVoxelField,
|
||||
distanceField = tileBuilder.distanceField,
|
||||
borderSize = tileBorderSizeInVoxels,
|
||||
minRegionSize = Mathf.RoundToInt(minRegionSize),
|
||||
srcQue = tileBuilder.tmpQueue1,
|
||||
dstQue = tileBuilder.tmpQueue2,
|
||||
relevantGraphSurfaces = relevantGraphSurfaces,
|
||||
relevantGraphSurfaceMode = relevantGraphSurfaceMode,
|
||||
cellSize = cellSize,
|
||||
cellHeight = cellHeight,
|
||||
graphTransform = graphToWorldSpace,
|
||||
graphSpaceBounds = tileGraphSpaceBounds[i],
|
||||
}.Execute();
|
||||
MarkerBuildRegions.End();
|
||||
|
||||
MarkerBuildContours.Begin();
|
||||
new JobBuildContours {
|
||||
field = tileBuilder.compactVoxelField,
|
||||
maxError = contourMaxError,
|
||||
maxEdgeLength = maxEdgeLength,
|
||||
buildFlags = VoxelUtilityBurst.RC_CONTOUR_TESS_WALL_EDGES | VoxelUtilityBurst.RC_CONTOUR_TESS_TILE_EDGES,
|
||||
cellSize = cellSize,
|
||||
outputContours = tileBuilder.contours,
|
||||
outputVerts = tileBuilder.contourVertices,
|
||||
}.Execute();
|
||||
MarkerBuildContours.End();
|
||||
|
||||
MarkerBuildMesh.Begin();
|
||||
new JobBuildMesh {
|
||||
contours = tileBuilder.contours,
|
||||
contourVertices = tileBuilder.contourVertices,
|
||||
mesh = tileBuilder.voxelMesh,
|
||||
field = tileBuilder.compactVoxelField,
|
||||
}.Execute();
|
||||
MarkerBuildMesh.End();
|
||||
|
||||
unsafe {
|
||||
TileMesh.TileMeshUnsafe* outputTileMesh = outputMeshes + i;
|
||||
|
||||
MarkerConvertAreasToTags.Begin();
|
||||
new JobConvertAreasToTags {
|
||||
areas = tileBuilder.voxelMesh.areas,
|
||||
}.Execute();
|
||||
MarkerConvertAreasToTags.End();
|
||||
|
||||
MarkerRemoveDuplicateVertices.Begin();
|
||||
new MeshUtility.JobRemoveDuplicateVertices {
|
||||
vertices = tileBuilder.voxelMesh.verts,
|
||||
triangles = tileBuilder.voxelMesh.tris,
|
||||
tags = tileBuilder.voxelMesh.areas,
|
||||
}.Execute();
|
||||
MarkerRemoveDuplicateVertices.End();
|
||||
|
||||
MarkerTransformTileCoordinates.Begin();
|
||||
new JobTransformTileCoordinates {
|
||||
vertices = tileBuilder.voxelMesh.verts.AsUnsafeSpan(),
|
||||
matrix = voxelToTileSpace,
|
||||
}.Execute();
|
||||
MarkerTransformTileCoordinates.End();
|
||||
|
||||
*outputTileMesh = new TileMesh.TileMeshUnsafe {
|
||||
// Convert the buffers to spans that own their memory.
|
||||
verticesInTileSpace = tileBuilder.voxelMesh.verts.AsUnsafeSpan().Clone(Allocator.Persistent),
|
||||
triangles = tileBuilder.voxelMesh.tris.AsUnsafeSpan().Clone(Allocator.Persistent),
|
||||
tags = tileBuilder.voxelMesh.areas.AsUnsafeSpan().Reinterpret<uint>().Clone(Allocator.Persistent),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20aeb827260a74a4492e7687fdebb14f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>
|
||||
/// Calculates node connections between triangles within each tile.
|
||||
/// Connections between tiles are handled at a later stage in <see cref="JobConnectTiles"/>.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
public struct JobCalculateTriangleConnections : IJob {
|
||||
[ReadOnly]
|
||||
public NativeArray<TileMesh.TileMeshUnsafe> tileMeshes;
|
||||
[WriteOnly]
|
||||
public NativeArray<TileNodeConnectionsUnsafe> nodeConnections;
|
||||
|
||||
public struct TileNodeConnectionsUnsafe {
|
||||
/// <summary>Stream of packed connection edge infos (from <see cref="Connection.PackShapeEdgeInfo"/>)</summary>
|
||||
public Unity.Collections.LowLevel.Unsafe.UnsafeAppendBuffer neighbours;
|
||||
/// <summary>Number of neighbours for each triangle</summary>
|
||||
public Unity.Collections.LowLevel.Unsafe.UnsafeAppendBuffer neighbourCounts;
|
||||
}
|
||||
|
||||
public void Execute () {
|
||||
Assert.AreEqual(tileMeshes.Length, nodeConnections.Length);
|
||||
|
||||
var nodeRefs = new NativeParallelHashMap<int2, uint>(128, Allocator.Temp);
|
||||
bool duplicates = false;
|
||||
for (int ti = 0; ti < tileMeshes.Length; ti++) {
|
||||
nodeRefs.Clear();
|
||||
var tile = tileMeshes[ti];
|
||||
var numIndices = tile.triangles.Length;
|
||||
var neighbours = new Unity.Collections.LowLevel.Unsafe.UnsafeAppendBuffer(numIndices * 2 * 4, 4, Allocator.Persistent);
|
||||
var neighbourCounts = new Unity.Collections.LowLevel.Unsafe.UnsafeAppendBuffer(numIndices * 4, 4, Allocator.Persistent);
|
||||
const int TriangleIndexBits = 28;
|
||||
unsafe {
|
||||
Assert.IsTrue(numIndices % 3 == 0);
|
||||
// Access data via the raw pointer to avoid bounds checks
|
||||
var triangles = tile.triangles.ptr;
|
||||
for (int i = 0, j = 0; i < numIndices; i += 3, j++) {
|
||||
duplicates |= !nodeRefs.TryAdd(new int2(triangles[i+0], triangles[i+1]), (uint)j | (0 << TriangleIndexBits));
|
||||
duplicates |= !nodeRefs.TryAdd(new int2(triangles[i+1], triangles[i+2]), (uint)j | (1 << TriangleIndexBits));
|
||||
duplicates |= !nodeRefs.TryAdd(new int2(triangles[i+2], triangles[i+0]), (uint)j | (2 << TriangleIndexBits));
|
||||
}
|
||||
|
||||
for (int i = 0; i < numIndices; i += 3) {
|
||||
var cnt = 0;
|
||||
for (int edge = 0; edge < 3; edge++) {
|
||||
if (nodeRefs.TryGetValue(new int2(triangles[i+((edge+1) % 3)], triangles[i+edge]), out var match)) {
|
||||
var other = match & ((1 << TriangleIndexBits) - 1);
|
||||
var otherEdge = (int)(match >> TriangleIndexBits);
|
||||
neighbours.Add(other);
|
||||
var edgeInfo = Connection.PackShapeEdgeInfo((byte)edge, (byte)otherEdge, true, true, true);
|
||||
neighbours.Add((int)edgeInfo);
|
||||
cnt += 1;
|
||||
}
|
||||
}
|
||||
neighbourCounts.Add(cnt);
|
||||
}
|
||||
}
|
||||
nodeConnections[ti] = new TileNodeConnectionsUnsafe {
|
||||
neighbours = neighbours,
|
||||
neighbourCounts = neighbourCounts,
|
||||
};
|
||||
}
|
||||
|
||||
if (duplicates) {
|
||||
UnityEngine.Debug.LogWarning("Duplicate triangle edges were found in the input mesh. These have been removed. Are you sure your mesh is suitable for being used as a navmesh directly?\nThis could be caused by the mesh's normals not being consistent.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30417132dbc15504abbdf1b70224c006
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,159 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Jobs.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>
|
||||
/// Connects adjacent tiles together.
|
||||
///
|
||||
/// This only creates connections between tiles. Connections internal to a tile should be handled by <see cref="JobCalculateTriangleConnections"/>.
|
||||
///
|
||||
/// Use the <see cref="ScheduleBatch"/> method to connect a bunch of tiles efficiently using maximum parallelism.
|
||||
/// </summary>
|
||||
public struct JobConnectTiles : IJob {
|
||||
/// <summary>GCHandle referring to a NavmeshTile[] array of size tileRect.Width*tileRect.Height</summary>
|
||||
public System.Runtime.InteropServices.GCHandle tiles;
|
||||
public int coordinateSum;
|
||||
public int direction;
|
||||
public int zOffset;
|
||||
public int zStride;
|
||||
Vector2 tileWorldSize;
|
||||
IntRect tileRect;
|
||||
/// <summary>Maximum vertical distance between two tiles to create a connection between them</summary>
|
||||
public float maxTileConnectionEdgeDistance;
|
||||
|
||||
static readonly Unity.Profiling.ProfilerMarker ConnectTilesMarker = new Unity.Profiling.ProfilerMarker("ConnectTiles");
|
||||
|
||||
/// <summary>
|
||||
/// Schedule jobs to connect all the given tiles with each other while exploiting as much parallelism as possible.
|
||||
/// tilesHandle should be a GCHandle referring to a NavmeshTile[] array of size tileRect.Width*tileRect.Height.
|
||||
/// </summary>
|
||||
public static JobHandle ScheduleBatch (System.Runtime.InteropServices.GCHandle tilesHandle, JobHandle dependency, IntRect tileRect, Vector2 tileWorldSize, float maxTileConnectionEdgeDistance) {
|
||||
// First connect all tiles with an EVEN coordinate sum
|
||||
// This would be the white squares on a chess board.
|
||||
// Then connect all tiles with an ODD coordinate sum (which would be all black squares on a chess board).
|
||||
// This will prevent the different threads that do all
|
||||
// this in parallel from conflicting with each other.
|
||||
// The directions are also done separately
|
||||
// first they are connected along the X direction and then along the Z direction.
|
||||
// Looping over 0 and then 1
|
||||
|
||||
int workers = Mathf.Max(1, JobsUtility.JobWorkerCount);
|
||||
var handles = new NativeArray<JobHandle>(workers, Allocator.Temp);
|
||||
for (int coordinateSum = 0; coordinateSum <= 1; coordinateSum++) {
|
||||
for (int direction = 0; direction <= 1; direction++) {
|
||||
for (int i = 0; i < workers; i++) {
|
||||
handles[i] = new JobConnectTiles {
|
||||
tiles = tilesHandle,
|
||||
tileRect = tileRect,
|
||||
tileWorldSize = tileWorldSize,
|
||||
coordinateSum = coordinateSum,
|
||||
direction = direction,
|
||||
maxTileConnectionEdgeDistance = maxTileConnectionEdgeDistance,
|
||||
zOffset = i,
|
||||
zStride = workers,
|
||||
}.Schedule(dependency);
|
||||
}
|
||||
dependency = JobHandle.CombineDependencies(handles);
|
||||
}
|
||||
}
|
||||
|
||||
return dependency;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule jobs to connect all the given tiles inside innerRect with tiles that are outside it, while exploiting as much parallelism as possible.
|
||||
/// tilesHandle should be a GCHandle referring to a NavmeshTile[] array of size tileRect.Width*tileRect.Height.
|
||||
/// </summary>
|
||||
public static JobHandle ScheduleRecalculateBorders (System.Runtime.InteropServices.GCHandle tilesHandle, JobHandle dependency, IntRect tileRect, IntRect innerRect, Vector2 tileWorldSize, float maxTileConnectionEdgeDistance) {
|
||||
var w = innerRect.Width;
|
||||
var h = innerRect.Height;
|
||||
|
||||
// Note: conservative estimate of number of handles. There may be fewer in reality.
|
||||
var allDependencies = new NativeArray<JobHandle>(2*w + 2*math.max(0, h - 2), Allocator.Temp);
|
||||
int count = 0;
|
||||
for (int z = 0; z < h; z++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
// Check if the tile is on the border of the inner rect
|
||||
if (!(x == 0 || z == 0 || x == w - 1 || z == h - 1)) continue;
|
||||
|
||||
var tileX = innerRect.xmin + x;
|
||||
var tileZ = innerRect.ymin + z;
|
||||
|
||||
// For a corner tile, the jobs need to run sequentially
|
||||
var dep = dependency;
|
||||
for (int direction = 0; direction < 4; direction++) {
|
||||
var nx = tileX + (direction == 0 ? 1 : direction == 1 ? -1 : 0);
|
||||
var nz = tileZ + (direction == 2 ? 1 : direction == 3 ? -1 : 0);
|
||||
if (innerRect.Contains(nx, nz) || !tileRect.Contains(nx, nz)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dep = new JobConnectTilesSingle {
|
||||
tiles = tilesHandle,
|
||||
tileIndex1 = tileX + tileZ * tileRect.Width,
|
||||
tileIndex2 = nx + nz * tileRect.Width,
|
||||
tileWorldSize = tileWorldSize,
|
||||
maxTileConnectionEdgeDistance = maxTileConnectionEdgeDistance,
|
||||
}.Schedule(dep);
|
||||
}
|
||||
|
||||
allDependencies[count++] = dep;
|
||||
}
|
||||
}
|
||||
return JobHandle.CombineDependencies(allDependencies);
|
||||
}
|
||||
|
||||
public void Execute () {
|
||||
var tiles = (NavmeshTile[])this.tiles.Target;
|
||||
|
||||
var tileRectDepth = tileRect.Height;
|
||||
var tileRectWidth = tileRect.Width;
|
||||
for (int z = zOffset; z < tileRectDepth; z += zStride) {
|
||||
for (int x = 0; x < tileRectWidth; x++) {
|
||||
if ((x + z) % 2 == coordinateSum) {
|
||||
int tileIndex1 = x + z * tileRectWidth;
|
||||
int tileIndex2;
|
||||
if (direction == 0 && x < tileRectWidth - 1) {
|
||||
tileIndex2 = x + 1 + z * tileRectWidth;
|
||||
} else if (direction == 1 && z < tileRectDepth - 1) {
|
||||
tileIndex2 = x + (z + 1) * tileRectWidth;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
ConnectTilesMarker.Begin();
|
||||
NavmeshBase.ConnectTiles(tiles[tileIndex1], tiles[tileIndex2], tileWorldSize.x, tileWorldSize.y, maxTileConnectionEdgeDistance);
|
||||
ConnectTilesMarker.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects two adjacent tiles together.
|
||||
///
|
||||
/// This only creates connections between tiles. Connections internal to a tile should be handled by <see cref="JobCalculateTriangleConnections"/>.
|
||||
/// </summary>
|
||||
struct JobConnectTilesSingle : IJob {
|
||||
/// <summary>GCHandle referring to a NavmeshTile[] array of size tileRect.Width*tileRect.Height</summary>
|
||||
public System.Runtime.InteropServices.GCHandle tiles;
|
||||
/// <summary>Index of the first tile in the <see cref="tiles"/> array</summary>
|
||||
public int tileIndex1;
|
||||
/// <summary>Index of the second tile in the <see cref="tiles"/> array</summary>
|
||||
public int tileIndex2;
|
||||
/// <summary>Size of a tile in world units</summary>
|
||||
public Vector2 tileWorldSize;
|
||||
/// <summary>Maximum vertical distance between two tiles to create a connection between them</summary>
|
||||
public float maxTileConnectionEdgeDistance;
|
||||
|
||||
public void Execute () {
|
||||
var tiles = (NavmeshTile[])this.tiles.Target;
|
||||
|
||||
NavmeshBase.ConnectTiles(tiles[tileIndex1], tiles[tileIndex2], tileWorldSize.x, tileWorldSize.y, maxTileConnectionEdgeDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd00a18824d04764783722c547fb60f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using Pathfinding.Graphs.Navmesh.Voxelization.Burst;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>Convert recast region IDs to the tags that should be applied to the nodes</summary>
|
||||
[BurstCompile]
|
||||
public struct JobConvertAreasToTags : IJob {
|
||||
public NativeList<int> areas;
|
||||
|
||||
public void Execute () {
|
||||
unsafe {
|
||||
for (int i = 0; i < areas.Length; i++) {
|
||||
var area = areas[i];
|
||||
// The user supplied IDs start at 1 because 0 is reserved for NotWalkable
|
||||
areas[i] = (area & VoxelUtilityBurst.TagReg) != 0 ? (area & VoxelUtilityBurst.TagRegMask) - 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 229fdb01207c1ab4796deea78744e136
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,140 @@
|
||||
using Pathfinding.Collections;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>
|
||||
/// Builds tiles optimized for pathfinding, from a list of <see cref="TileMesh.TileMeshUnsafe"/>.
|
||||
///
|
||||
/// This job takes the following steps:
|
||||
/// - Transform all vertices using the <see cref="graphToWorldSpace"/> matrix.
|
||||
/// - Remove duplicate vertices
|
||||
/// - If <see cref="recalculateNormals"/> is enabled: ensure all triangles are laid out in the clockwise direction.
|
||||
/// </summary>
|
||||
public struct JobCreateTiles : IJob {
|
||||
/// <summary>An array of <see cref="TileMesh.TileMeshUnsafe"/> of length tileRect.Width*tileRect.Height, or an uninitialized array</summary>
|
||||
[ReadOnly]
|
||||
[NativeDisableContainerSafetyRestriction]
|
||||
public NativeArray<TileMesh.TileMeshUnsafe> preCutTileMeshes;
|
||||
|
||||
/// <summary>An array of <see cref="TileMesh.TileMeshUnsafe"/> of length tileRect.Width*tileRect.Height</summary>
|
||||
[ReadOnly]
|
||||
public NativeArray<TileMesh.TileMeshUnsafe> tileMeshes;
|
||||
|
||||
/// <summary>
|
||||
/// An array of <see cref="NavmeshTile"/> of length tileRect.Width*tileRect.Height.
|
||||
/// This array will be filled with the created tiles.
|
||||
/// </summary>
|
||||
public System.Runtime.InteropServices.GCHandle tiles;
|
||||
|
||||
/// <summary>Graph index of the graph that these nodes will be added to</summary>
|
||||
public uint graphIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Number of tiles in the graph.
|
||||
///
|
||||
/// This may be much bigger than the <see cref="tileRect"/> that we are actually processing.
|
||||
/// For example if a graph update is performed, the <see cref="tileRect"/> will just cover the tiles that are recalculated,
|
||||
/// while <see cref="graphTileCount"/> will contain all tiles in the graph.
|
||||
/// </summary>
|
||||
public Vector2Int graphTileCount;
|
||||
|
||||
/// <summary>
|
||||
/// Rectangle of tiles that we are processing.
|
||||
///
|
||||
/// (xmax, ymax) must be smaller than graphTileCount.
|
||||
/// If for examples <see cref="graphTileCount"/> is (10, 10) and <see cref="tileRect"/> is {2, 3, 5, 6} then we are processing tiles (2, 3) to (5, 6) inclusive.
|
||||
/// </summary>
|
||||
public IntRect tileRect;
|
||||
|
||||
/// <summary>Initial penalty for all nodes in the tile</summary>
|
||||
public uint initialPenalty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, all triangles will be guaranteed to be laid out in clockwise order.
|
||||
/// If false, their original order will be preserved.
|
||||
/// </summary>
|
||||
public bool recalculateNormals;
|
||||
|
||||
/// <summary>Size of a tile in world units along the graph's X and Z axes</summary>
|
||||
public Vector2 tileWorldSize;
|
||||
|
||||
/// <summary>Matrix to convert from graph space to world space</summary>
|
||||
public Matrix4x4 graphToWorldSpace;
|
||||
|
||||
public void Execute () {
|
||||
var tiles = (NavmeshTile[])this.tiles.Target;
|
||||
Assert.AreEqual(tileMeshes.Length, tiles.Length);
|
||||
Assert.AreEqual(tileMeshes.Length, tileRect.Area);
|
||||
Assert.IsTrue(tileRect.xmax < graphTileCount.x);
|
||||
Assert.IsTrue(tileRect.ymax < graphTileCount.y);
|
||||
|
||||
var tileRectWidth = tileRect.Width;
|
||||
var tileRectDepth = tileRect.Height;
|
||||
|
||||
bool isUsingCuts = preCutTileMeshes.IsCreated;
|
||||
if (isUsingCuts) {
|
||||
Assert.AreEqual(preCutTileMeshes.Length, tiles.Length);
|
||||
Assert.AreEqual(preCutTileMeshes.Length, tileRect.Area);
|
||||
}
|
||||
|
||||
for (int z = 0; z < tileRectDepth; z++) {
|
||||
for (int x = 0; x < tileRectWidth; x++) {
|
||||
var tileIndex = z*tileRectWidth + x;
|
||||
// If we are just updating a part of the graph we still want to assign the nodes the proper global tile index
|
||||
var graphTileIndex = (z + tileRect.ymin)*graphTileCount.x + (x + tileRect.xmin);
|
||||
var mesh = tileMeshes[tileIndex];
|
||||
|
||||
// Convert tile space to graph space and world space
|
||||
var verticesInGraphSpace = mesh.verticesInTileSpace.Clone(Allocator.Persistent);
|
||||
var verticesInWorldSpace = verticesInGraphSpace.Clone(Allocator.Persistent);
|
||||
var tileSpaceToGraphSpaceOffset = (Int3) new Vector3(tileWorldSize.x * (x + tileRect.xmin), 0, tileWorldSize.y * (z + tileRect.ymin));
|
||||
for (int i = 0; i < verticesInGraphSpace.Length; i++) {
|
||||
var v = verticesInGraphSpace[i] + tileSpaceToGraphSpaceOffset;
|
||||
verticesInGraphSpace[i] = v;
|
||||
verticesInWorldSpace[i] = (Int3)graphToWorldSpace.MultiplyPoint3x4((Vector3)v);
|
||||
}
|
||||
|
||||
// Create a new navmesh tile and assign its settings
|
||||
var triangles = mesh.triangles.Clone(Allocator.Persistent);
|
||||
var tile = new NavmeshTile {
|
||||
x = x + tileRect.xmin,
|
||||
z = z + tileRect.ymin,
|
||||
w = 1,
|
||||
d = 1,
|
||||
tris = triangles,
|
||||
vertsInGraphSpace = verticesInGraphSpace,
|
||||
verts = verticesInWorldSpace,
|
||||
bbTree = new BBTree(triangles, verticesInGraphSpace),
|
||||
nodes = new TriangleMeshNode[triangles.Length/3],
|
||||
// Leave empty for now, it will be filled in later
|
||||
graph = null,
|
||||
isCut = false,
|
||||
};
|
||||
|
||||
if (isUsingCuts) {
|
||||
// Copy pre-cut data to be able to reference it when re-cutting the tile.
|
||||
// If no cuts are used, we don't save the pre-cut data, to reduce memory usage,
|
||||
// as it is identical to the post-cut data.
|
||||
// These arrays can be re-created from the other tile data, if needed.
|
||||
var preCutMesh = preCutTileMeshes[tileIndex];
|
||||
tile.preCutVertsInTileSpace = preCutMesh.verticesInTileSpace.Clone(Allocator.Persistent);
|
||||
tile.preCutTris = preCutMesh.triangles.Clone(Allocator.Persistent);
|
||||
tile.preCutTags = preCutMesh.tags.Clone(Allocator.Persistent);
|
||||
tile.isCut = true;
|
||||
}
|
||||
|
||||
Profiler.BeginSample("CreateNodes");
|
||||
NavmeshBase.CreateNodes(tile, tile.tris, graphTileIndex, graphIndex, mesh.tags, false, null, initialPenalty, false);
|
||||
Profiler.EndSample();
|
||||
|
||||
tiles[tileIndex] = tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b86cf43938afd654a8f1b711e55977d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using Pathfinding.Util;
|
||||
using Pathfinding.Collections;
|
||||
using Unity.Burst;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>
|
||||
/// Transforms vertices from voxel coordinates to tile coordinates.
|
||||
///
|
||||
/// This essentially constitutes multiplying the vertices by the <see cref="matrix"/>.
|
||||
///
|
||||
/// Note: The input space is in raw voxel coordinates, the output space is in tile coordinates stored in millimeters (as is typical for the Int3 struct. See <see cref="Int3.Precision"/>).
|
||||
/// </summary>
|
||||
[BurstCompile(FloatMode = FloatMode.Fast)]
|
||||
public struct JobTransformTileCoordinates : IJob {
|
||||
public unsafe UnsafeSpan<Int3> vertices;
|
||||
public Matrix4x4 matrix;
|
||||
|
||||
public void Execute () {
|
||||
unsafe {
|
||||
for (uint i = 0; i < vertices.length; i++) {
|
||||
// Transform from voxel indices to a proper Int3 coordinate, then convert it to a Vector3 float coordinate
|
||||
var p = vertices[i];
|
||||
vertices[i] = (Int3)matrix.MultiplyPoint3x4(new Vector3(p.x, p.y, p.z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff97d8db3ca9a074dbfbd83fa5ad16be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using Pathfinding.Pooling;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace Pathfinding.Graphs.Navmesh.Jobs {
|
||||
/// <summary>
|
||||
/// Writes connections to each node in each tile.
|
||||
///
|
||||
/// It also calculates the connection costs between nodes.
|
||||
///
|
||||
/// This job is run after all tiles have been built and the connections have been calculated.
|
||||
///
|
||||
/// See: <see cref="JobCalculateTriangleConnections"/>
|
||||
/// </summary>
|
||||
public struct JobWriteNodeConnections : IJob {
|
||||
/// <summary>Connections for each tile</summary>
|
||||
[ReadOnly]
|
||||
public NativeArray<JobCalculateTriangleConnections.TileNodeConnectionsUnsafe> nodeConnections;
|
||||
/// <summary>Array of <see cref="NavmeshTile"/></summary>
|
||||
public System.Runtime.InteropServices.GCHandle tiles;
|
||||
|
||||
public void Execute () {
|
||||
var tiles = (NavmeshTile[])this.tiles.Target;
|
||||
Assert.AreEqual(nodeConnections.Length, tiles.Length);
|
||||
|
||||
for (int i = 0; i < tiles.Length; i++) {
|
||||
Profiler.BeginSample("CreateConnections");
|
||||
var connections = nodeConnections[i];
|
||||
Apply(tiles[i].nodes, connections);
|
||||
connections.neighbourCounts.Dispose();
|
||||
connections.neighbours.Dispose();
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
|
||||
void Apply (TriangleMeshNode[] nodes, JobCalculateTriangleConnections.TileNodeConnectionsUnsafe connections) {
|
||||
var neighbourCountsReader = connections.neighbourCounts.AsReader();
|
||||
var neighboursReader = connections.neighbours.AsReader();
|
||||
|
||||
for (int i = 0; i < nodes.Length; i++) {
|
||||
var node = nodes[i];
|
||||
var neighbourCount = neighbourCountsReader.ReadNext<int>();
|
||||
var conns = node.connections = ArrayPool<Connection>.ClaimWithExactLength(neighbourCount);
|
||||
for (int j = 0; j < neighbourCount; j++) {
|
||||
var otherIndex = neighboursReader.ReadNext<int>();
|
||||
var shapeEdgeInfo = (byte)neighboursReader.ReadNext<int>();
|
||||
var other = nodes[otherIndex];
|
||||
var cost = (node.position - other.position).costMagnitude;
|
||||
conns[j] = new Connection(
|
||||
other,
|
||||
(uint)cost,
|
||||
shapeEdgeInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eea3ec9fc5dd8604c9902e09277d86d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user