a3dc7b3f48
hondacrx: - Initial commit: Switch to .Net Core 2.0 - Fix build and removed not needed files Fabi: - Updated solution platforms. - Changed folder structure. - Change library target framework to netstandard2.0. - Updated solution platforms again... - Removed windows specific kernel32 function usage (Ctrl-C handler).
1651 lines
50 KiB
C#
1651 lines
50 KiB
C#
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<int> 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<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 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<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 (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<int> 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<int> 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<int> 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<int> 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<int> tris, List<int> 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<int> tris,
|
|
List<int> edges, List<int> 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<int> 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.data.Length;++i){
|
|
hp.data[i] = 0;
|
|
}
|
|
|
|
//tack.resize(0);
|
|
stack.Clear();
|
|
|
|
int[] offset = new int[9*2]
|
|
{
|
|
0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0,
|
|
};
|
|
|
|
// Use poly vertices as seed points for the flood fill.
|
|
for (int j = 0; j < npoly; ++j)
|
|
{
|
|
int cx = 0, cz = 0, ci =-1;
|
|
int dmin = RC_UNSET_HEIGHT;
|
|
for (int k = 0; k < 9; ++k)
|
|
{
|
|
int ax = (int)verts[poly[polyStart + j]*3+0] + offset[k*2+0];
|
|
int ay = (int)verts[poly[polyStart + j]*3+1];
|
|
int az = (int)verts[poly[polyStart + j]*3+2] + offset[k*2+1];
|
|
if (ax < hp.xmin || ax >= hp.xmin+hp.width ||
|
|
az < hp.ymin || az >= hp.ymin+hp.height)
|
|
continue;
|
|
|
|
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<hp.data.Length;++i){
|
|
hp.data[i] = 0xffff;
|
|
}
|
|
|
|
// Mark start locations.
|
|
for (int i = 0; i < stack.Count; i += 3)
|
|
{
|
|
int cx = stack[i+0];
|
|
int cy = stack[i+1];
|
|
int ci = stack[i+2];
|
|
int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width;
|
|
rcCompactSpan cs = chf.spans[ci];
|
|
hp.data[idx] = cs.y;
|
|
|
|
// getHeightData seeds are given in coordinates with borders
|
|
stack[i+0] += bs;
|
|
stack[i+1] += bs;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void getHeightData(rcCompactHeightfield chf,
|
|
ushort[] poly, int polyStart, int npoly,
|
|
ushort[] verts, int bs,
|
|
rcHeightPatch hp, List<int> 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<hp.data.Length;++i){
|
|
hp.data[i] = 0xffff;
|
|
}
|
|
bool empty = true;
|
|
|
|
// Copy the height from the same region, and mark region borders
|
|
// as seed points to fill the rest.
|
|
for (int hy = 0; hy < hp.height; hy++)
|
|
{
|
|
int y = hp.ymin + hy + bs;
|
|
for (int hx = 0; hx < hp.width; hx++)
|
|
{
|
|
int x = hp.xmin + hx + bs;
|
|
rcCompactCell c = chf.cells[x+y*chf.width];
|
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
|
{
|
|
rcCompactSpan s = chf.spans[i];
|
|
if (s.reg == region)
|
|
{
|
|
// Store height
|
|
hp.data[hx + hy*hp.width] = s.y;
|
|
empty = false;
|
|
|
|
// If any of the neighbours is not in same region,
|
|
// add the current location as flood fill start
|
|
bool border = false;
|
|
for (int dir = 0; dir < 4; ++dir)
|
|
{
|
|
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
|
|
{
|
|
int ax = x + rcGetDirOffsetX(dir);
|
|
int ay = y + rcGetDirOffsetY(dir);
|
|
int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);
|
|
rcCompactSpan aSpan = chf.spans[ai];
|
|
if (aSpan.reg != region)
|
|
{
|
|
border = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (border)
|
|
{
|
|
stack.Add(x);
|
|
stack.Add(y);
|
|
stack.Add(i);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the polygon does not contain 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
|
|
if (empty){
|
|
getHeightDataSeedsFromVertices(chf, poly, polyStart, npoly, verts, bs, hp, stack);
|
|
}
|
|
|
|
const int RETRACT_SIZE = 256;
|
|
int head = 0;
|
|
|
|
while (head*3 < stack.Count)
|
|
{
|
|
int cx = stack[head*3+0];
|
|
int cy = stack[head*3+1];
|
|
int ci = stack[head*3+2];
|
|
head++;
|
|
if (head >= 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<stack.Count - RETRACT_SIZE*3;++i){
|
|
stack[i] = stack[RETRACT_SIZE*3 + i];
|
|
}
|
|
}
|
|
//stack.resize(stack.Count-RETRACT_SIZE*3);
|
|
int newSize =stack.Count - RETRACT_SIZE*3;
|
|
Debug.Assert(newSize > 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<int> 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<int> 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<int> edges = new List<int>();
|
|
List<int> tris = new List<int>();
|
|
List<int> stack = new List<int>();
|
|
List<int> samples = new List<int>();
|
|
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<int> 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<float> 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;
|
|
}
|
|
}
|
|
|