void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax) { // Calculate bounding box. rcVcopy(bmin, verts); rcVcopy(bmax, verts); for (int i = 1; i < nv; ++i) { const float* v = &verts[i * 3]; rcVmin(bmin, v); rcVmax(bmax, v); } }
void rcCalcBounds(const dtCoordinates* verts, int nv, dtCoordinates& bmin, dtCoordinates& bmax) { // Calculate bounding box. rcVcopy(bmin, verts); rcVcopy(bmax, verts); for (int i = 1; i < nv; ++i) { const dtCoordinates v( verts[i] ); rcVmin(bmin, v); rcVmax(bmax, v); } }
void rcCalcBounds( const dtCoordinates* verts, const int nv, const dtCoordinates& square_min, const dtCoordinates& square_max, dtCoordinates& bmin, dtCoordinates& bmax ) { // Calculate bounding box. rcVcopy(bmin, verts[0]); rcVcopy(bmax, verts[0]); for (int i = 1; i < nv; ++i) { const dtCoordinates v( verts[i] ); rcVmin(bmin, v); rcVmax(bmax, v); bmin.SetX( rcClamp( bmin.X(), square_min.X(), square_max.X() ) ); bmin.SetZ( rcClamp( bmin.Z(), square_min.Z(), square_max.Z() ) ); bmax.SetX( rcClamp( bmax.X(), square_min.X(), square_max.X() ) ); bmax.SetZ( rcClamp( bmax.Z(), square_min.Z(), square_max.Z() ) ); } }
void MapBuilder::getGridBounds(uint32 mapID, uint32 &minX, uint32 &minY, uint32 &maxX, uint32 &maxY) const { // min and max are initialized to invalid values so the caller iterating the [min, max] range // will never enter the loop unless valid min/max values are found maxX = 0; maxY = 0; minX = std::numeric_limits<uint32>::max(); minY = std::numeric_limits<uint32>::max(); float bmin[3] = { 0, 0, 0 }; float bmax[3] = { 0, 0, 0 }; float lmin[3] = { 0, 0, 0 }; float lmax[3] = { 0, 0, 0 }; MeshData meshData; // make sure we process maps which don't have tiles // initialize the static tree, which loads WDT models if (!m_terrainBuilder->loadVMap(mapID, 64, 64, meshData)) return; // get the coord bounds of the model data if (meshData.solidVerts.size() + meshData.liquidVerts.size() == 0) return; // get the coord bounds of the model data if (meshData.solidVerts.size() && meshData.liquidVerts.size()) { rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax); rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax); rcVmin(bmin, lmin); rcVmax(bmax, lmax); } else if (meshData.solidVerts.size()) rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax); else rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax); // convert coord bounds to grid bounds maxX = 32 - bmin[0] / GRID_SIZE; maxY = 32 - bmin[2] / GRID_SIZE; minX = 32 - bmax[0] / GRID_SIZE; minY = 32 - bmax[2] / GRID_SIZE; }
void MapBuilder::getGridBounds(uint32 mapID, uint32 &minX, uint32 &minY, uint32 &maxX, uint32 &maxY) { maxX = INT_MAX; maxY = INT_MAX; minX = INT_MIN; minY = INT_MIN; float bmin[3] = { 0, 0, 0 }; float bmax[3] = { 0, 0, 0 }; float lmin[3] = { 0, 0, 0 }; float lmax[3] = { 0, 0, 0 }; MeshData meshData; // make sure we process maps which don't have tiles // initialize the static tree, which loads WDT models if (!m_terrainBuilder->loadVMap(mapID, 64, 64, meshData)) return; // get the coord bounds of the model data if (meshData.solidVerts.size() + meshData.liquidVerts.size() == 0) return; // get the coord bounds of the model data if (meshData.solidVerts.size() && meshData.liquidVerts.size()) { rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax); rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax); rcVmin(bmin, lmin); rcVmax(bmax, lmax); } else if (meshData.solidVerts.size()) rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax); else rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax); // convert coord bounds to grid bounds maxX = 32 - bmin[0] / GRID_SIZE; maxY = 32 - bmin[2] / GRID_SIZE; minX = 32 - bmax[0] / GRID_SIZE; minY = 32 - bmax[2] / GRID_SIZE; }
static bool rasterizeTri(const float* v0, const float* v1, const float* v2, const unsigned char area, 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], 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); rcVmin(tmin, v2); rcVmax(tmax, v1); rcVmax(tmax, v2); // If the triangle does not touch the bbox of the heightfield, skip the triagle. if (!overlapBounds(bmin, bmax, tmin, tmax)) return true; // 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, *inrow = buf+7*3, *p1 = inrow+7*3, *p2 = p1+7*3; rcVcopy(&in[0], v0); rcVcopy(&in[1*3], v1); rcVcopy(&in[2*3], v2); int nvrow, 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], 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, RC_SPAN_MAX_HEIGHT); unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); if (!addSpan(hf, x, y, ismin, ismax, area, flagMergeThr)) return false; } } return true; }
bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh) { rcAssert(ctx); if (!nmeshes || !meshes) return true; ctx->startTimer(RC_TIMER_MERGE_POLYMESH); mesh.nvp = meshes[0]->nvp; mesh.cs = meshes[0]->cs; mesh.ch = meshes[0]->ch; rcVcopy(mesh.bmin, meshes[0]->bmin); rcVcopy(mesh.bmax, meshes[0]->bmax); int maxVerts = 0; int maxPolys = 0; int maxVertsPerMesh = 0; for (int i = 0; i < nmeshes; ++i) { rcVmin(mesh.bmin, meshes[i]->bmin); rcVmax(mesh.bmax, meshes[i]->bmax); maxVertsPerMesh = rcMax(maxVertsPerMesh, meshes[i]->nverts); maxVerts += meshes[i]->nverts; maxPolys += meshes[i]->npolys; } mesh.nverts = 0; mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVerts*3, RC_ALLOC_PERM); if (!mesh.verts) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' (%d).", maxVerts*3); return false; } mesh.npolys = 0; mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys*2*mesh.nvp, RC_ALLOC_PERM); if (!mesh.polys) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.polys' (%d).", maxPolys*2*mesh.nvp); return false; } memset(mesh.polys, 0xff, sizeof(unsigned short)*maxPolys*2*mesh.nvp); mesh.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM); if (!mesh.regs) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.regs' (%d).", maxPolys); return false; } memset(mesh.regs, 0, sizeof(unsigned short)*maxPolys); mesh.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxPolys, RC_ALLOC_PERM); if (!mesh.areas) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.areas' (%d).", maxPolys); return false; } memset(mesh.areas, 0, sizeof(unsigned char)*maxPolys); mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM); if (!mesh.flags) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.flags' (%d).", maxPolys); return false; } memset(mesh.flags, 0, sizeof(unsigned short)*maxPolys); rcScopedDelete<int> nextVert = (int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP); if (!nextVert) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' (%d).", maxVerts); return false; } memset(nextVert, 0, sizeof(int)*maxVerts); rcScopedDelete<int> firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); if (!firstVert) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); return false; } for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) firstVert[i] = -1; rcScopedDelete<unsigned short> vremap = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerMesh, RC_ALLOC_PERM); if (!vremap) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' (%d).", maxVertsPerMesh); return false; } memset(nextVert, 0, sizeof(int)*maxVerts); for (int i = 0; i < nmeshes; ++i) { const rcPolyMesh* pmesh = meshes[i]; const unsigned short ox = (unsigned short)floorf((pmesh->bmin[0]-mesh.bmin[0])/mesh.cs+0.5f); const unsigned short oz = (unsigned short)floorf((pmesh->bmin[2]-mesh.bmin[2])/mesh.cs+0.5f); for (int j = 0; j < pmesh->nverts; ++j) { unsigned short* v = &pmesh->verts[j*3]; vremap[j] = addVertex(v[0]+ox, v[1], v[2]+oz, mesh.verts, firstVert, nextVert, mesh.nverts); } for (int j = 0; j < pmesh->npolys; ++j) { unsigned short* tgt = &mesh.polys[mesh.npolys*2*mesh.nvp]; unsigned short* src = &pmesh->polys[j*2*mesh.nvp]; mesh.regs[mesh.npolys] = pmesh->regs[j]; mesh.areas[mesh.npolys] = pmesh->areas[j]; mesh.flags[mesh.npolys] = pmesh->flags[j]; mesh.npolys++; for (int k = 0; k < mesh.nvp; ++k) { if (src[k] == RC_MESH_NULL_IDX) break; tgt[k] = vremap[src[k]]; } } } // Calculate adjacency. if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp)) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed."); return false; } if (mesh.nverts > 0xffff) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff); } if (mesh.npolys > 0xffff) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff); } ctx->stopTimer(RC_TIMER_MERGE_POLYMESH); return true; }
void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, const float hmin, const float hmax, unsigned char areaId, rcCompactHeightfield& chf) { rcAssert(ctx); ctx->startTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); float bmin[3], bmax[3]; rcVcopy(bmin, verts); rcVcopy(bmax, verts); for (int i = 1; i < nverts; ++i) { rcVmin(bmin, &verts[i*3]); rcVmax(bmax, &verts[i*3]); } bmin[1] = hmin; bmax[1] = hmax; int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs); int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs); int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch); int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs); if (maxx < 0) return; if (minx >= chf.width) return; if (maxz < 0) return; if (minz >= chf.height) return; if (minx < 0) minx = 0; if (maxx >= chf.width) maxx = chf.width-1; if (minz < 0) minz = 0; if (maxz >= chf.height) maxz = chf.height-1; // TODO: Optimize. for (int z = minz; z <= maxz; ++z) { for (int x = minx; x <= maxx; ++x) { const rcCompactCell& c = chf.cells[x+z*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan& s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; if ((int)s.y >= miny && (int)s.y <= maxy) { float p[3]; p[0] = chf.bmin[0] + (x+0.5f)*chf.cs; p[1] = 0; p[2] = chf.bmin[2] + (z+0.5f)*chf.cs; if (pointInPoly(nverts, verts, p)) { chf.areas[i] = areaId; } } } } } ctx->stopTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); }
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 void rasterizeTri(const float* v0, const float* v1, const float* v2, const unsigned char area, 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], 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); rcVmin(tmin, v2); rcVmax(tmax, v1); 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 footpring of the triangle on the grid. int x0 = (int)((tmin[0] - bmin[0])*ics); int y0 = (int)((tmin[2] - bmin[2])*ics); int x1 = (int)((tmax[0] - bmin[0])*ics); int y1 = (int)((tmax[2] - bmin[2])*ics); x0 = rcClamp(x0, 0, w-1); y0 = rcClamp(y0, 0, h-1); x1 = rcClamp(x1, 0, w-1); y1 = rcClamp(y1, 0, h-1); // Clip the triangle into all grid cells it touches. float in[7*3], out[7*3], inrow[7*3]; for (int y = y0; y <= y1; ++y) { // Clip polygon to row. rcVcopy(&in[0], v0); rcVcopy(&in[1*3], v1); rcVcopy(&in[2*3], v2); int nvrow = 3; const float cz = bmin[2] + y*cs; nvrow = clipPoly(in, nvrow, out, 0, 1, -cz); if (nvrow < 3) continue; nvrow = clipPoly(out, nvrow, inrow, 0, -1, cz+cs); if (nvrow < 3) continue; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. int nv = nvrow; const float cx = bmin[0] + x*cs; nv = clipPoly(inrow, nv, out, 1, 0, -cx); if (nv < 3) continue; nv = clipPoly(out, nv, in, -1, 0, cx+cs); if (nv < 3) continue; // Calculate min and max of the span. float smin = in[1], smax = in[1]; for (int i = 1; i < nv; ++i) { smin = rcMin(smin, in[i*3+1]); smax = rcMax(smax, in[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, RC_SPAN_MAX_HEIGHT); unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); addSpan(hf, x, y, ismin, ismax, area, flagMergeThr); } } }
static bool buildPolyDetail(rcBuildContext* 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 = 256; static const int MAX_EDGE = 64; float edge[(MAX_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; // 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]; 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_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] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, 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; } } 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++; } } } } // Tesselate 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); } } // 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] = {0,0,0}; float bestd = 0; for (int i = 0; i < nsamples; ++i) { float pt[3]; pt[0] = samples[i*3+0]*sampleDist; pt[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; rcVcopy(bestpt,pt); } } // If the max error is within accepted threshold, stop tesselating. if (bestd <= sampleMaxError) break; // 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); if (nverts >= MAX_VERTS) break; } } 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; }