static bool mergeRegions(rcRegion& rega, rcRegion& regb) { unsigned short aid = rega.id; unsigned short bid = regb.id; // Duplicate current neighbourhood. rcIntArray acon; acon.resize(rega.connections.size()); for (int i = 0; i < rega.connections.size(); ++i) acon[i] = rega.connections[i]; rcIntArray& bcon = regb.connections; // Find insertion point on A. int insa = -1; for (int i = 0; i < acon.size(); ++i) { if (acon[i] == bid) { insa = i; break; } } if (insa == -1) return false; // Find insertion point on B. int insb = -1; for (int i = 0; i < bcon.size(); ++i) { if (bcon[i] == aid) { insb = i; break; } } if (insb == -1) return false; // Merge neighbours. rega.connections.resize(0); for (int i = 0, ni = acon.size(); i < ni-1; ++i) rega.connections.push(acon[(insa+1+i) % ni]); for (int i = 0, ni = bcon.size(); i < ni-1; ++i) rega.connections.push(bcon[(insb+1+i) % ni]); removeAdjacentNeighbours(rega); for (int j = 0; j < regb.floors.size(); ++j) addUniqueFloorRegion(rega, regb.floors[j]); rega.spanCount += regb.spanCount; regb.spanCount = 0; regb.connections.resize(0); 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 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 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; }