Path: blob/master/thirdparty/recastnavigation/Recast/Source/RecastRegion.cpp
9913 views
//1// Copyright (c) 2009-2010 Mikko Mononen [email protected]2//3// This software is provided 'as-is', without any express or implied4// warranty. In no event will the authors be held liable for any damages5// arising from the use of this software.6// Permission is granted to anyone to use this software for any purpose,7// including commercial applications, and to alter it and redistribute it8// freely, subject to the following restrictions:9// 1. The origin of this software must not be misrepresented; you must not10// claim that you wrote the original software. If you use this software11// in a product, an acknowledgment in the product documentation would be12// appreciated but is not required.13// 2. Altered source versions must be plainly marked as such, and must not be14// misrepresented as being the original software.15// 3. This notice may not be removed or altered from any source distribution.16//1718#include <float.h>19#include <math.h>20#include <string.h>21#include <stdlib.h>22#include <stdio.h>23#include "Recast.h"24#include "RecastAlloc.h"25#include "RecastAssert.h"2627namespace28{29struct LevelStackEntry30{31LevelStackEntry(int x_, int y_, int index_) : x(x_), y(y_), index(index_) {}32int x;33int y;34int index;35};36} // namespace3738static void calculateDistanceField(rcCompactHeightfield& chf, unsigned short* src, unsigned short& maxDist)39{40const int w = chf.width;41const int h = chf.height;4243// Init distance and points.44for (int i = 0; i < chf.spanCount; ++i)45src[i] = 0xffff;4647// Mark boundary cells.48for (int y = 0; y < h; ++y)49{50for (int x = 0; x < w; ++x)51{52const rcCompactCell& c = chf.cells[x+y*w];53for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)54{55const rcCompactSpan& s = chf.spans[i];56const unsigned char area = chf.areas[i];5758int nc = 0;59for (int dir = 0; dir < 4; ++dir)60{61if (rcGetCon(s, dir) != RC_NOT_CONNECTED)62{63const int ax = x + rcGetDirOffsetX(dir);64const int ay = y + rcGetDirOffsetY(dir);65const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);66if (area == chf.areas[ai])67nc++;68}69}70if (nc != 4)71src[i] = 0;72}73}74}757677// Pass 178for (int y = 0; y < h; ++y)79{80for (int x = 0; x < w; ++x)81{82const rcCompactCell& c = chf.cells[x+y*w];83for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)84{85const rcCompactSpan& s = chf.spans[i];8687if (rcGetCon(s, 0) != RC_NOT_CONNECTED)88{89// (-1,0)90const int ax = x + rcGetDirOffsetX(0);91const int ay = y + rcGetDirOffsetY(0);92const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0);93const rcCompactSpan& as = chf.spans[ai];94if (src[ai]+2 < src[i])95src[i] = src[ai]+2;9697// (-1,-1)98if (rcGetCon(as, 3) != RC_NOT_CONNECTED)99{100const int aax = ax + rcGetDirOffsetX(3);101const int aay = ay + rcGetDirOffsetY(3);102const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3);103if (src[aai]+3 < src[i])104src[i] = src[aai]+3;105}106}107if (rcGetCon(s, 3) != RC_NOT_CONNECTED)108{109// (0,-1)110const int ax = x + rcGetDirOffsetX(3);111const int ay = y + rcGetDirOffsetY(3);112const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3);113const rcCompactSpan& as = chf.spans[ai];114if (src[ai]+2 < src[i])115src[i] = src[ai]+2;116117// (1,-1)118if (rcGetCon(as, 2) != RC_NOT_CONNECTED)119{120const int aax = ax + rcGetDirOffsetX(2);121const int aay = ay + rcGetDirOffsetY(2);122const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2);123if (src[aai]+3 < src[i])124src[i] = src[aai]+3;125}126}127}128}129}130131// Pass 2132for (int y = h-1; y >= 0; --y)133{134for (int x = w-1; x >= 0; --x)135{136const rcCompactCell& c = chf.cells[x+y*w];137for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)138{139const rcCompactSpan& s = chf.spans[i];140141if (rcGetCon(s, 2) != RC_NOT_CONNECTED)142{143// (1,0)144const int ax = x + rcGetDirOffsetX(2);145const int ay = y + rcGetDirOffsetY(2);146const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2);147const rcCompactSpan& as = chf.spans[ai];148if (src[ai]+2 < src[i])149src[i] = src[ai]+2;150151// (1,1)152if (rcGetCon(as, 1) != RC_NOT_CONNECTED)153{154const int aax = ax + rcGetDirOffsetX(1);155const int aay = ay + rcGetDirOffsetY(1);156const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1);157if (src[aai]+3 < src[i])158src[i] = src[aai]+3;159}160}161if (rcGetCon(s, 1) != RC_NOT_CONNECTED)162{163// (0,1)164const int ax = x + rcGetDirOffsetX(1);165const int ay = y + rcGetDirOffsetY(1);166const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1);167const rcCompactSpan& as = chf.spans[ai];168if (src[ai]+2 < src[i])169src[i] = src[ai]+2;170171// (-1,1)172if (rcGetCon(as, 0) != RC_NOT_CONNECTED)173{174const int aax = ax + rcGetDirOffsetX(0);175const int aay = ay + rcGetDirOffsetY(0);176const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0);177if (src[aai]+3 < src[i])178src[i] = src[aai]+3;179}180}181}182}183}184185maxDist = 0;186for (int i = 0; i < chf.spanCount; ++i)187maxDist = rcMax(src[i], maxDist);188189}190191static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr,192unsigned short* src, unsigned short* dst)193{194const int w = chf.width;195const int h = chf.height;196197thr *= 2;198199for (int y = 0; y < h; ++y)200{201for (int x = 0; x < w; ++x)202{203const rcCompactCell& c = chf.cells[x+y*w];204for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)205{206const rcCompactSpan& s = chf.spans[i];207const unsigned short cd = src[i];208if (cd <= thr)209{210dst[i] = cd;211continue;212}213214int d = (int)cd;215for (int dir = 0; dir < 4; ++dir)216{217if (rcGetCon(s, dir) != RC_NOT_CONNECTED)218{219const int ax = x + rcGetDirOffsetX(dir);220const int ay = y + rcGetDirOffsetY(dir);221const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);222d += (int)src[ai];223224const rcCompactSpan& as = chf.spans[ai];225const int dir2 = (dir+1) & 0x3;226if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)227{228const int ax2 = ax + rcGetDirOffsetX(dir2);229const int ay2 = ay + rcGetDirOffsetY(dir2);230const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);231d += (int)src[ai2];232}233else234{235d += cd;236}237}238else239{240d += cd*2;241}242}243dst[i] = (unsigned short)((d+5)/9);244}245}246}247return dst;248}249250251static bool floodRegion(int x, int y, int i,252unsigned short level, unsigned short r,253rcCompactHeightfield& chf,254unsigned short* srcReg, unsigned short* srcDist,255rcTempVector<LevelStackEntry>& stack)256{257const int w = chf.width;258259const unsigned char area = chf.areas[i];260261// Flood fill mark region.262stack.clear();263stack.push_back(LevelStackEntry(x, y, i));264srcReg[i] = r;265srcDist[i] = 0;266267unsigned short lev = level >= 2 ? level-2 : 0;268int count = 0;269270while (stack.size() > 0)271{272LevelStackEntry& back = stack.back();273int cx = back.x;274int cy = back.y;275int ci = back.index;276stack.pop_back();277278const rcCompactSpan& cs = chf.spans[ci];279280// Check if any of the neighbours already have a valid region set.281unsigned short ar = 0;282for (int dir = 0; dir < 4; ++dir)283{284// 8 connected285if (rcGetCon(cs, dir) != RC_NOT_CONNECTED)286{287const int ax = cx + rcGetDirOffsetX(dir);288const int ay = cy + rcGetDirOffsetY(dir);289const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);290if (chf.areas[ai] != area)291continue;292unsigned short nr = srcReg[ai];293if (nr & RC_BORDER_REG) // Do not take borders into account.294continue;295if (nr != 0 && nr != r)296{297ar = nr;298break;299}300301const rcCompactSpan& as = chf.spans[ai];302303const int dir2 = (dir+1) & 0x3;304if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)305{306const int ax2 = ax + rcGetDirOffsetX(dir2);307const int ay2 = ay + rcGetDirOffsetY(dir2);308const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);309if (chf.areas[ai2] != area)310continue;311unsigned short nr2 = srcReg[ai2];312if (nr2 != 0 && nr2 != r)313{314ar = nr2;315break;316}317}318}319}320if (ar != 0)321{322srcReg[ci] = 0;323continue;324}325326count++;327328// Expand neighbours.329for (int dir = 0; dir < 4; ++dir)330{331if (rcGetCon(cs, dir) != RC_NOT_CONNECTED)332{333const int ax = cx + rcGetDirOffsetX(dir);334const int ay = cy + rcGetDirOffsetY(dir);335const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);336if (chf.areas[ai] != area)337continue;338if (chf.dist[ai] >= lev && srcReg[ai] == 0)339{340srcReg[ai] = r;341srcDist[ai] = 0;342stack.push_back(LevelStackEntry(ax, ay, ai));343}344}345}346}347348return count > 0;349}350351// Struct to keep track of entries in the region table that have been changed.352struct DirtyEntry353{354DirtyEntry(int index_, unsigned short region_, unsigned short distance2_)355: index(index_), region(region_), distance2(distance2_) {}356int index;357unsigned short region;358unsigned short distance2;359};360static void expandRegions(int maxIter, unsigned short level,361rcCompactHeightfield& chf,362unsigned short* srcReg, unsigned short* srcDist,363rcTempVector<LevelStackEntry>& stack,364bool fillStack)365{366const int w = chf.width;367const int h = chf.height;368369if (fillStack)370{371// Find cells revealed by the raised level.372stack.clear();373for (int y = 0; y < h; ++y)374{375for (int x = 0; x < w; ++x)376{377const rcCompactCell& c = chf.cells[x+y*w];378for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)379{380if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA)381{382stack.push_back(LevelStackEntry(x, y, i));383}384}385}386}387}388else // use cells in the input stack389{390// mark all cells which already have a region391for (int j=0; j<stack.size(); j++)392{393int i = stack[j].index;394if (srcReg[i] != 0)395stack[j].index = -1;396}397}398399rcTempVector<DirtyEntry> dirtyEntries;400int iter = 0;401while (stack.size() > 0)402{403int failed = 0;404dirtyEntries.clear();405406for (int j = 0; j < stack.size(); j++)407{408int x = stack[j].x;409int y = stack[j].y;410int i = stack[j].index;411if (i < 0)412{413failed++;414continue;415}416417unsigned short r = srcReg[i];418unsigned short d2 = 0xffff;419const unsigned char area = chf.areas[i];420const rcCompactSpan& s = chf.spans[i];421for (int dir = 0; dir < 4; ++dir)422{423if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue;424const int ax = x + rcGetDirOffsetX(dir);425const int ay = y + rcGetDirOffsetY(dir);426const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);427if (chf.areas[ai] != area) continue;428if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0)429{430if ((int)srcDist[ai]+2 < (int)d2)431{432r = srcReg[ai];433d2 = srcDist[ai]+2;434}435}436}437if (r)438{439stack[j].index = -1; // mark as used440dirtyEntries.push_back(DirtyEntry(i, r, d2));441}442else443{444failed++;445}446}447448// Copy entries that differ between src and dst to keep them in sync.449for (int i = 0; i < dirtyEntries.size(); i++) {450int idx = dirtyEntries[i].index;451srcReg[idx] = dirtyEntries[i].region;452srcDist[idx] = dirtyEntries[i].distance2;453}454455if (failed == stack.size())456break;457458if (level > 0)459{460++iter;461if (iter >= maxIter)462break;463}464}465}466467468469static void sortCellsByLevel(unsigned short startLevel,470rcCompactHeightfield& chf,471const unsigned short* srcReg,472unsigned int nbStacks, rcTempVector<LevelStackEntry>* stacks,473unsigned short loglevelsPerStack) // the levels per stack (2 in our case) as a bit shift474{475const int w = chf.width;476const int h = chf.height;477startLevel = startLevel >> loglevelsPerStack;478479for (unsigned int j=0; j<nbStacks; ++j)480stacks[j].clear();481482// put all cells in the level range into the appropriate stacks483for (int y = 0; y < h; ++y)484{485for (int x = 0; x < w; ++x)486{487const rcCompactCell& c = chf.cells[x+y*w];488for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)489{490if (chf.areas[i] == RC_NULL_AREA || srcReg[i] != 0)491continue;492493int level = chf.dist[i] >> loglevelsPerStack;494int sId = startLevel - level;495if (sId >= (int)nbStacks)496continue;497if (sId < 0)498sId = 0;499500stacks[sId].push_back(LevelStackEntry(x, y, i));501}502}503}504}505506507static void appendStacks(const rcTempVector<LevelStackEntry>& srcStack,508rcTempVector<LevelStackEntry>& dstStack,509const unsigned short* srcReg)510{511for (int j=0; j<srcStack.size(); j++)512{513int i = srcStack[j].index;514if ((i < 0) || (srcReg[i] != 0))515continue;516dstStack.push_back(srcStack[j]);517}518}519520struct rcRegion521{522inline rcRegion(unsigned short i) :523spanCount(0),524id(i),525areaType(0),526remap(false),527visited(false),528overlap(false),529connectsToBorder(false),530ymin(0xffff),531ymax(0)532{}533534int spanCount; // Number of spans belonging to this region535unsigned short id; // ID of the region536unsigned char areaType; // Are type.537bool remap;538bool visited;539bool overlap;540bool connectsToBorder;541unsigned short ymin, ymax;542rcIntArray connections;543rcIntArray floors;544};545546static void removeAdjacentNeighbours(rcRegion& reg)547{548// Remove adjacent duplicates.549for (int i = 0; i < reg.connections.size() && reg.connections.size() > 1; )550{551int ni = (i+1) % reg.connections.size();552if (reg.connections[i] == reg.connections[ni])553{554// Remove duplicate555for (int j = i; j < reg.connections.size()-1; ++j)556reg.connections[j] = reg.connections[j+1];557reg.connections.pop();558}559else560++i;561}562}563564static void replaceNeighbour(rcRegion& reg, unsigned short oldId, unsigned short newId)565{566bool neiChanged = false;567for (int i = 0; i < reg.connections.size(); ++i)568{569if (reg.connections[i] == oldId)570{571reg.connections[i] = newId;572neiChanged = true;573}574}575for (int i = 0; i < reg.floors.size(); ++i)576{577if (reg.floors[i] == oldId)578reg.floors[i] = newId;579}580if (neiChanged)581removeAdjacentNeighbours(reg);582}583584static bool canMergeWithRegion(const rcRegion& rega, const rcRegion& regb)585{586if (rega.areaType != regb.areaType)587return false;588int n = 0;589for (int i = 0; i < rega.connections.size(); ++i)590{591if (rega.connections[i] == regb.id)592n++;593}594if (n > 1)595return false;596for (int i = 0; i < rega.floors.size(); ++i)597{598if (rega.floors[i] == regb.id)599return false;600}601return true;602}603604static void addUniqueFloorRegion(rcRegion& reg, int n)605{606for (int i = 0; i < reg.floors.size(); ++i)607if (reg.floors[i] == n)608return;609reg.floors.push(n);610}611612static bool mergeRegions(rcRegion& rega, rcRegion& regb)613{614unsigned short aid = rega.id;615unsigned short bid = regb.id;616617// Duplicate current neighbourhood.618rcIntArray acon;619acon.resize(rega.connections.size());620for (int i = 0; i < rega.connections.size(); ++i)621acon[i] = rega.connections[i];622rcIntArray& bcon = regb.connections;623624// Find insertion point on A.625int insa = -1;626for (int i = 0; i < acon.size(); ++i)627{628if (acon[i] == bid)629{630insa = i;631break;632}633}634if (insa == -1)635return false;636637// Find insertion point on B.638int insb = -1;639for (int i = 0; i < bcon.size(); ++i)640{641if (bcon[i] == aid)642{643insb = i;644break;645}646}647if (insb == -1)648return false;649650// Merge neighbours.651rega.connections.clear();652for (int i = 0, ni = acon.size(); i < ni-1; ++i)653rega.connections.push(acon[(insa+1+i) % ni]);654655for (int i = 0, ni = bcon.size(); i < ni-1; ++i)656rega.connections.push(bcon[(insb+1+i) % ni]);657658removeAdjacentNeighbours(rega);659660for (int j = 0; j < regb.floors.size(); ++j)661addUniqueFloorRegion(rega, regb.floors[j]);662rega.spanCount += regb.spanCount;663regb.spanCount = 0;664regb.connections.resize(0);665666return true;667}668669static bool isRegionConnectedToBorder(const rcRegion& reg)670{671// Region is connected to border if672// one of the neighbours is null id.673for (int i = 0; i < reg.connections.size(); ++i)674{675if (reg.connections[i] == 0)676return true;677}678return false;679}680681static bool isSolidEdge(rcCompactHeightfield& chf, const unsigned short* srcReg,682int x, int y, int i, int dir)683{684const rcCompactSpan& s = chf.spans[i];685unsigned short r = 0;686if (rcGetCon(s, dir) != RC_NOT_CONNECTED)687{688const int ax = x + rcGetDirOffsetX(dir);689const int ay = y + rcGetDirOffsetY(dir);690const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);691r = srcReg[ai];692}693if (r == srcReg[i])694return false;695return true;696}697698static void walkContour(int x, int y, int i, int dir,699rcCompactHeightfield& chf,700const unsigned short* srcReg,701rcIntArray& cont)702{703int startDir = dir;704int starti = i;705706const rcCompactSpan& ss = chf.spans[i];707unsigned short curReg = 0;708if (rcGetCon(ss, dir) != RC_NOT_CONNECTED)709{710const int ax = x + rcGetDirOffsetX(dir);711const int ay = y + rcGetDirOffsetY(dir);712const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(ss, dir);713curReg = srcReg[ai];714}715cont.push(curReg);716717int iter = 0;718while (++iter < 40000)719{720const rcCompactSpan& s = chf.spans[i];721722if (isSolidEdge(chf, srcReg, x, y, i, dir))723{724// Choose the edge corner725unsigned short r = 0;726if (rcGetCon(s, dir) != RC_NOT_CONNECTED)727{728const int ax = x + rcGetDirOffsetX(dir);729const int ay = y + rcGetDirOffsetY(dir);730const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);731r = srcReg[ai];732}733if (r != curReg)734{735curReg = r;736cont.push(curReg);737}738739dir = (dir+1) & 0x3; // Rotate CW740}741else742{743int ni = -1;744const int nx = x + rcGetDirOffsetX(dir);745const int ny = y + rcGetDirOffsetY(dir);746if (rcGetCon(s, dir) != RC_NOT_CONNECTED)747{748const rcCompactCell& nc = chf.cells[nx+ny*chf.width];749ni = (int)nc.index + rcGetCon(s, dir);750}751if (ni == -1)752{753// Should not happen.754return;755}756x = nx;757y = ny;758i = ni;759dir = (dir+3) & 0x3; // Rotate CCW760}761762if (starti == i && startDir == dir)763{764break;765}766}767768// Remove adjacent duplicates.769if (cont.size() > 1)770{771for (int j = 0; j < cont.size(); )772{773int nj = (j+1) % cont.size();774if (cont[j] == cont[nj])775{776for (int k = j; k < cont.size()-1; ++k)777cont[k] = cont[k+1];778cont.pop();779}780else781++j;782}783}784}785786787static bool mergeAndFilterRegions(rcContext* ctx, int minRegionArea, int mergeRegionSize,788unsigned short& maxRegionId,789rcCompactHeightfield& chf,790unsigned short* srcReg, rcIntArray& overlaps)791{792const int w = chf.width;793const int h = chf.height;794795const int nreg = maxRegionId+1;796rcTempVector<rcRegion> regions;797if (!regions.reserve(nreg)) {798ctx->log(RC_LOG_ERROR, "mergeAndFilterRegions: Out of memory 'regions' (%d).", nreg);799return false;800}801802// Construct regions803for (int i = 0; i < nreg; ++i)804regions.push_back(rcRegion((unsigned short) i));805806// Find edge of a region and find connections around the contour.807for (int y = 0; y < h; ++y)808{809for (int x = 0; x < w; ++x)810{811const rcCompactCell& c = chf.cells[x+y*w];812for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)813{814unsigned short r = srcReg[i];815if (r == 0 || r >= nreg)816continue;817818rcRegion& reg = regions[r];819reg.spanCount++;820821// Update floors.822for (int j = (int)c.index; j < ni; ++j)823{824if (i == j) continue;825unsigned short floorId = srcReg[j];826if (floorId == 0 || floorId >= nreg)827continue;828if (floorId == r)829reg.overlap = true;830addUniqueFloorRegion(reg, floorId);831}832833// Have found contour834if (reg.connections.size() > 0)835continue;836837reg.areaType = chf.areas[i];838839// Check if this cell is next to a border.840int ndir = -1;841for (int dir = 0; dir < 4; ++dir)842{843if (isSolidEdge(chf, srcReg, x, y, i, dir))844{845ndir = dir;846break;847}848}849850if (ndir != -1)851{852// The cell is at border.853// Walk around the contour to find all the neighbours.854walkContour(x, y, i, ndir, chf, srcReg, reg.connections);855}856}857}858}859860// Remove too small regions.861rcIntArray stack(32);862rcIntArray trace(32);863for (int i = 0; i < nreg; ++i)864{865rcRegion& reg = regions[i];866if (reg.id == 0 || (reg.id & RC_BORDER_REG))867continue;868if (reg.spanCount == 0)869continue;870if (reg.visited)871continue;872873// Count the total size of all the connected regions.874// Also keep track of the regions connects to a tile border.875bool connectsToBorder = false;876int spanCount = 0;877stack.clear();878trace.clear();879880reg.visited = true;881stack.push(i);882883while (stack.size())884{885// Pop886int ri = stack.pop();887888rcRegion& creg = regions[ri];889890spanCount += creg.spanCount;891trace.push(ri);892893for (int j = 0; j < creg.connections.size(); ++j)894{895if (creg.connections[j] & RC_BORDER_REG)896{897connectsToBorder = true;898continue;899}900rcRegion& neireg = regions[creg.connections[j]];901if (neireg.visited)902continue;903if (neireg.id == 0 || (neireg.id & RC_BORDER_REG))904continue;905// Visit906stack.push(neireg.id);907neireg.visited = true;908}909}910911// If the accumulated regions size is too small, remove it.912// Do not remove areas which connect to tile borders913// as their size cannot be estimated correctly and removing them914// can potentially remove necessary areas.915if (spanCount < minRegionArea && !connectsToBorder)916{917// Kill all visited regions.918for (int j = 0; j < trace.size(); ++j)919{920regions[trace[j]].spanCount = 0;921regions[trace[j]].id = 0;922}923}924}925926// Merge too small regions to neighbour regions.927int mergeCount = 0 ;928do929{930mergeCount = 0;931for (int i = 0; i < nreg; ++i)932{933rcRegion& reg = regions[i];934if (reg.id == 0 || (reg.id & RC_BORDER_REG))935continue;936if (reg.overlap)937continue;938if (reg.spanCount == 0)939continue;940941// Check to see if the region should be merged.942if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(reg))943continue;944945// Small region with more than 1 connection.946// Or region which is not connected to a border at all.947// Find smallest neighbour region that connects to this one.948int smallest = 0xfffffff;949unsigned short mergeId = reg.id;950for (int j = 0; j < reg.connections.size(); ++j)951{952if (reg.connections[j] & RC_BORDER_REG) continue;953rcRegion& mreg = regions[reg.connections[j]];954if (mreg.id == 0 || (mreg.id & RC_BORDER_REG) || mreg.overlap) continue;955if (mreg.spanCount < smallest &&956canMergeWithRegion(reg, mreg) &&957canMergeWithRegion(mreg, reg))958{959smallest = mreg.spanCount;960mergeId = mreg.id;961}962}963// Found new id.964if (mergeId != reg.id)965{966unsigned short oldId = reg.id;967rcRegion& target = regions[mergeId];968969// Merge neighbours.970if (mergeRegions(target, reg))971{972// Fixup regions pointing to current region.973for (int j = 0; j < nreg; ++j)974{975if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG)) continue;976// If another region was already merged into current region977// change the nid of the previous region too.978if (regions[j].id == oldId)979regions[j].id = mergeId;980// Replace the current region with the new one if the981// current regions is neighbour.982replaceNeighbour(regions[j], oldId, mergeId);983}984mergeCount++;985}986}987}988}989while (mergeCount > 0);990991// Compress region Ids.992for (int i = 0; i < nreg; ++i)993{994regions[i].remap = false;995if (regions[i].id == 0) continue; // Skip nil regions.996if (regions[i].id & RC_BORDER_REG) continue; // Skip external regions.997regions[i].remap = true;998}9991000unsigned short regIdGen = 0;1001for (int i = 0; i < nreg; ++i)1002{1003if (!regions[i].remap)1004continue;1005unsigned short oldId = regions[i].id;1006unsigned short newId = ++regIdGen;1007for (int j = i; j < nreg; ++j)1008{1009if (regions[j].id == oldId)1010{1011regions[j].id = newId;1012regions[j].remap = false;1013}1014}1015}1016maxRegionId = regIdGen;10171018// Remap regions.1019for (int i = 0; i < chf.spanCount; ++i)1020{1021if ((srcReg[i] & RC_BORDER_REG) == 0)1022srcReg[i] = regions[srcReg[i]].id;1023}10241025// Return regions that we found to be overlapping.1026for (int i = 0; i < nreg; ++i)1027if (regions[i].overlap)1028overlaps.push(regions[i].id);10291030return true;1031}103210331034static void addUniqueConnection(rcRegion& reg, int n)1035{1036for (int i = 0; i < reg.connections.size(); ++i)1037if (reg.connections[i] == n)1038return;1039reg.connections.push(n);1040}10411042static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea,1043unsigned short& maxRegionId,1044rcCompactHeightfield& chf,1045unsigned short* srcReg)1046{1047const int w = chf.width;1048const int h = chf.height;10491050const int nreg = maxRegionId+1;1051rcTempVector<rcRegion> regions;10521053// Construct regions1054if (!regions.reserve(nreg)) {1055ctx->log(RC_LOG_ERROR, "mergeAndFilterLayerRegions: Out of memory 'regions' (%d).", nreg);1056return false;1057}1058for (int i = 0; i < nreg; ++i)1059regions.push_back(rcRegion((unsigned short) i));10601061// Find region neighbours and overlapping regions.1062rcIntArray lregs(32);1063for (int y = 0; y < h; ++y)1064{1065for (int x = 0; x < w; ++x)1066{1067const rcCompactCell& c = chf.cells[x+y*w];10681069lregs.clear();10701071for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)1072{1073const rcCompactSpan& s = chf.spans[i];1074const unsigned short ri = srcReg[i];1075if (ri == 0 || ri >= nreg) continue;1076rcRegion& reg = regions[ri];10771078reg.spanCount++;10791080reg.ymin = rcMin(reg.ymin, s.y);1081reg.ymax = rcMax(reg.ymax, s.y);10821083// Collect all region layers.1084lregs.push(ri);10851086// Update neighbours1087for (int dir = 0; dir < 4; ++dir)1088{1089if (rcGetCon(s, dir) != RC_NOT_CONNECTED)1090{1091const int ax = x + rcGetDirOffsetX(dir);1092const int ay = y + rcGetDirOffsetY(dir);1093const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);1094const unsigned short rai = srcReg[ai];1095if (rai > 0 && rai < nreg && rai != ri)1096addUniqueConnection(reg, rai);1097if (rai & RC_BORDER_REG)1098reg.connectsToBorder = true;1099}1100}11011102}11031104// Update overlapping regions.1105for (int i = 0; i < lregs.size()-1; ++i)1106{1107for (int j = i+1; j < lregs.size(); ++j)1108{1109if (lregs[i] != lregs[j])1110{1111rcRegion& ri = regions[lregs[i]];1112rcRegion& rj = regions[lregs[j]];1113addUniqueFloorRegion(ri, lregs[j]);1114addUniqueFloorRegion(rj, lregs[i]);1115}1116}1117}11181119}1120}11211122// Create 2D layers from regions.1123unsigned short layerId = 1;11241125for (int i = 0; i < nreg; ++i)1126regions[i].id = 0;11271128// Merge montone regions to create non-overlapping areas.1129rcIntArray stack(32);1130for (int i = 1; i < nreg; ++i)1131{1132rcRegion& root = regions[i];1133// Skip already visited.1134if (root.id != 0)1135continue;11361137// Start search.1138root.id = layerId;11391140stack.clear();1141stack.push(i);11421143while (stack.size() > 0)1144{1145// Pop front1146rcRegion& reg = regions[stack[0]];1147for (int j = 0; j < stack.size()-1; ++j)1148stack[j] = stack[j+1];1149stack.resize(stack.size()-1);11501151const int ncons = (int)reg.connections.size();1152for (int j = 0; j < ncons; ++j)1153{1154const int nei = reg.connections[j];1155rcRegion& regn = regions[nei];1156// Skip already visited.1157if (regn.id != 0)1158continue;1159// Skip if the neighbour is overlapping root region.1160bool overlap = false;1161for (int k = 0; k < root.floors.size(); k++)1162{1163if (root.floors[k] == nei)1164{1165overlap = true;1166break;1167}1168}1169if (overlap)1170continue;11711172// Deepen1173stack.push(nei);11741175// Mark layer id1176regn.id = layerId;1177// Merge current layers to root.1178for (int k = 0; k < regn.floors.size(); ++k)1179addUniqueFloorRegion(root, regn.floors[k]);1180root.ymin = rcMin(root.ymin, regn.ymin);1181root.ymax = rcMax(root.ymax, regn.ymax);1182root.spanCount += regn.spanCount;1183regn.spanCount = 0;1184root.connectsToBorder = root.connectsToBorder || regn.connectsToBorder;1185}1186}11871188layerId++;1189}11901191// Remove small regions1192for (int i = 0; i < nreg; ++i)1193{1194if (regions[i].spanCount > 0 && regions[i].spanCount < minRegionArea && !regions[i].connectsToBorder)1195{1196unsigned short reg = regions[i].id;1197for (int j = 0; j < nreg; ++j)1198if (regions[j].id == reg)1199regions[j].id = 0;1200}1201}12021203// Compress region Ids.1204for (int i = 0; i < nreg; ++i)1205{1206regions[i].remap = false;1207if (regions[i].id == 0) continue; // Skip nil regions.1208if (regions[i].id & RC_BORDER_REG) continue; // Skip external regions.1209regions[i].remap = true;1210}12111212unsigned short regIdGen = 0;1213for (int i = 0; i < nreg; ++i)1214{1215if (!regions[i].remap)1216continue;1217unsigned short oldId = regions[i].id;1218unsigned short newId = ++regIdGen;1219for (int j = i; j < nreg; ++j)1220{1221if (regions[j].id == oldId)1222{1223regions[j].id = newId;1224regions[j].remap = false;1225}1226}1227}1228maxRegionId = regIdGen;12291230// Remap regions.1231for (int i = 0; i < chf.spanCount; ++i)1232{1233if ((srcReg[i] & RC_BORDER_REG) == 0)1234srcReg[i] = regions[srcReg[i]].id;1235}12361237return true;1238}1239124012411242/// @par1243///1244/// This is usually the second to the last step in creating a fully built1245/// compact heightfield. This step is required before regions are built1246/// using #rcBuildRegions or #rcBuildRegionsMonotone.1247///1248/// After this step, the distance data is available via the rcCompactHeightfield::maxDistance1249/// and rcCompactHeightfield::dist fields.1250///1251/// @see rcCompactHeightfield, rcBuildRegions, rcBuildRegionsMonotone1252bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf)1253{1254rcAssert(ctx);12551256rcScopedTimer timer(ctx, RC_TIMER_BUILD_DISTANCEFIELD);12571258if (chf.dist)1259{1260rcFree(chf.dist);1261chf.dist = 0;1262}12631264unsigned short* src = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP);1265if (!src)1266{1267ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'src' (%d).", chf.spanCount);1268return false;1269}1270unsigned short* dst = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP);1271if (!dst)1272{1273ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dst' (%d).", chf.spanCount);1274rcFree(src);1275return false;1276}12771278unsigned short maxDist = 0;12791280{1281rcScopedTimer timerDist(ctx, RC_TIMER_BUILD_DISTANCEFIELD_DIST);12821283calculateDistanceField(chf, src, maxDist);1284chf.maxDistance = maxDist;1285}12861287{1288rcScopedTimer timerBlur(ctx, RC_TIMER_BUILD_DISTANCEFIELD_BLUR);12891290// Blur1291if (boxBlur(chf, 1, src, dst) != src)1292rcSwap(src, dst);12931294// Store distance.1295chf.dist = src;1296}12971298rcFree(dst);12991300return true;1301}13021303static void paintRectRegion(int minx, int maxx, int miny, int maxy, unsigned short regId,1304rcCompactHeightfield& chf, unsigned short* srcReg)1305{1306const int w = chf.width;1307for (int y = miny; y < maxy; ++y)1308{1309for (int x = minx; x < maxx; ++x)1310{1311const rcCompactCell& c = chf.cells[x+y*w];1312for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)1313{1314if (chf.areas[i] != RC_NULL_AREA)1315srcReg[i] = regId;1316}1317}1318}1319}132013211322static const unsigned short RC_NULL_NEI = 0xffff;13231324struct rcSweepSpan1325{1326unsigned short rid; // row id1327unsigned short id; // region id1328unsigned short ns; // number samples1329unsigned short nei; // neighbour id1330};13311332/// @par1333///1334/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour.1335/// Contours will form simple polygons.1336///1337/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be1338/// re-assigned to the zero (null) region.1339///1340/// Partitioning can result in smaller than necessary regions. @p mergeRegionArea helps1341/// reduce unecessarily small regions.1342///1343/// See the #rcConfig documentation for more information on the configuration parameters.1344///1345/// The region data will be available via the rcCompactHeightfield::maxRegions1346/// and rcCompactSpan::reg fields.1347///1348/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions.1349///1350/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig1351bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf,1352const int borderSize, const int minRegionArea, const int mergeRegionArea)1353{1354rcAssert(ctx);13551356rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS);13571358const int w = chf.width;1359const int h = chf.height;1360unsigned short id = 1;13611362rcScopedDelete<unsigned short> srcReg((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP));1363if (!srcReg)1364{1365ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount);1366return false;1367}1368memset(srcReg,0,sizeof(unsigned short)*chf.spanCount);13691370const int nsweeps = rcMax(chf.width,chf.height);1371rcScopedDelete<rcSweepSpan> sweeps((rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan)*nsweeps, RC_ALLOC_TEMP));1372if (!sweeps)1373{1374ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps);1375return false;1376}137713781379// Mark border regions.1380if (borderSize > 0)1381{1382// Make sure border will not overflow.1383const int bw = rcMin(w, borderSize);1384const int bh = rcMin(h, borderSize);1385// Paint regions1386paintRectRegion(0, bw, 0, h, id|RC_BORDER_REG, chf, srcReg); id++;1387paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++;1388paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++;1389paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++;1390}13911392chf.borderSize = borderSize;13931394rcIntArray prev(256);13951396// Sweep one line at a time.1397for (int y = borderSize; y < h-borderSize; ++y)1398{1399// Collect spans from this row.1400prev.resize(id+1);1401memset(&prev[0],0,sizeof(int)*id);1402unsigned short rid = 1;14031404for (int x = borderSize; x < w-borderSize; ++x)1405{1406const rcCompactCell& c = chf.cells[x+y*w];14071408for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)1409{1410const rcCompactSpan& s = chf.spans[i];1411if (chf.areas[i] == RC_NULL_AREA) continue;14121413// -x1414unsigned short previd = 0;1415if (rcGetCon(s, 0) != RC_NOT_CONNECTED)1416{1417const int ax = x + rcGetDirOffsetX(0);1418const int ay = y + rcGetDirOffsetY(0);1419const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0);1420if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])1421previd = srcReg[ai];1422}14231424if (!previd)1425{1426previd = rid++;1427sweeps[previd].rid = previd;1428sweeps[previd].ns = 0;1429sweeps[previd].nei = 0;1430}14311432// -y1433if (rcGetCon(s,3) != RC_NOT_CONNECTED)1434{1435const int ax = x + rcGetDirOffsetX(3);1436const int ay = y + rcGetDirOffsetY(3);1437const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3);1438if (srcReg[ai] && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])1439{1440unsigned short nr = srcReg[ai];1441if (!sweeps[previd].nei || sweeps[previd].nei == nr)1442{1443sweeps[previd].nei = nr;1444sweeps[previd].ns++;1445prev[nr]++;1446}1447else1448{1449sweeps[previd].nei = RC_NULL_NEI;1450}1451}1452}14531454srcReg[i] = previd;1455}1456}14571458// Create unique ID.1459for (int i = 1; i < rid; ++i)1460{1461if (sweeps[i].nei != RC_NULL_NEI && sweeps[i].nei != 0 &&1462prev[sweeps[i].nei] == (int)sweeps[i].ns)1463{1464sweeps[i].id = sweeps[i].nei;1465}1466else1467{1468sweeps[i].id = id++;1469}1470}14711472// Remap IDs1473for (int x = borderSize; x < w-borderSize; ++x)1474{1475const rcCompactCell& c = chf.cells[x+y*w];14761477for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)1478{1479if (srcReg[i] > 0 && srcReg[i] < rid)1480srcReg[i] = sweeps[srcReg[i]].id;1481}1482}1483}148414851486{1487rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER);14881489// Merge regions and filter out small regions.1490rcIntArray overlaps;1491chf.maxRegions = id;1492if (!mergeAndFilterRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg, overlaps))1493return false;14941495// Monotone partitioning does not generate overlapping regions.1496}14971498// Store the result out.1499for (int i = 0; i < chf.spanCount; ++i)1500chf.spans[i].reg = srcReg[i];15011502return true;1503}15041505/// @par1506///1507/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour.1508/// Contours will form simple polygons.1509///1510/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be1511/// re-assigned to the zero (null) region.1512///1513/// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors.1514/// @p mergeRegionArea helps reduce unecessarily small regions.1515///1516/// See the #rcConfig documentation for more information on the configuration parameters.1517///1518/// The region data will be available via the rcCompactHeightfield::maxRegions1519/// and rcCompactSpan::reg fields.1520///1521/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions.1522///1523/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig1524bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf,1525const int borderSize, const int minRegionArea, const int mergeRegionArea)1526{1527rcAssert(ctx);15281529rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS);15301531const int w = chf.width;1532const int h = chf.height;15331534rcScopedDelete<unsigned short> buf((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*2, RC_ALLOC_TEMP));1535if (!buf)1536{1537ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4);1538return false;1539}15401541ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED);15421543const int LOG_NB_STACKS = 3;1544const int NB_STACKS = 1 << LOG_NB_STACKS;1545rcTempVector<LevelStackEntry> lvlStacks[NB_STACKS];1546for (int i=0; i<NB_STACKS; ++i)1547lvlStacks[i].reserve(256);15481549rcTempVector<LevelStackEntry> stack;1550stack.reserve(256);15511552unsigned short* srcReg = buf;1553unsigned short* srcDist = buf+chf.spanCount;15541555memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount);1556memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount);15571558unsigned short regionId = 1;1559unsigned short level = (chf.maxDistance+1) & ~1;15601561// TODO: Figure better formula, expandIters defines how much the1562// watershed "overflows" and simplifies the regions. Tying it to1563// agent radius was usually good indication how greedy it could be.1564// const int expandIters = 4 + walkableRadius * 2;1565const int expandIters = 8;15661567if (borderSize > 0)1568{1569// Make sure border will not overflow.1570const int bw = rcMin(w, borderSize);1571const int bh = rcMin(h, borderSize);15721573// Paint regions1574paintRectRegion(0, bw, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;1575paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;1576paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++;1577paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;1578}15791580chf.borderSize = borderSize;15811582int sId = -1;1583while (level > 0)1584{1585level = level >= 2 ? level-2 : 0;1586sId = (sId+1) & (NB_STACKS-1);15871588// ctx->startTimer(RC_TIMER_DIVIDE_TO_LEVELS);15891590if (sId == 0)1591sortCellsByLevel(level, chf, srcReg, NB_STACKS, lvlStacks, 1);1592else1593appendStacks(lvlStacks[sId-1], lvlStacks[sId], srcReg); // copy left overs from last level15941595// ctx->stopTimer(RC_TIMER_DIVIDE_TO_LEVELS);15961597{1598rcScopedTimer timerExpand(ctx, RC_TIMER_BUILD_REGIONS_EXPAND);15991600// Expand current regions until no empty connected cells found.1601expandRegions(expandIters, level, chf, srcReg, srcDist, lvlStacks[sId], false);1602}16031604{1605rcScopedTimer timerFloor(ctx, RC_TIMER_BUILD_REGIONS_FLOOD);16061607// Mark new regions with IDs.1608for (int j = 0; j<lvlStacks[sId].size(); j++)1609{1610LevelStackEntry current = lvlStacks[sId][j];1611int x = current.x;1612int y = current.y;1613int i = current.index;1614if (i >= 0 && srcReg[i] == 0)1615{1616if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack))1617{1618if (regionId == 0xFFFF)1619{1620ctx->log(RC_LOG_ERROR, "rcBuildRegions: Region ID overflow");1621return false;1622}16231624regionId++;1625}1626}1627}1628}1629}16301631// Expand current regions until no empty connected cells found.1632expandRegions(expandIters*8, 0, chf, srcReg, srcDist, stack, true);16331634ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED);16351636{1637rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER);16381639// Merge regions and filter out smalle regions.1640rcIntArray overlaps;1641chf.maxRegions = regionId;1642if (!mergeAndFilterRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg, overlaps))1643return false;16441645// If overlapping regions were found during merging, split those regions.1646if (overlaps.size() > 0)1647{1648ctx->log(RC_LOG_ERROR, "rcBuildRegions: %d overlapping regions.", overlaps.size());1649}1650}16511652// Write the result out.1653for (int i = 0; i < chf.spanCount; ++i)1654chf.spans[i].reg = srcReg[i];16551656return true;1657}165816591660bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf,1661const int borderSize, const int minRegionArea)1662{1663rcAssert(ctx);16641665rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS);16661667const int w = chf.width;1668const int h = chf.height;1669unsigned short id = 1;16701671rcScopedDelete<unsigned short> srcReg((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP));1672if (!srcReg)1673{1674ctx->log(RC_LOG_ERROR, "rcBuildLayerRegions: Out of memory 'src' (%d).", chf.spanCount);1675return false;1676}1677memset(srcReg,0,sizeof(unsigned short)*chf.spanCount);16781679const int nsweeps = rcMax(chf.width,chf.height);1680rcScopedDelete<rcSweepSpan> sweeps((rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan)*nsweeps, RC_ALLOC_TEMP));1681if (!sweeps)1682{1683ctx->log(RC_LOG_ERROR, "rcBuildLayerRegions: Out of memory 'sweeps' (%d).", nsweeps);1684return false;1685}168616871688// Mark border regions.1689if (borderSize > 0)1690{1691// Make sure border will not overflow.1692const int bw = rcMin(w, borderSize);1693const int bh = rcMin(h, borderSize);1694// Paint regions1695paintRectRegion(0, bw, 0, h, id|RC_BORDER_REG, chf, srcReg); id++;1696paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++;1697paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++;1698paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++;1699}17001701chf.borderSize = borderSize;17021703rcIntArray prev(256);17041705// Sweep one line at a time.1706for (int y = borderSize; y < h-borderSize; ++y)1707{1708// Collect spans from this row.1709prev.resize(id+1);1710memset(&prev[0],0,sizeof(int)*id);1711unsigned short rid = 1;17121713for (int x = borderSize; x < w-borderSize; ++x)1714{1715const rcCompactCell& c = chf.cells[x+y*w];17161717for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)1718{1719const rcCompactSpan& s = chf.spans[i];1720if (chf.areas[i] == RC_NULL_AREA) continue;17211722// -x1723unsigned short previd = 0;1724if (rcGetCon(s, 0) != RC_NOT_CONNECTED)1725{1726const int ax = x + rcGetDirOffsetX(0);1727const int ay = y + rcGetDirOffsetY(0);1728const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0);1729if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])1730previd = srcReg[ai];1731}17321733if (!previd)1734{1735previd = rid++;1736sweeps[previd].rid = previd;1737sweeps[previd].ns = 0;1738sweeps[previd].nei = 0;1739}17401741// -y1742if (rcGetCon(s,3) != RC_NOT_CONNECTED)1743{1744const int ax = x + rcGetDirOffsetX(3);1745const int ay = y + rcGetDirOffsetY(3);1746const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3);1747if (srcReg[ai] && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])1748{1749unsigned short nr = srcReg[ai];1750if (!sweeps[previd].nei || sweeps[previd].nei == nr)1751{1752sweeps[previd].nei = nr;1753sweeps[previd].ns++;1754prev[nr]++;1755}1756else1757{1758sweeps[previd].nei = RC_NULL_NEI;1759}1760}1761}17621763srcReg[i] = previd;1764}1765}17661767// Create unique ID.1768for (int i = 1; i < rid; ++i)1769{1770if (sweeps[i].nei != RC_NULL_NEI && sweeps[i].nei != 0 &&1771prev[sweeps[i].nei] == (int)sweeps[i].ns)1772{1773sweeps[i].id = sweeps[i].nei;1774}1775else1776{1777sweeps[i].id = id++;1778}1779}17801781// Remap IDs1782for (int x = borderSize; x < w-borderSize; ++x)1783{1784const rcCompactCell& c = chf.cells[x+y*w];17851786for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)1787{1788if (srcReg[i] > 0 && srcReg[i] < rid)1789srcReg[i] = sweeps[srcReg[i]].id;1790}1791}1792}179317941795{1796rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER);17971798// Merge monotone regions to layers and remove small regions.1799chf.maxRegions = id;1800if (!mergeAndFilterLayerRegions(ctx, minRegionArea, chf.maxRegions, chf, srcReg))1801return false;1802}180318041805// Store the result out.1806for (int i = 0; i < chf.spanCount; ++i)1807chf.spans[i].reg = srcReg[i];18081809return true;1810}181118121813