/// @par /// /// This filter is usually applied after applying area id's using functions /// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea. /// /// @see rcCompactHeightfield public virtual bool medianFilterWalkableArea(Context ctx, CompactHeightfield chf) { int w = chf.width; int h = chf.height; ctx.startTimer("MEDIAN_AREA"); int[] areas = new int[chf.spanCount]; 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]; if (chf.areas[i] == RecastConstants.RC_NULL_AREA) { areas[i] = chf.areas[i]; continue; } int[] nei = new int[9]; for (int j = 0; j < 9; ++j) { nei[j] = chf.areas[i]; } for (int dir = 0; dir < 4; ++dir) { if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED) { int ax = x + RecastCommon.GetDirOffsetX(dir); int ay = y + RecastCommon.GetDirOffsetY(dir); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, dir); if (chf.areas[ai] != RecastConstants.RC_NULL_AREA) { nei[dir * 2 + 0] = chf.areas[ai]; } CompactSpan @as = chf.spans[ai]; int dir2 = (dir + 1) & 0x3; if (RecastCommon.GetCon(@as, dir2) != RecastConstants.RC_NOT_CONNECTED) { int ax2 = ax + RecastCommon.GetDirOffsetX(dir2); int ay2 = ay + RecastCommon.GetDirOffsetY(dir2); int ai2 = chf.cells[ax2 + ay2 * w].index + RecastCommon.GetCon(@as, dir2); if (chf.areas[ai2] != RecastConstants.RC_NULL_AREA) { nei[dir * 2 + 1] = chf.areas[ai2]; } } } } Array.Sort(nei); areas[i] = nei[4]; } } } chf.areas = areas; ctx.stopTimer("MEDIAN_AREA"); return(true); }
/// @par /// /// Basically, any spans that are closer to a boundary or obstruction than the specified radius /// are marked as unwalkable. /// /// This method is usually called immediately after the heightfield has been built. /// /// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius public static void erodeWalkableArea(Context ctx, int radius, CompactHeightfield chf) { int w = chf.width; int h = chf.height; ctx.startTimer("ERODE_AREA"); int[] dist = new int[chf.spanCount]; Arrays.fill(dist, 255); // Mark boundary cells. 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) { if (chf.areas[i] == RecastConstants.RC_NULL_AREA) { dist[i] = 0; } else { CompactSpan s = chf.spans[i]; int nc = 0; for (int dir = 0; dir < 4; ++dir) { if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED) { int nx = x + RecastCommon.GetDirOffsetX(dir); int ny = y + RecastCommon.GetDirOffsetY(dir); int nidx = chf.cells[nx + ny * w].index + RecastCommon.GetCon(s, dir); if (chf.areas[nidx] != RecastConstants.RC_NULL_AREA) { nc++; } } } // At least one missing neighbour. if (nc != 4) { dist[i] = 0; } } } } } int nd; // Pass 1 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]; if (RecastCommon.GetCon(s, 0) != RecastConstants.RC_NOT_CONNECTED) { // (-1,0) int ax = x + RecastCommon.GetDirOffsetX(0); int ay = y + RecastCommon.GetDirOffsetY(0); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 0); CompactSpan @as = chf.spans[ai]; nd = Math.Min(dist[ai] + 2, 255); if (nd < dist[i]) { dist[i] = nd; } // (-1,-1) if (RecastCommon.GetCon(@as, 3) != RecastConstants.RC_NOT_CONNECTED) { int aax = ax + RecastCommon.GetDirOffsetX(3); int aay = ay + RecastCommon.GetDirOffsetY(3); int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 3); nd = Math.Min(dist[aai] + 3, 255); if (nd < dist[i]) { dist[i] = nd; } } } if (RecastCommon.GetCon(s, 3) != RecastConstants.RC_NOT_CONNECTED) { // (0,-1) int ax = x + RecastCommon.GetDirOffsetX(3); int ay = y + RecastCommon.GetDirOffsetY(3); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 3); CompactSpan @as = chf.spans[ai]; nd = Math.Min(dist[ai] + 2, 255); if (nd < dist[i]) { dist[i] = nd; } // (1,-1) if (RecastCommon.GetCon(@as, 2) != RecastConstants.RC_NOT_CONNECTED) { int aax = ax + RecastCommon.GetDirOffsetX(2); int aay = ay + RecastCommon.GetDirOffsetY(2); int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 2); nd = Math.Min(dist[aai] + 3, 255); if (nd < dist[i]) { dist[i] = nd; } } } } } } // Pass 2 for (int y = h - 1; y >= 0; --y) { for (int x = w - 1; x >= 0; --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]; if (RecastCommon.GetCon(s, 2) != RecastConstants.RC_NOT_CONNECTED) { // (1,0) int ax = x + RecastCommon.GetDirOffsetX(2); int ay = y + RecastCommon.GetDirOffsetY(2); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 2); CompactSpan @as = chf.spans[ai]; nd = Math.Min(dist[ai] + 2, 255); if (nd < dist[i]) { dist[i] = nd; } // (1,1) if (RecastCommon.GetCon(@as, 1) != RecastConstants.RC_NOT_CONNECTED) { int aax = ax + RecastCommon.GetDirOffsetX(1); int aay = ay + RecastCommon.GetDirOffsetY(1); int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 1); nd = Math.Min(dist[aai] + 3, 255); if (nd < dist[i]) { dist[i] = nd; } } } if (RecastCommon.GetCon(s, 1) != RecastConstants.RC_NOT_CONNECTED) { // (0,1) int ax = x + RecastCommon.GetDirOffsetX(1); int ay = y + RecastCommon.GetDirOffsetY(1); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 1); CompactSpan @as = chf.spans[ai]; nd = Math.Min(dist[ai] + 2, 255); if (nd < dist[i]) { dist[i] = nd; } // (-1,1) if (RecastCommon.GetCon(@as, 0) != RecastConstants.RC_NOT_CONNECTED) { int aax = ax + RecastCommon.GetDirOffsetX(0); int aay = ay + RecastCommon.GetDirOffsetY(0); int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 0); nd = Math.Min(dist[aai] + 3, 255); if (nd < dist[i]) { dist[i] = nd; } } } } } } int thr = radius * 2; for (int i = 0; i < chf.spanCount; ++i) { if (dist[i] < thr) { chf.areas[i] = RecastConstants.RC_NULL_AREA; } } ctx.stopTimer("ERODE_AREA"); }
public static HeightfieldLayerSet buildHeightfieldLayers(Context ctx, CompactHeightfield chf, int borderSize, int walkableHeight) { ctx.startTimer("RC_TIMER_BUILD_LAYERS"); int w = chf.width; int h = chf.height; int[] srcReg = new int[chf.spanCount]; Arrays.fill(srcReg, 0xFF); int nsweeps = chf.width; // Math.max(chf.width, chf.height); SweepSpan[] sweeps = new SweepSpan[nsweeps]; for (int i = 0; i < sweeps.Length; i++) { sweeps[i] = new SweepSpan(); } // Partition walkable area into monotone regions. int[] prevCount = new int[256]; int regId = 0; // Sweep one line at a time. for (int y = borderSize; y < h - borderSize; ++y) { // Collect spans from this row. Arrays.fill(prevCount, 0, regId, 0); int sweepId = 0; for (int x = borderSize; x < w - borderSize; ++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]; if (chf.areas[i] == RecastConstants.RC_NULL_AREA) { continue; } int sid = 0xFF; // -x if (RecastCommon.GetCon(s, 0) != RecastConstants.RC_NOT_CONNECTED) { int ax = x + RecastCommon.GetDirOffsetX(0); int ay = y + RecastCommon.GetDirOffsetY(0); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 0); if (chf.areas[ai] != RecastConstants.RC_NULL_AREA && srcReg[ai] != 0xff) { sid = srcReg[ai]; } } if (sid == 0xff) { sid = sweepId++; sweeps[sid].nei = 0xff; sweeps[sid].ns = 0; } // -y if (RecastCommon.GetCon(s, 3) != RecastConstants.RC_NOT_CONNECTED) { int ax = x + RecastCommon.GetDirOffsetX(3); int ay = y + RecastCommon.GetDirOffsetY(3); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 3); int nr = srcReg[ai]; if (nr != 0xff) { // Set neighbour when first valid neighbour is // encoutered. if (sweeps[sid].ns == 0) { sweeps[sid].nei = nr; } if (sweeps[sid].nei == nr) { // Update existing neighbour sweeps[sid].ns++; prevCount[nr]++; } else { // This is hit if there is nore than one // neighbour. // Invalidate the neighbour. sweeps[sid].nei = 0xff; } } } srcReg[i] = sid; } } // Create unique ID. for (int i = 0; i < sweepId; ++i) { // If the neighbour is set and there is only one continuous // connection to it, // the sweep will be merged with the previous one, else new // region is created. if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns) { sweeps[i].id = sweeps[i].nei; } else { if (regId == 255) { throw new Exception("rcBuildHeightfieldLayers: Region ID overflow."); } sweeps[i].id = regId++; } } // Remap local sweep ids to region ids. for (int x = borderSize; x < w - borderSize; ++x) { CompactCell c = chf.cells[x + y * w]; for (int i = c.index, ni = c.index + c.count; i < ni; ++i) { if (srcReg[i] != 0xff) { srcReg[i] = sweeps[srcReg[i]].id; } } } } int nregs = regId; LayerRegion[] regs = new LayerRegion[nregs]; // Construct regions for (int i = 0; i < nregs; ++i) { regs[i] = new LayerRegion(i); } // Find region neighbours and overlapping regions. List <int> lregs = new List <int>(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { CompactCell c = chf.cells[x + y * w]; lregs.Clear(); for (int i = c.index, ni = c.index + c.count; i < ni; ++i) { CompactSpan s = chf.spans[i]; int ri = srcReg[i]; if (ri == 0xff) { continue; } regs[ri].ymin = Math.Min(regs[ri].ymin, s.y); regs[ri].ymax = Math.Max(regs[ri].ymax, s.y); // Collect all region layers. lregs.Add(ri); // Update neighbours for (int dir = 0; dir < 4; ++dir) { if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED) { int ax = x + RecastCommon.GetDirOffsetX(dir); int ay = y + RecastCommon.GetDirOffsetY(dir); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, dir); int rai = srcReg[ai]; if (rai != 0xff && rai != ri) { addUnique(regs[ri].neis, rai); } } } } // Update overlapping regions. for (int i = 0; i < lregs.Count - 1; ++i) { for (int j = i + 1; j < lregs.Count; ++j) { if (lregs[i] != lregs[j]) { LayerRegion ri = regs[lregs[i]]; LayerRegion rj = regs[lregs[j]]; addUnique(ri.layers, lregs[j]); addUnique(rj.layers, lregs[i]); } } } } } // Create 2D layers from regions. int layerId = 0; List <int> stack = new List <int>(); for (int i = 0; i < nregs; ++i) { LayerRegion root = regs[i]; // Skip already visited. if (root.layerId != 0xff) { continue; } // Start search. root.layerId = layerId; root.@base = true; stack.Add(i); while (stack.Count > 0) { // Pop front LayerRegion reg = regs[stack[0]]; stack.RemoveAt(0); foreach (int nei in reg.neis) { LayerRegion regn = regs[nei]; // Skip already visited. if (regn.layerId != 0xff) { continue; } // Skip if the neighbour is overlapping root region. if (contains(root.layers, nei)) { continue; } // Skip if the height range would become too large. int ymin = Math.Min(root.ymin, regn.ymin); int ymax = Math.Max(root.ymax, regn.ymax); if ((ymax - ymin) >= 255) { continue; } // Deepen stack.Add(nei); // Mark layer id regn.layerId = layerId; // Merge current layers to root. foreach (int layer in regn.layers) { addUnique(root.layers, layer); } root.ymin = Math.Min(root.ymin, regn.ymin); root.ymax = Math.Max(root.ymax, regn.ymax); } } layerId++; } // Merge non-overlapping regions that are close in height. int mergeHeight = walkableHeight * 4; for (int i = 0; i < nregs; ++i) { LayerRegion ri = regs[i]; if (!ri.@base) { continue; } int newId = ri.layerId; for (;;) { int oldId = 0xff; for (int j = 0; j < nregs; ++j) { if (i == j) { continue; } LayerRegion rj = regs[j]; if (!rj.@base) { continue; } // Skip if the regions are not close to each other. if (!overlapRange(ri.ymin, ri.ymax + mergeHeight, rj.ymin, rj.ymax + mergeHeight)) { continue; } // Skip if the height range would become too large. int ymin = Math.Min(ri.ymin, rj.ymin); int ymax = Math.Max(ri.ymax, rj.ymax); if ((ymax - ymin) >= 255) { continue; } // Make sure that there is no overlap when merging 'ri' and // 'rj'. bool overlap = false; // Iterate over all regions which have the same layerId as // 'rj' for (int k = 0; k < nregs; ++k) { if (regs[k].layerId != rj.layerId) { continue; } // Check if region 'k' is overlapping region 'ri' // Index to 'regs' is the same as region id. if (contains(ri.layers, k)) { overlap = true; break; } } // Cannot merge of regions overlap. if (overlap) { continue; } // Can merge i and j. oldId = rj.layerId; break; } // Could not find anything to merge with, stop. if (oldId == 0xff) { break; } // Merge for (int j = 0; j < nregs; ++j) { LayerRegion rj = regs[j]; if (rj.layerId == oldId) { rj.@base = false; // Remap layerIds. rj.layerId = newId; // Add overlaid layers from 'rj' to 'ri'. foreach (int layer in rj.layers) { addUnique(ri.layers, layer); } // Update height bounds. ri.ymin = Math.Min(ri.ymin, rj.ymin); ri.ymax = Math.Max(ri.ymax, rj.ymax); } } } } // Compact layerIds int[] remap = new int[256]; // Find number of unique layers. layerId = 0; for (int i = 0; i < nregs; ++i) { remap[regs[i].layerId] = 1; } for (int i = 0; i < 256; ++i) { if (remap[i] != 0) { remap[i] = layerId++; } else { remap[i] = 0xff; } } // Remap ids. for (int i = 0; i < nregs; ++i) { regs[i].layerId = remap[regs[i].layerId]; } // No layers, return empty. if (layerId == 0) { // ctx.stopTimer(RC_TIMER_BUILD_LAYERS); return(null); } // Create layers. // rcAssert(lset.layers == 0); int lw = w - borderSize * 2; int lh = h - borderSize * 2; // Build contracted bbox for layers. float[] bmin = new float[3]; float[] bmax = new float[3]; RecastVectors.copy(bmin, chf.bmin); RecastVectors.copy(bmax, chf.bmax); bmin[0] += borderSize * chf.cs; bmin[2] += borderSize * chf.cs; bmax[0] -= borderSize * chf.cs; bmax[2] -= borderSize * chf.cs; HeightfieldLayerSet lset = new HeightfieldLayerSet(); lset.layers = new HeightfieldLayer[layerId]; for (int i = 0; i < lset.layers.Length; i++) { lset.layers[i] = new HeightfieldLayer(); } // Store layers. for (int i = 0; i < lset.layers.Length; ++i) { int curId = i; HeightfieldLayer layer = lset.layers[i]; int gridSize = lw * lh; layer.heights = new int[gridSize]; Arrays.fill(layer.heights, 0xFF); layer.areas = new int[gridSize]; layer.cons = new int[gridSize]; // Find layer height bounds. int hmin = 0, hmax = 0; for (int j = 0; j < nregs; ++j) { if (regs[j].@base && regs[j].layerId == curId) { hmin = regs[j].ymin; hmax = regs[j].ymax; } } layer.width = lw; layer.height = lh; layer.cs = chf.cs; layer.ch = chf.ch; // Adjust the bbox to fit the heightfield. RecastVectors.copy(layer.bmin, bmin); RecastVectors.copy(layer.bmax, bmax); layer.bmin[1] = bmin[1] + hmin * chf.ch; layer.bmax[1] = bmin[1] + hmax * chf.ch; layer.hmin = hmin; layer.hmax = hmax; // Update usable data region. layer.minx = layer.width; layer.maxx = 0; layer.miny = layer.height; layer.maxy = 0; // Copy height and area from compact heightfield. for (int y = 0; y < lh; ++y) { for (int x = 0; x < lw; ++x) { int cx = borderSize + x; int cy = borderSize + y; CompactCell c = chf.cells[cx + cy * w]; for (int j = c.index, nj = c.index + c.count; j < nj; ++j) { CompactSpan s = chf.spans[j]; // Skip unassigned regions. if (srcReg[j] == 0xff) { continue; } // Skip of does nto belong to current layer. int lid = regs[srcReg[j]].layerId; if (lid != curId) { continue; } // Update data bounds. layer.minx = Math.Min(layer.minx, x); layer.maxx = Math.Max(layer.maxx, x); layer.miny = Math.Min(layer.miny, y); layer.maxy = Math.Max(layer.maxy, y); // Store height and area type. int idx = x + y * lw; layer.heights[idx] = (char)(s.y - hmin); layer.areas[idx] = chf.areas[j]; // Check connection. char portal = (char)0; char con = (char)0; for (int dir = 0; dir < 4; ++dir) { if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED) { int ax = cx + RecastCommon.GetDirOffsetX(dir); int ay = cy + RecastCommon.GetDirOffsetY(dir); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, dir); int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff; // Portal mask if (chf.areas[ai] != RecastConstants.RC_NULL_AREA && lid != alid) { portal |= (char)(1 << dir); // Update height so that it matches on both // sides of the portal. CompactSpan @as = chf.spans[ai]; if (@as.y > hmin) { layer.heights[idx] = Math.Max(layer.heights[idx], (char)(@as.y - hmin)); } } // Valid connection mask if (chf.areas[ai] != RecastConstants.RC_NULL_AREA && lid == alid) { int nx = ax - borderSize; int ny = ay - borderSize; if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) { con |= (char)(1 << dir); } } } } layer.cons[idx] = (portal << 4) | con; } } } if (layer.minx > layer.maxx) { layer.minx = layer.maxx = 0; } if (layer.miny > layer.maxy) { layer.miny = layer.maxy = 0; } } // ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return(lset); }
/// @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 /// /// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen /// parameters control how closely the simplified contours will match the raw contours. /// /// Simplified contours are generated such that the vertices for portals between areas match up. /// (They are considered mandatory vertices.) /// /// Setting @p maxEdgeLength to zero will disabled the edge length feature. /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig public static ContourSet buildContours(Context ctx, CompactHeightfield chf, float maxError, int maxEdgeLen, int buildFlags) { int w = chf.width; int h = chf.height; int borderSize = chf.borderSize; ContourSet cset = new ContourSet(); ctx.startTimer("BUILD_CONTOURS"); RecastVectors.copy(cset.bmin, chf.bmin, 0); RecastVectors.copy(cset.bmax, chf.bmax, 0); if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. float pad = borderSize * chf.cs; cset.bmin[0] += pad; cset.bmin[2] += pad; cset.bmax[0] -= pad; cset.bmax[2] -= pad; } cset.cs = chf.cs; cset.ch = chf.ch; cset.width = chf.width - chf.borderSize * 2; cset.height = chf.height - chf.borderSize * 2; cset.borderSize = chf.borderSize; int[] flags = new int[chf.spanCount]; ctx.startTimer("BUILD_CONTOURS_TRACE"); // Mark boundaries. 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) { int res = 0; CompactSpan s = chf.spans[i]; if (chf.spans[i].reg == 0 || (chf.spans[i].reg & RecastConstants.RC_BORDER_REG) != 0) { flags[i] = 0; continue; } for (int dir = 0; dir < 4; ++dir) { int r = 0; if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED) { int ax = x + RecastCommon.GetDirOffsetX(dir); int ay = y + RecastCommon.GetDirOffsetY(dir); int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, dir); r = chf.spans[ai].reg; } if (r == chf.spans[i].reg) { res |= (1 << dir); } } flags[i] = res ^ 0xf; // Inverse, mark non connected edges. } } } ctx.stopTimer("BUILD_CONTOURS_TRACE"); List <int> verts = new List <int>(256); List <int> simplified = new List <int>(64); 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) { if (flags[i] == 0 || flags[i] == 0xf) { flags[i] = 0; continue; } int reg = chf.spans[i].reg; if (reg == 0 || (reg & RecastConstants.RC_BORDER_REG) != 0) { continue; } int area = chf.areas[i]; verts.Clear(); simplified.Clear(); ctx.startTimer("BUILD_CONTOURS_TRACE"); walkContour(x, y, i, chf, flags, verts); ctx.stopTimer("BUILD_CONTOURS_TRACE"); ctx.startTimer("BUILD_CONTOURS_SIMPLIFY"); simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); removeDegenerateSegments(simplified); ctx.stopTimer("BUILD_CONTOURS_SIMPLIFY"); // Store region->contour remap info. // Create contour. if (simplified.Count / 4 >= 3) { Contour cont = new Contour(); cset.conts.Add(cont); cont.nverts = simplified.Count / 4; cont.verts = new int[simplified.Count]; for (int l = 0; l < cont.verts.Length; l++) { cont.verts[l] = simplified[l]; } if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. for (int j = 0; j < cont.nverts; ++j) { cont.verts[j * 4] -= borderSize; cont.verts[j * 4 + 2] -= borderSize; } } cont.nrverts = verts.Count / 4; cont.rverts = new int[verts.Count]; for (int l = 0; l < cont.rverts.Length; l++) { cont.rverts[l] = verts[l]; } if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. for (int j = 0; j < cont.nrverts; ++j) { cont.rverts[j * 4] -= borderSize; cont.rverts[j * 4 + 2] -= borderSize; } } cont.reg = reg; cont.area = area; } } } } // Merge holes if needed. if (cset.conts.Count > 0) { // Calculate winding of all polygons. int[] winding = new int[cset.conts.Count]; int nholes = 0; for (int i = 0; i < cset.conts.Count; ++i) { Contour cont = cset.conts[i]; // If the contour is wound backwards, it is a hole. winding[i] = calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0 ? -1 : 1; if (winding[i] < 0) { nholes++; } } if (nholes > 0) { // Collect outline contour and holes contours per region. // We assume that there is one outline and multiple holes. int nregions = chf.maxRegions + 1; ContourRegion[] regions = new ContourRegion[nregions]; for (int i = 0; i < nregions; i++) { regions[i] = new ContourRegion(); } for (int i = 0; i < cset.conts.Count; ++i) { Contour cont = cset.conts[i]; // Positively would contours are outlines, negative holes. if (winding[i] > 0) { if (regions[cont.reg].outline != null) { throw new Exception("rcBuildContours: Multiple outlines for region " + cont.reg + "."); } regions[cont.reg].outline = cont; } else { regions[cont.reg].nholes++; } } for (int i = 0; i < nregions; i++) { if (regions[i].nholes > 0) { regions[i].holes = new ContourHole[regions[i].nholes]; for (int nh = 0; nh < regions[i].nholes; nh++) { regions[i].holes[nh] = new ContourHole(); } regions[i].nholes = 0; } } for (int i = 0; i < cset.conts.Count; ++i) { Contour cont = cset.conts[i]; ContourRegion reg = regions[cont.reg]; if (winding[i] < 0) { reg.holes[reg.nholes++].contour = cont; } } // Finally merge each regions holes into the outline. for (int i = 0; i < nregions; i++) { ContourRegion reg = regions[i]; if (reg.nholes == 0) { continue; } if (reg.outline != null) { mergeRegionHoles(ctx, reg); } else { // The region does not have an outline. // This can happen if the contour becaomes selfoverlapping because of // too aggressive simplification settings. throw new Exception("rcBuildContours: Bad outline for region " + i + ", contour simplification is likely too aggressive."); } } } } ctx.stopTimer("BUILD_CONTOURS"); return(cset); }
private static int getCornerHeight(int x, int y, int i, int dir, CompactHeightfield chf, bool isBorderVertex) { CompactSpan s = chf.spans[i]; int ch = s.y; int dirp = (dir + 1) & 0x3; int[] regs = { 0, 0, 0, 0 }; // Combine region and area codes in order to prevent // border vertices which are in between two areas to be removed. regs[0] = chf.spans[i].reg | (chf.areas[i] << 16); if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED) { int ax = x + RecastCommon.GetDirOffsetX(dir); int ay = y + RecastCommon.GetDirOffsetY(dir); int ai = chf.cells[ax + ay * chf.width].index + RecastCommon.GetCon(s, dir); CompactSpan @as = chf.spans[ai]; ch = Math.Max(ch, @as.y); regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16); if (RecastCommon.GetCon(@as, dirp) != RecastConstants.RC_NOT_CONNECTED) { int ax2 = ax + RecastCommon.GetDirOffsetX(dirp); int ay2 = ay + RecastCommon.GetDirOffsetY(dirp); int ai2 = chf.cells[ax2 + ay2 * chf.width].index + RecastCommon.GetCon(@as, dirp); CompactSpan as2 = chf.spans[ai2]; ch = Math.Max(ch, as2.y); regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16); } } if (RecastCommon.GetCon(s, dirp) != RecastConstants.RC_NOT_CONNECTED) { int ax = x + RecastCommon.GetDirOffsetX(dirp); int ay = y + RecastCommon.GetDirOffsetY(dirp); int ai = chf.cells[ax + ay * chf.width].index + RecastCommon.GetCon(s, dirp); CompactSpan @as = chf.spans[ai]; ch = Math.Max(ch, @as.y); regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16); if (RecastCommon.GetCon(@as, dir) != RecastConstants.RC_NOT_CONNECTED) { int ax2 = ax + RecastCommon.GetDirOffsetX(dir); int ay2 = ay + RecastCommon.GetDirOffsetY(dir); int ai2 = chf.cells[ax2 + ay2 * chf.width].index + RecastCommon.GetCon(@as, dir); CompactSpan as2 = chf.spans[ai2]; ch = Math.Max(ch, as2.y); regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16); } } // Check if the vertex is special edge vertex, these vertices will be removed later. for (int j = 0; j < 4; ++j) { int a = j; int b = (j + 1) & 0x3; int c = (j + 2) & 0x3; int d = (j + 3) & 0x3; // The vertex is a border vertex there are two same exterior cells in a row, // followed by two interior cells and none of the regions are out of bounds. bool twoSameExts = (regs[a] & regs[b] & RecastConstants.RC_BORDER_REG) != 0 && regs[a] == regs[b]; bool twoInts = ((regs[c] | regs[d]) & RecastConstants.RC_BORDER_REG) == 0; bool intsSameArea = (regs[c] >> 16) == (regs[d] >> 16); bool noZeros = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0; if (twoSameExts && twoInts && intsSameArea && noZeros) { isBorderVertex = true; break; } } return(ch); }
private static void walkContour(int x, int y, int i, CompactHeightfield chf, int[] flags, List <int> points) { // Choose the first non-connected edge int dir = 0; while ((flags[i] & (1 << dir)) == 0) { dir++; } int startDir = dir; int starti = i; int area = chf.areas[i]; int iter = 0; while (++iter < 40000) { if ((flags[i] & (1 << dir)) != 0) { // Choose the edge corner bool isBorderVertex = false; bool isAreaBorder = false; int px = x; int py = getCornerHeight(x, y, i, dir, chf, isBorderVertex); int pz = y; switch (dir) { case 0: pz++; break; case 1: px++; pz++; break; case 2: px++; break; } int r = 0; CompactSpan s = chf.spans[i]; if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED) { int ax = x + RecastCommon.GetDirOffsetX(dir); int ay = y + RecastCommon.GetDirOffsetY(dir); int ai = chf.cells[ax + ay * chf.width].index + RecastCommon.GetCon(s, dir); r = chf.spans[ai].reg; if (area != chf.areas[ai]) { isAreaBorder = true; } } if (isBorderVertex) { r |= RecastConstants.RC_BORDER_VERTEX; } if (isAreaBorder) { r |= RecastConstants.RC_AREA_BORDER; } points.Add(px); points.Add(py); points.Add(pz); points.Add(r); flags[i] &= ~(1 << dir); // Remove visited edges dir = (dir + 1) & 0x3; // Rotate CW } else { int ni = -1; int nx = x + RecastCommon.GetDirOffsetX(dir); int ny = y + RecastCommon.GetDirOffsetY(dir); CompactSpan s = chf.spans[i]; if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED) { CompactCell nc = chf.cells[nx + ny * chf.width]; ni = nc.index + RecastCommon.GetCon(s, dir); } if (ni == -1) { // Should not happen. return; } x = nx; y = ny; i = ni; dir = (dir + 3) & 0x3; // Rotate CCW } if (starti == i && startDir == dir) { break; } } }
/// @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); }
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); } } }