Fix for issue #38 and improvements to detail mesh tessellation

- do circumCircle calculations relative to first vertex for adde
precision
- improved the results of triangulateHull(), produces less degen
triangles
- fixed case with contour merging when outline is null
This commit is contained in:
Mikko Mononen 2014-06-24 15:23:47 +03:00
parent 480f56e94d
commit 129efa5833
2 changed files with 134 additions and 142 deletions

View File

@ -37,7 +37,7 @@ static int getCornerHeight(int x, int y, int i, int dir,
unsigned int regs[4] = {0,0,0,0}; unsigned int regs[4] = {0,0,0,0};
// Combine region and area codes in order to prevent // Combine region and area codes in order to prevent
// border vertices which are in between two areas to be removed. // border vertices which are in between two areas to be removed.
regs[0] = chf.spans[i].reg | (chf.areas[i] << 16); regs[0] = chf.spans[i].reg | (chf.areas[i] << 16);
if (rcGetCon(s, dir) != RC_NOT_CONNECTED) if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
@ -188,27 +188,6 @@ static float distancePtSeg(const int x, const int z,
const int px, const int pz, const int px, const int pz,
const int qx, const int qz) const int qx, const int qz)
{ {
/* float pqx = (float)(qx - px);
float pqy = (float)(qy - py);
float pqz = (float)(qz - pz);
float dx = (float)(x - px);
float dy = (float)(y - py);
float dz = (float)(z - pz);
float d = pqx*pqx + pqy*pqy + pqz*pqz;
float t = pqx*dx + pqy*dy + pqz*dz;
if (d > 0)
t /= d;
if (t < 0)
t = 0;
else if (t > 1)
t = 1;
dx = px + t*pqx - x;
dy = py + t*pqy - y;
dz = pz + t*pqz - z;
return dx*dx + dy*dy + dz*dz;*/
float pqx = (float)(qx - px); float pqx = (float)(qx - px);
float pqz = (float)(qz - pz); float pqz = (float)(qz - pz);
float dx = (float)(x - px); float dx = (float)(x - px);
@ -258,13 +237,13 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified,
simplified.push(points[i*4+2]); simplified.push(points[i*4+2]);
simplified.push(i); simplified.push(i);
} }
} }
} }
if (simplified.size() == 0) if (simplified.size() == 0)
{ {
// If there is no connections at all, // If there is no connections at all,
// create some initial points for the simplification process. // create some initial points for the simplification process.
// Find lower-left and upper-right vertices of the contour. // Find lower-left and upper-right vertices of the contour.
int llx = points[0]; int llx = points[0];
int lly = points[1]; int lly = points[1];
@ -315,7 +294,7 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified,
int ax = simplified[i*4+0]; int ax = simplified[i*4+0];
int az = simplified[i*4+2]; int az = simplified[i*4+2];
int ai = simplified[i*4+3]; int ai = simplified[i*4+3];
int bx = simplified[ii*4+0]; int bx = simplified[ii*4+0];
int bz = simplified[ii*4+2]; int bz = simplified[ii*4+2];
int bi = simplified[ii*4+3]; int bi = simplified[ii*4+3];
@ -324,7 +303,7 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified,
float maxd = 0; float maxd = 0;
int maxi = -1; int maxi = -1;
int ci, cinc, endi; int ci, cinc, endi;
// Traverse the segment in lexilogical order so that the // Traverse the segment in lexilogical order so that the
// max deviation is calculated similarly when traversing // max deviation is calculated similarly when traversing
// opposite segments. // opposite segments.
@ -400,11 +379,11 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified,
const int bx = simplified[ii*4+0]; const int bx = simplified[ii*4+0];
const int bz = simplified[ii*4+2]; const int bz = simplified[ii*4+2];
const int bi = simplified[ii*4+3]; const int bi = simplified[ii*4+3];
// Find maximum deviation from the segment. // Find maximum deviation from the segment.
int maxi = -1; int maxi = -1;
int ci = (ai+1) % pn; int ci = (ai+1) % pn;
// Tessellate only outer edges or edges between areas. // Tessellate only outer edges or edges between areas.
bool tess = false; bool tess = false;
// Wall edges. // Wall edges.
@ -576,7 +555,7 @@ static bool intersectSegCountour(const int* d0, const int* d1, int i, int n, con
const int* p1 = &verts[k1 * 4]; const int* p1 = &verts[k1 * 4];
if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1)) if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1))
continue; continue;
if (intersect(d0, d1, p0, p1)) if (intersect(d0, d1, p0, p1))
return true; return true;
} }
@ -630,9 +609,9 @@ static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib)
int* verts = (int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM); int* verts = (int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM);
if (!verts) if (!verts)
return false; return false;
int nv = 0; int nv = 0;
// Copy contour A. // Copy contour A.
for (int i = 0; i <= ca.nverts; ++i) for (int i = 0; i <= ca.nverts; ++i)
{ {
@ -660,7 +639,7 @@ static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib)
rcFree(ca.verts); rcFree(ca.verts);
ca.verts = verts; ca.verts = verts;
ca.nverts = nv; ca.nverts = nv;
rcFree(cb.verts); rcFree(cb.verts);
cb.verts = 0; cb.verts = 0;
cb.nverts = 0; cb.nverts = 0;
@ -747,7 +726,7 @@ static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region)
findLeftMostVertex(region.holes[i].contour, &region.holes[i].minx, &region.holes[i].minz, &region.holes[i].leftmost); findLeftMostVertex(region.holes[i].contour, &region.holes[i].minx, &region.holes[i].minz, &region.holes[i].leftmost);
qsort(region.holes, region.nholes, sizeof(rcContourHole), compareHoles); qsort(region.holes, region.nholes, sizeof(rcContourHole), compareHoles);
int maxVerts = region.outline->nverts; int maxVerts = region.outline->nverts;
for (int i = 0; i < region.nholes; i++) for (int i = 0; i < region.nholes; i++)
maxVerts += region.holes[i].contour->nverts; maxVerts += region.holes[i].contour->nverts;
@ -758,9 +737,9 @@ static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region)
ctx->log(RC_LOG_WARNING, "mergeRegionHoles: Failed to allocated diags %d.", maxVerts); ctx->log(RC_LOG_WARNING, "mergeRegionHoles: Failed to allocated diags %d.", maxVerts);
return; return;
} }
rcContour* outline = region.outline; rcContour* outline = region.outline;
// Merge holes into the outline one by one. // Merge holes into the outline one by one.
for (int i = 0; i < region.nholes; i++) for (int i = 0; i < region.nholes; i++)
{ {
@ -793,7 +772,7 @@ static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region)
} }
// Sort potential diagonals by distance, we want to make the connection as short as possible. // Sort potential diagonals by distance, we want to make the connection as short as possible.
qsort(diags, ndiags, sizeof(rcPotentialDiagonal), compareDiagDist); qsort(diags, ndiags, sizeof(rcPotentialDiagonal), compareDiagDist);
// Find a diagonal that is not intersecting the outline not the remaining holes. // Find a diagonal that is not intersecting the outline not the remaining holes.
index = -1; index = -1;
for (int j = 0; j < ndiags; j++) for (int j = 0; j < ndiags; j++)
@ -834,13 +813,13 @@ static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region)
/// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen /// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen
/// parameters control how closely the simplified contours will match the raw contours. /// parameters control how closely the simplified contours will match the raw contours.
/// ///
/// Simplified contours are generated such that the vertices for portals between areas match up. /// Simplified contours are generated such that the vertices for portals between areas match up.
/// (They are considered mandatory vertices.) /// (They are considered mandatory vertices.)
/// ///
/// Setting @p maxEdgeLength to zero will disabled the edge length feature. /// Setting @p maxEdgeLength to zero will disabled the edge length feature.
/// ///
/// See the #rcConfig documentation for more information on the configuration parameters. /// See the #rcConfig documentation for more information on the configuration parameters.
/// ///
/// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig
bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
const float maxError, const int maxEdgeLen, const float maxError, const int maxEdgeLen,
@ -943,17 +922,17 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
verts.resize(0); verts.resize(0);
simplified.resize(0); simplified.resize(0);
ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE);
walkContour(x, y, i, chf, flags, verts); walkContour(x, y, i, chf, flags, verts);
ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE);
ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY);
simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags);
removeDegenerateSegments(simplified); removeDegenerateSegments(simplified);
ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY);
// Store region->contour remap info. // Store region->contour remap info.
// Create contour. // Create contour.
if (simplified.size()/4 >= 3) if (simplified.size()/4 >= 3)
@ -974,10 +953,10 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
} }
rcFree(cset.conts); rcFree(cset.conts);
cset.conts = newConts; cset.conts = newConts;
ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours); ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours);
} }
rcContour* cont = &cset.conts[cset.nconts++]; rcContour* cont = &cset.conts[cset.nconts++];
cont->nverts = simplified.size()/4; cont->nverts = simplified.size()/4;
@ -1024,7 +1003,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
} }
} }
} }
// Merge holes if needed. // Merge holes if needed.
if (cset.nconts > 0) if (cset.nconts > 0)
{ {
@ -1057,7 +1036,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
return false; return false;
} }
memset(regions, 0, sizeof(rcContourRegion)*nregions); memset(regions, 0, sizeof(rcContourRegion)*nregions);
rcScopedDelete<rcContourHole> holes = (rcContourHole*)rcAlloc(sizeof(rcContourHole)*cset.nconts, RC_ALLOC_TEMP); rcScopedDelete<rcContourHole> holes = (rcContourHole*)rcAlloc(sizeof(rcContourHole)*cset.nconts, RC_ALLOC_TEMP);
if (!holes) if (!holes)
{ {
@ -1065,7 +1044,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
return false; return false;
} }
memset(holes, 0, sizeof(rcContourHole)*cset.nconts); memset(holes, 0, sizeof(rcContourHole)*cset.nconts);
for (int i = 0; i < cset.nconts; ++i) for (int i = 0; i < cset.nconts; ++i)
{ {
rcContour& cont = cset.conts[i]; rcContour& cont = cset.conts[i];
@ -1102,8 +1081,20 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
// Finally merge each regions holes into the outline. // Finally merge each regions holes into the outline.
for (int i = 0; i < nregions; i++) for (int i = 0; i < nregions; i++)
{ {
if (regions[i].nholes > 0) rcContourRegion& reg = regions[i];
mergeRegionHoles(ctx, regions[i]); if (!reg.nholes) continue;
if (reg.outline)
{
mergeRegionHoles(ctx, reg);
}
else
{
// The region does not have an outline.
// This can happen if the contour becaomes selfoverlapping because of
// too aggressive simplification settings.
ctx->log(RC_LOG_ERROR, "rcBuildContours: Bad outline for region %d, contour simplification is likely too aggressive.", i);
}
} }
} }

