コード例 #1
0
ファイル: RecastMeshDetail.cpp プロジェクト: arrian/3d-engine
static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
							const float sampleDist, const float sampleMaxError,
							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 = 0;

	for (int i = 0; i < nin; ++i)
		rcVcopy(&verts[i*3], &in[i*3]);
	nverts = nin;
	
	const float cs = chf.cs;
	const float ics = 1.0f/cs;
	
	// 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, 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++;
				}
			}
		}
	}
	

	// Tessellate the base mesh.
	edges.resize(0);
	tris.resize(0);

	delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges);
	
	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, adding default data.");
		for (int i = 2; i < nverts; ++i)
		{
			tris.push(0);
			tris.push(i-1);
			tris.push(i);
			tris.push(0);
		}
		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.resize(0);
		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, 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.resize(0);
			tris.resize(0);
			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);
	}

	return true;
}
コード例 #2
0
ファイル: RecastRegion.cpp プロジェクト: Jekls/PhantomCore
static unsigned short* expandRegions(int maxIter, unsigned short level,
									 rcCompactHeightfield& chf,
									 unsigned short* srcReg, unsigned short* srcDist,
									 unsigned short* dstReg, unsigned short* dstDist, 
									 rcIntArray& stack)
{
	const int w = chf.width;
	const int h = chf.height;

	// Find cells revealed by the raised level.
	stack.resize(0);
	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.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA)
				{
					stack.push(x);
					stack.push(y);
					stack.push(i);
				}
			}
		}
	}
	
	int iter = 0;
	while (stack.size() > 0)
	{
		int failed = 0;
		
		memcpy(dstReg, srcReg, sizeof(unsigned short)*chf.spanCount);
		memcpy(dstDist, srcDist, sizeof(unsigned short)*chf.spanCount);
		
		for (int j = 0; j < stack.size(); j += 3)
		{
			int x = stack[j+0];
			int y = stack[j+1];
			int i = stack[j+2];
			if (i < 0)
			{
				failed++;
				continue;
			}
			
			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)
			{
				if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue;
				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)
					{
						r = srcReg[ai];
						d2 = srcDist[ai]+2;
					}
				}
			}
			if (r)
			{
				stack[j+2] = -1; // mark as used
				dstReg[i] = r;
				dstDist[i] = d2;
			}
			else
			{
				failed++;
			}
		}
		
		// rcSwap source and dest.
		rcSwap(srcReg, dstReg);
		rcSwap(srcDist, dstDist);
		
		if (failed*3 == stack.size())
			break;
		
		if (level > 0)
		{
			++iter;
			if (iter >= maxIter)
				break;
		}
	}
	
	return srcReg;
}
コード例 #3
0
ファイル: RecastRegion.cpp プロジェクト: Jekls/PhantomCore
bool rcBuildDistanceField(rcCompactHeightfield& chf)
{
	rcTimeVal startTime = rcGetPerformanceTimer();
	
	if (chf.dist)
	{
		delete [] chf.dist;
		chf.dist = 0;
	}
	
	unsigned short* dist0 = new unsigned short[chf.spanCount];
	if (!dist0)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dist0' (%d).", chf.spanCount);
		return false;
	}
	unsigned short* dist1 = new unsigned short[chf.spanCount];
	if (!dist1)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dist1' (%d).", chf.spanCount);
		delete [] dist0;
		return false;
	}
	
	unsigned short* src = dist0;
	unsigned short* dst = dist1;

	unsigned short maxDist = 0;

	rcTimeVal distStartTime = rcGetPerformanceTimer();
	
	if (calculateDistanceField(chf, src, dst, maxDist) != src)
		rcSwap(src, dst);
	
	chf.maxDistance = maxDist;
	
	rcTimeVal distEndTime = rcGetPerformanceTimer();
	
	rcTimeVal blurStartTime = rcGetPerformanceTimer();
	
	// Blur
	if (boxBlur(chf, 1, src, dst) != src)
		rcSwap(src, dst);
	
	// Store distance.
	chf.dist = src;
	
	rcTimeVal blurEndTime = rcGetPerformanceTimer();
	
	delete [] dst;
	
	rcTimeVal endTime = rcGetPerformanceTimer();
	
