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