/// @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. /// /// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors. /// @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 rcBuildRegions(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; rcScopedDelete<unsigned short> buf = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); if (!buf) { ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4); return false; } ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); rcIntArray stack(1024); rcIntArray visited(1024); unsigned short* srcReg = buf; unsigned short* srcDist = buf+chf.spanCount; unsigned short* dstReg = buf+chf.spanCount*2; unsigned short* dstDist = buf+chf.spanCount*3; memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount); memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount); unsigned short regionId = 1; unsigned short level = (chf.maxDistance+1) & ~1; // TODO: Figure better formula, expandIters defines how much the // watershed "overflows" and simplifies the regions. Tying it to // agent radius was usually good indication how greedy it could be. // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; 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, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; chf.borderSize = borderSize; } while (level > 0) { level = level >= 2 ? level-2 : 0; ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND); // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND); ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD); // Mark new regions with IDs. 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) continue; if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) regionId++; } } } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD); } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); // Filter out small regions. chf.maxRegions = regionId; if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) return false; ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); // Write 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; }
/// @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. /// /// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors. /// @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 rcBuildRegions(rcCompactHeightfield& chf, const int borderSize, const int minRegionArea, const int mergeRegionArea) { const int w = chf.width; const int h = chf.height; rcScopedDelete<unsigned short> buf = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); if (!buf) { return false; } const int LOG_NB_STACKS = 3; const int NB_STACKS = 1 << LOG_NB_STACKS; rcIntArray lvlStacks[NB_STACKS]; for (int i=0; i<NB_STACKS; ++i) lvlStacks[i].resize(1024); rcIntArray stack(1024); rcIntArray visited(1024); unsigned short* srcReg = buf; unsigned short* srcDist = buf+chf.spanCount; unsigned short* dstReg = buf+chf.spanCount*2; unsigned short* dstDist = buf+chf.spanCount*3; memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount); memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount); unsigned short regionId = 1; unsigned short level = (chf.maxDistance+1) & ~1; // TODO: Figure better formula, expandIters defines how much the // watershed "overflows" and simplifies the regions. Tying it to // agent radius was usually good indication how greedy it could be. // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; 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, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; chf.borderSize = borderSize; } int sId = -1; while (level > 0) { level = level >= 2 ? level-2 : 0; sId = (sId+1) & (NB_STACKS-1); if (sId == 0) sortCellsByLevel(level, chf, srcReg, NB_STACKS, lvlStacks, 1); else appendStacks(lvlStacks[sId-1], lvlStacks[sId], srcReg); // copy left overs from last level // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, lvlStacks[sId], false) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } // Mark new regions with IDs. for (int j=0; j<lvlStacks[sId].size(); j+=3) { int x = lvlStacks[sId][j]; int y = lvlStacks[sId][j+1]; int i = lvlStacks[sId][j+2]; if (i >= 0 && srcReg[i] == 0) { if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) regionId++; } } } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack, true) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } // Filter out small regions. chf.maxRegions = regionId; if (!filterSmallRegions(minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) return false; // Write the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; return true; }
/// @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 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; }
bool rcBuildRegions(rcCompactHeightfield& chf, int borderSize, int minRegionSize, int mergeRegionSize) { rcTimeVal startTime = rcGetPerformanceTimer(); const int w = chf.width; const int h = chf.height; if (!chf.regs) { chf.regs = new unsigned short[chf.spanCount]; if (!chf.regs) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'chf.reg' (%d).", chf.spanCount); return false; } } rcScopedDelete<unsigned short> tmp = new unsigned short[chf.spanCount*4]; if (!tmp) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4); return false; } rcTimeVal regStartTime = rcGetPerformanceTimer(); rcIntArray stack(1024); rcIntArray visited(1024); unsigned short* srcReg = tmp; unsigned short* srcDist = tmp+chf.spanCount; unsigned short* dstReg = tmp+chf.spanCount*2; unsigned short* dstDist = tmp+chf.spanCount*3; memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount); memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount); unsigned short regionId = 1; unsigned short level = (chf.maxDistance+1) & ~1; // TODO: Figure better formula, expandIters defines how much the // watershed "overflows" and simplifies the regions. Tying it to // agent radius was usually good indication how greedy it could be. // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; // Mark border regions. paintRectRegion(0, borderSize, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(w-borderSize, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, 0, borderSize, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, h-borderSize, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; rcTimeVal expTime = 0; rcTimeVal floodTime = 0; while (level > 0) { level = level >= 2 ? level-2 : 0; rcTimeVal expStartTime = rcGetPerformanceTimer(); // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } expTime += rcGetPerformanceTimer() - expStartTime; rcTimeVal floodStartTime = rcGetPerformanceTimer(); // Mark new regions with IDs. 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) continue; if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) regionId++; } } } floodTime += rcGetPerformanceTimer() - floodStartTime; } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } rcTimeVal regEndTime = rcGetPerformanceTimer(); rcTimeVal filterStartTime = rcGetPerformanceTimer(); // Filter out small regions. chf.maxRegions = regionId; if (!filterSmallRegions(minRegionSize, mergeRegionSize, chf.maxRegions, chf, srcReg)) return false; rcTimeVal filterEndTime = rcGetPerformanceTimer(); // Write the result out. memcpy(chf.regs, srcReg, sizeof(unsigned short)*chf.spanCount); rcTimeVal endTime = rcGetPerformanceTimer(); /* if (rcGetLog()) { rcGetLog()->log(RC_LOG_PROGRESS, "Build regions: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f); rcGetLog()->log(RC_LOG_PROGRESS, " - reg: %.3f ms", rcGetDeltaTimeUsec(regStartTime, regEndTime)/1000.0f); rcGetLog()->log(RC_LOG_PROGRESS, " - exp: %.3f ms", rcGetDeltaTimeUsec(0, expTime)/1000.0f); rcGetLog()->log(RC_LOG_PROGRESS, " - flood: %.3f ms", rcGetDeltaTimeUsec(0, floodTime)/1000.0f); rcGetLog()->log(RC_LOG_PROGRESS, " - filter: %.3f ms", rcGetDeltaTimeUsec(filterStartTime, filterEndTime)/1000.0f); } */ if (rcGetBuildTimes()) { rcGetBuildTimes()->buildRegions += rcGetDeltaTimeUsec(startTime, endTime); rcGetBuildTimes()->buildRegionsReg += rcGetDeltaTimeUsec(regStartTime, regEndTime); rcGetBuildTimes()->buildRegionsExp += rcGetDeltaTimeUsec(0, expTime); rcGetBuildTimes()->buildRegionsFlood += rcGetDeltaTimeUsec(0, floodTime); rcGetBuildTimes()->buildRegionsFilter += rcGetDeltaTimeUsec(filterStartTime, filterEndTime); } return true; }
/// @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. /// /// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors. /// @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 rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int minRegionArea, const int mergeRegionArea) { rcAssert(ctx); rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS); const int w = chf.width; const int h = chf.height; rcScopedDelete<unsigned short> buf((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP)); if (!buf) { ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4); return false; } ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); const int LOG_NB_STACKS = 3; const int NB_STACKS = 1 << LOG_NB_STACKS; rcIntArray lvlStacks[NB_STACKS]; for (int i=0; i<NB_STACKS; ++i) lvlStacks[i].resize(1024); rcIntArray stack(1024); rcIntArray visited(1024); unsigned short* srcReg = buf; unsigned short* srcDist = buf+chf.spanCount; unsigned short* dstReg = buf+chf.spanCount*2; unsigned short* dstDist = buf+chf.spanCount*3; memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount); memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount); unsigned short regionId = 1; unsigned short level = (chf.maxDistance+1) & ~1; // TODO: Figure better formula, expandIters defines how much the // watershed "overflows" and simplifies the regions. Tying it to // agent radius was usually good indication how greedy it could be. // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; 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, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; chf.borderSize = borderSize; } int sId = -1; while (level > 0) { level = level >= 2 ? level-2 : 0; sId = (sId+1) & (NB_STACKS-1); // ctx->startTimer(RC_TIMER_DIVIDE_TO_LEVELS); if (sId == 0) sortCellsByLevel(level, chf, srcReg, NB_STACKS, lvlStacks, 1); else appendStacks(lvlStacks[sId-1], lvlStacks[sId], srcReg); // copy left overs from last level // ctx->stopTimer(RC_TIMER_DIVIDE_TO_LEVELS); { rcScopedTimer timerExpand(ctx, RC_TIMER_BUILD_REGIONS_EXPAND); // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, lvlStacks[sId], false) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } } { rcScopedTimer timerFloor(ctx, RC_TIMER_BUILD_REGIONS_FLOOD); // Mark new regions with IDs. for (int j = 0; j<lvlStacks[sId].size(); j += 3) { int x = lvlStacks[sId][j]; int y = lvlStacks[sId][j+1]; int i = lvlStacks[sId][j+2]; if (i >= 0 && srcReg[i] == 0) { if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) { if (regionId == 0xFFFF) { ctx->log(RC_LOG_ERROR, "rcBuildRegions: Region ID overflow"); return false; } regionId++; } } } } } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack, true) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); { rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER); // Merge regions and filter out smalle regions. rcIntArray overlaps; chf.maxRegions = regionId; if (!mergeAndFilterRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg, overlaps)) return false; // If overlapping regions were found during merging, split those regions. if (overlaps.size() > 0) { ctx->log(RC_LOG_ERROR, "rcBuildRegions: %d overlapping regions.", overlaps.size()); } } // Write the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; return true; }
dtStatus dtBuildTileCacheRegions(dtTileCacheAlloc* alloc, const int borderSize, const int minRegionArea, const int mergeRegionArea, dtTileCacheLayer& layer, dtTileCacheDistanceField dfield) { dtAssert(alloc); const int w = (int)layer.header->width; const int h = (int)layer.header->height; const int size = w*h; dtFixedArray<unsigned short> buf(alloc, size*4); if (!buf) { return DT_FAILURE | DT_OUT_OF_MEMORY; } dtIntArray stack(1024); dtIntArray visited(1024); unsigned short* srcReg = buf; unsigned short* srcDist = buf+size; unsigned short* dstReg = buf+size*2; unsigned short* dstDist = buf+size*3; memset(srcReg, 0, sizeof(unsigned short)*size); memset(srcDist, 0, sizeof(unsigned short)*size); unsigned short regionId = 1; unsigned short level = (dfield.maxDist+1) & ~1; // TODO: Figure better formula, expandIters defines how much the // watershed "overflows" and simplifies the regions. Tying it to // agent radius was usually good indication how greedy it could be. // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; if (borderSize > 0) { // Make sure border will not overflow. const int bw = dtMin(w, borderSize); const int bh = dtMin(h, borderSize); // Paint regions paintRectRegion(0, bw, 0, h, regionId|DT_BORDER_REG, layer, srcReg); regionId++; paintRectRegion(w-bw, w, 0, h, regionId|DT_BORDER_REG, layer, srcReg); regionId++; paintRectRegion(0, w, 0, bh, regionId|DT_BORDER_REG, layer, srcReg); regionId++; paintRectRegion(0, w, h-bh, h, regionId|DT_BORDER_REG, layer, srcReg); regionId++; } while (level > 0) { level = level >= 2 ? level-2 : 0; // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, layer, dfield, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { dtSwap(srcReg, dstReg); dtSwap(srcDist, dstDist); } // Mark new regions with IDs. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const int i=x+y*w; if (dfield.data[i] < level || srcReg[i] != 0 || layer.areas[i] == DT_TILECACHE_NULL_AREA) continue; if (floodRegion(x, y, i, level, regionId, layer, dfield, srcReg, srcDist, stack)) regionId++; } } } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, layer, dfield, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { dtSwap(srcReg, dstReg); dtSwap(srcDist, dstDist); } dtStatus status = filterSmallRegions(alloc, layer, minRegionArea, mergeRegionArea, regionId, srcReg); if (dtStatusFailed(status)) { return status; } // Write the result out. memcpy(layer.regs, srcReg, sizeof(unsigned short)*size); layer.regCount = regionId; return DT_SUCCESS; }
/// @UE4: rcBuildRegions is now split into two functions: gathering regions and merging them /// it allows reusing existing code for building layer set (flood fill based partitioning) /// /// spanBuf4 is temporary buffer, allocated and freed by caller (size = chf.spanCount * 4) /// bool rcGatherRegionsNoFilter(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, unsigned short* spanBuf4) { const int w = chf.width; const int h = chf.height; rcIntArray stack(1024); rcIntArray visited(1024); unsigned short* srcReg = spanBuf4; unsigned short* srcDist = spanBuf4+chf.spanCount; unsigned short* dstReg = spanBuf4+chf.spanCount*2; unsigned short* dstDist = spanBuf4+chf.spanCount*3; memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount); memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount); unsigned short regionId = 1; unsigned short level = (chf.maxDistance+1) & ~1; // TODO: Figure better formula, expandIters defines how much the // watershed "overflows" and simplifies the regions. Tying it to // agent radius was usually good indication how greedy it could be. // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; 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, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; chf.borderSize = borderSize; } while (level > 0) { level = level >= 2 ? level-2 : 0; ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND); // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND); ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD); // Mark new regions with IDs. 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) continue; if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) regionId++; } } } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD); } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } chf.maxRegions = regionId; return true; }