Ejemplo n.º 1
0
        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);
                }
            }
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        private void SimplifyContour(ref IntArray points, ref IntArray simplified, float maxError, int maxEdgeLen, int buildFlags)
        {
            bool hasConnections = false;

            for (int i = 0; i < points.Size; i += 4)
            {
                if ((points[i + 3] & ContourRegMask) != 0)
                {
                    hasConnections = true;
                    break;
                }
            }
            if (hasConnections)
            {
                for (int i = 0, ni = points.Size / 4; i < ni; i++)
                {
                    int  ii            = (i + 1) % ni;
                    bool differentRegs = (points[i * 4 + 3] & ContourRegMask) != (points[ii * 4 + 3] & ContourRegMask);
                    bool areaBorders   = (points[i * 4 + 3] & AreaBorder) != (points[ii * 4 + 3] & AreaBorder);
                    if (differentRegs || areaBorders)
                    {
                        simplified.Push(points[i * 4 + 0]);
                        simplified.Push(points[i * 4 + 1]);
                        simplified.Push(points[i * 4 + 2]);
                        simplified.Push(i);
                    }
                }
            }

            if (simplified.Size == 0)
            {
                int llx = points[0];
                int lly = points[1];
                int llz = points[2];
                int lli = 0;
                int urx = points[0];
                int ury = points[1];
                int urz = points[2];
                int uri = 0;
                for (int i = 0; i < points.Size; i += 4)
                {
                    int x = points[i + 0];
                    int y = points[i + 1];
                    int z = points[i + 2];
                    if (x < llx || (x == llx && z < llz))
                    {
                        llx = x;
                        lly = y;
                        llz = z;
                        lli = i / 4;
                    }
                    if (x > urx || (x == urx && z > urz))
                    {
                        urx = x;
                        ury = y;
                        urz = z;
                        uri = i / 4;
                    }
                }
                simplified.Push(llx);
                simplified.Push(lly);
                simplified.Push(llz);
                simplified.Push(lli);

                simplified.Push(urx);
                simplified.Push(ury);
                simplified.Push(urz);
                simplified.Push(uri);
            }

            int pn = points.Size / 4;

            // go through all the points, as more points are added on to the end
            for (int i = 0; i < simplified.Size / 4;)
            {
                int ii = (i + 1) % (simplified.Size / 4);

                int ax = simplified[i * 4 + 0];
                int az = simplified[i * 4 + 2];
                int ai = simplified[i * 4 + 3];

                int bx = simplified[ii * 4 + 0];
                int bz = simplified[ii * 4 + 2];
                int bi = simplified[ii * 4 + 3];

                float maxd = 0;
                int   maxi = -1;
                int   ci, cinc, endi;

                if (bx > ax || (bx == ax && bz > az))
                {
                    cinc = 1;
                    ci   = (ai + cinc) % pn;
                    endi = bi;
                }
                else
                {
                    cinc = pn - 1;
                    ci   = (bi + cinc) % pn;
                    endi = ai;
                }

                if ((points[ci * 4 + 3] & ContourRegMask) == 0 || (points[ci * 4 + 3] & AreaBorder) != 0)
                {
                    while (ci != endi)
                    {
                        float d = DistancePtSeg(points[ci * 4 + 0], points[ci * 4 + 2], ax, az, bx, bz);
                        if (d > maxd)
                        {
                            maxd = d;
                            maxi = ci;
                        }
                        ci = (ci + cinc) % pn;
                    }
                }

                // if max deviation is larger than accepted error add new point
                float errorSqrd = maxError * maxError;
                if (maxi != -1 && maxd > (maxError * maxError))
                {
                    simplified.Resize(simplified.Size + 4);
                    int n = simplified.Size / 4;
                    for (int j = n - 1; j > i; --j)
                    {
                        simplified[j * 4 + 0] = simplified[(j - 1) * 4 + 0];
                        simplified[j * 4 + 1] = simplified[(j - 1) * 4 + 1];
                        simplified[j * 4 + 2] = simplified[(j - 1) * 4 + 2];
                        simplified[j * 4 + 3] = simplified[(j - 1) * 4 + 3];
                    }

                    simplified[(i + 1) * 4 + 0] = points[maxi * 4 + 0];
                    simplified[(i + 1) * 4 + 1] = points[maxi * 4 + 1];
                    simplified[(i + 1) * 4 + 2] = points[maxi * 4 + 2];
                    simplified[(i + 1) * 4 + 3] = maxi;
                }
                else
                {
                    i++;
                }
            }

            // split too long edges
            if (maxEdgeLen > 0 &&
                (buildFlags & (BuildContourFlags.ContourTessWallEdges | BuildContourFlags.ContourTessAreaEdges)) != 0)
            {
                for (int i = 0; i < simplified.Size / 4;)
                {
                    int ii = (i + 1) % (simplified.Size / 4);

                    int ax = simplified[i * 4 + 0];
                    int az = simplified[i * 4 + 2];
                    int ai = simplified[i * 4 + 3];

                    int bx = simplified[ii * 4 + 0];
                    int bz = simplified[ii * 4 + 2];
                    int bi = simplified[ii * 4 + 3];

                    int maxi = -1;
                    int ci   = (ai + 1) % pn;

                    bool tess = false;

                    if ((buildFlags & BuildContourFlags.ContourTessWallEdges) != 0 &&
                        (points[ci * 4 + 3] & ContourRegMask) == 0)
                    {
                        tess = true;
                    }
                    if ((buildFlags & BuildContourFlags.ContourTessAreaEdges) != 0 &&
                        (points[ci * 4 + 3] & AreaBorder) != 0)
                    {
                        tess = true;
                    }

                    if (tess)
                    {
                        int dx = bx - ax;
                        int dz = bz - az;
                        if (dx * dx + dz * dz > maxEdgeLen * maxEdgeLen)
                        {
                            int n = bi < ai ? (bi + pn - ai) : (bi - ai);
                            if (n > 1)
                            {
                                if (bx > ax || (bx == ax && bz > az))
                                {
                                    maxi = (int)(ai + n / 2f) % pn;
                                }
                                else
                                {
                                    maxi = (int)(ai + (n + 1) / 2f) % pn;
                                }
                            }
                        }
                    }

                    if (maxi != -1)
                    {
                        simplified.Resize(simplified.Size + 4);
                        int n = simplified.Size / 4;
                        for (int j = n - 1; j > i; j--)
                        {
                            simplified[j * 4 + 0] = simplified[(j - 1) * 4 + 0];
                            simplified[j * 4 + 1] = simplified[(j - 1) * 4 + 1];
                            simplified[j * 4 + 2] = simplified[(j - 1) * 4 + 2];
                            simplified[j * 4 + 3] = simplified[(j - 1) * 4 + 3];
                        }
                        simplified[(i + 1) * 4 + 0] = points[maxi * 4 + 0];
                        simplified[(i + 1) * 4 + 1] = points[maxi * 4 + 1];
                        simplified[(i + 1) * 4 + 2] = points[maxi * 4 + 2];
                        simplified[(i + 1) * 4 + 3] = maxi;
                    }
                    else
                    {
                        i++;
                    }
                }
            }

            for (int i = 0; i < simplified.Size / 4; i++)
            {
                int ai = (simplified[i * 4 + 3] + 1) % pn;
                int bi = simplified[i * 4 + 3];
                simplified[i * 4 + 3] = (points[ai * 4 + 3] & (ContourRegMask | AreaBorder)) |
                                        (points[bi * 4 + 3] & BorderVertex);
            }
        }