/*	if (rcGetLog())
	{
		rcGetLog()->log(RC_LOG_PROGRESS, "Build distance field: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
		rcGetLog()->log(RC_LOG_PROGRESS, " - dist: %.3f ms", rcGetDeltaTimeUsec(distStartTime, distEndTime)/1000.0f);
		rcGetLog()->log(RC_LOG_PROGRESS, " - blur: %.3f ms", rcGetDeltaTimeUsec(blurStartTime, blurEndTime)/1000.0f);
	}*/
	if (rcGetBuildTimes())
	{
		rcGetBuildTimes()->buildDistanceField += rcGetDeltaTimeUsec(startTime, endTime);
		rcGetBuildTimes()->buildDistanceFieldDist += rcGetDeltaTimeUsec(distStartTime, distEndTime);
		rcGetBuildTimes()->buildDistanceFieldBlur += rcGetDeltaTimeUsec(blurStartTime, blurEndTime);
	}
	
	return true;
}
コード例 #4
0
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;
}
コード例 #5
0
ファイル: RecastRegion.cpp プロジェクト: Jekls/PhantomCore
bool rcBuildRegions(rcCompactHeightfield& chf,
					int borderSize, int minRegionSize, int mergeRegionSize)
{
	rcTimeVal startTime = rcGetPerformanceTimer();
	
	const int w = chf.width;
	const int h = chf.height;

	if (!chf.regs)
	{
		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);
			return false;
		}
	}
	
	rcScopedDelete<unsigned short> tmp = new unsigned short[chf.spanCount*4];
	if (!tmp)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4);
		return false;
	}
	
	rcTimeVal regStartTime = rcGetPerformanceTimer();
	
	rcIntArray stack(1024);
	rcIntArray visited(1024);
	
	unsigned short* srcReg = tmp;
	unsigned short* srcDist = tmp+chf.spanCount;
	unsigned short* dstReg = tmp+chf.spanCount*2;
	unsigned short* dstDist = tmp+chf.spanCount*3;
	
	memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount);
	memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount);
	
	unsigned short regionId = 1;
	unsigned short level = (chf.maxDistance+1) & ~1;

	// 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, 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 > 0)
	{
		level = level >= 2 ? level-2 : 0;
		
		rcTimeVal expStartTime = rcGetPerformanceTimer();
		
		// Expand current regions until no empty connected cells found.
		if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg)
		{
			rcSwap(srcReg, dstReg);
			rcSwap(srcDist, dstDist);
		}
		
		expTime += rcGetPerformanceTimer() - expStartTime;
		
		rcTimeVal floodStartTime = rcGetPerformanceTimer();
		
		// Mark new regions with IDs.
		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.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA)
						continue;
					
					if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack))
						regionId++;
				}
			}
		}
		
		floodTime += rcGetPerformanceTimer() - floodStartTime;
		
	}
	
	// Expand current regions until no empty connected cells found.
	if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg)
	{
		rcSwap(srcReg, dstReg);
		rcSwap(srcDist, dstDist);
	}
	
	rcTimeVal regEndTime = rcGetPerformanceTimer();
	
	rcTimeVal filterStartTime = rcGetPerformanceTimer();
	
	// Filter out small regions.
	chf.maxRegions = regionId;
	if (!filterSmallRegions(minRegionSize, mergeRegionSize, chf.maxRegions, chf, srcReg))
		return false;
	
	rcTimeVal filterEndTime = rcGetPerformanceTimer();
		
	// Write the result out.
	memcpy(chf.regs, srcReg, sizeof(unsigned short)*chf.spanCount);
	
	rcTimeVal endTime = rcGetPerformanceTimer();
	
