Example #1
0
    /// @par
    ///
    /// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen
    /// parameters control how closely the simplified contours will match the raw contours.
    ///
    /// Simplified contours are generated such that the vertices for portals between areas match up.
    /// (They are considered mandatory vertices.)
    ///
    /// Setting @p maxEdgeLength to zero will disabled the edge length feature.
    ///
    /// See the #rcConfig documentation for more information on the configuration parameters.
    ///
    /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig
    public static bool rcBuildContours(rcContext ctx, rcCompactHeightfield chf, double maxError, int maxEdgeLen, rcContourSet cset, int buildFlags = 1)
    {
        Debug.Assert(ctx != null, "rcContext is null");

        int w          = chf.width;
        int h          = chf.height;
        int borderSize = chf.borderSize;

        ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS);

        rcVcopy(cset.bmin, chf.bmin);
        rcVcopy(cset.bmax, chf.bmax);
        if (borderSize > 0)
        {
            // If the heightfield was build with bordersize, remove the offset.
            float pad = borderSize * chf.cs;
            cset.bmin[0] += pad;
            cset.bmin[2] += pad;
            cset.bmax[0] -= pad;
            cset.bmax[2] -= pad;
        }
        cset.cs         = chf.cs;
        cset.ch         = chf.ch;
        cset.width      = chf.width - chf.borderSize * 2;
        cset.height     = chf.height - chf.borderSize * 2;
        cset.borderSize = chf.borderSize;
        cset.maxError   = (float)maxError;

        int maxContours = Math.Max((int)chf.maxRegions, 8);

        cset.conts = new rcContour[maxContours];
        for (var i = 0; i < maxContours; ++i)
        {
            cset.conts[i] = new rcContour();
        }
        cset.nconts = 0;

        byte[] flags = new byte[chf.spanCount];
        if (flags == null)
        {
            ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' " + chf.spanCount);
            return(false);
        }

        ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);

        // Mark boundaries.
        for (int y = 0; y < h; ++y)
        {
            for (int x = 0; x < w; ++x)
            {
                rcCompactCell c = chf.cells[x + y * w];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    byte          res = 0;
                    rcCompactSpan s   = chf.spans[i];
                    if (chf.spans[i].reg == 0 || (chf.spans[i].reg & RC_BORDER_REG) != 0)
                    {
                        flags[i] = 0;
                        continue;
                    }
                    for (int dir = 0; dir < 4; ++dir)
                    {
                        ushort r = 0;
                        if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                        {
                            int ax = x + rcGetDirOffsetX(dir);
                            int ay = y + rcGetDirOffsetY(dir);
                            int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, dir);
                            r = chf.spans[ai].reg;
                        }
                        if (r == chf.spans[i].reg)
                        {
                            res |= (byte)(1 << dir);
                        }
                    }
                    flags[i] = (byte)(res ^ 0xf); // Inverse, mark non connected edges.
                }
            }
        }

        ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);

        List <int> verts      = new(256);
        List <int> simplified = new(64);

        for (int y = 0; y < h; ++y)
        {
            for (int x = 0; x < w; ++x)
            {
                rcCompactCell c = chf.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] = 0;
                        continue;
                    }
                    ushort reg = chf.spans[i].reg;
                    if (reg == 0 || (reg & RC_BORDER_REG) != 0)
                    {
                        continue;
                    }
                    byte area = chf.areas[i];

                    verts.Clear();
                    simplified.Clear();

                    ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);
                    walkContour(x, y, i, chf, flags, verts);
                    ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);

                    ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY);
                    simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags);
                    removeDegenerateSegments(simplified);
                    ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY);

                    // Store region.contour remap info.
                    // Create contour.
                    if (simplified.Count / 4 >= 3)
                    {
                        if (cset.nconts >= maxContours)
                        {
                            // Allocate more contours.
                            // This can happen when there are tiny holes in the heightfield.
                            int oldMax = maxContours;
                            maxContours *= 2;
                            rcContour[] newConts = new rcContour[maxContours];
                            for (int j = 0; j < cset.nconts; ++j)
                            {
                                newConts[j] = cset.conts[j];
                                // Reset source pointers to prevent data deletion.
                                cset.conts[j].verts  = null;
                                cset.conts[j].rverts = null;
                            }
                            cset.conts = newConts;

                            ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Expanding max contours from " + oldMax + " to " + maxContours);
                        }

                        int contId = cset.nconts;

                        if (contId == 7)
                        {
                        }
                        cset.nconts++;
                        rcContour cont = cset.conts[contId];

                        cont.nverts = simplified.Count / 4;
                        cont.verts  = new int[cont.nverts * 4];
                        if (cont.verts == null)
                        {
                            ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' " + cont.nverts);
                            return(false);
                        }

                        for (int j = 0; j < cont.nverts * 4; ++j)
                        {
                            cont.verts[j] = simplified[j];
                        }
                        if (borderSize > 0)
                        {
                            // If the heightfield was build with bordersize, remove the offset.
                            for (int j = 0; j < cont.nverts; ++j)
                            {
                                cont.verts[j * 4]     -= borderSize;
                                cont.verts[j * 4 + 2] -= borderSize;
                            }
                        }

                        cont.nrverts = verts.Count / 4;
                        cont.rverts  = new int[cont.nrverts * 4];
                        if (cont.rverts == null)
                        {
                            ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' " + cont.nrverts);
                            return(false);
                        }

                        for (int j = 0; j < cont.nrverts * 4; ++j)
                        {
                            cont.rverts[j] = verts[j];
                        }
                        if (borderSize > 0)
                        {
                            // If the heightfield was build with bordersize, remove the offset.
                            for (int j = 0; j < cont.nrverts; ++j)
                            {
                                cont.rverts[j * 4]     -= borderSize;
                                cont.rverts[j * 4 + 2] -= borderSize;
                            }
                        }

                        cont.reg  = reg;
                        cont.area = area;

                        cset.conts[contId] = cont;
                    }
                }
            }
        }

        // Merge holes if needed.
        if (cset.nconts > 0)
        {
            // Calculate winding of all polygons.
            sbyte[] winding = new sbyte[cset.nconts];

            int nholes = 0;
            for (int i = 0; i < cset.nconts; ++i)
            {
                rcContour cont = cset.conts[i];
                // If the contour is wound backwards, it is a hole.
                winding[i] = (sbyte)(calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0 ? -1 : 1);
                if (winding[i] < 0)
                {
                    nholes++;
                }
            }

            if (nholes > 0)
            {
                // Collect outline contour and holes contours per region.
                // We assume that there is one outline and multiple holes.
                int nregions = chf.maxRegions + 1;
                rcContourRegion[] regions = new rcContourRegion[nregions];
                for (var i = 0; i < nregions; ++i)
                {
                    regions[i] = new rcContourRegion();
                }

                rcContourHole[] holes = new rcContourHole[cset.nconts];
                for (var i = 0; i < cset.nconts; ++i)
                {
                    holes[i] = new rcContourHole();
                }

                for (int i = 0; i < cset.nconts; ++i)
                {
                    rcContour cont = cset.conts[i];
                    // Positively would contours are outlines, negative holes.
                    if (winding[i] > 0)
                    {
                        regions[cont.reg].outline = cont;
                    }
                    else
                    {
                        regions[cont.reg].nholes++;
                    }
                }
                int index = 0;
                for (int i = 0; i < nregions; i++)
                {
                    if (regions[i].nholes > 0)
                    {
                        regions[i].holes = new rcContourHole[cset.nconts];
                        Array.Copy(holes, index, regions[i].holes, 0, cset.nconts - index);
                        index            += regions[i].nholes;
                        regions[i].nholes = 0;
                    }
                }
                for (int i = 0; i < cset.nconts; ++i)
                {
                    rcContour       cont = cset.conts[i];
                    rcContourRegion reg  = regions[cont.reg];
                    if (winding[i] < 0)
                    {
                        reg.holes[reg.nholes++].contour = cont;
                    }
                }

                // Finally merge each regions holes into the outline.
                for (int i = 0; i < nregions; i++)
                {
                    rcContourRegion reg = regions[i];
                    if (reg.nholes == 0)
                    {
                        continue;
                    }

                    if (reg.outline.verts != null)
                    {
                        mergeRegionHoles(ctx, reg);
                    }
                    else
                    {
                        // The region does not have an outline.
                        // This can happen if the contour becaomes selfoverlapping because of
                        // too aggressive simplification settings.
                        ctx.log(rcLogCategory.RC_LOG_ERROR, string.Format("rcBuildContours: Bad outline for region {0}, contour simplification is likely too aggressive.", i));
                    }
                }
            }
        }

        return(true);
    }
