using System; using System.Collections.Generic; using System.Diagnostics; public static partial class Recast{ const ushort RC_UNSET_HEIGHT = 0xffff; public class rcHeightPatch { public rcHeightPatch(){ } public ushort[] data = null; public int xmin = 0; public int ymin = 0; public int width = 0; public int height = 0; }; public static float vdot2(float[] a, float[] b) { return a[0]*b[0] + a[2]*b[2]; } public static float vdot2(float[] a, int aStart, float[] b, int bStart) { return a[aStart]*b[bStart] + a[aStart +2]*b[bStart + 2]; } public static float vdistSq2(float[] p, float[] q) { float dx = q[0] - p[0]; float dy = q[2] - p[2]; return dx*dx + dy*dy; } public static float vdistSq2(float[] p, int pStart, float[] q, int qStart) { float dx = q[qStart + 0] - p[pStart + 0]; float dy = q[qStart + 2] - p[pStart + 2]; return dx*dx + dy*dy; } public static float vdist2(float[] p, float[] q) { return (float)Math.Sqrt(vdistSq2(p,q)); } public static float vdist2(float[] p, int pStart, float[] q, int qStart) { return (float)Math.Sqrt(vdistSq2(p,pStart,q,qStart)); } public static float vcross2(float[] p1, float[] p2, float[]p3) { float u1 = p2[0] - p1[0]; float v1 = p2[2] - p1[2]; float u2 = p3[0] - p1[0]; float v2 = p3[2] - p1[2]; return u1 * v2 - v1 * u2; } public static float vcross2(float[] p1, int p1Start, float[] p2, int p2Start, float[]p3, int p3Start) { float u1 = p2[0 + p2Start] - p1[0 + p1Start]; float v1 = p2[2 + p2Start] - p1[2 + p1Start]; float u2 = p3[0 + p3Start] - p1[0 + p1Start]; float v2 = p3[2 + p3Start] - p1[2 + p1Start]; return u1 * v2 - v1 * u2; } public static bool circumCircle(float[] p1, float[] p2, float[]p3, float[] c,ref float r) { const float EPS = 1e-6f; float cp = vcross2(p1, p2, p3); if (Math.Abs(cp) > EPS) { float p1Sq = vdot2(p1,p1); float p2Sq = vdot2(p2,p2); float p3Sq = vdot2(p3,p3); c[0] = (p1Sq*(p2[2]-p3[2]) + p2Sq*(p3[2]-p1[2]) + p3Sq*(p1[2]-p2[2])) / (2*cp); c[2] = (p1Sq*(p3[0]-p2[0]) + p2Sq*(p1[0]-p3[0]) + p3Sq*(p2[0]-p1[0])) / (2*cp); r = vdist2(c, p1); return true; } c[0] = p1[0]; c[2] = p1[2]; r = 0; return false; } public static bool circumCircle(float[] p1, int p1Start, float[] p2, int p2Start, float[]p3, int p3Start, float[] c, int cStart ,ref float r) { const float EPS = 1e-6f; float cp = vcross2(p1, p1Start, p2, p2Start, p3, p3Start); if (Math.Abs(cp) > EPS) { float p1Sq = vdot2(p1,p1Start,p1, p1Start); float p2Sq = vdot2(p2, p2Start,p2, p2Start); float p3Sq = vdot2(p3,p3Start,p3,p3Start); c[cStart+ 0] = (p1Sq * (p2[p2Start + 2] - p3[p3Start + 2]) + p2Sq * (p3[p3Start + 2] - p1[p1Start + 2]) + p3Sq * (p1[p1Start + 2] - p2[p2Start + 2])) / (2 * cp); c[cStart+ 2] = (p1Sq * (p3[p3Start + 0] - p2[p2Start + 0]) + p2Sq * (p1[p1Start + 0] - p3[p3Start + 0]) + p3Sq * (p2[p2Start + 0] - p1[p1Start + 0])) / (2 * cp); r = vdist2(c, cStart, p1, p1Start); return true; } c[cStart + 0] = p1[p1Start + 0]; c[cStart + 2] = p1[p1Start + 2]; r = 0; return false; } static float distPtTri(float[] p, float[]a, float[] b, float[]c) { float[] v0 = new float[3]; float[] v1 = new float[3]; float[] v2 = new float[3]; rcVsub(v0, c,a); rcVsub(v1, b,a); rcVsub(v2, p,a); float dot00 = vdot2(v0, v0); float dot01 = vdot2(v0, v1); float dot02 = vdot2(v0, v2); float dot11 = vdot2(v1, v1); float dot12 = vdot2(v1, v2); // Compute barycentric coordinates float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); float u = (dot11 * dot02 - dot01 * dot12) * invDenom; float v = (dot00 * dot12 - dot01 * dot02) * invDenom; // If point lies inside the triangle, return interpolated y-coord. const float EPS = 1e-4f; if (u >= -EPS && v >= -EPS && (u+v) <= 1+EPS) { float y = a[1] + v0[1]*u + v1[1]*v; return Math.Abs(y-p[1]); } return float.MaxValue; } static float distPtTri(float[] p, int pStart, float[] a, int aStart, float[] b, int bStart, float[] c, int cStart) { float[] v0 = new float[3]; float[] v1 = new float[3]; float[] v2 = new float[3]; rcVsub(v0,0, c,cStart, a,aStart); rcVsub(v1,0, b,bStart, a,aStart); rcVsub(v2,0, p,pStart, a,aStart); float dot00 = vdot2(v0, v0); float dot01 = vdot2(v0, v1); float dot02 = vdot2(v0, v2); float dot11 = vdot2(v1, v1); float dot12 = vdot2(v1, v2); // Compute barycentric coordinates float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); float u = (dot11 * dot02 - dot01 * dot12) * invDenom; float v = (dot00 * dot12 - dot01 * dot02) * invDenom; // If point lies inside the triangle, return interpolated y-coord. const float EPS = 1e-4f; if (u >= -EPS && v >= -EPS && (u + v) <= 1 + EPS) { float y = a[aStart+1] + v0[1] * u + v1[1] * v; return Math.Abs(y - p[pStart+1]); } return float.MaxValue; } static float distancePtSeg(float[] pt, float[] p, float[] q) { float pqx = q[0] - p[0]; float pqy = q[1] - p[1]; float pqz = q[2] - p[2]; float dx = pt[0] - p[0]; float dy = pt[1] - p[1]; float dz = pt[2] - p[2]; 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 = p[0] + t*pqx - pt[0]; dy = p[1] + t*pqy - pt[1]; dz = p[2] + t*pqz - pt[2]; return dx*dx + dy*dy + dz*dz; } static float distancePtSeg(float[] pt, int ptStart, float[] p, int pStart, float[] q, int qStart) { float pqx = q[qStart + 0] - p[pStart + 0]; float pqy = q[qStart + 1] - p[pStart + 1]; float pqz = q[qStart + 2] - p[pStart + 2]; float dx = pt[ptStart + 0] - p[pStart + 0]; float dy = pt[ptStart + 1] - p[pStart + 1]; float dz = pt[ptStart + 2] - p[pStart + 2]; 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 = p[pStart + 0] + t * pqx - pt[ptStart + 0]; dy = p[pStart + 1] + t * pqy - pt[ptStart + 1]; dz = p[pStart + 2] + t * pqz - pt[ptStart + 2]; return dx * dx + dy * dy + dz * dz; } static float distancePtSeg2d(float[] pt, float[] p, float[] q) { float pqx = q[0] - p[0]; float pqz = q[2] - p[2]; float dx = pt[0] - p[0]; float dz = pt[2] - p[2]; float d = pqx*pqx + pqz*pqz; float t = pqx*dx + pqz*dz; if (d > 0) t /= d; if (t < 0) t = 0; else if (t > 1) t = 1; dx = p[0] + t*pqx - pt[0]; dz = p[2] + t*pqz - pt[2]; return dx*dx + dz*dz; } static float distancePtSeg2d(float[] pt, int ptStart, float[] p, int pStart, float[] q, int qStart) { float pqx = q[qStart + 0] - p[0 + pStart]; float pqz = q[qStart + 2] - p[2 + pStart]; float dx = pt[ptStart + 0] - p[0 + pStart]; float dz = pt[ptStart + 2] - p[2 + pStart]; float d = pqx*pqx + pqz*pqz; float t = pqx*dx + pqz*dz; if (d > 0) t /= d; if (t < 0) t = 0; else if (t > 1) t = 1; dx = p[0 + pStart] + t*pqx - pt[ptStart + 0]; dz = p[2 + pStart] + t*pqz - pt[ptStart + 2]; return dx*dx + dz*dz; } static float distToTriMesh(float[] p, float[] verts, int nverts, List tris, int ntris) { float dmin = float.MaxValue; for (int i = 0; i < ntris; ++i) { int vaStart = tris[i * 4 + 0] * 3; int vbStart = tris[i * 4 + 1] * 3; int vcStart = tris[i * 4 + 2] * 3; float d = distPtTri(p,0, verts,vaStart,verts,vbStart,verts,vcStart); if (d < dmin) dmin = d; } if (dmin == float.MaxValue) return -1; return dmin; } static float distToPoly(int nvert, float[] verts, float[] p) { float dmin = float.MaxValue; int i, j; bool c = false; for (i = 0, j = nvert-1; i < nvert; j = i++) { int viStart = i * 3; int vjStart = j * 3; if (((verts[viStart+2] > p[2]) != (verts[vjStart+2] > p[2])) && (p[0] < (verts[vjStart + 0] - verts[viStart + 0]) * (p[2] - verts[viStart + 2]) / (verts[vjStart + 2] - verts[viStart + 2]) + verts[viStart + 0])) c = !c; dmin = Math.Min(dmin, distancePtSeg2d(p, 0, verts, vjStart, verts, viStart)); } return c ? -dmin : dmin; } static ushort getHeight(float fx, float fy, float fz, float cs, float ics, float ch, rcHeightPatch hp) { int ix = (int)Math.Floor(fx*ics + 0.01f); int iz = (int)Math.Floor(fz*ics + 0.01f); ix = rcClamp(ix-hp.xmin, 0, hp.width - 1); iz = rcClamp(iz-hp.ymin, 0, hp.height - 1); ushort h = hp.data[ix+iz*hp.width]; if (h == RC_UNSET_HEIGHT) { // Special case when data might be bad. // Find nearest neighbour pixel which has valid height. int[] off/*[8*2]*/ = new int[] { -1,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1}; float dmin = float.MaxValue; for (int i = 0; i < 8; ++i) { int nx = ix+off[i*2+0]; int nz = iz+off[i*2+1]; if (nx < 0 || nz < 0 || nx >= hp.width || nz >= hp.height) { continue; } ushort nh = hp.data[nx+nz*hp.width]; if (nh == RC_UNSET_HEIGHT){ continue; } float d = Math.Abs(nh*ch - fy); if (d < dmin) { h = nh; 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; } public static class EdgeValues { public const int UNDEF = -1; public const int HULL = -2; }; static int findEdge(List edges, int nedges, int s, int t) { for (int i = 0; i < nedges; i++) { //int[] e = &edges[i*4]; int eIndex = i*4; if ((edges[eIndex + 0] == s && edges[eIndex + 1] == t) || (edges[eIndex + 0] == t && edges[eIndex + 1] == s)){ return i; } } return EdgeValues.UNDEF; } static int findEdge(int[] edges, int nedges, int s, int t) { for (int i = 0; i < nedges; i++) { //int[] e = &edges[i*4]; int eIndex = i*4; if ((edges[eIndex + 0] == s && edges[eIndex + 1] == t) || (edges[eIndex + 0] == t && edges[eIndex + 1] == s)){ return i; } } return EdgeValues.UNDEF; } static int addEdge(rcContext ctx, List edges, ref int nedges, int maxEdges, int s, int t, int l, int r) { if (nedges >= maxEdges) { ctx.log(rcLogCategory.RC_LOG_ERROR, "addEdge (list version): Too many edges ("+nedges+"/"+maxEdges+")."); return EdgeValues.UNDEF; } // Add edge if not already in the triangulation. int e = findEdge(edges, nedges, s, t); if (e == EdgeValues.UNDEF) { //int* edge = &edges[nedges*4]; //int edgeIndex = nedges*4; /*edges[edgeIndex + 0] = s; edges[edgeIndex + 1] = t; edges[edgeIndex + 2] = l; edges[edgeIndex + 3] = r;*/ edges.Add(s); edges.Add(t); edges.Add(l); edges.Add(r); return nedges++; } else { return EdgeValues.UNDEF; } } static int addEdge(rcContext ctx, int[] edges, ref int nedges, int maxEdges, int s, int t, int l, int r) { if (nedges >= maxEdges) { ctx.log(rcLogCategory.RC_LOG_ERROR, "addEdge: Too many edges ("+nedges+"/"+maxEdges+")."); return EdgeValues.UNDEF; } // Add edge if not already in the triangulation. int e = findEdge(edges, nedges, s, t); if (e == EdgeValues.UNDEF) { //int* edge = &edges[nedges*4]; int edgeIndex = nedges*4; edges[edgeIndex + 0] = s; edges[edgeIndex + 1] = t; edges[edgeIndex + 2] = l; edges[edgeIndex + 3] = r; return nedges++; } else { return EdgeValues.UNDEF; } } static void updateLeftFace(List e, int eStart, int s, int t, int f) { if (e[eStart + 0] == s && e[eStart + 1] == t && e[eStart + 2] == EdgeValues.UNDEF){ e[eStart + 2] = f; }else if (e[eStart + 1] == s && e[eStart + 0] == t && e[eStart + 3] == EdgeValues.UNDEF){ e[eStart + 3] = f; } } static void updateLeftFace(List e, int s, int t, int f) { if (e[0] == s && e[1] == t && e[2] == EdgeValues.UNDEF){ e[2] = f; }else if (e[1] == s && e[0] == t && e[3] == EdgeValues.UNDEF){ e[3] = f; } } static int overlapSegSeg2d(float[] a, float[] b, float[] c, float[] d) { float a1 = vcross2(a, b, d); float a2 = vcross2(a, b, c); if (a1*a2 < 0.0f) { float a3 = vcross2(c, d, a); float a4 = a3 + a2 - a1; if (a3 * a4 < 0.0f){ return 1; } } return 0; } static int overlapSegSeg2d(float[] a, int aStart, float[] b, int bStart, float[] c, int cStart, float[] d, int dStart) { float a1 = vcross2(a, aStart, b, bStart, d, dStart); float a2 = vcross2(a, aStart, b, bStart, c, cStart); if (a1*a2 < 0.0f) { float a3 = vcross2(c, cStart, d, dStart, a, aStart); float a4 = a3 + a2 - a1; if (a3 * a4 < 0.0f){ return 1; } } return 0; } static bool overlapEdges(float[] pts, List edges, int nedges, int s1, int t1) { for (int i = 0; i < nedges; ++i) { int s0 = edges[i*4+0]; int t0 = edges[i*4+1]; // Same or connected edges do not overlap. if (s0 == s1 || s0 == t1 || t0 == s1 || t0 == t1){ continue; } if (overlapSegSeg2d(pts,s0*3,pts,t0*3, pts,s1*3,pts,t1*3) != 0){ return true; } } return false; } static void completeFacet(rcContext ctx, float[] pts, int npts, List edges, ref int nedges, int maxEdges, ref int nfaces, int e) { const float EPS = 1e-5f; //int[] edge = &edges[e*4]; int edgeIndex = e*4; // Cache s and t. int s,t; if (edges[edgeIndex + 2] == EdgeValues.UNDEF) { s = edges[edgeIndex + 0]; t = edges[edgeIndex + 1]; } else if (edges[edgeIndex + 3] == EdgeValues.UNDEF) { s = edges[edgeIndex + 1]; t = edges[edgeIndex + 0]; } else { // Edge already completed. return; } // Find best point on left of edge. int pt = npts; float[] c = new float[] {0,0,0}; float r = -1.0f; for (int u = 0; u < npts; ++u) { if (u == s || u == t) { continue; } if (vcross2(pts,s*3, pts, t*3, pts, u*3) > EPS) { if (r < 0) { // The circle is not updated yet, do it now. pt = u; circumCircle(pts,s*3, pts,t*3, pts,u*3, c, 0,ref r); continue; } float d = vdist2(c, 0 , pts, u*3); float tol = 0.001f; if (d > r*(1+tol)) { // Outside current circumcircle, skip. continue; } else if (d < r*(1-tol)) { // Inside safe circumcircle, update circle. pt = u; circumCircle(pts,s*3, pts,t*3, pts,u*3, c, 0,ref r); } else { // Inside epsilon circum circle, do extra tests to make sure the edge is valid. // s-u and t-u cannot overlap with s-pt nor t-pt if they exists. if (overlapEdges(pts, edges, nedges, s,u)) continue; if (overlapEdges(pts, edges, nedges, t,u)) continue; // Edge is valid. pt = u; circumCircle(pts,s*3, pts,t*3, pts,u*3, c, 0, ref r); } } } // Add new triangle or update edge info if s-t is on hull. if (pt < npts) { // Update face information of edge being completed. updateLeftFace(edges,e*4, s, t, nfaces); // Add new edge or update face info of old edge. e = findEdge(edges, nedges, pt, s); if (e == EdgeValues.UNDEF) addEdge(ctx, edges, ref nedges, maxEdges, pt, s, nfaces, EdgeValues.UNDEF); else updateLeftFace(edges,e*4, pt, s, nfaces); // Add new edge or update face info of old edge. e = findEdge(edges, nedges, t, pt); if (e == EdgeValues.UNDEF) addEdge(ctx, edges, ref nedges, maxEdges, t, pt, nfaces, EdgeValues.UNDEF); else updateLeftFace(edges,e*4, t, pt, nfaces); nfaces++; } else { updateLeftFace(edges,e*4, s, t, EdgeValues.HULL); } } static void delaunayHull(rcContext ctx, int npts, float[] pts, int nhull, int[] hull, List tris, List edges) { int nfaces = 0; int nedges = 0; int maxEdges = npts*10; //edges.resize(maxEdges*4); edges.Capacity = maxEdges * 4; for (int i = 0, j = nhull-1; i < nhull; j=i++){ addEdge(ctx, edges, ref nedges, maxEdges, hull[j],hull[i], EdgeValues.HULL, EdgeValues.UNDEF); } int currentEdge = 0; while (currentEdge < nedges) { if (edges[currentEdge*4+2] == EdgeValues.UNDEF){ completeFacet(ctx, pts, npts, edges,ref nedges, maxEdges, ref nfaces, currentEdge); } if (edges[currentEdge*4+3] == EdgeValues.UNDEF){ completeFacet(ctx, pts, npts, edges, ref nedges, maxEdges, ref nfaces, currentEdge); } currentEdge++; } // Create tris //tris.resize(nfaces*4); tris.Capacity = nfaces*4; tris.Clear(); for (int i = 0; i < nfaces*4; ++i){ //tris[i] = -1; tris.Add(-1); } for (int i = 0; i < nedges; ++i) { //const int* e = &edges[i*4]; int edgeIndex = i*4; if (edges[edgeIndex + 3] >= 0) { // Left face //int* t = &tris[e[3]*4]; int tIndex = edges[edgeIndex +3]*4; if (tris[tIndex + 0] == -1) { tris[tIndex + 0] = edges[edgeIndex +0]; tris[tIndex + 1] = edges[edgeIndex +1]; } else if (tris[tIndex + 0] == edges[edgeIndex +1]) tris[tIndex + 2] = edges[edgeIndex +0]; else if (tris[tIndex + 1] == edges[edgeIndex +0]) tris[tIndex + 2] = edges[edgeIndex +1]; } if (edges[edgeIndex +2] >= 0) { // Right //int* t = &tris[e[2]*4]; int tIndex = edges[edgeIndex + 2]*4; if (tris[tIndex + 0] == -1) { tris[tIndex + 0] = edges[edgeIndex + 1]; tris[tIndex + 1] = edges[edgeIndex + 0]; } else if (tris[tIndex + 0] == edges[edgeIndex + 0]) tris[tIndex + 2] = edges[edgeIndex + 1]; else if (tris[tIndex + 1] == edges[edgeIndex + 1]) tris[tIndex + 2] = edges[edgeIndex + 0]; } } for (int i = 0; i < tris.Count/4; ++i) { //int* t = &tris[i*4]; int tIndex = i*4; if (tris[tIndex + 0] == -1 || tris[tIndex + 1] == -1 || tris[tIndex + 2] == -1) { ctx.log(rcLogCategory.RC_LOG_WARNING, "delaunayHull: Removing dangling face "+i+" [" + tris[tIndex + 0] + "," + tris[tIndex + 1] + "," + tris[tIndex + 2] + "]."); tris[tIndex + 0] = tris[tris.Count-4]; tris[tIndex + 1] = tris[tris.Count-3]; tris[tIndex + 2] = tris[tris.Count-2]; tris[tIndex + 3] = tris[tris.Count-1]; //tris.resize(tris.Count-4); //tris.Capacity = tris.Count - 4; tris.RemoveRange(tris.Count - 4, 4); --i; } } } public static float getJitterX(int i) { return (((i * 0x8da6b343) & 0xffff) / 65535.0f * 2.0f) - 1.0f; } public static float getJitterY(int i) { return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f; } static bool buildPolyDetail(rcContext ctx, float[] _in, int nin, float sampleDist, float sampleMaxError, rcCompactHeightfield chf,rcHeightPatch hp, float[] verts, ref int nverts, List tris, List edges, List samples) { const int MAX_VERTS = 127; const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts). const int MAX_VERTS_PER_EDGE = 32; float[] edge = new float[(MAX_VERTS_PER_EDGE+1)*3]; int[] hull = new int[MAX_VERTS]; int nhull = 0; nverts = 0; for (int i = 0; i < nin; ++i){ rcVcopy(verts,i*3, _in,i*3); } nverts = nin; float cs = chf.cs; 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]; int vjStart = j*3; int viStart = 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 (Math.Abs(_in[vjStart]-_in[viStart]) < 1e-6f) { if (_in[vjStart + 2] > _in[viStart + 2]) { rcSwap(ref vjStart,ref viStart); swapped = true; } } else { if (_in[vjStart] > _in[viStart]) { rcSwap(ref vjStart,ref viStart); swapped = true; } } // Create samples along the edge. float dx = _in[viStart] - _in[vjStart];//vi[0] - vj[0]; float dy = _in[viStart+1] - _in[vjStart+1];//vi[1] - vj[1]; float dz = _in[viStart+2] - _in[vjStart+2];//vi[2] - vj[2]; float d = (float)Math.Sqrt(dx*dx + dz*dz); int nn = 1 + (int)Math.Floor(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]; int posStart = k*3; edge[posStart + 0] = _in[vjStart + 0] + dx*u; edge[posStart + 1] = _in[vjStart + 1] + dy*u; edge[posStart + 2] = _in[vjStart + 2] + dz*u; edge[posStart + 1] = getHeight(edge[posStart + 0],edge[posStart + 1],edge[posStart + 2], cs, ics, chf.ch, hp)*chf.ch; } // Simplify samples. int[] idx = new int[MAX_VERTS_PER_EDGE];// {0,nn}; idx[1] = nn; int nidx = 2; for (int k = 0; k < nidx-1; ) { int a = idx[k]; int b = idx[k+1]; //float* va = &edge[a*3]; //float* vb = &edge[b*3]; int vaStart = a*3; int vbStart = b*3; // Find maximum deviation along the segment. float maxd = 0; int maxi = -1; for (int m = a+1; m < b; ++m) { int ptStart = m * 3; float dev = distancePtSeg(edge, ptStart, edge, vaStart,edge, vbStart); 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 > sampleMaxError * 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]); 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]); rcVcopy(verts,nverts*3,edge,idx[k]*3); hull[nhull++] = nverts; nverts++; } } } } // Tessellate the base mesh. //edges.resize(0); //tris.resize(0); edges.Clear(); tris.Clear(); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); if (tris.Count == 0) { // Could not triangulate the poly, make sure there is some valid data there. ctx.log(rcLogCategory.RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon, adding default data."); for (int i = 2; i < nverts; ++i) { tris.Add(0); tris.Add(i-1); tris.Add(i); tris.Add(0); } return true; } if (sampleDist > 0) { // Create sample locations in a grid. float[] bmin = new float[3]; float[] bmax = new float[3]; rcVcopy(bmin, _in); rcVcopy(bmax, _in); for (int i = 1; i < nin; ++i) { rcVmin(bmin, 0, _in,i*3); rcVmax(bmax, 0, _in,i*3); } int x0 = (int)Math.Floor(bmin[0]/sampleDist); int x1 = (int)Math.Ceiling(bmax[0]/sampleDist); int z0 = (int)Math.Floor(bmin[2]/sampleDist); int z1 = (int)Math.Ceiling(bmax[2]/sampleDist); //samples.resize(0); samples.Clear(); for (int z = z0; z < z1; ++z) { for (int x = x0; x < x1; ++x) { float[] pt = new float[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.Add(x); samples.Add(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, hp)); samples.Add(z); samples.Add(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. int nsamples = samples.Count/4; for (int iter = 0; iter < nsamples; ++iter) { if (nverts >= MAX_VERTS){ break; } // Find sample with most error. float[] bestpt = new float[] {0.0f,0.0f,0.0f}; float bestd = 0; int besti = -1; for (int i = 0; i < nsamples; ++i) { // int* s = &samples[i*4]; int sStart = i*4; if (samples[sStart + 3] != 0) continue; // skip added. float[] pt = new float[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] = samples[sStart + 0]*sampleDist + getJitterX(i)*cs*0.1f; pt[1] = samples[sStart + 1]*chf.ch; pt[2] = samples[sStart + 2]*sampleDist + getJitterY(i)*cs*0.1f; float d = distToTriMesh(pt, verts, nverts, tris, tris.Count/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,0); nverts++; // Create new triangulation. // TODO: Incremental add instead of full rebuild. //edges.resize(0); //tris.resize(0); edges.Clear(); tris.Clear(); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); } } int ntris = tris.Count/4; if (ntris > MAX_TRIS) { //tris.resize(MAX_TRIS*4); tris.RemoveRange(MAX_TRIS*4, tris.Count - MAX_TRIS*4); ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from "+ntris+" to max "+MAX_TRIS+"."); } return true; } static void getHeightDataSeedsFromVertices(rcCompactHeightfield chf, ushort[] poly, int polyStart, int npoly, ushort[] verts, int bs, rcHeightPatch hp, List stack) { // Floodfill the heightfield to get 2D height data, // starting at vertex locations as seeds. // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. //memset(hp.data, 0, sizeof(ushort)*hp.width*hp.height); for (int i=0;i= hp.xmin+hp.width || az < hp.ymin || az >= hp.ymin+hp.height) continue; rcCompactCell c = chf.cells[(ax+bs)+(az+bs)*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; int d = Math.Abs(ay - (int)s.y); if (d < dmin) { cx = ax; cz = az; ci = i; dmin = d; } } } if (ci != -1) { stack.Add(cx); stack.Add(cz); stack.Add(ci); } } // Find center of the polygon using flood fill. int pcx = 0, pcz = 0; for (int j = 0; j < npoly; ++j) { pcx += (int)verts[poly[polyStart + j]*3+0]; pcz += (int)verts[poly[polyStart + j]*3+2]; } pcx /= npoly; pcz /= npoly; for (int i = 0; i < stack.Count; i += 3) { int cx = stack[i+0]; int cy = stack[i+1]; int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; hp.data[idx] = 1; } while (stack.Count > 0) { int ci = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); int cy = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); int cx = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); // Check if close to center of the polygon. if (Math.Abs(cx-pcx) <= 1 && Math.Abs(cy-pcz) <= 1) { //stack.resize(0); stack.Clear(); stack.Add(cx); stack.Add(cy); stack.Add(ci); break; } rcCompactSpan cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; int ax = cx + rcGetDirOffsetX(dir); int ay = cy + rcGetDirOffsetY(dir); if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || ay < hp.ymin || ay >= (hp.ymin+hp.height)) continue; if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) continue; 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; hp.data[idx] = 1; stack.Add(ax); stack.Add(ay); stack.Add(ai); } } //memset(hp.data, 0xff, sizeof(ushort)*hp.width*hp.height); for (int i=0;i stack, int region) { // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. //stack.resize(0); //memset(hp.data, 0xff, sizeof(ushort)*hp.width*hp.height); stack.Clear(); for (int i=0;i= RETRACT_SIZE) { head = 0; if (stack.Count > RETRACT_SIZE*3){ //memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.Count-RETRACT_SIZE*3)); for (int i=0;i 0,"Resizing under zero"); stack.RemoveRange(newSize, stack.Count - newSize); } rcCompactSpan cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; int ax = cx + rcGetDirOffsetX(dir); int ay = cy + rcGetDirOffsetY(dir); int hx = ax - hp.xmin - bs; int hy = ay - hp.ymin - bs; if (hx < 0 || hx >= hp.width || hy < 0 || hy >= hp.height) continue; if (hp.data[hx + hy*hp.width] != RC_UNSET_HEIGHT) continue; int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir); rcCompactSpan aSpan = chf.spans[ai]; hp.data[hx + hy*hp.width] = aSpan.y; stack.Add(ax); stack.Add(ay); stack.Add(ai); } } } static byte getEdgeFlags(float[] va,float[] vb, float[] vpoly, int npoly) { // Return true if edge (va,vb) is part of the polygon. float thrSqr = 0.001f * 0.001f; for (int i = 0, j = npoly-1; i < npoly; j=i++) { if (distancePtSeg2d(va, 0, vpoly, j*3, vpoly, i*3) < thrSqr && distancePtSeg2d(vb, 0, vpoly, j*3, vpoly, i*3) < thrSqr) return 1; } return 0; } static byte getEdgeFlags(float[] va, int vaStart, float[] vb, int vbStart, float[] vpoly, int vpolyStart, int npoly) { // Return true if edge (va,vb) is part of the polygon. float thrSqr = 0.001f * 0.001f; for (int i = 0, j = npoly - 1; i < npoly; j = i++) { if (distancePtSeg2d(va, vaStart, vpoly, vpolyStart + j * 3, vpoly, vpolyStart + i * 3) < thrSqr && distancePtSeg2d(vb, vbStart, vpoly, vpolyStart + j * 3, vpoly, vpolyStart + i * 3) < thrSqr) return 1; } return 0; } static byte getTriFlags(float[] va, float[] vb, float[] vc, float[] vpoly, int npoly) { byte flags = 0; flags |= (byte)( getEdgeFlags(va,vb,vpoly,npoly) << 0); flags |= (byte)( getEdgeFlags(vb,vc,vpoly,npoly) << 2); flags |= (byte)( getEdgeFlags(vc,va,vpoly,npoly) << 4); return flags; } static byte getTriFlags(float[] va, int vaStart, float[] vb, int vbStart, float[] vc, int vcStart, float[] vpoly, int vpolyStart, int npoly) { byte flags = 0; flags |= (byte)(getEdgeFlags(va, vaStart, vb, vbStart, vpoly, vpolyStart, npoly) << 0); flags |= (byte)(getEdgeFlags(vb, vbStart, vc, vcStart, vpoly, vpolyStart, npoly) << 2); flags |= (byte)(getEdgeFlags(vc, vcStart, va, vaStart, vpoly, vpolyStart, npoly) << 4); return flags; } public static int rccsPop(List list) { //Let it crash if empty, so that we know there s a pb int ret = list[list.Count - 1]; list.RemoveAt(list.Count - 1); return ret; } public static void rccsResizeList(List list, int length) { if (length > list.Count){ for (int i = list.Count; i < length; ++i) { list.Add(0); } } else if (length < list.Count){ list.RemoveRange(length, list.Count - length); } } /// @par /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig public static bool rcBuildPolyMeshDetail(rcContext ctx, rcPolyMesh mesh, rcCompactHeightfield chf, float sampleDist, float sampleMaxError, rcPolyMeshDetail dmesh) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); if (mesh.nverts == 0 || mesh.npolys == 0) return true; int nvp = mesh.nvp; float cs = mesh.cs; float ch = mesh.ch; float[] orig = mesh.bmin; int borderSize = mesh.borderSize; List edges = new List(); List tris = new List(); List stack = new List(); List samples = new List(); edges.Capacity = 64; tris.Capacity = 512; stack.Capacity = 512; samples.Capacity = 512; float[] verts = new float[256*3]; rcHeightPatch hp = new rcHeightPatch(); int nPolyVerts = 0; int maxhw = 0, maxhh = 0; //rcScopedDelete bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP); int[] bounds = new int[mesh.npolys*4]; if (bounds == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' ("+ mesh.npolys*4+")."); return false; } //rcScopedDelete poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP); float[] poly = new float[nvp*3]; if (poly == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' ("+nvp*3+")."); return false; } // Find max size for a polygon area. for (int i = 0; i < mesh.npolys; ++i) { //ushort* p = &mesh.polys[i*nvp*2]; int pStart = i*nvp*2; //int& xmin = bounds[i*4+0]; //int& xmax = bounds[i*4+1]; //int& ymin = bounds[i*4+2]; //int& ymax = bounds[i*4+3]; int xmin = i*4+0; int xmax = i*4+1; int ymin = i*4+2; int ymax = i*4+3; bounds[xmin] = chf.width; bounds[xmax] = 0; bounds[ymin] = chf.height; bounds[ymax] = 0; for (int j = 0; j < nvp; ++j) { if(mesh.polys[pStart + j] == RC_MESH_NULL_IDX) break; //t ushort* v = &mesh.verts[p[j]*3]; int vIndex = mesh.polys[pStart + j] * 3; bounds[xmin] = Math.Min(bounds[xmin], (int)mesh.verts[vIndex + 0]); bounds[xmax] = Math.Max(bounds[xmax], (int)mesh.verts[vIndex + 0]); bounds[ymin] = Math.Min(bounds[ymin], (int)mesh.verts[vIndex + 2]); bounds[ymax] = Math.Max(bounds[ymax], (int)mesh.verts[vIndex + 2]); nPolyVerts++; } bounds[xmin] = Math.Max(0,bounds[xmin]-1); bounds[xmax] = Math.Min(chf.width,bounds[xmax]+1); bounds[ymin] = Math.Max(0,bounds[ymin]-1); bounds[ymax] = Math.Min(chf.height,bounds[ymax]+1); if (bounds[xmin] >= bounds[xmax] || bounds[ymin] >= bounds[ymax]) continue; maxhw = Math.Max(maxhw, bounds[xmax]-bounds[xmin]); maxhh = Math.Max(maxhh, bounds[ymax]-bounds[ymin]); } //hp.data = (ushort*)rcAlloc(sizeof(ushort)*maxhw*maxhh, RC_ALLOC_TEMP); hp.data = new ushort[maxhh*maxhw]; if (hp.data == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' ("+maxhw*maxhh+")."); return false; } dmesh.nmeshes = mesh.npolys; dmesh.nverts = 0; dmesh.ntris = 0; //dmesh.meshes = (uint*)rcAlloc(sizeof(uint)*dmesh.nmeshes*4, RC_ALLOC_PERM); dmesh.meshes = new uint[dmesh.nmeshes*4]; if (dmesh.meshes == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' ("+dmesh.nmeshes*4+")."); return false; } int vcap = nPolyVerts+nPolyVerts/2; int tcap = vcap*2; dmesh.nverts = 0; //dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); dmesh.verts = new float[vcap*3]; if (dmesh.verts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' ("+vcap*3+")."); return false; } dmesh.ntris = 0; //dmesh.tris = (byte*)rcAlloc(sizeof(byte*)*tcap*4, RC_ALLOC_PERM); dmesh.tris = new byte[tcap*4]; if (dmesh.tris == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' ("+tcap*4+")."); return false; } for (int i = 0; i < mesh.npolys; ++i) { //const ushort* p = &mesh.polys[i*nvp*2]; int pIndex = i*nvp*2; // Store polygon vertices for processing. int npoly = 0; for (int j = 0; j < nvp; ++j) { if(mesh.polys[pIndex + j] == RC_MESH_NULL_IDX) break; //const ushort* v = &mesh.verts[p[j]*3]; int vIndex = mesh.polys[pIndex + j] * 3; poly[j*3+0] = mesh.verts[vIndex + 0]*cs; poly[j*3+1] = mesh.verts[vIndex + 1]*ch; poly[j*3+2] = mesh.verts[vIndex + 2]*cs; npoly++; } // Get the height data from the area of the polygon. hp.xmin = bounds[i*4+0]; hp.ymin = bounds[i*4+2]; hp.width = bounds[i*4+1]-bounds[i*4+0]; hp.height = bounds[i*4+3]-bounds[i*4+2]; getHeightData(chf, mesh.polys, pIndex, npoly, mesh.verts, borderSize, hp, stack, mesh.regs[i]); // Build detail mesh. int nverts = 0; if (!buildPolyDetail(ctx, poly, npoly, sampleDist, sampleMaxError, chf, hp, verts, ref nverts, tris, edges, samples)) { return false; } // Move detail verts to world space. for (int j = 0; j < nverts; ++j) { verts[j*3+0] += orig[0]; verts[j*3+1] += orig[1] + chf.ch; // Is this offset necessary? verts[j*3+2] += orig[2]; } // Offset poly too, will be used to flag checking. for (int j = 0; j < npoly; ++j) { poly[j*3+0] += orig[0]; poly[j*3+1] += orig[1]; poly[j*3+2] += orig[2]; } // Store detail submesh. int ntris = tris.Count/4; dmesh.meshes[i*4+0] = (uint)dmesh.nverts; dmesh.meshes[i*4+1] = (uint)nverts; dmesh.meshes[i*4+2] = (uint)dmesh.ntris; dmesh.meshes[i*4+3] = (uint)ntris; // Store vertices, allocate more memory if necessary. if (dmesh.nverts+nverts > vcap) { while (dmesh.nverts+nverts > vcap){ vcap += 256; } //float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); float[] newv = new float[vcap*3]; if (newv == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' ("+vcap*3+")."); return false; } if (dmesh.nverts != 0){ //memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts); for (int j=0;j<3*dmesh.nverts;++j){ newv[j] = dmesh.verts[j]; } } //rcFree(dmesh.verts); //dmesh.verts = null; dmesh.verts = newv; } for (int j = 0; j < nverts; ++j) { dmesh.verts[dmesh.nverts*3+0] = verts[j*3+0]; dmesh.verts[dmesh.nverts*3+1] = verts[j*3+1]; dmesh.verts[dmesh.nverts*3+2] = verts[j*3+2]; dmesh.nverts++; } // Store triangles, allocate more memory if necessary. if (dmesh.ntris+ntris > tcap) { while (dmesh.ntris+ntris > tcap){ tcap += 256; } //byte* newt = (byte*)rcAlloc(sizeof(byte)*tcap*4, RC_ALLOC_PERM); byte[] newt = new byte[tcap*4]; if (newt == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' ("+tcap*4+")."); return false; } if (dmesh.ntris != 0){ //memcpy(newt, dmesh.tris, sizeof(byte)*4*dmesh.ntris); for (int j = 0;j<4*dmesh.ntris;++j){ newt[j] = dmesh.tris[j]; } } //rcFree(dmesh.tris); dmesh.tris = newt; } for (int j = 0; j < ntris; ++j) { //const int* t = &tris[j*4]; int tIndex = j*4; dmesh.tris[dmesh.ntris*4+0] = (byte)tris[tIndex + 0]; dmesh.tris[dmesh.ntris*4+1] = (byte)tris[tIndex + 1]; dmesh.tris[dmesh.ntris*4+2] = (byte)tris[tIndex + 2]; dmesh.tris[dmesh.ntris*4+3] = getTriFlags(verts, tris[tIndex + 0]*3, verts, tris[tIndex + 1]*3, verts, tris[tIndex + 2]*3, poly, 0, npoly); dmesh.ntris++; } } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); return true; } /// @see rcAllocPolyMeshDetail, rcPolyMeshDetail static bool rcMergePolyMeshDetails(rcContext ctx, rcPolyMeshDetail[] meshes, int nmeshes, ref rcPolyMeshDetail mesh) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_MERGE_POLYMESHDETAIL); int maxVerts = 0; int maxTris = 0; int maxMeshes = 0; for (int i = 0; i < nmeshes; ++i) { if (meshes[i] == null) { continue; } maxVerts += meshes[i].nverts; maxTris += meshes[i].ntris; maxMeshes += meshes[i].nmeshes; } mesh.nmeshes = 0; //mesh.meshes = (uint*)rcAlloc(sizeof(uint)*maxMeshes*4, RC_ALLOC_PERM); mesh.meshes = new uint[maxMeshes*4]; if (mesh.meshes == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' ("+maxMeshes*4+")."); return false; } mesh.ntris = 0; //mesh.tris = (byte*)rcAlloc(sizeof(byte)*maxTris*4, RC_ALLOC_PERM); mesh.tris = new byte[maxTris*4]; if (mesh.tris == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (" + maxTris*4 + ")."); return false; } mesh.nverts = 0; //mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM); mesh.verts = new float[maxVerts*3]; if (mesh.verts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' ("+maxVerts*3+")."); return false; } // Merge datas. for (int i = 0; i < nmeshes; ++i) { rcPolyMeshDetail dm = meshes[i]; if (dm == null) { continue; } for (int j = 0; j < dm.nmeshes; ++j) { //uint* dst = &mesh.meshes[mesh.nmeshes*4]; //uint* src = &dm.meshes[j*4]; int dstIndex = mesh.nmeshes*4; int srcIndex = j*4; mesh.meshes[dstIndex + 0] = (uint)mesh.nverts + dm.meshes[srcIndex + 0]; mesh.meshes[dstIndex + 1] = dm.meshes[srcIndex + 1]; mesh.meshes[dstIndex + 2] = (uint)mesh.ntris + dm.meshes[srcIndex + 2]; mesh.meshes[dstIndex + 3] = dm.meshes[srcIndex + 3]; mesh.nmeshes++; } for (int k = 0; k < dm.nverts; ++k) { rcVcopy(mesh.verts,mesh.nverts*3, dm.verts, k*3); mesh.nverts++; } for (int k = 0; k < dm.ntris; ++k) { mesh.tris[mesh.ntris*4+0] = dm.tris[k*4+0]; mesh.tris[mesh.ntris*4+1] = dm.tris[k*4+1]; mesh.tris[mesh.ntris*4+2] = dm.tris[k*4+2]; mesh.tris[mesh.ntris*4+3] = dm.tris[k*4+3]; mesh.ntris++; } } ctx.stopTimer(rcTimerLabel.RC_TIMER_MERGE_POLYMESHDETAIL); return true; } }