/*	if (rcGetLog())
	{
		rcGetLog()->log(RC_LOG_PROGRESS, "Build regions: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
		rcGetLog()->log(RC_LOG_PROGRESS, " - reg: %.3f ms", rcGetDeltaTimeUsec(regStartTime, regEndTime)/1000.0f);
		rcGetLog()->log(RC_LOG_PROGRESS, " - exp: %.3f ms", rcGetDeltaTimeUsec(0, expTime)/1000.0f);
		rcGetLog()->log(RC_LOG_PROGRESS, " - flood: %.3f ms", rcGetDeltaTimeUsec(0, floodTime)/1000.0f);
		rcGetLog()->log(RC_LOG_PROGRESS, " - filter: %.3f ms", rcGetDeltaTimeUsec(filterStartTime, filterEndTime)/1000.0f);
	}
*/
	if (rcGetBuildTimes())
	{
		rcGetBuildTimes()->buildRegions += rcGetDeltaTimeUsec(startTime, endTime);
		rcGetBuildTimes()->buildRegionsReg += rcGetDeltaTimeUsec(regStartTime, regEndTime);
		rcGetBuildTimes()->buildRegionsExp += rcGetDeltaTimeUsec(0, expTime);
		rcGetBuildTimes()->buildRegionsFlood += rcGetDeltaTimeUsec(0, floodTime);
		rcGetBuildTimes()->buildRegionsFilter += rcGetDeltaTimeUsec(filterStartTime, filterEndTime);
	}
		
	return true;
}
コード例 #6
0
ファイル: RecastMesh.cpp プロジェクト: RyunosukeOno/rayjack
static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem)
{
	const int nvp = mesh.nvp;
	
	// Count number of polygons to remove.
	int numRemovedVerts = 0;
	int numTouchedVerts = 0;
	int numRemainingEdges = 0;
	for (int i = 0; i < mesh.npolys; ++i)
	{
		unsigned short* p = &mesh.polys[i*nvp*2];
		const int nv = countPolyVerts(p, nvp);
		int numRemoved = 0;
		int numVerts = 0;
		for (int j = 0; j < nv; ++j)
		{
			if (p[j] == rem)
			{
				numTouchedVerts++;
				numRemoved++;
			}
			numVerts++;
		}
		if (numRemoved)
		{
			numRemovedVerts += numRemoved;
			numRemainingEdges += numVerts-(numRemoved+1);
		}
	}
	
	// There would be too few edges remaining to create a polygon.
	// This can happen for example when a tip of a triangle is marked
	// as deletion, but there are no other polys that share the vertex.
	// In this case, the vertex should not be removed.
	if (numRemainingEdges <= 2)
		return false;
	
	// Find edges which share the removed vertex.
	const int maxEdges = numTouchedVerts*2;
	int nedges = 0;
	rcScopedDelete<int> edges = (int*)rcAlloc(sizeof(int)*maxEdges*3, RC_ALLOC_TEMP);
	if (!edges)
	{
		ctx->log(RC_LOG_WARNING, "canRemoveVertex: Out of memory 'edges' (%d).", maxEdges*3);
		return false;
	}
		
	for (int i = 0; i < mesh.npolys; ++i)
	{
		unsigned short* p = &mesh.polys[i*nvp*2];
		const int nv = countPolyVerts(p, nvp);

		// Collect edges which touches the removed vertex.
		for (int j = 0, k = nv-1; j < nv; k = j++)
		{
			if (p[j] == rem || p[k] == rem)
			{
				// Arrange edge so that a=rem.
				int a = p[j], b = p[k];
				if (b == rem)
					rcSwap(a,b);
					
				// Check if the edge exists
				bool exists = false;
				for (int m = 0; m < nedges; ++m)
				{
					int* e = &edges[m*3];
					if (e[1] == b)
					{
						// Exists, increment vertex share count.
						e[2]++;
						exists = true;
					}
				}
				// Add new edge.
				if (!exists)
				{
					int* e = &edges[nedges*3];
					e[0] = a;
					e[1] = b;
					e[2] = 1;
					nedges++;
				}
			}
		}
	}

	// There should be no more than 2 open edges.
	// This catches the case that two non-adjacent polygons
	// share the removed vertex. In that case, do not remove the vertex.
	int numOpenEdges = 0;
	for (int i = 0; i < nedges; ++i)
	{
		if (edges[i*3+2] < 2)
			numOpenEdges++;
	}
	if (numOpenEdges > 2)
		return false;
	
	return true;
}
コード例 #7
0
static bool buildPolyDetail(const float* in, const int nin, unsigned short reg,
							const float sampleDist, const float sampleMaxError,
							const rcCompactHeightfield& chf, const rcHeightPatch& hp,
							float* verts, int& nverts, rcIntArray& tris,
							rcIntArray& edges, rcIntArray& idx, rcIntArray& samples)
{
	static const int MAX_VERTS = 256;
	static const int MAX_EDGE = 64;
	float edge[(MAX_EDGE+1)*3];

	nverts = 0;

	for (int i = 0; i < nin; ++i)
		vcopy(&verts[i*3], &in[i*3]);
	nverts = nin;
	
	const float ics = 1.0f/chf.cs;
	
	// Tesselate 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];
			// 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);
			}
			else
			{
				if (vj[0] > vi[0])
					rcSwap(vj,vi);
			}
			// 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_EDGE) nn = MAX_EDGE;
			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] = chf.bmin[1] + getHeight(pos, chf.bmin, ics, hp)*chf.ch;
			}
			// Simplify samples.
			int idx[MAX_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 d = distancePtSeg(&edge[m*3],va,vb);
					if (d > maxd)
					{
						maxd = d;
						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;
				}
			}
			// Add new vertices.
			for (int k = 1; k < nidx-1; ++k)
			{
				vcopy(&verts[nverts*3], &edge[idx[k]*3]);
				nverts++;
			}
		}
	}
	
	// Tesselate the base mesh.
	edges.resize(0);
	tris.resize(0);
	idx.resize(0);
	delaunay(nverts, verts, idx, tris, edges);

	if (sampleDist > 0)
	{
		// Create sample locations in a grid.
		float bmin[3], bmax[3];
		vcopy(bmin, in);
		vcopy(bmax, in);
		for (int i = 1; i < nin; ++i)
		{
			vmin(bmin, &in[i*3]);
			vmax(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.resize(0);
		for (int z = z0; z < z1; ++z)
		{
			for (int x = x0; x < x1; ++x)
			{
				float pt[3];
				pt[0] = x*sampleDist;
				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, chf.bmin, ics, hp));
				samples.push(z);
			}
		}
				
		// 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()/3;
		for (int iter = 0; iter < nsamples; ++iter)
		{
			// Find sample with most error.
			float bestpt[3];
			float bestd = 0;
			for (int i = 0; i < nsamples; ++i)
			{
				float pt[3];
				pt[0] = samples[i*3+0]*sampleDist;
				pt[1] = chf.bmin[1] + samples[i*3+1]*chf.ch;
				pt[2] = samples[i*3+2]*sampleDist;
				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;
					vcopy(bestpt,pt);
				}
			}
			// If the max error is within accepted threshold, stop tesselating.
			if (bestd <= sampleMaxError)
				break;

			// Add the new sample point.
			vcopy(&verts[nverts*3],bestpt);
			nverts++;
			
			// Create new triangulation.
			// TODO: Incremental add instead of full rebuild.
			edges.resize(0);
			tris.resize(0);
			idx.resize(0);
			delaunay(nverts, verts, idx, tris, edges);

			if (nverts >= MAX_VERTS)
				break;
		}
	}

	return true;
}
コード例 #8
0
/// @par
/// 
/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour.
/// Contours will form simple polygons.
/// 
/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be
/// re-assigned to the zero (null) region.
/// 
/// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors. 
/// @p mergeRegionArea helps reduce unecessarily small regions.
/// 
/// See the #rcConfig documentation for more information on the configuration parameters.
/// 
/// The region data will be available via the rcCompactHeightfield::maxRegions
/// and rcCompactSpan::reg fields.
/// 
/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions.
/// 
/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig
bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf,
					const int borderSize, const int minRegionArea, const int mergeRegionArea)
{
	rcAssert(ctx);
	
	rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS);
	
	const int w = chf.width;
	const int h = chf.height;
	
	rcScopedDelete<unsigned short> buf((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP));
	if (!buf)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4);
		return false;
	}
	
	ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED);

	const int LOG_NB_STACKS = 3;
	const int NB_STACKS = 1 << LOG_NB_STACKS;
	rcIntArray lvlStacks[NB_STACKS];
	for (int i=0; i<NB_STACKS; ++i)
		lvlStacks[i].resize(1024);

	rcIntArray stack(1024);
	rcIntArray visited(1024);
	
	unsigned short* srcReg = buf;
	unsigned short* srcDist = buf+chf.spanCount;
	unsigned short* dstReg = buf+chf.spanCount*2;
	unsigned short* dstDist = buf+chf.spanCount*3;
	
	memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount);
	memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount);
	
	unsigned short regionId = 1;
	unsigned short level = (chf.maxDistance+1) & ~1;

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

	if (borderSize > 0)
	{
		// Make sure border will not overflow.
		const int bw = rcMin(w, borderSize);
		const int bh = rcMin(h, borderSize);
		
		// Paint regions
		paintRectRegion(0, bw, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;

		chf.borderSize = borderSize;
	}
	
	int sId = -1;
	while (level > 0)
	{
		level = level >= 2 ? level-2 : 0;
		sId = (sId+1) & (NB_STACKS-1);

//		ctx->startTimer(RC_TIMER_DIVIDE_TO_LEVELS);

		if (sId == 0)
			sortCellsByLevel(level, chf, srcReg, NB_STACKS, lvlStacks, 1);
		else 
			appendStacks(lvlStacks[sId-1], lvlStacks[sId], srcReg); // copy left overs from last level

//		ctx->stopTimer(RC_TIMER_DIVIDE_TO_LEVELS);

		{
			rcScopedTimer timerExpand(ctx, RC_TIMER_BUILD_REGIONS_EXPAND);

			// Expand current regions until no empty connected cells found.
			if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, lvlStacks[sId], false) != srcReg)
			{
				rcSwap(srcReg, dstReg);
				rcSwap(srcDist, dstDist);
			}
		}
		
		{
			rcScopedTimer timerFloor(ctx, RC_TIMER_BUILD_REGIONS_FLOOD);

			// Mark new regions with IDs.
			for (int j = 0; j<lvlStacks[sId].size(); j += 3)
			{
				int x = lvlStacks[sId][j];
				int y = lvlStacks[sId][j+1];
				int i = lvlStacks[sId][j+2];
				if (i >= 0 && srcReg[i] == 0)
				{
					if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack))
					{
						if (regionId == 0xFFFF)
						{
							ctx->log(RC_LOG_ERROR, "rcBuildRegions: Region ID overflow");
							return false;
						}
						
						regionId++;
					}
				}
			}
		}
	}
	
	// Expand current regions until no empty connected cells found.
	if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack, true) != srcReg)
	{
		rcSwap(srcReg, dstReg);
		rcSwap(srcDist, dstDist);
	}
	
	ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED);
	
	{
		rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER);

		// Merge regions and filter out smalle regions.
		rcIntArray overlaps;
		chf.maxRegions = regionId;
		if (!mergeAndFilterRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg, overlaps))
			return false;

		// If overlapping regions were found during merging, split those regions.
		if (overlaps.size() > 0)
		{
			ctx->log(RC_LOG_ERROR, "rcBuildRegions: %d overlapping regions.", overlaps.size());
		}
	}
		
	// Write the result out.
	for (int i = 0; i < chf.spanCount; ++i)
		chf.spans[i].reg = srcReg[i];
	
	return true;
}
コード例 #9
0
bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf,
	const int borderSize, const int minRegionArea, const int mergeRegionArea)
{
	rcAssert(ctx);

	ctx->startTimer(RC_TIMER_BUILD_REGIONS);

	const int w = chf.width;
	const int h = chf.height;

	rcScopedDelete<unsigned short> buf = static_cast<unsigned short*>(rcAlloc(sizeof(unsigned short)*chf.spanCount * 4, RC_ALLOC_TEMP));
	if (!buf)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount * 4);
		return false;
	}

	ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED);

	rcIntArray stack(1024);
	rcIntArray visited(1024);

	unsigned short* srcReg = buf;
	unsigned short* srcDist = buf + chf.spanCount;
	unsigned short* dstReg = buf + chf.spanCount * 2;
	unsigned short* dstDist = buf + chf.spanCount * 3;

	memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount);
	memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount);

	unsigned short regionId = 1;
	unsigned short level = (chf.maxDistance + 1) & ~1;

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

	if (borderSize > 0)
	{
		// Make sure border will not overflow.
		const int bw = rcMin(w, borderSize);
		const int bh = rcMin(h, borderSize);
		// Paint regions
		paintRectRegion(0, bw, 0, h, regionId | RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(w - bw, w, 0, h, regionId | RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(0, w, 0, bh, regionId | RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(0, w, h - bh, h, regionId | RC_BORDER_REG, chf, srcReg); regionId++;

		chf.borderSize = borderSize;
	}

	while (level > 0)
	{
		level = level >= 2 ? level - 2 : 0;

		ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND);

		// Expand current regions until no empty connected cells found.
		if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg)
		{
			rcSwap(srcReg, dstReg);
			rcSwap(srcDist, dstDist);
		}

		ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND);

		ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD);

		// Mark new regions with IDs.
		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 = static_cast<int>(c.index), ni = static_cast<int>(c.index + c.count); i < ni; ++i)
				{
					if (chf.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA)
						continue;
					if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack))
						regionId++;
				}
			}
		}

		ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD);
	}

	// Expand current regions until no empty connected cells found.
	if (expandRegions(expandIters * 8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg)
	{
		rcSwap(srcReg, dstReg);
		rcSwap(srcDist, dstDist);
	}

	ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED);

	ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER);

	// Filter out small regions.
	chf.maxRegions = regionId;
	if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg))
		return false;

	ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER);

	// Write the result out.
	for (int i = 0; i < chf.spanCount; ++i)
		chf.spans[i].reg = srcReg[i];

	ctx->stopTimer(RC_TIMER_BUILD_REGIONS);

	return true;
}
コード例 #10
0
static void seedArrayWithPolyCenter(rcContext* ctx, const rcCompactHeightfield& chf,
									const unsigned short* poly, const int npoly,
									const unsigned short* verts, const int bs,
									rcHeightPatch& hp, rcIntArray& array)
{
	// Note: Reads to the compact heightfield are offset by border size (bs)
	// since border size offset is already removed from the polymesh vertices.
	
	static const int offset[9*2] =
	{
		0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0,
	};
	
	// Find cell closest to a poly vertex
	int startCellX = 0, startCellY = 0, startSpanIndex = -1;
	int dmin = RC_UNSET_HEIGHT;
	for (int j = 0; j < npoly && dmin > 0; ++j)
	{
		for (int k = 0; k < 9 && dmin > 0; ++k)
		{
			const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0];
			const int ay = (int)verts[poly[j]*3+1];
			const int az = (int)verts[poly[j]*3+2] + offset[k*2+1];
			if (ax < hp.xmin || ax >= hp.xmin+hp.width ||
				az < hp.ymin || az >= hp.ymin+hp.height)
				continue;
			
			const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width];
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni && dmin > 0; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				int d = rcAbs(ay - (int)s.y);
				if (d < dmin)
				{
					startCellX = ax;
					startCellY = az;
					startSpanIndex = i;
					dmin = d;
				}
			}
		}
	}
	
	rcAssert(startSpanIndex != -1);
	// Find center of the polygon
	int pcx = 0, pcy = 0;
	for (int j = 0; j < npoly; ++j)
	{
		pcx += (int)verts[poly[j]*3+0];
		pcy += (int)verts[poly[j]*3+2];
	}
	pcx /= npoly;
	pcy /= npoly;
	
	// Use seeds array as a stack for DFS
	array.resize(0);
	array.push(startCellX);
	array.push(startCellY);
	array.push(startSpanIndex);

	int dirs[] = { 0, 1, 2, 3 };
	memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height);
	// DFS to move to the center. Note that we need a DFS here and can not just move
	// directly towards the center without recording intermediate nodes, even though the polygons
	// are convex. In very rare we can get stuck due to contour simplification if we do not
	// record nodes.
	int cx = -1, cy = -1, ci = -1;
	while (true)
	{
		if (array.size() < 3)
		{
			ctx->log(RC_LOG_WARNING, "Walk towards polygon center failed to reach center");
			break;
		}

		ci = array.pop();
		cy = array.pop();
		cx = array.pop();

		if (cx == pcx && cy == pcy)
			break;

		// If we are already at the correct X-position, prefer direction
		// directly towards the center in the Y-axis; otherwise prefer
		// direction in the X-axis
		int directDir;
		if (cx == pcx)
			directDir = rcGetDirForOffset(0, pcy > cy ? 1 : -1);
		else
			directDir = rcGetDirForOffset(pcx > cx ? 1 : -1, 0);

		// Push the direct dir last so we start with this on next iteration
		rcSwap(dirs[directDir], dirs[3]);

		const rcCompactSpan& cs = chf.spans[ci];
		for (int i = 0; i < 4; i++)
		{
			int dir = dirs[i];
			if (rcGetCon(cs, dir) == RC_NOT_CONNECTED)
				continue;

			int newX = cx + rcGetDirOffsetX(dir);
			int newY = cy + rcGetDirOffsetY(dir);

			int hpx = newX - hp.xmin;
			int hpy = newY - hp.ymin;
			if (hpx < 0 || hpx >= hp.width || hpy < 0 || hpy >= hp.height)
				continue;

			if (hp.data[hpx+hpy*hp.width] != 0)
				continue;

			hp.data[hpx+hpy*hp.width] = 1;
			array.push(newX);
			array.push(newY);
			array.push((int)chf.cells[(newX+bs)+(newY+bs)*chf.width].index + rcGetCon(cs, dir));
		}

		rcSwap(dirs[directDir], dirs[3]);
	}

	array.resize(0);
	// getHeightData seeds are given in coordinates with borders
	array.push(cx+bs);
	array.push(cy+bs);
	array.push(ci);

	memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);
	const rcCompactSpan& cs = chf.spans[ci];
	hp.data[cx-hp.xmin+(cy-hp.ymin)*hp.width] = cs.y;
}
コード例 #11
0
static void rasterizeTri(const float* v0, const float* v1, const float* v2,
						 const navAreaMask areaMask, rcHeightfield& hf,
						 const float* bmin, const float* bmax,
						 const float cs, const float ics, const float ich,
						 const int flagMergeThr)
{
	const int w = hf.width;
	const int h = hf.height;
	float tmin[3];
	float tmax[3];
	const float by = bmax[1] - bmin[1];
	
	// Calculate the bounding box of the triangle.
	rcVcopy(tmin, v0);
	rcVcopy(tmax, v0);
	rcVmin(tmin, v1);
	rcVmax(tmax, v1);
	rcVmin(tmin, v2);
	rcVmax(tmax, v2);
	
	// If the triangle does not touch the bbox of the heightfield, skip the triagle.
	if (!overlapBounds(bmin, bmax, tmin, tmax))
		return;
	
	// Calculate the footprint of the triangle on the grid's y-axis
	int y0 = (int)((tmin[2] - bmin[2]) * ics);
	int y1 = (int)((tmax[2] - bmin[2]) * ics);
	y0 = rcClamp(y0, 0, h-1);
	y1 = rcClamp(y1, 0, h-1);
	
	// Clip the triangle into all grid cells it touches.
	float buf[7 * 3 * 4];
	float *in = buf;
	float *inrow = buf + 7 * 3;
	float *p1 = inrow + 7 * 3;
	float *p2 = p1 + 7 * 3;

	rcVcopy(&in[0], v0);
	rcVcopy(&in[1 * 3], v1);
	rcVcopy(&in[2 * 3], v2);
	int nvrow;
	int nvIn = 3;
	
	for (int y = y0; y <= y1; ++y)
	{
		// Clip polygon to row. Store the remaining polygon as well
		const float cz = bmin[2] + y * cs;
		dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz + cs, 2);
		rcSwap(in, p1);
		if (nvrow < 3) {
			continue;
		}
		
		// find the horizontal bounds in the row
		float minX = inrow[0];
		float maxX = inrow[0];
		for (int i = 1; i < nvrow; ++i)
		{
			if (minX > inrow[i * 3])	minX = inrow[i * 3];
			if (maxX < inrow[i * 3])	maxX = inrow[i * 3];
		}
		int x0 = (int)((minX - bmin[0]) * ics);
		int x1 = (int)((maxX - bmin[0]) * ics);
		x0 = rcClamp(x0, 0, w - 1);
		x1 = rcClamp(x1, 0, w - 1);

		int nv, nv2 = nvrow;

		for (int x = x0; x <= x1; ++x)
		{
			// Clip polygon to column. store the remaining polygon as well
			const float cx = bmin[0] + x*cs;
			dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0);
			rcSwap(inrow, p2);
			if (nv < 3) continue;
			
			// Calculate min and max of the span.
			float smin = p1[1], smax = p1[1];
			for (int i = 1; i < nv; ++i)
			{
				smin = rcMin(smin, p1[i*3+1]);
				smax = rcMax(smax, p1[i*3+1]);
			}
			smin -= bmin[1];
			smax -= bmin[1];
			// Skip the span if it is outside the heightfield bbox
			if (smax < 0.0f) continue;
			if (smin > by) continue;
			// Clamp the span to the heightfield bbox.
			if (smin < 0.0f) smin = 0;
			if (smax > by) smax = by;
			
			// Snap the span to the heightfield height grid.
			unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, rcSpan::RC_SPAN_MAX_HEIGHT);
			unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin + 1, rcSpan::RC_SPAN_MAX_HEIGHT);
			
			addSpan(hf, x, y, ismin, ismax, areaMask, flagMergeThr);
		}
	}
}
コード例 #12
0
/// @UE4: rcBuildRegions is now split into two functions: gathering regions and merging them
///       it allows reusing existing code for building layer set (flood fill based partitioning)
///		  
///		  spanBuf4 is temporary buffer, allocated and freed by caller (size = chf.spanCount * 4)
///
bool rcGatherRegionsNoFilter(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, unsigned short* spanBuf4)
{
	const int w = chf.width;
	const int h = chf.height;

	rcIntArray stack(1024);
	rcIntArray visited(1024);

	unsigned short* srcReg = spanBuf4;
	unsigned short* srcDist = spanBuf4+chf.spanCount;
	unsigned short* dstReg = spanBuf4+chf.spanCount*2;
	unsigned short* dstDist = spanBuf4+chf.spanCount*3;

	memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount);
	memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount);

	unsigned short regionId = 1;
	unsigned short level = (chf.maxDistance+1) & ~1;

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

	if (borderSize > 0)
	{
		// Make sure border will not overflow.
		const int bw = rcMin(w, borderSize);
		const int bh = rcMin(h, borderSize);
		// Paint regions
		paintRectRegion(0, bw, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
		paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;

		chf.borderSize = borderSize;
	}

	while (level > 0)
	{
		level = level >= 2 ? level-2 : 0;

		ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND);

		// Expand current regions until no empty connected cells found.
		if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg)
		{
			rcSwap(srcReg, dstReg);
			rcSwap(srcDist, dstDist);
		}

		ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND);

		ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD);

		// Mark new regions with IDs.
		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.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA)
						continue;
					if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack))
						regionId++;
				}
			}
		}

		ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD);
	}

	// Expand current regions until no empty connected cells found.
	if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg)
	{
		rcSwap(srcReg, dstReg);
		rcSwap(srcDist, dstDist);
	}

	chf.maxRegions = regionId;
	return true;
}