Example #2
0
    public static void walkContour(int x, int y, int i,
                                   rcCompactHeightfield chf,
                                   byte[] flags, List <int> points)
    {
        // Choose the first non-connected edge
        byte dir = 0;

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

        byte startDir = dir;
        int  starti   = i;

        byte area = chf.areas[i];

        int iter = 0;

        while (++iter < 40000)
        {
            if ((flags[i] & (1 << dir)) != 0)
            {
                // Choose the edge corner
                bool isBorderVertex = false;
                bool isAreaBorder   = false;
                int  px             = x;
                int  py             = getCornerHeight(x, y, i, dir, chf, ref isBorderVertex);
                int  pz             = y;
                switch (dir)
                {
                case 0: pz++; break;

                case 1: px++; pz++; break;

                case 2: px++; break;
                }
                int           r = 0;
                rcCompactSpan s = chf.spans[i];
                if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                {
                    int ax = x + rcGetDirOffsetX(dir);
                    int ay = y + rcGetDirOffsetY(dir);
                    int ai = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(s, dir);
                    r = (int)chf.spans[ai].reg;
                    if (area != chf.areas[ai])
                    {
                        isAreaBorder = true;
                    }
                }
                if (isBorderVertex)
                {
                    r |= RC_BORDER_VERTEX;
                }
                if (isAreaBorder)
                {
                    r |= RC_AREA_BORDER;
                }
                points.Add(px);
                points.Add(py);
                points.Add(pz);
                points.Add(r);

                flags[i] &= (byte)(~(1 << dir));     // Remove visited edges
                dir       = (byte)((dir + 1) & 0x3); // Rotate CW
            }
            else
            {
                int           ni = -1;
                int           nx = x + rcGetDirOffsetX(dir);
                int           ny = y + rcGetDirOffsetY(dir);
                rcCompactSpan s  = chf.spans[i];
                if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                {
                    rcCompactCell nc = chf.cells[nx + ny * chf.width];
                    ni = (int)nc.index + rcGetCon(s, dir);
                }
                if (ni == -1)
                {
                    // Should not happen.
                    return;
                }
                x   = nx;
                y   = ny;
                i   = ni;
                dir = (byte)((dir + 3) & 0x3);  // Rotate CCW
            }

            if (starti == i && startDir == dir)
            {
                break;
            }
        }
    }
Example #3
0
 static int getCornerHeight(int x, int y, int i, int dir,
                            rcCompactHeightfield chf,
                            ref bool isBorderVertex)
 {
     rcCompactSpan s    = chf.spans ![i];
Example #4
0
    static int getCornerHeight(int x, int y, int i, int dir,
                               rcCompactHeightfield chf,
                               ref bool isBorderVertex)
    {
        rcCompactSpan s    = chf.spans[i];
        int           ch   = (int)s.y;
        int           dirp = (dir + 1) & 0x3;

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

        // Combine region and area codes in order to prevent
        // border vertices which are in between two areas to be removed.
        regs[0] = (uint)(chf.spans[i].reg | (chf.areas[i] << 16));

        if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
        {
            int           ax    = x + rcGetDirOffsetX(dir);
            int           ay    = y + rcGetDirOffsetY(dir);
            int           ai    = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(s, dir);
            rcCompactSpan aSpan = chf.spans[ai];
            ch      = Math.Max(ch, (int)aSpan.y);
            regs[1] = (uint)(chf.spans[ai].reg | (chf.areas[ai] << 16));
            if (rcGetCon(aSpan, dirp) != RC_NOT_CONNECTED)
            {
                int           ax2 = ax + rcGetDirOffsetX(dirp);
                int           ay2 = ay + rcGetDirOffsetY(dirp);
                int           ai2 = (int)chf.cells[ax2 + ay2 * chf.width].index + rcGetCon(aSpan, dirp);
                rcCompactSpan as2 = chf.spans[ai2];
                ch      = Math.Max(ch, (int)as2.y);
                regs[2] = (uint)(chf.spans[ai2].reg | (chf.areas[ai2] << 16));
            }
        }
        if (rcGetCon(s, dirp) != RC_NOT_CONNECTED)
        {
            int           ax    = x + rcGetDirOffsetX(dirp);
            int           ay    = y + rcGetDirOffsetY(dirp);
            int           ai    = (int)chf.cells[ax + ay * chf.width].index + rcGetCon(s, dirp);
            rcCompactSpan aSpan = chf.spans[ai];
            ch      = Math.Max(ch, (int)aSpan.y);
            regs[3] = (uint)(chf.spans[ai].reg | (chf.areas[ai] << 16));
            if (rcGetCon(aSpan, dir) != RC_NOT_CONNECTED)
            {
                int           ax2 = ax + rcGetDirOffsetX(dir);
                int           ay2 = ay + rcGetDirOffsetY(dir);
                int           ai2 = (int)chf.cells[ax2 + ay2 * chf.width].index + rcGetCon(aSpan, dir);
                rcCompactSpan as2 = chf.spans[ai2];
                ch      = Math.Max(ch, (int)as2.y);
                regs[2] = (uint)(chf.spans[ai2].reg | (chf.areas[ai2] << 16));
            }
        }

        // Check if the vertex is special edge vertex, these vertices will be removed later.
        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;

            // The vertex is a border vertex there are two same exterior cells in a row,
            // followed by two interior cells and none of the regions are out of bounds.
            bool twoSameExts  = (regs[a] & regs[b] & RC_BORDER_REG) != 0 && regs[a] == regs[b];
            bool twoInts      = ((regs[c] | regs[d]) & RC_BORDER_REG) == 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);
    }
