internal static void calcTriNormal(float[] verts, int v0, int v1, int v2, float[] norm) { float[] e0 = new float[3]; float[] e1 = new float[3]; RecastVectors.sub(e0, verts, v1 * 3, v0 * 3); RecastVectors.sub(e1, verts, v2 * 3, v0 * 3); RecastVectors.cross(norm, e0, e1); RecastVectors.normalize(norm); }
//divides a convex polygons into two convex polygons on both sides of a line private static int[] dividePoly(float[] buf, int @in, int nin, int out1, int out2, float x, int axis) { float[] d = new float[12]; for (int i = 0; i < nin; ++i) { d[i] = x - buf[@in + i * 3 + axis]; } int m = 0, n = 0; for (int i = 0, j = nin - 1; i < nin; j = i, ++i) { bool ina = d[j] >= 0; bool inb = d[i] >= 0; if (ina != inb) { float s = d[j] / (d[j] - d[i]); buf[out1 + m * 3 + 0] = buf[@in + j * 3 + 0] + (buf[@in + i * 3 + 0] - buf[@in + j * 3 + 0]) * s; buf[out1 + m * 3 + 1] = buf[@in + j * 3 + 1] + (buf[@in + i * 3 + 1] - buf[@in + j * 3 + 1]) * s; buf[out1 + m * 3 + 2] = buf[@in + j * 3 + 2] + (buf[@in + i * 3 + 2] - buf[@in + j * 3 + 2]) * s; RecastVectors.copy(buf, out2 + n * 3, buf, out1 + m * 3); m++; n++; // add the i'th point to the right polygon. Do NOT add points that are on the dividing line // since these were already added above if (d[i] > 0) { RecastVectors.copy(buf, out1 + m * 3, buf, @in + i * 3); m++; } else if (d[i] < 0) { RecastVectors.copy(buf, out2 + n * 3, buf, @in + i * 3); n++; } } else // same side { // add the i'th point to the right polygon. Addition is done even for points on the dividing line if (d[i] >= 0) { RecastVectors.copy(buf, out1 + m * 3, buf, @in + i * 3); m++; if (d[i] != 0) { continue; } } RecastVectors.copy(buf, out2 + n * 3, buf, @in + i * 3); n++; } } return(new int[] { m, n }); }
public InputGeom(List <float> vertexPositions, List <int> meshFaces) { vertices = new float[vertexPositions.Count]; for (int i = 0; i < vertices.Length; i++) { vertices[i] = vertexPositions[i]; } faces = new int[meshFaces.Count]; for (int i = 0; i < faces.Length; i++) { faces[i] = meshFaces[i]; } bmin = new float[3]; bmax = new float[3]; RecastVectors.copy(bmin, vertices, 0); RecastVectors.copy(bmax, vertices, 0); for (int i = 1; i < vertices.Length / 3; i++) { RecastVectors.min(bmin, vertices, i * 3); RecastVectors.max(bmax, vertices, i * 3); } }
/// @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"); }
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); }
/// @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); }
/// @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); }
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); } } }