Example #1
0
        internal static int getHeightFieldSpanCount(Context ctx, Heightfield hf)
        {
            int w         = hf.width;
            int h         = hf.height;
            int spanCount = 0;

            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    for (Span s = hf.spans[x + y * w]; s != null; s = s.next)
                    {
                        if (s.area != RecastConstants.RC_NULL_AREA)
                        {
                            spanCount++;
                        }
                    }
                }
            }
            return(spanCount);
        }
        /// @par
        ///
        /// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb
        /// from the current span's maximum.
        /// This method removes the impact of the overestimation of conservative voxelization
        /// so the resulting mesh will not have regions hanging in the air over ledges.
        ///
        /// A span is a ledge if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt>
        ///
        /// @see rcHeightfield, rcConfig
        public static void filterLedgeSpans(Context ctx, int walkableHeight, int walkableClimb, Heightfield solid)
        {
            ctx.startTimer("FILTER_BORDER");

            int w          = solid.width;
            int h          = solid.height;
            int MAX_HEIGHT = 0xffff;

            // Mark border spans.
            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    for (Span s = solid.spans[x + y * w]; s != null; s = s.next)
                    {
                        // Skip non walkable spans.
                        if (s.area == RecastConstants.RC_NULL_AREA)
                        {
                            continue;
                        }

                        int bot = (s.smax);
                        int top = s.next != null ? s.next.smin : MAX_HEIGHT;

                        // Find neighbours minimum height.
                        int minh = MAX_HEIGHT;

                        // Min and max height of accessible neighbours.
                        int asmin = s.smax;
                        int asmax = s.smax;

                        for (int dir = 0; dir < 4; ++dir)
                        {
                            int dx = x + RecastCommon.GetDirOffsetX(dir);
                            int dy = y + RecastCommon.GetDirOffsetY(dir);
                            // Skip neighbours which are out of bounds.
                            if (dx < 0 || dy < 0 || dx >= w || dy >= h)
                            {
                                minh = Math.Min(minh, -walkableClimb - bot);
                                continue;
                            }

                            // From minus infinity to the first span.
                            Span ns   = solid.spans[dx + dy * w];
                            int  nbot = -walkableClimb;
                            int  ntop = ns != null ? ns.smin : MAX_HEIGHT;
                            // Skip neightbour if the gap between the spans is too small.
                            if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
                            {
                                minh = Math.Min(minh, nbot - bot);
                            }

                            // Rest of the spans.
                            for (ns = solid.spans[dx + dy * w]; ns != null; ns = ns.next)
                            {
                                nbot = ns.smax;
                                ntop = ns.next != null ? ns.next.smin : MAX_HEIGHT;
                                // Skip neightbour if the gap between the spans is too small.
                                if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
                                {
                                    minh = Math.Min(minh, nbot - bot);

                                    // Find min/max accessible neighbour height.
                                    if (Math.Abs(nbot - bot) <= walkableClimb)
                                    {
                                        if (nbot < asmin)
                                        {
                                            asmin = nbot;
                                        }
                                        if (nbot > asmax)
                                        {
                                            asmax = nbot;
                                        }
                                    }
                                }
                            }
                        }

                        // The current span is close to a ledge if the drop to any
                        // neighbour span is less than the walkableClimb.
                        if (minh < -walkableClimb)
                        {
                            s.area = RecastConstants.RC_NULL_AREA;
                        }

                        // If the difference between all neighbours is too large,
                        // we are at steep slope, mark the span as ledge.
                        if ((asmax - asmin) > walkableClimb)
                        {
                            s.area = RecastConstants.RC_NULL_AREA;
                        }
                    }
                }
            }

            ctx.stopTimer("FILTER_BORDER");
        }
        /// @par
        ///
        /// Allows the formation of walkable regions that will flow over low lying
        /// objects such as curbs, and up structures such as stairways.
        ///
        /// Two neighboring spans are walkable if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb</tt>
        ///
        /// @warning Will override the effect of #rcFilterLedgeSpans.  So if both filters are used, call
        /// #rcFilterLedgeSpans after calling this filter.
        ///
        /// @see rcHeightfield, rcConfig
        public static void filterLowHangingWalkableObstacles(Context ctx, int walkableClimb, Heightfield solid)
        {
            ctx.startTimer("FILTER_LOW_OBSTACLES");

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

            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    Span ps = null;
                    bool previousWalkable = false;
                    int  previousArea     = RecastConstants.RC_NULL_AREA;

                    for (Span s = solid.spans[x + y * w]; s != null; ps = s, s = s.next)
                    {
                        bool walkable = s.area != RecastConstants.RC_NULL_AREA;
                        // If current span is not walkable, but there is walkable
                        // span just below it, mark the span above it walkable too.
                        if (!walkable && previousWalkable)
                        {
                            if (Math.Abs(s.smax - ps.smax) <= walkableClimb)
                            {
                                s.area = previousArea;
                            }
                        }
                        // Copy walkable flag so that it cannot propagate
                        // past multiple non-walkable objects.
                        previousWalkable = walkable;
                        previousArea     = s.area;
                    }
                }
            }

            ctx.stopTimer("FILTER_LOW_OBSTACLES");
        }
        /// @par
        ///
        /// For this filter, the clearance above the span is the distance from the span's
        /// maximum to the next higher span's minimum. (Same grid column.)
        ///
        /// @see rcHeightfield, rcConfig
        public static void filterWalkableLowHeightSpans(Context ctx, int walkableHeight, Heightfield solid)
        {
            ctx.startTimer("FILTER_WALKABLE");

            int w          = solid.width;
            int h          = solid.height;
            int MAX_HEIGHT = 0xffff;

            // Remove walkable flag from spans which do not have enough
            // space above them for the agent to stand there.
            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    for (Span s = solid.spans[x + y * w]; s != null; s = s.next)
                    {
                        int bot = (s.smax);
                        int top = s.next != null ? s.next.smin : MAX_HEIGHT;
                        if ((top - bot) <= walkableHeight)
                        {
                            s.area = RecastConstants.RC_NULL_AREA;
                        }
                    }
                }
            }
            ctx.stopTimer("FILTER_WALKABLE");
        }