View File

@ -56,7 +56,7 @@ inline float vdist2(const float* p, const float* q)
} }
inline float vcross2(const float* p1, const float* p2, const float* p3) inline float vcross2(const float* p1, const float* p2, const float* p3)
{ {
const float u1 = p2[0] - p1[0]; const float u1 = p2[0] - p1[0];
const float v1 = p2[2] - p1[2]; const float v1 = p2[2] - p1[2];
const float u2 = p3[0] - p1[0]; const float u2 = p3[0] - p1[0];
@ -68,21 +68,27 @@ static bool circumCircle(const float* p1, const float* p2, const float* p3,
float* c, float& r) float* c, float& r)
{ {
static const float EPS = 1e-6f; static const float EPS = 1e-6f;
// Calculate the circle relative to p1, to avoid some precision issues.
const float v1[3] = {0,0,0};
float v2[3], v3[3];
rcVsub(v2, p2,p1);
rcVsub(v3, p3,p1);
const float cp = vcross2(p1, p2, p3); const float cp = vcross2(v1, v2, v3);
if (fabsf(cp) > EPS) if (fabsf(cp) > EPS)
{ {
const float p1Sq = vdot2(p1,p1); const float v1Sq = vdot2(v1,v1);
const float p2Sq = vdot2(p2,p2); const float v2Sq = vdot2(v2,v2);
const float p3Sq = vdot2(p3,p3); const float v3Sq = vdot2(v3,v3);
c[0] = (p1Sq*(p2[2]-p3[2]) + p2Sq*(p3[2]-p1[2]) + p3Sq*(p1[2]-p2[2])) / (2*cp); c[0] = (v1Sq*(v2[2]-v3[2]) + v2Sq*(v3[2]-v1[2]) + v3Sq*(v1[2]-v2[2])) / (2*cp);
c[2] = (p1Sq*(p3[0]-p2[0]) + p2Sq*(p1[0]-p3[0]) + p3Sq*(p2[0]-p1[0])) / (2*cp); c[1] = 0;
r = vdist2(c, p1); c[2] = (v1Sq*(v3[0]-v2[0]) + v2Sq*(v1[0]-v3[0]) + v3Sq*(v2[0]-v1[0])) / (2*cp);
r = vdist2(c, v1);
rcVadd(c, c, p1);
return true; return true;
} }
c[0] = p1[0]; rcVcopy(c, p1);
c[2] = p1[2];
r = 0; r = 0;
return false; return false;
} }
@ -93,7 +99,7 @@ static float distPtTri(const float* p, const float* a, const float* b, const flo
rcVsub(v0, c,a); rcVsub(v0, c,a);
rcVsub(v1, b,a); rcVsub(v1, b,a);
rcVsub(v2, p,a); rcVsub(v2, p,a);
const float dot00 = vdot2(v0, v0); const float dot00 = vdot2(v0, v0);
const float dot01 = vdot2(v0, v1); const float dot01 = vdot2(v0, v1);
const float dot02 = vdot2(v0, v2); const float dot02 = vdot2(v0, v2);
@ -178,7 +184,7 @@ static float distToTriMesh(const float* p, const float* verts, const int /*nvert
static float distToPoly(int nvert, const float* verts, const float* p) static float distToPoly(int nvert, const float* verts, const float* p)
{ {
float dmin = FLT_MAX; float dmin = FLT_MAX;
int i, j, c = 0; int i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++) for (i = 0, j = nvert-1; i < nvert; j = i++)
@ -216,22 +222,13 @@ static unsigned short getHeight(const float fx, const float fy, const float fz,
if (nx < 0 || nz < 0 || nx >= hp.width || nz >= hp.height) continue; if (nx < 0 || nz < 0 || nx >= hp.width || nz >= hp.height) continue;
const unsigned short nh = hp.data[nx+nz*hp.width]; const unsigned short nh = hp.data[nx+nz*hp.width];
if (nh == RC_UNSET_HEIGHT) continue; if (nh == RC_UNSET_HEIGHT) continue;
const float d = fabsf(nh*ch - fy); const float d = fabsf(nh*ch - fy);
if (d < dmin) if (d < dmin)
{ {
h = nh; h = nh;
dmin = d; dmin = d;
} }
/* const float dx = (nx+0.5f)*cs - fx;
const float dz = (nz+0.5f)*cs - fz;
const float d = dx*dx+dz*dz;
if (d < dmin)
{
h = nh;
dmin = d;
} */
} }
} }
return h; return h;
@ -263,7 +260,7 @@ static int addEdge(rcContext* ctx, int* edges, int& nedges, const int maxEdges,
return UNDEF; return UNDEF;
} }
// Add edge if not already in the triangulation. // Add edge if not already in the triangulation.
int e = findEdge(edges, nedges, s, t); int e = findEdge(edges, nedges, s, t);
if (e == UNDEF) if (e == UNDEF)
{ {
@ -286,7 +283,7 @@ static void updateLeftFace(int* e, int s, int t, int f)
e[2] = f; e[2] = f;
else if (e[1] == s && e[0] == t && e[3] == UNDEF) else if (e[1] == s && e[0] == t && e[3] == UNDEF)
e[3] = f; e[3] = f;
} }
static int overlapSegSeg2d(const float* a, const float* b, const float* c, const float* d) static int overlapSegSeg2d(const float* a, const float* b, const float* c, const float* d)
{ {
@ -298,7 +295,7 @@ static int overlapSegSeg2d(const float* a, const float* b, const float* c, const
float a4 = a3 + a2 - a1; float a4 = a3 + a2 - a1;
if (a3 * a4 < 0.0f) if (a3 * a4 < 0.0f)
return 1; return 1;
} }
return 0; return 0;
} }
@ -320,7 +317,7 @@ static bool overlapEdges(const float* pts, const int* edges, int nedges, int s1,
static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges, int& nedges, const int maxEdges, int& nfaces, int e) static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges, int& nedges, const int maxEdges, int& nfaces, int e)
{ {
static const float EPS = 1e-5f; static const float EPS = 1e-5f;
int* edge = &edges[e*4]; int* edge = &edges[e*4];
// Cache s and t. // Cache s and t.
@ -337,11 +334,11 @@ static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges
} }
else else
{ {
// Edge already completed. // Edge already completed.
return; return;
} }
// Find best point on left of edge. // Find best point on left of edge.
int pt = npts; int pt = npts;
float c[3] = {0,0,0}; float c[3] = {0,0,0};
float r = -1; float r = -1;
@ -385,20 +382,20 @@ static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges
} }
} }
// Add new triangle or update edge info if s-t is on hull. // Add new triangle or update edge info if s-t is on hull.
if (pt < npts) if (pt < npts)
{ {
// Update face information of edge being completed. // Update face information of edge being completed.
updateLeftFace(&edges[e*4], s, t, nfaces); updateLeftFace(&edges[e*4], s, t, nfaces);
// Add new edge or update face info of old edge. // Add new edge or update face info of old edge.
e = findEdge(edges, nedges, pt, s); e = findEdge(edges, nedges, pt, s);
if (e == UNDEF) if (e == UNDEF)
addEdge(ctx, edges, nedges, maxEdges, pt, s, nfaces, UNDEF); addEdge(ctx, edges, nedges, maxEdges, pt, s, nfaces, UNDEF);
else else
updateLeftFace(&edges[e*4], pt, s, nfaces); updateLeftFace(&edges[e*4], pt, s, nfaces);
// Add new edge or update face info of old edge. // Add new edge or update face info of old edge.
e = findEdge(edges, nedges, t, pt); e = findEdge(edges, nedges, t, pt);
if (e == UNDEF) if (e == UNDEF)
addEdge(ctx, edges, nedges, maxEdges, t, pt, nfaces, UNDEF); addEdge(ctx, edges, nedges, maxEdges, t, pt, nfaces, UNDEF);
@ -434,7 +431,7 @@ static void delaunayHull(rcContext* ctx, const int npts, const float* pts,
completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge); completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge);
currentEdge++; currentEdge++;
} }
// Create tris // Create tris
tris.resize(nfaces*4); tris.resize(nfaces*4);
for (int i = 0; i < nfaces*4; ++i) for (int i = 0; i < nfaces*4; ++i)
@ -523,16 +520,18 @@ inline int prev(int i, int n)
static void triangulateHull(const int nverts, const float* verts, const int nhull, const int* hull, rcIntArray& tris) static void triangulateHull(const int nverts, const float* verts, const int nhull, const int* hull, rcIntArray& tris)
{ {
int start = 0, left = 1, right = nhull-1; int start = 0, left = 1, right = nhull-1;
// Start from shortest ear. // Start from an ear with shortest perimeter.
float dmin = FLT_MAX; // This tends to favor well formed triangles as starting point.
float dmin = 0;
for (int i = 0; i < nhull; i++) for (int i = 0; i < nhull; i++)
{ {
int pi = prev(i, nhull); int pi = prev(i, nhull);
int ni = next(i, nhull); int ni = next(i, nhull);
const float* pv = &verts[hull[pi]*3]; const float* pv = &verts[hull[pi]*3];
const float* cv = &verts[hull[i]*3];
const float* nv = &verts[hull[ni]*3]; const float* nv = &verts[hull[ni]*3];
const float d = vdistSq2(pv, nv); const float d = vdist2(pv,cv) + vdist2(cv,nv) + vdist2(nv,pv);
if (d < dmin) if (d < dmin)
{ {
start = i; start = i;
@ -547,22 +546,24 @@ static void triangulateHull(const int nverts, const float* verts, const int nhul
tris.push(hull[left]); tris.push(hull[left]);
tris.push(hull[right]); tris.push(hull[right]);
tris.push(0); tris.push(0);
// Triangulate the polygon by adding the shortest diagonal // Triangulate the polygon by moving left or right,
// by moving left or right. // depending on which triangle has shorter perimeter.
// This heuristic was chose emprically, since it seems
// handle tesselated straight edges well.
while (next(left, nhull) != right) while (next(left, nhull) != right)
{ {
// Check to see if se should advance left or right. // Check to see if se should advance left or right.
int nleft = next(left, nhull); int nleft = next(left, nhull);
int nright = prev(right, nhull); int nright = prev(right, nhull);
const float* cvleft = &verts[hull[left]*3]; const float* cvleft = &verts[hull[left]*3];
const float* nvleft = &verts[hull[nleft]*3]; const float* nvleft = &verts[hull[nleft]*3];
const float* cvright = &verts[hull[right]*3]; const float* cvright = &verts[hull[right]*3];
const float* nvright = &verts[hull[nright]*3]; const float* nvright = &verts[hull[nright]*3];
const float dleft = vdist2(cvleft, nvleft) + vdist2(nvleft, cvright);
const float dleft = vdistSq2(nvleft, cvright); const float dright = vdist2(cvright, nvright) + vdist2(cvleft, nvright);
const float dright = vdistSq2(cvleft, nvright);
if (dleft < dright) if (dleft < dright)
{ {
tris.push(hull[left]); tris.push(hull[left]);
@ -605,19 +606,19 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
float edge[(MAX_VERTS_PER_EDGE+1)*3]; float edge[(MAX_VERTS_PER_EDGE+1)*3];
int hull[MAX_VERTS]; int hull[MAX_VERTS];
int nhull = 0; int nhull = 0;
nverts = 0; nverts = 0;
for (int i = 0; i < nin; ++i) for (int i = 0; i < nin; ++i)
rcVcopy(&verts[i*3], &in[i*3]); rcVcopy(&verts[i*3], &in[i*3]);
nverts = nin; nverts = nin;
edges.resize(0); edges.resize(0);
tris.resize(0); tris.resize(0);
const float cs = chf.cs; const float cs = chf.cs;
const float ics = 1.0f/cs; const float ics = 1.0f/cs;
// Calculate minimum extents of the polygon based on input data. // Calculate minimum extents of the polygon based on input data.
float minExtent = polyMinExtent(verts, nverts); float minExtent = polyMinExtent(verts, nverts);
@ -726,27 +727,27 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
} }
} }
} }
// If the polygon minimum extent is small (sliver or small triangle), do not try to add internal points. // If the polygon minimum extent is small (sliver or small triangle), do not try to add internal points.
if (minExtent < sampleDist*2) if (minExtent < sampleDist*2)
{ {
triangulateHull(nverts, verts, nhull, hull, tris); triangulateHull(nverts, verts, nhull, hull, tris);
return true; return true;
} }
// Tessellate the base mesh. // Tessellate the base mesh.
// We're using the triangulateHull instead of delaunayHull as it tends to // We're using the triangulateHull instead of delaunayHull as it tends to
// create a bit better triangulation for long thing triangles when there // create a bit better triangulation for long thing triangles when there
// are no internal points. // are no internal points.
triangulateHull(nverts, verts, nhull, hull, tris); triangulateHull(nverts, verts, nhull, hull, tris);
if (tris.size() == 0) if (tris.size() == 0)
{ {
// Could not triangulate the poly, make sure there is some valid data there. // 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); ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon (%d verts).", nverts);
return true; return true;
} }
if (sampleDist > 0) if (sampleDist > 0)
{ {
// Create sample locations in a grid. // Create sample locations in a grid.
@ -779,7 +780,7 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
samples.push(0); // Not added samples.push(0); // Not added
} }
} }
// Add the samples starting from the one that has the most // Add the samples starting from the one that has the most
// error. The procedure stops when all samples are added // error. The procedure stops when all samples are added
// or when the max error is within treshold. // or when the max error is within treshold.
@ -788,7 +789,7 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
{ {
if (nverts >= MAX_VERTS) if (nverts >= MAX_VERTS)
break; break;
// Find sample with most error. // Find sample with most error.
float bestpt[3] = {0,0,0}; float bestpt[3] = {0,0,0};
float bestd = 0; float bestd = 0;
@ -826,24 +827,24 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
edges.resize(0); edges.resize(0);
tris.resize(0); tris.resize(0);
delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges);
} }
} }
const int ntris = tris.size()/4; const int ntris = tris.size()/4;
if (ntris > MAX_TRIS) if (ntris > MAX_TRIS)
{ {
tris.resize(MAX_TRIS*4); tris.resize(MAX_TRIS*4);
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS); ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS);
} }
return true; return true;
} }
static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf, static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf,
const unsigned short* poly, const int npoly, const unsigned short* poly, const int npoly,
const unsigned short* verts, const int bs, const unsigned short* verts, const int bs,
rcHeightPatch& hp, rcIntArray& stack) rcHeightPatch& hp, rcIntArray& stack)
{ {
// Floodfill the heightfield to get 2D height data, // Floodfill the heightfield to get 2D height data,
// starting at vertex locations as seeds. // starting at vertex locations as seeds.
@ -947,7 +948,7 @@ static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf,
continue; continue;
const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir);
int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width;
hp.data[idx] = 1; hp.data[idx] = 1;
@ -956,9 +957,9 @@ static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf,
stack.push(ai); stack.push(ai);
} }
} }
memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);
// Mark start locations. // Mark start locations.
for (int i = 0; i < stack.size(); i += 3) for (int i = 0; i < stack.size(); i += 3)
{ {
@ -968,8 +969,8 @@ static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf,
int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width;
const rcCompactSpan& cs = chf.spans[ci]; const rcCompactSpan& cs = chf.spans[ci];
hp.data[idx] = cs.y; hp.data[idx] = cs.y;
// getHeightData seeds are given in coordinates with borders // getHeightData seeds are given in coordinates with borders
stack[i+0] += bs; stack[i+0] += bs;
stack[i+1] += bs; stack[i+1] += bs;
} }
@ -986,12 +987,12 @@ static void getHeightData(const rcCompactHeightfield& chf,
{ {
// Note: Reads to the compact heightfield are offset by border size (bs) // Note: Reads to the compact heightfield are offset by border size (bs)
// since border size offset is already removed from the polymesh vertices. // since border size offset is already removed from the polymesh vertices.
stack.resize(0); stack.resize(0);
memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);
bool empty = true; bool empty = true;
// Copy the height from the same region, and mark region borders // Copy the height from the same region, and mark region borders
// as seed points to fill the rest. // as seed points to fill the rest.
for (int hy = 0; hy < hp.height; hy++) for (int hy = 0; hy < hp.height; hy++)
@ -1009,7 +1010,7 @@ static void getHeightData(const rcCompactHeightfield& chf,
// Store height // Store height
hp.data[hx + hy*hp.width] = s.y; hp.data[hx + hy*hp.width] = s.y;
empty = false; empty = false;
// If any of the neighbours is not in same region, // If any of the neighbours is not in same region,
// add the current location as flood fill start // add the current location as flood fill start
bool border = false; bool border = false;
@ -1038,8 +1039,8 @@ static void getHeightData(const rcCompactHeightfield& chf,
} }
} }
} }
} }
// if the polygon does not contian any points from the current region (rare, but happens) // if the polygon does not contian any points from the current region (rare, but happens)
// then use the cells closest to the polygon vertices as seeds to fill the height field // then use the cells closest to the polygon vertices as seeds to fill the height field
if (empty) if (empty)
@ -1061,7 +1062,7 @@ static void getHeightData(const rcCompactHeightfield& chf,
memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3)); memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3));
stack.resize(stack.size()-RETRACT_SIZE*3); stack.resize(stack.size()-RETRACT_SIZE*3);
} }
const rcCompactSpan& cs = chf.spans[ci]; const rcCompactSpan& cs = chf.spans[ci];
for (int dir = 0; dir < 4; ++dir) for (int dir = 0; dir < 4; ++dir)
{ {
@ -1080,9 +1081,9 @@ static void getHeightData(const rcCompactHeightfield& chf,
const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir); const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir);
const rcCompactSpan& as = chf.spans[ai]; const rcCompactSpan& as = chf.spans[ai];
hp.data[hx + hy*hp.width] = as.y; hp.data[hx + hy*hp.width] = as.y;
stack.push(ax); stack.push(ax);
stack.push(ay); stack.push(ay);
stack.push(ai); stack.push(ai);
@ -1097,7 +1098,7 @@ static unsigned char getEdgeFlags(const float* va, const float* vb,
static const float thrSqr = rcSqr(0.001f); static const float thrSqr = rcSqr(0.001f);
for (int i = 0, j = npoly-1; i < npoly; j=i++) for (int i = 0, j = npoly-1; i < npoly; j=i++)
{ {
if (distancePtSeg2d(va, &vpoly[j*3], &vpoly[i*3]) < thrSqr && if (distancePtSeg2d(va, &vpoly[j*3], &vpoly[i*3]) < thrSqr &&
distancePtSeg2d(vb, &vpoly[j*3], &vpoly[i*3]) < thrSqr) distancePtSeg2d(vb, &vpoly[j*3], &vpoly[i*3]) < thrSqr)
return 1; return 1;
} }
@ -1126,7 +1127,7 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa
rcAssert(ctx); rcAssert(ctx);
ctx->startTimer(RC_TIMER_BUILD_POLYMESHDETAIL); ctx->startTimer(RC_TIMER_BUILD_POLYMESHDETAIL);
if (mesh.nverts == 0 || mesh.npolys == 0) if (mesh.nverts == 0 || mesh.npolys == 0)
return true; return true;
@ -1205,10 +1206,10 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4); ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4);
return false; return false;
} }
int vcap = nPolyVerts+nPolyVerts/2; int vcap = nPolyVerts+nPolyVerts/2;
int tcap = vcap*2; int tcap = vcap*2;
dmesh.nverts = 0; dmesh.nverts = 0;
dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM);
if (!dmesh.verts) if (!dmesh.verts)
@ -1256,7 +1257,7 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa
{ {
return false; return false;
} }
// Move detail verts to world space. // Move detail verts to world space.
for (int j = 0; j < nverts; ++j) for (int j = 0; j < nverts; ++j)
{ {
@ -1271,21 +1272,21 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa
poly[j*3+1] += orig[1]; poly[j*3+1] += orig[1];
poly[j*3+2] += orig[2]; poly[j*3+2] += orig[2];
} }
// Store detail submesh. // Store detail submesh.
const int ntris = tris.size()/4; const int ntris = tris.size()/4;
dmesh.meshes[i*4+0] = (unsigned int)dmesh.nverts; dmesh.meshes[i*4+0] = (unsigned int)dmesh.nverts;
dmesh.meshes[i*4+1] = (unsigned int)nverts; dmesh.meshes[i*4+1] = (unsigned int)nverts;
dmesh.meshes[i*4+2] = (unsigned int)dmesh.ntris; dmesh.meshes[i*4+2] = (unsigned int)dmesh.ntris;
dmesh.meshes[i*4+3] = (unsigned int)ntris; dmesh.meshes[i*4+3] = (unsigned int)ntris;
// Store vertices, allocate more memory if necessary. // Store vertices, allocate more memory if necessary.
if (dmesh.nverts+nverts > vcap) if (dmesh.nverts+nverts > vcap)
{ {
while (dmesh.nverts+nverts > vcap) while (dmesh.nverts+nverts > vcap)
vcap += 256; vcap += 256;
float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM);
if (!newv) if (!newv)
{ {
@ -1331,9 +1332,9 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa
dmesh.ntris++; dmesh.ntris++;
} }
} }
ctx->stopTimer(RC_TIMER_BUILD_POLYMESHDETAIL); ctx->stopTimer(RC_TIMER_BUILD_POLYMESHDETAIL);
return true; return true;
} }
@ -1343,11 +1344,11 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int
rcAssert(ctx); rcAssert(ctx);
ctx->startTimer(RC_TIMER_MERGE_POLYMESHDETAIL); ctx->startTimer(RC_TIMER_MERGE_POLYMESHDETAIL);
int maxVerts = 0; int maxVerts = 0;
int maxTris = 0; int maxTris = 0;
int maxMeshes = 0; int maxMeshes = 0;
for (int i = 0; i < nmeshes; ++i) for (int i = 0; i < nmeshes; ++i)
{ {
if (!meshes[i]) continue; if (!meshes[i]) continue;
@ -1355,7 +1356,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int
maxTris += meshes[i]->ntris; maxTris += meshes[i]->ntris;
maxMeshes += meshes[i]->nmeshes; maxMeshes += meshes[i]->nmeshes;
} }
mesh.nmeshes = 0; mesh.nmeshes = 0;
mesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*maxMeshes*4, RC_ALLOC_PERM); mesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*maxMeshes*4, RC_ALLOC_PERM);
if (!mesh.meshes) if (!mesh.meshes)
@ -1363,7 +1364,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4); ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4);
return false; return false;
} }
mesh.ntris = 0; mesh.ntris = 0;
mesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris*4, RC_ALLOC_PERM); mesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris*4, RC_ALLOC_PERM);
if (!mesh.tris) if (!mesh.tris)
@ -1371,7 +1372,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4); ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4);
return false; return false;
} }
mesh.nverts = 0; mesh.nverts = 0;
mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM); mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM);
if (!mesh.verts) if (!mesh.verts)
@ -1395,7 +1396,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int
dst[3] = src[3]; dst[3] = src[3];
mesh.nmeshes++; mesh.nmeshes++;
} }
for (int k = 0; k < dm->nverts; ++k) for (int k = 0; k < dm->nverts; ++k)
{ {
rcVcopy(&mesh.verts[mesh.nverts*3], &dm->verts[k*3]); rcVcopy(&mesh.verts[mesh.nverts*3], &dm->verts[k*3]);
@ -1410,7 +1411,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int
mesh.ntris++; mesh.ntris++;
} }
} }
ctx->stopTimer(RC_TIMER_MERGE_POLYMESHDETAIL); ctx->stopTimer(RC_TIMER_MERGE_POLYMESHDETAIL);
return true; return true;