3D世界如何寻路,导航寻路RecastNavigation解析(下)

导航

3D世界如何寻路,导航寻路RecastNavigation解析(上)
3D世界如何寻路,导航寻路RecastNavigation解析(中)

构建Mesh

1.切割三角形

在这里插入图片描述

首先把轮廓切割成一个个三角形

static int triangulate(int n, const int* verts, int* indices, int* tris)
{
	int ntris = 0;
	int* dst = tris;
	
	// The last bit of the index is used to indicate if the vertex can be removed.
	for (int i = 0; i < n; i++)
	{
		int i1 = next(i, n);
		int i2 = next(i1, n);
		if (diagonal(i, i2, n, verts, indices))
			indices[i1] |= 0x80000000;
	}
	
	while (n > 3)
	{
		int minLen = -1;
		int mini = -1;
		for (int i = 0; i < n; i++)
		{
			int i1 = next(i, n);
			if (indices[i1] & 0x80000000)
			{
				const int* p0 = &verts[(indices[i] & 0x0fffffff) * 4];
				const int* p2 = &verts[(indices[next(i1, n)] & 0x0fffffff) * 4];
				
				int dx = p2[0] - p0[0];
				int dy = p2[2] - p0[2];
				int len = dx*dx + dy*dy;
				
				if (minLen < 0 || len < minLen)
				{
					minLen = len;
					mini = i;
				}
			}
		}
		
		if (mini == -1)
		{
			// We might get here because the contour has overlapping segments, like this:
			//
			//  A o-o=====o---o B
			//   /  |C   D|    \.
			//  o   o     o     o
			//  :   :     :     :
			// We'll try to recover by loosing up the inCone test a bit so that a diagonal
			// like A-B or C-D can be found and we can continue.
			minLen = -1;
			mini = -1;
			for (int i = 0; i < n; i++)
			{
				int i1 = next(i, n);
				int i2 = next(i1, n);
				if (diagonalLoose(i, i2, n, verts, indices))
				{
					const int* p0 = &verts[(indices[i] & 0x0fffffff) * 4];
					const int* p2 = &verts[(indices[next(i2, n)] & 0x0fffffff) * 4];
					int dx = p2[0] - p0[0];
					int dy = p2[2] - p0[2];
					int len = dx*dx + dy*dy;
					
					if (minLen < 0 || len < minLen)
					{
						minLen = len;
						mini = i;
					}
				}
			}
			if (mini == -1)
			{
				// The contour is messed up. This sometimes happens
				// if the contour simplification is too aggressive.
				return -ntris;
			}
		}
		
		int i = mini;
		int i1 = next(i, n);
		int i2 = next(i1, n);
		
		*dst++ = indices[i] & 0x0fffffff;
		*dst++ = indices[i1] & 0x0fffffff;
		*dst++ = indices[i2] & 0x0fffffff;
		ntris++;
		
		// Removes P[i1] by copying P[i+1]...P[n-1] left one index.
		n--;
		for (int k = i1; k < n; k++)
			indices[k] = indices[k+1];
		
		if (i1 >= n) i1 = 0;
		i = prev(i1,n);
		// Update diagonal flags.
		if (diagonal(prev(i, n), i1, n, verts, indices))
			indices[i] |= 0x80000000;
		else
			indices[i] &= 0x0fffffff;
		
		if (diagonal(i, next(i1, n), n, verts, indices))
			indices[i1] |= 0x80000000;
		else
			indices[i1] &= 0x0fffffff;
	}
	
	// Append the remaining triangle.
	*dst++ = indices[0] & 0x0fffffff;
	*dst++ = indices[1] & 0x0fffffff;
	*dst++ = indices[2] & 0x0fffffff;
	ntris++;
	
	return ntris;
}

这个函数的实现使用了一种称为 "耳剪法" 的算法,该算法通过逐个剪掉多边形的 “耳朵”(一个可以被移除的顶点和其相邻的两个顶点可以构成的三角形)来将多边形分割成三角形。

函数首先遍历所有的顶点,检查每个顶点和其下两个的顶点是否可以构成一个对角线。如果可以就将该顶点的索引的最高位设置为1(indices[i1] |= 0x80000000),表示该顶点可以被移除。

