From 5fce286267f9deb11b9c3580b2ba4529ef3c9a0f Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 14 Jan 2016 11:11:06 +0100 Subject: [PATCH] Use flood fill for potentially overlapping polys This changes the detail sampling to find height data by using the flood fill method for polygons that were merged because of a border vertex removal. This could potentially cause overlapping polygons to receive the same region IDs which would later make the fast-path sampling in getHeightData sample the wrong heights. Also includes optimizations for this path and getHeightData itself. Previously the seeding was responsible for 25% of the total time spent in getHeightData in these cases. We now use the direction and prefer moving directly towards the center of the polygon. This makes the overhead of seedArrayWithPolyCenter virtually nonexistent (~2% according to profiling). Fix #146 --- Recast/Include/Recast.h | 22 ++- Recast/Include/RecastAlloc.h | 32 +++- Recast/Source/RecastAlloc.cpp | 18 +- Recast/Source/RecastMesh.cpp | 22 ++- Recast/Source/RecastMeshDetail.cpp | 289 +++++++++++++++-------------- 5 files changed, 214 insertions(+), 169 deletions(-) diff --git a/Recast/Include/Recast.h b/Recast/Include/Recast.h index d92d659..feb1015 100644 --- a/Recast/Include/Recast.h +++ b/Recast/Include/Recast.h @@ -513,6 +513,14 @@ void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh); /// @see rcCompactSpan::reg static const unsigned short RC_BORDER_REG = 0x8000; +/// Polygon touches multiple regions. +/// If a polygon has this region ID it was merged with or created +/// from polygons of different regions during the polymesh +/// build step that removes redundant border vertices. +/// (Used during the polymesh and detail polymesh build processes) +/// @see rcPolyMesh::regs +static const unsigned short RC_MULTIPLE_REGS = 0; + /// Border vertex flag. /// If a region ID has this bit set, then the associated element lies on /// a tile border. If a contour vertex's region ID has this bit set, the @@ -1059,7 +1067,7 @@ inline int rcGetCon(const rcCompactSpan& s, int dir) /// in the direction. inline int rcGetDirOffsetX(int dir) { - const int offset[4] = { -1, 0, 1, 0, }; + static const int offset[4] = { -1, 0, 1, 0, }; return offset[dir&0x03]; } @@ -1069,10 +1077,20 @@ inline int rcGetDirOffsetX(int dir) /// in the direction. inline int rcGetDirOffsetY(int dir) { - const int offset[4] = { 0, 1, 0, -1 }; + static const int offset[4] = { 0, 1, 0, -1 }; return offset[dir&0x03]; } +/// Gets the direction for the specified offset. One of x and y should be 0. +/// @param[in] x The x offset. [Limits: -1 <= value <= 1] +/// @param[in] y The y offset. [Limits: -1 <= value <= 1] +/// @return The direction that represents the offset. +inline int rcGetDirForOffset(int x, int y) +{ + static const int dirs[5] = { 3, 0, -1, 2, 1 }; + return dirs[((y+1)<<1)+x]; +} + /// @} /// @name Layer, Contour, Polymesh, and Detail Mesh Functions /// @see rcHeightfieldLayer, rcContourSet, rcPolyMesh, rcPolyMeshDetail diff --git a/Recast/Include/RecastAlloc.h b/Recast/Include/RecastAlloc.h index 438be9e..500dea2 100644 --- a/Recast/Include/RecastAlloc.h +++ b/Recast/Include/RecastAlloc.h @@ -64,40 +64,54 @@ class rcIntArray int m_size, m_cap; inline rcIntArray(const rcIntArray&); inline rcIntArray& operator=(const rcIntArray&); + + void doResize(int n); public: /// Constructs an instance with an initial array size of zero. - inline rcIntArray() : m_data(0), m_size(0), m_cap(0) {} + rcIntArray() : m_data(0), m_size(0), m_cap(0) {} /// Constructs an instance initialized to the specified size. /// @param[in] n The initial size of the integer array. - inline rcIntArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); } - inline ~rcIntArray() { rcFree(m_data); } + rcIntArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); } + ~rcIntArray() { rcFree(m_data); } /// Specifies the new size of the integer array. /// @param[in] n The new size of the integer array. - void resize(int n); + void resize(int n) + { + if (n > m_cap) + doResize(n); + + m_size = n; + } /// Push the specified integer onto the end of the array and increases the size by one. /// @param[in] item The new value. - inline void push(int item) { resize(m_size+1); m_data[m_size-1] = item; } + void push(int item) { resize(m_size+1); m_data[m_size-1] = item; } /// Returns the value at the end of the array and reduces the size by one. /// @return The value at the end of the array. - inline int pop() { if (m_size > 0) m_size--; return m_data[m_size]; } + int pop() + { + if (m_size > 0) + m_size--; + + return m_data[m_size]; + } /// The value at the specified array index. /// @warning Does not provide overflow protection. /// @param[in] i The index of the value. - inline const int& operator[](int i) const { return m_data[i]; } + const int& operator[](int i) const { return m_data[i]; } /// The value at the specified array index. /// @warning Does not provide overflow protection. /// @param[in] i The index of the value. - inline int& operator[](int i) { return m_data[i]; } + int& operator[](int i) { return m_data[i]; } /// The current size of the integer array. - inline int size() const { return m_size; } + int size() const { return m_size; } }; /// A simple helper class used to delete an array when it goes out of scope. diff --git a/Recast/Source/RecastAlloc.cpp b/Recast/Source/RecastAlloc.cpp index b5ec151..af843f7 100644 --- a/Recast/Source/RecastAlloc.cpp +++ b/Recast/Source/RecastAlloc.cpp @@ -72,17 +72,13 @@ void rcFree(void* ptr) /// Using this method ensures the array is at least large enough to hold /// the specified number of elements. This can improve performance by /// avoiding auto-resizing during use. -void rcIntArray::resize(int n) +void rcIntArray::doResize(int n) { - if (n > m_cap) - { - if (!m_cap) m_cap = n; - while (m_cap < n) m_cap *= 2; - int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP); - if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int)); - rcFree(m_data); - m_data = newData; - } - m_size = n; + if (!m_cap) m_cap = n; + while (m_cap < n) m_cap *= 2; + int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP); + if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int)); + rcFree(m_data); + m_data = newData; } diff --git a/Recast/Source/RecastMesh.cpp b/Recast/Source/RecastMesh.cpp index f806c3e..7f7d342 100644 --- a/Recast/Source/RecastMesh.cpp +++ b/Recast/Source/RecastMesh.cpp @@ -528,8 +528,8 @@ static int getPolyMergeValue(unsigned short* pa, unsigned short* pb, return dx*dx + dy*dy; } -static void mergePolys(unsigned short* pa, unsigned short* pb, int ea, int eb, - unsigned short* tmp, const int nvp) +static void mergePolyVerts(unsigned short* pa, unsigned short* pb, int ea, int eb, + unsigned short* tmp, const int nvp) { const int na = countPolyVerts(pa, nvp); const int nb = countPolyVerts(pb, nvp); @@ -695,7 +695,7 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hole' (%d).", numRemovedVerts*nvp); return false; } - + int nhreg = 0; rcScopedDelete hreg = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); if (!hreg) @@ -895,7 +895,14 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short polys[npolys*nvp+0] = (unsigned short)hole[t[0]]; polys[npolys*nvp+1] = (unsigned short)hole[t[1]]; polys[npolys*nvp+2] = (unsigned short)hole[t[2]]; - pregs[npolys] = (unsigned short)hreg[t[0]]; + + // If this polygon covers multiple region types then + // mark it as such + if (hreg[t[0]] != hreg[t[1]] || hreg[t[1]] != hreg[t[2]]) + pregs[npolys] = RC_MULTIPLE_REGS; + else + pregs[npolys] = (unsigned short)hreg[t[0]]; + pareas[npolys] = (unsigned char)harea[t[0]]; npolys++; } @@ -936,7 +943,10 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short // Found best, merge. unsigned short* pa = &polys[bestPa*nvp]; unsigned short* pb = &polys[bestPb*nvp]; - mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); + mergePolyVerts(pa, pb, bestEa, bestEb, tmpPoly, nvp); + if (pregs[bestPa] != pregs[bestPb]) + pregs[bestPa] = RC_MULTIPLE_REGS; + unsigned short* last = &polys[(npolys-1)*nvp]; if (pb != last) memcpy(pb, last, sizeof(unsigned short)*nvp); @@ -1182,7 +1192,7 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe // Found best, merge. unsigned short* pa = &polys[bestPa*nvp]; unsigned short* pb = &polys[bestPb*nvp]; - mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); + mergePolyVerts(pa, pb, bestEa, bestEb, tmpPoly, nvp); unsigned short* lastPoly = &polys[(npolys-1)*nvp]; if (pb != lastPoly) memcpy(pb, lastPoly, sizeof(unsigned short)*nvp); diff --git a/Recast/Source/RecastMeshDetail.cpp b/Recast/Source/RecastMeshDetail.cpp index 3c6c45d..43a19a0 100644 --- a/Recast/Source/RecastMeshDetail.cpp +++ b/Recast/Source/RecastMeshDetail.cpp @@ -834,33 +834,25 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, return true; } - -static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf, - const unsigned short* poly, const int npoly, - const unsigned short* verts, const int bs, - rcHeightPatch& hp, rcIntArray& stack) +static void seedArrayWithPolyCenter(rcContext* ctx, const rcCompactHeightfield& chf, + const unsigned short* poly, const int npoly, + const unsigned short* verts, const int bs, + rcHeightPatch& hp, rcIntArray& array) { - // Floodfill the heightfield to get 2D height data, - // starting at vertex locations as seeds. - // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. - memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); - - stack.resize(0); - static const int offset[9*2] = { 0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, }; - // Use poly vertices as seed points for the flood fill. - for (int j = 0; j < npoly; ++j) + // Find cell closest to a poly vertex + int startCellX = 0, startCellY = 0, startSpanIndex = -1; + int dmin = RC_UNSET_HEIGHT; + for (int j = 0; j < npoly && dmin > 0; ++j) { - int cx = 0, cz = 0, ci =-1; - int dmin = RC_UNSET_HEIGHT; - for (int k = 0; k < 9; ++k) + for (int k = 0; k < 9 && dmin > 0; ++k) { const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0]; const int ay = (int)verts[poly[j]*3+1]; @@ -870,191 +862,208 @@ static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf, continue; const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni && dmin > 0; ++i) { const rcCompactSpan& s = chf.spans[i]; int d = rcAbs(ay - (int)s.y); if (d < dmin) { - cx = ax; - cz = az; - ci = i; + startCellX = ax; + startCellY = az; + startSpanIndex = i; dmin = d; } } } - if (ci != -1) - { - stack.push(cx); - stack.push(cz); - stack.push(ci); - } } - // Find center of the polygon using flood fill. - int pcx = 0, pcz = 0; + rcAssert(startSpanIndex != -1); + // Find center of the polygon + int pcx = 0, pcy = 0; for (int j = 0; j < npoly; ++j) { pcx += (int)verts[poly[j]*3+0]; - pcz += (int)verts[poly[j]*3+2]; + pcy += (int)verts[poly[j]*3+2]; } pcx /= npoly; - pcz /= npoly; + pcy /= npoly; - for (int i = 0; i < stack.size(); i += 3) + // Use seeds array as a stack for DFS + array.resize(0); + array.push(startCellX); + array.push(startCellY); + array.push(startSpanIndex); + + int dirs[] = { 0, 1, 2, 3 }; + memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); + // DFS to move to the center. Note that we need a DFS here and can not just move + // directly towards the center without recording intermediate nodes, even though the polygons + // are convex. In very rare we can get stuck due to contour simplification if we do not + // record nodes. + int cx = -1, cy = -1, ci = -1; + while (true) { - int cx = stack[i+0]; - int cy = stack[i+1]; - int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; - hp.data[idx] = 1; - } - - while (stack.size() > 0) - { - int ci = stack.pop(); - int cy = stack.pop(); - int cx = stack.pop(); - - // Check if close to center of the polygon. - if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1) + if (array.size() < 3) { - stack.resize(0); - stack.push(cx); - stack.push(cy); - stack.push(ci); + ctx->log(RC_LOG_WARNING, "Walk towards polygon center failed to reach center"); break; } - + + ci = array.pop(); + cy = array.pop(); + cx = array.pop(); + + if (cx == pcx && cy == pcy) + break; + + // If we are already at the correct X-position, prefer direction + // directly towards the center in the Y-axis; otherwise prefer + // direction in the X-axis + int directDir; + if (cx == pcx) + directDir = rcGetDirForOffset(0, pcy > cy ? 1 : -1); + else + directDir = rcGetDirForOffset(pcx > cx ? 1 : -1, 0); + + // Push the direct dir last so we start with this on next iteration + rcSwap(dirs[directDir], dirs[3]); + const rcCompactSpan& cs = chf.spans[ci]; - - for (int dir = 0; dir < 4; ++dir) + for (int i = 0; i < 4; i++) { - if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; - - const int ax = cx + rcGetDirOffsetX(dir); - const int ay = cy + rcGetDirOffsetY(dir); - - if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || - ay < hp.ymin || ay >= (hp.ymin+hp.height)) + int dir = dirs[i]; + if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; - - if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) + + int newX = cx + rcGetDirOffsetX(dir); + int newY = cy + rcGetDirOffsetY(dir); + + int hpx = newX - hp.xmin; + int hpy = newY - hp.ymin; + if (hpx < 0 || hpx >= hp.width || hpy < 0 || hpy >= hp.height) continue; - - const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); - - int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; - hp.data[idx] = 1; - - stack.push(ax); - stack.push(ay); - stack.push(ai); + + if (hp.data[hpx+hpy*hp.width] != 0) + continue; + + hp.data[hpx+hpy*hp.width] = 1; + array.push(newX); + array.push(newY); + array.push((int)chf.cells[(newX+bs)+(newY+bs)*chf.width].index + rcGetCon(cs, dir)); } + + rcSwap(dirs[directDir], dirs[3]); } - + + array.resize(0); + // getHeightData seeds are given in coordinates with borders + array.push(cx+bs); + array.push(cy+bs); + array.push(ci); + memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); - - // Mark start locations. - for (int i = 0; i < stack.size(); i += 3) - { - int cx = stack[i+0]; - int cy = stack[i+1]; - int ci = stack[i+2]; - int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; - const rcCompactSpan& cs = chf.spans[ci]; - hp.data[idx] = cs.y; - - // getHeightData seeds are given in coordinates with borders - stack[i+0] += bs; - stack[i+1] += bs; - } - + const rcCompactSpan& cs = chf.spans[ci]; + hp.data[cx-hp.xmin+(cy-hp.ymin)*hp.width] = cs.y; } +static void push3(rcIntArray& queue, int v1, int v2, int v3) +{ + queue.resize(queue.size() + 3); + queue[queue.size() - 3] = v1; + queue[queue.size() - 2] = v2; + queue[queue.size() - 1] = v3; +} -static void getHeightData(const rcCompactHeightfield& chf, +static void getHeightData(rcContext* ctx, const rcCompactHeightfield& chf, const unsigned short* poly, const int npoly, const unsigned short* verts, const int bs, - rcHeightPatch& hp, rcIntArray& stack, + rcHeightPatch& hp, rcIntArray& queue, int region) { // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. - stack.resize(0); + queue.resize(0); + // Set all heights to RC_UNSET_HEIGHT. memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); - + bool empty = true; - // Copy the height from the same region, and mark region borders - // as seed points to fill the rest. - for (int hy = 0; hy < hp.height; hy++) + // We cannot sample from this poly if it was created from polys + // of different regions. If it was then it could potentially be overlapping + // with polys of that region and the heights sampled here could be wrong. + if (region != RC_MULTIPLE_REGS) { - int y = hp.ymin + hy + bs; - for (int hx = 0; hx < hp.width; hx++) + // Copy the height from the same region, and mark region borders + // as seed points to fill the rest. + for (int hy = 0; hy < hp.height; hy++) { - int x = hp.xmin + hx + bs; - const rcCompactCell& c = chf.cells[x+y*chf.width]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + int y = hp.ymin + hy + bs; + for (int hx = 0; hx < hp.width; hx++) { - const rcCompactSpan& s = chf.spans[i]; - if (s.reg == region) + int x = hp.xmin + hx + bs; + const rcCompactCell& c = chf.cells[x + y*chf.width]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { - // Store height - hp.data[hx + hy*hp.width] = s.y; - empty = false; - - // If any of the neighbours is not in same region, - // add the current location as flood fill start - bool border = false; - for (int dir = 0; dir < 4; ++dir) + const rcCompactSpan& s = chf.spans[i]; + if (s.reg == region) { - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + // Store height + hp.data[hx + hy*hp.width] = s.y; + empty = false; + + // If any of the neighbours is not in same region, + // add the current location as flood fill start + bool border = false; + for (int dir = 0; dir < 4; ++dir) { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); - const rcCompactSpan& as = chf.spans[ai]; - if (as.reg != region) + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { - border = true; - break; + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(s, dir); + const rcCompactSpan& as = chf.spans[ai]; + if (as.reg != region) + { + border = true; + break; + } } } + if (border) + push3(queue, x, y, i); + break; } - if (border) - { - stack.push(x); - stack.push(y); - stack.push(i); - } - break; } } } } - // if the polygon does not contian any points from the current region (rare, but happens) - // then use the cells closest to the polygon vertices as seeds to fill the height field + // if the polygon does not contain any points from the current region (rare, but happens) + // or if it could potentially be overlapping polygons of the same region, + // then use the center as the seed point. if (empty) - getHeightDataSeedsFromVertices(chf, poly, npoly, verts, bs, hp, stack); + seedArrayWithPolyCenter(ctx, chf, poly, npoly, verts, bs, hp, queue); static const int RETRACT_SIZE = 256; int head = 0; - while (head*3 < stack.size()) + // We assume the seed is centered in the polygon, so a BFS to collect + // height data will ensure we do not move onto overlapping polygons and + // sample wrong heights. + while (head*3 < queue.size()) { - int cx = stack[head*3+0]; - int cy = stack[head*3+1]; - int ci = stack[head*3+2]; + int cx = queue[head*3+0]; + int cy = queue[head*3+1]; + int ci = queue[head*3+2]; head++; if (head >= RETRACT_SIZE) { head = 0; - if (stack.size() > RETRACT_SIZE*3) - memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3)); - stack.resize(stack.size()-RETRACT_SIZE*3); + if (queue.size() > RETRACT_SIZE*3) + memmove(&queue[0], &queue[RETRACT_SIZE*3], sizeof(int)*(queue.size()-RETRACT_SIZE*3)); + queue.resize(queue.size()-RETRACT_SIZE*3); } const rcCompactSpan& cs = chf.spans[ci]; @@ -1067,7 +1076,7 @@ static void getHeightData(const rcCompactHeightfield& chf, const int hx = ax - hp.xmin - bs; const int hy = ay - hp.ymin - bs; - if (hx < 0 || hx >= hp.width || hy < 0 || hy >= hp.height) + if ((unsigned int)hx >= (unsigned int)hp.width || (unsigned int)hy >= (unsigned int)hp.height) continue; if (hp.data[hx + hy*hp.width] != RC_UNSET_HEIGHT) @@ -1078,9 +1087,7 @@ static void getHeightData(const rcCompactHeightfield& chf, hp.data[hx + hy*hp.width] = as.y; - stack.push(ax); - stack.push(ay); - stack.push(ai); + push3(queue, ax, ay, ai); } } } @@ -1133,7 +1140,7 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa rcIntArray edges(64); rcIntArray tris(512); - rcIntArray stack(512); + rcIntArray arr(512); rcIntArray samples(512); float verts[256*3]; rcHeightPatch hp; @@ -1240,7 +1247,7 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa hp.ymin = bounds[i*4+2]; hp.width = bounds[i*4+1]-bounds[i*4+0]; hp.height = bounds[i*4+3]-bounds[i*4+2]; - getHeightData(chf, p, npoly, mesh.verts, borderSize, hp, stack, mesh.regs[i]); + getHeightData(ctx, chf, p, npoly, mesh.verts, borderSize, hp, arr, mesh.regs[i]); // Build detail mesh. int nverts = 0;