private bool BuildPolyDetail(float[] inArray, int nin, float sampleDist, float sampleMaxError, CompactHeightfield chf, HeightPatch hp, ref float[] verts, out int nverts, ref IntArray tris, ref IntArray edges, ref IntArray samples)
        {
            int MaxVerts        = 127;
            int MaxTris         = 255;
            int MaxVertsPerEdge = 32;

            float[] edge  = new float[(MaxVertsPerEdge + 1) * 3];
            int[]   hull  = new int[MaxVerts];
            int     nhull = 0;

            nverts = 0;

            for (int i = 0; i < nin; i++)
            {
                Array.Copy(inArray, i * 3, verts, i * 3, 3);
            }

            nverts = nin;

            float cs  = chf.Cs;
            float ics = 1.0f / cs;

            if (sampleDist > 0)
            {
                for (int i = 0, j = nin - 1; i < nin; j = i++)
                {
                    int vj = j * 3;
                    int vi = i * 3;

                    bool swapped = false;
                    if (Math.Abs(inArray[vj + 0] - inArray[vi + 0]) < 1e-6f)
                    {
                        if (inArray[vj + 2] > inArray[vi + 2])
                        {
                            int temp = vj;
                            vj      = vi;
                            vi      = temp;
                            swapped = true;
                        }
                    }
                    else
                    {
                        if (inArray[vj + 0] > inArray[vi + 0])
                        {
                            int temp = vj;
                            vj      = vi;
                            vi      = temp;
                            swapped = true;
                        }
                    }

                    float dx = inArray[vi + 0] - inArray[vj + 0];
                    float dy = inArray[vi + 1] - inArray[vj + 1];
                    float dz = inArray[vi + 2] - inArray[vj + 2];
                    float d  = (float)Math.Sqrt(dx * dx + dz * dz);
                    int   nn = 1 + (int)Math.Floor(d / sampleDist);
                    if (nn >= MaxVertsPerEdge)
                    {
                        nn = MaxVertsPerEdge - 1;
                    }
                    if (nverts + nn >= MaxVerts)
                    {
                        nn = MaxVerts - 1 - nverts;
                    }

                    for (int k = 0; k <= nn; k++)
                    {
                        float u   = (float)k / (float)nn;
                        int   pos = k * 3;
                        edge[pos + 0] = inArray[vj + 0] + dx * u;
                        edge[pos + 1] = inArray[vj + 1] + dy * u;
                        edge[pos + 2] = inArray[vj + 2] + dz * u;
                        edge[pos + 1] = GetHeight(edge[pos + 0], edge[pos + 1], edge[pos + 2], cs, ics, chf.Ch, hp) * chf.Ch;
                    }

                    int[] idx = new int[MaxVertsPerEdge];
                    idx[0] = 0; idx[1] = nn;
                    int nidx = 2;
                    for (int k = 0; k < nidx - 1;)
                    {
                        int a  = idx[k];
                        int b  = idx[k + 1];
                        int va = a * 3;
                        int vb = b * 3;

                        float maxd = 0;
                        int   maxi = -1;
                        for (int m = a + 1; m < b; m++)
                        {
                            float dev = DistancePtSeg(edge[m * 3 + 0], edge[m * 3 + 1], edge[m * 3 + 2], edge[va + 0], edge[va + 1], edge[va + 2], edge[vb + 0], edge[vb + 1], edge[vb + 2]);
                            if (dev > maxd)
                            {
                                maxd = dev;
                                maxi = m;
                            }
                        }

                        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;
                    if (swapped)
                    {
                        for (int k = nidx - 2; k > 0; k--)
                        {
                            Array.Copy(edge, idx[k] * 3, verts, nverts * 3, 3);
                            hull[nhull++] = nverts;
                            nverts++;
                        }
                    }
                    else
                    {
                        for (int k = 1; k < nidx - 1; k++)
                        {
                            Array.Copy(edge, idx[k] * 3, verts, nverts * 3, 3);
                            hull[nhull++] = nverts;
                            nverts++;
                        }
                    }
                }
            }

            edges.Resize(0);
            tris.Resize(0);

            DelaunayHull(nverts, verts, nhull, hull, ref tris, ref edges);

            if (tris.Size == 0)
            {
                // error add default data
                for (int i = 2; i < nverts; i++)
                {
                    tris.Push(0);
                    tris.Push(i - 1);
                    tris.Push(i);
                    tris.Push(0);
                }
                return(true);
            }

            if (sampleDist > 0)
            {
                float[] bmin = new float[3], bmax = new float[3];
                Array.Copy(inArray, 0, bmin, 0, 3);
                Array.Copy(inArray, 0, bmax, 0, 3);

                for (int i = 1; i < nin; i++)
                {
                    bmin[0] = Math.Min(bmin[0], inArray[i * 3 + 0]);
                    bmin[1] = Math.Min(bmin[1], inArray[i * 3 + 1]);
                    bmin[2] = Math.Min(bmin[2], inArray[i * 3 + 2]);
                    bmax[0] = Math.Max(bmax[0], inArray[i * 3 + 0]);
                    bmax[1] = Math.Max(bmax[1], inArray[i * 3 + 1]);
                    bmax[2] = Math.Max(bmax[2], inArray[i * 3 + 2]);
                }

                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);
                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;

                        if (DistToPoly(nin, inArray, pt[0], pt[1], pt[2]) > -sampleDist / 2)
                        {
                            continue;
                        }
                        samples.Push(x);
                        samples.Push(GetHeight(pt[0], pt[1], pt[2], cs, ics, chf.Ch, hp));
                        samples.Push(z);
                        samples.Push(0);
                    }
                }

                int nsamples = samples.Size / 4;
                for (int iter = 0; iter < nsamples; iter++)
                {
                    if (nverts >= MaxVerts)
                    {
                        break;
                    }

                    float[] bestpt = { 0, 0, 0 };
                    float   bestd  = 0;
                    int     besti  = -1;
                    for (int i = 0; i < nsamples; i++)
                    {
                        int s = i * 4;
                        if (samples[s + 3] != 0)
                        {
                            continue;
                        }
                        float[] pt = new float[3];

                        pt[0] = samples[s + 0] * sampleDist + GetJitterX(i) * cs * 0.1f;
                        pt[1] = samples[s + 1] * chf.Ch;
                        pt[2] = samples[s + 2] * sampleDist + GetJitterY(i) * cs * 0.1f;
                        float d = DistToTriMesh(pt[0], pt[1], pt[2], verts, nverts, tris, tris.Size / 4);
                        if (d < 0)
                        {
                            continue;        // didn't hit the mesh
                        }
                        if (d > bestd)
                        {
                            bestd = d;
                            besti = i;
                            Array.Copy(pt, 0, bestpt, 0, 3);
                        }
                    }
                    if (bestd <= sampleMaxError || besti == -1)
                    {
                        break;
                    }
                    samples[besti * 4 + 3] = 1;
                    Array.Copy(bestpt, 0, verts, nverts * 3, 3);
                    nverts++;

                    edges.Resize(0);
                    tris.Resize(0);
                    DelaunayHull(nverts, verts, nhull, hull, ref tris, ref edges);
                }
            }

            int ntris = tris.Size / 4;

            if (ntris > MaxTris)
            {
                // error, shrink
                tris.Resize(MaxTris * 4);
            }
            return(true);
        }
        public DetailPolyMesh(PolyMesh mesh, CompactHeightfield chf, float sampleDist, float sampleMaxError)
        {
            if (mesh.NVerts == 0 || mesh.NPolys == 0)
            {
                return;
            }

            int   nvp = mesh.Nvp;
            float cs  = mesh.Cs;
            float ch  = mesh.Ch;

            float[] orig       = mesh.BMin;
            int     borderSize = mesh.BorderSize;

            IntArray edges   = new IntArray(64);
            IntArray tris    = new IntArray(512);
            IntArray stack   = new IntArray(512);
            IntArray samples = new IntArray(512);

            float[]     verts = new float[256 * 3];
            HeightPatch hp = new HeightPatch();
            int         nPolyVerts = 0;
            int         maxhw = 0, maxhh = 0;

            int[]   bounds = new int[mesh.NPolys * 4];
            float[] poly   = new float[nvp * 3];

            for (int i = 0; i < mesh.NPolys; i++)
            {
                int p    = i * nvp * 2;
                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[p + j] == PolyMesh.MeshNullIdx)
                    {
                        break;
                    }
                    int v = mesh.Polys[p + j] * 3;
                    bounds[xmin] = Math.Min(bounds[xmin], mesh.Verts[v + 0]);
                    bounds[xmax] = Math.Max(bounds[xmax], mesh.Verts[v + 0]);
                    bounds[ymin] = Math.Min(bounds[ymin], mesh.Verts[v + 2]);
                    bounds[ymax] = Math.Max(bounds[ymax], mesh.Verts[v + 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 = new int[maxhw * maxhh];

            NMeshes = mesh.NPolys;
            //NVerts = 0;
            //NTris = 0;
            Meshes = new long[NMeshes * 4];

            int vcap = nPolyVerts + nPolyVerts / 2;
            int tcap = vcap * 2;

            NVerts = 0;
            Verts  = new float[vcap * 3];
            NTris  = 0;
            Tris   = new short[tcap * 4];
            int nverts;

            for (int i = 0; i < mesh.NPolys; i++)
            {
                int p     = i * nvp * 2;
                int npoly = 0;
                for (int j = 0; j < nvp; j++)
                {
                    if (mesh.Polys[p + j] == PolyMesh.MeshNullIdx)
                    {
                        break;
                    }
                    int v = mesh.Polys[p + j] * 3;
                    poly[j * 3 + 0] = mesh.Verts[v + 0] * cs;
                    poly[j * 3 + 1] = mesh.Verts[v + 1] * ch;
                    poly[j * 3 + 2] = mesh.Verts[v + 2] * cs;
                    npoly++;
                }

                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];

                int[] tempPoly = new int[nvp];
                Array.Copy(mesh.Polys, p, tempPoly, 0, nvp);

                GetHeightData(chf, tempPoly, npoly, mesh.Verts, borderSize, ref hp, ref stack);

                if (!BuildPolyDetail(poly, npoly, sampleDist, sampleMaxError, chf, hp, ref verts, out nverts, ref tris, ref edges, ref samples))
                {
                    return;
                }

                for (int j = 0; j < nverts; j++)
                {
                    verts[j * 3 + 0] += orig[0];
                    verts[j * 3 + 1] += orig[1] + chf.Ch;
                    verts[j * 3 + 2] += orig[2];
                }
                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];
                }

                int ntris = tris.Size / 4;

                Meshes[i * 4 + 0] = NVerts;
                Meshes[i * 4 + 1] = nverts;
                Meshes[i * 4 + 2] = NTris;
                Meshes[i * 4 + 3] = ntris;

                if (NVerts + nverts > vcap)
                {
                    while (NVerts + nverts > vcap)
                    {
                        vcap += 256;
                    }

                    float[] newv = new float[vcap * 3];
                    if (NVerts > 0)
                    {
                        Array.Copy(Verts, newv, 3 * NVerts);
                    }

                    Verts = newv;
                }

                for (int j = 0; j < nverts; j++)
                {
                    Verts[NVerts * 3 + 0] = verts[j * 3 + 0];
                    Verts[NVerts * 3 + 1] = verts[j * 3 + 1];
                    Verts[NVerts * 3 + 2] = verts[j * 3 + 2];
                    NVerts++;
                }

                if (NTris + ntris > tcap)
                {
                    while (NTris + ntris > tcap)
                    {
                        tcap += 256;
                    }
                    short[] newt = new short[tcap * 4];
                    if (NTris > 0)
                    {
                        Array.Copy(Tris, newt, 4 * NTris);
                    }

                    Tris = newt;
                }
                for (int j = 0; j < ntris; j++)
                {
                    int t = j * 4;
                    Tris[NTris * 4 + 0] = (short)tris[t + 0];
                    Tris[NTris * 4 + 1] = (short)tris[t + 1];
                    Tris[NTris * 4 + 2] = (short)tris[t + 2];
                    Tris[NTris * 4 + 3] = GetTriFlags(verts, tris[t + 0] * 3, verts, tris[t + 1] * 3, verts, tris[t + 2] * 3, poly, npoly);
                    NTris++;
                }
            }
        }
        private void GetHeightData(CompactHeightfield chf, int[] p, int npoly, int[] verts, int bs, ref HeightPatch hp, ref IntArray stack)
        {
            for (int i = 0; i < hp.Width * hp.Height; i++)
            {
                hp.Data[i] = 0;
            }

            stack.Resize(0);

            int[] offset = { 0, 0, -1, -1, 0, -1, 1, -1, 1, 0, 1, 1, 0, 1, -1, 1, -1, 0 };

            for (int j = 0; j < npoly; j++)
            {
                int cx = 0, cz = 0, ci = -1;

                int dmin = UnsetHeight;
                for (int k = 0; k < 9; k++)
                {
                    int ax = verts[p[j] * 3 + 0] + offset[k * 2 + 0];
                    int ay = verts[p[j] * 3 + 1];
                    int az = verts[p[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;
                    }

                    CompactCell c = chf.Cells[(ax + bs) + (az + bs) * chf.Width];
                    for (int i = (int)c.Index, ni = (int)(c.Index + c.Count); i < ni; i++)
                    {
                        CompactSpan s = chf.Spans[i];
                        int         d = Math.Abs(ay - s.Y);
                        if (d < dmin)
                        {
                            cx   = ax;
                            cz   = az;
                            ci   = i;
                            dmin = d;
                        }
                    }
                }
                if (ci != -1)
                {
                    stack.Push(cx);
                    stack.Push(cz);
                    stack.Push(ci);
                }
            }

            int pcx = 0, pcz = 0;

            for (int j = 0; j < npoly; j++)
            {
                pcx += verts[p[j] * 3 + 0];
                pcz += verts[p[j] * 3 + 2];
            }
            pcx /= npoly;
            pcz /= npoly;

            for (int i = 0; i < stack.Size; 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.Size > 0)
            {
                int ci = stack.Pop();
                int cy = stack.Pop();
                int cx = stack.Pop();

                if (Math.Abs(cx - pcx) <= 1 && Math.Abs(cy - pcz) <= 1)
                {
                    stack.Resize(0);
                    stack.Push(cx);
                    stack.Push(cy);
                    stack.Push(ci);
                    break;
                }

                CompactSpan cs = chf.Spans[ci];

                for (int dir = 0; dir < 4; dir++)
                {
                    if (cs.GetCon(dir) == CompactHeightfield.NotConnected)
                    {
                        continue;
                    }

                    int ax = cx + Helper.GetDirOffsetX(dir);
                    int ay = cy + Helper.GetDirOffsetY(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 + cs.GetCon(dir);

                    int idx = ax - hp.XMin + (ay - hp.YMin) * hp.Width;
                    hp.Data[idx] = 1;

                    stack.Push(ax);
                    stack.Push(ay);
                    stack.Push(ai);
                }
            }

            for (int i = 0; i < hp.Data.Length; i++)
            {
                hp.Data[i] = UnsetHeight;
            }

            for (int i = 0; i < stack.Size; 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;
                CompactSpan cs  = chf.Spans[ci];
                hp.Data[idx] = cs.Y;
            }

            int RetractSize = 256;
            int head        = 0;

            while (head * 3 < stack.Size)
            {
                int cx = stack[head * 3 + 0];
                int cy = stack[head * 3 + 1];
                int ci = stack[head * 3 + 2];
                head++;
                if (head >= RetractSize)
                {
                    head = 0;
                    if (stack.Size > RetractSize * 3)
                    {
                        Array.Copy(stack.Data, RetractSize * 3, stack.Data, 0, stack.Size - RetractSize * 3);
                    }
                    stack.Resize(stack.Size - RetractSize * 3);
                }

                CompactSpan cs = chf.Spans[ci];
                for (int dir = 0; dir < 4; dir++)
                {
                    if (cs.GetCon(dir) == CompactHeightfield.NotConnected)
                    {
                        continue;
                    }

                    int ax = cx + Helper.GetDirOffsetX(dir);
                    int ay = cy + Helper.GetDirOffsetY(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] != UnsetHeight)
                    {
                        continue;
                    }

                    int ai = (int)chf.Cells[(ax + bs) + (ay + bs) * chf.Width].Index + cs.GetCon(dir);

                    CompactSpan aspan = chf.Spans[ai];
                    int         idx   = ax - hp.XMin + (ay - hp.YMin) * hp.Width;
                    hp.Data[idx] = aspan.Y;

                    stack.Push(ax);
                    stack.Push(ay);
                    stack.Push(ai);
                }
            }
        }
        public ContourSet(CompactHeightfield cfh, float maxError, int maxEdgeLen, int buildFlags = BuildContourFlags.ContourTessWallEdges)
        {
            int w          = cfh.Width;
            int h          = cfh.Height;
            int borderSize = cfh.BorderSize;

            BMin = new float[3];
            BMax = new float[3];
            Array.Copy(cfh.BMin, BMin, 3);
            Array.Copy(cfh.BMax, BMax, 3);

            if (borderSize > 0)
            {
                float pad = borderSize * cfh.Cs;
                BMin[0] += pad;
                BMin[2] += pad;
                BMax[0] -= pad;
                BMax[2] -= pad;
            }

            Cs         = cfh.Cs;
            Ch         = cfh.Ch;
            Width      = cfh.Width - cfh.BorderSize * 2;
            Height     = cfh.Height - cfh.BorderSize * 2;
            BorderSize = cfh.BorderSize;
            int maxContours = Math.Max(cfh.MaxRegions, 8);

            Conts = new Contour[maxContours];
            for (int i = 0; i < maxContours; i++)
            {
                Conts[i] = new Contour();
            }
            NConts = 0;

            char[] flags = new char[cfh.SpanCount];

            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    CompactCell c = cfh.Cells[x + y * w];
                    for (int i = (int)c.Index, ni = (int)(c.Index + c.Count); i < ni; i++)
                    {
                        if (i == 4782)
                        {
                            int z = 0;
                        }
                        int         res = 0;
                        CompactSpan s   = cfh.Spans[i];
                        if (s.Reg == 0 || (s.Reg & CompactHeightfield.BorderReg) != 0)
                        {
                            flags[i] = (char)0;
                            continue;
                        }
                        for (int dir = 0; dir < 4; dir++)
                        {
                            int r = 0;
                            if (s.GetCon(dir) != CompactHeightfield.NotConnected)
                            {
                                int ax = x + Helper.GetDirOffsetX(dir);
                                int ay = y + Helper.GetDirOffsetY(dir);
                                int ai = (int)cfh.Cells[ax + ay * w].Index + s.GetCon(dir);
                                r = cfh.Spans[ai].Reg;
                            }
                            if (r == cfh.Spans[i].Reg)
                            {
                                res |= (1 << dir);
                            }
                        }
                        flags[i] = (char)(res ^ 0xf);
                    }
                }
            }

            IntArray verts      = new IntArray(256);
            IntArray simplified = new IntArray(64);

            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    CompactCell c = cfh.Cells[x + y * w];
                    for (int i = (int)c.Index, ni = (int)(c.Index + c.Count); i < ni; i++)
                    {
                        if (flags[i] == 0 || flags[i] == 0xf)
                        {
                            flags[i] = (char)0;
                            continue;
                        }

                        int reg = cfh.Spans[i].Reg;
                        if (reg == 0 || (reg & CompactHeightfield.BorderReg) != 0)
                        {
                            continue;
                        }
                        uint area = cfh.Areas[i];

                        verts.Resize(0);
                        simplified.Resize(0);

                        WalkContour(x, y, i, cfh, ref flags, ref verts);

                        SimplifyContour(ref verts, ref simplified, maxError, maxEdgeLen, buildFlags);
                        RemoveDegenerateSegments(ref simplified);

                        if ((simplified.Size / 4) >= 3)
                        {
                            // We need more space than we allocated...
                            if (NConts >= maxContours)
                            {
                                int oldMax = maxContours;
                                maxContours *= 2;
                                Contour[] newConts = new Contour[maxContours];
                                for (int j = 0; j < maxContours; j++)
                                {
                                    newConts[j] = new Contour();
                                }
                                for (int j = 0; j < NConts; j++)
                                {
                                    newConts[j] = Conts[j];
                                }
                                Conts = newConts;
                            }
                            Contour cont = Conts[NConts++];
                            cont.NVerts = simplified.Size / 4;
                            cont.Verts  = new int[cont.NVerts * 4];
                            Array.Copy(simplified.ToArray(), cont.Verts, cont.NVerts * 4);
                            if (borderSize > 0)
                            {
                                for (int j = 0; j < cont.NVerts; j++)
                                {
                                    int v = j * 4;
                                    cont.Verts[v + 0] -= borderSize;
                                    cont.Verts[v + 2] -= borderSize;
                                }
                            }

                            cont.NRVerts = verts.Size / 4;
                            cont.RVerts  = new int[cont.NRVerts * 4];
                            Array.Copy(verts.ToArray(), cont.RVerts, cont.NRVerts * 4);
                            if (borderSize > 0)
                            {
                                for (int j = 0; j < cont.NRVerts; j++)
                                {
                                    int v = j * 4;
                                    cont.RVerts[v + 0] -= borderSize;
                                    cont.RVerts[v + 2] -= borderSize;
                                }
                            }

                            cont.Reg  = reg;
                            cont.Area = (short)area;
                        }
                    }
                }
            }

            // check and merge droppings
            for (int i = 0; i < NConts; i++)
            {
                Contour cont = Conts[i];
                if (CalcAreaOfPolygon2D(cont.Verts, cont.NVerts) < 0)
                {
                    int mergeIdx = -1;
                    for (int j = 0; j < NConts; j++)
                    {
                        if (i == j)
                        {
                            continue;
                        }
                        if (Conts[j].NVerts > 0 && Conts[j].Reg == cont.Reg)
                        {
                            if (CalcAreaOfPolygon2D(Conts[j].Verts, Conts[j].NVerts) > 0)
                            {
                                mergeIdx = j;
                                break;
                            }
                        }
                    }
                    if (mergeIdx == -1)
                    {
                        // error
                    }
                    else
                    {
                        Contour mcont = Conts[mergeIdx];
                        int     ia = 0, ib = 0;
                        GetClosestIndices(mcont.Verts, mcont.NVerts, cont.Verts, cont.NVerts, ref ia, ref ib);
                        if (ia == -1 || ib == -1)
                        {
                            // bad merge
                            continue;
                        }
                        if (!MergeContours(ref mcont, ref cont, ia, ib))
                        {
                            // Merge failed
                            continue;
                        }
                    }
                }
            }
        }
        private int GetCornerHeight(int x, int y, int i, int dir, CompactHeightfield cfh, ref bool isBorderVertex)
        {
            CompactSpan s    = cfh.Spans[i];
            int         ch   = s.Y;
            int         dirp = (dir + 1) & 0x3;

            uint[] regs = { 0, 0, 0, 0 };

            regs[0] = (uint)(cfh.Spans[i].Reg | (cfh.Areas[i] << 16));

            if (s.GetCon(dir) != CompactHeightfield.NotConnected)
            {
                int         ax    = x + Helper.GetDirOffsetX(dir);
                int         ay    = y + Helper.GetDirOffsetY(dir);
                int         ai    = (int)cfh.Cells[ax + ay * cfh.Width].Index + s.GetCon(dir);
                CompactSpan aspan = cfh.Spans[ai];
                ch      = Math.Max(ch, aspan.Y);
                regs[1] = (uint)(cfh.Spans[ai].Reg | (cfh.Areas[ai] << 16));
                if (aspan.GetCon(dirp) != CompactHeightfield.NotConnected)
                {
                    int         ax2 = ax + Helper.GetDirOffsetX(dirp);
                    int         ay2 = ay + Helper.GetDirOffsetY(dirp);
                    int         ai2 = (int)cfh.Cells[ax2 + ay2 * cfh.Width].Index + aspan.GetCon(dirp);
                    CompactSpan as2 = cfh.Spans[ai2];
                    ch      = Math.Max(ch, as2.Y);
                    regs[2] = (uint)(cfh.Spans[ai2].Reg | (cfh.Areas[ai2] << 16));
                }
            }
            if (s.GetCon(dirp) != CompactHeightfield.NotConnected)
            {
                int         ax    = x + Helper.GetDirOffsetX(dirp);
                int         ay    = y + Helper.GetDirOffsetY(dirp);
                int         ai    = (int)cfh.Cells[ax + ay * cfh.Width].Index + s.GetCon(dirp);
                CompactSpan aspan = cfh.Spans[ai];
                ch      = Math.Max(ch, aspan.Y);
                regs[3] = (uint)(cfh.Spans[ai].Reg | (cfh.Areas[ai] << 16));
                if (aspan.GetCon(dir) != CompactHeightfield.NotConnected)
                {
                    int         ax2 = ax + Helper.GetDirOffsetX(dir);
                    int         ay2 = ay + Helper.GetDirOffsetY(dir);
                    int         ai2 = (int)cfh.Cells[ax2 + ay2 * cfh.Width].Index + aspan.GetCon(dir);
                    CompactSpan as2 = cfh.Spans[ai2];
                    ch      = Math.Max(ch, as2.Y);
                    regs[2] = (uint)(cfh.Spans[ai2].Reg | (cfh.Areas[ai2] << 16));
                }
            }

            for (int j = 0; j < 4; j++)
            {
                int a = j;
                int b = (j + 1) & 0x3;
                int c = (j + 2) & 0x3;
                int d = (j + 3) & 0x3;

                bool twoSameExts  = (regs[a] & regs[b] & CompactHeightfield.BorderReg) != 0 && regs[a] == regs[b];
                bool twoInts      = ((regs[c] | regs[d]) & CompactHeightfield.BorderReg) == 0;
                bool intsSameArea = (regs[c] >> 16) == (regs[d] >> 16);
                bool noZeros      = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0;
                if (twoSameExts && twoInts && intsSameArea && noZeros)
                {
                    isBorderVertex = true;
                    break;
                }
            }

            return(ch);
        }
        private void WalkContour(int x, int y, int i, CompactHeightfield cfh, ref char[] flags, ref IntArray points)
        {
            char dir = (char)0;

            while ((flags[i] & (1 << dir)) == 0)
            {
                dir++;
            }

            char startDir = dir;
            char tempDir  = dir;
            int  starti   = i;

            uint area = cfh.Areas[i];
            int  iter = 0;

            while (++iter < 40000)
            {
                if ((flags[i] & (1 << dir)) > 0)
                {
                    bool isBorderVertex = false;
                    bool isAreaBorder   = false;
                    int  px             = x;
                    int  py             = GetCornerHeight(x, y, i, tempDir, cfh, ref isBorderVertex);
                    int  pz             = y;

                    if (dir == (char)0)
                    {
                        pz++;
                    }
                    else if (dir == (char)1)
                    {
                        px++;
                        pz++;
                    }
                    else if (dir == (char)2)
                    {
                        px++;
                    }

                    int         r = 0;
                    CompactSpan s = cfh.Spans[i];
                    if (s.GetCon(dir) != CompactHeightfield.NotConnected)
                    {
                        int ax = x + Helper.GetDirOffsetX(dir);
                        int ay = y + Helper.GetDirOffsetY(dir);
                        int ai = (int)cfh.Cells[ax + ay * cfh.Width].Index + s.GetCon(dir);
                        r = cfh.Spans[ai].Reg;
                        if (area != cfh.Areas[ai])
                        {
                            isAreaBorder = true;
                        }
                    }
                    if (isBorderVertex)
                    {
                        r |= BorderVertex;
                    }
                    if (isAreaBorder)
                    {
                        r |= AreaBorder;
                    }

                    points.Push(px);
                    points.Push(py);
                    points.Push(pz);
                    points.Push(r);

                    flags[i] &= (char)~(1 << dir);
                    dir       = (char)((dir + 1) & 0x3); // rotate CW
                }
                else
                {
                    int         ni = -1;
                    int         nx = x + Helper.GetDirOffsetX(dir);
                    int         ny = y + Helper.GetDirOffsetY(dir);
                    CompactSpan s  = cfh.Spans[i];
                    if (s.GetCon(dir) != CompactHeightfield.NotConnected)
                    {
                        CompactCell nc = cfh.Cells[nx + ny * cfh.Width];
                        ni = (int)nc.Index + s.GetCon(dir);
                    }
                    if (ni == -1)
                    {
                        // error
                        return;
                    }
                    x   = nx;
                    y   = ny;
                    i   = ni;
                    dir = (char)((dir + 3) & 0x3);
                }
                if (starti == i && startDir == dir)
                {
                    break;
                }
            }
        }