private bool MergeContours(ref Contour ca, ref Contour cb, int ia, int ib)
        {
            int maxVerts = ca.NVerts + cb.NVerts + 2;

            int[] verts = new int[maxVerts * 4];
            int   nv    = 0;

            for (int i = 0; i < ca.NVerts; i++)
            {
                int dst = nv * 4;
                int src = ((ia + i) % ca.NVerts) * 4;
                verts[dst + 0] = ca.Verts[src + 0];
                verts[dst + 1] = ca.Verts[src + 1];
                verts[dst + 2] = ca.Verts[src + 2];
                verts[dst + 3] = ca.Verts[src + 3];
                nv++;
            }

            for (int i = 0; i < cb.NVerts; i++)
            {
                int dst = nv * 4;
                int src = ((ib + i) % cb.NVerts) * 4;
                verts[dst + 0] = cb.Verts[src + 0];
                verts[dst + 1] = cb.Verts[src + 1];
                verts[dst + 2] = cb.Verts[src + 2];
                verts[dst + 3] = cb.Verts[src + 3];
                nv++;
            }

            ca.Verts  = verts;
            ca.NVerts = nv;
            cb.Verts  = null;
            cb.NVerts = 0;

            return(true);
        }
        public PolyMesh(ContourSet cset, int nvp)
        {
            Array.Copy(cset.BMin, BMin, 3);
            Array.Copy(cset.BMax, BMax, 3);
            Cs         = cset.Cs;
            Ch         = cset.Ch;
            BorderSize = cset.BorderSize;

            int maxVertices     = 0;
            int maxTris         = 0;
            int maxVertsPerCont = 0;

            for (int i = 0; i < cset.NConts; i++)
            {
                if (cset.Conts[i].NVerts < 3)
                {
                    continue;
                }
                maxVertices    += cset.Conts[i].NVerts;
                maxTris        += cset.Conts[i].NVerts - 2;
                maxVertsPerCont = Math.Max(maxVertsPerCont, cset.Conts[i].NVerts);
            }

            short[] vflags = new short[maxVertices];

            Verts = new int[maxVertices * 3];
            Polys = new int[maxTris * nvp * 2];
            Regs  = new int[maxTris];
            Areas = new short[maxTris];

            NVerts   = 0;
            NPolys   = 0;
            Nvp      = nvp;
            MaxPolys = maxTris;

            for (int i = 0; i < maxTris * nvp * 2; i++)
            {
                Polys[i] = 0xffff;     // memset(mesh.polys, 0xff)
            }

            int[] nextVert = new int[maxVertices];

            int[] firstVert = new int[VertexBucketCount];
            for (int i = 0; i < firstVert.Length; i++)
            {
                firstVert[i] = -1;
            }

            int[] indices = new int[maxVertsPerCont];
            int[] tris    = new int[maxVertsPerCont * 3];
            int[] polys   = new int[(maxVertsPerCont + 1) * nvp];
            int[] tmpPoly = new int[nvp];

            for (int i = 0; i < cset.NConts; i++)
            {
                Contour cont = cset.Conts[i];

                if (cont.NVerts < 3)
                {
                    continue;
                }

                for (int j = 0; j < cont.NVerts; j++)
                {
                    indices[j] = j;
                }

                int ntris = Triangulate(cont.NVerts, cont.Verts, ref indices, ref tris);
                if (ntris <= 0)
                {
                    // error
                    ntris = -ntris;
                }

                for (int j = 0; j < cont.NVerts; j++)
                {
                    int v = j * 4;
                    indices[j] = AddVertex(cont.Verts[v + 0], cont.Verts[v + 1], cont.Verts[v + 2], ref firstVert, ref nextVert);
                    if ((cont.Verts[v + 3] & ContourSet.BorderVertex) != 0)
                    {
                        vflags[indices[j]] = 1;
                    }
                }

                //build initial polygons
                int npolys = 0;
                for (int j = 0; j < polys.Length; j++)
                {
                    polys[j] = 0xffff;
                }
                for (int j = 0; j < ntris; j++)
                {
                    int t = j * 3;
                    if (tris[t + 0] != tris[t + 1] && tris[t + 0] != tris[t + 2] && tris[t + 1] != tris[t + 2])
                    {
                        polys[npolys * nvp + 0] = indices[tris[t + 0]];
                        polys[npolys * nvp + 1] = indices[tris[t + 1]];
                        polys[npolys * nvp + 2] = indices[tris[t + 2]];
                        npolys++;
                    }
                }
                if (npolys == 0)
                {
                    continue;
                }

                if (nvp > 3)
                {
                    for (;;)
                    {
                        int bestMergeVal = 0;
                        int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;

                        for (int j = 0; j < npolys - 1; j++)
                        {
                            int pj = j * nvp; // polys[j*nvp]
                            for (int k = j + 1; k < npolys; k++)
                            {
                                int pk = k * nvp;
                                int ea = 0, eb = 0;
                                int v = GetPolyMergeValue(polys, pj, pk, Verts, ref ea, ref eb, nvp);
                                if (v > bestMergeVal)
                                {
                                    bestMergeVal = v;
                                    bestPa       = j;
                                    bestPb       = k;
                                    bestEa       = ea;
                                    bestEb       = eb;
                                }
                            }
                        }

                        if (bestMergeVal > 0)
                        {
                            int pa = bestPa * nvp;
                            int pb = bestPb * nvp;
                            MergePolys(ref polys, pa, pb, bestEa, bestEb, tmpPoly, nvp);
                            // fill in the hole at pb with the last poly
                            Array.Copy(polys, (npolys - 1) * nvp, polys, pb, nvp);
                            npolys--;
                        }
                        else
                        {
                            break;
                        }
                    }
                }

                for (int j = 0; j < npolys; j++)
                {
                    int p = NPolys * nvp * 2;
                    int q = j * nvp;
                    for (int k = 0; k < nvp; k++)
                    {
                        Polys[p + k] = polys[q + k];
                    }
                    Regs[NPolys]  = cont.Reg;
                    Areas[NPolys] = cont.Area;
                    NPolys++;
                }
            }

            for (int i = 0; i < NVerts; i++)
            {
                //if (vflags[i] > 0)
                //{
                //    if (!CanRemoveVertex(i)) continue;
                //    if (!RemoveVertex(i, maxTris))
                //    {
                //        // error
                //    }

                //    for (int j = i; j < NVerts; j++)
                //    {
                //        vflags[j] = vflags[j + 1];
                //    }
                //    i--;
                //}
            }

            if (!BuildMeshAdjacency(nvp))
            {
                // error
            }

            // find portal edges
            if (BorderSize > 0) // defaults to 0
            {
                int w = cset.Width;
                int h = cset.Height;
                for (int i = 0; i < NPolys; i++)
                {
                    int p = i * 2 * nvp;
                    for (int j = 0; j < nvp; j++)
                    {
                        if (Polys[p + j] == MeshNullIdx)
                        {
                            break;
                        }
                        if (Polys[p + nvp + j] != MeshNullIdx)
                        {
                            continue;
                        }
                        int nj = j + 1;
                        if (nj >= nvp || Polys[p + nj] == MeshNullIdx)
                        {
                            nj = 0;
                        }
                        int va = Polys[p + j] * 3;
                        int vb = Polys[p + nj] * 3;

                        if (Verts[va + 0] == 0 && Verts[vb + 0] == 0)
                        {
                            Polys[p + nvp + j] = 0x8000 | 0;
                        }
                        else if (Verts[va + 2] == h && Verts[vb + 2] == h)
                        {
                            Polys[p + nvp + j] = 0x8000 | 1;
                        }
                        else if (Verts[va + 0] == w && Verts[vb + 0] == w)
                        {
                            Polys[p + nvp + j] = 0x8000 | 2;
                        }
                        else if (Verts[va + 2] == 0 && Verts[vb + 2] == 0)
                        {
                            Polys[p + nvp + j] = 0x8000 | 3;
                        }
                    }
                }
            }

            // user fills in this data
            Flags = new int[NPolys];
        }
        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;
                        }
                    }
                }
            }
        }