using UnityEngine; using System.Collections.Generic; namespace Pathfinding.Voxels { /// /// Voxelizer for recast graphs. /// /// In comments: units wu are World Units, vx are Voxels /// public partial class Voxelize { public List inputMeshes; /// Maximum ledge height that is considered to still be traversable. [Limit: >=0] [Units: vx] public readonly int voxelWalkableClimb; /// /// Minimum floor to 'ceiling' height that will still allow the floor area to /// be considered walkable. [Limit: >= 3] [Units: vx] /// public readonly uint voxelWalkableHeight; /// The xz-plane cell size to use for fields. [Limit: > 0] [Units: wu] public readonly float cellSize = 0.2F; /// The y-axis cell size to use for fields. [Limit: > 0] [Units: wu] public readonly float cellHeight = 0.1F; public int minRegionSize = 100; /// The size of the non-navigable border around the heightfield. [Limit: >=0] [Units: vx] public int borderSize = 0; /// The maximum allowed length for contour edges along the border of the mesh. [Limit: >= 0] [Units: vx] public float maxEdgeLength = 20; /// The maximum slope that is considered walkable. [Limits: 0 <= value < 90] [Units: Degrees] public float maxSlope = 30; public RecastGraph.RelevantGraphSurfaceMode relevantGraphSurfaceMode; /// The world AABB to rasterize public Bounds forcedBounds; public VoxelArea voxelArea; public VoxelContourSet countourSet; /// Transform from voxel space to world space Pathfinding.Util.GraphTransform transform; /// Transform from voxel space to graph space public Pathfinding.Util.GraphTransform transformVoxel2Graph { get; private set; } /// /// Width in voxels. /// Must match the /// public int width; /// /// Depth in voxels. /// Must match the /// public int depth; #region Debug Vector3 voxelOffset = Vector3.zero; public Vector3 CompactSpanToVector (int x, int z, int i) { return voxelOffset+new Vector3((x+0.5f)*cellSize, voxelArea.compactSpans[i].y*cellHeight, (z+0.5f)*cellSize); } public void VectorToIndex (Vector3 p, out int x, out int z) { p -= voxelOffset; x = Mathf.RoundToInt((p.x / cellSize) - 0.5f); z = Mathf.RoundToInt((p.z / cellSize) - 0.5f); } #endregion #region Constants /// @name Constants @{ public const uint NotConnected = 0x3f; /// Unmotivated variable, but let's clamp the layers at 65535 const int MaxLayers = 65535; /// TODO: : Check up on this variable const int MaxRegions = 500; const int UnwalkableArea = 0; /// /// If heightfield region ID has the following bit set, the region is on border area /// and excluded from many calculations. /// const ushort BorderReg = 0x8000; /// /// 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. /// const int RC_BORDER_VERTEX = 0x10000; const int RC_AREA_BORDER = 0x20000; const int VERTEX_BUCKET_COUNT = 1<<12; /// Tessellate wall edges public const int RC_CONTOUR_TESS_WALL_EDGES = 1 << 0; /// Tessellate edges between areas public const int RC_CONTOUR_TESS_AREA_EDGES = 1 << 1; /// Tessellate edges at the border of the tile public const int RC_CONTOUR_TESS_TILE_EDGES = 1 << 2; /// Mask used with contours to extract region id. const int ContourRegMask = 0xffff; #endregion /// @} readonly Vector3 cellScale; public Voxelize (float ch, float cs, float walkableClimb, float walkableHeight, float maxSlope, float maxEdgeLength) { cellSize = cs; cellHeight = ch; this.maxSlope = maxSlope; cellScale = new Vector3(cellSize, cellHeight, cellSize); voxelWalkableHeight = (uint)(walkableHeight/cellHeight); voxelWalkableClimb = Mathf.RoundToInt(walkableClimb/cellHeight); this.maxEdgeLength = maxEdgeLength; } public void Init () { // Initialize the voxel area if (voxelArea == null || voxelArea.width != width || voxelArea.depth != depth) voxelArea = new VoxelArea(width, depth); else voxelArea.Reset(); } public void VoxelizeInput (Pathfinding.Util.GraphTransform graphTransform, Bounds graphSpaceBounds) { AstarProfiler.StartProfile("Build Navigation Mesh"); AstarProfiler.StartProfile("Voxelizing - Step 1"); // 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)); transformVoxel2Graph = new Pathfinding.Util.GraphTransform(voxelMatrix); // Transform from voxel space to world space // add half a voxel to fix rounding transform = graphTransform * voxelMatrix * Matrix4x4.TRS(new Vector3(0.5f, 0, 0.5f), Quaternion.identity, Vector3.one); int maximumVoxelYCoord = (int)(graphSpaceBounds.size.y / cellHeight); AstarProfiler.EndProfile("Voxelizing - Step 1"); AstarProfiler.StartProfile("Voxelizing - Step 2 - Init"); // Cosine of the slope limit in voxel space (some tweaks are needed because the voxel space might be stretched out along the y axis) float slopeLimit = Mathf.Cos(Mathf.Atan(Mathf.Tan(maxSlope*Mathf.Deg2Rad)*(cellSize/cellHeight))); // Temporary arrays used for rasterization var clipperOrig = new VoxelPolygonClipper(3); var clipperX1 = new VoxelPolygonClipper(7); var clipperX2 = new VoxelPolygonClipper(7); var clipperZ1 = new VoxelPolygonClipper(7); var clipperZ2 = new VoxelPolygonClipper(7); if (inputMeshes == null) throw new System.NullReferenceException("inputMeshes not set"); // Find the largest lengths of vertex arrays and check for meshes which can be skipped int maxVerts = 0; for (int m = 0; m < inputMeshes.Count; m++) { maxVerts = System.Math.Max(inputMeshes[m].vertices.Length, maxVerts); } // Create buffer, here vertices will be stored multiplied with the local-to-voxel-space matrix var verts = new Vector3[maxVerts]; AstarProfiler.EndProfile("Voxelizing - Step 2 - Init"); AstarProfiler.StartProfile("Voxelizing - Step 2"); // This loop is the hottest place in the whole rasterization process // it usually accounts for around 50% of the time for (int m = 0; m < inputMeshes.Count; m++) { RasterizationMesh mesh = inputMeshes[m]; var meshMatrix = mesh.matrix; // Flip the orientation of all faces if the mesh is scaled in such a way // that the face orientations would change // This happens for example if a mesh has a negative scale along an odd number of axes // e.g it happens for the scale (-1, 1, 1) but not for (-1, -1, 1) or (1,1,1) var flipOrientation = VectorMath.ReversesFaceOrientations(meshMatrix); Vector3[] vs = mesh.vertices; int[] tris = mesh.triangles; int trisLength = mesh.numTriangles; // Transform vertices first to world space and then to voxel space for (int i = 0; i < vs.Length; i++) verts[i] = transform.InverseTransform(meshMatrix.MultiplyPoint3x4(vs[i])); int mesharea = mesh.area; for (int i = 0; i < trisLength; i += 3) { Vector3 p1 = verts[tris[i]]; Vector3 p2 = verts[tris[i+1]]; Vector3 p3 = verts[tris[i+2]]; if (flipOrientation) { var tmp = p1; p1 = p3; p3 = tmp; } int minX = (int)(Utility.Min(p1.x, p2.x, p3.x)); int minZ = (int)(Utility.Min(p1.z, p2.z, p3.z)); int maxX = (int)System.Math.Ceiling(Utility.Max(p1.x, p2.x, p3.x)); int maxZ = (int)System.Math.Ceiling(Utility.Max(p1.z, p2.z, p3.z)); minX = Mathf.Clamp(minX, 0, voxelArea.width-1); maxX = Mathf.Clamp(maxX, 0, voxelArea.width-1); minZ = Mathf.Clamp(minZ, 0, voxelArea.depth-1); maxZ = Mathf.Clamp(maxZ, 0, voxelArea.depth-1); // Check if the mesh is completely out of bounds if (minX >= voxelArea.width || minZ >= voxelArea.depth || maxX <= 0 || maxZ <= 0) continue; Vector3 normal; int area; //AstarProfiler.StartProfile ("Rasterize..."); normal = Vector3.Cross(p2-p1, p3-p1); float cosSlopeAngle = Vector3.Dot(normal.normalized, Vector3.up); if (cosSlopeAngle < slopeLimit) { area = UnwalkableArea; } else { area = 1 + mesharea; } clipperOrig[0] = p1; clipperOrig[1] = p2; clipperOrig[2] = p3; clipperOrig.n = 3; for (int x = minX; x <= maxX; x++) { clipperOrig.ClipPolygonAlongX(ref clipperX1, 1f, -x+0.5f); if (clipperX1.n < 3) { continue; } clipperX1.ClipPolygonAlongX(ref clipperX2, -1F, x+0.5F); if (clipperX2.n < 3) { continue; } float clampZ1 = clipperX2.z[0]; float clampZ2 = clipperX2.z[0]; for (int q = 1; q < clipperX2.n; q++) { float val = clipperX2.z[q]; clampZ1 = System.Math.Min(clampZ1, val); clampZ2 = System.Math.Max(clampZ2, val); } int clampZ1I = Mathf.Clamp((int)System.Math.Round(clampZ1), 0, voxelArea.depth-1); int clampZ2I = Mathf.Clamp((int)System.Math.Round(clampZ2), 0, voxelArea.depth-1); for (int z = clampZ1I; z <= clampZ2I; z++) { clipperX2.ClipPolygonAlongZWithYZ(ref clipperZ1, 1F, -z+0.5F); if (clipperZ1.n < 3) { continue; } clipperZ1.ClipPolygonAlongZWithY(ref clipperZ2, -1F, z+0.5F); if (clipperZ2.n < 3) { continue; } float sMin = clipperZ2.y[0]; float sMax = clipperZ2.y[0]; for (int q = 1; q < clipperZ2.n; q++) { float val = clipperZ2.y[q]; sMin = System.Math.Min(sMin, val); sMax = System.Math.Max(sMax, val); } int maxi = (int)System.Math.Ceiling(sMax); // Skip span if below or above the bounding box if (maxi >= 0 && sMin <= maximumVoxelYCoord) { // Make sure mini >= 0 int mini = System.Math.Max(0, (int)sMin); // Make sure the span is at least 1 voxel high maxi = System.Math.Max(mini+1, maxi); voxelArea.AddLinkedSpan(z*voxelArea.width+x, (uint)mini, (uint)maxi, area, voxelWalkableClimb); } } } } //AstarProfiler.EndFastProfile(0); //AstarProfiler.EndProfile ("Rasterize..."); } AstarProfiler.EndProfile("Voxelizing - Step 2"); } public void DebugDrawSpans () { #if !ASTAR_RECAST_CLASS_BASED_LINKED_LIST int wd = voxelArea.width*voxelArea.depth; var min = forcedBounds.min; LinkedVoxelSpan[] spans = voxelArea.linkedSpans; for (int z = 0, pz = 0; z < wd; z += voxelArea.width, pz++) { for (int x = 0; x < voxelArea.width; x++) { for (int s = z+x; s != -1 && spans[s].bottom != VoxelArea.InvalidSpanValue; s = spans[s].next) { uint bottom = spans[s].top; uint top = spans[s].next != -1 ? spans[spans[s].next].bottom : VoxelArea.MaxHeight; if (bottom > top) { Debug.Log(bottom + " " + top); Debug.DrawLine(new Vector3(x*cellSize, bottom*cellHeight, pz*cellSize)+min, new Vector3(x*cellSize, top*cellHeight, pz*cellSize)+min, Color.yellow, 1); } //Debug.DrawRay (p,voxelArea.VectorDirection[d]*cellSize*0.5F,Color.red); if (top - bottom < voxelWalkableHeight) { //spans[s].area = UnwalkableArea; } } } } #else Debug.LogError("This debug method only works with !ASTAR_RECAST_CLASS_BASED_LINKED_LIST"); #endif } public void BuildCompactField () { AstarProfiler.StartProfile("Build Compact Voxel Field"); //Build compact representation int spanCount = voxelArea.GetSpanCount(); voxelArea.compactSpanCount = spanCount; if (voxelArea.compactSpans == null || voxelArea.compactSpans.Length < spanCount) { voxelArea.compactSpans = new CompactVoxelSpan[spanCount]; voxelArea.areaTypes = new int[spanCount]; } uint idx = 0; int w = voxelArea.width; int d = voxelArea.depth; int wd = w*d; if (this.voxelWalkableHeight >= 0xFFFF) { Debug.LogWarning("Too high walkable height to guarantee correctness. Increase voxel height or lower walkable height."); } #if !ASTAR_RECAST_CLASS_BASED_LINKED_LIST LinkedVoxelSpan[] spans = voxelArea.linkedSpans; #endif //Parallel.For (0, voxelArea.depth, delegate (int pz) { for (int z = 0, pz = 0; z < wd; z += w, pz++) { for (int x = 0; x < w; x++) { #if !ASTAR_RECAST_CLASS_BASED_LINKED_LIST int spanIndex = x+z; if (spans[spanIndex].bottom == VoxelArea.InvalidSpanValue) { voxelArea.compactCells[x+z] = new CompactVoxelCell(0, 0); continue; } uint index = idx; uint count = 0; //Vector3 p = new Vector3(x,0,pz)*cellSize+voxelOffset; while (spanIndex != -1) { if (spans[spanIndex].area != UnwalkableArea) { int bottom = (int)spans[spanIndex].top; int next = spans[spanIndex].next; int top = next != -1 ? (int)spans[next].bottom : VoxelArea.MaxHeightInt; voxelArea.compactSpans[idx] = new CompactVoxelSpan((ushort)(bottom > 0xFFFF ? 0xFFFF : bottom), (uint)(top-bottom > 0xFFFF ? 0xFFFF : top-bottom)); voxelArea.areaTypes[idx] = spans[spanIndex].area; idx++; count++; } spanIndex = spans[spanIndex].next; } voxelArea.compactCells[x+z] = new CompactVoxelCell(index, count); #else VoxelSpan s = voxelArea.cells[x+z].firstSpan; if (s == null) { voxelArea.compactCells[x+z] = new CompactVoxelCell(0, 0); continue; } uint index = idx; uint count = 0; //Vector3 p = new Vector3(x,0,pz)*cellSize+voxelOffset; while (s != null) { if (s.area != UnwalkableArea) { int bottom = (int)s.top; int top = s.next != null ? (int)s.next.bottom : VoxelArea.MaxHeightInt; voxelArea.compactSpans[idx] = new CompactVoxelSpan((ushort)Mathf.Clamp(bottom, 0, 0xffff), (uint)Mathf.Clamp(top-bottom, 0, 0xffff)); voxelArea.areaTypes[idx] = s.area; idx++; count++; } s = s.next; } voxelArea.compactCells[x+z] = new CompactVoxelCell(index, count); #endif } } AstarProfiler.EndProfile("Build Compact Voxel Field"); } public void BuildVoxelConnections () { AstarProfiler.StartProfile("Build Voxel Connections"); int wd = voxelArea.width*voxelArea.depth; CompactVoxelSpan[] spans = voxelArea.compactSpans; CompactVoxelCell[] cells = voxelArea.compactCells; // Build voxel connections for (int z = 0, pz = 0; z < wd; z += voxelArea.width, pz++) { for (int x = 0; x < voxelArea.width; x++) { CompactVoxelCell c = cells[x+z]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; i++) { CompactVoxelSpan s = spans[i]; spans[i].con = 0xFFFFFFFF; for (int d = 0; d < 4; d++) { int nx = x+voxelArea.DirectionX[d]; int nz = z+voxelArea.DirectionZ[d]; if (nx < 0 || nz < 0 || nz >= wd || nx >= voxelArea.width) { continue; } CompactVoxelCell nc = cells[nx+nz]; for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; k++) { CompactVoxelSpan ns = 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 - nc.index; if (connIdx > MaxLayers) { Debug.LogError("Too many layers"); continue; } spans[i].SetConnection(d, connIdx); break; } } } } } } AstarProfiler.EndProfile("Build Voxel Connections"); } void DrawLine (int a, int b, int[] indices, int[] verts, Color color) { int p1 = (indices[a] & 0x0fffffff) * 4; int p2 = (indices[b] & 0x0fffffff) * 4; Debug.DrawLine(VoxelToWorld(verts[p1+0], verts[p1+1], verts[p1+2]), VoxelToWorld(verts[p2+0], verts[p2+1], verts[p2+2]), color); } #if ASTARDEBUG Vector3 ConvertPos (int x, int y, int z) { Vector3 p = Vector3.Scale( new Vector3( x+0.5F, y, (z/(float)voxelArea.width)+0.5F ) , cellScale) +voxelOffset; return p; } #endif /// /// Convert from voxel coordinates to world coordinates. /// (0,0,0) in voxel coordinates is a bottom corner of the bounding box. /// (1,0,0) is one voxel in the +X direction of that. /// public Vector3 VoxelToWorld (int x, int y, int z) { Vector3 p = Vector3.Scale( new Vector3( x, y, z ) , cellScale) +voxelOffset; return p; } /// /// Convert from voxel coordinates to world coordinates. /// (0,0,0) in voxel coordinates is a bottom corner of the bounding box. /// (1,0,0) is one voxel in the +X direction of that. /// public Int3 VoxelToWorldInt3 (Int3 voxelPosition) { var pos = voxelPosition * Int3.Precision; pos = new Int3(Mathf.RoundToInt(pos.x * cellScale.x), Mathf.RoundToInt(pos.y * cellScale.y), Mathf.RoundToInt(pos.z * cellScale.z)); return pos +(Int3)voxelOffset; } Vector3 ConvertPosWithoutOffset (int x, int y, int z) { Vector3 p = Vector3.Scale( new Vector3( x, y, (z/(float)voxelArea.width) ) , cellScale) +voxelOffset; return p; } Vector3 ConvertPosition (int x, int z, int i) { CompactVoxelSpan s = voxelArea.compactSpans[i]; return new Vector3(x*cellSize, s.y*cellHeight, (z/(float)voxelArea.width)*cellSize)+voxelOffset; } public void ErodeWalkableArea (int radius) { AstarProfiler.StartProfile("Erode Walkable Area"); ushort[] src = voxelArea.tmpUShortArr; if (src == null || src.Length < voxelArea.compactSpanCount) { src = voxelArea.tmpUShortArr = new ushort[voxelArea.compactSpanCount]; } // Set all elements in src to 0xffff Pathfinding.Util.Memory.MemSet(src, 0xffff, sizeof(ushort)); CalculateDistanceField(src); for (int i = 0; i < src.Length; i++) { //Note multiplied with 2 because the distance field increments distance by 2 for each voxel (and 3 for diagonal) if (src[i] < radius*2) { voxelArea.areaTypes[i] = UnwalkableArea; } } AstarProfiler.EndProfile("Erode Walkable Area"); } public void BuildDistanceField () { AstarProfiler.StartProfile("Build Distance Field"); ushort[] src = voxelArea.tmpUShortArr; if (src == null || src.Length < voxelArea.compactSpanCount) { src = voxelArea.tmpUShortArr = new ushort[voxelArea.compactSpanCount]; } // Set all elements in src to 0xffff Pathfinding.Util.Memory.MemSet(src, 0xffff, sizeof(ushort)); voxelArea.maxDistance = CalculateDistanceField(src); ushort[] dst = voxelArea.dist; if (dst == null || dst.Length < voxelArea.compactSpanCount) { dst = new ushort[voxelArea.compactSpanCount]; } dst = BoxBlur(src, dst); voxelArea.dist = dst; AstarProfiler.EndProfile("Build Distance Field"); } /// TODO: Complete the ErodeVoxels function translation [System.Obsolete("This function is not complete and should not be used")] public void ErodeVoxels (int radius) { if (radius > 255) { Debug.LogError("Max Erode Radius is 255"); radius = 255; } int wd = voxelArea.width*voxelArea.depth; int[] dist = new int[voxelArea.compactSpanCount]; for (int i = 0; i < dist.Length; i++) { dist[i] = 0xFF; } for (int z = 0; z < wd; z += voxelArea.width) { for (int x = 0; x < voxelArea.width; x++) { CompactVoxelCell c = voxelArea.compactCells[x+z]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; i++) { if (voxelArea.areaTypes[i] != UnwalkableArea) { CompactVoxelSpan s = voxelArea.compactSpans[i]; int nc = 0; for (int dir = 0; dir < 4; dir++) { if (s.GetConnection(dir) != NotConnected) nc++; } //At least one missing neighbour if (nc != 4) { dist[i] = 0; } } } } } //int nd = 0; //Pass 1 /*for (int z=0;z < wd;z += voxelArea.width) { * for (int x=0;x < voxelArea.width;x++) { * * CompactVoxelCell c = voxelArea.compactCells[x+z]; * * for (int i= (int)c.index, ci = (int)(c.index+c.count); i < ci; i++) { * CompactVoxelSpan s = voxelArea.compactSpans[i]; * * if (s.GetConnection (0) != NotConnected) { * // (-1,0) * int nx = x+voxelArea.DirectionX[0]; * int nz = z+voxelArea.DirectionZ[0]; * * int ni = (int)(voxelArea.compactCells[nx+nz].index+s.GetConnection (0)); * CompactVoxelSpan ns = voxelArea.compactSpans[ni]; * * if (dist[ni]+2 < dist[i]) { * dist[i] = (ushort)(dist[ni]+2); * } * * if (ns.GetConnection (3) != NotConnected) { * // (-1,0) + (0,-1) = (-1,-1) * int nnx = nx+voxelArea.DirectionX[3]; * int nnz = nz+voxelArea.DirectionZ[3]; * * int nni = (int)(voxelArea.compactCells[nnx+nnz].index+ns.GetConnection (3)); * * if (src[nni]+3 < src[i]) { * src[i] = (ushort)(src[nni]+3); * } * } * } * * if (s.GetConnection (3) != NotConnected) { * // (0,-1) * int nx = x+voxelArea.DirectionX[3]; * int nz = z+voxelArea.DirectionZ[3]; * * int ni = (int)(voxelArea.compactCells[nx+nz].index+s.GetConnection (3)); * * if (src[ni]+2 < src[i]) { * src[i] = (ushort)(src[ni]+2); * } * * CompactVoxelSpan ns = voxelArea.compactSpans[ni]; * * if (ns.GetConnection (2) != NotConnected) { * * // (0,-1) + (1,0) = (1,-1) * int nnx = nx+voxelArea.DirectionX[2]; * int nnz = nz+voxelArea.DirectionZ[2]; * * voxelOffset nni = (int)(voxelArea.compactCells[nnx+nnz].index+ns.GetConnection (2)); * * if (src[nni]+3 < src[i]) { * src[i] = (ushort)(src[nni]+3); * } * } * } * } * } * }*/ } public void FilterLowHeightSpans (uint voxelWalkableHeight, float cs, float ch) { int wd = voxelArea.width*voxelArea.depth; //Filter all ledges #if !ASTAR_RECAST_CLASS_BASED_LINKED_LIST LinkedVoxelSpan[] spans = voxelArea.linkedSpans; for (int z = 0, pz = 0; z < wd; z += voxelArea.width, pz++) { for (int x = 0; x < voxelArea.width; x++) { for (int s = z+x; s != -1 && spans[s].bottom != VoxelArea.InvalidSpanValue; s = spans[s].next) { uint bottom = spans[s].top; uint top = spans[s].next != -1 ? spans[spans[s].next].bottom : VoxelArea.MaxHeight; if (top - bottom < voxelWalkableHeight) { spans[s].area = UnwalkableArea; } } } } #else for (int z = 0, pz = 0; z < wd; z += voxelArea.width, pz++) { for (int x = 0; x < voxelArea.width; x++) { for (VoxelSpan s = voxelArea.cells[z+x].firstSpan; s != null; s = s.next) { uint bottom = s.top; uint top = s.next != null ? s.next.bottom : VoxelArea.MaxHeight; if (top - bottom < voxelWalkableHeight) { s.area = UnwalkableArea; } } } } #endif } //Code almost completely ripped from Recast public void FilterLedges (uint voxelWalkableHeight, int voxelWalkableClimb, float cs, float ch) { int wd = voxelArea.width*voxelArea.depth; #if !ASTAR_RECAST_CLASS_BASED_LINKED_LIST LinkedVoxelSpan[] spans = voxelArea.linkedSpans; int[] DirectionX = voxelArea.DirectionX; int[] DirectionZ = voxelArea.DirectionZ; #endif int width = voxelArea.width; //Filter all ledges for (int z = 0, pz = 0; z < wd; z += width, pz++) { for (int x = 0; x < width; x++) { #if !ASTAR_RECAST_CLASS_BASED_LINKED_LIST if (spans[x+z].bottom == VoxelArea.InvalidSpanValue) continue; for (int s = x+z; s != -1; s = spans[s].next) { //Skip non-walkable spans if (spans[s].area == UnwalkableArea) { continue; } int bottom = (int)spans[s].top; int top = spans[s].next != -1 ? (int)spans[spans[s].next].bottom : VoxelArea.MaxHeightInt; int minHeight = VoxelArea.MaxHeightInt; int aMinHeight = (int)spans[s].top; int aMaxHeight = aMinHeight; for (int d = 0; d < 4; d++) { int nx = x+DirectionX[d]; int nz = z+DirectionZ[d]; //Skip out-of-bounds points if (nx < 0 || nz < 0 || nz >= wd || nx >= width) { spans[s].area = UnwalkableArea; break; } int nsx = nx+nz; int nbottom = -voxelWalkableClimb; int ntop = spans[nsx].bottom != VoxelArea.InvalidSpanValue ? (int)spans[nsx].bottom : VoxelArea.MaxHeightInt; if (System.Math.Min(top, ntop) - System.Math.Max(bottom, nbottom) > voxelWalkableHeight) { minHeight = System.Math.Min(minHeight, nbottom - bottom); } //Loop through spans if (spans[nsx].bottom != VoxelArea.InvalidSpanValue) { for (int ns = nsx; ns != -1; ns = spans[ns].next) { nbottom = (int)spans[ns].top; ntop = spans[ns].next != -1 ? (int)spans[spans[ns].next].bottom : VoxelArea.MaxHeightInt; if (System.Math.Min(top, ntop) - System.Math.Max(bottom, nbottom) > voxelWalkableHeight) { minHeight = System.Math.Min(minHeight, nbottom - bottom); if (System.Math.Abs(nbottom - bottom) <= voxelWalkableClimb) { if (nbottom < aMinHeight) { aMinHeight = nbottom; } if (nbottom > aMaxHeight) { aMaxHeight = nbottom; } } } } } } if (minHeight < -voxelWalkableClimb || (aMaxHeight - aMinHeight) > voxelWalkableClimb) { spans[s].area = UnwalkableArea; } } #else for (VoxelSpan s = voxelArea.cells[z+x].firstSpan; s != null; s = s.next) { //Skip non-walkable spans if (s.area == UnwalkableArea) { continue; } int bottom = (int)s.top; int top = s.next != null ? (int)s.next.bottom : VoxelArea.MaxHeightInt; int minHeight = VoxelArea.MaxHeightInt; int aMinHeight = (int)s.top; int aMaxHeight = (int)s.top; for (int d = 0; d < 4; d++) { int nx = x+voxelArea.DirectionX[d]; int nz = z+voxelArea.DirectionZ[d]; //Skip out-of-bounds points if (nx < 0 || nz < 0 || nz >= wd || nx >= voxelArea.width) { s.area = UnwalkableArea; break; } VoxelSpan nsx = voxelArea.cells[nx+nz].firstSpan; int nbottom = -voxelWalkableClimb; int ntop = nsx != null ? (int)nsx.bottom : VoxelArea.MaxHeightInt; if (System.Math.Min(top, ntop) - System.Math.Max(bottom, nbottom) > voxelWalkableHeight) { minHeight = System.Math.Min(minHeight, nbottom - bottom); } //Loop through spans for (VoxelSpan ns = nsx; ns != null; ns = ns.next) { nbottom = (int)ns.top; ntop = ns.next != null ? (int)ns.next.bottom : VoxelArea.MaxHeightInt; if (System.Math.Min(top, ntop) - System.Math.Max(bottom, nbottom) > voxelWalkableHeight) { minHeight = System.Math.Min(minHeight, nbottom - bottom); if (System.Math.Abs(nbottom - bottom) <= voxelWalkableClimb) { if (nbottom < aMinHeight) { aMinHeight = nbottom; } if (nbottom > aMaxHeight) { aMaxHeight = nbottom; } } } } } if (minHeight < -voxelWalkableClimb || (aMaxHeight - aMinHeight) > voxelWalkableClimb) { s.area = UnwalkableArea; } } #endif } } } } }