/// @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); }
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); } }