Example #5
0
        private CompactHeightfield buildCompactHeightfield(InputGeom geom, RecastBuilderConfig bcfg, Context ctx)
        {
            RecastConfig cfg = bcfg.cfg;
            //
            // Step 2. Rasterize input polygon soup.
            //

            // Allocate voxel heightfield where we rasterize our input data to.
            Heightfield solid = new Heightfield(bcfg.width, bcfg.height, bcfg.bmin, bcfg.bmax, cfg.cs, cfg.ch);

            // Allocate array that can hold triangle area types.
            // If you have multiple meshes you need to process, allocate
            // and array which can hold the max number of triangles you need to
            // process.

            // Find triangles which are walkable based on their slope and rasterize
            // them.
            // If your input data is multiple meshes, you can transform them here,
            // calculate
            // the are type for each of the meshes and rasterize them.
            float[] verts     = geom.Verts;
            bool    tiled     = cfg.tileSize > 0;
            int     totaltris = 0;

            if (tiled)
            {
                ChunkyTriMesh chunkyMesh = geom.ChunkyMesh;
                float[]       tbmin      = new float[2];
                float[]       tbmax      = new float[2];
                tbmin[0] = bcfg.bmin[0];
                tbmin[1] = bcfg.bmin[2];
                tbmax[0] = bcfg.bmax[0];
                tbmax[1] = bcfg.bmax[2];
                List <ChunkyTriMeshNode> nodes = chunkyMesh.getChunksOverlappingRect(tbmin, tbmax);
                foreach (ChunkyTriMeshNode node in nodes)
                {
                    int[] tris  = node.tris;
                    int   ntris = tris.Length / 3;
                    totaltris += ntris;
                    int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris);
                    RecastRasterization.rasterizeTriangles(ctx, verts, tris, m_triareas, ntris, solid, cfg.walkableClimb);
                }
            }
            else
            {
                int[] tris       = geom.Tris;
                int   ntris      = tris.Length / 3;
                int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris);
                totaltris = ntris;
                RecastRasterization.rasterizeTriangles(ctx, verts, tris, m_triareas, ntris, solid, cfg.walkableClimb);
            }
            //
            // Step 3. Filter walkables surfaces.
            //

            // Once all geometry is rasterized, we do initial pass of filtering to
            // remove unwanted overhangs caused by the conservative rasterization
            // as well as filter spans where the character cannot possibly stand.
            RecastFilter.filterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, solid);
            RecastFilter.filterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, solid);
            RecastFilter.filterWalkableLowHeightSpans(ctx, cfg.walkableHeight, solid);

            //
            // Step 4. Partition walkable surface to simple regions.
            //

            // Compact the heightfield so that it is faster to handle from now on.
            // This will result more cache coherent data as well as the neighbours
            // between walkable cells will be calculated.
            CompactHeightfield chf = Recast.buildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, solid);

            // Erode the walkable area by agent radius.
            RecastArea.erodeWalkableArea(ctx, cfg.walkableRadius, chf);
            // (Optional) Mark areas.
            foreach (ConvexVolume vol in geom.ConvexVolumes)
            {
                RecastArea.markConvexPolyArea(ctx, vol.verts, vol.hmin, vol.hmax, vol.area, chf);
            }
            return(chf);
        }