循环找到构成对角线长度最短可以被移除的顶点后,函数会将其相邻的两个顶点和它自己构成一个新的三角形,然后将这个三角形的顶点索引添加到 tris 数组中。然后,函数会更新 indices 数组,将被移除的顶点的索引删除,同时更新其相邻顶点的对角线标志

最后,函数会将剩下的三个顶点构成的三角形添加到 tris 数组中,然后返回生成的三角形的数量。

static bool diagonalieLoose(int i, int j, int n, const int* verts, int* indices)
{
	const int* d0 = &verts[(indices[i] & 0x0fffffff) * 4];
	const int* d1 = &verts[(indices[j] & 0x0fffffff) * 4];
	
	// For each edge (k,k+1) of P
	for (int k = 0; k < n; k++)
	{
		int k1 = next(k, n);
		// Skip edges incident to i or j
		if (!((k == i) || (k1 == i) || (k == j) || (k1 == j)))
		{
			const int* p0 = &verts[(indices[k] & 0x0fffffff) * 4];
			const int* p1 = &verts[(indices[k1] & 0x0fffffff) * 4];
			
			if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1))
				continue;
			
			if (intersectProp(d0, d1, p0, p1))
				return false;
		}
	}
	return true;
}

用于判断i,j连成的边,是否和其他两两组成边相交(这条边的2个顶点也不能再i,j顶点上)

static bool	inConeLoose(int i, int j, int n, const int* verts, int* indices)
{
	const int* pi = &verts[(indices[i] & 0x0fffffff) * 4];
	const int* pj = &verts[(indices[j] & 0x0fffffff) * 4];
	const int* pi1 = &verts[(indices[next(i, n)] & 0x0fffffff) * 4];
	const int* pin1 = &verts[(indices[prev(i, n)] & 0x0fffffff) * 4];
	
	// If P[i] is a convex vertex [ i+1 left or on (i-1,i) ].
	if (leftOn(pin1, pi, pi1))
		return leftOn(pi, pj, pin1) && leftOn(pj, pi, pi1);
	// Assume (i-1,i,i+1) not collinear.
	// else P[i] is reflex.
	return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1));
}

判断i,j组成的边,是否在i,i-1,i+1组成的三角形内,并且不共线

在这里插入图片描述

2.合并多边形

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

getPolyMergeValue用于获得共边长度

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);
	
	// Merge polygons.
	memset(tmp, 0xff, sizeof(unsigned short)*nvp);
	int n = 0;
	// Add pa
	for (int i = 0; i < na-1; ++i)
		tmp[n++] = pa[(ea+1+i) % na];
	// Add pb
	for (int i = 0; i < nb-1; ++i)
		tmp[n++] = pb[(eb+1+i) % nb];

	
	memcpy(pa, tmp, sizeof(unsigned short)*nvp);
}

mergePolyVerts找到最长共边的2个三角形,把他们合并到一个多边形中

并且过滤相同的顶点,nvp表示一个多边形,最多有几个顶点

在这里插入图片描述

精度不是很理想,我们调整下,maxEdgeLen调成2默认5,nvp调成5默认6

在这里插入图片描述

好一点哈

3.构建多边形网格邻接关系