Example #5
0
    /// @par
    ///
    /// The value of spacial parameters are in world units.
    ///
    /// The y-values of the polygon vertices are ignored. So the polygon is effectively
    /// projected onto the xz-plane at @p hmin, then extruded to @p hmax.
    ///
    /// @see rcCompactHeightfield, rcMedianFilterWalkableArea
    public static void rcMarkConvexPolyArea(rcContext ctx, float[] verts, int nverts,
                                            float hmin, float hmax, byte areaId,
                                            rcCompactHeightfield chf)
    {
        Debug.Assert(ctx != null, "rcContext is null");

        ctx.startTimer(rcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA);

        float[] bmin = new float[3];
        float[] bmax = new float[3];
        rcVcopy(bmin, verts);
        rcVcopy(bmax, verts);
        for (int i = 1; i < nverts; ++i)
        {
            int vStart = i * 3;
            rcVmin(bmin, 0, verts, vStart);
            rcVmax(bmax, 0, verts, vStart);
        }
        bmin[1] = hmin;
        bmax[1] = hmax;

        int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs);
        int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch);
        int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs);
        int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs);
        int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch);
        int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs);

        if (maxx < 0)
        {
            return;
        }
        if (minx >= chf.width)
        {
            return;
        }
        if (maxz < 0)
        {
            return;
        }
        if (minz >= chf.height)
        {
            return;
        }

        if (minx < 0)
        {
            minx = 0;
        }
        if (maxx >= chf.width)
        {
            maxx = chf.width - 1;
        }
        if (minz < 0)
        {
            minz = 0;
        }
        if (maxz >= chf.height)
        {
            maxz = chf.height - 1;
        }


        // TODO: Optimize.
        for (int z = minz; z <= maxz; ++z)
        {
            for (int x = minx; x <= maxx; ++x)
            {
                rcCompactCell c = chf.cells[x + z * chf.width];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    rcCompactSpan s = chf.spans[i];
                    if (chf.areas[i] == RC_NULL_AREA)
                    {
                        continue;
                    }
                    if ((int)s.y >= miny && (int)s.y <= maxy)
                    {
                        float[] p = new float[3];
                        p[0] = chf.bmin[0] + (x + 0.5f) * chf.cs;
                        p[1] = 0;
                        p[2] = chf.bmin[2] + (z + 0.5f) * chf.cs;

                        if (pointInPoly(nverts, verts, p))
                        {
                            chf.areas[i] = areaId;
                        }
                    }
                }
            }
        }

        ctx.stopTimer(rcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA);
    }
Example #6
0
    /// @par
    ///
    /// The value of spacial parameters are in world units.
    ///
    /// @see rcCompactHeightfield, rcMedianFilterWalkableArea
    static public void rcMarkCylinderArea(rcContext ctx, float[] pos,
                                          float r, float h, byte areaId,
                                          rcCompactHeightfield chf)
    {
        Debug.Assert(ctx != null, "rcContext is null");

        ctx.startTimer(rcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA);

        float[] bmin = new float[3];
        float[] bmax = new float[3];
        bmin[0] = pos[0] - r;
        bmin[1] = pos[1];
        bmin[2] = pos[2] - r;
        bmax[0] = pos[0] + r;
        bmax[1] = pos[1] + h;
        bmax[2] = pos[2] + r;
        float r2 = r * r;

        int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs);
        int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch);
        int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs);
        int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs);
        int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch);
        int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs);

        if (maxx < 0)
        {
            return;
        }
        if (minx >= chf.width)
        {
            return;
        }
        if (maxz < 0)
        {
            return;
        }
        if (minz >= chf.height)
        {
            return;
        }

        if (minx < 0)
        {
            minx = 0;
        }
        if (maxx >= chf.width)
        {
            maxx = chf.width - 1;
        }
        if (minz < 0)
        {
            minz = 0;
        }
        if (maxz >= chf.height)
        {
            maxz = chf.height - 1;
        }


        for (int z = minz; z <= maxz; ++z)
        {
            for (int x = minx; x <= maxx; ++x)
            {
                rcCompactCell c = chf.cells[x + z * chf.width];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    rcCompactSpan s = chf.spans[i];

                    if (chf.areas[i] == RC_NULL_AREA)
                    {
                        continue;
                    }

                    if ((int)s.y >= miny && (int)s.y <= maxy)
                    {
                        float sx = chf.bmin[0] + (x + 0.5f) * chf.cs;
                        float sz = chf.bmin[2] + (z + 0.5f) * chf.cs;
                        float dx = sx - pos[0];
                        float dz = sz - pos[2];

                        if (dx * dx + dz * dz < r2)
                        {
                            chf.areas[i] = areaId;
                        }
                    }
                }
            }
        }

        ctx.stopTimer(rcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA);
    }
Example #7
0
    /// @par
    ///
    /// The value of spacial parameters are in world units.
    ///
    /// @see rcCompactHeightfield, rcMedianFilterWalkableArea
    public static void rcMarkBoxArea(rcContext ctx, float[] bmin, float[] bmax, byte areaId,
                                     rcCompactHeightfield chf)
    {
        Debug.Assert(ctx != null, "rcContext is null");

        ctx.startTimer(rcTimerLabel.RC_TIMER_MARK_BOX_AREA);

        int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs);
        int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch);
        int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs);
        int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs);
        int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch);
        int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs);

        if (maxx < 0)
        {
            return;
        }
        if (minx >= chf.width)
        {
            return;
        }
        if (maxz < 0)
        {
            return;
        }
        if (minz >= chf.height)
        {
            return;
        }

        if (minx < 0)
        {
            minx = 0;
        }
        if (maxx >= chf.width)
        {
            maxx = chf.width - 1;
        }
        if (minz < 0)
        {
            minz = 0;
        }
        if (maxz >= chf.height)
        {
            maxz = chf.height - 1;
        }

        for (int z = minz; z <= maxz; ++z)
        {
            for (int x = minx; x <= maxx; ++x)
            {
                rcCompactCell c = chf.cells[x + z * chf.width];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    rcCompactSpan s = chf.spans[i];
                    if ((int)s.y >= miny && (int)s.y <= maxy)
                    {
                        if (chf.areas[i] != RC_NULL_AREA)
                        {
                            chf.areas[i] = areaId;
                        }
                    }
                }
            }
        }

        ctx.stopTimer(rcTimerLabel.RC_TIMER_MARK_BOX_AREA);
    }