Example #6
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);
        }
Example #7
0
        /// <summary>
        /// The span addition can be set to favor flags. If the span is merged to another span and the new 'smax' is
        /// within 'flagMergeThr' units from the existing span, the span flags are merged.
        /// </summary>
        /// <seealso cref= Heightfield, Span. </seealso>
        private static void addSpan(Heightfield hf, int x, int y, int smin, int smax, int area, int flagMergeThr)
        {
            int idx = x + y * hf.width;

            Span s = new Span();

            s.smin = smin;
            s.smax = smax;
            s.area = area;
            s.next = null;

            // Empty cell, add the first span.
            if (hf.spans[idx] == null)
            {
                hf.spans[idx] = s;
                return;
            }
            Span prev = null;
            Span cur  = hf.spans[idx];

            // Insert and merge spans.
            while (cur != null)
            {
                if (cur.smin > s.smax)
                {
                    // Current span is further than the new span, break.
                    break;
                }
                else if (cur.smax < s.smin)
                {
                    // Current span is before the new span advance.
                    prev = cur;
                    cur  = cur.next;
                }
                else
                {
                    // Merge spans.
                    if (cur.smin < s.smin)
                    {
                        s.smin = cur.smin;
                    }
                    if (cur.smax > s.smax)
                    {
                        s.smax = cur.smax;
                    }

                    // Merge flags.
                    if (Math.Abs(s.smax - cur.smax) <= flagMergeThr)
                    {
                        s.area = Math.Max(s.area, cur.area);
                    }

                    // Remove current span.
                    Span next = cur.next;
                    if (prev != null)
                    {
                        prev.next = next;
                    }
                    else
                    {
                        hf.spans[idx] = next;
                    }
                    cur = next;
                }
            }

            // Insert new span.
            if (prev != null)
            {
                s.next    = prev.next;
                prev.next = s;
            }
            else
            {
                s.next        = hf.spans[idx];
                hf.spans[idx] = s;
            }
        }
Example #8
0
        /// <summary>
        /// Spans will only be added for triangles that overlap the heightfield grid.
        /// </summary>
        /// <seealso cref= Heightfield </seealso>
        public static void rasterizeTriangles(Context ctx, float[] verts, int[] areas, int nt, Heightfield solid, int flagMergeThr)
        {
            ctx.startTimer("RASTERIZE_TRIANGLES");

            float ics = 1.0f / solid.cs;
            float ich = 1.0f / solid.ch;

            // Rasterize triangles.
            for (int i = 0; i < nt; ++i)
            {
                int v0 = (i * 3 + 0);
                int v1 = (i * 3 + 1);
                int v2 = (i * 3 + 2);
                // Rasterize.
                rasterizeTri(verts, v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr);
            }
            ctx.stopTimer("RASTERIZE_TRIANGLES");
        }
Example #9
0
        /// <summary>
        /// No spans will be added if the triangle does not overlap the heightfield grid.
        /// </summary>
        /// <seealso cref= Heightfield </seealso>
        public static void rasterizeTriangle(Context ctx, float[] verts, int v0, int v1, int v2, int area, Heightfield solid, int flagMergeThr)
        {
            ctx.startTimer("RASTERIZE_TRIANGLES");

            float ics = 1.0f / solid.cs;
            float ich = 1.0f / solid.ch;

            rasterizeTri(verts, v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr);

            ctx.stopTimer("RASTERIZE_TRIANGLES");
        }