static bool buildMeshAdjacency(unsigned short* polys, const int npolys,
							   const int nverts, const int vertsPerPoly)
{
	// Based on code by Eric Lengyel from:
	// https://web.archive.org/web/20080704083314/http://www.terathon.com/code/edges.php
	
	int maxEdgeCount = npolys*vertsPerPoly;
	unsigned short* firstEdge = (unsigned short*)rcAlloc(sizeof(unsigned short)*(nverts + maxEdgeCount), RC_ALLOC_TEMP);
	if (!firstEdge)
		return false;
	unsigned short* nextEdge = firstEdge + nverts;
	int edgeCount = 0;
	
	rcEdge* edges = (rcEdge*)rcAlloc(sizeof(rcEdge)*maxEdgeCount, RC_ALLOC_TEMP);
	if (!edges)
	{
		rcFree(firstEdge);
		return false;
	}
	
	for (int i = 0; i < nverts; i++)
		firstEdge[i] = RC_MESH_NULL_IDX;
	
	for (int i = 0; i < npolys; ++i)
	{
		unsigned short* t = &polys[i*vertsPerPoly*2];
		for (int j = 0; j < vertsPerPoly; ++j)
		{
			if (t[j] == RC_MESH_NULL_IDX) break;
			unsigned short v0 = t[j];
			unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1];
			if (v0 < v1)
			{
				rcEdge& edge = edges[edgeCount];
				edge.vert[0] = v0;
				edge.vert[1] = v1;
				edge.poly[0] = (unsigned short)i;
				edge.polyEdge[0] = (unsigned short)j;
				edge.poly[1] = (unsigned short)i;
				edge.polyEdge[1] = 0;
				// Insert edge
				nextEdge[edgeCount] = firstEdge[v0];
				firstEdge[v0] = (unsigned short)edgeCount;
				edgeCount++;
			}
		}
	}
	
	for (int i = 0; i < npolys; ++i)
	{
		unsigned short* t = &polys[i*vertsPerPoly*2];
		for (int j = 0; j < vertsPerPoly; ++j)
		{
			if (t[j] == RC_MESH_NULL_IDX) break;
			unsigned short v0 = t[j];
			unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1];
			if (v0 > v1)
			{
				for (unsigned short e = firstEdge[v1]; e != RC_MESH_NULL_IDX; e = nextEdge[e])
				{
					rcEdge& edge = edges[e];
					if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1])
					{
						edge.poly[1] = (unsigned short)i;
						edge.polyEdge[1] = (unsigned short)j;
						break;
					}
				}
			}
		}
	}
	
	// Store adjacency
	for (int i = 0; i < edgeCount; ++i)
	{
		const rcEdge& e = edges[i];
		if (e.poly[0] != e.poly[1])
		{
			unsigned short* p0 = &polys[e.poly[0]*vertsPerPoly*2];
			unsigned short* p1 = &polys[e.poly[1]*vertsPerPoly*2];
			p0[vertsPerPoly + e.polyEdge[0]] = e.poly[1];
			p1[vertsPerPoly + e.polyEdge[1]] = e.poly[0];
		}
	}
	
	rcFree(firstEdge);
	rcFree(edges);
	
	return true;
}

遍历所有的多边形,对于每个多边形,遍历所有的顶点,对于每个顶点,它找出与这个顶点相连的所有边,并将这些边存储到 edges 数组中

firstEdge 用于存储每个顶点的第一条边的索引,nextEdge 用于存储每条边的下一条边的索引

然后,函数遍历所有的边,找到另外一条顶点顺序相反的边(多边形顺时针走线),说明是共边,把信息记录在nvp后面

这就是为什么mesh.polys的长度是maxTris*nvp*2

构建Detail Mesh

有了Mesh后,我们就可以寻路了,为什么还要有Detail Mesh

3D中,每个网格里面的层级高度是不一样的,我们需要再把Mesh细分,获得更高精度的Mesh用于寻路

在这里插入图片描述

getHeightData

检查多边形是否由不同区域的多边形创建,如果都是同一区域,则把高度信息记录下

不是同一区域,将区域边界标记为种子点以填充其余部分数据

然后,函数进行广度优先搜索(BFS)来收集高度数据。BFS将确保我们不会移动到重叠的多边形上并采样错误的高度

总的来说就是获得多边形x-z的所有高度信息(比如有多层)

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& 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.
	
	queue.clear();
	// Set all heights to RC_UNSET_HEIGHT.
	memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);

	bool empty = true;
	
	// 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)
	{
		// 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 y = hp.ymin + hy + bs;
			for (int hx = 0; hx < hp.width; hx++)
			{
				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)
				{
					const rcCompactSpan& s = chf.spans[i];
					if (s.reg == region)
					{
						// 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)
						{
							if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
							{
								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 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)
		seedArrayWithPolyCenter(ctx, chf, poly, npoly, verts, bs, hp, queue);
	
	static const int RETRACT_SIZE = 256;
	int head = 0;
	
	// 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 = queue[head*3+0];
		int cy = queue[head*3+1];
		int ci = queue[head*3+2];
		head++;
		if (head >= RETRACT_SIZE)
		{
			head = 0;
			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];
		for (int dir = 0; dir < 4; ++dir)
		{
			if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue;
			
			const int ax = cx + rcGetDirOffsetX(dir);
			const int ay = cy + rcGetDirOffsetY(dir);
			const int hx = ax - hp.xmin - bs;
			const int hy = ay - hp.ymin - bs;
			
			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)
				continue;
			
			const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir);
			const rcCompactSpan& as = chf.spans[ai];
			
			hp.data[hx + hy*hp.width] = as.y;
			
			push3(queue, ax, ay, ai);
		}
	}
}

