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 { /// /// 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. /// public struct TileBuilderBurst : IArenaDisposable { public LinkedVoxelField linkedVoxelField; public CompactVoxelField compactVoxelField; public NativeList distanceField; public NativeQueue tmpQueue1; public NativeQueue tmpQueue2; public NativeList contours; public NativeList 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(Allocator.Persistent); tmpQueue2 = new NativeQueue(Allocator.Persistent); distanceField = new NativeList(0, Allocator.Persistent); contours = new NativeList(Allocator.Persistent); contourVertices = new NativeList(Allocator.Persistent); voxelMesh = new VoxelMesh { verts = new NativeList(Allocator.Persistent), tris = new NativeList(Allocator.Persistent), areas = new NativeList(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); } } /// /// 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 atomic variable. /// [BurstCompile(CompileSynchronously = true)] // TODO: [BurstCompile(FloatMode = FloatMode.Fast)] public struct JobBuildTileMeshFromVoxels : IJob { public TileBuilderBurst tileBuilder; [ReadOnly] public TileBuilder.BucketMapping inputMeshes; [ReadOnly] public NativeArray tileGraphSpaceBounds; public Matrix4x4 voxelToTileSpace; /// /// 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. /// public Vector2 graphSpaceLimits; [NativeDisableUnsafePtrRestriction] public unsafe TileMesh.TileMeshUnsafe* outputMeshes; /// Max number of tiles to process in this job 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 relevantGraphSurfaces; public RecastGraph.RelevantGraphSurfaceMode relevantGraphSurfaceMode; [NativeDisableUnsafePtrRestriction] public unsafe int* currentTileCounter; public void SetOutputMeshes (NativeArray arr) { unsafe { outputMeshes = (TileMesh.TileMeshUnsafe*)arr.GetUnsafeReadOnlyPtr(); } } public void SetCounter (NativeReference 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(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().Clone(Allocator.Persistent), }; } } } } }