rcBuildPolyMesh ,将上面求出的contours 拆分成多个小的凸的polygons,方法是先将contours三角化,再对三角形组装,对生成的polygons有最大边数限制,每个polygon 都记录area和regs,一个firstVert存放对所有顶点哈希划分的首个数据的下标,nextVert 存放所有顶点的的下一个顶点的下标。大小是大于等于nverts的。
在recastmesh.cpp 1017行,有个对总的顶点数的限制
if (maxVertices >= 0xfffe)
{
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices %d.", maxVertices);
return false;
}
triangulate() 多边形三角化。原理就是i-1,i,i+1,i+2 四个相邻点,满足i+2在i-1,i,i+1的内部,并且线段i,i+2 与其他所有边都不相交,则i,i+1,i+2可以组成一个三角形,将i+1 从顶点中移除,再对剩余顶点做同样操作。另外多了一点对下面这个部分的判断操作。这是因为前一步融合holes到outline时,中间多出来一根线。
static bool diagonal(int i, int j, int n, const int* verts, int* indices)
{
return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices);
}
inCone(i, j, n, verts, indices) 与前面一章的incone类似
diagonalie(i, j, n, verts, indices)判断i j点组成的边与其他边都不相交。
将三角形先放到poly缓存中。
int npolys = 0;
memset(polys, 0xff, maxVertsPerCont*nvp*sizeof(unsigned short));
for (int j = 0; j < ntris; ++j)
{
int* t = &tris[j*3];
if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2])
{
polys[npolys*nvp+0] = (unsigned short)indices[t[0]];
polys[npolys*nvp+1] = (unsigned short)indices[t[1]];
polys[npolys*nvp+2] = (unsigned short)indices[t[2]];
npolys++;
}
}
接下来,多边形合并,根据预设的条件maxVertsPerCont 来进行合并。这里在上面切分三角形后,再对三角形进行合并或者合并后的多边形进行合并。做最大限度的合并
这个在原始注释下理解应该比较容易。下面是可合并的多边形所满足的条件。注意合并后的多变形必须是凸多边形。变数少于maxVertsPerCont。
static int getPolyMergeValue(unsigned short* pa, unsigned short* pb,
const unsigned short* verts, int& ea, int& eb,
const int nvp)
{
const int na = countPolyVerts(pa, nvp);
const int nb = countPolyVerts(pb, nvp);
// If the merged polygon would be too big, do not merge.
if (na+nb-2 > nvp)
return -1;
// Check if the polygons share an edge.
ea = -1;
eb = -1;
for (int i = 0; i < na; ++i)
{
unsigned short va0 = pa[i];
unsigned short va1 = pa[(i+1) % na];
if (va0 > va1)
rcSwap(va0, va1);
for (int j = 0; j < nb; ++j)
{
unsigned short vb0 = pb[j];
unsigned short vb1 = pb[(j+1) % nb];
if (vb0 > vb1)
rcSwap(vb0, vb1);
if (va0 == vb0 && va1 == vb1)
{
ea = i;
eb = j;
break;
}
}
}
// No common edge, cannot merge.
if (ea == -1 || eb == -1)
return -1;
// Check to see if the merged polygon would be convex.
unsigned short va, vb, vc;
va = pa[(ea+na-1) % na];
vb = pa[ea];
vc = pb[(eb+2) % nb];
if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3]))
return -1;
va = pb[(eb+nb-1) % nb];
vb = pb[eb];
vc = pa[(ea+2) % na];
if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3]))
return -1;
va = pa[ea];
vb = pa[(ea+1)%na];
int dx = (int)verts[va*3+0] - (int)verts[vb*3+0];
int dy = (int)verts[va*3+2] - (int)verts[vb*3+2];
return dx*dx + dy*dy;
}
最后将polygon存放到mesh中。
再循环对其他的原始的contour做这个操作。知道所有的原始contour全部做完。
canRemoveVertex 比较容易理解,计算边缘点是否可以被移除,主要需要理解里面的mesh->edges数组,存放所有的边信息,用于后面产生poly信息。mesh->polys 数组存放多边形信息()每个多边形最多mesh->nvp条边。
下面是removeVertex
static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem, const int maxTris)
{
const int nvp = mesh.nvp;
// Count number of polygons to remove.
int numRemovedVerts = 0;
for (int i = 0; i < mesh.npolys; ++i)
{
unsigned short* p = &mesh.polys[i*nvp*2];
const int nv = countPolyVerts(p, nvp);
for (int j = 0; j < nv; ++j)
{
if (p[j] == rem)
numRemovedVerts++;
}
}
int nedges = 0;
rcScopedDelete<int> edges((int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp*4, RC_ALLOC_TEMP));
if (!edges)
{
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'edges' (%d).", numRemovedVerts*nvp*4);
return false;
}
int nhole = 0;
rcScopedDelete<int> hole((int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP));
if (!hole)
{
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hole' (%d).", numRemovedVerts*nvp);
return false;
}
int nhreg = 0;
rcScopedDelete<int> hreg((int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP));
if (!hreg)
{
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' (%d).", numRemovedVerts*nvp);
return false;
}
int nharea = 0;
rcScopedDelete<int> harea((int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP));
if (!harea)
{
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'harea' (%d).", numRemovedVerts*nvp);
return false;
}
//注意,一个删除点可能刚好是多个多边形的会交点。下面是为了剔除这样的点的操作
for (int i = 0; i < mesh.npolys; ++i)
{
unsigned short* p = &mesh.polys[i*nvp*2];
const int nv = countPolyVerts(p, nvp);
bool hasRem = false;
for (int j = 0; j < nv; ++j)
if (p[j] == rem) hasRem = true;
if (hasRem)
{
// Collect edges which does not touch the removed vertex.
for (int j = 0, k = nv-1; j < nv; k = j++)
{
if (p[j] != rem && p[k] != rem)
{
int* e = &edges[nedges*4]; //存放 所有的poly中 与移除点不相关的边,注意这样就会让poly断开,所有edges只是存放剩下的边
e[0] = p[k];
e[1] = p[j];
e[2] = mesh.regs[i];
e[3] = mesh.areas[i];
nedges++;
}
}
// Remove the polygon.
unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2]; //移除对应的多边形
if (p != p2)
memcpy(p,p2,sizeof(unsigned short)*nvp);
memset(p+nvp,0xff,sizeof(unsigned short)*nvp);
mesh.regs[i] = mesh.regs[mesh.npolys-1];
mesh.areas[i] = mesh.areas[mesh.npolys-1];
mesh.npolys--;
--i;
}
}
// Remove vertex.
for (int i = (int)rem; i < mesh.nverts - 1; ++i)//从mesh中移除掉移除点,并在后面对所有的存放点的下标的缓存中,值大于rem全部减1
{
mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0];
mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1];
mesh.verts[i*3+2] = mesh.verts[(i+1)*3+2];
}
mesh.nverts--;
// Adjust indices to match the removed vertex layout.
for (int i = 0; i < mesh.npolys; ++i)
{
unsigned short* p = &mesh.polys[i*nvp*2];
const int nv = countPolyVerts(p, nvp);
for (int j = 0; j < nv; ++j)
if (p[j] > rem) p[j]--;
}
for (int i = 0; i < nedges; ++i)
{
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;
//接下来将断开的地方(这里叫hole,与rcbuildContour中的hole 不是一个概念)修补起来
// Start with one vertex, keep appending connected
// segments to the start and end of the hole.
pushBack(edges[0], hole, nhole);
pushBack(edges[2], hreg, nhreg);
pushBack(edges[3], harea, nharea);
while (nedges) //主要操作是将分散的线段,又变回连续的点集。这些点集首尾相接刚好成了完整的多边形,这样就完成了特定点的剔除
{
bool match = false;
for (int i = 0; i < nedges; ++i)
{
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)
{
// 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;
}
}
if (!match)
break;
}
//经过上面的步骤,holes中存放的可能是一个大的多边形,可能需要对这个多边形进行拆分,需要再次做个(三角形化,再组装)的操作。
rcScopedDelete<int> tris((int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP));//存放顶点下标的下标,这里顶点下标存放在hole中,而tris存放三角形,三角形的顶点这里使用的就是通过hole数组的下标来访问
if (!tris)
{
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tris' (%d).", nhole*3);
return false;
}
rcScopedDelete<int> tverts((int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP));
if (!tverts)
{
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' (%d).", nhole*4);
return false;
}
rcScopedDelete<int> thole((int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP));
if (!thole)
{
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'thole' (%d).", nhole);
return false;
}
// Generate temp vertex array for triangulation.
for (int i = 0; i < nhole; ++i)
{
const int pi = hole[i];
tverts[i*4+0] = mesh.verts[pi*3+0];
tverts[i*4+1] = mesh.verts[pi*3+1];
tverts[i*4+2] = mesh.verts[pi*3+2];
tverts[i*4+3] = 0;
thole[i] = i;
}
// Triangulate the hole.
int ntris = triangulate(nhole, &tverts[0], &thole[0], tris);//
if (ntris < 0)
{
ntris = -ntris;
ctx->log(RC_LOG_WARNING, "removeVertex: triangulate() returned bad results.");
}
// Merge the hole triangles back to polygons.
rcScopedDelete<unsigned short> polys((unsigned short*)rcAlloc(sizeof(unsigned short)*(ntris+1)*nvp, RC_ALLOC_TEMP));
if (!polys)
{
ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'polys' (%d).", (ntris+1)*nvp);
return false;
}
rcScopedDelete<unsigned short> pregs((unsigned short*)rcAlloc(sizeof(unsigned short)*ntris, RC_ALLOC_TEMP));
if (!pregs)
{
ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pregs' (%d).", ntris);
return false;
}
rcScopedDelete<unsigned char> pareas((unsigned char*)rcAlloc(sizeof(unsigned char)*ntris, RC_ALLOC_TEMP));
if (!pareas)
{
ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' (%d).", ntris);
return false;
}
unsigned short* tmpPoly = &polys[ntris*nvp];
// Build initial polygons.
int npolys = 0;
memset(polys, 0xff, ntris*nvp*sizeof(unsigned short));
for (int j = 0; j < ntris; ++j)
{
int* t = &tris[j*3];
if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2])
{
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]];
// 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++;
}
}
if (!npolys)
return true;
// Merge polygons.
if (nvp > 3)//合并多边形,参考前面讲解。
{
for (;;)
{
// Find best polygons to merge.
int bestMergeVal = 0;
int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
for (int j = 0; j < npolys-1; ++j)
{
unsigned short* pj = &polys[j*nvp];
for (int k = j+1; k < npolys; ++k)
{
unsigned short* pk = &polys[k*nvp];
int ea, eb;
int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb, nvp);
if (v > bestMergeVal)
{
bestMergeVal = v;
bestPa = j;
bestPb = k;
bestEa = ea;
bestEb = eb;
}
}
}
if (bestMergeVal > 0)
{
// Found best, merge.
unsigned short* pa = &polys[bestPa*nvp];
unsigned short* pb = &polys[bestPb*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);
pregs[bestPb] = pregs[npolys-1];
pareas[bestPb] = pareas[npolys-1];
npolys--;
}
else
{
// Could not merge any polygons, stop.
break;
}
}
}
// Store polygons.
for (int i = 0; i < npolys; ++i)//存放多边形
{
if (mesh.npolys >= maxTris) break;
unsigned short* p = &mesh.polys[mesh.npolys*nvp*2];
memset(p,0xff,sizeof(unsigned short)*nvp*2);
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)
{
ctx->log(RC_LOG_ERROR, "removeVertex: Too many polygons %d (max:%d).", mesh.npolys, maxTris);
return false;
}
}
return true;
}
buildMeshAdjacency 建立多边形的邻接关系。mesh->polys的每个poly的缓存大小为vertsPerPoly*2,第一个vertsperpoly存放的是poly的顶点的下标,而第二个vertsPerPoly,存放当前poly中 以这个点作为起始点的edge的 相邻poly的 下标。理解这里需要先看下面:
现有相邻的两个多边形 poly 1 和poly 2,按recast中的顺时针方向存放多边形,那么poly 1的点的顺序为[x]DCBA[x],poly 2的点的存放顺序是[x]EBCF[x]。公共边BC。但是两者的顺序是相反的。也就是通过索引下标i(j,j+1)访问索引的时候V0,V1,在poly1中V0V1为CB,在poly2中V0V1为BC。
// Store adjacency
for (int i = 0; i < edgeCount; ++i)
{
const rcEdge& e = edges[i];
if (e.poly[0] != e.poly[1])//判断这个edge是否是两个poly的共用edge,起点和终点记录的poly不一样就说明是共用边
{
unsigned short* p0 = &polys[e.poly[0]*vertsPerPoly*2];
unsigned short* p1 = &polys[e.poly[1]*vertsPerPoly*2];
//将邻接的poly下标存放到这个poly的这个公用edge的起始点。注意,两个poly中的这个共用的edge的起始点不一样。结合上面的图理解。
p0[vertsPerPoly + e.polyEdge[0]] = e.poly[1];
p1[vertsPerPoly + e.polyEdge[1]] = e.poly[0];
}
}