Example #10
0
        private static void rasterizeTri(float[] verts, int v0, int v1, int v2, int area, Heightfield hf, float[] bmin, float[] bmax, float cs, float ics, float ich, int flagMergeThr)
        {
            int w = hf.width;
            int h = hf.height;

            float[] tmin = new float[3]; float[] tmax = new float[3];
            float   by = bmax[1] - bmin[1];

            // Calculate the bounding box of the triangle.
            RecastVectors.copy(tmin, verts, v0 * 3);
            RecastVectors.copy(tmax, verts, v0 * 3);
            RecastVectors.min(tmin, verts, v1 * 3);
            RecastVectors.min(tmin, verts, v2 * 3);
            RecastVectors.max(tmax, verts, v1 * 3);
            RecastVectors.max(tmax, verts, v2 * 3);

            // If the triangle does not touch the bbox of the heightfield, skip the triagle.
            if (!overlapBounds(bmin, bmax, tmin, tmax))
            {
                return;
            }

            // Calculate the footprint of the triangle on the grid's y-axis
            int y0 = (int)((tmin[2] - bmin[2]) * ics);
            int y1 = (int)((tmax[2] - bmin[2]) * ics);

            y0 = RecastCommon.clamp(y0, 0, h - 1);
            y1 = RecastCommon.clamp(y1, 0, h - 1);

            // Clip the triangle into all grid cells it touches.
            float[] buf   = new float[7 * 3 * 4];
            int     @in   = 0;
            int     inrow = 7 * 3;
            int     p1    = inrow + 7 * 3;
            int     p2    = p1 + 7 * 3;

            RecastVectors.copy(buf, 0, verts, v0 * 3);
            RecastVectors.copy(buf, 3, verts, v1 * 3);
            RecastVectors.copy(buf, 6, verts, v2 * 3);
            int nvrow, nvIn = 3;

            for (int y = y0; y <= y1; ++y)
            {
                // Clip polygon to row. Store the remaining polygon as well
                float cz      = bmin[2] + y * cs;
                int[] nvrowin = dividePoly(buf, @in, nvIn, inrow, p1, cz + cs, 2);
                nvrow = nvrowin[0];
                nvIn  = nvrowin[1];
                {
                    int temp = @in;
                    @in = p1;
                    p1  = temp;
                }
                if (nvrow < 3)
                {
                    continue;
                }

                // find the horizontal bounds in the row
                float minX = buf[inrow], maxX = buf[inrow];
                for (int i = 1; i < nvrow; ++i)
                {
                    if (minX > buf[inrow + i * 3])
                    {
                        minX = buf[inrow + i * 3];
                    }
                    if (maxX < buf[inrow + i * 3])
                    {
                        maxX = buf[inrow + i * 3];
                    }
                }
                int x0 = (int)((minX - bmin[0]) * ics);
                int x1 = (int)((maxX - bmin[0]) * ics);
                x0 = RecastCommon.clamp(x0, 0, w - 1);
                x1 = RecastCommon.clamp(x1, 0, w - 1);

                int nv, nv2 = nvrow;
                for (int x = x0; x <= x1; ++x)
                {
                    // Clip polygon to column. store the remaining polygon as well
                    float cx    = bmin[0] + x * cs;
                    int[] nvnv2 = dividePoly(buf, inrow, nv2, p1, p2, cx + cs, 0);
                    nv  = nvnv2[0];
                    nv2 = nvnv2[1];
                    {
                        int temp = inrow;
                        inrow = p2;
                        p2    = temp;
                    }
                    if (nv < 3)
                    {
                        continue;
                    }

                    // Calculate min and max of the span.
                    float smin = buf[p1 + 1], smax = buf[p1 + 1];
                    for (int i = 1; i < nv; ++i)
                    {
                        smin = Math.Min(smin, buf[p1 + i * 3 + 1]);
                        smax = Math.Max(smax, buf[p1 + i * 3 + 1]);
                    }
                    smin -= bmin[1];
                    smax -= bmin[1];
                    // Skip the span if it is outside the heightfield bbox
                    if (smax < 0.0f)
                    {
                        continue;
                    }
                    if (smin > by)
                    {
                        continue;
                    }
                    // Clamp the span to the heightfield bbox.
                    if (smin < 0.0f)
                    {
                        smin = 0;
                    }
                    if (smax > by)
                    {
                        smax = by;
                    }

                    // Snap the span to the heightfield height grid.
                    int ismin = RecastCommon.clamp((int)Math.Floor(smin * ich), 0, RecastConstants.RC_SPAN_MAX_HEIGHT);
                    int ismax = RecastCommon.clamp((int)Math.Ceiling(smax * ich), ismin + 1, RecastConstants.RC_SPAN_MAX_HEIGHT);

                    addSpan(hf, x, y, ismin, ismax, area, flagMergeThr);
                }
            }
        }