public void Execute() { int wd = field.width * field.depth; // Build voxel connections 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, ni = (int)(c.index + c.count); i < ni; i++) { CompactVoxelSpan s = field.spans[i]; s.con = 0xFFFFFFFF; for (int d = 0; d < 4; d++) { int nx = x + VoxelUtilityBurst.DX[d]; int nz = z + VoxelUtilityBurst.DZ[d] * field.width; if (nx < 0 || nz < 0 || nz >= wd || nx >= field.width) { continue; } CompactVoxelCell nc = field.cells[nx + nz]; for (int k = nc.index, nk = (int)(nc.index + nc.count); k < nk; k++) { CompactVoxelSpan ns = field.spans[k]; int bottom = System.Math.Max(s.y, ns.y); int top = System.Math.Min((int)s.y + (int)s.h, (int)ns.y + (int)ns.h); if ((top - bottom) >= voxelWalkableHeight && System.Math.Abs((int)ns.y - (int)s.y) <= voxelWalkableClimb) { uint connIdx = (uint)k - (uint)nc.index; if (connIdx > CompactVoxelField.MaxLayers) { throw new System.Exception("Too many layers"); } s.SetConnection(d, connIdx); break; } } } field.spans[i] = s; } } } }
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 = (int)c.index, ni = (int)(c.index + c.count); i < ni; i++) { if (field.areaTypes[i] != CompactVoxelField.UnwalkableArea) { srcReg[i] = region; } } } } }
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); } } } }
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 }
/// <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 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] = (ushort)0; srcDist[i] = (ushort)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((int)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 = (int)c.index, ni = (int)(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 (distanceIndexOffsets[distanceIndexOffsets.Length - 1] != totalRelevantSpans) { throw new System.Exception("Unexpected span count"); } // 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 = (int)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; } } } } } Util.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 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, distanceField, 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; // Filter out small regions. FilterSmallRegions(field, srcReg, minRegionSize, maxRegions); // 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 }
public void BuildFromLinkedField(LinkedVoxelField field) { int idx = 0; Assert.AreEqual(this.width, field.width); Assert.AreEqual(this.depth, field.depth); this.depth = field.depth; int w = field.width; int d = field.depth; int wd = w * d; int spanCount = field.GetSpanCount(); spans.Resize(spanCount, NativeArrayOptions.UninitializedMemory); areaTypes.Resize(spanCount, NativeArrayOptions.UninitializedMemory); cells.Resize(wd, NativeArrayOptions.UninitializedMemory); if (this.voxelWalkableHeight >= ushort.MaxValue) { throw new System.Exception("Too high walkable height to guarantee correctness. Increase voxel height or lower walkable height."); } var linkedSpans = field.linkedSpans; for (int z = 0, pz = 0; z < wd; z += w, pz++) { for (int x = 0; x < w; x++) { int spanIndex = x + z; if (linkedSpans[spanIndex].bottom == LinkedVoxelField.InvalidSpanValue) { cells[x + z] = new CompactVoxelCell(0, 0); continue; } int index = idx; int count = 0; while (spanIndex != -1) { if (linkedSpans[spanIndex].area != UnwalkableArea) { int bottom = (int)linkedSpans[spanIndex].top; int next = linkedSpans[spanIndex].next; int top = next != -1 ? (int)linkedSpans[next].bottom : VoxelArea.MaxHeightInt; // TODO: Why is top-bottom clamped to a ushort range? spans[idx] = new CompactVoxelSpan((ushort)math.min(bottom, ushort.MaxValue), (uint)math.min(top - bottom, ushort.MaxValue)); areaTypes[idx] = linkedSpans[spanIndex].area; idx++; count++; } spanIndex = linkedSpans[spanIndex].next; } cells[x + z] = new CompactVoxelCell(index, count); } } if (idx != spanCount) { throw new System.Exception("Found span count does not match expected value"); } }
public void WalkContour(int x, int z, int i, NativeArray <ushort> flags, NativeList <int> verts) { // Choose the first non-connected edge int dir = 0; while ((flags[i] & (ushort)(1 << dir)) == 0) { dir++; } int startDir = dir; int startI = i; int area = field.areaTypes[i]; int iter = 0; while (iter++ < 40000) { //Are we facing a region edge if ((flags[i] & (ushort)(1 << dir)) != 0) { //Choose the edge corner bool isBorderVertex = false; bool isAreaBorder = false; int px = x; int py = GetCornerHeight(x, z, i, dir, ref isBorderVertex); int pz = z; switch (dir) { case 0: pz += field.width;; break; case 1: px++; pz += field.width; break; case 2: px++; break; } /*case 1: px++; break; * case 2: px++; pz += field.width; break; * case 3: pz += field.width; break; */ int r = 0; CompactVoxelSpan s = field.spans[i]; if (s.GetConnection(dir) != CompactVoxelField.NotConnected) { int ni = (int)field.cells[field.GetNeighbourIndex(x + z, dir)].index + s.GetConnection(dir); r = (int)field.spans[ni].reg; if (area != field.areaTypes[ni]) { isAreaBorder = true; } } if (isBorderVertex) { r |= VoxelUtilityBurst.RC_BORDER_VERTEX; } if (isAreaBorder) { r |= VoxelUtilityBurst.RC_AREA_BORDER; } verts.Add(px); verts.Add(py); verts.Add(pz); verts.Add(r); //Debug.DrawRay (previousPos,new Vector3 ((dir == 1 || dir == 2) ? 1 : 0, 0, (dir == 0 || dir == 1) ? 1 : 0),Color.cyan); flags[i] = (ushort)(flags[i] & ~(1 << dir)); // Remove visited edges // & 0x3 is the same as % 4 (for positive numbers) dir = (dir + 1) & 0x3; // Rotate CW } else { int ni = -1; int nx = x + VoxelUtilityBurst.DX[dir]; int nz = z + VoxelUtilityBurst.DZ[dir] * field.width; CompactVoxelSpan s = field.spans[i]; if (s.GetConnection(dir) != CompactVoxelField.NotConnected) { CompactVoxelCell nc = field.cells[nx + nz]; ni = (int)nc.index + s.GetConnection(dir); } if (ni == -1) { Debug.LogWarning("Degenerate triangles might have been generated.\n" + "Usually this is not a problem, but if you have a static level, try to modify the graph settings slightly to avoid this edge case."); return; } x = nx; z = nz; i = ni; // & 0x3 is the same as % 4 (modulo 4) dir = (dir + 3) & 0x3; // Rotate CCW } if (startI == i && startDir == dir) { break; } } }
public void Execute() { outputContours.Clear(); outputVerts.Clear(); int w = field.width; int d = field.depth; int wd = w * d; //cset.bounds = field.bounds; const ushort BorderReg = VoxelUtilityBurst.BorderReg; //cset.nconts = 0; //NOTE: This array may contain any data, but since we explicitly set all data in it before we use it, it's OK. var flags = new NativeArray <ushort>(field.spans.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); // Mark boundaries. (@?) 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++) { ushort res = 0; CompactVoxelSpan s = field.spans[i]; if (s.reg == 0 || (s.reg & BorderReg) == BorderReg) { flags[i] = 0; continue; } for (int dir = 0; dir < 4; dir++) { int r = 0; if (s.GetConnection(dir) != CompactVoxelField.NotConnected) { int ni = (int)field.cells[field.GetNeighbourIndex(x + z, dir)].index + s.GetConnection(dir); r = field.spans[ni].reg; } //@TODO - Why isn't this inside the previous IF if (r == s.reg) { res |= (ushort)(1 << dir); } } //Inverse, mark non connected edges. flags[i] = (ushort)(res ^ 0xf); } } } NativeList <int> verts = new NativeList <int>(256, Allocator.Temp); NativeList <int> simplified = new NativeList <int>(64, Allocator.Temp); 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]; if (flags[i] == 0 || flags[i] == 0xf) { flags[i] = 0; continue; } int reg = field.spans[i].reg; if (reg == 0 || (reg & BorderReg) == BorderReg) { continue; } int area = field.areaTypes[i]; verts.Clear(); simplified.Clear(); WalkContour(x, z, i, flags, verts); SimplifyContour(verts, simplified, maxError, buildFlags); RemoveDegenerateSegments(simplified); VoxelContour contour = new VoxelContour(); contour.vertexStartIndex = outputVerts.Length; outputVerts.AddRange(simplified); // #if ASTAR_RECAST_INCLUDE_RAW_VERTEX_CONTOUR // //Not used at the moment, just debug stuff // contour.rverts = ClaimIntArr(verts.Length); // for (int j = 0; j < verts.Length; j++) contour.rverts[j] = verts[j]; // #endif contour.nverts = simplified.Length / 4; contour.reg = reg; contour.area = area; outputContours.Add(contour); // #if ASTARDEBUG // for (int q = 0, j = (simplified.Length/4)-1; q < (simplified.Length/4); j = q, q++) { // int i4 = q*4; // int j4 = j*4; // Vector3 p1 = Vector3.Scale( // new Vector3( // simplified[i4+0], // simplified[i4+1], // (simplified[i4+2]/(float)field.width) // ), // cellScale) // +voxelOffset; // Vector3 p2 = Vector3.Scale( // new Vector3( // simplified[j4+0], // simplified[j4+1], // (simplified[j4+2]/(float)field.width) // ) // , cellScale) // +voxelOffset; // if (CalcAreaOfPolygon2D(contour.verts, contour.nverts) > 0) { // Debug.DrawLine(p1, p2, AstarMath.IntToColor(reg, 0.5F)); // } else { // Debug.DrawLine(p1, p2, Color.red); // } // } // #endif } } } verts.Dispose(); simplified.Dispose(); // Check and merge droppings. // Sometimes the previous algorithms can fail and create several outputContours // per area. This pass will try to merge the holes into the main region. for (int i = 0; i < outputContours.Length; i++) { VoxelContour cont = outputContours[i]; // Check if the contour is would backwards. if (CalcAreaOfPolygon2D(outputVerts, cont.vertexStartIndex, cont.nverts) < 0) { // Find another contour which has the same region ID. int mergeIdx = -1; for (int j = 0; j < outputContours.Length; j++) { if (i == j) { continue; } if (outputContours[j].nverts > 0 && outputContours[j].reg == cont.reg) { // Make sure the polygon is correctly oriented. if (CalcAreaOfPolygon2D(outputVerts, outputContours[j].vertexStartIndex, outputContours[j].nverts) > 0) { mergeIdx = j; break; } } } if (mergeIdx == -1) { // Debug.LogError("rcBuildContours: Could not find merge target for bad contour "+i+"."); } else { // Debugging // Debug.LogWarning ("Fixing contour"); VoxelContour mcont = outputContours[mergeIdx]; // Merge by closest points. int ia = 0, ib = 0; GetClosestIndices(outputVerts, mcont.vertexStartIndex, mcont.nverts, cont.vertexStartIndex, cont.nverts, ref ia, ref ib); if (ia == -1 || ib == -1) { // Debug.LogWarning("rcBuildContours: Failed to find merge points for "+i+" and "+mergeIdx+"."); continue; } if (!MergeContours(outputVerts, ref mcont, ref cont, ia, ib)) { //Debug.LogWarning("rcBuildContours: Failed to merge contours "+i+" and "+mergeIdx+"."); continue; } outputContours[mergeIdx] = mcont; outputContours[i] = cont; } } } }