Example #8
0
    /// @par
    ///
    /// This filter is usually applied after applying area id's using functions
    /// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea.
    ///
    /// @see rcCompactHeightfield
    public static bool rcMedianFilterWalkableArea(rcContext ctx, rcCompactHeightfield chf)
    {
        Debug.Assert(ctx != null, "rcContext is null");

        int w = chf.width;
        int h = chf.height;

        ctx.startTimer(rcTimerLabel.RC_TIMER_MEDIAN_AREA);

        byte[] areas = new byte[chf.spanCount];//(byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP);
        if (areas == null)
        {
            ctx.log(rcLogCategory.RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' " + chf.spanCount);
            return(false);
        }

        // Init distance.
        for (int i = 0; i < chf.spanCount; ++i)
        {
            areas[i] = 0xff;
        }
        //memset(areas, 0xff, sizeof(byte)*chf.spanCount);

        for (int y = 0; y < h; ++y)
        {
            for (int x = 0; x < w; ++x)
            {
                rcCompactCell c = chf.cells[x + y * w];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    rcCompactSpan s = chf.spans[i];
                    if (chf.areas[i] == RC_NULL_AREA)
                    {
                        areas[i] = chf.areas[i];
                        continue;
                    }

                    byte[] nei = new byte[9];
                    for (int j = 0; j < 9; ++j)
                    {
                        nei[j] = chf.areas[i];
                    }

                    for (int dir = 0; dir < 4; ++dir)
                    {
                        if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                        {
                            int ax = x + rcGetDirOffsetX(dir);
                            int ay = y + rcGetDirOffsetY(dir);
                            int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, dir);
                            if (chf.areas[ai] != RC_NULL_AREA)
                            {
                                nei[dir * 2 + 0] = chf.areas[ai];
                            }

                            rcCompactSpan aSpan = chf.spans[ai];
                            int           dir2  = (dir + 1) & 0x3;
                            if (rcGetCon(aSpan, dir2) != RC_NOT_CONNECTED)
                            {
                                int ax2 = ax + rcGetDirOffsetX(dir2);
                                int ay2 = ay + rcGetDirOffsetY(dir2);
                                int ai2 = (int)chf.cells[ax2 + ay2 * w].index + rcGetCon(aSpan, dir2);
                                if (chf.areas[ai2] != RC_NULL_AREA)
                                {
                                    nei[dir * 2 + 1] = chf.areas[ai2];
                                }
                            }
                        }
                    }
                    insertSort(nei, 9);
                    areas[i] = nei[4];
                }
            }
        }

        chf.areas = areas;

        ctx.stopTimer(rcTimerLabel.RC_TIMER_MEDIAN_AREA);

        return(true);
    }
Example #9
0
    /// @par
    ///
    /// Basically, any spans that are closer to a boundary or obstruction than the specified radius
    /// are marked as unwalkable.
    ///
    /// This method is usually called immediately after the heightfield has been built.
    ///
    /// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius
    public static bool rcErodeWalkableArea(rcContext ctx, int radius, rcCompactHeightfield chf)
    {
        Debug.Assert(ctx != null, "rcContext is null");

        int w = chf.width;
        int h = chf.height;

        ctx.startTimer(rcTimerLabel.RC_TIMER_ERODE_AREA);

        byte[] dist = new byte[chf.spanCount];//(byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP);
        if (dist == null)
        {
            ctx.log(rcLogCategory.RC_LOG_ERROR, "erodeWalkableArea: Out of memory 'dist' " + chf.spanCount);
            return(false);
        }

        // Init distance.
        for (int i = 0; i < chf.spanCount; ++i)
        {
            dist[i] = 0xff;
        }
        //	memset(dist, 0xff, sizeof(byte)*chf.spanCount);

        // Mark boundary cells.
        for (int y = 0; y < h; ++y)
        {
            for (int x = 0; x < w; ++x)
            {
                rcCompactCell c = chf.cells[x + y * w];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    if (chf.areas[i] == RC_NULL_AREA)
                    {
                        dist[i] = 0;
                    }
                    else
                    {
                        rcCompactSpan s  = chf.spans[i];
                        int           nc = 0;
                        for (int dir = 0; dir < 4; ++dir)
                        {
                            if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                            {
                                int nx   = x + rcGetDirOffsetX(dir);
                                int ny   = y + rcGetDirOffsetY(dir);
                                int nidx = (int)chf.cells[nx + ny * w].index + rcGetCon(s, dir);
                                if (chf.areas[nidx] != RC_NULL_AREA)
                                {
                                    nc++;
                                }
                            }
                        }
                        // At least one missing neighbour.
                        if (nc != 4)
                        {
                            dist[i] = 0;
                        }
                    }
                }
            }
        }

        byte nd = 0;

        // Pass 1
        for (int y = 0; y < h; ++y)
        {
            for (int x = 0; x < w; ++x)
            {
                rcCompactCell c = chf.cells[x + y * w];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    rcCompactSpan s = chf.spans[i];

                    if (rcGetCon(s, 0) != RC_NOT_CONNECTED)
                    {
                        // (-1,0)
                        int           ax    = x + rcGetDirOffsetX(0);
                        int           ay    = y + rcGetDirOffsetY(0);
                        int           ai    = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 0);
                        rcCompactSpan aSpan = chf.spans[ai];
                        nd = (byte)Math.Min((int)dist[ai] + 2, 255);
                        if (nd < dist[i])
                        {
                            dist[i] = nd;
                        }

                        // (-1,-1)
                        if (rcGetCon(aSpan, 3) != RC_NOT_CONNECTED)
                        {
                            int aax = ax + rcGetDirOffsetX(3);
                            int aay = ay + rcGetDirOffsetY(3);
                            int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 3);
                            nd = (byte)Math.Min((int)dist[aai] + 3, 255);
                            if (nd < dist[i])
                            {
                                dist[i] = nd;
                            }
                        }
                    }
                    if (rcGetCon(s, 3) != RC_NOT_CONNECTED)
                    {
                        // (0,-1)
                        int           ax    = x + rcGetDirOffsetX(3);
                        int           ay    = y + rcGetDirOffsetY(3);
                        int           ai    = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 3);
                        rcCompactSpan aSpan = chf.spans[ai];
                        nd = (byte)Math.Min((int)dist[ai] + 2, 255);
                        if (nd < dist[i])
                        {
                            dist[i] = nd;
                        }

                        // (1,-1)
                        if (rcGetCon(aSpan, 2) != RC_NOT_CONNECTED)
                        {
                            int aax = ax + rcGetDirOffsetX(2);
                            int aay = ay + rcGetDirOffsetY(2);
                            int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 2);
                            nd = (byte)Math.Min((int)dist[aai] + 3, 255);
                            if (nd < dist[i])
                            {
                                dist[i] = nd;
                            }
                        }
                    }
                }
            }
        }

        // Pass 2
        for (int y = h - 1; y >= 0; --y)
        {
            for (int x = w - 1; x >= 0; --x)
            {
                rcCompactCell c = chf.cells[x + y * w];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    rcCompactSpan s = chf.spans[i];

                    if (rcGetCon(s, 2) != RC_NOT_CONNECTED)
                    {
                        // (1,0)
                        int           ax    = x + rcGetDirOffsetX(2);
                        int           ay    = y + rcGetDirOffsetY(2);
                        int           ai    = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 2);
                        rcCompactSpan aSpan = chf.spans[ai];
                        nd = (byte)Math.Min((int)dist[ai] + 2, 255);
                        if (nd < dist[i])
                        {
                            dist[i] = nd;
                        }

                        // (1,1)
                        if (rcGetCon(aSpan, 1) != RC_NOT_CONNECTED)
                        {
                            int aax = ax + rcGetDirOffsetX(1);
                            int aay = ay + rcGetDirOffsetY(1);
                            int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 1);
                            nd = (byte)Math.Min((int)dist[aai] + 3, 255);
                            if (nd < dist[i])
                            {
                                dist[i] = nd;
                            }
                        }
                    }
                    if (rcGetCon(s, 1) != RC_NOT_CONNECTED)
                    {
                        // (0,1)
                        int           ax    = x + rcGetDirOffsetX(1);
                        int           ay    = y + rcGetDirOffsetY(1);
                        int           ai    = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 1);
                        rcCompactSpan aSpan = chf.spans[ai];
                        nd = (byte)Math.Min((int)dist[ai] + 2, 255);
                        if (nd < dist[i])
                        {
                            dist[i] = nd;
                        }

                        // (-1,1)
                        if (rcGetCon(aSpan, 0) != RC_NOT_CONNECTED)
                        {
                            int aax = ax + rcGetDirOffsetX(0);
                            int aay = ay + rcGetDirOffsetY(0);
                            int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 0);
                            nd = (byte)Math.Min((int)dist[aai] + 3, 255);
                            if (nd < dist[i])
                            {
                                dist[i] = nd;
                            }
                        }
                    }
                }
            }
        }

        byte thr = (byte)(radius * 2);

        for (int i = 0; i < chf.spanCount; ++i)
        {
            if (dist[i] < thr)
            {
                chf.areas[i] = RC_NULL_AREA;
            }
        }

        ctx.stopTimer(rcTimerLabel.RC_TIMER_ERODE_AREA);

        return(true);
    }
