/// Sets the neighbor connection data for the specified direction.
        ///  @param[in]		s		The span to update.
        ///  @param[in]		dir		The direction to set. [Limits: 0 <= value < 4]
        ///  @param[in]		i		The index of the neighbor span.
        public static void SetCon(CompactSpan s, int dir, int i)
        {
            int shift = dir * 6;
            int con   = s.con;

            s.con = (con & ~(0x3f << shift)) | ((i & 0x3f) << shift);
        }
        /// @par
        ///
        /// The value of spacial parameters are in world units.
        ///
        /// @see rcCompactHeightfield, rcMedianFilterWalkableArea
        public virtual void markCylinderArea(Context ctx, float[] pos, float r, float h, int areaId, CompactHeightfield chf)
        {
            ctx.startTimer("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)
                {
                    CompactCell c = chf.cells[x + z * chf.width];
                    for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
                    {
                        CompactSpan s = chf.spans[i];

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

                        if (s.y >= miny && 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("MARK_CYLINDER_AREA");
        }
        /// Gets neighbor connection data for the specified direction.
        ///  @param[in]		s		The span to check.
        ///  @param[in]		dir		The direction to check. [Limits: 0 <= value < 4]
        ///  @return The neighbor connection data for the specified direction,
        ///     or #RC_NOT_CONNECTED if there is no connection.
        internal static int GetCon(CompactSpan s, int dir)
        {
            int shift = dir * 6;

            return((s.con >> shift) & 0x3f);
        }
        /// @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 void erodeWalkableArea(Context ctx, int radius, CompactHeightfield chf)
        {
            int w = chf.width;
            int h = chf.height;

            ctx.startTimer("ERODE_AREA");

            int[] dist = new int[chf.spanCount];
            Arrays.fill(dist, 255);
            // Mark boundary cells.
            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    CompactCell c = chf.cells[x + y * w];
                    for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
                    {
                        if (chf.areas[i] == RecastConstants.RC_NULL_AREA)
                        {
                            dist[i] = 0;
                        }
                        else
                        {
                            CompactSpan s  = chf.spans[i];
                            int         nc = 0;
                            for (int dir = 0; dir < 4; ++dir)
                            {
                                if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED)
                                {
                                    int nx   = x + RecastCommon.GetDirOffsetX(dir);
                                    int ny   = y + RecastCommon.GetDirOffsetY(dir);
                                    int nidx = chf.cells[nx + ny * w].index + RecastCommon.GetCon(s, dir);
                                    if (chf.areas[nidx] != RecastConstants.RC_NULL_AREA)
                                    {
                                        nc++;
                                    }
                                }
                            }
                            // At least one missing neighbour.
                            if (nc != 4)
                            {
                                dist[i] = 0;
                            }
                        }
                    }
                }
            }

            int nd;

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

                        if (RecastCommon.GetCon(s, 0) != RecastConstants.RC_NOT_CONNECTED)
                        {
                            // (-1,0)
                            int         ax  = x + RecastCommon.GetDirOffsetX(0);
                            int         ay  = y + RecastCommon.GetDirOffsetY(0);
                            int         ai  = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 0);
                            CompactSpan @as = chf.spans[ai];
                            nd = Math.Min(dist[ai] + 2, 255);
                            if (nd < dist[i])
                            {
                                dist[i] = nd;
                            }

                            // (-1,-1)
                            if (RecastCommon.GetCon(@as, 3) != RecastConstants.RC_NOT_CONNECTED)
                            {
                                int aax = ax + RecastCommon.GetDirOffsetX(3);
                                int aay = ay + RecastCommon.GetDirOffsetY(3);
                                int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 3);
                                nd = Math.Min(dist[aai] + 3, 255);
                                if (nd < dist[i])
                                {
                                    dist[i] = nd;
                                }
                            }
                        }
                        if (RecastCommon.GetCon(s, 3) != RecastConstants.RC_NOT_CONNECTED)
                        {
                            // (0,-1)
                            int         ax  = x + RecastCommon.GetDirOffsetX(3);
                            int         ay  = y + RecastCommon.GetDirOffsetY(3);
                            int         ai  = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 3);
                            CompactSpan @as = chf.spans[ai];
                            nd = Math.Min(dist[ai] + 2, 255);
                            if (nd < dist[i])
                            {
                                dist[i] = nd;
                            }

                            // (1,-1)
                            if (RecastCommon.GetCon(@as, 2) != RecastConstants.RC_NOT_CONNECTED)
                            {
                                int aax = ax + RecastCommon.GetDirOffsetX(2);
                                int aay = ay + RecastCommon.GetDirOffsetY(2);
                                int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 2);
                                nd = Math.Min(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)
                {
                    CompactCell c = chf.cells[x + y * w];
                    for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
                    {
                        CompactSpan s = chf.spans[i];

                        if (RecastCommon.GetCon(s, 2) != RecastConstants.RC_NOT_CONNECTED)
                        {
                            // (1,0)
                            int         ax  = x + RecastCommon.GetDirOffsetX(2);
                            int         ay  = y + RecastCommon.GetDirOffsetY(2);
                            int         ai  = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 2);
                            CompactSpan @as = chf.spans[ai];
                            nd = Math.Min(dist[ai] + 2, 255);
                            if (nd < dist[i])
                            {
                                dist[i] = nd;
                            }

                            // (1,1)
                            if (RecastCommon.GetCon(@as, 1) != RecastConstants.RC_NOT_CONNECTED)
                            {
                                int aax = ax + RecastCommon.GetDirOffsetX(1);
                                int aay = ay + RecastCommon.GetDirOffsetY(1);
                                int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 1);
                                nd = Math.Min(dist[aai] + 3, 255);
                                if (nd < dist[i])
                                {
                                    dist[i] = nd;
                                }
                            }
                        }
                        if (RecastCommon.GetCon(s, 1) != RecastConstants.RC_NOT_CONNECTED)
                        {
                            // (0,1)
                            int         ax  = x + RecastCommon.GetDirOffsetX(1);
                            int         ay  = y + RecastCommon.GetDirOffsetY(1);
                            int         ai  = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 1);
                            CompactSpan @as = chf.spans[ai];
                            nd = Math.Min(dist[ai] + 2, 255);
                            if (nd < dist[i])
                            {
                                dist[i] = nd;
                            }

                            // (-1,1)
                            if (RecastCommon.GetCon(@as, 0) != RecastConstants.RC_NOT_CONNECTED)
                            {
                                int aax = ax + RecastCommon.GetDirOffsetX(0);
                                int aay = ay + RecastCommon.GetDirOffsetY(0);
                                int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 0);
                                nd = Math.Min(dist[aai] + 3, 255);
                                if (nd < dist[i])
                                {
                                    dist[i] = nd;
                                }
                            }
                        }
                    }
                }
            }

            int thr = radius * 2;

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

            ctx.stopTimer("ERODE_AREA");
        }
        /// @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 markConvexPolyArea(Context ctx, float[] verts, float hmin, float hmax, int areaId, CompactHeightfield chf)
        {
            ctx.startTimer("MARK_CONVEXPOLY_AREA");

            float[] bmin = new float[3]; float[] bmax = new float[3];
            RecastVectors.copy(bmin, verts, 0);
            RecastVectors.copy(bmax, verts, 0);
            for (int i = 1; i < verts.Length; ++i)
            {
                RecastVectors.min(bmin, verts, i * 3);
                RecastVectors.max(bmax, verts, i * 3);
            }
            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)
                {
                    CompactCell c = chf.cells[x + z * chf.width];
                    for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
                    {
                        CompactSpan s = chf.spans[i];
                        if (chf.areas[i] == RecastConstants.RC_NULL_AREA)
                        {
                            continue;
                        }
                        if (s.y >= miny && 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(verts, p))
                            {
                                chf.areas[i] = areaId;
                            }
                        }
                    }
                }
            }

            ctx.stopTimer("MARK_CONVEXPOLY_AREA");
        }
        /// @par
        ///
        /// This filter is usually applied after applying area id's using functions
        /// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea.
        ///
        /// @see rcCompactHeightfield
        public virtual bool medianFilterWalkableArea(Context ctx, CompactHeightfield chf)
        {
            int w = chf.width;
            int h = chf.height;

            ctx.startTimer("MEDIAN_AREA");

            int[] areas = new int[chf.spanCount];

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

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

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

                                CompactSpan @as  = chf.spans[ai];
                                int         dir2 = (dir + 1) & 0x3;
                                if (RecastCommon.GetCon(@as, dir2) != RecastConstants.RC_NOT_CONNECTED)
                                {
                                    int ax2 = ax + RecastCommon.GetDirOffsetX(dir2);
                                    int ay2 = ay + RecastCommon.GetDirOffsetY(dir2);
                                    int ai2 = chf.cells[ax2 + ay2 * w].index + RecastCommon.GetCon(@as, dir2);
                                    if (chf.areas[ai2] != RecastConstants.RC_NULL_AREA)
                                    {
                                        nei[dir * 2 + 1] = chf.areas[ai2];
                                    }
                                }
                            }
                        }
                        Array.Sort(nei);
                        areas[i] = nei[4];
                    }
                }
            }
            chf.areas = areas;

            ctx.stopTimer("MEDIAN_AREA");

            return(true);
        }
        /// @par
        ///
        /// The value of spacial parameters are in world units.
        ///
        /// @see rcCompactHeightfield, rcMedianFilterWalkableArea
        public virtual void markBoxArea(Context ctx, float[] bmin, float[] bmax, int areaId, CompactHeightfield chf)
        {
            ctx.startTimer("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)
                {
                    CompactCell c = chf.cells[x + z * chf.width];
                    for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
                    {
                        CompactSpan s = chf.spans[i];
                        if (s.y >= miny && s.y <= maxy)
                        {
                            if (chf.areas[i] != RecastConstants.RC_NULL_AREA)
                            {
                                chf.areas[i] = areaId;
                            }
                        }
                    }
                }
            }

            ctx.stopTimer("MARK_BOX_AREA");
        }
        public static HeightfieldLayerSet buildHeightfieldLayers(Context ctx, CompactHeightfield chf, int borderSize, int walkableHeight)
        {
            ctx.startTimer("RC_TIMER_BUILD_LAYERS");
            int w = chf.width;
            int h = chf.height;

            int[] srcReg = new int[chf.spanCount];
            Arrays.fill(srcReg, 0xFF);
            int nsweeps = chf.width;             // Math.max(chf.width, chf.height);

            SweepSpan[] sweeps = new SweepSpan[nsweeps];
            for (int i = 0; i < sweeps.Length; i++)
            {
                sweeps[i] = new SweepSpan();
            }
            // Partition walkable area into monotone regions.
            int[] prevCount = new int[256];
            int   regId     = 0;

            // Sweep one line at a time.
            for (int y = borderSize; y < h - borderSize; ++y)
            {
                // Collect spans from this row.
                Arrays.fill(prevCount, 0, regId, 0);
                int sweepId = 0;

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

                    for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
                    {
                        CompactSpan s = chf.spans[i];
                        if (chf.areas[i] == RecastConstants.RC_NULL_AREA)
                        {
                            continue;
                        }
                        int sid = 0xFF;
                        // -x

                        if (RecastCommon.GetCon(s, 0) != RecastConstants.RC_NOT_CONNECTED)
                        {
                            int ax = x + RecastCommon.GetDirOffsetX(0);
                            int ay = y + RecastCommon.GetDirOffsetY(0);
                            int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 0);
                            if (chf.areas[ai] != RecastConstants.RC_NULL_AREA && srcReg[ai] != 0xff)
                            {
                                sid = srcReg[ai];
                            }
                        }

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

                        // -y
                        if (RecastCommon.GetCon(s, 3) != RecastConstants.RC_NOT_CONNECTED)
                        {
                            int ax = x + RecastCommon.GetDirOffsetX(3);
                            int ay = y + RecastCommon.GetDirOffsetY(3);
                            int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 3);
                            int 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] == sweeps[i].ns)
                    {
                        sweeps[i].id = sweeps[i].nei;
                    }
                    else
                    {
                        if (regId == 255)
                        {
                            throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
                        }
                        sweeps[i].id = regId++;
                    }
                }

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

            LayerRegion[] regs = new LayerRegion[nregs];

            // Construct regions
            for (int i = 0; i < nregs; ++i)
            {
                regs[i] = new LayerRegion(i);
            }

            // Find region neighbours and overlapping regions.
            List <int> lregs = new List <int>();

            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    CompactCell c = chf.cells[x + y * w];

                    lregs.Clear();

                    for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
                    {
                        CompactSpan s  = chf.spans[i];
                        int         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.
                        lregs.Add(ri);

                        // Update neighbours
                        for (int dir = 0; dir < 4; ++dir)
                        {
                            if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED)
                            {
                                int ax  = x + RecastCommon.GetDirOffsetX(dir);
                                int ay  = y + RecastCommon.GetDirOffsetY(dir);
                                int ai  = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, dir);
                                int rai = srcReg[ai];
                                if (rai != 0xff && rai != ri)
                                {
                                    addUnique(regs[ri].neis, rai);
                                }
                            }
                        }
                    }

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

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

            List <int> stack = new List <int>();

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

                // Start search.
                root.layerId = layerId;
                root.@base   = true;

                stack.Add(i);

                while (stack.Count > 0)
                {
                    // Pop front
                    LayerRegion reg = regs[stack[0]];
                    stack.RemoveAt(0);

                    foreach (int nei in reg.neis)
                    {
                        LayerRegion regn = regs[nei];
                        // Skip already visited.
                        if (regn.layerId != 0xff)
                        {
                            continue;
                        }
                        // Skip if the neighbour is overlapping root region.
                        if (contains(root.layers, 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;
                        }

                        // Deepen
                        stack.Add(nei);

                        // Mark layer id
                        regn.layerId = layerId;
                        // Merge current layers to root.
                        foreach (int layer in regn.layers)
                        {
                            addUnique(root.layers, layer);
                        }
                        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.
            int mergeHeight = walkableHeight * 4;

            for (int i = 0; i < nregs; ++i)
            {
                LayerRegion ri = regs[i];
                if (!ri.@base)
                {
                    continue;
                }

                int newId = ri.layerId;

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

                    for (int j = 0; j < nregs; ++j)
                    {
                        if (i == j)
                        {
                            continue;
                        }
                        LayerRegion rj = regs[j];
                        if (!rj.@base)
                        {
                            continue;
                        }

                        // Skip if the regions are not close to each other.
                        if (!overlapRange(ri.ymin, ri.ymax + mergeHeight, rj.ymin, 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 merging '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, 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)
                    {
                        LayerRegion rj = regs[j];
                        if (rj.layerId == oldId)
                        {
                            rj.@base = false;
                            // Remap layerIds.
                            rj.layerId = newId;
                            // Add overlaid layers from 'rj' to 'ri'.
                            foreach (int layer in rj.layers)
                            {
                                addUnique(ri.layers, layer);
                            }
                            // Update height bounds.
                            ri.ymin = Math.Min(ri.ymin, rj.ymin);
                            ri.ymax = Math.Max(ri.ymax, rj.ymax);
                        }
                    }
                }
            }

            // Compact layerIds
            int[] remap = new int[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(RC_TIMER_BUILD_LAYERS);
                return(null);
            }

            // Create layers.
            // rcAssert(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];
            RecastVectors.copy(bmin, chf.bmin);
            RecastVectors.copy(bmax, chf.bmax);
            bmin[0] += borderSize * chf.cs;
            bmin[2] += borderSize * chf.cs;
            bmax[0] -= borderSize * chf.cs;
            bmax[2] -= borderSize * chf.cs;

            HeightfieldLayerSet lset = new HeightfieldLayerSet();

            lset.layers = new HeightfieldLayer[layerId];
            for (int i = 0; i < lset.layers.Length; i++)
            {
                lset.layers[i] = new HeightfieldLayer();
            }

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

                HeightfieldLayer layer = lset.layers[i];

                int gridSize = lw * lh;

                layer.heights = new int[gridSize];
                Arrays.fill(layer.heights, 0xFF);
                layer.areas = new int[gridSize];
                layer.cons  = new int[gridSize];

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

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

                // Adjust the bbox to fit the heightfield.
                RecastVectors.copy(layer.bmin, bmin);
                RecastVectors.copy(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 heightfield.
                for (int y = 0; y < lh; ++y)
                {
                    for (int x = 0; x < lw; ++x)
                    {
                        int         cx = borderSize + x;
                        int         cy = borderSize + y;
                        CompactCell c  = chf.cells[cx + cy * w];
                        for (int j = c.index, nj = c.index + c.count; j < nj; ++j)
                        {
                            CompactSpan s = chf.spans[j];
                            // Skip unassigned regions.
                            if (srcReg[j] == 0xff)
                            {
                                continue;
                            }
                            // Skip of does nto belong to current layer.
                            int 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] = (char)(s.y - hmin);
                            layer.areas[idx]   = chf.areas[j];

                            // Check connection.
                            char portal = (char)0;
                            char con    = (char)0;
                            for (int dir = 0; dir < 4; ++dir)
                            {
                                if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED)
                                {
                                    int ax   = cx + RecastCommon.GetDirOffsetX(dir);
                                    int ay   = cy + RecastCommon.GetDirOffsetY(dir);
                                    int ai   = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, dir);
                                    int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
                                    // Portal mask
                                    if (chf.areas[ai] != RecastConstants.RC_NULL_AREA && lid != alid)
                                    {
                                        portal |= (char)(1 << dir);
                                        // Update height so that it matches on both
                                        // sides of the portal.
                                        CompactSpan @as = chf.spans[ai];
                                        if (@as.y > hmin)
                                        {
                                            layer.heights[idx] = Math.Max(layer.heights[idx], (char)(@as.y - hmin));
                                        }
                                    }
                                    // Valid connection mask
                                    if (chf.areas[ai] != RecastConstants.RC_NULL_AREA && lid == alid)
                                    {
                                        int nx = ax - borderSize;
                                        int ny = ay - borderSize;
                                        if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
                                        {
                                            con |= (char)(1 << dir);
                                        }
                                    }
                                }
                            }
                            layer.cons[idx] = (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(RC_TIMER_BUILD_LAYERS);
            return(lset);
        }
Example #9
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 ContourSet buildContours(Context ctx, CompactHeightfield chf, float maxError, int maxEdgeLen, int buildFlags)
        {
            int        w          = chf.width;
            int        h          = chf.height;
            int        borderSize = chf.borderSize;
            ContourSet cset       = new ContourSet();

            ctx.startTimer("BUILD_CONTOURS");
            RecastVectors.copy(cset.bmin, chf.bmin, 0);
            RecastVectors.copy(cset.bmax, chf.bmax, 0);
            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[] flags = new int[chf.spanCount];

            ctx.startTimer("BUILD_CONTOURS_TRACE");

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

            ctx.stopTimer("BUILD_CONTOURS_TRACE");

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

            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    CompactCell c = chf.cells[x + y * w];
                    for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
                    {
                        if (flags[i] == 0 || flags[i] == 0xf)
                        {
                            flags[i] = 0;
                            continue;
                        }
                        int reg = chf.spans[i].reg;
                        if (reg == 0 || (reg & RecastConstants.RC_BORDER_REG) != 0)
                        {
                            continue;
                        }
                        int area = chf.areas[i];

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

                        ctx.startTimer("BUILD_CONTOURS_TRACE");
                        walkContour(x, y, i, chf, flags, verts);
                        ctx.stopTimer("BUILD_CONTOURS_TRACE");

                        ctx.startTimer("BUILD_CONTOURS_SIMPLIFY");
                        simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags);
                        removeDegenerateSegments(simplified);
                        ctx.stopTimer("BUILD_CONTOURS_SIMPLIFY");

                        // Store region->contour remap info.
                        // Create contour.
                        if (simplified.Count / 4 >= 3)
                        {
                            Contour cont = new Contour();
                            cset.conts.Add(cont);

                            cont.nverts = simplified.Count / 4;
                            cont.verts  = new int[simplified.Count];
                            for (int l = 0; l < cont.verts.Length; l++)
                            {
                                cont.verts[l] = simplified[l];
                            }

                            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[verts.Count];
                            for (int l = 0; l < cont.rverts.Length; l++)
                            {
                                cont.rverts[l] = verts[l];
                            }
                            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;
                        }
                    }
                }
            }

            // Merge holes if needed.
            if (cset.conts.Count > 0)
            {
                // Calculate winding of all polygons.
                int[] winding = new int[cset.conts.Count];
                int   nholes  = 0;
                for (int i = 0; i < cset.conts.Count; ++i)
                {
                    Contour cont = cset.conts[i];
                    // If the contour is wound backwards, it is a hole.
                    winding[i] = 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;
                    ContourRegion[] regions  = new ContourRegion[nregions];
                    for (int i = 0; i < nregions; i++)
                    {
                        regions[i] = new ContourRegion();
                    }

                    for (int i = 0; i < cset.conts.Count; ++i)
                    {
                        Contour cont = cset.conts[i];
                        // Positively would contours are outlines, negative holes.
                        if (winding[i] > 0)
                        {
                            if (regions[cont.reg].outline != null)
                            {
                                throw new Exception("rcBuildContours: Multiple outlines for region " + cont.reg + ".");
                            }
                            regions[cont.reg].outline = cont;
                        }
                        else
                        {
                            regions[cont.reg].nholes++;
                        }
                    }
                    for (int i = 0; i < nregions; i++)
                    {
                        if (regions[i].nholes > 0)
                        {
                            regions[i].holes = new ContourHole[regions[i].nholes];
                            for (int nh = 0; nh < regions[i].nholes; nh++)
                            {
                                regions[i].holes[nh] = new ContourHole();
                            }
                            regions[i].nholes = 0;
                        }
                    }
                    for (int i = 0; i < cset.conts.Count; ++i)
                    {
                        Contour       cont = cset.conts[i];
                        ContourRegion 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++)
                    {
                        ContourRegion reg = regions[i];
                        if (reg.nholes == 0)
                        {
                            continue;
                        }

                        if (reg.outline != 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.
                            throw new Exception("rcBuildContours: Bad outline for region " + i + ", contour simplification is likely too aggressive.");
                        }
                    }
                }
            }
            ctx.stopTimer("BUILD_CONTOURS");
            return(cset);
        }
Example #10
0
        private static int getCornerHeight(int x, int y, int i, int dir, CompactHeightfield chf, bool isBorderVertex)
        {
            CompactSpan s    = chf.spans[i];
            int         ch   = s.y;
            int         dirp = (dir + 1) & 0x3;

            int[] regs = { 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] = chf.spans[i].reg | (chf.areas[i] << 16);

            if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED)
            {
                int         ax  = x + RecastCommon.GetDirOffsetX(dir);
                int         ay  = y + RecastCommon.GetDirOffsetY(dir);
                int         ai  = chf.cells[ax + ay * chf.width].index + RecastCommon.GetCon(s, dir);
                CompactSpan @as = chf.spans[ai];
                ch      = Math.Max(ch, @as.y);
                regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16);
                if (RecastCommon.GetCon(@as, dirp) != RecastConstants.RC_NOT_CONNECTED)
                {
                    int         ax2 = ax + RecastCommon.GetDirOffsetX(dirp);
                    int         ay2 = ay + RecastCommon.GetDirOffsetY(dirp);
                    int         ai2 = chf.cells[ax2 + ay2 * chf.width].index + RecastCommon.GetCon(@as, dirp);
                    CompactSpan as2 = chf.spans[ai2];
                    ch      = Math.Max(ch, as2.y);
                    regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);
                }
            }
            if (RecastCommon.GetCon(s, dirp) != RecastConstants.RC_NOT_CONNECTED)
            {
                int         ax  = x + RecastCommon.GetDirOffsetX(dirp);
                int         ay  = y + RecastCommon.GetDirOffsetY(dirp);
                int         ai  = chf.cells[ax + ay * chf.width].index + RecastCommon.GetCon(s, dirp);
                CompactSpan @as = chf.spans[ai];
                ch      = Math.Max(ch, @as.y);
                regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16);
                if (RecastCommon.GetCon(@as, dir) != RecastConstants.RC_NOT_CONNECTED)
                {
                    int         ax2 = ax + RecastCommon.GetDirOffsetX(dir);
                    int         ay2 = ay + RecastCommon.GetDirOffsetY(dir);
                    int         ai2 = chf.cells[ax2 + ay2 * chf.width].index + RecastCommon.GetCon(@as, dir);
                    CompactSpan as2 = chf.spans[ai2];
                    ch      = Math.Max(ch, as2.y);
                    regs[2] = 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] & RecastConstants.RC_BORDER_REG) != 0 && regs[a] == regs[b];
                bool twoInts      = ((regs[c] | regs[d]) & RecastConstants.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 #11
0
        private static void walkContour(int x, int y, int i, CompactHeightfield chf, int[] flags, List <int> points)
        {
            // Choose the first non-connected edge
            int dir = 0;

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

            int startDir = dir;
            int starti   = i;

            int 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, isBorderVertex);
                    int  pz             = y;
                    switch (dir)
                    {
                    case 0:
                        pz++;
                        break;

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

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

                    flags[i] &= ~(1 << dir);                   // Remove visited edges
                    dir       = (dir + 1) & 0x3;               // Rotate CW
                }
                else
                {
                    int         ni = -1;
                    int         nx = x + RecastCommon.GetDirOffsetX(dir);
                    int         ny = y + RecastCommon.GetDirOffsetY(dir);
                    CompactSpan s  = chf.spans[i];
                    if (RecastCommon.GetCon(s, dir) != RecastConstants.RC_NOT_CONNECTED)
                    {
                        CompactCell nc = chf.cells[nx + ny * chf.width];
                        ni = nc.index + RecastCommon.GetCon(s, dir);
                    }
                    if (ni == -1)
                    {
                        // Should not happen.
                        return;
                    }
                    x   = nx;
                    y   = ny;
                    i   = ni;
                    dir = (dir + 3) & 0x3;                     // Rotate CCW
                }

                if (starti == i && startDir == dir)
                {
                    break;
                }
            }
        }
