static bool filterSmallRegions(int minRegionSize, int mergeRegionSize, unsigned short& maxRegionId, rcCompactHeightfield& chf, unsigned short* src) { const int w = chf.width; const int h = chf.height; int nreg = maxRegionId+1; rcRegion* regions = new rcRegion[nreg]; if (!regions) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg); return false; } for (int i = 0; i < nreg; ++i) regions[i].id = (unsigned short)i; // Find edge of a region and find connections around the contour. 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 short r = src[i*2]; if (r == 0 || r >= nreg) continue; rcRegion& reg = regions[r]; reg.count++; // Update floors. for (int j = (int)c.index; j < ni; ++j) { if (i == j) continue; unsigned short floorId = src[j*2]; if (floorId == 0 || floorId >= nreg) continue; addUniqueFloorRegion(reg, floorId); } // 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, src, x, y, i, dir)) { ndir = dir; break; } } if (ndir != -1) { // The cell is at border. // Walk around the contour to find all the neighbours. walkContour(x, y, i, ndir, chf, src, reg.connections); } } } } // Remove too small unconnected regions. for (int i = 0; i < nreg; ++i) { rcRegion& reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG)) continue; if (reg.count == 0) continue; if (reg.connections.size() == 1 && reg.connections[0] == 0) { if (reg.count < minRegionSize) { // Non-connected small region, remove. reg.count = 0; reg.id = 0; } } } // Merge too small regions to neighbour regions. int mergeCount = 0 ; do { mergeCount = 0; for (int i = 0; i < nreg; ++i) { rcRegion& reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG)) continue; if (reg.count == 0) continue; // Check to see if the region should be merged. if (reg.count > mergeRegionSize && isRegionConnectedToBorder(reg)) continue; // Small region with more than 1 connection. // Or region which is not connected to a border at all. // Find smallest neighbour region that connects to this one. int smallest = 0xfffffff; unsigned short mergeId = reg.id; for (int j = 0; j < reg.connections.size(); ++j) { if (reg.connections[j] & RC_BORDER_REG) continue; rcRegion& mreg = regions[reg.connections[j]]; if (mreg.id == 0 || (mreg.id & RC_BORDER_REG)) continue; if (mreg.count < smallest && canMergeWithRegion(reg, mreg.id) && canMergeWithRegion(mreg, reg.id)) { smallest = mreg.count; mergeId = mreg.id; } } // Found new id. if (mergeId != reg.id) { unsigned short oldId = reg.id; rcRegion& target = regions[mergeId]; // Merge neighbours. if (mergeRegions(target, reg)) { // Fixup regions pointing to current region. for (int j = 0; j < nreg; ++j) { if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG)) continue; // If another region was already merged into current region // change the nid of the previous region too. if (regions[j].id == oldId) regions[j].id = mergeId; // Replace the current region with the new one if the // current regions is neighbour. replaceNeighbour(regions[j], oldId, mergeId); } mergeCount++; } } } } while (mergeCount > 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 ((src[i*2] & RC_BORDER_REG) == 0) src[i*2] = regions[src[i*2]].id; } delete [] regions; return true; }
static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegionSize, unsigned short& maxRegionId, rcCompactHeightfield& chf, unsigned short* srcReg) { 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, "filterSmallRegions: 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 edge of a region and find connections around the contour. 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 short r = srcReg[i]; if (r == 0 || r >= nreg) continue; rcRegion& reg = regions[r]; reg.spanCount++; // Update floors. for (int j = (int)c.index; j < ni; ++j) { if (i == j) continue; unsigned short floorId = srcReg[j]; if (floorId == 0 || floorId >= nreg) continue; addUniqueFloorRegion(reg, floorId); } // Have found contour if (reg.connections.size() > 0) continue; reg.areaType = chf.areas[i]; // 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 neighbours. walkContour(x, y, i, ndir, chf, srcReg, reg.connections); } } } } // Remove too small regions. rcIntArray stack(32); rcIntArray trace(32); for (int i = 0; i < nreg; ++i) { rcRegion& reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG)) continue; if (reg.spanCount == 0) continue; if (reg.visited) continue; // Count the total size of all the connected regions. // Also keep track of the regions connects to a tile border. bool connectsToBorder = false; int spanCount = 0; stack.resize(0); trace.resize(0); reg.visited = true; stack.push(i); while (stack.size()) { // Pop int ri = stack.pop(); rcRegion& creg = regions[ri]; spanCount += creg.spanCount; trace.push(ri); for (int j = 0; j < creg.connections.size(); ++j) { if (creg.connections[j] & RC_BORDER_REG) { connectsToBorder = true; continue; } rcRegion& neireg = regions[creg.connections[j]]; if (neireg.visited) continue; if (neireg.id == 0 || (neireg.id & RC_BORDER_REG)) continue; // Visit stack.push(neireg.id); neireg.visited = true; } } // If the accumulated regions size is too small, remove it. // Do not remove areas which connect to tile borders // as their size cannot be estimated correctly and removing them // can potentially remove necessary areas. if (spanCount < minRegionArea && !connectsToBorder) { // Kill all visited regions. for (int j = 0; j < trace.size(); ++j) { regions[trace[j]].spanCount = 0; regions[trace[j]].id = 0; } } } // Merge too small regions to neighbour regions. int mergeCount = 0 ; do { mergeCount = 0; for (int i = 0; i < nreg; ++i) { rcRegion& reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG)) continue; if (reg.spanCount == 0) continue; // Check to see if the region should be merged. if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(reg)) continue; // Small region with more than 1 connection. // Or region which is not connected to a border at all. // Find smallest neighbour region that connects to this one. int smallest = 0xfffffff; unsigned short mergeId = reg.id; for (int j = 0; j < reg.connections.size(); ++j) { if (reg.connections[j] & RC_BORDER_REG) continue; rcRegion& mreg = regions[reg.connections[j]]; if (mreg.id == 0 || (mreg.id & RC_BORDER_REG)) continue; if (mreg.spanCount < smallest && canMergeWithRegion(reg, mreg) && canMergeWithRegion(mreg, reg)) { smallest = mreg.spanCount; mergeId = mreg.id; } } // Found new id. if (mergeId != reg.id) { unsigned short oldId = reg.id; rcRegion& target = regions[mergeId]; // Merge neighbours. if (mergeRegions(target, reg)) { // Fixup regions pointing to current region. for (int j = 0; j < nreg; ++j) { if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG)) continue; // If another region was already merged into current region // change the nid of the previous region too. if (regions[j].id == oldId) regions[j].id = mergeId; // Replace the current region with the new one if the // current regions is neighbour. replaceNeighbour(regions[j], oldId, mergeId); } mergeCount++; } } } } while (mergeCount > 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 dtStatus filterSmallRegions(dtTileCacheAlloc* alloc, dtTileCacheLayer& layer, int minRegionArea, int mergeRegionSize, unsigned short& maxRegionId, unsigned short* srcReg) { const int w = (int)layer.header->width; const int h = (int)layer.header->height; const int nreg = maxRegionId+1; dtFixedArray<dtLayerRegion> regions(alloc, nreg); if (!regions) { return DT_FAILURE | DT_OUT_OF_MEMORY; } // Construct regions regions.set(0); for (int i = 0; i < nreg; ++i) regions[i] = dtLayerRegion((unsigned short)i); // Find edge of a region and find connections around the contour. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const int i = x+y*w; unsigned short r = srcReg[i]; if (r == DT_TILECACHE_NULL_AREA || r >= nreg) continue; dtLayerRegion& reg = regions[r]; reg.cellCount++; // Have found contour if (reg.connections.size() > 0) continue; reg.areaType = layer.areas[i]; // Check if this cell is next to a border. int ndir = -1; for (int dir = 0; dir < 4; ++dir) { if (isSolidEdge(layer, srcReg, x, y, i, dir)) { ndir = dir; break; } } if (ndir != -1) { // The cell is at border. // Walk around the contour to find all the neighbours. walkContour(x, y, i, ndir, layer, srcReg, reg.connections); } } } // Remove too small regions. dtIntArray stack(32); dtIntArray trace(32); for (int i = 0; i < nreg; ++i) { dtLayerRegion& reg = regions[i]; if (reg.id == 0 || (reg.id & DT_BORDER_REG)) continue; if (reg.cellCount == 0) continue; if (reg.visited) continue; // Count the total size of all the connected regions. // Also keep track of the regions connects to a tile border. bool connectsToBorder = false; int cellCount = 0; stack.resize(0); trace.resize(0); reg.visited = true; stack.push(i); while (stack.size()) { // Pop int ri = stack.pop(); dtLayerRegion& creg = regions[ri]; cellCount += creg.cellCount; trace.push(ri); for (int j = 0; j < creg.connections.size(); ++j) { if (creg.connections[j] & DT_BORDER_REG) { connectsToBorder = true; continue; } dtLayerRegion& neireg = regions[creg.connections[j]]; if (neireg.visited) continue; if (neireg.id == 0 || (neireg.id & DT_BORDER_REG)) continue; // Visit stack.push(neireg.id); neireg.visited = true; } } // If the accumulated regions size is too small, remove it. // Do not remove areas which connect to tile borders // as their size cannot be estimated correctly and removing them // can potentially remove necessary areas. if (cellCount < minRegionArea && !connectsToBorder) { // Kill all visited regions. for (int j = 0; j < trace.size(); ++j) { regions[trace[j]].cellCount = 0; regions[trace[j]].id = 0; } } } // Merge too small regions to neighbour regions. int mergeCount = 0 ; do { mergeCount = 0; for (int i = 0; i < nreg; ++i) { dtLayerRegion& reg = regions[i]; if (reg.id == 0 || (reg.id & DT_BORDER_REG)) continue; if (reg.cellCount == 0) continue; // Check to see if the region should be merged. if (reg.cellCount > mergeRegionSize && isRegionConnectedToBorder(reg)) continue; // Small region with more than 1 connection. // Or region which is not connected to a border at all. // Find smallest neighbour region that connects to this one. int smallest = 0xfffffff; unsigned short mergeId = reg.id; for (int j = 0; j < reg.connections.size(); ++j) { if (reg.connections[j] & DT_BORDER_REG) continue; dtLayerRegion& mreg = regions[reg.connections[j]]; if (mreg.id == 0 || (mreg.id & DT_BORDER_REG)) continue; if (mreg.cellCount < smallest && canMergeWithRegion(reg, mreg) && canMergeWithRegion(mreg, reg)) { smallest = mreg.cellCount; mergeId = mreg.id; } } // Found new id. if (mergeId != reg.id) { unsigned short oldId = reg.id; dtLayerRegion& target = regions[mergeId]; // Merge neighbours. if (mergeRegions(target, reg)) { // Fixup regions pointing to current region. for (int j = 0; j < nreg; ++j) { if (regions[j].id == 0 || (regions[j].id & DT_BORDER_REG)) continue; // If another region was already merged into current region // change the nid of the previous region too. if (regions[j].id == oldId) regions[j].id = mergeId; // Replace the current region with the new one if the // current regions is neighbour. replaceNeighbour(regions[j], oldId, mergeId); } mergeCount++; } } } } while (mergeCount > 0); // Compress region Ids. for (int i = 0; i < nreg; ++i) { regions[i].remap = false; if (regions[i].id == DT_TILECACHE_NULL_AREA) continue; // Skip nil regions. if (regions[i].id & DT_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 = w*h-1; i >= 0; i--) { if ((srcReg[i] & DT_BORDER_REG) == 0) srcReg[i] = regions[srcReg[i]].id; } for (int i = 0; i < nreg; ++i) regions[i].~dtLayerRegion(); return DT_SUCCESS; }