Example #10
0
    /// @par
    ///
    /// See the #rcConfig documentation for more information on the configuration parameters.
    ///
    /// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig
    public static bool rcBuildHeightfieldLayers(rcContext ctx, rcCompactHeightfield chf,
                                                int borderSize, int walkableHeight,
                                                rcHeightfieldLayerSet lset)
    {
        Debug.Assert(ctx != null, "rcContext is null");

        ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_LAYERS);

        int w = chf.width;
        int h = chf.height;

        byte[] srcReg = new byte[chf.spanCount];
        if (srcReg == null)
        {
            ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' " + chf.spanCount);
            return(false);
        }

        for (int i = 0; i < chf.spanCount; ++i)
        {
            srcReg[i] = 0xff;
        }

        int nsweeps = chf.width;

        rcLayerSweepSpan[] sweeps = new rcLayerSweepSpan[nsweeps];
        if (sweeps == null)
        {
            ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' " + nsweeps);
            return(false);
        }


        // Partition walkable area into monotone regions.
        int[] prevCount = new int[256];
        byte  regId     = 0;

        for (int y = borderSize; y < h - borderSize; ++y)
        {
            //memset to 0 is done by C# alloc
            //memset(prevCount,0,sizeof(int)*regId);

            byte sweepId = 0;

            for (int x = borderSize; x < w - borderSize; ++x)
            {
                rcCompactCell c = chf.cells[x + y * w];

                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    rcCompactSpan s = chf.spans[i];
                    if (chf.areas[i] == RC_NULL_AREA)
                    {
                        continue;
                    }

                    byte sid = 0xff;

                    // -x
                    if (rcGetCon(s, 0) != RC_NOT_CONNECTED)
                    {
                        int ax = x + rcGetDirOffsetX(0);
                        int ay = y + rcGetDirOffsetY(0);
                        int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 0);
                        if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
                        {
                            sid = srcReg[ai];
                        }
                    }

                    if (sid == 0xff)
                    {
                        sid             = sweepId++;
                        sweeps[sid].nei = (byte)0xff;
                        sweeps[sid].ns  = 0;
                    }

                    // -y
                    if (rcGetCon(s, 3) != RC_NOT_CONNECTED)
                    {
                        int  ax = x + rcGetDirOffsetX(3);
                        int  ay = y + rcGetDirOffsetY(3);
                        int  ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 3);
                        byte nr = srcReg[ai];
                        if (nr != 0xff)
                        {
                            // Set neighbour when first valid neighbour is encoutered.
                            if (sweeps[sid].ns == 0)
                            {
                                sweeps[sid].nei = nr;
                            }

                            if (sweeps[sid].nei == nr)
                            {
                                // Update existing neighbour
                                sweeps[sid].ns++;
                                prevCount[nr]++;
                            }
                            else
                            {
                                // This is hit if there is nore than one neighbour.
                                // Invalidate the neighbour.
                                sweeps[sid].nei = 0xff;
                            }
                        }
                    }

                    srcReg[i] = sid;
                }
            }

            // Create unique ID.
            for (int i = 0; i < sweepId; ++i)
            {
                // If the neighbour is set and there is only one continuous connection to it,
                // the sweep will be merged with the previous one, else new region is created.
                if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns)
                {
                    sweeps[i].id = sweeps[i].nei;
                }
                else
                {
                    if (regId == 255)
                    {
                        ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow.");
                        return(false);
                    }
                    sweeps[i].id = regId++;
                }
            }

            // Remap local sweep ids to region ids.
            for (int x = borderSize; x < w - borderSize; ++x)
            {
                rcCompactCell c = chf.cells[x + y * w];
                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    if (srcReg[i] != 0xff)
                    {
                        srcReg[i] = sweeps[srcReg[i]].id;
                    }
                }
            }
        }

        // Allocate and init layer regions.
        int nregs = (int)regId;

        rcLayerRegion[] regs = new rcLayerRegion[nregs];
        if (regs == null)
        {
            ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' " + nregs);
            return(false);
        }
        //memset(regs, 0, sizeof(rcLayerRegion)*nregs);
        for (int i = 0; i < nregs; ++i)
        {
            regs[i].layerId = 0xff;
            regs[i].ymin    = 0xffff;
            regs[i].ymax    = 0;
        }

        // Find region neighbours and overlapping regions.
        for (int y = 0; y < h; ++y)
        {
            for (int x = 0; x < w; ++x)
            {
                rcCompactCell c = chf.cells[x + y * w];

                byte[] lregs  = new byte[RC_MAX_LAYERS];
                int    nlregs = 0;

                for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                {
                    rcCompactSpan s  = chf.spans[i];
                    byte          ri = srcReg[i];
                    if (ri == 0xff)
                    {
                        continue;
                    }

                    regs[ri].ymin = Math.Min(regs[ri].ymin, s.y);
                    regs[ri].ymax = Math.Max(regs[ri].ymax, s.y);

                    // Collect all region layers.
                    if (nlregs < RC_MAX_LAYERS)
                    {
                        lregs[nlregs++] = ri;
                    }

                    // Update neighbours
                    for (int dir = 0; dir < 4; ++dir)
                    {
                        if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                        {
                            int  ax  = x + rcGetDirOffsetX(dir);
                            int  ay  = y + rcGetDirOffsetY(dir);
                            int  ai  = (int)chf.cells[ax + ay * w].index + rcGetCon(s, dir);
                            byte rai = srcReg[ai];
                            if (rai != 0xff && rai != ri)
                            {
                                addUnique(regs[ri].neis, ref regs[ri].nneis, rai);
                            }
                        }
                    }
                }

                // Update overlapping regions.
                for (int i = 0; i < nlregs - 1; ++i)
                {
                    for (int j = i + 1; j < nlregs; ++j)
                    {
                        if (lregs[i] != lregs[j])
                        {
                            rcLayerRegion ri = regs[lregs[i]];
                            rcLayerRegion rj = regs[lregs[j]];
                            addUnique(ri.layers, ref ri.nlayers, lregs[j]);
                            addUnique(rj.layers, ref rj.nlayers, lregs[i]);
                        }
                    }
                }
            }
        }

        // Create 2D layers from regions.
        byte layerId = 0;

        const int MAX_STACK = 64;

        byte[] stack  = new byte[MAX_STACK];
        int    nstack = 0;

        for (int i = 0; i < nregs; ++i)
        {
            rcLayerRegion root = regs[i];
            // Skip alreadu visited.
            if (root.layerId != 0xff)
            {
                continue;
            }

            // Start search.
            root.layerId  = layerId;
            root.baseFlag = 1;

            nstack          = 0;
            stack[nstack++] = (byte)i;

            while (nstack != 0)
            {
                // Pop front
                rcLayerRegion reg = regs[stack[0]];
                nstack--;
                for (int j = 0; j < nstack; ++j)
                {
                    stack[j] = stack[j + 1];
                }

                int nneis = (int)reg.nneis;
                for (int j = 0; j < nneis; ++j)
                {
                    byte          nei  = reg.neis[j];
                    rcLayerRegion regn = regs[nei];
                    // Skip already visited.
                    if (regn.layerId != 0xff)
                    {
                        continue;
                    }
                    // Skip if the neighbour is overlapping root region.
                    if (contains(root.layers, root.nlayers, nei))
                    {
                        continue;
                    }
                    // Skip if the height range would become too large.
                    int ymin = Math.Min(root.ymin, regn.ymin);
                    int ymax = Math.Max(root.ymax, regn.ymax);
                    if ((ymax - ymin) >= 255)
                    {
                        continue;
                    }

                    if (nstack < MAX_STACK)
                    {
                        // Deepen
                        stack[nstack++] = (byte)nei;

                        // Mark layer id
                        regn.layerId = layerId;
                        // Merge current layers to root.
                        for (int k = 0; k < regn.nlayers; ++k)
                        {
                            addUnique(root.layers, ref root.nlayers, regn.layers[k]);
                        }
                        root.ymin = Math.Min(root.ymin, regn.ymin);
                        root.ymax = Math.Max(root.ymax, regn.ymax);
                    }
                }
            }

            layerId++;
        }

        // Merge non-overlapping regions that are close in height.
        ushort mergeHeight = (ushort)(walkableHeight * 4);

        for (int i = 0; i < nregs; ++i)
        {
            rcLayerRegion ri = regs[i];
            if (ri.baseFlag == 0)
            {
                continue;
            }

            byte newId = ri.layerId;

            for (; ;)
            {
                byte oldId = 0xff;

                for (int j = 0; j < nregs; ++j)
                {
                    if (i == j)
                    {
                        continue;
                    }
                    rcLayerRegion rj = regs[j];
                    if (rj.baseFlag == 0)
                    {
                        continue;
                    }

                    // Skip if teh regions are not close to each other.
                    if (!overlapRange(ri.ymin,
                                      (ushort)(ri.ymax + mergeHeight),
                                      rj.ymin,
                                      (ushort)(rj.ymax + mergeHeight)))
                    {
                        continue;
                    }
                    // Skip if the height range would become too large.
                    int ymin = Math.Min(ri.ymin, rj.ymin);
                    int ymax = Math.Max(ri.ymax, rj.ymax);
                    if ((ymax - ymin) >= 255)
                    {
                        continue;
                    }

                    // Make sure that there is no overlap when mergin 'ri' and 'rj'.
                    bool overlap = false;
                    // Iterate over all regions which have the same layerId as 'rj'
                    for (int k = 0; k < nregs; ++k)
                    {
                        if (regs[k].layerId != rj.layerId)
                        {
                            continue;
                        }
                        // Check if region 'k' is overlapping region 'ri'
                        // Index to 'regs' is the same as region id.
                        if (contains(ri.layers, ri.nlayers, (byte)k))
                        {
                            overlap = true;
                            break;
                        }
                    }
                    // Cannot merge of regions overlap.
                    if (overlap)
                    {
                        continue;
                    }

                    // Can merge i and j.
                    oldId = rj.layerId;
                    break;
                }

                // Could not find anything to merge with, stop.
                if (oldId == 0xff)
                {
                    break;
                }

                // Merge
                for (int j = 0; j < nregs; ++j)
                {
                    rcLayerRegion rj = regs[j];
                    if (rj.layerId == oldId)
                    {
                        rj.baseFlag = 0;
                        // Remap layerIds.
                        rj.layerId = newId;
                        // Add overlaid layers from 'rj' to 'ri'.
                        for (int k = 0; k < rj.nlayers; ++k)
                        {
                            addUnique(ri.layers, ref ri.nlayers, rj.layers[k]);
                        }
                        // Update heigh bounds.
                        ri.ymin = Math.Min(ri.ymin, rj.ymin);
                        ri.ymax = Math.Max(ri.ymax, rj.ymax);
                    }
                }
            }
        }

        // Compact layerIds
        byte[] remap = new byte[256];
        //memset(remap, 0, 256);

        // Find number of unique layers.
        layerId = 0;
        for (int i = 0; i < nregs; ++i)
        {
            remap[regs[i].layerId] = 1;
        }
        for (int i = 0; i < 256; ++i)
        {
            if (remap[i] != 0)
            {
                remap[i] = layerId++;
            }
            else
            {
                remap[i] = 0xff;
            }
        }
        // Remap ids.
        for (int i = 0; i < nregs; ++i)
        {
            regs[i].layerId = remap[regs[i].layerId];
        }

        // No layers, return empty.
        if (layerId == 0)
        {
            ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_LAYERS);
            return(true);
        }

        // Create layers.
        Debug.Assert(lset.layers == null, "Assert lset.layers == 0");

        int lw = w - borderSize * 2;
        int lh = h - borderSize * 2;

        // Build contracted bbox for layers.
        float[] bmin = new float[3];
        float[] bmax = new float[3];
        rcVcopy(bmin, chf.bmin);
        rcVcopy(bmax, chf.bmax);
        bmin[0] += borderSize * chf.cs;
        bmin[2] += borderSize * chf.cs;
        bmax[0] -= borderSize * chf.cs;
        bmax[2] -= borderSize * chf.cs;

        lset.nlayers = (int)layerId;

        //lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM);
        lset.layers = new rcHeightfieldLayer[lset.nlayers];
        if (lset.layers == null)
        {
            ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' " + lset.nlayers);
            return(false);
        }
        //memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers);


        // Store layers.
        for (int i = 0; i < lset.nlayers; ++i)
        {
            byte curId = (byte)i;

            // Allocate memory for the current layer.
            rcHeightfieldLayer layer = lset.layers[i];
            //memset(layer, 0, sizeof(rcHeightfieldLayer));

            int gridSize = sizeof(byte) * lw * lh;

            layer.heights = new byte[gridSize];//(byte*)rcAlloc(gridSize, RC_ALLOC_PERM);
            if (layer.heights == null)
            {
                ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' " + gridSize);
                return(false);
            }
            //memset(layer.heights, 0xff, gridSize);
            for (int j = 0; j < gridSize; ++j)
            {
                layer.heights[j] = 0xFF;
            }

            layer.areas = new byte[gridSize];// (byte*)rcAlloc(gridSize, RC_ALLOC_PERM);
            if (layer.areas == null)
            {
                ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' " + gridSize);
                return(false);
            }
            //memset(layer.areas, 0, gridSize);

            layer.cons = new byte[gridSize];//  (byte*)rcAlloc(gridSize, RC_ALLOC_PERM);
            if (layer.cons == null)
            {
                ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' " + gridSize);
                return(false);
            }
            //memset(layer.cons, 0, gridSize);

            // Find layer height bounds.
            int hmin = 0, hmax = 0;
            for (int j = 0; j < nregs; ++j)
            {
                if (regs[j].baseFlag != 0 && regs[j].layerId == curId)
                {
                    hmin = (int)regs[j].ymin;
                    hmax = (int)regs[j].ymax;
                }
            }

            layer.width  = lw;
            layer.height = lh;
            layer.cs     = chf.cs;
            layer.ch     = chf.ch;

            // Adjust the bbox to fit the heighfield.
            rcVcopy(layer.bmin, bmin);
            rcVcopy(layer.bmax, bmax);
            layer.bmin[1] = bmin[1] + hmin * chf.ch;
            layer.bmax[1] = bmin[1] + hmax * chf.ch;
            layer.hmin    = hmin;
            layer.hmax    = hmax;

            // Update usable data region.
            layer.minx = layer.width;
            layer.maxx = 0;
            layer.miny = layer.height;
            layer.maxy = 0;

            // Copy height and area from compact heighfield.
            for (int y = 0; y < lh; ++y)
            {
                for (int x = 0; x < lw; ++x)
                {
                    int           cx = borderSize + x;
                    int           cy = borderSize + y;
                    rcCompactCell c  = chf.cells[cx + cy * w];
                    for (int j = (int)c.index, nj = (int)(c.index + c.count); j < nj; ++j)
                    {
                        rcCompactSpan s = chf.spans[j];
                        // Skip unassigned regions.
                        if (srcReg[j] == 0xff)
                        {
                            continue;
                        }
                        // Skip of does nto belong to current layer.
                        byte lid = regs[srcReg[j]].layerId;
                        if (lid != curId)
                        {
                            continue;
                        }

                        // Update data bounds.
                        layer.minx = Math.Min(layer.minx, x);
                        layer.maxx = Math.Max(layer.maxx, x);
                        layer.miny = Math.Min(layer.miny, y);
                        layer.maxy = Math.Max(layer.maxy, y);

                        // Store height and area type.
                        int idx = x + y * lw;
                        layer.heights[idx] = (byte)(s.y - hmin);
                        layer.areas[idx]   = chf.areas[j];

                        // Check connection.
                        byte portal = 0;
                        byte con    = 0;
                        for (int dir = 0; dir < 4; ++dir)
                        {
                            if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                            {
                                int  ax   = cx + rcGetDirOffsetX(dir);
                                int  ay   = cy + rcGetDirOffsetY(dir);
                                int  ai   = (int)chf.cells[ax + ay * w].index + rcGetCon(s, dir);
                                byte alid = srcReg[ai] != (byte)0xff ? regs[srcReg[ai]].layerId : (byte)0xff;
                                // Portal mask
                                if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
                                {
                                    portal |= (byte)(1 << dir);
                                    // Update height so that it matches on both sides of the portal.
                                    rcCompactSpan aSpan = chf.spans[ai];
                                    if (aSpan.y > hmin)
                                    {
                                        layer.heights[idx] = Math.Max(layer.heights[idx], (byte)(aSpan.y - hmin));
                                    }
                                }
                                // Valid connection mask
                                if (chf.areas[ai] != RC_NULL_AREA && lid == alid)
                                {
                                    int nx = ax - borderSize;
                                    int ny = ay - borderSize;
                                    if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
                                    {
                                        con |= (byte)(1 << dir);
                                    }
                                }
                            }
                        }

                        layer.cons[idx] = (byte)((portal << 4) | con);
                    }
                }
            }

            if (layer.minx > layer.maxx)
            {
                layer.minx = layer.maxx = 0;
            }
            if (layer.miny > layer.maxy)
            {
                layer.miny = layer.maxy = 0;
            }
        }

        ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_LAYERS);

        return(true);
    }
        /// @par
        ///
        /// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen
        /// parameters control how closely the simplified contours will match the raw contours.
        ///
        /// Simplified contours are generated such that the vertices for portals between areas match up.
        /// (They are considered mandatory vertices.)
        ///
        /// Setting @p maxEdgeLength to zero will disabled the edge length feature.
        ///
        /// See the #rcConfig documentation for more information on the configuration parameters.
        ///
        /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig
        public static bool rcBuildContours(rcContext ctx, rcCompactHeightfield chf,
                                           float maxError, int maxEdgeLen,
                                           rcContourSet cset, int buildFlags)
        {
            Debug.Assert(ctx != null, "rcContext is null");

            int w          = chf.width;
            int h          = chf.height;
            int borderSize = chf.borderSize;

            ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS);

            rcVcopy(cset.bmin, chf.bmin);
            rcVcopy(cset.bmax, chf.bmax);
            if (borderSize > 0)
            {
                // If the heightfield was build with bordersize, remove the offset.
                float pad = borderSize * chf.cs;
                cset.bmin[0] += pad;
                cset.bmin[2] += pad;
                cset.bmax[0] -= pad;
                cset.bmax[2] -= pad;
            }
            cset.cs         = chf.cs;
            cset.ch         = chf.ch;
            cset.width      = chf.width - chf.borderSize * 2;
            cset.height     = chf.height - chf.borderSize * 2;
            cset.borderSize = chf.borderSize;

            int maxContours = Math.Max((int)chf.maxRegions, 8);

            //cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM);
            cset.conts = new rcContour[maxContours];
            //if (cset.conts == null)