Example #12
0
        /// @par
        ///
        /// This is just the beginning of the process of fully building a compact heightfield.
        /// Various filters may be applied, then the distance field and regions built.
        /// E.g: #rcBuildDistanceField and #rcBuildRegions
        ///
        /// See the #rcConfig documentation for more information on the configuration parameters.
        ///
        /// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig

        public static CompactHeightfield buildCompactHeightfield(Context ctx, int walkableHeight, int walkableClimb, Heightfield hf)
        {
            ctx.startTimer("BUILD_COMPACTHEIGHTFIELD");

            CompactHeightfield chf = new CompactHeightfield();
            int w         = hf.width;
            int h         = hf.height;
            int spanCount = getHeightFieldSpanCount(ctx, hf);

            // Fill in header.
            chf.width          = w;
            chf.height         = h;
            chf.spanCount      = spanCount;
            chf.walkableHeight = walkableHeight;
            chf.walkableClimb  = walkableClimb;
            chf.maxRegions     = 0;
            RecastVectors.copy(chf.bmin, hf.bmin);
            RecastVectors.copy(chf.bmax, hf.bmax);
            chf.bmax[1] += walkableHeight * hf.ch;
            chf.cs       = hf.cs;
            chf.ch       = hf.ch;
            chf.cells    = new CompactCell[w * h];
            chf.spans    = new CompactSpan[spanCount];
            chf.areas    = new int[spanCount];
            int MAX_HEIGHT = 0xffff;

            for (int i = 0; i < chf.cells.Length; i++)
            {
                chf.cells[i] = new CompactCell();
            }
            for (int i = 0; i < chf.spans.Length; i++)
            {
                chf.spans[i] = new CompactSpan();
            }
            // Fill in cells and spans.
            int idx = 0;

            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    Span s = hf.spans[x + y * w];
                    // If there are no spans at this cell, just leave the data to index=0, count=0.
                    if (s == null)
                    {
                        continue;
                    }
                    CompactCell c = chf.cells[x + y * w];
                    c.index = idx;
                    c.count = 0;
                    while (s != null)
                    {
                        if (s.area != RecastConstants.RC_NULL_AREA)
                        {
                            int bot = s.smax;
                            int top = s.next != null ? (int)s.next.smin : MAX_HEIGHT;
                            chf.spans[idx].y = RecastCommon.clamp(bot, 0, 0xffff);
                            chf.spans[idx].h = RecastCommon.clamp(top - bot, 0, 0xff);
                            chf.areas[idx]   = s.area;
                            idx++;
                            c.count++;
                        }
                        s = s.next;
                    }
                }
            }

            // Find neighbour connections.
            int MAX_LAYERS       = RecastConstants.RC_NOT_CONNECTED - 1;
            int tooHighNeighbour = 0;

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

                        for (int dir = 0; dir < 4; ++dir)
                        {
                            RecastCommon.SetCon(s, dir, RecastConstants.RC_NOT_CONNECTED);
                            int nx = x + RecastCommon.GetDirOffsetX(dir);
                            int ny = y + RecastCommon.GetDirOffsetY(dir);
                            // First check that the neighbour cell is in bounds.
                            if (nx < 0 || ny < 0 || nx >= w || ny >= h)
                            {
                                continue;
                            }

                            // Iterate over all neighbour spans and check if any of the is
                            // accessible from current cell.
                            CompactCell nc = chf.cells[nx + ny * w];
                            for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k)
                            {
                                CompactSpan ns  = chf.spans[k];
                                int         bot = Math.Max(s.y, ns.y);
                                int         top = Math.Min(s.y + s.h, ns.y + ns.h);

                                // Check that the gap between the spans is walkable,
                                // and that the climb height between the gaps is not too high.
                                if ((top - bot) >= walkableHeight && Math.Abs(ns.y - s.y) <= walkableClimb)
                                {
                                    // Mark direction as walkable.
                                    int lidx = k - nc.index;
                                    if (lidx < 0 || lidx > MAX_LAYERS)
                                    {
                                        tooHighNeighbour = Math.Max(tooHighNeighbour, lidx);
                                        continue;
                                    }
                                    RecastCommon.SetCon(s, dir, lidx);
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            if (tooHighNeighbour > MAX_LAYERS)
            {
                throw new Exception("rcBuildCompactHeightfield: Heightfield has too many layers " + tooHighNeighbour + " (max: " + MAX_LAYERS + ")");
            }
            ctx.stopTimer("BUILD_COMPACTHEIGHTFIELD");
            return(chf);
        }