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).

This commit is contained in:
Mikko Mononen 2010-02-01 14:08:06 +00:00
parent 139409c645
commit 9b881bde8c
7 changed files with 451 additions and 91 deletions

View File

@ -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.

View File

@ -58,6 +58,7 @@ struct rcBuildTimes
int filterWalkable;
int filterMarkReachable;
int buildPolymesh;
int erodeArea;
int buildDistanceField;
int buildDistanceFieldDist;
int buildDistanceFieldBlur;

View File

@ -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
}
}
}
}
}

View File

@ -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 <float.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#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;
}

View File

@ -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;
}
}
}

View File

@ -497,11 +497,11 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
}
int nedges = 0;
rcScopedDelete<int> edges = new int[nrem*nvp*3];
rcScopedDelete<int> 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<int> 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<unsigned char> 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<int> 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<int> 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)
{

View File

@ -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<unsigned short> 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();