From 9b881bde8c119bd078ab8a2e5847738784e24150 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 1 Feb 2010 14:08:06 +0000 Subject: [PATCH] NOTE: Changed the generation procedure, see samples! Erode walkable area before area is generated. Allow to mark areas on chf. Generate regions following areas (+ many fixes here and there to make it alwasy work). --- Recast/Include/Recast.h | 39 +++-- Recast/Include/RecastLog.h | 1 + Recast/Source/Recast.cpp | 10 +- Recast/Source/RecastArea.cpp | 242 ++++++++++++++++++++++++++++++++ Recast/Source/RecastContour.cpp | 69 ++++++--- Recast/Source/RecastMesh.cpp | 79 +++++++++-- Recast/Source/RecastRegion.cpp | 102 ++++++++------ 7 files changed, 451 insertions(+), 91 deletions(-) create mode 100644 Recast/Source/RecastArea.cpp diff --git a/Recast/Include/Recast.h b/Recast/Include/Recast.h index 54a32cd..5b5422a 100644 --- a/Recast/Include/Recast.h +++ b/Recast/Include/Recast.h @@ -101,14 +101,14 @@ struct rcCompactHeightfield { inline rcCompactHeightfield() : maxDistance(0), maxRegions(0), cells(0), - spans(0), dist(0), reg(0) {} + spans(0), dist(0), regs(0), areas(0) {} inline ~rcCompactHeightfield() { delete [] cells; delete [] spans; delete [] dist; - delete [] reg; - delete [] flags; + delete [] regs; + delete [] areas; } int width, height; // Width and height of the heighfield. int spanCount; // Number of spans in the heightfield. @@ -120,8 +120,8 @@ struct rcCompactHeightfield rcCompactCell* cells; // Pointer to width*height cells. rcCompactSpan* spans; // Pointer to spans. unsigned short* dist; // Pointer to per span distance to border. - unsigned short* reg; // Pointer to per span region ID. - unsigned short* flags; // Pointer to per span flags. + unsigned short* regs; // Pointer to per span region ID. + unsigned char* areas; // Pointer to per span area ID. }; struct rcContour @@ -133,6 +133,7 @@ struct rcContour int* rverts; // Raw vertex coordinates, each vertex contains 4 components. int nrverts; // Number of raw vertices. unsigned short reg; // Region ID of the contour. + unsigned char area; // Area ID of the contour. }; struct rcContourSet @@ -158,11 +159,14 @@ struct rcContourSet // z = bmin[2] + verts[i*3+2]*cs; struct rcPolyMesh { - inline rcPolyMesh() : verts(0), polys(0), regs(0), nverts(0), npolys(0), nvp(3) {} - inline ~rcPolyMesh() { delete [] verts; delete [] polys; delete [] regs; } + inline rcPolyMesh() : verts(0), polys(0), regs(0), areas(0), nverts(0), npolys(0), nvp(3) {} + + inline ~rcPolyMesh() { delete [] verts; delete [] polys; delete [] regs; delete [] areas; } + unsigned short* verts; // Vertices of the mesh, 3 elements per vertex. unsigned short* polys; // Polygons of the mesh, nvp*2 elements per polygon. - unsigned short* regs; // Regions of the polygons. + unsigned short* regs; // Region ID of the polygons. + unsigned char* areas; // Area ID of polygons. int nverts; // Number of vertices. int npolys; // Number of polygons. int nvp; // Max number of vertices per polygon. @@ -242,12 +246,20 @@ static const unsigned short RC_BORDER_REG = 0x8000; // removed in order to match the segments and vertices at tile boundaries. static const int RC_BORDER_VERTEX = 0x10000; +static const int RC_AREA_BORDER = 0x20000; + // Mask used with contours to extract region id. static const int RC_CONTOUR_REG_MASK = 0xffff; // Null index which is used with meshes to mark unset or invalid indices. static const unsigned short RC_MESH_NULL_IDX = 0xffff; +// Area ID that is considered empty. +static const unsigned char RC_NULL_AREA = 0; + +// Area ID that is considered generally walkable. +static const unsigned char RC_WALKABLE_AREA = 255; + // Value returned by rcGetCon() if the direction is not connected. static const int RC_NOT_CONNECTED = 0xf; @@ -496,6 +508,13 @@ bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb rcHeightfield& hf, rcCompactHeightfield& chf); +bool rcErodeArea(unsigned char areaId, int radius, rcCompactHeightfield& chf); + +bool rcMarkBoxArea(const float* bmin, const float* bmax, unsigned char areaId, + rcCompactHeightfield& chf); + + + // Builds distance field and stores it into the combat heightfield. // Params: // chf - (in/out) compact heightfield representing the open space. @@ -512,13 +531,11 @@ bool rcBuildDistanceField(rcCompactHeightfield& chf); // removed or merged to neighbour region. // Params: // chf - (in/out) compact heightfield representing the open space. -// walkableRadius - (in) the radius of the agent. // minRegionSize - (in) the smallest allowed regions size. // maxMergeRegionSize - (in) the largest allowed regions size which can be merged. // Returns false if operation ran out of memory. bool rcBuildRegions(rcCompactHeightfield& chf, - int walkableRadius, int borderSize, - int minRegionSize, int mergeRegionSize); + int borderSize, int minRegionSize, int mergeRegionSize); // Divides the walkable heighfied into simple regions using simple monotone partitioning. // Each region has only one contour and no overlaps. diff --git a/Recast/Include/RecastLog.h b/Recast/Include/RecastLog.h index 026ef73..d9cd4c3 100644 --- a/Recast/Include/RecastLog.h +++ b/Recast/Include/RecastLog.h @@ -58,6 +58,7 @@ struct rcBuildTimes int filterWalkable; int filterMarkReachable; int buildPolymesh; + int erodeArea; int buildDistanceField; int buildDistanceFieldDist; int buildDistanceFieldBlur; diff --git a/Recast/Source/Recast.cpp b/Recast/Source/Recast.cpp index 16ad9e1..175d9c4 100644 --- a/Recast/Source/Recast.cpp +++ b/Recast/Source/Recast.cpp @@ -162,14 +162,14 @@ bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb return false; } memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); - chf.flags = new unsigned short[spanCount]; - if (!chf.flags) + chf.areas = new unsigned char[spanCount]; + if (!chf.areas) { if (rcGetLog()) - rcGetLog()->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.flags' (%d)", spanCount); + rcGetLog()->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); return false; } - memset(chf.flags, 0, sizeof(unsigned short)*spanCount); + memset(chf.areas, RC_WALKABLE_AREA, sizeof(unsigned char)*spanCount); const int MAX_HEIGHT = 0xffff; @@ -239,8 +239,6 @@ bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb } } - - } } } diff --git a/Recast/Source/RecastArea.cpp b/Recast/Source/RecastArea.cpp new file mode 100644 index 0000000..d9fb0f8 --- /dev/null +++ b/Recast/Source/RecastArea.cpp @@ -0,0 +1,242 @@ +// +// Copyright (c) 2009 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include "Recast.h" +#include "RecastLog.h" +#include "RecastTimer.h" + + +bool rcErodeArea(unsigned char areaId, int radius, rcCompactHeightfield& chf) +{ + const int w = chf.width; + const int h = chf.height; + + rcTimeVal startTime = rcGetPerformanceTimer(); + + unsigned char* dist = new unsigned char[chf.spanCount]; + if (!dist) + return false; + + // Init distance. + memset(dist, 0xff, sizeof(unsigned char)*chf.spanCount); + + // Mark boundary cells. + 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) + { + if (chf.areas[i] != RC_NULL_AREA) + { + const rcCompactSpan& s = chf.spans[i]; + int nc = 0; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != 0xf) + { + 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); + if (chf.areas[ai] == areaId) + nc++; + } + } + // At least one missing neighbour. + if (nc != 4) + dist[i] = 0; + } + } + } + } + + unsigned char nd; + + // Pass 1 + 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) + { + const rcCompactSpan& s = chf.spans[i]; + + if (rcGetCon(s, 0) != 0xf) + { + // (-1,0) + const int ax = x + rcGetDirOffsetX(0); + const int ay = y + rcGetDirOffsetY(0); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); + const rcCompactSpan& as = chf.spans[ai]; + nd = (unsigned char)rcMin((int)dist[ai]+2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (-1,-1) + if (rcGetCon(as, 3) != 0xf) + { + const int aax = ax + rcGetDirOffsetX(3); + const int aay = ay + rcGetDirOffsetY(3); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3); + nd = (unsigned char)rcMin((int)dist[aai]+3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + if (rcGetCon(s, 3) != 0xf) + { + // (0,-1) + const int ax = x + rcGetDirOffsetX(3); + const int ay = y + rcGetDirOffsetY(3); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); + const rcCompactSpan& as = chf.spans[ai]; + nd = (unsigned char)rcMin((int)dist[ai]+2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (1,-1) + if (rcGetCon(as, 2) != 0xf) + { + const int aax = ax + rcGetDirOffsetX(2); + const int aay = ay + rcGetDirOffsetY(2); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2); + nd = (unsigned char)rcMin((int)dist[aai]+3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + } + } + } + + // Pass 2 + for (int y = h-1; y >= 0; --y) + { + for (int x = w-1; x >= 0; --x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + + if (rcGetCon(s, 2) != 0xf) + { + // (1,0) + const int ax = x + rcGetDirOffsetX(2); + const int ay = y + rcGetDirOffsetY(2); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); + const rcCompactSpan& as = chf.spans[ai]; + nd = (unsigned char)rcMin((int)dist[ai]+2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (1,1) + if (rcGetCon(as, 1) != 0xf) + { + const int aax = ax + rcGetDirOffsetX(1); + const int aay = ay + rcGetDirOffsetY(1); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1); + nd = (unsigned char)rcMin((int)dist[aai]+3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + if (rcGetCon(s, 1) != 0xf) + { + // (0,1) + const int ax = x + rcGetDirOffsetX(1); + const int ay = y + rcGetDirOffsetY(1); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); + const rcCompactSpan& as = chf.spans[ai]; + nd = (unsigned char)rcMin((int)dist[ai]+2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (-1,1) + if (rcGetCon(as, 0) != 0xf) + { + const int aax = ax + rcGetDirOffsetX(0); + const int aay = ay + rcGetDirOffsetY(0); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0); + nd = (unsigned char)rcMin((int)dist[aai]+3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + } + } + } + + const unsigned char thr = (unsigned char)(radius*2); + for (int i = 0; i < chf.spanCount; ++i) + if (dist[i] < thr) + chf.areas[i] = 0; + + delete [] dist; + + rcTimeVal endTime = rcGetPerformanceTimer(); + + if (rcGetBuildTimes()) + { + rcGetBuildTimes()->erodeArea += rcGetDeltaTimeUsec(startTime, endTime); + } + + return true; +} + +bool rcMarkBoxArea(const float* bmin, const float* bmax, unsigned char areaId, + rcCompactHeightfield& chf) +{ + int minx = (int)floorf((bmin[0]-chf.bmin[0])/chf.cs); + int miny = (int)floorf((bmin[1]-chf.bmin[1])/chf.ch); + int minz = (int)floorf((bmin[2]-chf.bmin[2])/chf.cs); + int maxx = (int)ceilf((bmax[0]-chf.bmin[0])/chf.cs); + int maxy = (int)ceilf((bmax[1]-chf.bmin[1])/chf.ch); + int maxz = (int)ceilf((bmax[2]-chf.bmin[2])/chf.cs); + + minx = rcClamp(minx, 0, chf.width); + minz = rcClamp(minz, 0, chf.height); + maxx = rcClamp(maxx, 0, chf.width); + maxz = rcClamp(maxz, 0, chf.height); + + for (int z = minz; z < maxz; ++z) + { + for (int x = minx; x < maxx; ++x) + { + const rcCompactCell& c = chf.cells[x+z*chf.width]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan& s = chf.spans[i]; + if ((int)s.y >= miny && (int)s.y < maxy) + { + if (areaId < chf.areas[i]) + chf.areas[i] = areaId; + } + } + } + } + return true; +} diff --git a/Recast/Source/RecastContour.cpp b/Recast/Source/RecastContour.cpp index f6ff78d..2263a91 100644 --- a/Recast/Source/RecastContour.cpp +++ b/Recast/Source/RecastContour.cpp @@ -33,9 +33,11 @@ static int getCornerHeight(int x, int y, int i, int dir, int ch = (int)s.y; int dirp = (dir+1) & 0x3; - unsigned short regs[4] = {0,0,0,0}; + unsigned int regs[4] = {0,0,0,0}; - regs[0] = chf.reg[i]; + // Combine region and area codes in order to prevent + // border vertices which are in between two areas to be removed. + regs[0] = chf.regs[i] | (chf.areas[i] << 16); if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { @@ -44,7 +46,7 @@ static int getCornerHeight(int x, int y, int i, int dir, const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); const rcCompactSpan& as = chf.spans[ai]; ch = rcMax(ch, (int)as.y); - regs[1] = chf.reg[ai]; + regs[1] = chf.regs[ai] | (chf.areas[ai] << 16); if (rcGetCon(as, dirp) != RC_NOT_CONNECTED) { const int ax2 = ax + rcGetDirOffsetX(dirp); @@ -52,7 +54,7 @@ static int getCornerHeight(int x, int y, int i, int dir, const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dirp); const rcCompactSpan& as2 = chf.spans[ai2]; ch = rcMax(ch, (int)as2.y); - regs[2] = chf.reg[ai2]; + regs[2] = chf.regs[ai2] | (chf.areas[ai2] << 16); } } if (rcGetCon(s, dirp) != RC_NOT_CONNECTED) @@ -62,7 +64,7 @@ static int getCornerHeight(int x, int y, int i, int dir, const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp); const rcCompactSpan& as = chf.spans[ai]; ch = rcMax(ch, (int)as.y); - regs[3] = chf.reg[ai]; + regs[3] = chf.regs[ai] | (chf.areas[ai] << 16); if (rcGetCon(as, dir) != RC_NOT_CONNECTED) { const int ax2 = ax + rcGetDirOffsetX(dir); @@ -70,7 +72,7 @@ static int getCornerHeight(int x, int y, int i, int dir, const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dir); const rcCompactSpan& as2 = chf.spans[ai2]; ch = rcMax(ch, (int)as2.y); - regs[2] = chf.reg[ai2]; + regs[2] = chf.regs[ai2] | (chf.areas[ai2] << 16); } } @@ -86,8 +88,9 @@ static int getCornerHeight(int x, int y, int i, int dir, // followed by two interior cells and none of the regions are out of bounds. const bool twoSameExts = (regs[a] & regs[b] & RC_BORDER_REG) != 0 && regs[a] == regs[b]; const bool twoInts = ((regs[c] | regs[d]) & RC_BORDER_REG) == 0; + const bool intsSameArea = (regs[c]>>16) == (regs[d]>>16); const bool noZeros = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0; - if (twoSameExts && twoInts && noZeros) + if (twoSameExts && twoInts && intsSameArea && noZeros) { isBorderVertex = true; break; @@ -109,6 +112,8 @@ static void walkContour(int x, int y, int i, unsigned char startDir = dir; int starti = i; + const unsigned char area = chf.areas[i]; + int iter = 0; while (++iter < 40000) { @@ -116,6 +121,7 @@ static void walkContour(int x, int y, int i, { // Choose the edge corner bool isBorderVertex = false; + bool isAreaBorder = false; int px = x; int py = getCornerHeight(x, y, i, dir, chf, isBorderVertex); int pz = y; @@ -132,10 +138,14 @@ static void walkContour(int x, int y, int i, 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); - r = (int)chf.reg[ai]; + r = (int)chf.regs[ai]; + if (area != chf.areas[ai]) + isAreaBorder = true; } if (isBorderVertex) r |= RC_BORDER_VERTEX; + if (isAreaBorder) + r |= RC_AREA_BORDER; points.push(px); points.push(py); points.push(pz); @@ -280,7 +290,9 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma for (int i = 0, ni = points.size()/4; i < ni; ++i) { int ii = (i+1) % ni; - if ((points[i*4+3] & RC_CONTOUR_REG_MASK) != (points[ii*4+3] & RC_CONTOUR_REG_MASK)) + const bool differentRegs = (points[i*4+3] & RC_CONTOUR_REG_MASK) != (points[ii*4+3] & RC_CONTOUR_REG_MASK); + const bool areaBorders = (points[i*4+3] & RC_AREA_BORDER) != (points[ii*4+3] & RC_AREA_BORDER); + if (differentRegs || areaBorders) { simplified.push(points[i*4+0]); simplified.push(points[i*4+1]); @@ -306,16 +318,33 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma int by = simplified[ii*4+1]; int bz = simplified[ii*4+2]; int bi = simplified[ii*4+3]; - + // Find maximum deviation from the segment. float maxd = 0; int maxi = -1; - int ci = (ai+1) % pn; + int ci, cinc, endi; - // Tesselate only outer edges. - if ((points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0) + // Traverse the segment in lexilogical order so that the + // max deviation is calculated similarly when traversing + // opposite segments. + if (bx > ax || (bx == ax && bz > az)) { - while (ci != bi) + cinc = 1; + ci = (ai+cinc) % pn; + endi = bi; + } + else + { + cinc = pn-1; + ci = (bi+cinc) % pn; + endi = ai; + } + + // Tesselate only outer edges oredges between areas. + if ((points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0 || + (points[ci*4+3] & RC_AREA_BORDER)) + { + while (ci != endi) { float d = distancePtSeg(points[ci*4+0], points[ci*4+1]/4, points[ci*4+2], ax, ay/4, az, bx, by/4, bz); @@ -324,7 +353,7 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma maxd = d; maxi = ci; } - ci = (ci+1) % pn; + ci = (ci+cinc) % pn; } } @@ -571,7 +600,7 @@ bool rcBuildContours(rcCompactHeightfield& chf, { unsigned char res = 0; const rcCompactSpan& s = chf.spans[i]; - if (!chf.reg[i] || (chf.reg[i] & RC_BORDER_REG)) + if (!chf.regs[i] || (chf.regs[i] & RC_BORDER_REG)) { flags[i] = 0; continue; @@ -584,9 +613,9 @@ bool rcBuildContours(rcCompactHeightfield& chf, 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); - r = chf.reg[ai]; + r = chf.regs[ai]; } - if (r == chf.reg[i]) + if (r == chf.regs[i]) res |= (1 << dir); } flags[i] = res ^ 0xf; // Inverse, mark non connected edges. @@ -613,9 +642,10 @@ bool rcBuildContours(rcCompactHeightfield& chf, flags[i] = 0; continue; } - unsigned short reg = chf.reg[i]; + const unsigned short reg = chf.regs[i]; if (!reg || (reg & RC_BORDER_REG)) continue; + const unsigned char area = chf.areas[i]; verts.resize(0); simplified.resize(0); @@ -656,6 +686,7 @@ bool rcBuildContours(rcCompactHeightfield& chf, cont->cz /= cont->nverts;*/ cont->reg = reg; + cont->area = area; } } } diff --git a/Recast/Source/RecastMesh.cpp b/Recast/Source/RecastMesh.cpp index 0d53eb1..d60f650 100644 --- a/Recast/Source/RecastMesh.cpp +++ b/Recast/Source/RecastMesh.cpp @@ -497,11 +497,11 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m } int nedges = 0; - rcScopedDelete edges = new int[nrem*nvp*3]; + rcScopedDelete edges = new int[nrem*nvp*4]; if (!edges) { if (rcGetLog()) - rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'edges' (%d).", nrem*nvp*3); + rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'edges' (%d).", nrem*nvp*4); return false; } @@ -522,6 +522,15 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' (%d).", nrem*nvp); return false; } + + int nharea = 0; + rcScopedDelete harea = new int[nrem*nvp]; + if (!harea) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'harea' (%d).", nrem*nvp); + return false; + } for (int i = 0; i < mesh.npolys; ++i) { @@ -537,10 +546,11 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m { if (p[j] != rem && p[k] != rem) { - int* e = &edges[nedges*3]; + int* e = &edges[nedges*4]; e[0] = p[k]; e[1] = p[j]; e[2] = mesh.regs[i]; + e[3] = mesh.areas[i]; nedges++; } } @@ -548,6 +558,7 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2]; memcpy(p,p2,sizeof(unsigned short)*nvp); mesh.regs[i] = mesh.regs[mesh.npolys-1]; + mesh.areas[i] = mesh.areas[mesh.npolys-1]; mesh.npolys--; --i; } @@ -572,15 +583,18 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m } for (int i = 0; i < nedges; ++i) { - if (edges[i*3+0] > rem) edges[i*3+0]--; - if (edges[i*3+1] > rem) edges[i*3+1]--; + if (edges[i*4+0] > rem) edges[i*4+0]--; + if (edges[i*4+1] > rem) edges[i*4+1]--; } if (nedges == 0) return true; + // Start with one vertex, keep appending connected + // segments to the start and end of the hole. hole[nhole] = edges[0]; hreg[nhole] = edges[2]; + harea[nhole] = edges[3]; nhole++; while (nedges) @@ -589,28 +603,34 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m for (int i = 0; i < nedges; ++i) { - const int ea = edges[i*3+0]; - const int eb = edges[i*3+1]; - const int r = edges[i*3+2]; + const int ea = edges[i*4+0]; + const int eb = edges[i*4+1]; + const int r = edges[i*4+2]; + const int a = edges[i*4+3]; bool add = false; if (hole[0] == eb) { + // The segment matches the beginning of the hole boundary. pushFront(ea, hole, nhole); pushFront(r, hreg, nhreg); + pushFront(a, harea, nharea); add = true; } else if (hole[nhole-1] == ea) { + // The segment matches the end of the hole boundary. pushBack(eb, hole, nhole); pushBack(r, hreg, nhreg); + pushBack(a, harea, nharea); add = true; } if (add) { - // Remove edge. - edges[i*3+0] = edges[(nedges-1)*3+0]; - edges[i*3+1] = edges[(nedges-1)*3+1]; - edges[i*3+2] = edges[(nedges-1)*3+2]; + // The edge segment was added, remove it. + edges[i*4+0] = edges[(nedges-1)*4+0]; + edges[i*4+1] = edges[(nedges-1)*4+1]; + edges[i*4+2] = edges[(nedges-1)*4+2]; + edges[i*4+3] = edges[(nedges-1)*4+3]; --nedges; match = true; --i; @@ -680,6 +700,13 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m rcGetLog()->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pregs' (%d).", ntris); return false; } + rcScopedDelete pareas = new unsigned char[ntris]; + if (!pregs) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' (%d).", ntris); + return false; + } unsigned short* tmpPoly = &polys[ntris*nvp]; @@ -694,7 +721,8 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m 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] = hreg[t[0]]; + pregs[npolys] = (unsigned short)hreg[t[0]]; + pareas[npolys] = (unsigned char)harea[t[0]]; npolys++; } } @@ -737,6 +765,7 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); memcpy(pb, &polys[(npolys-1)*nvp], sizeof(unsigned short)*nvp); pregs[bestPb] = pregs[npolys-1]; + pareas[bestPb] = pareas[npolys-1]; npolys--; } else @@ -756,6 +785,7 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m for (int j = 0; j < nvp; ++j) p[j] = polys[i*nvp+j]; mesh.regs[mesh.npolys] = pregs[i]; + mesh.areas[mesh.npolys] = pareas[i]; mesh.npolys++; if (mesh.npolys > maxTris) { @@ -827,7 +857,14 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh) rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.regs' (%d).", maxTris); return false; } - + mesh.areas = new unsigned char[maxTris]; + if (!mesh.areas) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.areas' (%d).", maxTris); + return false; + } + mesh.nverts = 0; mesh.npolys = 0; mesh.nvp = nvp; @@ -835,6 +872,7 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh) memset(mesh.verts, 0, sizeof(unsigned short)*maxVertices*3); memset(mesh.polys, 0xff, sizeof(unsigned short)*maxTris*nvp*2); memset(mesh.regs, 0, sizeof(unsigned short)*maxTris); + memset(mesh.areas, 0, sizeof(unsigned char)*maxTris); rcScopedDelete nextVert = new int[maxVertices]; if (!nextVert) @@ -992,6 +1030,7 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh) for (int k = 0; k < nvp; ++k) p[k] = q[k]; mesh.regs[mesh.npolys] = cont.reg; + mesh.areas[mesh.npolys] = cont.area; mesh.npolys++; if (mesh.npolys > maxTris) { @@ -1091,6 +1130,15 @@ bool rcMergePolyMeshes(rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh) return false; } memset(mesh.regs, 0, sizeof(unsigned short)*maxPolys); + + mesh.areas = new unsigned char[maxPolys]; + if (!mesh.areas) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.areas' (%d).", maxPolys); + return false; + } + memset(mesh.areas, 0, sizeof(unsigned char)*maxPolys); rcScopedDelete nextVert = new int[maxVerts]; if (!nextVert) @@ -1131,7 +1179,7 @@ bool rcMergePolyMeshes(rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh) { unsigned short* v = &pmesh->verts[j*3]; vremap[j] = addVertex(v[0]+ox, v[1], v[2]+oz, - mesh.verts, firstVert, nextVert, mesh.nverts); + mesh.verts, firstVert, nextVert, mesh.nverts); } for (int j = 0; j < pmesh->npolys; ++j) @@ -1139,6 +1187,7 @@ bool rcMergePolyMeshes(rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh) unsigned short* tgt = &mesh.polys[mesh.npolys*2*mesh.nvp]; unsigned short* src = &pmesh->polys[j*2*mesh.nvp]; mesh.regs[mesh.npolys] = pmesh->regs[j]; + mesh.areas[mesh.npolys] = pmesh->areas[j]; mesh.npolys++; for (int k = 0; k < mesh.nvp; ++k) { diff --git a/Recast/Source/RecastRegion.cpp b/Recast/Source/RecastRegion.cpp index d4b40fa..f1956d0 100644 --- a/Recast/Source/RecastRegion.cpp +++ b/Recast/Source/RecastRegion.cpp @@ -47,11 +47,19 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf, for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; + const unsigned char area = chf.areas[i]; + int nc = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - nc++; + { + 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); + if (area == chf.areas[ai]) + nc++; + } } if (nc != 4) src[i] = 0; @@ -59,6 +67,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf, } } + // Pass 1 for (int y = 0; y < h; ++y) { @@ -236,13 +245,15 @@ static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr, static bool floodRegion(int x, int y, int i, - unsigned short level, unsigned short minLevel, unsigned short r, + unsigned short level, unsigned short r, rcCompactHeightfield& chf, unsigned short* srcReg, unsigned short* srcDist, rcIntArray& stack) { const int w = chf.width; + const unsigned char area = chf.areas[i]; + // Flood fill mark region. stack.resize(0); stack.push((int)x); @@ -251,7 +262,7 @@ static bool floodRegion(int x, int y, int i, srcReg[i] = r; srcDist[i] = 0; - unsigned short lev = level >= minLevel+2 ? level-2 : minLevel; + unsigned short lev = level >= 2 ? level-2 : 0; int count = 0; while (stack.size() > 0) @@ -272,6 +283,8 @@ static bool floodRegion(int x, int y, int i, const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); + if (chf.areas[ai] != area) + continue; unsigned short nr = srcReg[ai]; if (nr != 0 && nr != r) ar = nr; @@ -284,7 +297,8 @@ static bool floodRegion(int x, int y, int i, const int ax2 = ax + rcGetDirOffsetX(dir2); const int ay2 = ay + rcGetDirOffsetY(dir2); const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); - + if (chf.areas[ai2] != area) + continue; unsigned short nr = srcReg[ai2]; if (nr != 0 && nr != r) ar = nr; @@ -306,6 +320,8 @@ static bool floodRegion(int x, int y, int i, const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); + if (chf.areas[ai] != area) + continue; if (chf.dist[ai] >= lev) { if (srcReg[ai] == 0) @@ -342,7 +358,7 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { - if (chf.dist[i] >= level && srcReg[i] == 0) + if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) { stack.push(x); stack.push(y); @@ -373,6 +389,7 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, unsigned short r = srcReg[i]; unsigned short d2 = 0xffff; + const unsigned char area = chf.areas[i]; const rcCompactSpan& s = chf.spans[i]; for (int dir = 0; dir < 4; ++dir) { @@ -380,6 +397,7 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, 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); + if (chf.areas[ai] != area) continue; if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0) { if ((int)srcDist[ai]+2 < (int)d2) @@ -422,10 +440,11 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, struct rcRegion { - inline rcRegion() : count(0), id(0), remap(false) {} + inline rcRegion() : count(0), id(0), area(0), remap(false) {} int count; unsigned short id; + unsigned char area; bool remap; rcIntArray connections; rcIntArray floors; @@ -471,6 +490,8 @@ static void replaceNeighbour(rcRegion& reg, unsigned short oldId, unsigned short static bool canMergeWithRegion(const rcRegion& rega, const rcRegion& regb) { + if (rega.area != regb.area) + return false; int n = 0; for (int i = 0; i < rega.connections.size(); ++i) { @@ -719,6 +740,8 @@ static bool filterSmallRegions(int minRegionSize, int mergeRegionSize, if (reg.connections.size() > 0) continue; + reg.area = chf.areas[i]; + // Check if this cell is next to a border. int ndir = -1; for (int dir = 0; dir < 4; ++dir) @@ -936,7 +959,7 @@ bool rcBuildDistanceField(rcCompactHeightfield& chf) } static void paintRectRegion(int minx, int maxx, int miny, int maxy, - unsigned short regId, unsigned short minLevel, + unsigned short regId, rcCompactHeightfield& chf, unsigned short* srcReg) { const int w = chf.width; @@ -947,7 +970,7 @@ static void paintRectRegion(int minx, int maxx, int miny, int maxy, const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { - if (chf.dist[i] >= minLevel) + if (chf.areas[i] != RC_NULL_AREA) srcReg[i] = regId; } } @@ -966,20 +989,18 @@ struct rcSweepSpan }; bool rcBuildRegionsMonotone(rcCompactHeightfield& chf, - int walkableRadius, int borderSize, - int minRegionSize, int mergeRegionSize) + int borderSize, int minRegionSize, int mergeRegionSize) { - rcTimeVal startTime = rcGetPerformanceTimer(); +/* rcTimeVal startTime = rcGetPerformanceTimer(); const int w = chf.width; const int h = chf.height; - unsigned short minLevel = (unsigned short)(walkableRadius*2); unsigned short id = 1; - if (chf.reg) + if (chf.regs) { - delete [] chf.reg; - chf.reg = 0; + delete [] chf.regs; + chf.regs = 0; } rcScopedDelete srcReg = new unsigned short[chf.spanCount]; @@ -1003,10 +1024,10 @@ bool rcBuildRegionsMonotone(rcCompactHeightfield& chf, // Mark border regions. if (borderSize) { - paintRectRegion(0, borderSize, 0, h, id|RC_BORDER_REG, minLevel, chf, srcReg); id++; - paintRectRegion(w-borderSize, w, 0, h, id|RC_BORDER_REG, minLevel, chf, srcReg); id++; - paintRectRegion(0, w, 0, borderSize, id|RC_BORDER_REG, minLevel, chf, srcReg); id++; - paintRectRegion(0, w, h-borderSize, h, id|RC_BORDER_REG, minLevel, chf, srcReg); id++; + paintRectRegion(0, borderSize, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(w-borderSize, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(0, w, 0, borderSize, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(0, w, h-borderSize, h, id|RC_BORDER_REG, chf, srcReg); id++; } rcIntArray prev(256); @@ -1110,7 +1131,7 @@ bool rcBuildRegionsMonotone(rcCompactHeightfield& chf, rcTimeVal filterEndTime = rcGetPerformanceTimer(); // Store the result out. - chf.reg = srcReg; + chf.regs = srcReg; srcReg = 0; rcTimeVal endTime = rcGetPerformanceTimer(); @@ -1120,23 +1141,23 @@ bool rcBuildRegionsMonotone(rcCompactHeightfield& chf, rcGetBuildTimes()->buildRegions += rcGetDeltaTimeUsec(startTime, endTime); rcGetBuildTimes()->buildRegionsFilter += rcGetDeltaTimeUsec(filterStartTime, filterEndTime); } +*/ return true; } bool rcBuildRegions(rcCompactHeightfield& chf, - int walkableRadius, int borderSize, - int minRegionSize, int mergeRegionSize) + int borderSize, int minRegionSize, int mergeRegionSize) { rcTimeVal startTime = rcGetPerformanceTimer(); const int w = chf.width; const int h = chf.height; - if (!chf.reg) + if (!chf.regs) { - chf.reg = new unsigned short[chf.spanCount]; - if (!chf.reg) + chf.regs = new unsigned short[chf.spanCount]; + if (!chf.regs) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'chf.reg' (%d).", chf.spanCount); @@ -1167,21 +1188,23 @@ bool rcBuildRegions(rcCompactHeightfield& chf, unsigned short regionId = 1; unsigned short level = (chf.maxDistance+1) & ~1; - - unsigned short minLevel = (unsigned short)(walkableRadius*2); - - const int expandIters = 4 + walkableRadius * 2; + + // TODO: Figure better formula, expandIters defines how much the + // watershed "overflows" and simplifies the regions. Tying it to + // agent radius was usually good indication how greedy it could be. +// const int expandIters = 4 + walkableRadius * 2; + const int expandIters = 8; // Mark border regions. - paintRectRegion(0, borderSize, 0, h, regionId|RC_BORDER_REG, minLevel, chf, srcReg); regionId++; - paintRectRegion(w-borderSize, w, 0, h, regionId|RC_BORDER_REG, minLevel, chf, srcReg); regionId++; - paintRectRegion(0, w, 0, borderSize, regionId|RC_BORDER_REG, minLevel, chf, srcReg); regionId++; - paintRectRegion(0, w, h-borderSize, h, regionId|RC_BORDER_REG, minLevel, chf, srcReg); regionId++; + paintRectRegion(0, borderSize, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(w-borderSize, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(0, w, 0, borderSize, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(0, w, h-borderSize, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; rcTimeVal expTime = 0; rcTimeVal floodTime = 0; - while (level > minLevel) + while (level > 0) { level = level >= 2 ? level-2 : 0; @@ -1206,10 +1229,10 @@ bool rcBuildRegions(rcCompactHeightfield& chf, const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { - if (chf.dist[i] < level || srcReg[i] != 0) + if (chf.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA) continue; - if (floodRegion(x, y, i, minLevel, level, regionId, chf, srcReg, srcDist, stack)) + if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) regionId++; } } @@ -1220,7 +1243,7 @@ bool rcBuildRegions(rcCompactHeightfield& chf, } // Expand current regions until no empty connected cells found. - if (expandRegions(expandIters*8, minLevel, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) + if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); @@ -1236,10 +1259,9 @@ bool rcBuildRegions(rcCompactHeightfield& chf, return false; rcTimeVal filterEndTime = rcGetPerformanceTimer(); - + // Write the result out. - for (int i = 0; i < chf.spanCount; ++i) - chf.reg[i] = srcReg[i]; + memcpy(chf.regs, srcReg, sizeof(unsigned short)*chf.spanCount); rcTimeVal endTime = rcGetPerformanceTimer();