summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRegion.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRegion.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRegion.cs813
1 files changed, 813 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRegion.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRegion.cs
new file mode 100644
index 0000000..0e40a38
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRegion.cs
@@ -0,0 +1,813 @@
+using UnityEngine;
+using Unity.Collections;
+using Unity.Mathematics;
+using Unity.Jobs;
+using Unity.Burst;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Navmesh.Voxelization.Burst {
+ [BurstCompile(CompileSynchronously = true)]
+ public struct JobBuildRegions : IJob {
+ public CompactVoxelField field;
+ public NativeList<ushort> distanceField;
+ public int borderSize;
+ public int minRegionSize;
+ public NativeQueue<Int3> srcQue;
+ public NativeQueue<Int3> dstQue;
+ public RecastGraph.RelevantGraphSurfaceMode relevantGraphSurfaceMode;
+ public NativeArray<RelevantGraphSurfaceInfo> relevantGraphSurfaces;
+
+ public float cellSize, cellHeight;
+ public Matrix4x4 graphTransform;
+ public Bounds graphSpaceBounds;
+
+ void MarkRectWithRegion (int minx, int maxx, int minz, int maxz, ushort region, NativeArray<ushort> srcReg) {
+ int md = maxz * field.width;
+
+ for (int z = minz*field.width; z < md; z += field.width) {
+ for (int x = minx; x < maxx; x++) {
+ CompactVoxelCell c = field.cells[z+x];
+
+ for (int i = c.index, ni = c.index+c.count; i < ni; i++) {
+ if (field.areaTypes[i] != CompactVoxelField.UnwalkableArea) {
+ srcReg[i] = region;
+ }
+ }
+ }
+ }
+ }
+
+ public static bool FloodRegion (int x, int z, int i, uint level, ushort r,
+ CompactVoxelField field,
+ NativeArray<ushort> distanceField,
+ NativeArray<ushort> srcReg,
+ NativeArray<ushort> srcDist,
+ NativeArray<Int3> stack,
+ NativeArray<int> flags,
+ NativeArray<bool> closed) {
+ int area = field.areaTypes[i];
+
+ // Flood f mark region.
+ int stackSize = 1;
+
+ stack[0] = new Int3 {
+ x = x,
+ y = i,
+ z = z,
+ };
+
+ srcReg[i] = r;
+ srcDist[i] = 0;
+
+ int lev = (int)(level >= 2 ? level-2 : 0);
+
+ int count = 0;
+
+ // Store these in local variables (for performance, avoids an extra indirection)
+ var compactCells = field.cells;
+ var compactSpans = field.spans;
+ var areaTypes = field.areaTypes;
+
+ while (stackSize > 0) {
+ stackSize--;
+ var c = stack[stackSize];
+ //Similar to the Pop operation of an array, but Pop is not implemented in List<>
+ int ci = c.y;
+ int cx = c.x;
+ int cz = c.z;
+
+ CompactVoxelSpan cs = compactSpans[ci];
+
+ //Debug.DrawRay (ConvertPosition(cx,cz,ci),Vector3.up, Color.cyan);
+
+ // Check if any of the neighbours already have a valid region set.
+ ushort ar = 0;
+
+ // Loop through four neighbours
+ // then check one neighbour of the neighbour
+ // to get the diagonal neighbour
+ for (int dir = 0; dir < 4; dir++) {
+ // 8 connected
+ if (cs.GetConnection(dir) != CompactVoxelField.NotConnected) {
+ int ax = cx + VoxelUtilityBurst.DX[dir];
+ int az = cz + VoxelUtilityBurst.DZ[dir]*field.width;
+
+ int ai = (int)compactCells[ax+az].index + cs.GetConnection(dir);
+
+ if (areaTypes[ai] != area)
+ continue;
+
+ ushort nr = srcReg[ai];
+
+ if ((nr & VoxelUtilityBurst.BorderReg) == VoxelUtilityBurst.BorderReg) // Do not take borders into account.
+ continue;
+
+ if (nr != 0 && nr != r) {
+ ar = nr;
+ // Found a valid region, skip checking the rest
+ break;
+ }
+
+ // Rotate dir 90 degrees
+ int dir2 = (dir+1) & 0x3;
+ var neighbour2 = compactSpans[ai].GetConnection(dir2);
+ // Check the diagonal connection
+ if (neighbour2 != CompactVoxelField.NotConnected) {
+ int ax2 = ax + VoxelUtilityBurst.DX[dir2];
+ int az2 = az + VoxelUtilityBurst.DZ[dir2]*field.width;
+
+ int ai2 = compactCells[ax2+az2].index + neighbour2;
+
+ if (areaTypes[ai2] != area)
+ continue;
+
+ ushort nr2 = srcReg[ai2];
+
+ if ((nr2 & VoxelUtilityBurst.BorderReg) == VoxelUtilityBurst.BorderReg) // Do not take borders into account.
+ continue;
+
+ if (nr2 != 0 && nr2 != r) {
+ ar = nr2;
+ // Found a valid region, skip checking the rest
+ break;
+ }
+ }
+ }
+ }
+
+ if (ar != 0) {
+ srcReg[ci] = 0;
+ srcDist[ci] = 0xFFFF;
+ continue;
+ }
+ count++;
+ closed[ci] = true;
+
+
+ // Expand neighbours.
+ for (int dir = 0; dir < 4; ++dir) {
+ if (cs.GetConnection(dir) == CompactVoxelField.NotConnected) continue;
+ int ax = cx + VoxelUtilityBurst.DX[dir];
+ int az = cz + VoxelUtilityBurst.DZ[dir]*field.width;
+ int ai = compactCells[ax+az].index + cs.GetConnection(dir);
+
+ if (areaTypes[ai] != area) continue;
+ if (srcReg[ai] != 0) continue;
+
+ if (distanceField[ai] >= lev && flags[ai] == 0) {
+ srcReg[ai] = r;
+ srcDist[ai] = 0;
+
+ stack[stackSize] = new Int3 {
+ x = ax,
+ y = ai,
+ z = az,
+ };
+ stackSize++;
+ } else {
+ flags[ai] = r;
+ srcDist[ai] = 2;
+ }
+ }
+ }
+
+
+ return count > 0;
+ }
+
+ public void Execute () {
+ srcQue.Clear();
+ dstQue.Clear();
+
+ /*System.Diagnostics.Stopwatch w0 = new System.Diagnostics.Stopwatch();
+ System.Diagnostics.Stopwatch w1 = new System.Diagnostics.Stopwatch();
+ System.Diagnostics.Stopwatch w2 = new System.Diagnostics.Stopwatch();
+ System.Diagnostics.Stopwatch w3 = new System.Diagnostics.Stopwatch();
+ System.Diagnostics.Stopwatch w4 = new System.Diagnostics.Stopwatch();
+ System.Diagnostics.Stopwatch w5 = new System.Diagnostics.Stopwatch();
+ w3.Start();*/
+
+ int w = field.width;
+ int d = field.depth;
+ int wd = w*d;
+ int spanCount = field.spans.Length;
+
+ int expandIterations = 8;
+
+ var srcReg = new NativeArray<ushort>(spanCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ var srcDist = new NativeArray<ushort>(spanCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ var closed = new NativeArray<bool>(spanCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ var spanFlags = new NativeArray<int>(spanCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ var stack = new NativeArray<Int3>(spanCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+
+ // The array pool arrays may contain arbitrary data. We need to zero it out.
+ for (int i = 0; i < spanCount; i++) {
+ srcReg[i] = 0;
+ srcDist[i] = 0xFFFF;
+ closed[i] = false;
+ spanFlags[i] = 0;
+ }
+
+ var spanDistances = distanceField;
+ var areaTypes = field.areaTypes;
+ var compactCells = field.cells;
+ const ushort BorderReg = VoxelUtilityBurst.BorderReg;
+
+ ushort regionId = 2;
+ MarkRectWithRegion(0, borderSize, 0, d, (ushort)(regionId | BorderReg), srcReg); regionId++;
+ MarkRectWithRegion(w-borderSize, w, 0, d, (ushort)(regionId | BorderReg), srcReg); regionId++;
+ MarkRectWithRegion(0, w, 0, borderSize, (ushort)(regionId | BorderReg), srcReg); regionId++;
+ MarkRectWithRegion(0, w, d-borderSize, d, (ushort)(regionId | BorderReg), srcReg); regionId++;
+
+ // TODO: Can be optimized
+ int maxDistance = 0;
+ for (int i = 0; i < distanceField.Length; i++) {
+ maxDistance = math.max(distanceField[i], maxDistance);
+ }
+
+ // A distance is 2 to an adjacent span and 1 for a diagonally adjacent one.
+ NativeArray<int> sortedSpanCounts = new NativeArray<int>((maxDistance)/2 + 1, Allocator.Temp);
+ for (int i = 0; i < field.spans.Length; i++) {
+ // Do not take borders or unwalkable spans into account.
+ if ((srcReg[i] & BorderReg) == BorderReg || areaTypes[i] == CompactVoxelField.UnwalkableArea)
+ continue;
+
+ sortedSpanCounts[distanceField[i]/2]++;
+ }
+
+ var distanceIndexOffsets = new NativeArray<int>(sortedSpanCounts.Length, Allocator.Temp);
+ for (int i = 1; i < distanceIndexOffsets.Length; i++) {
+ distanceIndexOffsets[i] = distanceIndexOffsets[i-1] + sortedSpanCounts[i-1];
+ }
+ var totalRelevantSpans = distanceIndexOffsets[distanceIndexOffsets.Length - 1] + sortedSpanCounts[sortedSpanCounts.Length - 1];
+
+ var bucketSortedSpans = new NativeArray<Int3>(totalRelevantSpans, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+
+ // Bucket sort the spans based on distance
+ for (int z = 0, pz = 0; z < wd; z += w, pz++) {
+ for (int x = 0; x < field.width; x++) {
+ CompactVoxelCell c = compactCells[z+x];
+
+ for (int i = c.index, ni = c.index+c.count; i < ni; i++) {
+ // Do not take borders or unwalkable spans into account.
+ if ((srcReg[i] & BorderReg) == BorderReg || areaTypes[i] == CompactVoxelField.UnwalkableArea)
+ continue;
+
+ int distIndex = distanceField[i] / 2;
+ bucketSortedSpans[distanceIndexOffsets[distIndex]++] = new Int3(x, i, z);
+ }
+ }
+ }
+
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (distanceIndexOffsets[distanceIndexOffsets.Length - 1] != totalRelevantSpans) throw new System.Exception("Unexpected span count");
+#endif
+
+ // Go through spans in reverse order (i.e largest distances first)
+ for (int distIndex = sortedSpanCounts.Length - 1; distIndex >= 0; distIndex--) {
+ var level = (uint)distIndex * 2;
+ var spansAtLevel = sortedSpanCounts[distIndex];
+ for (int i = 0; i < spansAtLevel; i++) {
+ // Go through the spans stored in bucketSortedSpans for this distance index.
+ // Note that distanceIndexOffsets[distIndex] will point to the element after the end of the group of spans.
+ // There is no particular reason for this, the code just turned out to be a bit simpler to implemen that way.
+ var spanInfo = bucketSortedSpans[distanceIndexOffsets[distIndex] - i - 1];
+ int spanIndex = spanInfo.y;
+
+ // This span is adjacent to a region, so we should start the BFS search from it
+ if (spanFlags[spanIndex] != 0 && srcReg[spanIndex] == 0) {
+ srcReg[spanIndex] = (ushort)spanFlags[spanIndex];
+ srcQue.Enqueue(spanInfo);
+ closed[spanIndex] = true;
+ }
+ }
+
+ // Expand a few iterations out from every known node
+ for (int expansionIteration = 0; expansionIteration < expandIterations && srcQue.Count > 0; expansionIteration++) {
+ while (srcQue.Count > 0) {
+ Int3 spanInfo = srcQue.Dequeue();
+ var area = areaTypes[spanInfo.y];
+ var span = field.spans[spanInfo.y];
+ var region = srcReg[spanInfo.y];
+ closed[spanInfo.y] = true;
+ ushort nextDist = (ushort)(srcDist[spanInfo.y] + 2);
+
+ // Go through the neighbours of the span
+ for (int dir = 0; dir < 4; dir++) {
+ var neighbour = span.GetConnection(dir);
+ if (neighbour == CompactVoxelField.NotConnected) continue;
+
+ int nx = spanInfo.x + VoxelUtilityBurst.DX[dir];
+ int nz = spanInfo.z + VoxelUtilityBurst.DZ[dir]*field.width;
+
+ int ni = compactCells[nx+nz].index + neighbour;
+
+ if ((srcReg[ni] & BorderReg) == BorderReg) // Do not take borders into account.
+ continue;
+
+ // Do not combine different area types
+ if (area == areaTypes[ni]) {
+ if (nextDist < srcDist[ni]) {
+ if (spanDistances[ni] < level) {
+ srcDist[ni] = nextDist;
+ spanFlags[ni] = region;
+ } else if (!closed[ni]) {
+ srcDist[ni] = nextDist;
+ if (srcReg[ni] == 0) dstQue.Enqueue(new Int3(nx, ni, nz));
+ srcReg[ni] = region;
+ }
+ }
+ }
+ }
+ }
+ Memory.Swap(ref srcQue, ref dstQue);
+ }
+
+ // Find the first span that has not been seen yet and start a new region that expands from there
+ var distanceFieldArr = distanceField.AsArray();
+ for (int i = 0; i < spansAtLevel; i++) {
+ var info = bucketSortedSpans[distanceIndexOffsets[distIndex] - i - 1];
+ if (srcReg[info.y] == 0) {
+ if (!FloodRegion(info.x, info.z, info.y, level, regionId, field, distanceFieldArr, srcReg, srcDist, stack, spanFlags, closed)) {
+ // The starting voxel was already adjacent to an existing region so we skip flooding it.
+ // It will be visited in the next area expansion.
+ } else {
+ regionId++;
+ }
+ }
+ }
+ }
+
+ var maxRegions = regionId;
+
+ // Transform from voxel space to graph space.
+ // then scale from voxel space (one unit equals one voxel)
+ // Finally add min
+ Matrix4x4 voxelMatrix = Matrix4x4.TRS(graphSpaceBounds.min, Quaternion.identity, Vector3.one) * Matrix4x4.Scale(new Vector3(cellSize, cellHeight, cellSize));
+
+ // Transform from voxel space to world space
+ // add half a voxel to fix rounding
+ var voxel2worldMatrix = graphTransform * voxelMatrix * Matrix4x4.Translate(new Vector3(0.5f, 0, 0.5f));
+
+ // Filter out small regions.
+ FilterSmallRegions(field, srcReg, minRegionSize, maxRegions, this.relevantGraphSurfaces, this.relevantGraphSurfaceMode, voxel2worldMatrix);
+
+ // Write the result out.
+ for (int i = 0; i < spanCount; i++) {
+ var span = field.spans[i];
+ span.reg = srcReg[i];
+ field.spans[i] = span;
+ }
+
+ // TODO:
+ // field.maxRegions = maxRegions;
+
+// #if ASTAR_DEBUGREPLAY
+// DebugReplay.BeginGroup("Regions");
+// for (int z = 0, pz = 0; z < wd; z += field.width, pz++) {
+// for (int x = 0; x < field.width; x++) {
+// CompactVoxelCell c = field.cells[x+z];
+// for (int i = (int)c.index; i < c.index+c.count; i++) {
+// CompactVoxelSpan s = field.spans[i];
+// DebugReplay.DrawCube(CompactSpanToVector(x, pz, i), UnityEngine.Vector3.one*cellSize, AstarMath.IntToColor(s.reg, 1.0f));
+// }
+// }
+// }
+
+// DebugReplay.EndGroup();
+
+// int maxDist = 0;
+// for (int i = 0; i < srcDist.Length; i++) if (srcDist[i] != 0xFFFF) maxDist = Mathf.Max(maxDist, srcDist[i]);
+
+// DebugReplay.BeginGroup("Distances");
+// for (int z = 0, pz = 0; z < wd; z += field.width, pz++) {
+// for (int x = 0; x < field.width; x++) {
+// CompactVoxelCell c = field.cells[x+z];
+// for (int i = (int)c.index; i < c.index+c.count; i++) {
+// CompactVoxelSpan s = field.spans[i];
+// float f = (float)srcDist[i]/maxDist;
+// DebugReplay.DrawCube(CompactSpanToVector(x, z/field.width, i), Vector3.one*cellSize, new Color(f, f, f));
+// }
+// }
+// }
+
+// DebugReplay.EndGroup();
+// #endif
+ }
+
+ /// <summary>
+ /// Find method in the UnionFind data structure.
+ /// See: https://en.wikipedia.org/wiki/Disjoint-set_data_structure
+ /// </summary>
+ static int union_find_find (NativeArray<int> arr, int x) {
+ if (arr[x] < 0) return x;
+ return arr[x] = union_find_find(arr, arr[x]);
+ }
+
+ /// <summary>
+ /// Join method in the UnionFind data structure.
+ /// See: https://en.wikipedia.org/wiki/Disjoint-set_data_structure
+ /// </summary>
+ static void union_find_union (NativeArray<int> arr, int a, int b) {
+ a = union_find_find(arr, a);
+ b = union_find_find(arr, b);
+ if (a == b) return;
+ if (arr[a] > arr[b]) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+ arr[a] += arr[b];
+ arr[b] = a;
+ }
+
+ public struct RelevantGraphSurfaceInfo {
+ public float3 position;
+ public float range;
+ }
+
+ /// <summary>Filters out or merges small regions.</summary>
+ public static void FilterSmallRegions (CompactVoxelField field, NativeArray<ushort> reg, int minRegionSize, int maxRegions, NativeArray<RelevantGraphSurfaceInfo> relevantGraphSurfaces, RecastGraph.RelevantGraphSurfaceMode relevantGraphSurfaceMode, float4x4 voxel2worldMatrix) {
+ // RelevantGraphSurface c = RelevantGraphSurface.Root;
+ // Need to use ReferenceEquals because it might be called from another thread
+ bool anySurfaces = relevantGraphSurfaces.Length != 0 && (relevantGraphSurfaceMode != RecastGraph.RelevantGraphSurfaceMode.DoNotRequire);
+
+ // Nothing to do here
+ if (!anySurfaces && minRegionSize <= 0) {
+ return;
+ }
+
+ var counter = new NativeArray<int>(maxRegions, Allocator.Temp);
+ var bits = new NativeArray<ushort>(maxRegions, Allocator.Temp, NativeArrayOptions.ClearMemory);
+ for (int i = 0; i < counter.Length; i++) counter[i] = -1;
+
+ int nReg = counter.Length;
+
+ int wd = field.width*field.depth;
+
+ const int RelevantSurfaceSet = 1 << 1;
+ const int BorderBit = 1 << 0;
+
+ // Mark RelevantGraphSurfaces
+
+ const ushort BorderReg = VoxelUtilityBurst.BorderReg;
+ // If they can also be adjacent to tile borders, this will also include the BorderBit
+ int RelevantSurfaceCheck = RelevantSurfaceSet | ((relevantGraphSurfaceMode == RecastGraph.RelevantGraphSurfaceMode.OnlyForCompletelyInsideTile) ? BorderBit : 0x0);
+ // int RelevantSurfaceCheck = 0;
+
+ if (anySurfaces) {
+ var world2voxelMatrix = math.inverse(voxel2worldMatrix);
+ for (int j = 0; j < relevantGraphSurfaces.Length; j++) {
+ var relevantGraphSurface = relevantGraphSurfaces[j];
+ var positionInVoxelSpace = math.transform(world2voxelMatrix, relevantGraphSurface.position);
+ int3 cellIndex = (int3)math.round(positionInVoxelSpace);
+
+ // Check for out of bounds
+ if (cellIndex.x >= 0 && cellIndex.z >= 0 && cellIndex.x < field.width && cellIndex.z < field.depth) {
+ var yScaleFactor = math.length(voxel2worldMatrix.c1.xyz);
+ int rad = (int)(relevantGraphSurface.range / yScaleFactor);
+
+ CompactVoxelCell cell = field.cells[cellIndex.x+cellIndex.z*field.width];
+ for (int i = cell.index; i < cell.index+cell.count; i++) {
+ CompactVoxelSpan s = field.spans[i];
+ if (System.Math.Abs(s.y - cellIndex.y) <= rad && reg[i] != 0) {
+ bits[union_find_find(counter, reg[i] & ~BorderReg)] |= RelevantSurfaceSet;
+ }
+ }
+ }
+ }
+ }
+
+ for (int z = 0; z < wd; z += field.width) {
+ for (int x = 0; x < field.width; x++) {
+ CompactVoxelCell cell = field.cells[x+z];
+
+ for (int i = cell.index; i < cell.index+cell.count; i++) {
+ CompactVoxelSpan s = field.spans[i];
+
+ int r = reg[i];
+
+ // Check if this is an unwalkable span
+ if ((r & ~BorderReg) == 0) continue;
+
+ if (r >= nReg) { //Probably border
+ bits[union_find_find(counter, r & ~BorderReg)] |= BorderBit;
+ continue;
+ }
+
+ int root = union_find_find(counter, r);
+ // Count this span
+ counter[root]--;
+
+ // Iterate through all neighbours of the span.
+ for (int dir = 0; dir < 4; dir++) {
+ if (s.GetConnection(dir) == CompactVoxelField.NotConnected) { continue; }
+
+ int nx = x + VoxelUtilityBurst.DX[dir];
+ int nz = z + VoxelUtilityBurst.DZ[dir] * field.width;
+
+ int ni = field.cells[nx+nz].index + s.GetConnection(dir);
+
+ int r2 = reg[ni];
+
+ // Check if the other span belongs to a different region and is walkable
+ if (r != r2 && (r2 & ~BorderReg) != 0) {
+ if ((r2 & BorderReg) != 0) {
+ // If it's a border region we just mark the current region as being adjacent to a border
+ bits[root] |= BorderBit;
+ } else {
+ // Join the adjacent region with this region.
+ union_find_union(counter, root, r2);
+ }
+ //counter[r] = minRegionSize;
+ }
+ }
+ //counter[r]++;
+ }
+ }
+ }
+
+ // Propagate bits to the region group representative using the union find structure
+ for (int i = 0; i < counter.Length; i++) bits[union_find_find(counter, i)] |= bits[i];
+
+ for (int i = 0; i < counter.Length; i++) {
+ int ctr = union_find_find(counter, i);
+
+ // Check if the region is adjacent to border.
+ // Mark it as being just large enough to always be included in the graph.
+ if ((bits[ctr] & BorderBit) != 0) counter[ctr] = -minRegionSize-2;
+
+ // Not in any relevant surface
+ // or it is adjacent to a border (see RelevantSurfaceCheck)
+ if (anySurfaces && (bits[ctr] & RelevantSurfaceCheck) == 0) counter[ctr] = -1;
+ }
+
+ for (int i = 0; i < reg.Length; i++) {
+ int r = reg[i];
+ // Ignore border regions
+ if (r >= nReg) {
+ continue;
+ }
+
+ // If the region group is too small then make the span unwalkable
+ if (counter[union_find_find(counter, r)] >= -minRegionSize-1) {
+ reg[i] = 0;
+ }
+ }
+ }
+ }
+
+ static class VoxelUtilityBurst {
+ /// <summary>All bits in the region which will be interpreted as a tag.</summary>
+ public const int TagRegMask = TagReg - 1;
+
+ /// <summary>
+ /// If a cell region has this bit set then
+ /// The remaining region bits (see <see cref="TagRegMask)"/> will be used for the node's tag.
+ /// </summary>
+ public const int TagReg = 1 << 14;
+
+ /// <summary>
+ /// If heightfield region ID has the following bit set, the region is on border area
+ /// and excluded from many calculations.
+ /// </summary>
+ public const ushort BorderReg = 1 << 15;
+
+ /// <summary>
+ /// If contour region ID has the following bit set, the vertex will be later
+ /// removed in order to match the segments and vertices at tile boundaries.
+ /// </summary>
+ public const int RC_BORDER_VERTEX = 1 << 16;
+
+ public const int RC_AREA_BORDER = 1 << 17;
+
+ public const int VERTEX_BUCKET_COUNT = 1<<12;
+
+ /// <summary>Tessellate wall edges</summary>
+ public const int RC_CONTOUR_TESS_WALL_EDGES = 1 << 0;
+
+ /// <summary>Tessellate edges between areas</summary>
+ public const int RC_CONTOUR_TESS_AREA_EDGES = 1 << 1;
+
+ /// <summary>Tessellate edges at the border of the tile</summary>
+ public const int RC_CONTOUR_TESS_TILE_EDGES = 1 << 2;
+
+ /// <summary>Mask used with contours to extract region id.</summary>
+ public const int ContourRegMask = 0xffff;
+
+ public static readonly int[] DX = new int[] { -1, 0, 1, 0 };
+ public static readonly int[] DZ = new int[] { 0, 1, 0, -1 };
+
+ public static void CalculateDistanceField (CompactVoxelField field, NativeArray<ushort> output) {
+ int wd = field.width*field.depth;
+
+ // Mark boundary cells
+ for (int z = 0; z < wd; z += field.width) {
+ for (int x = 0; x < field.width; x++) {
+ CompactVoxelCell c = field.cells[x+z];
+
+ for (int i = c.index, ci = c.index+c.count; i < ci; i++) {
+ CompactVoxelSpan s = field.spans[i];
+
+ int numConnections = 0;
+ for (int d = 0; d < 4; d++) {
+ if (s.GetConnection(d) != CompactVoxelField.NotConnected) {
+ //This function (CalculateDistanceField) is used for both ErodeWalkableArea and by itself.
+ //The C++ recast source uses different code for those two cases, but I have found it works with one function
+ //the field.areaTypes[ni] will actually only be one of two cases when used from ErodeWalkableArea
+ //so it will have the same effect as
+ // if (area != UnwalkableArea) {
+ //This line is the one where the differ most
+
+ numConnections++;
+ } else {
+ break;
+ }
+ }
+
+ // TODO: Check initialization
+ output[i] = numConnections == 4 ? ushort.MaxValue : (ushort)0;
+ }
+ }
+ }
+
+ // Grassfire transform
+ // Pass 1
+
+ for (int z = 0; z < wd; z += field.width) {
+ for (int x = 0; x < field.width; x++) {
+ int cellIndex = x + z;
+ CompactVoxelCell c = field.cells[cellIndex];
+
+ for (int i = c.index, ci = c.index+c.count; i < ci; i++) {
+ CompactVoxelSpan s = field.spans[i];
+ var dist = (int)output[i];
+
+ if (s.GetConnection(0) != CompactVoxelField.NotConnected) {
+ // (-1,0)
+ int neighbourCell = field.GetNeighbourIndex(cellIndex, 0);
+
+ int ni = field.cells[neighbourCell].index+s.GetConnection(0);
+
+ dist = math.min(dist, (int)output[ni]+2);
+
+ CompactVoxelSpan ns = field.spans[ni];
+
+ if (ns.GetConnection(3) != CompactVoxelField.NotConnected) {
+ // (-1,0) + (0,-1) = (-1,-1)
+ int neighbourCell2 = field.GetNeighbourIndex(neighbourCell, 3);
+
+ int nni = (int)(field.cells[neighbourCell2].index+ns.GetConnection(3));
+
+ dist = math.min(dist, (int)output[nni]+3);
+ }
+ }
+
+ if (s.GetConnection(3) != CompactVoxelField.NotConnected) {
+ // (0,-1)
+ int neighbourCell = field.GetNeighbourIndex(cellIndex, 3);
+
+ int ni = (int)(field.cells[neighbourCell].index+s.GetConnection(3));
+
+ dist = math.min(dist, (int)output[ni]+2);
+
+ CompactVoxelSpan ns = field.spans[ni];
+
+ if (ns.GetConnection(2) != CompactVoxelField.NotConnected) {
+ // (0,-1) + (1,0) = (1,-1)
+ int neighbourCell2 = field.GetNeighbourIndex(neighbourCell, 2);
+
+ int nni = (int)(field.cells[neighbourCell2].index+ns.GetConnection(2));
+
+ dist = math.min(dist, (int)output[nni]+3);
+ }
+ }
+
+ output[i] = (ushort)dist;
+ }
+ }
+ }
+
+ // Pass 2
+
+ for (int z = wd-field.width; z >= 0; z -= field.width) {
+ for (int x = field.width-1; x >= 0; x--) {
+ int cellIndex = x + z;
+ CompactVoxelCell c = field.cells[cellIndex];
+
+ for (int i = (int)c.index, ci = (int)(c.index+c.count); i < ci; i++) {
+ CompactVoxelSpan s = field.spans[i];
+ var dist = (int)output[i];
+
+ if (s.GetConnection(2) != CompactVoxelField.NotConnected) {
+ // (-1,0)
+ int neighbourCell = field.GetNeighbourIndex(cellIndex, 2);
+
+ int ni = (int)(field.cells[neighbourCell].index+s.GetConnection(2));
+
+ dist = math.min(dist, (int)output[ni]+2);
+
+ CompactVoxelSpan ns = field.spans[ni];
+
+ if (ns.GetConnection(1) != CompactVoxelField.NotConnected) {
+ // (-1,0) + (0,-1) = (-1,-1)
+ int neighbourCell2 = field.GetNeighbourIndex(neighbourCell, 1);
+
+ int nni = (int)(field.cells[neighbourCell2].index+ns.GetConnection(1));
+
+ dist = math.min(dist, (int)output[nni]+3);
+ }
+ }
+
+ if (s.GetConnection(1) != CompactVoxelField.NotConnected) {
+ // (0,-1)
+ int neighbourCell = field.GetNeighbourIndex(cellIndex, 1);
+
+ int ni = (int)(field.cells[neighbourCell].index+s.GetConnection(1));
+
+ dist = math.min(dist, (int)output[ni]+2);
+
+ CompactVoxelSpan ns = field.spans[ni];
+
+ if (ns.GetConnection(0) != CompactVoxelField.NotConnected) {
+ // (0,-1) + (1,0) = (1,-1)
+ int neighbourCell2 = field.GetNeighbourIndex(neighbourCell, 0);
+
+ int nni = (int)(field.cells[neighbourCell2].index+ns.GetConnection(0));
+
+ dist = math.min(dist, (int)output[nni]+3);
+ }
+ }
+
+ output[i] = (ushort)dist;
+ }
+ }
+ }
+
+// #if ASTAR_DEBUGREPLAY && FALSE
+// DebugReplay.BeginGroup("Distance Field");
+// for (int z = wd-field.width; z >= 0; z -= field.width) {
+// for (int x = field.width-1; x >= 0; x--) {
+// CompactVoxelCell c = field.cells[x+z];
+
+// for (int i = (int)c.index, ci = (int)(c.index+c.count); i < ci; i++) {
+// DebugReplay.DrawCube(CompactSpanToVector(x, z/field.width, i), Vector3.one*cellSize, new Color((float)output[i]/maxDist, (float)output[i]/maxDist, (float)output[i]/maxDist));
+// }
+// }
+// }
+// DebugReplay.EndGroup();
+// #endif
+ }
+
+ public static void BoxBlur (CompactVoxelField field, NativeArray<ushort> src, NativeArray<ushort> dst) {
+ ushort thr = 20;
+
+ int wd = field.width*field.depth;
+
+ for (int z = wd-field.width; z >= 0; z -= field.width) {
+ for (int x = field.width-1; x >= 0; x--) {
+ int cellIndex = x + z;
+ CompactVoxelCell c = field.cells[cellIndex];
+
+ for (int i = (int)c.index, ci = (int)(c.index+c.count); i < ci; i++) {
+ CompactVoxelSpan s = field.spans[i];
+
+ ushort cd = src[i];
+
+ if (cd < thr) {
+ dst[i] = cd;
+ continue;
+ }
+
+ int total = (int)cd;
+
+ for (int d = 0; d < 4; d++) {
+ if (s.GetConnection(d) != CompactVoxelField.NotConnected) {
+ var neighbourIndex = field.GetNeighbourIndex(cellIndex, d);
+ int ni = (int)(field.cells[neighbourIndex].index+s.GetConnection(d));
+
+ total += (int)src[ni];
+
+ CompactVoxelSpan ns = field.spans[ni];
+
+ int d2 = (d+1) & 0x3;
+
+ if (ns.GetConnection(d2) != CompactVoxelField.NotConnected) {
+ var neighbourIndex2 = field.GetNeighbourIndex(neighbourIndex, d2);
+
+ int nni = (int)(field.cells[neighbourIndex2].index+ns.GetConnection(d2));
+ total += (int)src[nni];
+ } else {
+ total += cd;
+ }
+ } else {
+ total += cd*2;
+ }
+ }
+ dst[i] = (ushort)((total+5)/9F);
+ }
+ }
+ }
+ }
+ }
+}