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; }
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; edges.resize(0); tris.resize(0); 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, 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, tris); 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 thing triangles when there // are no internal points. triangulateHull(nverts, verts, nhull, hull, 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.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 bool buildPolyDetail(rcContext* ctx, const dtCoordinates* in, const int nin, const float sampleDist, const float sampleMaxError, const rcCompactHeightfield& chf, const rcHeightPatch& hp, dtCoordinates* verts, int& nverts, rcIntArray& tris, rcIntArray& edges, rcIntArray& samples #ifdef MODIFY_VOXEL_FLAG , const char /*area*/ #endif // MODIFY_VOXEL_FLAG ) { 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; dtCoordinates edge[(MAX_VERTS_PER_EDGE+1)]; int hull[MAX_VERTS]; int nhull = 0; nverts = 0; for (int i = 0; i < nin; ++i) rcVcopy(verts[i], in[i]); 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. #ifdef MODIFY_VOXEL_FLAG if( 0 < sampleDist /*&& rcIsTerrainArea( area )*/ ) #else // MODIFY_VOXEL_FLAG if (sampleDist > 0) #endif // MODIFY_VOXEL_FLAG { for (int i = 0, j = nin-1; i < nin; j=i++) { const dtCoordinates* vj = &in[j]; const dtCoordinates* vi = &in[i]; 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->X()-vi->X()) < 1e-6f) { if (vj->Z() > vi->Z()) { rcSwap(vj,vi); swapped = true; } } else { if (vj->X() > vi->X()) { rcSwap(vj,vi); swapped = true; } } // Create samples along the edge. float dx = vi->X() - vj->X(); float dy = vi->Y() - vj->Y(); float dz = vi->Z() - vj->Z(); 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; dtCoordinates* pos = &edge[k]; pos->SetX( vj->X() + dx*u ); pos->SetY( vj->Y() + dy*u ); pos->SetZ( vj->Z() + dz*u ); pos->SetY( getHeight(pos->X(),pos->Y(),pos->Z(), 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 dtCoordinates va( edge[a] ); const dtCoordinates vb( edge[b] ); // 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],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], edge[idx[k]]); hull[nhull++] = nverts; nverts++; } } else { for (int k = 1; k < nidx-1; ++k) { rcVcopy(verts[nverts], edge[idx[k]]); 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; } #ifdef MODIFY_VOXEL_FLAG if( 0 < sampleDist /*&& rcIsTerrainArea( area )*/ ) #else // MODIFY_VOXEL_FLAG if (sampleDist > 0) #endif // MODIFY_VOXEL_FLAG { // Create sample locations in a grid. dtCoordinates bmin, bmax; rcVcopy(bmin, in[0]); rcVcopy(bmax, in[0]); for (int i = 1; i < nin; ++i) { rcVmin(bmin, in[i]); rcVmax(bmax, in[i]); } int x0 = (int)floorf(bmin.X()/sampleDist); int x1 = (int)ceilf(bmax.X()/sampleDist); int z0 = (int)floorf(bmin.Z()/sampleDist); int z1 = (int)ceilf(bmax.Z()/sampleDist); samples.resize(0); for (int z = z0; z < z1; ++z) { for (int x = x0; x < x1; ++x) { const dtCoordinates pt( x*sampleDist, (bmax.Y()+bmin.Y())*0.5f, 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.X(), pt.Y(), pt.Z(), 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. dtCoordinates bestpt; 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. const dtCoordinates pt( s[0]*sampleDist + getJitterX(i)*cs*0.1f, s[1]*chf.ch, s[2]*sampleDist + getJitterY(i)*cs*0.1f ); // The sample location is jittered to get rid of some bad triangulations // which are cause by symmetrical data from the grid structure. 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],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; }