static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr, unsigned short* src, unsigned short* dst) { const int w = chf.width; const int h = chf.height; thr *= 2; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; const unsigned short cd = src[i]; if (cd <= thr) { dst[i] = cd; continue; } int d = (int)cd; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); d += (int)src[ai]; const rcCompactSpan& as = chf.spans[ai]; const int dir2 = (dir+1) & 0x3; if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) { const int ax2 = ax + rcGetDirOffsetX(dir2); const int ay2 = ay + rcGetDirOffsetY(dir2); const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); d += (int)src[ai2]; } else { d += cd; } } else { d += cd*2; } } dst[i] = (unsigned short)((d+5)/9); } } } return dst; }
static bool isSolidEdge(rcCompactHeightfield& chf, unsigned short* srcReg, int x, int y, int i, int dir) { const rcCompactSpan& s = chf.spans[i]; unsigned short r = 0; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); r = srcReg[ai]; } if (r == srcReg[i]) return false; return true; }
static bool CollectLayerRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, unsigned short* srcReg, rcLayerRegionMonotone*& regs, int& nregs) { const int w = chf.width; const int h = chf.height; const int nsweeps = chf.width; rcScopedDelete<rcLayerSweepSpan> sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP); if (!sweeps) { ctx->log(RC_LOG_ERROR, "CollectLayerRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps); return false; } // Partition walkable area into monotone regions. rcIntArray prev(256); unsigned short regId = 0; for (int y = borderSize; y < h-borderSize; ++y) { prev.resize(regId+1); memset(&prev[0],0,sizeof(int)*regId); unsigned short sweepId = 0; for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; unsigned short sid = 0xffff; // -x if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(0); const int ay = y + rcGetDirOffsetY(0); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xffff) sid = srcReg[ai]; } if (sid == 0xffff) { sid = sweepId++; sweeps[sid].nei = 0xffff; sweeps[sid].ns = 0; } // -y if (rcGetCon(s,3) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(3); const int ay = y + rcGetDirOffsetY(3); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); const unsigned short nr = srcReg[ai]; if (nr != 0xffff) { // 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++; prev[nr]++; } else { // This is hit if there is nore than one neighbour. // Invalidate the neighbour. sweeps[sid].nei = 0xffff; } } } 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 != 0xffff && prev[sweeps[i].nei] == sweeps[i].ns) { sweeps[i].id = sweeps[i].nei; } else { sweeps[i].id = regId++; } } // Remap local sweep ids to region ids. for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (srcReg[i] != 0xffff) srcReg[i] = sweeps[srcReg[i]].id; } } } // Allocate and init layer regions. nregs = (int)regId; regs = (rcLayerRegionMonotone*)rcAlloc(sizeof(rcLayerRegionMonotone)*nregs, RC_ALLOC_TEMP); if (!regs) { ctx->log(RC_LOG_ERROR, "CollectLayerRegionsMonotone: Out of memory 'regs' (%d).", nregs); return false; } memset(regs, 0, sizeof(rcLayerRegionMonotone)*nregs); for (int i = 0; i < nregs; ++i) { regs[i].layerId = 0xffff; regs[i].ymin = 0xffff; regs[i].ymax = 0; } rcIntArray lregs(64); // Find region neighbours and overlapping regions. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; lregs.resize(0); for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; const unsigned short ri = srcReg[i]; if (ri == 0xffff) continue; regs[ri].ymin = rcMin(regs[ri].ymin, s.y); regs[ri].ymax = rcMax(regs[ri].ymax, s.y); // Collect all region layers. lregs.push(ri); // Update neighbours for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); const unsigned short rai = srcReg[ai]; if (rai != 0xffff && rai != ri) addUnique(regs[ri].neis, rai); } } } // Update overlapping regions. const int nlregs = lregs.size(); for (int i = 0; i < nlregs-1; ++i) { for (int j = i+1; j < nlregs; ++j) { if (lregs[i] != lregs[j]) { rcLayerRegionMonotone& ri = regs[lregs[i]]; rcLayerRegionMonotone& rj = regs[lregs[j]]; addUnique(ri.layers, lregs[j]); addUnique(rj.layers, lregs[i]); } } } } } return true; }
static void getHeightData(rcContext* ctx, const rcCompactHeightfield& chf, const unsigned short* poly, const int npoly, const unsigned short* verts, const int bs, rcHeightPatch& hp, rcIntArray& queue, int region) { // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. queue.resize(0); // Set all heights to RC_UNSET_HEIGHT. memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); bool empty = true; // We cannot sample from this poly if it was created from polys // of different regions. If it was then it could potentially be overlapping // with polys of that region and the heights sampled here could be wrong. if (region != RC_MULTIPLE_REGS) { // Copy the height from the same region, and mark region borders // as seed points to fill the rest. for (int hy = 0; hy < hp.height; hy++) { int y = hp.ymin + hy + bs; for (int hx = 0; hx < hp.width; hx++) { int x = hp.xmin + hx + bs; const rcCompactCell& c = chf.cells[x + y*chf.width]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (s.reg == region) { // Store height hp.data[hx + hy*hp.width] = s.y; empty = false; // If any of the neighbours is not in same region, // add the current location as flood fill start bool border = false; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(s, dir); const rcCompactSpan& as = chf.spans[ai]; if (as.reg != region) { border = true; break; } } } if (border) push3(queue, x, y, i); break; } } } } } // if the polygon does not contain any points from the current region (rare, but happens) // or if it could potentially be overlapping polygons of the same region, // then use the center as the seed point. if (empty) seedArrayWithPolyCenter(ctx, chf, poly, npoly, verts, bs, hp, queue); static const int RETRACT_SIZE = 256; int head = 0; // We assume the seed is centered in the polygon, so a BFS to collect // height data will ensure we do not move onto overlapping polygons and // sample wrong heights. while (head*3 < queue.size()) { int cx = queue[head*3+0]; int cy = queue[head*3+1]; int ci = queue[head*3+2]; head++; if (head >= RETRACT_SIZE) { head = 0; if (queue.size() > RETRACT_SIZE*3) memmove(&queue[0], &queue[RETRACT_SIZE*3], sizeof(int)*(queue.size()-RETRACT_SIZE*3)); queue.resize(queue.size()-RETRACT_SIZE*3); } const rcCompactSpan& cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int hx = ax - hp.xmin - bs; const int hy = ay - hp.ymin - bs; if ((unsigned int)hx >= (unsigned int)hp.width || (unsigned int)hy >= (unsigned int)hp.height) continue; if (hp.data[hx + hy*hp.width] != RC_UNSET_HEIGHT) continue; const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir); const rcCompactSpan& as = chf.spans[ai]; hp.data[hx + hy*hp.width] = as.y; push3(queue, ax, ay, ai); } } }
static unsigned short* expandRegions(int maxIter, unsigned short level, rcCompactHeightfield& chf, unsigned short* srcReg, unsigned short* srcDist, unsigned short* dstReg, unsigned short* dstDist, rcIntArray& stack) { const int w = chf.width; const int h = chf.height; // Find cells revealed by the raised level. stack.resize(0); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) { stack.push(x); stack.push(y); stack.push(i); } } } } int iter = 0; while (stack.size() > 0) { int failed = 0; memcpy(dstReg, srcReg, sizeof(unsigned short)*chf.spanCount); memcpy(dstDist, srcDist, sizeof(unsigned short)*chf.spanCount); for (int j = 0; j < stack.size(); j += 3) { int x = stack[j+0]; int y = stack[j+1]; int i = stack[j+2]; if (i < 0) { failed++; continue; } unsigned short r = srcReg[i]; unsigned short d2 = 0xffff; const unsigned char area = chf.areas[i]; const rcCompactSpan& s = chf.spans[i]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue; const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); if (chf.areas[ai] != area) continue; if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0) { if ((int)srcDist[ai]+2 < (int)d2) { r = srcReg[ai]; d2 = srcDist[ai]+2; } } } if (r) { stack[j+2] = -1; // mark as used dstReg[i] = r; dstDist[i] = d2; } else { failed++; } } // rcSwap source and dest. rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); if (failed*3 == stack.size()) break; if (level > 0) { ++iter; if (iter >= maxIter) break; } } return srcReg; }
static bool floodRegion(int x, int y, int i, unsigned short level, unsigned short r, rcCompactHeightfield& chf, unsigned short* srcReg, unsigned short* srcDist, rcIntArray& stack) { const int w = chf.width; const unsigned char area = chf.areas[i]; // Flood fill mark region. stack.resize(0); stack.push((int)x); stack.push((int)y); stack.push((int)i); srcReg[i] = r; srcDist[i] = 0; unsigned short lev = level >= 2 ? level-2 : 0; int count = 0; while (stack.size() > 0) { int ci = stack.pop(); int cy = stack.pop(); int cx = stack.pop(); const rcCompactSpan& cs = chf.spans[ci]; // Check if any of the neighbours already have a valid region set. unsigned short ar = 0; for (int dir = 0; dir < 4; ++dir) { // 8 connected if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); if (chf.areas[ai] != area) continue; unsigned short nr = srcReg[ai]; if (nr & RC_BORDER_REG) // Do not take borders into account. continue; if (nr != 0 && nr != r) ar = nr; const rcCompactSpan& as = chf.spans[ai]; const int dir2 = (dir+1) & 0x3; if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) { const int ax2 = ax + rcGetDirOffsetX(dir2); const int ay2 = ay + rcGetDirOffsetY(dir2); const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); if (chf.areas[ai2] != area) continue; unsigned short nr2 = srcReg[ai2]; if (nr2 != 0 && nr2 != r) ar = nr2; } } } if (ar != 0) { srcReg[ci] = 0; continue; } count++; // Expand neighbours. for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); if (chf.areas[ai] != area) continue; if (chf.dist[ai] >= lev && srcReg[ai] == 0) { srcReg[ai] = r; srcDist[ai] = 0; stack.push(ax); stack.push(ay); stack.push(ai); } } } } return count > 0; }
/// @par /// /// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. /// Contours will form simple polygons. /// /// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be /// re-assigned to the zero (null) region. /// /// Partitioning can result in smaller than necessary regions. @p mergeRegionArea helps /// reduce unecessarily small regions. /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// The region data will be available via the rcCompactHeightfield::maxRegions /// and rcCompactSpan::reg fields. /// /// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. /// /// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int minRegionArea, const int mergeRegionArea) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_REGIONS); const int w = chf.width; const int h = chf.height; unsigned short id = 1; rcScopedDelete<unsigned short> srcReg = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); if (!srcReg) { ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount); return false; } memset(srcReg,0,sizeof(unsigned short)*chf.spanCount); const int nsweeps = rcMax(chf.width,chf.height); rcScopedDelete<rcSweepSpan> sweeps = (rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan)*nsweeps, RC_ALLOC_TEMP); if (!sweeps) { ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps); return false; } // Mark border regions. if (borderSize > 0) { // Make sure border will not overflow. const int bw = rcMin(w, borderSize); const int bh = rcMin(h, borderSize); // Paint regions paintRectRegion(0, bw, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++; chf.borderSize = borderSize; } rcIntArray prev(256); // Sweep one line at a time. for (int y = borderSize; y < h-borderSize; ++y) { // Collect spans from this row. prev.resize(id+1); memset(&prev[0],0,sizeof(int)*id); unsigned short rid = 1; for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; // -x unsigned short previd = 0; if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(0); const int ay = y + rcGetDirOffsetY(0); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) previd = srcReg[ai]; } if (!previd) { previd = rid++; sweeps[previd].rid = previd; sweeps[previd].ns = 0; sweeps[previd].nei = 0; } // -y if (rcGetCon(s,3) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(3); const int ay = y + rcGetDirOffsetY(3); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); if (srcReg[ai] && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) { unsigned short nr = srcReg[ai]; if (!sweeps[previd].nei || sweeps[previd].nei == nr) { sweeps[previd].nei = nr; sweeps[previd].ns++; prev[nr]++; } else { sweeps[previd].nei = RC_NULL_NEI; } } } srcReg[i] = previd; } } // Create unique ID. for (int i = 1; i < rid; ++i) { if (sweeps[i].nei != RC_NULL_NEI && sweeps[i].nei != 0 && prev[sweeps[i].nei] == (int)sweeps[i].ns) { sweeps[i].id = sweeps[i].nei; } else { sweeps[i].id = id++; } } // Remap IDs for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (srcReg[i] > 0 && srcReg[i] < rid) srcReg[i] = sweeps[srcReg[i]].id; } } } ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); // Filter out small regions. chf.maxRegions = id; if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) return false; ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); // Store the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; ctx->stopTimer(RC_TIMER_BUILD_REGIONS); return true; }
bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf) { rcAssert(ctx); const int w = chf.width; const int h = chf.height; ctx->startTimer(RC_TIMER_MEDIAN_AREA); unsigned char* areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); if (!areas) { ctx->log(RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' (%d).", chf.spanCount); return false; } // Init distance. memset(areas, 0xff, sizeof(unsigned char)*chf.spanCount); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) { areas[i] = chf.areas[i]; continue; } unsigned char nei[9]; for (int j = 0; j < 9; ++j) nei[j] = chf.areas[i]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); if (chf.areas[ai] != RC_NULL_AREA) nei[dir*2+0] = chf.areas[ai]; const rcCompactSpan& as = chf.spans[ai]; const int dir2 = (dir+1) & 0x3; if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) { const int ax2 = ax + rcGetDirOffsetX(dir2); const int ay2 = ay + rcGetDirOffsetY(dir2); const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); if (chf.areas[ai2] != RC_NULL_AREA) nei[dir*2+1] = chf.areas[ai2]; } } } insertSort(nei, 9); areas[i] = nei[4]; } } } memcpy(chf.areas, areas, sizeof(unsigned char)*chf.spanCount); rcFree(areas); ctx->stopTimer(RC_TIMER_MEDIAN_AREA); return true; }
static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf, const unsigned short* poly, const int npoly, const unsigned short* verts, const int bs, rcHeightPatch& hp, rcIntArray& stack) { // Floodfill the heightfield to get 2D height data, // starting at vertex locations as seeds. // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); stack.resize(0); static const int offset[9*2] = { 0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, }; // Use poly vertices as seed points for the flood fill. for (int j = 0; j < npoly; ++j) { int cx = 0, cz = 0, ci =-1; int dmin = RC_UNSET_HEIGHT; for (int k = 0; k < 9; ++k) { const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0]; const int ay = (int)verts[poly[j]*3+1]; const int az = (int)verts[poly[j]*3+2] + offset[k*2+1]; if (ax < hp.xmin || ax >= hp.xmin+hp.width || az < hp.ymin || az >= hp.ymin+hp.height) continue; const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; int d = rcAbs(ay - (int)s.y); if (d < dmin) { cx = ax; cz = az; ci = i; dmin = d; } } } if (ci != -1) { stack.push(cx); stack.push(cz); stack.push(ci); } } // Find center of the polygon using flood fill. int pcx = 0, pcz = 0; for (int j = 0; j < npoly; ++j) { pcx += (int)verts[poly[j]*3+0]; pcz += (int)verts[poly[j]*3+2]; } pcx /= npoly; pcz /= npoly; for (int i = 0; i < stack.size(); i += 3) { int cx = stack[i+0]; int cy = stack[i+1]; int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; hp.data[idx] = 1; } while (stack.size() > 0) { int ci = stack.pop(); int cy = stack.pop(); int cx = stack.pop(); // Check if close to center of the polygon. if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1) { stack.resize(0); stack.push(cx); stack.push(cy); stack.push(ci); break; } const rcCompactSpan& cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || ay < hp.ymin || ay >= (hp.ymin+hp.height)) continue; if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) continue; const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; hp.data[idx] = 1; stack.push(ax); stack.push(ay); stack.push(ai); } } memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); // Mark start locations. for (int i = 0; i < stack.size(); i += 3) { int cx = stack[i+0]; int cy = stack[i+1]; int ci = stack[i+2]; int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; const rcCompactSpan& cs = chf.spans[ci]; hp.data[idx] = cs.y; // getHeightData seeds are given in coordinates with borders stack[i+0] += bs; stack[i+1] += bs; } }
static void walkContour(int x, int y, int i, rcCompactHeightfield& chf, unsigned char* flags, rcIntArray& points) { // Choose the first non-connected edge unsigned char dir = 0; while ((flags[i] & (1 << dir)) == 0) dir++; unsigned char startDir = dir; int starti = i; const navAreaMask area = chf.areaMasks[ i ]; int iter = 0; while (++iter < 40000) { if (flags[i] & (1 << dir)) { // 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; const rcCompactSpan& s = chf.spans[i]; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); r = (int)chf.spans[ai].regionID; if (area != chf.areaMasks[ai]) isAreaBorder = true; } if (isBorderVertex) r |= RC_BORDER_VERTEX; if (isAreaBorder) r |= RC_AREA_BORDER; points.push(px); points.push(py); points.push(pz); points.push(r); flags[i] &= ~(1 << dir); // Remove visited edges dir = (dir+1) & 0x3; // Rotate CW } else { int ni = -1; const int nx = x + rcGetDirOffsetX(dir); const int ny = y + rcGetDirOffsetY(dir); const rcCompactSpan& s = chf.spans[i]; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const rcCompactCell& nc = chf.cells[nx+ny*chf.width]; ni = (int)nc.index + rcGetCon(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; } } }
static bool floodRegion(int x, int y, int i, unsigned short level, unsigned short minLevel, unsigned short r, rcCompactHeightfield& chf, unsigned short* src, rcIntArray& stack) { const int w = chf.width; // Flood fill mark region. stack.resize(0); stack.push((int)x); stack.push((int)y); stack.push((int)i); src[i*2] = r; src[i*2+1] = 0; unsigned short lev = level >= minLevel+2 ? level-2 : minLevel; int count = 0; while (stack.size() > 0) { int ci = stack.pop(); int cy = stack.pop(); int cx = stack.pop(); const rcCompactSpan& cs = chf.spans[ci]; // Check if any of the neighbours already have a valid region set. unsigned short ar = 0; for (int dir = 0; dir < 4; ++dir) { // 8 connected if (rcGetCon(cs, dir) != 0xf) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); unsigned short nr = src[ai*2]; if (nr != 0 && nr != r) ar = nr; const rcCompactSpan& as = chf.spans[ai]; const int dir2 = (dir+1) & 0x3; if (rcGetCon(as, dir2) != 0xf) { const int ax2 = ax + rcGetDirOffsetX(dir2); const int ay2 = ay + rcGetDirOffsetY(dir2); const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); unsigned short nr = src[ai2*2]; if (nr != 0 && nr != r) ar = nr; } } } if (ar != 0) { src[ci*2] = 0; continue; } count++; // Expand neighbours. for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) != 0xf) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); if (chf.spans[ai].dist >= lev) { if (src[ai*2] == 0) { src[ai*2] = r; src[ai*2+1] = 0; stack.push(ax); stack.push(ay); stack.push(ai); } } } } } return count > 0; }
bool rcErodeArea(unsigned char areaId, int radius, rcCompactHeightfield& chf) { const int w = chf.width; const int h = chf.height; rcTimeVal startTime = rcGetPerformanceTimer(); unsigned char* dist = new unsigned char[chf.spanCount]; if (!dist) return false; // Init distance. memset(dist, 0xff, sizeof(unsigned char)*chf.spanCount); // Mark boundary cells. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (chf.areas[i] != RC_NULL_AREA) { const rcCompactSpan& s = chf.spans[i]; int nc = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != 0xf) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); if (chf.areas[ai] == areaId) nc++; } } // At least one missing neighbour. if (nc != 4) dist[i] = 0; } } } } unsigned char nd; // Pass 1 for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (rcGetCon(s, 0) != 0xf) { // (-1,0) const int ax = x + rcGetDirOffsetX(0); const int ay = y + rcGetDirOffsetY(0); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); const rcCompactSpan& as = chf.spans[ai]; nd = (unsigned char)rcMin((int)dist[ai]+2, 255); if (nd < dist[i]) dist[i] = nd; // (-1,-1) if (rcGetCon(as, 3) != 0xf) { const int aax = ax + rcGetDirOffsetX(3); const int aay = ay + rcGetDirOffsetY(3); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3); nd = (unsigned char)rcMin((int)dist[aai]+3, 255); if (nd < dist[i]) dist[i] = nd; } } if (rcGetCon(s, 3) != 0xf) { // (0,-1) const int ax = x + rcGetDirOffsetX(3); const int ay = y + rcGetDirOffsetY(3); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); const rcCompactSpan& as = chf.spans[ai]; nd = (unsigned char)rcMin((int)dist[ai]+2, 255); if (nd < dist[i]) dist[i] = nd; // (1,-1) if (rcGetCon(as, 2) != 0xf) { const int aax = ax + rcGetDirOffsetX(2); const int aay = ay + rcGetDirOffsetY(2); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2); nd = (unsigned char)rcMin((int)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) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (rcGetCon(s, 2) != 0xf) { // (1,0) const int ax = x + rcGetDirOffsetX(2); const int ay = y + rcGetDirOffsetY(2); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); const rcCompactSpan& as = chf.spans[ai]; nd = (unsigned char)rcMin((int)dist[ai]+2, 255); if (nd < dist[i]) dist[i] = nd; // (1,1) if (rcGetCon(as, 1) != 0xf) { const int aax = ax + rcGetDirOffsetX(1); const int aay = ay + rcGetDirOffsetY(1); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1); nd = (unsigned char)rcMin((int)dist[aai]+3, 255); if (nd < dist[i]) dist[i] = nd; } } if (rcGetCon(s, 1) != 0xf) { // (0,1) const int ax = x + rcGetDirOffsetX(1); const int ay = y + rcGetDirOffsetY(1); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); const rcCompactSpan& as = chf.spans[ai]; nd = (unsigned char)rcMin((int)dist[ai]+2, 255); if (nd < dist[i]) dist[i] = nd; // (-1,1) if (rcGetCon(as, 0) != 0xf) { const int aax = ax + rcGetDirOffsetX(0); const int aay = ay + rcGetDirOffsetY(0); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0); nd = (unsigned char)rcMin((int)dist[aai]+3, 255); if (nd < dist[i]) dist[i] = nd; } } } } } const unsigned char thr = (unsigned char)(radius*2); for (int i = 0; i < chf.spanCount; ++i) if (dist[i] < thr) chf.areas[i] = 0; delete [] dist; rcTimeVal endTime = rcGetPerformanceTimer(); if (rcGetBuildTimes()) { rcGetBuildTimes()->erodeArea += rcGetDeltaTimeUsec(startTime, endTime); } return true; }
bool rcBuildRegionsMonotone(rcCompactHeightfield& chf, int borderSize, int minRegionSize, int mergeRegionSize) { rcTimeVal startTime = rcGetPerformanceTimer(); const int w = chf.width; const int h = chf.height; unsigned short id = 1; if (chf.regs) { delete [] chf.regs; chf.regs = 0; } rcScopedDelete<unsigned short> srcReg = new unsigned short[chf.spanCount]; if (!srcReg) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount); return false; } memset(srcReg,0,sizeof(unsigned short)*chf.spanCount); rcScopedDelete<rcSweepSpan> sweeps = new rcSweepSpan[rcMax(chf.width,chf.height)]; if (!sweeps) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", chf.width); return false; } // Mark border regions. if (borderSize) { paintRectRegion(0, borderSize, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(w-borderSize, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, 0, borderSize, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, h-borderSize, h, id|RC_BORDER_REG, chf, srcReg); id++; } rcIntArray prev(256); // Sweep one line at a time. for (int y = borderSize; y < h-borderSize; ++y) { // Collect spans from this row. prev.resize(id+1); memset(&prev[0],0,sizeof(int)*id); unsigned short rid = 1; for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; // -x unsigned short previd = 0; if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(0); const int ay = y + rcGetDirOffsetY(0); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) previd = srcReg[ai]; } if (!previd) { previd = rid++; sweeps[previd].rid = previd; sweeps[previd].ns = 0; sweeps[previd].nei = 0; } // -y if (rcGetCon(s,3) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(3); const int ay = y + rcGetDirOffsetY(3); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); if (srcReg[ai] && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) { unsigned short nr = srcReg[ai]; if (!sweeps[previd].nei || sweeps[previd].nei == nr) { sweeps[previd].nei = nr; sweeps[previd].ns++; prev[nr]++; } else { sweeps[previd].nei = RC_NULL_NEI; } } } srcReg[i] = previd; } } // Create unique ID. for (int i = 1; i < rid; ++i) { if (sweeps[i].nei != RC_NULL_NEI && sweeps[i].nei != 0 && prev[sweeps[i].nei] == (int)sweeps[i].ns) { sweeps[i].id = sweeps[i].nei; } else { sweeps[i].id = id++; } } // Remap IDs for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (srcReg[i] > 0 && srcReg[i] < rid) srcReg[i] = sweeps[srcReg[i]].id; } } } rcTimeVal filterStartTime = rcGetPerformanceTimer(); // Filter out small regions. chf.maxRegions = id; if (!filterSmallRegions(minRegionSize, mergeRegionSize, chf.maxRegions, chf, srcReg)) return false; rcTimeVal filterEndTime = rcGetPerformanceTimer(); // Store the result out. chf.regs = srcReg; srcReg = 0; rcTimeVal endTime = rcGetPerformanceTimer(); if (rcGetBuildTimes()) { rcGetBuildTimes()->buildRegions += rcGetDeltaTimeUsec(startTime, endTime); rcGetBuildTimes()->buildRegionsFilter += rcGetDeltaTimeUsec(filterStartTime, filterEndTime); } return true; }
static void getHeightData(const rcCompactHeightfield& chf, const unsigned short* poly, const int npoly, const unsigned short* verts, rcHeightPatch& hp, rcIntArray& stack) { // Floodfill the heightfield to get 2D height data, // starting at vertex locations as seeds. memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); stack.resize(0); // Use poly vertices as seed points for the flood fill. for (int j = 0; j < npoly; ++j) { const int ax = (int)verts[poly[j]*3+0]; const int ay = (int)verts[poly[j]*3+1]; const int az = (int)verts[poly[j]*3+2]; if (ax < hp.xmin || ax >= hp.xmin+hp.width || az < hp.ymin || az >= hp.ymin+hp.height) continue; const rcCompactCell& c = chf.cells[ax+az*chf.width]; int dmin = 0xffff; int ai = -1; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; int d = rcAbs(ay - (int)s.y); if (d < dmin) { ai = i; dmin = d; } } if (ai != -1) { stack.push(ax); stack.push(az); stack.push(ai); } } while (stack.size() > 0) { int ci = stack.pop(); int cy = stack.pop(); int cx = stack.pop(); // Skip already visited locations. int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; if (hp.data[idx] != 0xffff) continue; const rcCompactSpan& cs = chf.spans[ci]; hp.data[idx] = cs.y; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == 0xf) continue; const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || ay < hp.ymin || ay >= (hp.ymin+hp.height)) continue; if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0xffff) continue; const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(cs, dir); stack.push(ax); stack.push(ay); stack.push(ai); } } }
static void seedArrayWithPolyCenter(rcContext* ctx, const rcCompactHeightfield& chf, const unsigned short* poly, const int npoly, const unsigned short* verts, const int bs, rcHeightPatch& hp, rcIntArray& array) { // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. static const int offset[9*2] = { 0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, }; // Find cell closest to a poly vertex int startCellX = 0, startCellY = 0, startSpanIndex = -1; int dmin = RC_UNSET_HEIGHT; for (int j = 0; j < npoly && dmin > 0; ++j) { for (int k = 0; k < 9 && dmin > 0; ++k) { const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0]; const int ay = (int)verts[poly[j]*3+1]; const int az = (int)verts[poly[j]*3+2] + offset[k*2+1]; if (ax < hp.xmin || ax >= hp.xmin+hp.width || az < hp.ymin || az >= hp.ymin+hp.height) continue; const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni && dmin > 0; ++i) { const rcCompactSpan& s = chf.spans[i]; int d = rcAbs(ay - (int)s.y); if (d < dmin) { startCellX = ax; startCellY = az; startSpanIndex = i; dmin = d; } } } } rcAssert(startSpanIndex != -1); // Find center of the polygon int pcx = 0, pcy = 0; for (int j = 0; j < npoly; ++j) { pcx += (int)verts[poly[j]*3+0]; pcy += (int)verts[poly[j]*3+2]; } pcx /= npoly; pcy /= npoly; // Use seeds array as a stack for DFS array.resize(0); array.push(startCellX); array.push(startCellY); array.push(startSpanIndex); int dirs[] = { 0, 1, 2, 3 }; memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); // DFS to move to the center. Note that we need a DFS here and can not just move // directly towards the center without recording intermediate nodes, even though the polygons // are convex. In very rare we can get stuck due to contour simplification if we do not // record nodes. int cx = -1, cy = -1, ci = -1; while (true) { if (array.size() < 3) { ctx->log(RC_LOG_WARNING, "Walk towards polygon center failed to reach center"); break; } ci = array.pop(); cy = array.pop(); cx = array.pop(); if (cx == pcx && cy == pcy) break; // If we are already at the correct X-position, prefer direction // directly towards the center in the Y-axis; otherwise prefer // direction in the X-axis int directDir; if (cx == pcx) directDir = rcGetDirForOffset(0, pcy > cy ? 1 : -1); else directDir = rcGetDirForOffset(pcx > cx ? 1 : -1, 0); // Push the direct dir last so we start with this on next iteration rcSwap(dirs[directDir], dirs[3]); const rcCompactSpan& cs = chf.spans[ci]; for (int i = 0; i < 4; i++) { int dir = dirs[i]; if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; int newX = cx + rcGetDirOffsetX(dir); int newY = cy + rcGetDirOffsetY(dir); int hpx = newX - hp.xmin; int hpy = newY - hp.ymin; if (hpx < 0 || hpx >= hp.width || hpy < 0 || hpy >= hp.height) continue; if (hp.data[hpx+hpy*hp.width] != 0) continue; hp.data[hpx+hpy*hp.width] = 1; array.push(newX); array.push(newY); array.push((int)chf.cells[(newX+bs)+(newY+bs)*chf.width].index + rcGetCon(cs, dir)); } rcSwap(dirs[directDir], dirs[3]); } array.resize(0); // getHeightData seeds are given in coordinates with borders array.push(cx+bs); array.push(cy+bs); array.push(ci); memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); const rcCompactSpan& cs = chf.spans[ci]; hp.data[cx-hp.xmin+(cy-hp.ymin)*hp.width] = cs.y; }
/// @par /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, rcHeightfieldLayerSet& lset) { rcAssert(ctx); rcScopedTimer timer(ctx, RC_TIMER_BUILD_LAYERS); const int w = chf.width; const int h = chf.height; rcScopedDelete<unsigned char> srcReg((unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP)); if (!srcReg) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' (%d).", chf.spanCount); return false; } memset(srcReg,0xff,sizeof(unsigned char)*chf.spanCount); const int nsweeps = chf.width; rcScopedDelete<rcLayerSweepSpan> sweeps((rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP)); if (!sweeps) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps); return false; } // Partition walkable area into monotone regions. int prevCount[256]; unsigned char regId = 0; for (int y = borderSize; y < h-borderSize; ++y) { memset(prevCount,0,sizeof(int)*regId); unsigned char sweepId = 0; for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; unsigned char sid = 0xff; // -x if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(0); const int ay = y + rcGetDirOffsetY(0); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff) sid = srcReg[ai]; } if (sid == 0xff) { sid = sweepId++; sweeps[sid].nei = 0xff; sweeps[sid].ns = 0; } // -y if (rcGetCon(s,3) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(3); const int ay = y + rcGetDirOffsetY(3); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); const unsigned char 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] == (int)sweeps[i].ns) { sweeps[i].id = sweeps[i].nei; } else { if (regId == 255) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow."); return false; } sweeps[i].id = regId++; } } // Remap local sweep ids to region ids. for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (srcReg[i] != 0xff) srcReg[i] = sweeps[srcReg[i]].id; } } } // Allocate and init layer regions. const int nregs = (int)regId; rcScopedDelete<rcLayerRegion> regs((rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP)); if (!regs) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs); return false; } memset(regs, 0, sizeof(rcLayerRegion)*nregs); for (int i = 0; i < nregs; ++i) { regs[i].layerId = 0xff; regs[i].ymin = 0xffff; regs[i].ymax = 0; } // Find region neighbours and overlapping regions. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; unsigned char lregs[RC_MAX_LAYERS]; int nlregs = 0; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; const unsigned char ri = srcReg[i]; if (ri == 0xff) continue; regs[ri].ymin = rcMin(regs[ri].ymin, s.y); regs[ri].ymax = rcMax(regs[ri].ymax, s.y); // Collect all region layers. if (nlregs < RC_MAX_LAYERS) lregs[nlregs++] = ri; // Update neighbours for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); const unsigned char rai = srcReg[ai]; if (rai != 0xff && rai != ri) { // Don't check return value -- if we cannot add the neighbor // it will just cause a few more regions to be created, which // is fine. addUnique(regs[ri].neis, regs[ri].nneis, RC_MAX_NEIS, rai); } } } } // Update overlapping regions. for (int i = 0; i < nlregs-1; ++i) { for (int j = i+1; j < nlregs; ++j) { if (lregs[i] != lregs[j]) { rcLayerRegion& ri = regs[lregs[i]]; rcLayerRegion& rj = regs[lregs[j]]; if (!addUnique(ri.layers, ri.nlayers, RC_MAX_LAYERS, lregs[j]) || !addUnique(rj.layers, rj.nlayers, RC_MAX_LAYERS, lregs[i])) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS."); return false; } } } } } } // Create 2D layers from regions. unsigned char layerId = 0; static const int MAX_STACK = 64; unsigned char stack[MAX_STACK]; int nstack = 0; for (int i = 0; i < nregs; ++i) { rcLayerRegion& root = regs[i]; // Skip already visited. if (root.layerId != 0xff) continue; // Start search. root.layerId = layerId; root.base = 1; nstack = 0; stack[nstack++] = (unsigned char)i; while (nstack) { // Pop front rcLayerRegion& reg = regs[stack[0]]; nstack--; for (int j = 0; j < nstack; ++j) stack[j] = stack[j+1]; const int nneis = (int)reg.nneis; for (int j = 0; j < nneis; ++j) { const unsigned char nei = reg.neis[j]; rcLayerRegion& regn = regs[nei]; // Skip already visited. if (regn.layerId != 0xff) continue; // Skip if the neighbour is overlapping root region. if (contains(root.layers, root.nlayers, nei)) continue; // Skip if the height range would become too large. const int ymin = rcMin(root.ymin, regn.ymin); const int ymax = rcMax(root.ymax, regn.ymax); if ((ymax - ymin) >= 255) continue; if (nstack < MAX_STACK) { // Deepen stack[nstack++] = (unsigned char)nei; // Mark layer id regn.layerId = layerId; // Merge current layers to root. for (int k = 0; k < regn.nlayers; ++k) { if (!addUnique(root.layers, root.nlayers, RC_MAX_LAYERS, regn.layers[k])) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS."); return false; } } root.ymin = rcMin(root.ymin, regn.ymin); root.ymax = rcMax(root.ymax, regn.ymax); } } } layerId++; } // Merge non-overlapping regions that are close in height. const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; for (int i = 0; i < nregs; ++i) { rcLayerRegion& ri = regs[i]; if (!ri.base) continue; unsigned char newId = ri.layerId; for (;;) { unsigned char oldId = 0xff; for (int j = 0; j < nregs; ++j) { if (i == j) continue; rcLayerRegion& 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. const int ymin = rcMin(ri.ymin, rj.ymin); const int ymax = rcMax(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,ri.nlayers, (unsigned char)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) { rcLayerRegion& rj = regs[j]; if (rj.layerId == oldId) { rj.base = 0; // Remap layerIds. rj.layerId = newId; // Add overlaid layers from 'rj' to 'ri'. for (int k = 0; k < rj.nlayers; ++k) { if (!addUnique(ri.layers, ri.nlayers, RC_MAX_LAYERS, rj.layers[k])) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS."); return false; } } // Update height bounds. ri.ymin = rcMin(ri.ymin, rj.ymin); ri.ymax = rcMax(ri.ymax, rj.ymax); } } } } // Compact layerIds unsigned char remap[256]; memset(remap, 0, 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]) 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) return true; // Create layers. rcAssert(lset.layers == 0); const int lw = w - borderSize*2; const int lh = h - borderSize*2; // Build contracted bbox for layers. float bmin[3], bmax[3]; rcVcopy(bmin, chf.bmin); rcVcopy(bmax, chf.bmax); bmin[0] += borderSize*chf.cs; bmin[2] += borderSize*chf.cs; bmax[0] -= borderSize*chf.cs; bmax[2] -= borderSize*chf.cs; lset.nlayers = (int)layerId; lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); if (!lset.layers) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers); return false; } memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); // Store layers. for (int i = 0; i < lset.nlayers; ++i) { unsigned char curId = (unsigned char)i; rcHeightfieldLayer* layer = &lset.layers[i]; const int gridSize = sizeof(unsigned char)*lw*lh; layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->heights) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize); return false; } memset(layer->heights, 0xff, gridSize); layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->areas) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize); return false; } memset(layer->areas, 0, gridSize); layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->cons) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize); return false; } memset(layer->cons, 0, 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 = (int)regs[j].ymin; hmax = (int)regs[j].ymax; } } layer->width = lw; layer->height = lh; layer->cs = chf.cs; layer->ch = chf.ch; // Adjust the bbox to fit the heightfield. rcVcopy(layer->bmin, bmin); rcVcopy(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) { const int cx = borderSize+x; const int cy = borderSize+y; const rcCompactCell& c = chf.cells[cx+cy*w]; for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j) { const rcCompactSpan& s = chf.spans[j]; // Skip unassigned regions. if (srcReg[j] == 0xff) continue; // Skip of does nto belong to current layer. unsigned char lid = regs[srcReg[j]].layerId; if (lid != curId) continue; // Update data bounds. layer->minx = rcMin(layer->minx, x); layer->maxx = rcMax(layer->maxx, x); layer->miny = rcMin(layer->miny, y); layer->maxy = rcMax(layer->maxy, y); // Store height and area type. const int idx = x+y*lw; layer->heights[idx] = (unsigned char)(s.y - hmin); layer->areas[idx] = chf.areas[j]; // Check connection. unsigned char portal = 0; unsigned char con = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); unsigned char alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff; // Portal mask if (chf.areas[ai] != RC_NULL_AREA && lid != alid) { portal |= (unsigned char)(1<<dir); // Update height so that it matches on both sides of the portal. const rcCompactSpan& as = chf.spans[ai]; if (as.y > hmin) layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin)); } // Valid connection mask if (chf.areas[ai] != RC_NULL_AREA && lid == alid) { const int nx = ax - borderSize; const int ny = ay - borderSize; if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) con |= (unsigned 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; } return true; }
static int getCornerHeight(int x, int y, int i, int dir, const rcCompactHeightfield& chf, bool& isBorderVertex) { const rcCompactSpan& s = chf.spans[i]; int ch = (int)s.minY; int dirp = (dir+1) & 0x3; struct CornerId { uint64_t areaMask; unsigned short region; bool No0() const { return areaMask != 0 && region != 0; } CornerId( unsigned short reg = 0, uint64_t area = 0 ) : areaMask( area ) , region( reg ) { } }; CornerId regs[ 4 ]; // Combine region and area codes in order to prevent // border vertices which are in between two areas to be removed. regs[ 0 ] = CornerId( chf.spans[ i ].regionID, chf.areaMasks[ i ] ); if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); const rcCompactSpan& as = chf.spans[ai]; ch = rcMax(ch, (int)as.minY); regs[ 1 ] = CornerId( chf.spans[ ai ].regionID, chf.areaMasks[ ai ] ); if (rcGetCon(as, dirp) != RC_NOT_CONNECTED) { const int ax2 = ax + rcGetDirOffsetX(dirp); const int ay2 = ay + rcGetDirOffsetY(dirp); const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dirp); const rcCompactSpan& as2 = chf.spans[ai2]; ch = rcMax(ch, (int)as2.minY); regs[ 2 ] = CornerId( chf.spans[ ai2 ].regionID, chf.areaMasks[ ai2 ] ); } } if (rcGetCon(s, dirp) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dirp); const int ay = y + rcGetDirOffsetY(dirp); const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp); const rcCompactSpan& as = chf.spans[ai]; ch = rcMax(ch, (int)as.minY); regs[ 3 ] = CornerId( chf.spans[ ai ].regionID, chf.areaMasks[ ai ] ); if (rcGetCon(as, dir) != RC_NOT_CONNECTED) { const int ax2 = ax + rcGetDirOffsetX(dir); const int ay2 = ay + rcGetDirOffsetY(dir); const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dir); const rcCompactSpan& as2 = chf.spans[ai2]; ch = rcMax(ch, (int)as2.minY); regs[ 2 ] = CornerId( chf.spans[ ai2 ].regionID, chf.areaMasks[ ai2 ] ); } } // Check if the vertex is special edge vertex, these vertices will be removed later. for (int j = 0; j < 4; ++j) { const int a = j; const int b = (j+1) & 0x3; const int c = (j+2) & 0x3; const 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. const bool twoSameExts = ( regs[ a ].region & regs[ b ].region & RC_BORDER_REG ) != 0 && regs[ a ].region == regs[ b ].region; const bool twoInts = ( ( regs[ c ].region | regs[ d ].region ) & RC_BORDER_REG ) == 0; const bool intsSameArea = ( regs[ c ].areaMask ) == ( regs[ d ].areaMask ); const bool noZeros = regs[ a ].No0() && regs[ b ].No0() && regs[ c ].No0() && regs[ d ].No0(); if (twoSameExts && twoInts && intsSameArea && noZeros) { isBorderVertex = true; break; } } return ch; }
static void getHeightData(const rcCompactHeightfield& chf, const unsigned short* poly, const int npoly, const unsigned short* verts, const int bs, rcHeightPatch& hp, rcIntArray& stack, int region) { // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. stack.resize(0); memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); bool empty = true; // Copy the height from the same region, and mark region borders // as seed points to fill the rest. for (int hy = 0; hy < hp.height; hy++) { int y = hp.ymin + hy + bs; for (int hx = 0; hx < hp.width; hx++) { int x = hp.xmin + hx + bs; const rcCompactCell& c = chf.cells[x+y*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (s.reg == region) { // Store height hp.data[hx + hy*hp.width] = s.y; empty = false; // If any of the neighbours is not in same region, // add the current location as flood fill start bool border = false; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); const rcCompactSpan& as = chf.spans[ai]; if (as.reg != region) { border = true; break; } } } if (border) { stack.push(x); stack.push(y); stack.push(i); } break; } } } } // if the polygon does not contian any points from the current region (rare, but happens) // then use the cells closest to the polygon vertices as seeds to fill the height field if (empty) getHeightDataSeedsFromVertices(chf, poly, npoly, verts, bs, hp, stack); static const int RETRACT_SIZE = 256; int head = 0; while (head*3 < stack.size()) { int cx = stack[head*3+0]; int cy = stack[head*3+1]; int ci = stack[head*3+2]; head++; if (head >= RETRACT_SIZE) { head = 0; if (stack.size() > RETRACT_SIZE*3) memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3)); stack.resize(stack.size()-RETRACT_SIZE*3); } const rcCompactSpan& cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int hx = ax - hp.xmin - bs; const int hy = ay - hp.ymin - bs; if (hx < 0 || hx >= hp.width || hy < 0 || hy >= hp.height) continue; if (hp.data[hx + hy*hp.width] != RC_UNSET_HEIGHT) continue; const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir); const rcCompactSpan& as = chf.spans[ai]; hp.data[hx + hy*hp.width] = as.y; stack.push(ax); stack.push(ay); stack.push(ai); } } }
/// @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 bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, const float maxError, const int maxEdgeLen, rcContourSet& cset, const int buildFlags) { rcAssert(ctx); const int w = chf.width; const int h = chf.height; const int borderSize = chf.borderSize; ctx->startTimer(RC_TIMER_BUILD_CONTOURS); rcVcopy(cset.bmin, chf.bmin); rcVcopy(cset.bmax, chf.bmax); if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. const float pad = borderSize*chf.cs; cset.bmin[0] += pad; cset.bmin[2] += pad; cset.bmax[0] -= pad; cset.bmax[2] -= pad; } cset.cellSizeXZ = chf.cs; cset.cellSizeY = chf.ch; cset.width = chf.width - chf.borderSize*2; cset.height = chf.height - chf.borderSize*2; cset.borderSize = chf.borderSize; int maxContours = rcMax((int)chf.maxRegions, 8); cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); if (!cset.conts) return false; cset.nconts = 0; rcScopedDelete<unsigned char> flags = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); if (!flags) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' (%d).", chf.spanCount); return false; } ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); // Mark boundaries. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { unsigned char res = 0; const rcCompactSpan& s = chf.spans[i]; if (!chf.spans[i].regionID || (chf.spans[i].regionID & RC_BORDER_REG)) { flags[i] = 0; continue; } for (int dir = 0; dir < 4; ++dir) { unsigned short r = 0; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); r = chf.spans[ai].regionID; } if (r == chf.spans[i].regionID) res |= (1 << dir); } flags[i] = res ^ 0xf; // Inverse, mark non connected edges. } } } ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); rcIntArray verts(256); rcIntArray simplified(64); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (flags[i] == 0 || flags[i] == 0xf) { flags[i] = 0; continue; } const unsigned short reg = chf.spans[i].regionID; if (!reg || (reg & RC_BORDER_REG)) continue; const navAreaMask areaMask = chf.areaMasks[ i ]; verts.resize(0); simplified.resize(0); ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); walkContour(x, y, i, chf, flags, verts); ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); removeDegenerateSegments(simplified); ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); // Store region->contour remap info. // Create contour. if (simplified.size()/4 >= 3) { if (cset.nconts >= maxContours) { // Allocate more contours. // This happens when a region has holes. const int oldMax = maxContours; maxContours *= 2; rcContour* newConts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); for (int j = 0; j < cset.nconts; ++j) { newConts[j] = cset.conts[j]; // Reset source pointers to prevent data deletion. cset.conts[j].verts = 0; cset.conts[j].rverts = 0; } rcFree(cset.conts); cset.conts = newConts; ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours); } rcContour* cont = &cset.conts[cset.nconts++]; cont->nverts = simplified.size()/4; cont->verts = (int*)rcAlloc(sizeof(int)*cont->nverts*4, RC_ALLOC_PERM); if (!cont->verts) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' (%d).", cont->nverts); return false; } memcpy(cont->verts, &simplified[0], sizeof(int)*cont->nverts*4); if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. for (int j = 0; j < cont->nverts; ++j) { int* v = &cont->verts[j*4]; v[0] -= borderSize; v[2] -= borderSize; } } cont->nrverts = verts.size()/4; cont->rverts = (int*)rcAlloc(sizeof(int)*cont->nrverts*4, RC_ALLOC_PERM); if (!cont->rverts) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' (%d).", cont->nrverts); return false; } memcpy(cont->rverts, &verts[0], sizeof(int)*cont->nrverts*4); if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. for (int j = 0; j < cont->nrverts; ++j) { int* v = &cont->rverts[j*4]; v[0] -= borderSize; v[2] -= borderSize; } } cont->reg = reg; cont->areaMask = areaMask; } } } } // Merge holes if needed. if (cset.nconts > 0) { // Calculate winding of all polygons. rcScopedDelete<char> winding = (char*)rcAlloc(sizeof(char)*cset.nconts, RC_ALLOC_TEMP); if (!winding) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'hole' (%d).", cset.nconts); return false; } int nholes = 0; for (int i = 0; i < cset.nconts; ++i) { rcContour& 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. const int nregions = chf.maxRegions+1; rcScopedDelete<rcContourRegion> regions = (rcContourRegion*)rcAlloc(sizeof(rcContourRegion)*nregions, RC_ALLOC_TEMP); if (!regions) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'regions' (%d).", nregions); return false; } memset(regions, 0, sizeof(rcContourRegion)*nregions); rcScopedDelete<rcContourHole> holes = (rcContourHole*)rcAlloc(sizeof(rcContourHole)*cset.nconts, RC_ALLOC_TEMP); if (!holes) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'holes' (%d).", cset.nconts); return false; } memset(holes, 0, sizeof(rcContourHole)*cset.nconts); for (int i = 0; i < cset.nconts; ++i) { rcContour& cont = cset.conts[i]; // Positively would contours are outlines, negative holes. if (winding[i] > 0) { if (regions[cont.reg].outline) ctx->log(RC_LOG_ERROR, "rcBuildContours: Multiple outlines for region %d.", cont.reg); regions[cont.reg].outline = &cont; } else { regions[cont.reg].nholes++; } } int index = 0; for (int i = 0; i < nregions; i++) { if (regions[i].nholes > 0) { regions[i].holes = &holes[index]; index += regions[i].nholes; regions[i].nholes = 0; } } for (int i = 0; i < cset.nconts; ++i) { rcContour& cont = cset.conts[i]; rcContourRegion& 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++) { rcContourRegion& reg = regions[i]; if (!reg.nholes) continue; if (reg.outline) { 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. ctx->log(RC_LOG_ERROR, "rcBuildContours: Bad outline for region %d, contour simplification is likely too aggressive.", i); } } } } ctx->stopTimer(RC_TIMER_BUILD_CONTOURS); return true; }
bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf) { rcAssert(ctx); const int w = chf.width; const int h = chf.height; ctx->startTimer(RC_TIMER_ERODE_AREA); unsigned char* dist = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); if (!dist) { ctx->log(RC_LOG_ERROR, "erodeWalkableArea: Out of memory 'dist' (%d).", chf.spanCount); return false; } // Init distance. memset(dist, 0xff, sizeof(unsigned char)*chf.spanCount); // Mark boundary cells. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (chf.areas[i] != RC_NULL_AREA) { const rcCompactSpan& s = chf.spans[i]; int nc = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) nc++; } // At least one missing neighbour. if (nc != 4) dist[i] = 0; } } } } unsigned char nd; // Pass 1 for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { // (-1,0) const int ax = x + rcGetDirOffsetX(0); const int ay = y + rcGetDirOffsetY(0); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); const rcCompactSpan& as = chf.spans[ai]; nd = (unsigned char)rcMin((int)dist[ai]+2, 255); if (nd < dist[i]) dist[i] = nd; // (-1,-1) if (rcGetCon(as, 3) != RC_NOT_CONNECTED) { const int aax = ax + rcGetDirOffsetX(3); const int aay = ay + rcGetDirOffsetY(3); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3); nd = (unsigned char)rcMin((int)dist[aai]+3, 255); if (nd < dist[i]) dist[i] = nd; } } if (rcGetCon(s, 3) != RC_NOT_CONNECTED) { // (0,-1) const int ax = x + rcGetDirOffsetX(3); const int ay = y + rcGetDirOffsetY(3); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); const rcCompactSpan& as = chf.spans[ai]; nd = (unsigned char)rcMin((int)dist[ai]+2, 255); if (nd < dist[i]) dist[i] = nd; // (1,-1) if (rcGetCon(as, 2) != RC_NOT_CONNECTED) { const int aax = ax + rcGetDirOffsetX(2); const int aay = ay + rcGetDirOffsetY(2); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2); nd = (unsigned char)rcMin((int)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) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (rcGetCon(s, 2) != RC_NOT_CONNECTED) { // (1,0) const int ax = x + rcGetDirOffsetX(2); const int ay = y + rcGetDirOffsetY(2); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); const rcCompactSpan& as = chf.spans[ai]; nd = (unsigned char)rcMin((int)dist[ai]+2, 255); if (nd < dist[i]) dist[i] = nd; // (1,1) if (rcGetCon(as, 1) != RC_NOT_CONNECTED) { const int aax = ax + rcGetDirOffsetX(1); const int aay = ay + rcGetDirOffsetY(1); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1); nd = (unsigned char)rcMin((int)dist[aai]+3, 255); if (nd < dist[i]) dist[i] = nd; } } if (rcGetCon(s, 1) != RC_NOT_CONNECTED) { // (0,1) const int ax = x + rcGetDirOffsetX(1); const int ay = y + rcGetDirOffsetY(1); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); const rcCompactSpan& as = chf.spans[ai]; nd = (unsigned char)rcMin((int)dist[ai]+2, 255); if (nd < dist[i]) dist[i] = nd; // (-1,1) if (rcGetCon(as, 0) != RC_NOT_CONNECTED) { const int aax = ax + rcGetDirOffsetX(0); const int aay = ay + rcGetDirOffsetY(0); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0); nd = (unsigned char)rcMin((int)dist[aai]+3, 255); if (nd < dist[i]) dist[i] = nd; } } } } } const unsigned char thr = (unsigned char)(radius*2); for (int i = 0; i < chf.spanCount; ++i) if (dist[i] < thr) chf.areas[i] = RC_NULL_AREA; rcFree(dist); ctx->stopTimer(RC_TIMER_ERODE_AREA); return true; }
static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea, unsigned short& maxRegionId, rcCompactHeightfield& chf, unsigned short* srcReg, rcIntArray& overlaps) { const int w = chf.width; const int h = chf.height; const int nreg = maxRegionId+1; rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP); if (!regions) { ctx->log(RC_LOG_ERROR, "mergeAndFilterLayerRegions: Out of memory 'regions' (%d).", nreg); return false; } // Construct regions for (int i = 0; i < nreg; ++i) new(®ions[i]) rcRegion((unsigned short)i); // Find region neighbours and overlapping regions. rcIntArray lregs(32); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; lregs.resize(0); for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; const unsigned short ri = srcReg[i]; if (ri == 0 || ri >= nreg) continue; rcRegion& reg = regions[ri]; reg.spanCount++; reg.ymin = rcMin(reg.ymin, s.y); reg.ymax = rcMax(reg.ymax, s.y); // Collect all region layers. lregs.push(ri); // Update neighbours for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); const unsigned short rai = srcReg[ai]; if (rai > 0 && rai < nreg && rai != ri) addUniqueConnection(reg, rai); if (rai & RC_BORDER_REG) reg.connectsToBorder = true; } } } // Update overlapping regions. for (int i = 0; i < lregs.size()-1; ++i) { for (int j = i+1; j < lregs.size(); ++j) { if (lregs[i] != lregs[j]) { rcRegion& ri = regions[lregs[i]]; rcRegion& rj = regions[lregs[j]]; addUniqueFloorRegion(ri, lregs[j]); addUniqueFloorRegion(rj, lregs[i]); } } } } } // Create 2D layers from regions. unsigned short layerId = 1; for (int i = 0; i < nreg; ++i) regions[i].id = 0; // Merge montone regions to create non-overlapping areas. rcIntArray stack(32); for (int i = 1; i < nreg; ++i) { rcRegion& root = regions[i]; // Skip already visited. if (root.id != 0) continue; // Start search. root.id = layerId; stack.resize(0); stack.push(i); while (stack.size() > 0) { // Pop front rcRegion& reg = regions[stack[0]]; for (int j = 0; j < stack.size()-1; ++j) stack[j] = stack[j+1]; stack.resize(stack.size()-1); const int ncons = (int)reg.connections.size(); for (int j = 0; j < ncons; ++j) { const int nei = reg.connections[j]; rcRegion& regn = regions[nei]; // Skip already visited. if (regn.id != 0) continue; // Skip if the neighbour is overlapping root region. bool overlap = false; for (int k = 0; k < root.floors.size(); k++) { if (root.floors[k] == nei) { overlap = true; break; } } if (overlap) continue; // Deepen stack.push(nei); // Mark layer id regn.id = layerId; // Merge current layers to root. for (int k = 0; k < regn.floors.size(); ++k) addUniqueFloorRegion(root, regn.floors[k]); root.ymin = rcMin(root.ymin, regn.ymin); root.ymax = rcMax(root.ymax, regn.ymax); root.spanCount += regn.spanCount; regn.spanCount = 0; root.connectsToBorder = root.connectsToBorder || regn.connectsToBorder; } } layerId++; } // Remove small regions for (int i = 0; i < nreg; ++i) { if (regions[i].spanCount > 0 && regions[i].spanCount < minRegionArea && !regions[i].connectsToBorder) { unsigned short reg = regions[i].id; for (int j = 0; j < nreg; ++j) if (regions[j].id == reg) regions[j].id = 0; } } // Compress region Ids. for (int i = 0; i < nreg; ++i) { regions[i].remap = false; if (regions[i].id == 0) continue; // Skip nil regions. if (regions[i].id & RC_BORDER_REG) continue; // Skip external regions. regions[i].remap = true; } unsigned short regIdGen = 0; for (int i = 0; i < nreg; ++i) { if (!regions[i].remap) continue; unsigned short oldId = regions[i].id; unsigned short newId = ++regIdGen; for (int j = i; j < nreg; ++j) { if (regions[j].id == oldId) { regions[j].id = newId; regions[j].remap = false; } } } maxRegionId = regIdGen; // Remap regions. for (int i = 0; i < chf.spanCount; ++i) { if ((srcReg[i] & RC_BORDER_REG) == 0) srcReg[i] = regions[srcReg[i]].id; } for (int i = 0; i < nreg; ++i) regions[i].~rcRegion(); rcFree(regions); return true; }
static void getHeightData(const rcCompactHeightfield& chf, const unsigned short* poly, const int npoly, const unsigned short* verts, rcHeightPatch& hp, rcIntArray& stack) { // Floodfill the heightfield to get 2D height data, // starting at vertex locations as seeds. memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); stack.resize(0); static const int offset[9*2] = { 0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, }; // Use poly vertices as seed points for the flood fill. for (int j = 0; j < npoly; ++j) { int cx = 0, cz = 0, ci =-1; int dmin = RC_UNSET_HEIGHT; for (int k = 0; k < 9; ++k) { const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0]; const int ay = (int)verts[poly[j]*3+1]; const int az = (int)verts[poly[j]*3+2] + offset[k*2+1]; if (ax < hp.xmin || ax >= hp.xmin+hp.width || az < hp.ymin || az >= hp.ymin+hp.height) continue; const rcCompactCell& c = chf.cells[ax+az*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; int d = rcAbs(ay - (int)s.y); if (d < dmin) { cx = ax; cz = az; ci = i; dmin = d; } } } if (ci != -1) { stack.push(cx); stack.push(cz); stack.push(ci); } } // Find center of the polygon using flood fill. int pcx = 0, pcz = 0; for (int j = 0; j < npoly; ++j) { pcx += (int)verts[poly[j]*3+0]; pcz += (int)verts[poly[j]*3+2]; } pcx /= npoly; pcz /= npoly; for (int i = 0; i < stack.size(); i += 3) { int cx = stack[i+0]; int cy = stack[i+1]; int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; hp.data[idx] = 1; } while (stack.size() > 0) { int ci = stack.pop(); int cy = stack.pop(); int cx = stack.pop(); // Check if close to center of the polygon. if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1) { stack.resize(0); stack.push(cx); stack.push(cy); stack.push(ci); break; } const rcCompactSpan& cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || ay < hp.ymin || ay >= (hp.ymin+hp.height)) continue; if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) continue; const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(cs, dir); int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; hp.data[idx] = 1; stack.push(ax); stack.push(ay); stack.push(ai); } } memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); // Mark start locations. for (int i = 0; i < stack.size(); i += 3) { int cx = stack[i+0]; int cy = stack[i+1]; int ci = stack[i+2]; int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; const rcCompactSpan& cs = chf.spans[ci]; hp.data[idx] = cs.y; } static const int RETRACT_SIZE = 256; int head = 0; while (head*3 < stack.size()) { int cx = stack[head*3+0]; int cy = stack[head*3+1]; int ci = stack[head*3+2]; head++; if (head >= RETRACT_SIZE) { head = 0; if (stack.size() > RETRACT_SIZE*3) memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3)); stack.resize(stack.size()-RETRACT_SIZE*3); } const rcCompactSpan& cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || ay < hp.ymin || ay >= (hp.ymin+hp.height)) continue; if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != RC_UNSET_HEIGHT) continue; const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(cs, dir); const rcCompactSpan& as = chf.spans[ai]; int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; hp.data[idx] = as.y; stack.push(ax); stack.push(ay); stack.push(ai); } } }
/// @par /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, rcHeightfieldLayerSet& lset) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_LAYERS); rcScopedDelete<unsigned short> spanBuf4 = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); if (!spanBuf4) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'spanBuf4' (%d).", chf.spanCount*4); return false; } ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); unsigned short* srcReg = spanBuf4; if (!rcGatherRegionsNoFilter(ctx, chf, borderSize, spanBuf4)) return false; ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); const int w = chf.width; const int h = chf.height; const int nreg = chf.maxRegions + 1; rcLayerRegion* regions = (rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nreg, RC_ALLOC_TEMP); if (!regions) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regions' (%d).", nreg); return false; } // Construct regions memset(regions, 0, sizeof(rcLayerRegion)*nreg); for (int i = 0; i < nreg; ++i) { regions[i].layerId = (unsigned short)i; regions[i].ymax = 0; regions[i].ymin = 0xffff; } // Find region neighbours and overlapping regions. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; const unsigned short ri = srcReg[i]; if (ri == 0 || ri >= nreg) continue; rcLayerRegion& reg = regions[ri]; reg.ymin = rcMin(reg.ymin, s.y); reg.ymax = rcMax(reg.ymax, s.y); reg.hasSpans = true; // Collect all region layers. for (int j = (int)c.index; j < ni; ++j) { unsigned short nri = srcReg[j]; if (nri == 0 || nri >= nreg) continue; if (nri != ri) { addUniqueLayerRegion(reg, nri); } } // Have found contour if (reg.connections.size() > 0) continue; // Check if this cell is next to a border. int ndir = -1; for (int dir = 0; dir < 4; ++dir) { if (isSolidEdge(chf, srcReg, x, y, i, dir)) { ndir = dir; break; } } if (ndir != -1) { // The cell is at border. // Walk around the contour to find all the neighbors. walkContour(x, y, i, ndir, chf, srcReg, reg.connections); } } } } // Create 2D layers from regions. unsigned short layerId = 0; rcIntArray stack(64); for (int i = 0; i < nreg; i++) { rcLayerRegion& reg = regions[i]; if (reg.visited || !reg.hasSpans) continue; reg.layerId = layerId; reg.visited = true; reg.base = true; stack.resize(0); stack.push(i); while (stack.size()) { int ri = stack.pop(); rcLayerRegion& creg = regions[ri]; for (int j = 0; j < creg.connections.size(); j++) { const unsigned short nei = (unsigned short)creg.connections[j]; if (nei & RC_BORDER_REG) continue; rcLayerRegion& regn = regions[nei]; // Skip already visited. if (regn.visited) continue; // Skip if the neighbor is overlapping root region. if (reg.layers.contains(nei)) continue; // Skip if the height range would become too large. const int ymin = rcMin(reg.ymin, regn.ymin); const int ymax = rcMin(reg.ymax, regn.ymax); if ((ymax - ymin) >= 255) continue; // visit stack.push(nei); regn.visited = true; regn.layerId = layerId; // add layers to root for (int k = 0; k < regn.layers.size(); k++) addUniqueLayerRegion(reg, regn.layers[k]); reg.ymin = rcMin(reg.ymin, regn.ymin); reg.ymax = rcMax(reg.ymax, regn.ymax); } } layerId++; } // Merge non-overlapping regions that are close in height. const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; for (int i = 0; i < nreg; i++) { rcLayerRegion& ri = regions[i]; if (!ri.base) continue; unsigned short newId = ri.layerId; for (;;) { unsigned short oldId = 0xffff; for (int j = 0; j < nreg; j++) { if (i == j) continue; rcLayerRegion& rj = regions[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. const int ymin = rcMin(ri.ymin, rj.ymin); const int ymax = rcMin(ri.ymax, rj.ymax); if ((ymax - ymin) >= 255) continue; // Make sure that there is no overlap when mergin 'ri' and 'rj'. bool overlap = false; // Iterate over all regions which have the same layerId as 'rj' for (int k = 0; k < nreg; ++k) { if (regions[k].layerId != rj.layerId) continue; // Check if region 'k' is overlapping region 'ri' // Index to 'regs' is the same as region id. if (ri.layers.contains(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 == 0xffff) break; // Merge for (int j = 0; j < nreg; ++j) { rcLayerRegion& rj = regions[j]; if (rj.layerId == oldId) { rj.base = 0; // Remap layerIds. rj.layerId = newId; // Add overlaid layers from 'rj' to 'ri'. for (int k = 0; k < rj.layers.size(); ++k) addUniqueLayerRegion(ri, rj.layers[k]); // Update height bounds. ri.ymin = rcMin(ri.ymin, rj.ymin); ri.ymax = rcMax(ri.ymax, rj.ymax); } } } } // Compress layer Ids. for (int i = 0; i < nreg; ++i) { regions[i].remap = regions[i].hasSpans; if (!regions[i].hasSpans) { regions[i].layerId = 0xffff; } } unsigned short maxLayerId = 0; for (int i = 0; i < nreg; ++i) { if (!regions[i].remap) continue; unsigned short oldId = regions[i].layerId; unsigned short newId = maxLayerId; for (int j = i; j < nreg; ++j) { if (regions[j].layerId == oldId) { regions[j].layerId = newId; regions[j].remap = false; } } maxLayerId++; } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); if (maxLayerId == 0) { ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; } // Create layers. rcAssert(lset.layers == 0); const int lw = w - borderSize*2; const int lh = h - borderSize*2; // Build contracted bbox for layers. float bmin[3], bmax[3]; rcVcopy(bmin, chf.bmin); rcVcopy(bmax, chf.bmax); bmin[0] += borderSize*chf.cs; bmin[2] += borderSize*chf.cs; bmax[0] -= borderSize*chf.cs; bmax[2] -= borderSize*chf.cs; lset.nlayers = (int)maxLayerId; lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); if (!lset.layers) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers); return false; } memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); // Store layers. for (int i = 0; i < lset.nlayers; ++i) { unsigned short curId = (unsigned short)i; // Allocate memory for the current layer. rcHeightfieldLayer* layer = &lset.layers[i]; memset(layer, 0, sizeof(rcHeightfieldLayer)); const int gridSize = sizeof(unsigned char)*lw*lh; layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->heights) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize); return false; } memset(layer->heights, 0xff, gridSize); layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->areas) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize); return false; } memset(layer->areas, 0, gridSize); layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->cons) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize); return false; } memset(layer->cons, 0, gridSize); // Find layer height bounds. int hmin = 0, hmax = 0; for (int j = 0; j < nreg; ++j) { if (regions[j].base && regions[j].layerId == curId) { hmin = (int)regions[j].ymin; hmax = (int)regions[j].ymax; } } layer->width = lw; layer->height = lh; layer->cs = chf.cs; layer->ch = chf.ch; // Adjust the bbox to fit the heighfield. rcVcopy(layer->bmin, bmin); rcVcopy(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 heighfield. for (int y = 0; y < lh; ++y) { for (int x = 0; x < lw; ++x) { const int cx = borderSize+x; const int cy = borderSize+y; const rcCompactCell& c = chf.cells[cx+cy*w]; for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j) { const rcCompactSpan& s = chf.spans[j]; // Skip unassigned regions. if (srcReg[j] == 0 || srcReg[j] >= nreg) continue; // Skip of does not belong to current layer. unsigned short lid = regions[srcReg[j]].layerId; if (lid != curId) continue; // Update data bounds. layer->minx = rcMin(layer->minx, x); layer->maxx = rcMax(layer->maxx, x); layer->miny = rcMin(layer->miny, y); layer->maxy = rcMax(layer->maxy, y); // Store height and area type. const int idx = x+y*lw; layer->heights[idx] = (unsigned char)(s.y - hmin); layer->areas[idx] = chf.areas[j]; // Check connection. unsigned char portal = 0; unsigned char con = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); unsigned short alid = (srcReg[ai] < nreg) ? regions[srcReg[ai]].layerId : 0xffff; // Portal mask if (chf.areas[ai] != RC_NULL_AREA && lid != alid) { portal |= (unsigned char)(1<<dir); // Update height so that it matches on both sides of the portal. const rcCompactSpan& as = chf.spans[ai]; if (as.y > hmin) layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin)); } // Valid connection mask if (chf.areas[ai] != RC_NULL_AREA && lid == alid) { const int nx = ax - borderSize; const int ny = ay - borderSize; if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) con |= (unsigned 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 true; }
static void calculateDistanceField(rcCompactHeightfield& chf, unsigned short* src, unsigned short& maxDist) { const int w = chf.width; const int h = chf.height; // Init distance and points. for (int i = 0; i < chf.spanCount; ++i) src[i] = 0xffff; // Mark boundary cells. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; const unsigned char area = chf.areas[i]; int nc = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); if (area == chf.areas[ai]) nc++; } } if (nc != 4) src[i] = 0; } } } // Pass 1 for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { // (-1,0) const int ax = x + rcGetDirOffsetX(0); const int ay = y + rcGetDirOffsetY(0); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); const rcCompactSpan& as = chf.spans[ai]; if (src[ai]+2 < src[i]) src[i] = src[ai]+2; // (-1,-1) if (rcGetCon(as, 3) != RC_NOT_CONNECTED) { const int aax = ax + rcGetDirOffsetX(3); const int aay = ay + rcGetDirOffsetY(3); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3); if (src[aai]+3 < src[i]) src[i] = src[aai]+3; } } if (rcGetCon(s, 3) != RC_NOT_CONNECTED) { // (0,-1) const int ax = x + rcGetDirOffsetX(3); const int ay = y + rcGetDirOffsetY(3); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); const rcCompactSpan& as = chf.spans[ai]; if (src[ai]+2 < src[i]) src[i] = src[ai]+2; // (1,-1) if (rcGetCon(as, 2) != RC_NOT_CONNECTED) { const int aax = ax + rcGetDirOffsetX(2); const int aay = ay + rcGetDirOffsetY(2); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2); if (src[aai]+3 < src[i]) src[i] = src[aai]+3; } } } } } // Pass 2 for (int y = h-1; y >= 0; --y) { for (int x = w-1; x >= 0; --x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (rcGetCon(s, 2) != RC_NOT_CONNECTED) { // (1,0) const int ax = x + rcGetDirOffsetX(2); const int ay = y + rcGetDirOffsetY(2); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); const rcCompactSpan& as = chf.spans[ai]; if (src[ai]+2 < src[i]) src[i] = src[ai]+2; // (1,1) if (rcGetCon(as, 1) != RC_NOT_CONNECTED) { const int aax = ax + rcGetDirOffsetX(1); const int aay = ay + rcGetDirOffsetY(1); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1); if (src[aai]+3 < src[i]) src[i] = src[aai]+3; } } if (rcGetCon(s, 1) != RC_NOT_CONNECTED) { // (0,1) const int ax = x + rcGetDirOffsetX(1); const int ay = y + rcGetDirOffsetY(1); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); const rcCompactSpan& as = chf.spans[ai]; if (src[ai]+2 < src[i]) src[i] = src[ai]+2; // (-1,1) if (rcGetCon(as, 0) != RC_NOT_CONNECTED) { const int aax = ax + rcGetDirOffsetX(0); const int aay = ay + rcGetDirOffsetY(0); const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0); if (src[aai]+3 < src[i]) src[i] = src[aai]+3; } } } } } maxDist = 0; for (int i = 0; i < chf.spanCount; ++i) maxDist = rcMax(src[i], maxDist); }
static bool SplitAndStoreLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, unsigned short* srcReg, rcLayerRegionMonotone* regs, const int nregs, rcHeightfieldLayerSet& lset) { // Create 2D layers from regions. unsigned short layerId = 0; rcIntArray stack(64); stack.resize(0); for (int i = 0; i < nregs; ++i) { rcLayerRegionMonotone& root = regs[i]; // Skip already visited. if (root.layerId != 0xffff) continue; // Start search. root.layerId = layerId; root.base = 1; stack.push(i); while (stack.size()) { // Pop front rcLayerRegionMonotone& reg = regs[stack[0]]; for (int j = 1; j < stack.size(); ++j) stack[j - 1] = stack[j]; stack.pop(); const int nneis = (int)reg.neis.size(); for (int j = 0; j < nneis; ++j) { const int nei = reg.neis[j]; rcLayerRegionMonotone& regn = regs[nei]; // Skip already visited. if (regn.layerId != 0xffff) continue; // Skip if the neighbour is overlapping root region. if (root.layers.contains(nei)) continue; // Skip if the height range would become too large. const int ymin = rcMin(root.ymin, regn.ymin); const int ymax = rcMin(root.ymax, regn.ymax); if ((ymax - ymin) >= 255) continue; // Deepen stack.push(nei); // Mark layer id regn.layerId = layerId; // Merge current layers to root. for (int k = 0; k < regn.layers.size(); ++k) addUnique(root.layers, regn.layers[k]); root.ymin = rcMin(root.ymin, regn.ymin); root.ymax = rcMax(root.ymax, regn.ymax); } } layerId++; } // Merge non-overlapping regions that are close in height. const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; for (int i = 0; i < nregs; ++i) { rcLayerRegionMonotone& ri = regs[i]; if (!ri.base) continue; unsigned short newId = ri.layerId; for (;;) { unsigned short oldId = 0xffff; for (int j = 0; j < nregs; ++j) { if (i == j) continue; rcLayerRegionMonotone& 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. const int ymin = rcMin(ri.ymin, rj.ymin); const int ymax = rcMin(ri.ymax, rj.ymax); if ((ymax - ymin) >= 255) continue; // Make sure that there is no overlap when mergin '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 (ri.layers.contains(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 == 0xffff) break; // Merge for (int j = 0; j < nregs; ++j) { rcLayerRegionMonotone& rj = regs[j]; if (rj.layerId == oldId) { rj.base = 0; // Remap layerIds. rj.layerId = newId; // Add overlaid layers from 'rj' to 'ri'. for (int k = 0; k < rj.layers.size(); ++k) addUnique(ri.layers, rj.layers[k]); // Update heigh bounds. ri.ymin = rcMin(ri.ymin, rj.ymin); ri.ymax = rcMax(ri.ymax, rj.ymax); } } } } // Compact layerIds layerId = 0; if (nregs < 256) { // Compact ids. unsigned short remap[256]; memset(remap, 0, sizeof(unsigned short)*256); // Find number of unique regions. for (int i = 0; i < nregs; ++i) remap[regs[i].layerId] = 1; for (int i = 0; i < 256; ++i) if (remap[i]) remap[i] = layerId++; // Remap ids. for (int i = 0; i < nregs; ++i) regs[i].layerId = remap[regs[i].layerId]; } else { for (int i = 0; i < nregs; ++i) regs[i].remap = true; for (int i = 0; i < nregs; ++i) { if (!regs[i].remap) continue; unsigned short oldId = regs[i].layerId; unsigned short newId = ++layerId; for (int j = i; j < nregs; ++j) { if (regs[j].layerId == oldId) { regs[j].layerId = newId; regs[j].remap = false; } } } } // No layers, return empty. if (layerId == 0) { ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; } // Create layers. rcAssert(lset.layers == 0); const int w = chf.width; const int h = chf.height; const int lw = w - borderSize*2; const int lh = h - borderSize*2; // Build contracted bbox for layers. float bmin[3], bmax[3]; rcVcopy(bmin, chf.bmin); rcVcopy(bmax, chf.bmax); bmin[0] += borderSize*chf.cs; bmin[2] += borderSize*chf.cs; bmax[0] -= borderSize*chf.cs; bmax[2] -= borderSize*chf.cs; lset.nlayers = (int)layerId; lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); if (!lset.layers) { ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'layers' (%d).", lset.nlayers); return false; } memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); // Store layers. for (int i = 0; i < lset.nlayers; ++i) { unsigned short curId = (unsigned short)i; // Allocate memory for the current layer. rcHeightfieldLayer* layer = &lset.layers[i]; memset(layer, 0, sizeof(rcHeightfieldLayer)); const int gridSize = sizeof(unsigned char)*lw*lh; layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->heights) { ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'heights' (%d).", gridSize); return false; } memset(layer->heights, 0xff, gridSize); layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->areas) { ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'areas' (%d).", gridSize); return false; } memset(layer->areas, 0, gridSize); layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->cons) { ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'cons' (%d).", gridSize); return false; } memset(layer->cons, 0, 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 = (int)regs[j].ymin; hmax = (int)regs[j].ymax; } } layer->width = lw; layer->height = lh; layer->cs = chf.cs; layer->ch = chf.ch; // Adjust the bbox to fit the heighfield. rcVcopy(layer->bmin, bmin); rcVcopy(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 heighfield. for (int y = 0; y < lh; ++y) { for (int x = 0; x < lw; ++x) { const int cx = borderSize+x; const int cy = borderSize+y; const rcCompactCell& c = chf.cells[cx+cy*w]; for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j) { const rcCompactSpan& s = chf.spans[j]; // Skip unassigned regions. if (srcReg[j] == 0xffff) continue; // Skip of does nto belong to current layer. unsigned short lid = regs[srcReg[j]].layerId; if (lid != curId) continue; // Update data bounds. layer->minx = rcMin(layer->minx, x); layer->maxx = rcMax(layer->maxx, x); layer->miny = rcMin(layer->miny, y); layer->maxy = rcMax(layer->maxy, y); // Store height and area type. const int idx = x+y*lw; layer->heights[idx] = (unsigned char)(s.y - hmin); layer->areas[idx] = chf.areas[j]; // Check connection. unsigned char portal = 0; unsigned char con = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); unsigned short alid = srcReg[ai] != 0xffff ? regs[srcReg[ai]].layerId : 0xffff; // Portal mask if (chf.areas[ai] != RC_NULL_AREA && lid != alid) { portal |= (unsigned char)(1<<dir); // Update height so that it matches on both sides of the portal. const rcCompactSpan& as = chf.spans[ai]; if (as.y > hmin) layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin)); } // Valid connection mask if (chf.areas[ai] != RC_NULL_AREA && lid == alid) { const int nx = ax - borderSize; const int ny = ay - borderSize; if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) { con |= (unsigned char)(1 << dir); // [UE4: make sure that connections are bidirectional, otherwise contour tracing will stuck in infinite loop] const int nidx = nx + (ny * lw); layer->cons[nidx] |= (unsigned char)(1 << ((dir + 2) % 4)); } } } } 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; } return true; }
static void walkContour(int x, int y, int i, int dir, rcCompactHeightfield& chf, unsigned short* srcReg, rcIntArray& cont) { int startDir = dir; int starti = i; const rcCompactSpan& ss = chf.spans[i]; unsigned short curReg = 0; if (rcGetCon(ss, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(ss, dir); curReg = srcReg[ai]; } cont.push(curReg); int iter = 0; while (++iter < 40000) { const rcCompactSpan& s = chf.spans[i]; if (isSolidEdge(chf, srcReg, x, y, i, dir)) { // Choose the edge corner unsigned short r = 0; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); r = srcReg[ai]; } if (r != curReg) { curReg = r; cont.push(curReg); } dir = (dir+1) & 0x3; // Rotate CW } else { int ni = -1; const int nx = x + rcGetDirOffsetX(dir); const int ny = y + rcGetDirOffsetY(dir); if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const rcCompactCell& nc = chf.cells[nx+ny*chf.width]; ni = (int)nc.index + rcGetCon(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; } } // Remove adjacent duplicates. if (cont.size() > 1) { for (int j = 0; j < cont.size(); ) { int nj = (j+1) % cont.size(); if (cont[j] == cont[nj]) { for (int k = j; k < cont.size()-1; ++k) cont[k] = cont[k+1]; cont.pop(); } else ++j; } } }
bool rcMedianFilterWalkableArea(rcCompactHeightfield& chf) { const int w = chf.width; const int h = chf.height; rcTimeVal startTime = rcGetPerformanceTimer(); unsigned char* areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); if (!areas) return false; // Init distance. memset(areas, 0xff, sizeof(unsigned char)*chf.spanCount); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) { areas[i] = chf.areas[i]; continue; } unsigned char nei[9]; for (int j = 0; j < 9; ++j) nei[j] = chf.areas[i]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); if (chf.areas[ai] != RC_NULL_AREA) nei[dir*2+0] = chf.areas[ai]; const rcCompactSpan& as = chf.spans[ai]; const int dir2 = (dir+1) & 0x3; if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) { const int ax2 = ax + rcGetDirOffsetX(dir2); const int ay2 = ay + rcGetDirOffsetY(dir2); const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); if (chf.areas[ai2] != RC_NULL_AREA) nei[dir*2+1] = chf.areas[ai2]; } } } insertSort(nei, 9); areas[i] = nei[4]; } } } memcpy(chf.areas, areas, sizeof(unsigned char)*chf.spanCount); rcFree(areas); rcTimeVal endTime = rcGetPerformanceTimer(); if (rcGetBuildTimes()) { rcGetBuildTimes()->filterMedian += rcGetDeltaTimeUsec(startTime, endTime); } return true; }