static bool canMergeWithRegion(rcRegion rega, rcRegion regb) { if (rega.areaType != regb.areaType) return false; int n = 0; for (int i = 0; i < rega.connections.Count; ++i) { if (rega.connections[i] == regb.id) n++; } if (n > 1) return false; for (int i = 0; i < rega.floors.Count; ++i) { if (rega.floors[i] == regb.id) return false; } return true; }
static bool filterSmallRegions(rcContext ctx, int minRegionArea, int mergeRegionSize, ref ushort maxRegionId, rcCompactHeightfield chf, ushort[] srcReg) { int w = chf.width; int h = chf.height; int nreg = maxRegionId+1; rcRegion[] regions = new rcRegion[nreg]; if (regions == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (" +nreg+ ")."); return false; } // Construct regions for (int i = 0; i < nreg; ++i){ regions[i] = new rcRegion((ushort) 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) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { ushort 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; ushort floorId = srcReg[j]; if (floorId == 0 || floorId >= nreg) continue; addUniqueFloorRegion(reg, floorId); } // Have found contour if (reg.connections.Count > 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. List<int> stack = new List<int>();//(32); List<int> trace= new List<int>();//(32); stack.Capacity = 32; trace.Capacity = 32; for (int i = 0; i < nreg; ++i) { rcRegion reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG) != 0) 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.Clear(); trace.Clear(); reg.visited = true; stack.Add(i); while (stack.Count != 0) { // Pop int ri = rccsPop(stack); rcRegion creg = regions[ri]; spanCount += creg.spanCount; trace.Add(ri); for (int j = 0; j < creg.connections.Count; ++j) { if ((creg.connections[j] & RC_BORDER_REG) != 0) { connectsToBorder = true; continue; } rcRegion neireg = regions[creg.connections[j]]; if (neireg.visited) continue; if (neireg.id == 0 || (neireg.id & RC_BORDER_REG) != 0) continue; // Visit stack.Add(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.Count; ++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) != 0) 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; ushort mergeId = reg.id; for (int j = 0; j < reg.connections.Count; ++j) { if ((reg.connections[j] & RC_BORDER_REG) != 0) continue; rcRegion mreg = regions[reg.connections[j]]; if (mreg.id == 0 || (mreg.id & RC_BORDER_REG) != 0) continue; if (mreg.spanCount < smallest && canMergeWithRegion(reg, mreg) && canMergeWithRegion(mreg, reg)) { smallest = mreg.spanCount; mergeId = mreg.id; } } // Found new id. if (mergeId != reg.id) { ushort 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) != 0) 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) != 0) continue; // Skip external regions. regions[i].remap = true; } ushort regIdGen = 0; for (int i = 0; i < nreg; ++i) { if (!regions[i].remap) continue; ushort oldId = regions[i].id; ushort 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; } return true; }
static void addUniqueFloorRegion(rcRegion reg, int n) { for (int i = 0; i < reg.floors.Count; ++i) if (reg.floors[i] == n) return; reg.floors.Add(n); }
static void replaceNeighbour(rcRegion reg, ushort oldId, ushort newId) { bool neiChanged = false; for (int i = 0; i < reg.connections.Count; ++i) { if (reg.connections[i] == oldId) { reg.connections[i] = newId; neiChanged = true; } } for (int i = 0; i < reg.floors.Count; ++i) { if (reg.floors[i] == oldId) reg.floors[i] = newId; } if (neiChanged) removeAdjacentNeighbours(reg); }
static void removeAdjacentNeighbours(rcRegion reg) { // Remove adjacent duplicates. for (int i = 0; i < reg.connections.Count && reg.connections.Count > 1; ) { int ni = (i+1) % reg.connections.Count; if (reg.connections[i] == reg.connections[ni]) { // Remove duplicate for (int j = i; j < reg.connections.Count-1; ++j){ reg.connections[j] = reg.connections[j+1]; } rccsPop(reg.connections); } else ++i; } }
static bool mergeRegions(rcRegion rega, rcRegion regb) { ushort aid = rega.id; ushort bid = regb.id; // Duplicate current neighbourhood. List<int> acon = new List<int>(); for (int i = 0; i < rega.connections.Count; ++i) acon.Add( rega.connections[i] ); List<int> bcon = regb.connections; // Find insertion point on A. int insa = -1; for (int i = 0; i < acon.Count; ++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.Count; ++i) { if (bcon[i] == aid) { insb = i; break; } } if (insb == -1) return false; // Merge neighbours. rega.connections.Clear(); for (int i = 0, ni = acon.Count; i < ni-1; ++i) rega.connections.Add(acon[(insa+1+i) % ni]); for (int i = 0, ni = bcon.Count; i < ni-1; ++i) rega.connections.Add(bcon[(insb+1+i) % ni]); removeAdjacentNeighbours(rega); for (int j = 0; j < regb.floors.Count; ++j) addUniqueFloorRegion(rega, regb.floors[j]); rega.spanCount += regb.spanCount; regb.spanCount = 0; regb.connections.Clear(); return true; }
static bool isRegionConnectedToBorder(rcRegion reg) { // Region is connected to border if // one of the neighbours is null id. for (int i = 0; i < reg.connections.Count; ++i) { if (reg.connections[i] == 0) return true; } return false; }