Example #1
0
        /// @par
        ///
        /// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen
        /// parameters control how closely the simplified contours will match the raw contours.
        ///
        /// Simplified contours are generated such that the vertices for portals between areas match up.
        /// (They are considered mandatory vertices.)
        ///
        /// Setting @p maxEdgeLength to zero will disabled the edge length feature.
        ///
        /// See the #rcConfig documentation for more information on the configuration parameters.
        ///
        /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig
        public static 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 #2
0
        private static void mergeRegionHoles(Context ctx, ContourRegion region)
        {
            // Sort holes from left to right.
            for (int i = 0; i < region.nholes; i++)
            {
                int[] minleft = findLeftMostVertex(region.holes[i].contour);
                region.holes[i].minx     = minleft[0];
                region.holes[i].minz     = minleft[1];
                region.holes[i].leftmost = minleft[2];
            }
            Array.Sort(region.holes, new CompareHoles());

            int maxVerts = region.outline.nverts;

            for (int i = 0; i < region.nholes; i++)
            {
                maxVerts += region.holes[i].contour.nverts;
            }

            PotentialDiagonal[] diags = new PotentialDiagonal[maxVerts];
            for (int pd = 0; pd < maxVerts; pd++)
            {
                diags[pd] = new PotentialDiagonal();
            }
            Contour outline = region.outline;

            // Merge holes into the outline one by one.
            for (int i = 0; i < region.nholes; i++)
            {
                Contour hole = region.holes[i].contour;

                int index      = -1;
                int bestVertex = region.holes[i].leftmost;
                for (int iter = 0; iter < hole.nverts; iter++)
                {
                    // Find potential diagonals.
                    // The 'best' vertex must be in the cone described by 3 cosequtive vertices of the outline.
                    // ..o j-1
                    //   |
                    //   |   * best
                    //   |
                    // j o-----o j+1
                    //         :
                    int ndiags = 0;
                    int corner = bestVertex * 4;
                    for (int j = 0; j < outline.nverts; j++)
                    {
                        if (inCone(j, outline.nverts, outline.verts, corner, hole.verts))
                        {
                            int dx = outline.verts[j * 4 + 0] - hole.verts[corner + 0];
                            int dz = outline.verts[j * 4 + 2] - hole.verts[corner + 2];
                            diags[ndiags].vert = j;
                            diags[ndiags].dist = dx * dx + dz * dz;
                            ndiags++;
                        }
                    }
                    // Sort potential diagonals by distance, we want to make the connection as short as possible.
                    Array.Sort(diags, 0, ndiags, new CompareDiagDist());

                    // Find a diagonal that is not intersecting the outline not the remaining holes.
                    index = -1;
                    for (int j = 0; j < ndiags; j++)
                    {
                        int  pt        = diags[j].vert * 4;
                        bool intersect = intersectSegCountour(pt, corner, diags[i].vert, outline.nverts, outline.verts, outline.verts, hole.verts);
                        for (int k = i; k < region.nholes && !intersect; k++)
                        {
                            intersect |= intersectSegCountour(pt, corner, -1, region.holes[k].contour.nverts, region.holes[k].contour.verts, outline.verts, hole.verts);
                        }
                        if (!intersect)
                        {
                            index = diags[j].vert;
                            break;
                        }
                    }
                    // If found non-intersecting diagonal, stop looking.
                    if (index != -1)
                    {
                        break;
                    }
                    // All the potential diagonals for the current vertex were intersecting, try next vertex.
                    bestVertex = (bestVertex + 1) % hole.nverts;
                }

                if (index == -1)
                {
                    ctx.warn("mergeHoles: Failed to find merge points for");
                    continue;
                }
                mergeContours(region.outline, hole, index, bestVertex);
            }
        }