polyMinExtent

遍历多边形的每一条边,找到离这边条边最远的点,记录下到这条边对应的最长的距离maxEdgeDist

然后看看每一条边对应的maxEdgeDist,选择最短的那个距离

这个值越小,说明这个多边形很细长,不圆润

static float polyMinExtent(const float* verts, const int nverts)
{
	float minDist = FLT_MAX;
	for (int i = 0; i < nverts; i++)
	{
		const int ni = (i+1) % nverts;
		const float* p1 = &verts[i*3];
		const float* p2 = &verts[ni*3];
		float maxEdgeDist = 0;
		for (int j = 0; j < nverts; j++)
		{
			if (j == i || j == ni) continue;
			float d = distancePtSeg2d(&verts[j*3], p1,p2);
			maxEdgeDist = rcMax(maxEdgeDist, d);
		}
		minDist = rcMin(minDist, maxEdgeDist);
	}
	return rcSqrt(minDist);
}

triangulateHull

函数首先初始化三个索引:startleftright。然后,函数遍历凸包的每个顶点,找到一个“耳朵”,即一个三角形,它的周长最短。这个三角形将作为剖分的起点。

然后,函数开始通过向左或向右移动来剖分多边形,这取决于哪个方向的三角形周长更短。然后,根据比较左边和右边的距离,决定是向左还是向右添加新的三角形。

hull的顶点顺序是逆时针的,所以next(i, nhull)是左,prev(i, nhull)是右

static void triangulateHull(const int /*nverts*/, const float* verts, const int nhull, const int* hull, const int nin, rcIntArray& tris)
{
	int start = 0, left = 1, right = nhull-1;
	
	// Start from an ear with shortest perimeter.
	// This tends to favor well formed triangles as starting point.
	float dmin = FLT_MAX;
	for (int i = 0; i < nhull; i++)
	{
		if (hull[i] >= nin) continue; // Ears are triangles with original vertices as middle vertex while others are actually line segments on edges
		int pi = prev(i, nhull);
		int ni = next(i, nhull);
		const float* pv = &verts[hull[pi]*3];
		const float* cv = &verts[hull[i]*3];
		const float* nv = &verts[hull[ni]*3];
		const float d = vdist2(pv,cv) + vdist2(cv,nv) + vdist2(nv,pv);
		if (d < dmin)
		{
			start = i;
			left = ni;
			right = pi;
			dmin = d;
		}
	}
	
	// Add first triangle
	tris.push(hull[start]);
	tris.push(hull[left]);
	tris.push(hull[right]);
	tris.push(0);
	
	// Triangulate the polygon by moving left or right,
	// depending on which triangle has shorter perimeter.
	// This heuristic was chose empirically, since it seems
	// handle tessellated straight edges well.
	while (next(left, nhull) != right)
	{
		// Check to see if se should advance left or right.
		int nleft = next(left, nhull);
		int nright = prev(right, nhull);
		
		const float* cvleft = &verts[hull[left]*3];
		const float* nvleft = &verts[hull[nleft]*3];
		const float* cvright = &verts[hull[right]*3];
		const float* nvright = &verts[hull[nright]*3];
		const float dleft = vdist2(cvleft, nvleft) + vdist2(nvleft, cvright);
		const float dright = vdist2(cvright, nvright) + vdist2(cvleft, nvright);
		
		if (dleft < dright)
		{
			tris.push(hull[left]);
			tris.push(hull[nleft]);
			tris.push(hull[right]);
			tris.push(0);
			left = nleft;
		}
		else
		{
			tris.push(hull[left]);
			tris.push(hull[nright]);
			tris.push(hull[right]);
			tris.push(0);
			right = nright;
		}
	}
}

buildPolyDetail