//		return false;
            cset.nconts = 0;

            //rcScopedDelete<byte> flags = (byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP);
            byte[] flags = new byte[chf.spanCount];
            if (flags == null)
            {
                ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' " + chf.spanCount);
                return(false);
            }

            ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);

            // Mark boundaries.
            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    rcCompactCell c = chf.cells[x + y * w];
                    for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i)
                    {
                        byte          res = 0;
                        rcCompactSpan s   = chf.spans[i];
                        if (chf.spans[i].reg == 0 || (chf.spans[i].reg & RC_BORDER_REG) != 0)
                        {
                            flags[i] = 0;
                            continue;
                        }
                        for (int dir = 0; dir < 4; ++dir)
                        {
                            ushort r = 0;
                            if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                            {
                                int ax = x + rcGetDirOffsetX(dir);
                                int ay = y + rcGetDirOffsetY(dir);
                                int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, dir);
                                r = chf.spans[ai].reg;
                            }
                            if (r == chf.spans[i].reg)
                            {
                                res |= (byte)(1 << dir);
                            }
                        }
                        flags[i] = (byte)(res ^ 0xf); // Inverse, mark non connected edges.
                    }
                }
            }

            ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);

            //List<int> verts(256);
            List <int> verts = new List <int>();

            verts.Capacity = 256;
            //List<int> simplified(64);
            List <int> simplified = new List <int>();

            simplified.Capacity = 64;

            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    rcCompactCell c = chf.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] = 0;
                            continue;
                        }
                        ushort reg = chf.spans[i].reg;
                        if (reg == 0 || (reg & RC_BORDER_REG) != 0)
                        {
                            continue;
                        }
                        byte area = chf.areas[i];

                        //verts.resize(0);
                        //simplified.resize(0);
                        verts.Clear();
                        simplified.Clear();

                        ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);
                        walkContour(x, y, i, chf, flags, verts);
                        ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE);

                        ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY);
                        simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags);
                        removeDegenerateSegments(simplified);
                        ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY);


                        // Store region.contour remap info.
                        // Create contour.
                        if (simplified.Count / 4 >= 3)
                        {
                            if (cset.nconts >= maxContours)
                            {
                                // Allocate more contours.
                                // This can happen when there are tiny holes in the heightfield.
                                int oldMax = maxContours;
                                maxContours *= 2;
                                rcContour[] newConts = new rcContour[maxContours];// (rcContour*)rcAlloc(sizeof(rcContour) * maxContours, RC_ALLOC_PERM);
                                for (int j = 0; j < cset.nconts; ++j)
                                {
                                    newConts[j] = cset.conts[j];
                                    // Reset source pointers to prevent data deletion.
                                    cset.conts[j].verts  = null;
                                    cset.conts[j].rverts = null;
                                }
                                //rcFree(cset.conts);
                                cset.conts = newConts;

                                ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Expanding max contours from " + oldMax + " to " + maxContours);
                            }

                            int contId = cset.nconts;
                            cset.nconts++;
                            rcContour cont = cset.conts[contId];

                            cont.nverts = simplified.Count / 4;
                            cont.verts  = new int[cont.nverts * 4]; //(int*)rcAlloc(sizeof(int)*cont.nverts*4, RC_ALLOC_PERM);
                            if (cont.verts == null)
                            {
                                ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' " + cont.nverts);
                                return(false);
                            }
                            //memcpy(cont.verts, &simplified[0], sizeof(int)*cont.nverts*4);
                            for (int j = 0; j < cont.nverts * 4; ++j)
                            {
                                cont.verts[j] = simplified[j];
                            }
                            if (borderSize > 0)
                            {
                                // If the heightfield was build with bordersize, remove the offset.
                                for (int j = 0; j < cont.nverts; ++j)
                                {
                                    //int* v = &cont.verts[j*4];
                                    cont.verts[j * 4]     -= borderSize;
                                    cont.verts[j * 4 + 2] -= borderSize;
                                    //v[0] -= borderSize;
                                    //v[2] -= borderSize;
                                }
                            }

                            cont.nrverts = verts.Count / 4;
                            cont.rverts  = new int[cont.nrverts * 4];//(int*)rcAlloc(sizeof(int)*cont.nrverts*4, RC_ALLOC_PERM);
                            if (cont.rverts == null)
                            {
                                ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' " + cont.nrverts);
                                return(false);
                            }
                            //memcpy(cont.rverts, &verts[0], sizeof(int)*cont.nrverts*4);
                            for (int j = 0; j < cont.nrverts * 4; ++j)
                            {
                                cont.rverts[j] = verts[j];
                            }
                            if (borderSize > 0)
                            {
                                // If the heightfield was build with bordersize, remove the offset.
                                for (int j = 0; j < cont.nrverts; ++j)
                                {
                                    //int* v = &cont.rverts[j*4];
                                    cont.rverts[j * 4]     -= borderSize;
                                    cont.rverts[j * 4 + 2] -= borderSize;
                                }
                            }

/*					cont.cx = cont.cy = cont.cz = 0;
 *                                      for (int i = 0; i < cont.nverts; ++i)
 *                                      {
 *                                              cont.cx += cont.verts[i*4+0];
 *                                              cont.cy += cont.verts[i*4+1];
 *                                              cont.cz += cont.verts[i*4+2];
 *                                      }
 *                                      cont.cx /= cont.nverts;
 *                                      cont.cy /= cont.nverts;
 *                                      cont.cz /= cont.nverts;*/

                            cont.reg  = reg;
                            cont.area = area;

                            cset.conts[contId] = cont;
                        }
                    }
                }
            }

            // Check and merge droppings.
            // Sometimes the previous algorithms can fail and create several contours
            // per area. This pass will try to merge the holes into the main region.
            for (int i = 0; i < cset.nconts; ++i)
            {
                rcContour cont = cset.conts[i];
                // Check if the contour is would backwards.
                if (calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0)
                {
                    // Find another contour which has the same region ID.
                    int mergeIdx = -1;
                    for (int j = 0; j < cset.nconts; ++j)
                    {
                        if (i == j)
                        {
                            continue;
                        }
                        if (cset.conts[j].nverts != 0 && cset.conts[j].reg == cont.reg)
                        {
                            // Make sure the polygon is correctly oriented.
                            if (calcAreaOfPolygon2D(cset.conts[j].verts, cset.conts[j].nverts) != 0)
                            {
                                mergeIdx = j;
                                break;
                            }
                        }
                    }
                    if (mergeIdx == -1)
                    {
                        ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Could not find merge target for bad contour " + i);
                    }
                    else
                    {
                        rcContour mcont = cset.conts[mergeIdx];
                        // Merge by closest points.
                        int ia = 0, ib = 0;
                        getClosestIndices(mcont.verts, mcont.nverts, cont.verts, cont.nverts, ref ia, ref ib);
                        if (ia == -1 || ib == -1)
                        {
                            ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Failed to find merge points for " + i + " and " + mergeIdx);
                            continue;
                        }
                        if (!mergeContours(ref mcont, ref cont, ia, ib))
                        {
                            ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Failed to merge contours " + i + " and " + mergeIdx);
                            continue;
                        }
                        cset.conts[mergeIdx] = mcont;
                        cset.conts[i]        = cont;
                    }
                }
            }

            ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS);

            return(true);
        }