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); } } } }
/// <summary>Filters out or merges small regions.</summary> public void FilterSmallRegions(CompactVoxelField field, NativeArray <ushort> reg, int minRegionSize, int maxRegions) { // RelevantGraphSurface c = RelevantGraphSurface.Root; // Need to use ReferenceEquals because it might be called from another thread // bool anySurfaces = !RelevantGraphSurface.ReferenceEquals(c, null) && (relevantGraphSurfaceMode != RecastGraph.RelevantGraphSurfaceMode.DoNotRequire); bool anySurfaces = false; // 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 // 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; // TODO // if (anySurfaces) { // // Need to use ReferenceEquals because it might be called from another thread // while (!RelevantGraphSurface.ReferenceEquals(c, null)) { // int x, z; // this.VectorToIndex(c.Position, out x, out z); // // Check for out of bounds // if (x >= 0 && z >= 0 && x < field.width && z < field.depth) { // int y = (int)((c.Position.y - voxelOffset.y)/cellHeight); // int rad = (int)(c.maxRange / cellHeight); // CompactVoxelCell cell = field.cells[x+z*field.width]; // for (int i = (int)cell.index; i < cell.index+cell.count; i++) { // CompactVoxelSpan s = field.spans[i]; // if (System.Math.Abs(s.y - y) <= rad && reg[i] != 0) { // bits[union_find_find(counter, (int)reg[i] & ~BorderReg)] |= RelevantSurfaceSet; // } // } // } // c = c.Next; // } // } const ushort BorderReg = VoxelUtilityBurst.BorderReg; for (int z = 0, pz = 0; z < wd; z += field.width, pz++) { for (int x = 0; x < field.width; x++) { CompactVoxelCell cell = field.cells[x + z]; for (int i = (int)cell.index; i < cell.index + cell.count; i++) { CompactVoxelSpan s = field.spans[i]; int r = (int)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 = (int)field.cells[nx + nz].index + s.GetConnection(dir); int r2 = (int)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 = (int)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; } } }
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 = (int)c.index, ci = (int)(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 = (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(0) != CompactVoxelField.NotConnected) { // (-1,0) int neighbourCell = field.GetNeighbourIndex(cellIndex, 0); int ni = (int)(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 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 = (int)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] = (ushort)0xFFFF; continue; } count++; if (closed != null) { closed[ci] = true; } // Expand neighbours. for (int dir = 0; dir < 4; ++dir) { 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; } if (srcReg[ai] == 0) { 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 if (flags != null) { flags[ai] = r; srcDist[ai] = 2; } } } } } return(count > 0); }