internal static int getHeightFieldSpanCount(Context ctx, Heightfield hf) { int w = hf.width; int h = hf.height; int spanCount = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { for (Span s = hf.spans[x + y * w]; s != null; s = s.next) { if (s.area != RecastConstants.RC_NULL_AREA) { spanCount++; } } } } return(spanCount); }
/// @par /// /// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb /// from the current span's maximum. /// This method removes the impact of the overestimation of conservative voxelization /// so the resulting mesh will not have regions hanging in the air over ledges. /// /// A span is a ledge if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt> /// /// @see rcHeightfield, rcConfig public static void filterLedgeSpans(Context ctx, int walkableHeight, int walkableClimb, Heightfield solid) { ctx.startTimer("FILTER_BORDER"); int w = solid.width; int h = solid.height; int MAX_HEIGHT = 0xffff; // Mark border spans. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { for (Span s = solid.spans[x + y * w]; s != null; s = s.next) { // Skip non walkable spans. if (s.area == RecastConstants.RC_NULL_AREA) { continue; } int bot = (s.smax); int top = s.next != null ? s.next.smin : MAX_HEIGHT; // Find neighbours minimum height. int minh = MAX_HEIGHT; // Min and max height of accessible neighbours. int asmin = s.smax; int asmax = s.smax; for (int dir = 0; dir < 4; ++dir) { int dx = x + RecastCommon.GetDirOffsetX(dir); int dy = y + RecastCommon.GetDirOffsetY(dir); // Skip neighbours which are out of bounds. if (dx < 0 || dy < 0 || dx >= w || dy >= h) { minh = Math.Min(minh, -walkableClimb - bot); continue; } // From minus infinity to the first span. Span ns = solid.spans[dx + dy * w]; int nbot = -walkableClimb; int ntop = ns != null ? ns.smin : MAX_HEIGHT; // Skip neightbour if the gap between the spans is too small. if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight) { minh = Math.Min(minh, nbot - bot); } // Rest of the spans. for (ns = solid.spans[dx + dy * w]; ns != null; ns = ns.next) { nbot = ns.smax; ntop = ns.next != null ? ns.next.smin : MAX_HEIGHT; // Skip neightbour if the gap between the spans is too small. if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight) { minh = Math.Min(minh, nbot - bot); // Find min/max accessible neighbour height. if (Math.Abs(nbot - bot) <= walkableClimb) { if (nbot < asmin) { asmin = nbot; } if (nbot > asmax) { asmax = nbot; } } } } } // The current span is close to a ledge if the drop to any // neighbour span is less than the walkableClimb. if (minh < -walkableClimb) { s.area = RecastConstants.RC_NULL_AREA; } // If the difference between all neighbours is too large, // we are at steep slope, mark the span as ledge. if ((asmax - asmin) > walkableClimb) { s.area = RecastConstants.RC_NULL_AREA; } } } } ctx.stopTimer("FILTER_BORDER"); }
/// @par /// /// Allows the formation of walkable regions that will flow over low lying /// objects such as curbs, and up structures such as stairways. /// /// Two neighboring spans are walkable if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb</tt> /// /// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call /// #rcFilterLedgeSpans after calling this filter. /// /// @see rcHeightfield, rcConfig public static void filterLowHangingWalkableObstacles(Context ctx, int walkableClimb, Heightfield solid) { ctx.startTimer("FILTER_LOW_OBSTACLES"); int w = solid.width; int h = solid.height; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { Span ps = null; bool previousWalkable = false; int previousArea = RecastConstants.RC_NULL_AREA; for (Span s = solid.spans[x + y * w]; s != null; ps = s, s = s.next) { bool walkable = s.area != RecastConstants.RC_NULL_AREA; // If current span is not walkable, but there is walkable // span just below it, mark the span above it walkable too. if (!walkable && previousWalkable) { if (Math.Abs(s.smax - ps.smax) <= walkableClimb) { s.area = previousArea; } } // Copy walkable flag so that it cannot propagate // past multiple non-walkable objects. previousWalkable = walkable; previousArea = s.area; } } } ctx.stopTimer("FILTER_LOW_OBSTACLES"); }
/// @par /// /// For this filter, the clearance above the span is the distance from the span's /// maximum to the next higher span's minimum. (Same grid column.) /// /// @see rcHeightfield, rcConfig public static void filterWalkableLowHeightSpans(Context ctx, int walkableHeight, Heightfield solid) { ctx.startTimer("FILTER_WALKABLE"); int w = solid.width; int h = solid.height; int MAX_HEIGHT = 0xffff; // Remove walkable flag from spans which do not have enough // space above them for the agent to stand there. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { for (Span s = solid.spans[x + y * w]; s != null; s = s.next) { int bot = (s.smax); int top = s.next != null ? s.next.smin : MAX_HEIGHT; if ((top - bot) <= walkableHeight) { s.area = RecastConstants.RC_NULL_AREA; } } } } ctx.stopTimer("FILTER_WALKABLE"); }
private CompactHeightfield buildCompactHeightfield(InputGeom geom, RecastBuilderConfig bcfg, Context ctx) { RecastConfig cfg = bcfg.cfg; // // Step 2. Rasterize input polygon soup. // // Allocate voxel heightfield where we rasterize our input data to. Heightfield solid = new Heightfield(bcfg.width, bcfg.height, bcfg.bmin, bcfg.bmax, cfg.cs, cfg.ch); // Allocate array that can hold triangle area types. // If you have multiple meshes you need to process, allocate // and array which can hold the max number of triangles you need to // process. // Find triangles which are walkable based on their slope and rasterize // them. // If your input data is multiple meshes, you can transform them here, // calculate // the are type for each of the meshes and rasterize them. float[] verts = geom.Verts; bool tiled = cfg.tileSize > 0; int totaltris = 0; if (tiled) { ChunkyTriMesh chunkyMesh = geom.ChunkyMesh; float[] tbmin = new float[2]; float[] tbmax = new float[2]; tbmin[0] = bcfg.bmin[0]; tbmin[1] = bcfg.bmin[2]; tbmax[0] = bcfg.bmax[0]; tbmax[1] = bcfg.bmax[2]; List <ChunkyTriMeshNode> nodes = chunkyMesh.getChunksOverlappingRect(tbmin, tbmax); foreach (ChunkyTriMeshNode node in nodes) { int[] tris = node.tris; int ntris = tris.Length / 3; totaltris += ntris; int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris); RecastRasterization.rasterizeTriangles(ctx, verts, tris, m_triareas, ntris, solid, cfg.walkableClimb); } } else { int[] tris = geom.Tris; int ntris = tris.Length / 3; int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris); totaltris = ntris; RecastRasterization.rasterizeTriangles(ctx, verts, tris, m_triareas, ntris, solid, cfg.walkableClimb); } // // Step 3. Filter walkables surfaces. // // Once all geometry is rasterized, we do initial pass of filtering to // remove unwanted overhangs caused by the conservative rasterization // as well as filter spans where the character cannot possibly stand. RecastFilter.filterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, solid); RecastFilter.filterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, solid); RecastFilter.filterWalkableLowHeightSpans(ctx, cfg.walkableHeight, solid); // // Step 4. Partition walkable surface to simple regions. // // Compact the heightfield so that it is faster to handle from now on. // This will result more cache coherent data as well as the neighbours // between walkable cells will be calculated. CompactHeightfield chf = Recast.buildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, solid); // Erode the walkable area by agent radius. RecastArea.erodeWalkableArea(ctx, cfg.walkableRadius, chf); // (Optional) Mark areas. foreach (ConvexVolume vol in geom.ConvexVolumes) { RecastArea.markConvexPolyArea(ctx, vol.verts, vol.hmin, vol.hmax, vol.area, chf); } return(chf); }
/// @par /// /// This is just the beginning of the process of fully building a compact heightfield. /// Various filters may be applied, then the distance field and regions built. /// E.g: #rcBuildDistanceField and #rcBuildRegions /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig public static CompactHeightfield buildCompactHeightfield(Context ctx, int walkableHeight, int walkableClimb, Heightfield hf) { ctx.startTimer("BUILD_COMPACTHEIGHTFIELD"); CompactHeightfield chf = new CompactHeightfield(); int w = hf.width; int h = hf.height; int spanCount = getHeightFieldSpanCount(ctx, hf); // Fill in header. chf.width = w; chf.height = h; chf.spanCount = spanCount; chf.walkableHeight = walkableHeight; chf.walkableClimb = walkableClimb; chf.maxRegions = 0; RecastVectors.copy(chf.bmin, hf.bmin); RecastVectors.copy(chf.bmax, hf.bmax); chf.bmax[1] += walkableHeight * hf.ch; chf.cs = hf.cs; chf.ch = hf.ch; chf.cells = new CompactCell[w * h]; chf.spans = new CompactSpan[spanCount]; chf.areas = new int[spanCount]; int MAX_HEIGHT = 0xffff; for (int i = 0; i < chf.cells.Length; i++) { chf.cells[i] = new CompactCell(); } for (int i = 0; i < chf.spans.Length; i++) { chf.spans[i] = new CompactSpan(); } // Fill in cells and spans. int idx = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { Span s = hf.spans[x + y * w]; // If there are no spans at this cell, just leave the data to index=0, count=0. if (s == null) { continue; } CompactCell c = chf.cells[x + y * w]; c.index = idx; c.count = 0; while (s != null) { if (s.area != RecastConstants.RC_NULL_AREA) { int bot = s.smax; int top = s.next != null ? (int)s.next.smin : MAX_HEIGHT; chf.spans[idx].y = RecastCommon.clamp(bot, 0, 0xffff); chf.spans[idx].h = RecastCommon.clamp(top - bot, 0, 0xff); chf.areas[idx] = s.area; idx++; c.count++; } s = s.next; } } } // Find neighbour connections. int MAX_LAYERS = RecastConstants.RC_NOT_CONNECTED - 1; int tooHighNeighbour = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { CompactCell c = chf.cells[x + y * w]; for (int i = c.index, ni = c.index + c.count; i < ni; ++i) { CompactSpan s = chf.spans[i]; for (int dir = 0; dir < 4; ++dir) { RecastCommon.SetCon(s, dir, RecastConstants.RC_NOT_CONNECTED); int nx = x + RecastCommon.GetDirOffsetX(dir); int ny = y + RecastCommon.GetDirOffsetY(dir); // First check that the neighbour cell is in bounds. if (nx < 0 || ny < 0 || nx >= w || ny >= h) { continue; } // Iterate over all neighbour spans and check if any of the is // accessible from current cell. CompactCell nc = chf.cells[nx + ny * w]; for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k) { CompactSpan ns = chf.spans[k]; int bot = Math.Max(s.y, ns.y); int top = Math.Min(s.y + s.h, ns.y + ns.h); // Check that the gap between the spans is walkable, // and that the climb height between the gaps is not too high. if ((top - bot) >= walkableHeight && Math.Abs(ns.y - s.y) <= walkableClimb) { // Mark direction as walkable. int lidx = k - nc.index; if (lidx < 0 || lidx > MAX_LAYERS) { tooHighNeighbour = Math.Max(tooHighNeighbour, lidx); continue; } RecastCommon.SetCon(s, dir, lidx); break; } } } } } } if (tooHighNeighbour > MAX_LAYERS) { throw new Exception("rcBuildCompactHeightfield: Heightfield has too many layers " + tooHighNeighbour + " (max: " + MAX_LAYERS + ")"); } ctx.stopTimer("BUILD_COMPACTHEIGHTFIELD"); return(chf); }
/// <summary> /// The span addition can be set to favor flags. If the span is merged to another span and the new 'smax' is /// within 'flagMergeThr' units from the existing span, the span flags are merged. /// </summary> /// <seealso cref= Heightfield, Span. </seealso> private static void addSpan(Heightfield hf, int x, int y, int smin, int smax, int area, int flagMergeThr) { int idx = x + y * hf.width; Span s = new Span(); s.smin = smin; s.smax = smax; s.area = area; s.next = null; // Empty cell, add the first span. if (hf.spans[idx] == null) { hf.spans[idx] = s; return; } Span prev = null; Span cur = hf.spans[idx]; // Insert and merge spans. while (cur != null) { if (cur.smin > s.smax) { // Current span is further than the new span, break. break; } else if (cur.smax < s.smin) { // Current span is before the new span advance. prev = cur; cur = cur.next; } else { // Merge spans. if (cur.smin < s.smin) { s.smin = cur.smin; } if (cur.smax > s.smax) { s.smax = cur.smax; } // Merge flags. if (Math.Abs(s.smax - cur.smax) <= flagMergeThr) { s.area = Math.Max(s.area, cur.area); } // Remove current span. Span next = cur.next; if (prev != null) { prev.next = next; } else { hf.spans[idx] = next; } cur = next; } } // Insert new span. if (prev != null) { s.next = prev.next; prev.next = s; } else { s.next = hf.spans[idx]; hf.spans[idx] = s; } }
/// <summary> /// Spans will only be added for triangles that overlap the heightfield grid. /// </summary> /// <seealso cref= Heightfield </seealso> public static void rasterizeTriangles(Context ctx, float[] verts, int[] areas, int nt, Heightfield solid, int flagMergeThr) { ctx.startTimer("RASTERIZE_TRIANGLES"); float ics = 1.0f / solid.cs; float ich = 1.0f / solid.ch; // Rasterize triangles. for (int i = 0; i < nt; ++i) { int v0 = (i * 3 + 0); int v1 = (i * 3 + 1); int v2 = (i * 3 + 2); // Rasterize. rasterizeTri(verts, v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); } ctx.stopTimer("RASTERIZE_TRIANGLES"); }
/// <summary> /// No spans will be added if the triangle does not overlap the heightfield grid. /// </summary> /// <seealso cref= Heightfield </seealso> public static void rasterizeTriangle(Context ctx, float[] verts, int v0, int v1, int v2, int area, Heightfield solid, int flagMergeThr) { ctx.startTimer("RASTERIZE_TRIANGLES"); float ics = 1.0f / solid.cs; float ich = 1.0f / solid.ch; rasterizeTri(verts, v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); ctx.stopTimer("RASTERIZE_TRIANGLES"); }
private static void rasterizeTri(float[] verts, int v0, int v1, int v2, int area, Heightfield hf, float[] bmin, float[] bmax, float cs, float ics, float ich, int flagMergeThr) { int w = hf.width; int h = hf.height; float[] tmin = new float[3]; float[] tmax = new float[3]; float by = bmax[1] - bmin[1]; // Calculate the bounding box of the triangle. RecastVectors.copy(tmin, verts, v0 * 3); RecastVectors.copy(tmax, verts, v0 * 3); RecastVectors.min(tmin, verts, v1 * 3); RecastVectors.min(tmin, verts, v2 * 3); RecastVectors.max(tmax, verts, v1 * 3); RecastVectors.max(tmax, verts, v2 * 3); // If the triangle does not touch the bbox of the heightfield, skip the triagle. if (!overlapBounds(bmin, bmax, tmin, tmax)) { return; } // Calculate the footprint of the triangle on the grid's y-axis int y0 = (int)((tmin[2] - bmin[2]) * ics); int y1 = (int)((tmax[2] - bmin[2]) * ics); y0 = RecastCommon.clamp(y0, 0, h - 1); y1 = RecastCommon.clamp(y1, 0, h - 1); // Clip the triangle into all grid cells it touches. float[] buf = new float[7 * 3 * 4]; int @in = 0; int inrow = 7 * 3; int p1 = inrow + 7 * 3; int p2 = p1 + 7 * 3; RecastVectors.copy(buf, 0, verts, v0 * 3); RecastVectors.copy(buf, 3, verts, v1 * 3); RecastVectors.copy(buf, 6, verts, v2 * 3); int nvrow, nvIn = 3; for (int y = y0; y <= y1; ++y) { // Clip polygon to row. Store the remaining polygon as well float cz = bmin[2] + y * cs; int[] nvrowin = dividePoly(buf, @in, nvIn, inrow, p1, cz + cs, 2); nvrow = nvrowin[0]; nvIn = nvrowin[1]; { int temp = @in; @in = p1; p1 = temp; } if (nvrow < 3) { continue; } // find the horizontal bounds in the row float minX = buf[inrow], maxX = buf[inrow]; for (int i = 1; i < nvrow; ++i) { if (minX > buf[inrow + i * 3]) { minX = buf[inrow + i * 3]; } if (maxX < buf[inrow + i * 3]) { maxX = buf[inrow + i * 3]; } } int x0 = (int)((minX - bmin[0]) * ics); int x1 = (int)((maxX - bmin[0]) * ics); x0 = RecastCommon.clamp(x0, 0, w - 1); x1 = RecastCommon.clamp(x1, 0, w - 1); int nv, nv2 = nvrow; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. store the remaining polygon as well float cx = bmin[0] + x * cs; int[] nvnv2 = dividePoly(buf, inrow, nv2, p1, p2, cx + cs, 0); nv = nvnv2[0]; nv2 = nvnv2[1]; { int temp = inrow; inrow = p2; p2 = temp; } if (nv < 3) { continue; } // Calculate min and max of the span. float smin = buf[p1 + 1], smax = buf[p1 + 1]; for (int i = 1; i < nv; ++i) { smin = Math.Min(smin, buf[p1 + i * 3 + 1]); smax = Math.Max(smax, buf[p1 + i * 3 + 1]); } smin -= bmin[1]; smax -= bmin[1]; // Skip the span if it is outside the heightfield bbox if (smax < 0.0f) { continue; } if (smin > by) { continue; } // Clamp the span to the heightfield bbox. if (smin < 0.0f) { smin = 0; } if (smax > by) { smax = by; } // Snap the span to the heightfield height grid. int ismin = RecastCommon.clamp((int)Math.Floor(smin * ich), 0, RecastConstants.RC_SPAN_MAX_HEIGHT); int ismax = RecastCommon.clamp((int)Math.Ceiling(smax * ich), ismin + 1, RecastConstants.RC_SPAN_MAX_HEIGHT); addSpan(hf, x, y, ismin, ismax, area, flagMergeThr); } } }