开始细分,每条边按照sampleDist长度分,看可以分成几份,向下取整,往edge添加顶点数据

然后拿到这条边的va,vb两端,循环遍历新增的顶点

取出离这条边最远的顶点(大于sampleMaxError),添加到edge

根据新edge组成的多边形,用triangulateHull细分

条件符合的话,还需要细分一次,这次相当于把细分的三角形,再根据sampleDist细分一次

static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
							const float sampleDist, const float sampleMaxError,
							const int heightSearchRadius, const rcCompactHeightfield& chf,
							const rcHeightPatch& hp, float* verts, int& nverts,
							rcIntArray& tris, rcIntArray& edges, rcIntArray& samples)
{
	static const int MAX_VERTS = 127;
	static const int MAX_TRIS = 255;	// Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts).
	static const int MAX_VERTS_PER_EDGE = 32;
	float edge[(MAX_VERTS_PER_EDGE+1)*3];
	int hull[MAX_VERTS];
	int nhull = 0;
	
	nverts = nin;
	
	for (int i = 0; i < nin; ++i)
		rcVcopy(&verts[i*3], &in[i*3]);
	
	edges.clear();
	tris.clear();
	
	const float cs = chf.cs;
	const float ics = 1.0f/cs;
	
	// Calculate minimum extents of the polygon based on input data.
	float minExtent = polyMinExtent(verts, nverts);
	
	// Tessellate outlines.
	// This is done in separate pass in order to ensure
	// seamless height values across the ply boundaries.
	if (sampleDist > 0)
	{
		for (int i = 0, j = nin-1; i < nin; j=i++)
		{
			const float* vj = &in[j*3];
			const float* vi = &in[i*3];
			bool swapped = false;
			// Make sure the segments are always handled in same order
			// using lexological sort or else there will be seams.
			if (fabsf(vj[0]-vi[0]) < 1e-6f)
			{
				if (vj[2] > vi[2])
				{
					rcSwap(vj,vi);
					swapped = true;
				}
			}
			else
			{
				if (vj[0] > vi[0])
				{
					rcSwap(vj,vi);
					swapped = true;
				}
			}
			// Create samples along the edge.
			float dx = vi[0] - vj[0];
			float dy = vi[1] - vj[1];
			float dz = vi[2] - vj[2];
			float d = sqrtf(dx*dx + dz*dz);
			int nn = 1 + (int)floorf(d/sampleDist);
			if (nn >= MAX_VERTS_PER_EDGE) nn = MAX_VERTS_PER_EDGE-1;
			if (nverts+nn >= MAX_VERTS)
				nn = MAX_VERTS-1-nverts;
			
			for (int k = 0; k <= nn; ++k)
			{
				float u = (float)k/(float)nn;
				float* pos = &edge[k*3];
				pos[0] = vj[0] + dx*u;
				pos[1] = vj[1] + dy*u;
				pos[2] = vj[2] + dz*u;
				pos[1] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, heightSearchRadius, hp)*chf.ch;
			}
			// Simplify samples.
			int idx[MAX_VERTS_PER_EDGE] = {0,nn};
			int nidx = 2;
			for (int k = 0; k < nidx-1; )
			{
				const int a = idx[k];
				const int b = idx[k+1];
				const float* va = &edge[a*3];
				const float* vb = &edge[b*3];
				// Find maximum deviation along the segment.
				float maxd = 0;
				int maxi = -1;
				for (int m = a+1; m < b; ++m)
				{
					float dev = distancePtSeg(&edge[m*3],va,vb);
					if (dev > maxd)
					{
						maxd = dev;
						maxi = m;
					}
				}
				// If the max deviation is larger than accepted error,
				// add new point, else continue to next segment.
				if (maxi != -1 && maxd > rcSqr(sampleMaxError))
				{
					for (int m = nidx; m > k; --m)
						idx[m] = idx[m-1];
					idx[k+1] = maxi;
					nidx++;
				}
				else
				{
					++k;
				}
			}
			
			hull[nhull++] = j;
			// Add new vertices.
			if (swapped)
			{
				for (int k = nidx-2; k > 0; --k)
				{
					rcVcopy(&verts[nverts*3], &edge[idx[k]*3]);
					hull[nhull++] = nverts;
					nverts++;
				}
			}
			else
			{
				for (int k = 1; k < nidx-1; ++k)
				{
					rcVcopy(&verts[nverts*3], &edge[idx[k]*3]);
					hull[nhull++] = nverts;
					nverts++;
				}
			}
		}
	}
	
	// If the polygon minimum extent is small (sliver or small triangle), do not try to add internal points.
	if (minExtent < sampleDist*2)
	{
		triangulateHull(nverts, verts, nhull, hull, nin, tris);
		setTriFlags(tris, nhull, hull);
		return true;
	}
	
	// Tessellate the base mesh.
	// We're using the triangulateHull instead of delaunayHull as it tends to
	// create a bit better triangulation for long thin triangles when there
	// are no internal points.
	triangulateHull(nverts, verts, nhull, hull, nin, tris);
	
	if (tris.size() == 0)
	{
		// Could not triangulate the poly, make sure there is some valid data there.
		ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon (%d verts).", nverts);
		return true;
	}
	
	if (sampleDist > 0)
	{
		// Create sample locations in a grid.
		float bmin[3], bmax[3];
		rcVcopy(bmin, in);
		rcVcopy(bmax, in);
		for (int i = 1; i < nin; ++i)
		{
			rcVmin(bmin, &in[i*3]);
			rcVmax(bmax, &in[i*3]);
		}
		int x0 = (int)floorf(bmin[0]/sampleDist);
		int x1 = (int)ceilf(bmax[0]/sampleDist);
		int z0 = (int)floorf(bmin[2]/sampleDist);
		int z1 = (int)ceilf(bmax[2]/sampleDist);
		samples.clear();
		for (int z = z0; z < z1; ++z)
		{
			for (int x = x0; x < x1; ++x)
			{
				float pt[3];
				pt[0] = x*sampleDist;
				pt[1] = (bmax[1]+bmin[1])*0.5f;
				pt[2] = z*sampleDist;
				// Make sure the samples are not too close to the edges.
				if (distToPoly(nin,in,pt) > -sampleDist/2) continue;
				samples.push(x);
				samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, heightSearchRadius, hp));
				samples.push(z);
				samples.push(0); // Not added
			}
		}
		
		// Add the samples starting from the one that has the most
		// error. The procedure stops when all samples are added
		// or when the max error is within treshold.
		const int nsamples = samples.size()/4;
		for (int iter = 0; iter < nsamples; ++iter)
		{
			if (nverts >= MAX_VERTS)
				break;
			
			// Find sample with most error.
			float bestpt[3] = {0,0,0};
			float bestd = 0;
			int besti = -1;
			for (int i = 0; i < nsamples; ++i)
			{
				const int* s = &samples[i*4];
				if (s[3]) continue; // skip added.
				float pt[3];
				// The sample location is jittered to get rid of some bad triangulations
				// which are cause by symmetrical data from the grid structure.
				pt[0] = s[0]*sampleDist + getJitterX(i)*cs*0.1f;
				pt[1] = s[1]*chf.ch;
				pt[2] = s[2]*sampleDist + getJitterY(i)*cs*0.1f;
				float d = distToTriMesh(pt, verts, nverts, &tris[0], tris.size()/4);
				if (d < 0) continue; // did not hit the mesh.
				if (d > bestd)
				{
					bestd = d;
					besti = i;
					rcVcopy(bestpt,pt);
				}
			}
			// If the max error is within accepted threshold, stop tesselating.
			if (bestd <= sampleMaxError || besti == -1)
				break;
			// Mark sample as added.
			samples[besti*4+3] = 1;
			// Add the new sample point.
			rcVcopy(&verts[nverts*3],bestpt);
			nverts++;
			
			// Create new triangulation.
			// TODO: Incremental add instead of full rebuild.
			edges.clear();
			tris.clear();
			delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges);
		}
	}
	
	const int ntris = tris.size()/4;
	if (ntris > MAX_TRIS)
	{
		tris.resize(MAX_TRIS*4);
		ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS);
	}

	setTriFlags(tris, nhull, hull);
	
	return true;
}

细分完后,把数据保存在PolyMeshDetail中,就是我们用到的寻路数据了

完结!!!

  • 31
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值