Ejemplo n.º 4
0
        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;
                }
            }
        }
        private bool FilterSmallRegions(int minRegionArea, int mergeRegionArea, ref int[] srcReg)
        {
            int w = Width;
            int h = Height;

            int nreg = MaxRegions + 1;

            Region[] regions = new Region[nreg];

            for (int i = 0; i < nreg; i++)
            {
                regions[i] = new Region(i);
            }

            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    CompactCell c = Cells[x + y * w];
                    for (int i = (int)c.Index, ni = (int)(c.Index + c.Count); i < ni; i++)
                    {
                        int r = srcReg[i];
                        if (r <= 0 || r >= nreg)
                        {
                            continue;
                        }
                        Region reg = regions[r];
                        reg.SpanCount++;

                        for (int j = (int)c.Index; j < ni; j++)
                        {
                            if (i == j)
                            {
                                continue;
                            }
                            int floorId = srcReg[j];
                            if (floorId <= 0 || floorId >= nreg)
                            {
                                continue;
                            }
                            AddUniqueFloorRegion(ref reg, floorId);
                        }

                        if (reg.Connections.Size > 0)
                        {
                            continue;
                        }

                        reg.AreaType = (short)Areas[i];

                        int ndir = -1;
                        for (int dir = 0; dir < 4; dir++)
                        {
                            if (IsSolidEdge(srcReg, x, y, i, dir))
                            {
                                ndir = dir;
                                break;
                            }
                        }

                        if (ndir != -1)
                        {
                            WalkContour(x, y, i, ndir, ref srcReg, ref reg);
                        }
                    }
                }
            }

            // remove too small regions.
            IntArray stack = new IntArray(32);
            IntArray trace = new IntArray(32);

            for (int i = 0; i < nreg; i++)
            {
                Region reg = regions[i];
                if (reg.Id <= 0 || (reg.Id & BorderReg) != 0)
                {
                    continue;
                }
                if (reg.SpanCount == 0)
                {
                    continue;
                }
                if (reg.Visited)
                {
                    continue;
                }

                bool connectsToBorder = false;
                int  spanCount        = 0;
                stack.Resize(0);
                trace.Resize(0);

                reg.Visited = true;
                stack.Push(i);

                while (stack.Size > 0)
                {
                    int ri = stack.Pop();

                    Region creg = regions[ri];
                    spanCount += creg.SpanCount;
                    trace.Push(ri);

                    for (int j = 0; j < creg.Connections.Size; j++)
                    {
                        if ((creg.Connections[j] & BorderReg) != 0)
                        {
                            connectsToBorder = true;
                            continue;
                        }
                        Region neireg = regions[creg.Connections[j]];
                        if (neireg.Visited)
                        {
                            continue;
                        }
                        if (neireg.Id <= 0 || (neireg.Id & BorderReg) != 0)
                        {
                            continue;
                        }
                        stack.Push(neireg.Id);
                        neireg.Visited = true;
                    }
                }

                // if the region's size is too small, remove it.
                if (spanCount < minRegionArea && !connectsToBorder)
                {
                    for (int j = 0; j < trace.Size; j++)
                    {
                        regions[trace[j]].SpanCount = 0;
                        regions[trace[j]].Id        = 0;
                    }
                }
            }

            int mergeCount = 0;

            do
            {
                mergeCount = 0;
                for (int i = 0; i < nreg; i++)
                {
                    Region reg = regions[i];
                    if (reg.Id <= 0 || (reg.Id & BorderReg) != 0)
                    {
                        continue;
                    }
                    if (reg.SpanCount == 0)
                    {
                        continue;
                    }

                    if (reg.SpanCount > mergeRegionArea && IsRegionConnectedToBorder(reg))
                    {
                        continue;
                    }

                    // small region with more than 1 connection
                    // or region which is not connected to a border at all.
                    // find smallest neighbor
                    int smallest = int.MaxValue;
                    int mergeId  = reg.Id;
                    for (int j = 0; j < reg.Connections.Size; j++)
                    {
                        if ((reg.Connections[j] & BorderReg) != 0)
                        {
                            continue;
                        }
                        Region mreg = regions[reg.Connections[j]];
                        if (mreg.Id <= 0 || (mreg.Id & BorderReg) != 0)
                        {
                            continue;
                        }
                        if (mreg.SpanCount < smallest && CanMergeWithRegion(reg, mreg) && CanMergeWithRegion(mreg, reg))
                        {
                            smallest = mreg.SpanCount;
                            mergeId  = mreg.Id;
                        }
                    }
                    if (mergeId != reg.Id)
                    {
                        int    oldId  = reg.Id;
                        Region target = regions[mergeId];

                        if (MergeRegions(ref target, ref reg))
                        {
                            for (int j = 0; j < nreg; j++)
                            {
                                if (regions[j].Id == 0 || (regions[j].Id & BorderReg) != 0)
                                {
                                    continue;
                                }
                                if (regions[j].Id == oldId)
                                {
                                    regions[j].Id = mergeId;
                                }
                                Region reg2 = regions[j];
                                ReplaceNeighbor(ref reg2, oldId, mergeId);
                            }
                            mergeCount++;
                        }
                    }
                }
            } while (mergeCount > 0);

            // compress regions Ids
            for (int i = 0; i < nreg; i++)
            {
                regions[i].Remap = false;
                if (regions[i].Id == 0 || (regions[i].Id & BorderReg) != 0)
                {
                    continue;
                }
                regions[i].Remap = true;
            }

            int regIdGen = 0;

            for (int i = 0; i < nreg; i++)
            {
                if (!regions[i].Remap)
                {
                    continue;
                }

                int oldId = regions[i].Id;
                int newId = ++regIdGen;
                for (int j = i; j < nreg; j++)
                {
                    if (regions[j].Id == oldId)
                    {
                        regions[j].Id    = newId;
                        regions[j].Remap = false;
                    }
                }
            }

            MaxRegions = regIdGen;

            // remap regions
            for (int i = 0; i < SpanCount; i++)
            {
                if ((srcReg[i] & BorderReg) == 0)
                {
                    srcReg[i] = regions[srcReg[i]].Id;
                }
            }

            return(true);
        }
        private int[] ExpandRegions(int maxIter, int level, ref int[] srcReg, ref int[] srcDist, ref int[] dstReg, ref int[] dstDist, ref IntArray stack)
        {
            int w = Width;
            int h = Height;

            stack.Resize(0);
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    CompactCell c = Cells[x + y * w];
                    for (int i = (int)c.Index, ni = (int)(c.Index + c.Count); i < ni; i++)
                    {
                        if (Dist[i] >= level && srcReg[i] == 0 && Areas[i] != HeightField.NullArea)
                        {
                            stack.Push(x);
                            stack.Push(y);
                            stack.Push(i);
                        }
                    }
                }
            }

            int iter = 0;

            while (stack.Size > 0)
            {
                int failed = 0;

                Buffer.BlockCopy(srcReg, 0, dstReg, 0, sizeof(int) * SpanCount);
                Buffer.BlockCopy(srcDist, 0, dstDist, 0, sizeof(int) * SpanCount);
                //Array.Copy(srcReg, dstReg, SpanCount);
                //Array.Copy(srcDist, dstDist, SpanCount);

                for (int j = 0; j < stack.Size; j += 3)
                {
                    int x = stack[j + 0];
                    int y = stack[j + 1];
                    int i = stack[j + 2];
                    if (i < 0)
                    {
                        failed++;
                        continue;
                    }

                    int         r    = srcReg[i];
                    int         d2   = short.MaxValue;
                    uint        area = Areas[i];
                    CompactSpan s    = Spans[i];
                    for (int dir = 0; dir < 4; dir++)
                    {
                        if (s.GetCon(dir) == NotConnected)
                        {
                            continue;
                        }
                        int ax = x + Helper.GetDirOffsetX(dir);
                        int ay = y + Helper.GetDirOffsetY(dir);
                        int ai = (int)Cells[ax + ay * w].Index + s.GetCon(dir);
                        if (Areas[ai] != area)
                        {
                            continue;
                        }
                        if (srcReg[ai] > 0 && (srcReg[ai] & BorderReg) == 0)
                        {
                            if (srcDist[ai] + 2 < d2)
                            {
                                r  = srcReg[ai];
                                d2 = srcDist[ai] + 2;
                            }
                        }
                    }
                    if (r != 0)
                    {
                        stack[j + 2] = -1;
                        dstReg[i]    = r;
                        dstDist[i]   = d2;
                    }
                    else
                    {
                        failed++;
                    }
                }
                int[] temp = srcReg;
                srcReg  = dstReg;
                dstReg  = temp;
                temp    = srcDist;
                srcDist = dstDist;
                dstDist = temp;

                if (failed * 3 == stack.Size)
                {
                    break;
                }

                if (level > 0)
                {
                    ++iter;
                    if (iter >= maxIter)
                    {
                        break;
                    }
                }
            }
            return(srcReg);
        }
        private bool FloodRegion(int x, int y, int i, int level, int r, ref int[] srcReg, ref int[] srcDist, ref IntArray stack)
        {
            int  w    = Width;
            uint area = Areas[i];

            stack.Resize(0);
            stack.Push(x);
            stack.Push(y);
            stack.Push(i);
            srcReg[i]  = r;
            srcDist[i] = 0;

            int lev   = level >= 2 ? level - 2 : 0;
            int count = 0;

            while (stack.Size > 0)
            {
                int ci = stack.Pop();
                int cy = stack.Pop();
                int cx = stack.Pop();

                CompactSpan cs = Spans[ci];

                int ar = 0;
                for (int dir = 0; dir < 4; dir++)
                {
                    if (cs.GetCon(dir) != NotConnected)
                    {
                        int ax = cx + Helper.GetDirOffsetX(dir);
                        int ay = cy + Helper.GetDirOffsetY(dir);
                        int ai = (int)Cells[ax + ay * w].Index + cs.GetCon(dir);
                        if (Areas[ai] != area)
                        {
                            continue;
                        }
                        int nr = srcReg[ai];
                        if ((nr & BorderReg) != 0)
                        {
                            continue;
                        }
                        if (nr != 0 && nr != r)
                        {
                            ar = nr;
                        }
                        CompactSpan aspan = Spans[ai];

                        int dir2 = (dir + 1) & 0x3;
                        if (aspan.GetCon(dir2) != NotConnected)
                        {
                            int ax2 = ax + Helper.GetDirOffsetX(dir2);
                            int ay2 = ay + Helper.GetDirOffsetY(dir2);
                            int ai2 = (int)Cells[ax2 + ay2 * w].Index + aspan.GetCon(dir2);
                            if (Areas[ai2] != area)
                            {
                                continue;
                            }
                            int nr2 = srcReg[ai2];
                            if (nr2 != 0 && nr2 != r)
                            {
                                ar = nr2;
                            }
                        }
                    }
                }
                if (ar != 0)
                {
                    srcReg[ci] = 0;
                    continue;
                }
                count++;

                for (int dir = 0; dir < 4; dir++)
                {
                    if (cs.GetCon(dir) != NotConnected)
                    {
                        int ax = cx + Helper.GetDirOffsetX(dir);
                        int ay = cy + Helper.GetDirOffsetY(dir);
                        int ai = (int)Cells[ax + ay * w].Index + cs.GetCon(dir);
                        if (Areas[ai] != area)
                        {
                            continue;
                        }
                        if (Dist[ai] >= lev && srcReg[ai] == 0)
                        {
                            srcReg[ai]  = r;
                            srcDist[ai] = 0;
                            stack.Push(ax);
                            stack.Push(ay);
                            stack.Push(ai);
                        }
                    }
                }
            }
            return(count > 0);
        }