/// @par /// /// This is usually the second to the last step in creating a fully built /// compact heightfield. This step is required before regions are built /// using #rcBuildRegions or #rcBuildRegionsMonotone. /// /// After this step, the distance data is available via the rcCompactHeightfield::maxDistance /// and rcCompactHeightfield::dist fields. /// /// @see rcCompactHeightfield, rcBuildRegions, rcBuildRegionsMonotone public static bool rcBuildDistanceField(rcContext ctx, rcCompactHeightfield chf) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD); chf.dist = null; //ushort* src = (ushort*)rcAlloc(sizeof(ushort)*chf.spanCount, RC_ALLOC_TEMP); ushort[] src = new ushort[chf.spanCount]; if (src == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'src' ("+chf.spanCount+")."); return false; } //ushort* dst = (ushort*)rcAlloc(sizeof(ushort)*chf.spanCount, RC_ALLOC_TEMP); ushort[] dst = new ushort[chf.spanCount]; if (dst == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dst' ("+chf.spanCount+")."); //rcFree(src); return false; } ushort maxDist = 0; ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD_DIST); calculateDistanceField(ctx, chf, src, ref maxDist); chf.maxDistance = maxDist; ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD_DIST); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD_BLUR); // Blur if (boxBlur(chf, 1, src, dst) != src){ rcSwap(ref src,ref dst); } // Store distance. chf.dist = src; ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD_BLUR); ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD); //rcFree(dst); dst = null; return true; }
public static void paintRectRegion(int minx, int maxx, int miny, int maxy, ushort regId, rcCompactHeightfield chf, ushort[] srcReg) { int w = chf.width; for (int y = miny; y < maxy; ++y) { for (int x = minx; x < maxx; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (chf.areas[i] != RC_NULL_AREA) srcReg[i] = regId; } } } }
/// @par /// /// The value of spacial parameters are in world units. /// /// @see rcCompactHeightfield, rcMedianFilterWalkableArea public static void rcMarkBoxArea(rcContext ctx, float[] bmin, float[] bmax, byte areaId, rcCompactHeightfield chf) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_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) { rcCompactCell c = chf.cells[x + z * chf.width]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if ((int)s.y >= miny && (int)s.y <= maxy) { if (chf.areas[i] != RC_NULL_AREA) chf.areas[i] = areaId; } } } } ctx.stopTimer(rcTimerLabel.RC_TIMER_MARK_BOX_AREA); }
static int getCornerHeight(int x, int y, int i, int dir, rcCompactHeightfield chf, ref bool isBorderVertex) { rcCompactSpan s = chf.spans[i]; int ch = (int)s.y; int dirp = (dir+1) & 0x3; uint[] regs = new uint[] {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] = (uint)( chf.spans[i].reg | (chf.areas[i] << 16) ); if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); rcCompactSpan aSpan = chf.spans[ai]; ch = Math.Max(ch, (int)aSpan.y); regs[1] = (uint)( chf.spans[ai].reg | (chf.areas[ai] << 16) ); if (rcGetCon(aSpan, dirp) != RC_NOT_CONNECTED) { int ax2 = ax + rcGetDirOffsetX(dirp); int ay2 = ay + rcGetDirOffsetY(dirp); int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(aSpan, dirp); rcCompactSpan as2 = chf.spans[ai2]; ch = Math.Max(ch, (int)as2.y); regs[2] = (uint)(chf.spans[ai2].reg | (chf.areas[ai2] << 16)); } } if (rcGetCon(s, dirp) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dirp); int ay = y + rcGetDirOffsetY(dirp); int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp); rcCompactSpan aSpan = chf.spans[ai]; ch = Math.Max(ch, (int)aSpan.y); regs[3] = (uint)(chf.spans[ai].reg | (chf.areas[ai] << 16)); if (rcGetCon(aSpan, dir) != RC_NOT_CONNECTED) { int ax2 = ax + rcGetDirOffsetX(dir); int ay2 = ay + rcGetDirOffsetY(dir); int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(aSpan, dir); rcCompactSpan as2 = chf.spans[ai2]; ch = Math.Max(ch, (int)as2.y); regs[2] = (uint)(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] & RC_BORDER_REG) != 0 && regs[a] == regs[b]; bool twoInts = ((regs[c] | regs[d]) & 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; }
public static void walkContour(int x, int y, int i, rcCompactHeightfield chf, byte[] flags, List<int> points) { // Choose the first non-connected edge byte dir = 0; while ((flags[i] & (1 << dir)) == 0) dir++; byte startDir = dir; int starti = i; byte 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,ref isBorderVertex); int pz = y; switch(dir) { case 0: pz++; break; case 1: px++; pz++; break; case 2: px++; break; } int r = 0; rcCompactSpan s = chf.spans[i]; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); r = (int)chf.spans[ai].reg; if (area != chf.areas[ai]) isAreaBorder = true; } if (isBorderVertex) r |= RC_BORDER_VERTEX; if (isAreaBorder) r |= RC_AREA_BORDER; points.Add(px); points.Add(py); points.Add(pz); points.Add(r); flags[i] &= (byte)( ~(1 << dir) ); // Remove visited edges dir = (byte)( (dir+1) & 0x3); // Rotate CW } else { int ni = -1; int nx = x + rcGetDirOffsetX(dir); int ny = y + rcGetDirOffsetY(dir); rcCompactSpan s = chf.spans[i]; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { rcCompactCell nc = chf.cells[nx+ny*chf.width]; ni = (int)nc.index + rcGetCon(s, dir); } if (ni == -1) { // Should not happen. return; } x = nx; y = ny; i = ni; dir = (byte)((dir+3) & 0x3); // Rotate CCW } if (starti == i && startDir == dir) { break; } } }
/// @par /// /// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. /// Contours will form simple polygons. /// /// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be /// re-assigned to the zero (null) region. /// /// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors. /// @p mergeRegionArea helps reduce unecessarily small regions. /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// The region data will be available via the rcCompactHeightfield::maxRegions /// and rcCompactSpan::reg fields. /// /// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. /// /// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig public static bool rcBuildRegions(rcContext ctx, rcCompactHeightfield chf, int borderSize, int minRegionArea, int mergeRegionArea) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS); int w = chf.width; int h = chf.height; //rcScopedDelete<ushort> buf = (ushort*)rcAlloc(sizeof(ushort)*chf.spanCount*4, RC_ALLOC_TEMP); /* ushort[] buf = new ushort[chf.spanCount*4]; if (buf == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' ("+chf.spanCount*4+")."); return false; } */ ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_WATERSHED); const int LOG_NB_STACKS = 3; const int NB_STACKS = 1 << LOG_NB_STACKS; List<int>[] lvlStacks = new List<int>[NB_STACKS]; for (int i = 0; i < NB_STACKS; ++i) { lvlStacks[i] = new List<int>(); //rccsResizeList(lvlStacks[i], 1024); lvlStacks[i].Capacity = 1024; } List<int> stack = new List<int>();//(1024); List<int> visited = new List<int>();//(1024); stack.Capacity = 1024; visited.Capacity = 1024; //rccResizeList(stack, 1024); //rccResizeList(visited, 1024); ushort[] srcReg = new ushort[chf.spanCount]; ushort[] srcDist = new ushort[chf.spanCount];//buf+chf.spanCount; ushort[] dstReg = new ushort[chf.spanCount];// buf+chf.spanCount*2; ushort[] dstDist = new ushort[chf.spanCount];//buf+chf.spanCount*3; //memset(srcReg, 0, sizeof(ushort)*chf.spanCount); //memset(srcDist, 0, sizeof(ushort)*chf.spanCount); ushort regionId = 1; ushort level = (ushort)((chf.maxDistance+1) & ~1); // TODO: Figure better formula, expandIters defines how much the // watershed "overflows" and simplifies the regions. Tying it to // agent radius was usually good indication how greedy it could be. // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; if (borderSize > 0) { // Make sure border will not overflow. int bw = Math.Min(w, borderSize); int bh = Math.Min(h, borderSize); // Paint regions paintRectRegion(0, bw, 0, h,(ushort)( regionId|RC_BORDER_REG ), chf, srcReg); regionId++; paintRectRegion(w - bw, w, 0, h, (ushort)(regionId | RC_BORDER_REG), chf, srcReg); regionId++; paintRectRegion(0, w, 0, bh, (ushort)(regionId | RC_BORDER_REG), chf, srcReg); regionId++; paintRectRegion(0, w, h - bh, h, (ushort)(regionId | RC_BORDER_REG), chf, srcReg); regionId++; chf.borderSize = borderSize; } int sId = -1; while (level > 0) { level = (ushort)(level >= 2 ? level-2 : 0); sId = (sId+1) & (NB_STACKS-1); // ctx.startTimer(rcTimerLabel.RC_TIMER_DIVIDE_TO_LEVELS); if (sId == 0) sortCellsByLevel(level, chf, srcReg, NB_STACKS, lvlStacks, 1); else appendStacks(lvlStacks[sId-1], lvlStacks[sId], srcReg); // copy left overs from last level // ctx.stopTimer(rcTimerLabel.RC_TIMER_DIVIDE_TO_LEVELS); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_EXPAND); // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, lvlStacks[sId], false) != srcReg) { rcSwap(ref srcReg,ref dstReg); rcSwap(ref srcDist,ref dstDist); } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_EXPAND); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FLOOD); // Mark new regions with IDs. for (int j=0; j<lvlStacks[sId].Count; j+=3) { int x = lvlStacks[sId][j]; int y = lvlStacks[sId][j+1]; int i = lvlStacks[sId][j+2]; if (i >= 0 && srcReg[i] == 0) { if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) regionId++; } } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FLOOD); } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack, true) != srcReg) { rcSwap(ref srcReg,ref dstReg); rcSwap(ref srcDist,ref dstDist); } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_WATERSHED); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER); // Filter out small regions. chf.maxRegions = regionId; if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, ref chf.maxRegions, chf, srcReg)) return false; ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER); // Write the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS); return true; }
static bool buildPolyDetail(rcContext ctx, float[] _in, int nin, float sampleDist, float sampleMaxError, rcCompactHeightfield chf,rcHeightPatch hp, float[] verts, ref int nverts, List<int> tris, List<int> edges, List<int> samples) { const int MAX_VERTS = 127; const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts). const int MAX_VERTS_PER_EDGE = 32; float[] edge = new float[(MAX_VERTS_PER_EDGE+1)*3]; int[] hull = new int[MAX_VERTS]; int nhull = 0; nverts = 0; for (int i = 0; i < nin; ++i){ rcVcopy(verts,i*3, _in,i*3); } nverts = nin; float cs = chf.cs; float ics = 1.0f/cs; // Tessellate outlines. // This is done in separate pass in order to ensure // seamless height values across the ply boundaries. if (sampleDist > 0) { for (int i = 0, j = nin-1; i < nin; j=i++) { //const float* vj = &in[j*3]; //const float* vi = &in[i*3]; int vjStart = j*3; int viStart = i*3; bool swapped = false; // Make sure the segments are always handled in same order // using lexological sort or else there will be seams. if (Math.Abs(_in[vjStart]-_in[viStart]) < 1e-6f) { if (_in[vjStart + 2] > _in[viStart + 2]) { rcSwap(ref vjStart,ref viStart); swapped = true; } } else { if (_in[vjStart] > _in[viStart]) { rcSwap(ref vjStart,ref viStart); swapped = true; } } // Create samples along the edge. float dx = _in[viStart] - _in[vjStart];//vi[0] - vj[0]; float dy = _in[viStart+1] - _in[vjStart+1];//vi[1] - vj[1]; float dz = _in[viStart+2] - _in[vjStart+2];//vi[2] - vj[2]; float d = (float)Math.Sqrt(dx*dx + dz*dz); int nn = 1 + (int)Math.Floor(d/sampleDist); if (nn >= MAX_VERTS_PER_EDGE) { nn = MAX_VERTS_PER_EDGE-1; } if (nverts+nn >= MAX_VERTS){ nn = MAX_VERTS-1-nverts; } for (int k = 0; k <= nn; ++k) { float u = (float)k/(float)nn; //float* pos = &edge[k*3]; int posStart = k*3; edge[posStart + 0] = _in[vjStart + 0] + dx*u; edge[posStart + 1] = _in[vjStart + 1] + dy*u; edge[posStart + 2] = _in[vjStart + 2] + dz*u; edge[posStart + 1] = getHeight(edge[posStart + 0],edge[posStart + 1],edge[posStart + 2], cs, ics, chf.ch, hp)*chf.ch; } // Simplify samples. int[] idx = new int[MAX_VERTS_PER_EDGE];// {0,nn}; idx[1] = nn; int nidx = 2; for (int k = 0; k < nidx-1; ) { int a = idx[k]; int b = idx[k+1]; //float* va = &edge[a*3]; //float* vb = &edge[b*3]; int vaStart = a*3; int vbStart = b*3; // Find maximum deviation along the segment. float maxd = 0; int maxi = -1; for (int m = a+1; m < b; ++m) { int ptStart = m * 3; float dev = distancePtSeg(edge, ptStart, edge, vaStart,edge, vbStart); if (dev > maxd) { maxd = dev; maxi = m; } } // If the max deviation is larger than accepted error, // add new point, else continue to next segment. if (maxi != -1 && maxd > sampleMaxError * sampleMaxError) { for (int m = nidx; m > k; --m) idx[m] = idx[m-1]; idx[k+1] = maxi; nidx++; } else { ++k; } } hull[nhull++] = j; // Add new vertices. if (swapped) { for (int k = nidx-2; k > 0; --k) { //rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); rcVcopy(verts,nverts*3,edge,idx[k]*3); hull[nhull++] = nverts; nverts++; } } else { for (int k = 1; k < nidx-1; ++k) { //rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); rcVcopy(verts,nverts*3,edge,idx[k]*3); hull[nhull++] = nverts; nverts++; } } } } // Tessellate the base mesh. //edges.resize(0); //tris.resize(0); edges.Clear(); tris.Clear(); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); if (tris.Count == 0) { // Could not triangulate the poly, make sure there is some valid data there. ctx.log(rcLogCategory.RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon, adding default data."); for (int i = 2; i < nverts; ++i) { tris.Add(0); tris.Add(i-1); tris.Add(i); tris.Add(0); } return true; } if (sampleDist > 0) { // Create sample locations in a grid. float[] bmin = new float[3]; float[] bmax = new float[3]; rcVcopy(bmin, _in); rcVcopy(bmax, _in); for (int i = 1; i < nin; ++i) { rcVmin(bmin, 0, _in,i*3); rcVmax(bmax, 0, _in,i*3); } int x0 = (int)Math.Floor(bmin[0]/sampleDist); int x1 = (int)Math.Ceiling(bmax[0]/sampleDist); int z0 = (int)Math.Floor(bmin[2]/sampleDist); int z1 = (int)Math.Ceiling(bmax[2]/sampleDist); //samples.resize(0); samples.Clear(); for (int z = z0; z < z1; ++z) { for (int x = x0; x < x1; ++x) { float[] pt = new float[3]; pt[0] = x*sampleDist; pt[1] = (bmax[1]+bmin[1])*0.5f; pt[2] = z*sampleDist; // Make sure the samples are not too close to the edges. if (distToPoly(nin,_in,pt) > -sampleDist/2) { continue; } samples.Add(x); samples.Add(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, hp)); samples.Add(z); samples.Add(0); // Not added } } // Add the samples starting from the one that has the most // error. The procedure stops when all samples are added // or when the max error is within treshold. int nsamples = samples.Count/4; for (int iter = 0; iter < nsamples; ++iter) { if (nverts >= MAX_VERTS){ break; } // Find sample with most error. float[] bestpt = new float[] {0.0f,0.0f,0.0f}; float bestd = 0; int besti = -1; for (int i = 0; i < nsamples; ++i) { // int* s = &samples[i*4]; int sStart = i*4; if (samples[sStart + 3] != 0) continue; // skip added. float[] pt = new float[3]; // The sample location is jittered to get rid of some bad triangulations // which are cause by symmetrical data from the grid structure. pt[0] = samples[sStart + 0]*sampleDist + getJitterX(i)*cs*0.1f; pt[1] = samples[sStart + 1]*chf.ch; pt[2] = samples[sStart + 2]*sampleDist + getJitterY(i)*cs*0.1f; float d = distToTriMesh(pt, verts, nverts, tris, tris.Count/4); if (d < 0) continue; // did not hit the mesh. if (d > bestd) { bestd = d; besti = i; rcVcopy(bestpt,pt); } } // If the max error is within accepted threshold, stop tesselating. if (bestd <= sampleMaxError || besti == -1) break; // Mark sample as added. samples[besti*4+3] = 1; // Add the new sample point. rcVcopy(verts,nverts*3,bestpt,0); nverts++; // Create new triangulation. // TODO: Incremental add instead of full rebuild. //edges.resize(0); //tris.resize(0); edges.Clear(); tris.Clear(); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); } } int ntris = tris.Count/4; if (ntris > MAX_TRIS) { //tris.resize(MAX_TRIS*4); tris.RemoveRange(MAX_TRIS*4, tris.Count - MAX_TRIS*4); ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from "+ntris+" to max "+MAX_TRIS+"."); } return true; }
static void getHeightData(rcCompactHeightfield chf, ushort[] poly, int polyStart, int npoly, ushort[] verts, int bs, rcHeightPatch hp, List<int> stack, int region) { // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. //stack.resize(0); //memset(hp.data, 0xff, sizeof(ushort)*hp.width*hp.height); stack.Clear(); for (int i=0;i<hp.data.Length;++i){ hp.data[i] = 0xffff; } bool empty = true; // Copy the height from the same region, and mark region borders // as seed points to fill the rest. for (int hy = 0; hy < hp.height; hy++) { int y = hp.ymin + hy + bs; for (int hx = 0; hx < hp.width; hx++) { int x = hp.xmin + hx + bs; rcCompactCell c = chf.cells[x+y*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (s.reg == region) { // Store height hp.data[hx + hy*hp.width] = s.y; empty = false; // If any of the neighbours is not in same region, // add the current location as flood fill start bool border = false; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); rcCompactSpan aSpan = chf.spans[ai]; if (aSpan.reg != region) { border = true; break; } } } if (border) { stack.Add(x); stack.Add(y); stack.Add(i); } break; } } } } // if the polygon does not contain any points from the current region (rare, but happens) // then use the cells closest to the polygon vertices as seeds to fill the height field if (empty){ getHeightDataSeedsFromVertices(chf, poly, polyStart, npoly, verts, bs, hp, stack); } const int RETRACT_SIZE = 256; int head = 0; while (head*3 < stack.Count) { int cx = stack[head*3+0]; int cy = stack[head*3+1]; int ci = stack[head*3+2]; head++; if (head >= RETRACT_SIZE) { head = 0; if (stack.Count > RETRACT_SIZE*3){ //memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.Count-RETRACT_SIZE*3)); for (int i=0;i<stack.Count - RETRACT_SIZE*3;++i){ stack[i] = stack[RETRACT_SIZE*3 + i]; } } //stack.resize(stack.Count-RETRACT_SIZE*3); int newSize =stack.Count - RETRACT_SIZE*3; Debug.Assert(newSize > 0,"Resizing under zero"); stack.RemoveRange(newSize, stack.Count - newSize); } rcCompactSpan cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; int ax = cx + rcGetDirOffsetX(dir); int ay = cy + rcGetDirOffsetY(dir); int hx = ax - hp.xmin - bs; int hy = ay - hp.ymin - bs; if (hx < 0 || hx >= hp.width || hy < 0 || hy >= hp.height) continue; if (hp.data[hx + hy*hp.width] != RC_UNSET_HEIGHT) continue; int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir); rcCompactSpan aSpan = chf.spans[ai]; hp.data[hx + hy*hp.width] = aSpan.y; stack.Add(ax); stack.Add(ay); stack.Add(ai); } } }
/// @par /// /// The value of spacial parameters are in world units. /// /// @see rcCompactHeightfield, rcMedianFilterWalkableArea public static void rcMarkCylinderArea(rcContext ctx, float[] pos, float r, float h, byte areaId, rcCompactHeightfield chf) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_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) { rcCompactCell c = chf.cells[x + z * chf.width]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; if ((int)s.y >= miny && (int)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(rcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA); }
static ushort[] boxBlur(rcCompactHeightfield chf, int thr, ushort[] src, ushort[] dst) { int w = chf.width; int h = chf.height; thr *= 2; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; ushort cd = src[i]; if (cd <= thr) { dst[i] = cd; continue; } int d = (int)cd; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); d += (int)src[ai]; rcCompactSpan aSpan = chf.spans[ai]; int dir2 = (dir+1) & 0x3; if (rcGetCon(aSpan, dir2) != RC_NOT_CONNECTED) { int ax2 = ax + rcGetDirOffsetX(dir2); int ay2 = ay + rcGetDirOffsetY(dir2); int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(aSpan, dir2); d += (int)src[ai2]; } else { d += cd; } } else { d += cd*2; } } dst[i] = (ushort)((d+5)/9); } } } return dst; }
/// @par /// /// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. /// Contours will form simple polygons. /// /// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be /// re-assigned to the zero (null) region. /// /// Partitioning can result in smaller than necessary regions. @p mergeRegionArea helps /// reduce unecessarily small regions. /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// The region data will be available via the rcCompactHeightfield::maxRegions /// and rcCompactSpan::reg fields. /// /// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. /// /// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig public static bool rcBuildRegionsMonotone(rcContext ctx, rcCompactHeightfield chf, int borderSize, int minRegionArea, int mergeRegionArea) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS); int w = chf.width; int h = chf.height; ushort id = 1; ushort[] srcReg = new ushort[chf.spanCount]; if (srcReg == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' ("+chf.spanCount+")."); return false; } int nsweeps = Math.Max(chf.width,chf.height); rcSweepSpan[] sweeps = new rcSweepSpan[nsweeps]; rccsArrayItemsCreate(sweeps); if (sweeps == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' ("+nsweeps+")."); return false; } // Mark border regions. if (borderSize > 0) { // Make sure border will not overflow. int bw = Math.Min(w, borderSize); int bh = Math.Min(h, borderSize); // Paint regions paintRectRegion(0, bw, 0, h, (ushort)(id|RC_BORDER_REG), chf, srcReg); id++; paintRectRegion(w-bw, w, 0, h, (ushort)(id|RC_BORDER_REG), chf, srcReg); id++; paintRectRegion(0, w, 0, bh, (ushort)(id|RC_BORDER_REG), chf, srcReg); id++; paintRectRegion(0, w, h-bh, h, (ushort)(id|RC_BORDER_REG), chf, srcReg); id++; chf.borderSize = borderSize; } List<int> prev = new List<int>();//256 prev.Capacity = 256; // Sweep one line at a time. for (int y = borderSize; y < h-borderSize; ++y) { // Collect spans from this row. rccsResizeList(prev, id+1); for (int i=0;i<id;++i){ prev[i] = 0; } ushort rid = 1; for (int x = borderSize; x < w-borderSize; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; // -x ushort previd = 0; if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(0); int ay = y + rcGetDirOffsetY(0); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) previd = srcReg[ai]; } if (previd == 0) { previd = rid++; sweeps[previd].rid = previd; sweeps[previd].ns = 0; sweeps[previd].nei = 0; } // -y if (rcGetCon(s,3) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(3); int ay = y + rcGetDirOffsetY(3); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); if (srcReg[ai] != 0 && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) { ushort nr = srcReg[ai]; if (sweeps[previd].nei == 0 || sweeps[previd].nei == nr) { sweeps[previd].nei = nr; sweeps[previd].ns++; prev[nr]++; } else { sweeps[previd].nei = RC_NULL_NEI; } } } srcReg[i] = previd; } } // Create unique ID. for (int i = 1; i < rid; ++i) { if (sweeps[i].nei != RC_NULL_NEI && sweeps[i].nei != 0 && prev[sweeps[i].nei] == (int)sweeps[i].ns) { sweeps[i].id = sweeps[i].nei; } else { sweeps[i].id = id++; } } // Remap IDs for (int x = borderSize; x < w-borderSize; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (srcReg[i] > 0 && srcReg[i] < rid) srcReg[i] = sweeps[srcReg[i]].id; } } } ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER); // Filter out small regions. chf.maxRegions = id; if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, ref chf.maxRegions, chf, srcReg)) return false; ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER); // Store the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS); return true; }
static void walkContour(int x, int y, int i, int dir, rcCompactHeightfield chf, ushort[] srcReg, List<int> cont) { int startDir = dir; int starti = i; rcCompactSpan ss = chf.spans[i]; ushort curReg = 0; if (rcGetCon(ss, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(ss, dir); curReg = srcReg[ai]; } cont.Add(curReg); int iter = 0; while (++iter < 40000) { rcCompactSpan s = chf.spans[i]; if (isSolidEdge(chf, srcReg, x, y, i, dir)) { // Choose the edge corner ushort r = 0; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); r = srcReg[ai]; } if (r != curReg) { curReg = r; cont.Add(curReg); } dir = (dir+1) & 0x3; // Rotate CW } else { int ni = -1; int nx = x + rcGetDirOffsetX(dir); int ny = y + rcGetDirOffsetY(dir); if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { rcCompactCell nc = chf.cells[nx+ny*chf.width]; ni = (int)nc.index + rcGetCon(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; } } // Remove adjacent duplicates. if (cont.Count > 1) { for (int j = 0; j < cont.Count; ) { int nj = (j+1) % cont.Count; if (cont[j] == cont[nj]) { for (int k = j; k < cont.Count-1; ++k) cont[k] = cont[k+1]; rccsPop(cont); } else ++j; } } }
// the levels per stack (2 in our case) as a bit shift static void sortCellsByLevel(ushort startLevel, rcCompactHeightfield chf, ushort[] srcReg, uint nbStacks, List<int>[] stacks, ushort loglevelsPerStack) { int w = chf.width; int h = chf.height; startLevel = (ushort)(startLevel >> loglevelsPerStack); for (uint j=0; j<nbStacks; ++j) stacks[j].Clear(); // put all cells in the level range into the appropriate stacks for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (chf.areas[i] == RC_NULL_AREA || srcReg[i] != 0) continue; int level = chf.dist[i] >> loglevelsPerStack; int sId = startLevel - level; if (sId >= (int)nbStacks) continue; if (sId < 0) sId = 0; stacks[sId].Add(x); stacks[sId].Add(y); stacks[sId].Add(i); } } } }
static bool isSolidEdge(rcCompactHeightfield chf, ushort[] srcReg, int x, int y, int i, int dir) { rcCompactSpan s = chf.spans[i]; ushort r = 0; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); r = srcReg[ai]; } if (r == srcReg[i]) return false; return true; }
/// @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 rcMarkConvexPolyArea(rcContext ctx, float[] verts, int nverts, float hmin, float hmax, byte areaId, rcCompactHeightfield chf) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA); float[] bmin = new float[3]; float[] bmax = new float[3]; rcVcopy(bmin, verts); rcVcopy(bmax, verts); for (int i = 1; i < nverts; ++i) { int vStart = i * 3; rcVmin(bmin, 0, verts, vStart); rcVmax(bmax, 0, verts, vStart); } 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) { rcCompactCell c = chf.cells[x + z * chf.width]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; if ((int)s.y >= miny && (int)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(nverts, verts, p)) { chf.areas[i] = areaId; } } } } } ctx.stopTimer(rcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA); }
static void calculateDistanceField( rcContext ctx, rcCompactHeightfield chf, ushort[] src, ref ushort maxDist) { int w = chf.width; int h = chf.height; // Init distance and points. for (int i = 0; i < chf.spanCount; ++i) src[i] = 0xffff; // Mark boundary cells. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; byte area = chf.areas[i]; int nc = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); if (area == chf.areas[ai]) nc++; } } if (nc != 4) src[i] = 0; } } } // Pass 1 for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { // (-1,0) int ax = x + rcGetDirOffsetX(0); int ay = y + rcGetDirOffsetY(0); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); rcCompactSpan aSpan = chf.spans[ai]; if (src[ai]+2 < src[i]){ src[i] = (ushort)(src[ai]+2); } // (-1,-1) if (rcGetCon(aSpan, 3) != RC_NOT_CONNECTED) { int aax = ax + rcGetDirOffsetX(3); int aay = ay + rcGetDirOffsetY(3); int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(aSpan, 3); if (src[aai]+3 < src[i]){ src[i] = (ushort)(src[aai]+3); } } } if (rcGetCon(s, 3) != RC_NOT_CONNECTED) { // (0,-1) int ax = x + rcGetDirOffsetX(3); int ay = y + rcGetDirOffsetY(3); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); rcCompactSpan aSpan = chf.spans[ai]; if (src[ai]+2 < src[i]){ src[i] = (ushort)(src[ai]+2); } // (1,-1) if (rcGetCon(aSpan, 2) != RC_NOT_CONNECTED) { int aax = ax + rcGetDirOffsetX(2); int aay = ay + rcGetDirOffsetY(2); int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(aSpan, 2); if (src[aai]+3 < src[i]){ src[i] = (ushort)(src[aai]+3); } } } } } } // Pass 2 for (int y = h-1; y >= 0; --y) { for (int x = w-1; x >= 0; --x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (rcGetCon(s, 2) != RC_NOT_CONNECTED) { // (1,0) int ax = x + rcGetDirOffsetX(2); int ay = y + rcGetDirOffsetY(2); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); rcCompactSpan aSpan = chf.spans[ai]; if (src[ai]+2 < src[i]){ src[i] = (ushort)(src[ai]+2); } // (1,1) if (rcGetCon(aSpan, 1) != RC_NOT_CONNECTED) { int aax = ax + rcGetDirOffsetX(1); int aay = ay + rcGetDirOffsetY(1); int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(aSpan, 1); if (src[aai]+3 < src[i]){ src[i] = (ushort)(src[aai]+3); } } } if (rcGetCon(s, 1) != RC_NOT_CONNECTED) { // (0,1) int ax = x + rcGetDirOffsetX(1); int ay = y + rcGetDirOffsetY(1); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); rcCompactSpan aSpan = chf.spans[ai]; if (src[ai]+2 < src[i]){ src[i] = (ushort)(src[ai]+2); } // (-1,1) if (rcGetCon(aSpan, 0) != RC_NOT_CONNECTED) { int aax = ax + rcGetDirOffsetX(0); int aay = ay + rcGetDirOffsetY(0); int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(aSpan, 0); if (src[aai]+3 < src[i]){ src[i] = (ushort)(src[aai]+3); } } } } } } maxDist = 0; for (int i = 0; i < chf.spanCount; ++i){ maxDist = Math.Max(src[i], maxDist); } }
/// @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 bool rcErodeWalkableArea(rcContext ctx, int radius, rcCompactHeightfield chf) { Debug.Assert(ctx != null, "rcContext is null"); int w = chf.width; int h = chf.height; ctx.startTimer(rcTimerLabel.RC_TIMER_ERODE_AREA); byte[] dist = new byte[chf.spanCount];//(byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP); if (dist == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "erodeWalkableArea: Out of memory 'dist' " + chf.spanCount); return false; } // Init distance. for (int i=0; i < chf.spanCount; ++i) { dist[i] = 0xff; } // memset(dist, 0xff, sizeof(byte)*chf.spanCount); // Mark boundary cells. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x + y * w]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { if (chf.areas[i] == RC_NULL_AREA) { dist[i] = 0; } else { rcCompactSpan s = chf.spans[i]; int nc = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int nx = x + rcGetDirOffsetX(dir); int ny = y + rcGetDirOffsetY(dir); int nidx = (int)chf.cells[nx + ny * w].index + rcGetCon(s, dir); if (chf.areas[nidx] != RC_NULL_AREA) { nc++; } } } // At least one missing neighbour. if (nc != 4) dist[i] = 0; } } } } byte nd = 0; // Pass 1 for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x + y * w]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { // (-1,0) int ax = x + rcGetDirOffsetX(0); int ay = y + rcGetDirOffsetY(0); int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 0); rcCompactSpan aSpan = chf.spans[ai]; nd = (byte)Math.Min((int)dist[ai] + 2, 255); if (nd < dist[i]) dist[i] = nd; // (-1,-1) if (rcGetCon(aSpan, 3) != RC_NOT_CONNECTED) { int aax = ax + rcGetDirOffsetX(3); int aay = ay + rcGetDirOffsetY(3); int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 3); nd = (byte)Math.Min((int)dist[aai] + 3, 255); if (nd < dist[i]) dist[i] = nd; } } if (rcGetCon(s, 3) != RC_NOT_CONNECTED) { // (0,-1) int ax = x + rcGetDirOffsetX(3); int ay = y + rcGetDirOffsetY(3); int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 3); rcCompactSpan aSpan = chf.spans[ai]; nd = (byte)Math.Min((int)dist[ai] + 2, 255); if (nd < dist[i]) dist[i] = nd; // (1,-1) if (rcGetCon(aSpan, 2) != RC_NOT_CONNECTED) { int aax = ax + rcGetDirOffsetX(2); int aay = ay + rcGetDirOffsetY(2); int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 2); nd = (byte)Math.Min((int)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) { rcCompactCell c = chf.cells[x + y * w]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (rcGetCon(s, 2) != RC_NOT_CONNECTED) { // (1,0) int ax = x + rcGetDirOffsetX(2); int ay = y + rcGetDirOffsetY(2); int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 2); rcCompactSpan aSpan = chf.spans[ai]; nd = (byte)Math.Min((int)dist[ai] + 2, 255); if (nd < dist[i]) dist[i] = nd; // (1,1) if (rcGetCon(aSpan, 1) != RC_NOT_CONNECTED) { int aax = ax + rcGetDirOffsetX(1); int aay = ay + rcGetDirOffsetY(1); int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 1); nd = (byte)Math.Min((int)dist[aai] + 3, 255); if (nd < dist[i]) dist[i] = nd; } } if (rcGetCon(s, 1) != RC_NOT_CONNECTED) { // (0,1) int ax = x + rcGetDirOffsetX(1); int ay = y + rcGetDirOffsetY(1); int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 1); rcCompactSpan aSpan = chf.spans[ai]; nd = (byte)Math.Min((int)dist[ai] + 2, 255); if (nd < dist[i]) dist[i] = nd; // (-1,1) if (rcGetCon(aSpan, 0) != RC_NOT_CONNECTED) { int aax = ax + rcGetDirOffsetX(0); int aay = ay + rcGetDirOffsetY(0); int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 0); nd = (byte)Math.Min((int)dist[aai] + 3, 255); if (nd < dist[i]) dist[i] = nd; } } } } } byte thr = (byte)(radius * 2); for (int i = 0; i < chf.spanCount; ++i) if (dist[i] < thr) chf.areas[i] = RC_NULL_AREA; ctx.stopTimer(rcTimerLabel.RC_TIMER_ERODE_AREA); return true; }
static ushort[] expandRegions(int maxIter, ushort level, rcCompactHeightfield chf, ushort[] srcReg, ushort[] srcDist, ushort[] dstReg, ushort[] dstDist, List<int> stack, bool fillStack) { int w = chf.width; int h = chf.height; if (fillStack) { // Find cells revealed by the raised level. stack.Clear(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) { stack.Add(x); stack.Add(y); stack.Add(i); } } } } } else // use cells in the input stack { // mark all cells which already have a region for (int j=0; j<stack.Count; j+=3) { int i = stack[j+2]; if (srcReg[i] != 0) stack[j+2] = -1; } } int iter = 0; while (stack.Count > 0) { int failed = 0; //memcpy(dstReg, srcReg, sizeof(ushort)*chf.spanCount); for (int i=0;i<chf.spanCount;++i){ dstReg[i] = srcReg[i]; } //memcpy(dstDist, srcDist, sizeof(ushort)*chf.spanCount); for (int i=0;i<chf.spanCount;++i){ dstDist[i] = srcDist[i]; } for (int j = 0; j < stack.Count; j += 3) { int x = stack[j+0]; int y = stack[j+1]; int i = stack[j+2]; if (i < 0) { failed++; continue; } ushort r = srcReg[i]; ushort d2 = 0xffff; byte area = chf.areas[i]; rcCompactSpan s = chf.spans[i]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue; int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); if (chf.areas[ai] != area) continue; if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0) { if ((int)srcDist[ai]+2 < (int)d2) { r = srcReg[ai]; d2 = (ushort)(srcDist[ai]+2); } } } if (r != 0) { stack[j+2] = -1; // mark as used dstReg[i] = r; dstDist[i] = d2; } else { failed++; } } // rcSwap source and dest. rcSwap(ref srcReg, ref dstReg); rcSwap(ref srcDist, ref dstDist); if (failed*3 == stack.Count) break; if (level > 0) { ++iter; if (iter >= maxIter) break; } } return srcReg; }
/// @par /// /// This filter is usually applied after applying area id's using functions /// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea. /// /// @see rcCompactHeightfield public static bool rcMedianFilterWalkableArea(rcContext ctx, rcCompactHeightfield chf) { Debug.Assert(ctx != null, "rcContext is null"); int w = chf.width; int h = chf.height; ctx.startTimer(rcTimerLabel.RC_TIMER_MEDIAN_AREA); byte[] areas = new byte[chf.spanCount];//(byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP); if (areas == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' " + chf.spanCount); return false; } // Init distance. for (int i = 0; i < chf.spanCount; ++i) { areas[i] = 0xff; } //memset(areas, 0xff, sizeof(byte)*chf.spanCount); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x + y * w]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) { areas[i] = chf.areas[i]; continue; } byte[] nei = new byte[9]; for (int j = 0; j < 9; ++j) nei[j] = chf.areas[i]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, dir); if (chf.areas[ai] != RC_NULL_AREA) nei[dir * 2 + 0] = chf.areas[ai]; rcCompactSpan aSpan = chf.spans[ai]; int dir2 = (dir + 1) & 0x3; if (rcGetCon(aSpan, dir2) != RC_NOT_CONNECTED) { int ax2 = ax + rcGetDirOffsetX(dir2); int ay2 = ay + rcGetDirOffsetY(dir2); int ai2 = (int)chf.cells[ax2 + ay2 * w].index + rcGetCon(aSpan, dir2); if (chf.areas[ai2] != RC_NULL_AREA) nei[dir * 2 + 1] = chf.areas[ai2]; } } } insertSort(nei, 9); areas[i] = nei[4]; } } } chf.areas = areas; //memcpy(chf.areas, areas, sizeof(byte)*chf.spanCount); //rcFree(areas); ctx.stopTimer(rcTimerLabel.RC_TIMER_MEDIAN_AREA); return true; }
static bool filterSmallRegions(rcContext ctx, int minRegionArea, int mergeRegionSize, ref ushort maxRegionId, rcCompactHeightfield chf, ushort[] srcReg) { int w = chf.width; int h = chf.height; int nreg = maxRegionId+1; rcRegion[] regions = new rcRegion[nreg]; if (regions == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (" +nreg+ ")."); return false; } // Construct regions for (int i = 0; i < nreg; ++i){ regions[i] = new rcRegion((ushort) i); } // Find edge of a region and find connections around the contour. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { ushort r = srcReg[i]; if (r == 0 || r >= nreg) continue; rcRegion reg = regions[r]; reg.spanCount++; // Update floors. for (int j = (int)c.index; j < ni; ++j) { if (i == j) continue; ushort floorId = srcReg[j]; if (floorId == 0 || floorId >= nreg) continue; addUniqueFloorRegion(reg, floorId); } // Have found contour if (reg.connections.Count > 0) continue; reg.areaType = chf.areas[i]; // Check if this cell is next to a border. int ndir = -1; for (int dir = 0; dir < 4; ++dir) { if (isSolidEdge(chf, srcReg, x, y, i, dir)) { ndir = dir; break; } } if (ndir != -1) { // The cell is at border. // Walk around the contour to find all the neighbours. walkContour(x, y, i, ndir, chf, srcReg, reg.connections); } } } } // Remove too small regions. List<int> stack = new List<int>();//(32); List<int> trace= new List<int>();//(32); stack.Capacity = 32; trace.Capacity = 32; for (int i = 0; i < nreg; ++i) { rcRegion reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG) != 0) continue; if (reg.spanCount == 0) continue; if (reg.visited) continue; // Count the total size of all the connected regions. // Also keep track of the regions connects to a tile border. bool connectsToBorder = false; int spanCount = 0; stack.Clear(); trace.Clear(); reg.visited = true; stack.Add(i); while (stack.Count != 0) { // Pop int ri = rccsPop(stack); rcRegion creg = regions[ri]; spanCount += creg.spanCount; trace.Add(ri); for (int j = 0; j < creg.connections.Count; ++j) { if ((creg.connections[j] & RC_BORDER_REG) != 0) { connectsToBorder = true; continue; } rcRegion neireg = regions[creg.connections[j]]; if (neireg.visited) continue; if (neireg.id == 0 || (neireg.id & RC_BORDER_REG) != 0) continue; // Visit stack.Add(neireg.id); neireg.visited = true; } } // If the accumulated regions size is too small, remove it. // Do not remove areas which connect to tile borders // as their size cannot be estimated correctly and removing them // can potentially remove necessary areas. if (spanCount < minRegionArea && !connectsToBorder) { // Kill all visited regions. for (int j = 0; j < trace.Count; ++j) { regions[trace[j]].spanCount = 0; regions[trace[j]].id = 0; } } } // Merge too small regions to neighbour regions. int mergeCount = 0 ; do { mergeCount = 0; for (int i = 0; i < nreg; ++i) { rcRegion reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG) != 0) continue; if (reg.spanCount == 0) continue; // Check to see if the region should be merged. if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(reg)) continue; // Small region with more than 1 connection. // Or region which is not connected to a border at all. // Find smallest neighbour region that connects to this one. int smallest = 0xfffffff; ushort mergeId = reg.id; for (int j = 0; j < reg.connections.Count; ++j) { if ((reg.connections[j] & RC_BORDER_REG) != 0) continue; rcRegion mreg = regions[reg.connections[j]]; if (mreg.id == 0 || (mreg.id & RC_BORDER_REG) != 0) continue; if (mreg.spanCount < smallest && canMergeWithRegion(reg, mreg) && canMergeWithRegion(mreg, reg)) { smallest = mreg.spanCount; mergeId = mreg.id; } } // Found new id. if (mergeId != reg.id) { ushort oldId = reg.id; rcRegion target = regions[mergeId]; // Merge neighbours. if ( mergeRegions(target, reg)) { // Fixup regions pointing to current region. for (int j = 0; j < nreg; ++j) { if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG) != 0) continue; // If another region was already merged into current region // change the nid of the previous region too. if (regions[j].id == oldId) regions[j].id = mergeId; // Replace the current region with the new one if the // current regions is neighbour. replaceNeighbour(regions[j], oldId, mergeId); } mergeCount++; } } } } while (mergeCount > 0); // Compress region Ids. for (int i = 0; i < nreg; ++i) { regions[i].remap = false; if (regions[i].id == 0) continue; // Skip nil regions. if ((regions[i].id & RC_BORDER_REG) != 0) continue; // Skip external regions. regions[i].remap = true; } ushort regIdGen = 0; for (int i = 0; i < nreg; ++i) { if (!regions[i].remap) continue; ushort oldId = regions[i].id; ushort newId = ++regIdGen; for (int j = i; j < nreg; ++j) { if (regions[j].id == oldId) { regions[j].id = newId; regions[j].remap = false; } } } maxRegionId = regIdGen; // Remap regions. for (int i = 0; i < chf.spanCount; ++i) { if ((srcReg[i] & RC_BORDER_REG) == 0) srcReg[i] = regions[srcReg[i]].id; } return true; }
static void getHeightDataSeedsFromVertices(rcCompactHeightfield chf, ushort[] poly, int polyStart, int npoly, ushort[] verts, int bs, rcHeightPatch hp, List<int> stack) { // Floodfill the heightfield to get 2D height data, // starting at vertex locations as seeds. // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. //memset(hp.data, 0, sizeof(ushort)*hp.width*hp.height); for (int i=0;i<hp.data.Length;++i){ hp.data[i] = 0; } //tack.resize(0); stack.Clear(); int[] offset = new int[9*2] { 0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, }; // Use poly vertices as seed points for the flood fill. for (int j = 0; j < npoly; ++j) { int cx = 0, cz = 0, ci =-1; int dmin = RC_UNSET_HEIGHT; for (int k = 0; k < 9; ++k) { int ax = (int)verts[poly[polyStart + j]*3+0] + offset[k*2+0]; int ay = (int)verts[poly[polyStart + j]*3+1]; int az = (int)verts[poly[polyStart + j]*3+2] + offset[k*2+1]; if (ax < hp.xmin || ax >= hp.xmin+hp.width || az < hp.ymin || az >= hp.ymin+hp.height) continue; rcCompactCell c = chf.cells[(ax+bs)+(az+bs)*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan s = chf.spans[i]; int d = Math.Abs(ay - (int)s.y); if (d < dmin) { cx = ax; cz = az; ci = i; dmin = d; } } } if (ci != -1) { stack.Add(cx); stack.Add(cz); stack.Add(ci); } } // Find center of the polygon using flood fill. int pcx = 0, pcz = 0; for (int j = 0; j < npoly; ++j) { pcx += (int)verts[poly[polyStart + j]*3+0]; pcz += (int)verts[poly[polyStart + j]*3+2]; } pcx /= npoly; pcz /= npoly; for (int i = 0; i < stack.Count; i += 3) { int cx = stack[i+0]; int cy = stack[i+1]; int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; hp.data[idx] = 1; } while (stack.Count > 0) { int ci = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); int cy = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); int cx = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); // Check if close to center of the polygon. if (Math.Abs(cx-pcx) <= 1 && Math.Abs(cy-pcz) <= 1) { //stack.resize(0); stack.Clear(); stack.Add(cx); stack.Add(cy); stack.Add(ci); break; } rcCompactSpan cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; int ax = cx + rcGetDirOffsetX(dir); int ay = cy + rcGetDirOffsetY(dir); if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || ay < hp.ymin || ay >= (hp.ymin+hp.height)) continue; if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) continue; int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; hp.data[idx] = 1; stack.Add(ax); stack.Add(ay); stack.Add(ai); } } //memset(hp.data, 0xff, sizeof(ushort)*hp.width*hp.height); for (int i=0;i<hp.data.Length;++i){ hp.data[i] = 0xffff; } // Mark start locations. for (int i = 0; i < stack.Count; i += 3) { int cx = stack[i+0]; int cy = stack[i+1]; int ci = stack[i+2]; int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; rcCompactSpan cs = chf.spans[ci]; hp.data[idx] = cs.y; // getHeightData seeds are given in coordinates with borders stack[i+0] += bs; stack[i+1] += bs; } }
/// @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 bool rcBuildContours(rcContext ctx, rcCompactHeightfield chf, float maxError, int maxEdgeLen, rcContourSet cset, int buildFlags) { Debug.Assert(ctx != null, "rcContext is null"); int w = chf.width; int h = chf.height; int borderSize = chf.borderSize; ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS); rcVcopy(cset.bmin, chf.bmin); rcVcopy(cset.bmax, chf.bmax); 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 maxContours = Math.Max((int)chf.maxRegions, 8); //cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); cset.conts = new rcContour[maxContours]; //if (cset.conts == null) // return false; cset.nconts = 0; //rcScopedDelete<byte> flags = (byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP); byte[] flags = new byte[chf.spanCount]; if (flags == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' " + chf.spanCount); return false; } ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE); // Mark boundaries. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { byte res = 0; rcCompactSpan s = chf.spans[i]; if (chf.spans[i].reg == 0 || (chf.spans[i].reg & RC_BORDER_REG) != 0) { flags[i] = 0; continue; } for (int dir = 0; dir < 4; ++dir) { ushort r = 0; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { int ax = x + rcGetDirOffsetX(dir); int ay = y + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); r = chf.spans[ai].reg; } if (r == chf.spans[i].reg) res |= (byte)(1 << dir); } flags[i] = (byte)(res ^ 0xf); // Inverse, mark non connected edges. } } } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE); //List<int> verts(256); List<int> verts = new List<int>(); verts.Capacity = 256; //List<int> simplified(64); List<int> simplified = new List<int>(); simplified.Capacity = 64; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { rcCompactCell c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (flags[i] == 0 || flags[i] == 0xf) { flags[i] = 0; continue; } ushort reg = chf.spans[i].reg; if (reg == 0 || (reg & RC_BORDER_REG) != 0) { continue; } byte area = chf.areas[i]; //verts.resize(0); //simplified.resize(0); verts.Clear(); simplified.Clear(); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE); walkContour(x, y, i, chf, flags, verts); ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY); simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); removeDegenerateSegments(simplified); ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY); // Store region.contour remap info. // Create contour. if (simplified.Count/4 >= 3) { if (cset.nconts >= maxContours) { // Allocate more contours. // This can happen when there are tiny holes in the heightfield. int oldMax = maxContours; maxContours *= 2; rcContour[] newConts = new rcContour[maxContours];// (rcContour*)rcAlloc(sizeof(rcContour) * maxContours, RC_ALLOC_PERM); for (int j = 0; j < cset.nconts; ++j) { newConts[j] = cset.conts[j]; // Reset source pointers to prevent data deletion. cset.conts[j].verts = null; cset.conts[j].rverts = null; } //rcFree(cset.conts); cset.conts = newConts; ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Expanding max contours from " + oldMax + " to "+ maxContours); } int contId = cset.nconts; cset.nconts++; rcContour cont = cset.conts[contId]; cont.nverts = simplified.Count/4; cont.verts = new int[cont.nverts * 4]; //(int*)rcAlloc(sizeof(int)*cont.nverts*4, RC_ALLOC_PERM); if (cont.verts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' " + cont.nverts); return false; } //memcpy(cont.verts, &simplified[0], sizeof(int)*cont.nverts*4); for (int j = 0; j < cont.nverts * 4; ++j) { cont.verts[j] = simplified[j]; } if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. for (int j = 0; j < cont.nverts; ++j) { //int* v = &cont.verts[j*4]; cont.verts[j * 4] -= borderSize; cont.verts[j*4 + 2] -= borderSize; //v[0] -= borderSize; //v[2] -= borderSize; } } cont.nrverts = verts.Count/4; cont.rverts = new int[cont.nrverts * 4];//(int*)rcAlloc(sizeof(int)*cont.nrverts*4, RC_ALLOC_PERM); if (cont.rverts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' " + cont.nrverts); return false; } //memcpy(cont.rverts, &verts[0], sizeof(int)*cont.nrverts*4); for (int j = 0; j < cont.nrverts * 4; ++j) { cont.rverts[j] = verts[j]; } if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. for (int j = 0; j < cont.nrverts; ++j) { //int* v = &cont.rverts[j*4]; cont.rverts[j * 4] -= borderSize; cont.rverts[j * 4 + 2] -= borderSize; } } /* cont.cx = cont.cy = cont.cz = 0; for (int i = 0; i < cont.nverts; ++i) { cont.cx += cont.verts[i*4+0]; cont.cy += cont.verts[i*4+1]; cont.cz += cont.verts[i*4+2]; } cont.cx /= cont.nverts; cont.cy /= cont.nverts; cont.cz /= cont.nverts;*/ cont.reg = reg; cont.area = area; cset.conts[contId] = cont; } } } } // Check and merge droppings. // Sometimes the previous algorithms can fail and create several contours // per area. This pass will try to merge the holes into the main region. for (int i = 0; i < cset.nconts; ++i) { rcContour cont = cset.conts[i]; // Check if the contour is would backwards. if (calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0) { // Find another contour which has the same region ID. int mergeIdx = -1; for (int j = 0; j < cset.nconts; ++j) { if (i == j) continue; if (cset.conts[j].nverts != 0 && cset.conts[j].reg == cont.reg) { // Make sure the polygon is correctly oriented. if (calcAreaOfPolygon2D(cset.conts[j].verts, cset.conts[j].nverts) != 0) { mergeIdx = j; break; } } } if (mergeIdx == -1) { ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Could not find merge target for bad contour " + i); } else { rcContour mcont = cset.conts[mergeIdx]; // Merge by closest points. int ia = 0, ib = 0; getClosestIndices(mcont.verts, mcont.nverts, cont.verts, cont.nverts, ref ia, ref ib); if (ia == -1 || ib == -1) { ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Failed to find merge points for " + i + " and " + mergeIdx); continue; } if (!mergeContours(ref mcont,ref cont, ia, ib)) { ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Failed to merge contours " + i + " and " + mergeIdx); continue; } cset.conts[mergeIdx] = mcont; cset.conts[i] = cont; } } } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS); return true; }
/// @par /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig public static bool rcBuildPolyMeshDetail(rcContext ctx, rcPolyMesh mesh, rcCompactHeightfield chf, float sampleDist, float sampleMaxError, rcPolyMeshDetail dmesh) { Debug.Assert(ctx != null, "rcContext is null"); ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); if (mesh.nverts == 0 || mesh.npolys == 0) return true; int nvp = mesh.nvp; float cs = mesh.cs; float ch = mesh.ch; float[] orig = mesh.bmin; int borderSize = mesh.borderSize; List<int> edges = new List<int>(); List<int> tris = new List<int>(); List<int> stack = new List<int>(); List<int> samples = new List<int>(); edges.Capacity = 64; tris.Capacity = 512; stack.Capacity = 512; samples.Capacity = 512; float[] verts = new float[256*3]; rcHeightPatch hp = new rcHeightPatch(); int nPolyVerts = 0; int maxhw = 0, maxhh = 0; //rcScopedDelete<int> bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP); int[] bounds = new int[mesh.npolys*4]; if (bounds == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' ("+ mesh.npolys*4+")."); return false; } //rcScopedDelete<float> poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP); float[] poly = new float[nvp*3]; if (poly == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' ("+nvp*3+")."); return false; } // Find max size for a polygon area. for (int i = 0; i < mesh.npolys; ++i) { //ushort* p = &mesh.polys[i*nvp*2]; int pStart = i*nvp*2; //int& xmin = bounds[i*4+0]; //int& xmax = bounds[i*4+1]; //int& ymin = bounds[i*4+2]; //int& ymax = bounds[i*4+3]; int xmin = i*4+0; int xmax = i*4+1; int ymin = i*4+2; int ymax = i*4+3; bounds[xmin] = chf.width; bounds[xmax] = 0; bounds[ymin] = chf.height; bounds[ymax] = 0; for (int j = 0; j < nvp; ++j) { if(mesh.polys[pStart + j] == RC_MESH_NULL_IDX) break; //t ushort* v = &mesh.verts[p[j]*3]; int vIndex = mesh.polys[pStart + j] * 3; bounds[xmin] = Math.Min(bounds[xmin], (int)mesh.verts[vIndex + 0]); bounds[xmax] = Math.Max(bounds[xmax], (int)mesh.verts[vIndex + 0]); bounds[ymin] = Math.Min(bounds[ymin], (int)mesh.verts[vIndex + 2]); bounds[ymax] = Math.Max(bounds[ymax], (int)mesh.verts[vIndex + 2]); nPolyVerts++; } bounds[xmin] = Math.Max(0,bounds[xmin]-1); bounds[xmax] = Math.Min(chf.width,bounds[xmax]+1); bounds[ymin] = Math.Max(0,bounds[ymin]-1); bounds[ymax] = Math.Min(chf.height,bounds[ymax]+1); if (bounds[xmin] >= bounds[xmax] || bounds[ymin] >= bounds[ymax]) continue; maxhw = Math.Max(maxhw, bounds[xmax]-bounds[xmin]); maxhh = Math.Max(maxhh, bounds[ymax]-bounds[ymin]); } //hp.data = (ushort*)rcAlloc(sizeof(ushort)*maxhw*maxhh, RC_ALLOC_TEMP); hp.data = new ushort[maxhh*maxhw]; if (hp.data == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' ("+maxhw*maxhh+")."); return false; } dmesh.nmeshes = mesh.npolys; dmesh.nverts = 0; dmesh.ntris = 0; //dmesh.meshes = (uint*)rcAlloc(sizeof(uint)*dmesh.nmeshes*4, RC_ALLOC_PERM); dmesh.meshes = new uint[dmesh.nmeshes*4]; if (dmesh.meshes == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' ("+dmesh.nmeshes*4+")."); return false; } int vcap = nPolyVerts+nPolyVerts/2; int tcap = vcap*2; dmesh.nverts = 0; //dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); dmesh.verts = new float[vcap*3]; if (dmesh.verts == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' ("+vcap*3+")."); return false; } dmesh.ntris = 0; //dmesh.tris = (byte*)rcAlloc(sizeof(byte*)*tcap*4, RC_ALLOC_PERM); dmesh.tris = new byte[tcap*4]; if (dmesh.tris == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' ("+tcap*4+")."); return false; } for (int i = 0; i < mesh.npolys; ++i) { //const ushort* p = &mesh.polys[i*nvp*2]; int pIndex = i*nvp*2; // Store polygon vertices for processing. int npoly = 0; for (int j = 0; j < nvp; ++j) { if(mesh.polys[pIndex + j] == RC_MESH_NULL_IDX) break; //const ushort* v = &mesh.verts[p[j]*3]; int vIndex = mesh.polys[pIndex + j] * 3; poly[j*3+0] = mesh.verts[vIndex + 0]*cs; poly[j*3+1] = mesh.verts[vIndex + 1]*ch; poly[j*3+2] = mesh.verts[vIndex + 2]*cs; npoly++; } // Get the height data from the area of the polygon. hp.xmin = bounds[i*4+0]; hp.ymin = bounds[i*4+2]; hp.width = bounds[i*4+1]-bounds[i*4+0]; hp.height = bounds[i*4+3]-bounds[i*4+2]; getHeightData(chf, mesh.polys, pIndex, npoly, mesh.verts, borderSize, hp, stack, mesh.regs[i]); // Build detail mesh. int nverts = 0; if (!buildPolyDetail(ctx, poly, npoly, sampleDist, sampleMaxError, chf, hp, verts, ref nverts, tris, edges, samples)) { return false; } // Move detail verts to world space. for (int j = 0; j < nverts; ++j) { verts[j*3+0] += orig[0]; verts[j*3+1] += orig[1] + chf.ch; // Is this offset necessary? verts[j*3+2] += orig[2]; } // Offset poly too, will be used to flag checking. for (int j = 0; j < npoly; ++j) { poly[j*3+0] += orig[0]; poly[j*3+1] += orig[1]; poly[j*3+2] += orig[2]; } // Store detail submesh. int ntris = tris.Count/4; dmesh.meshes[i*4+0] = (uint)dmesh.nverts; dmesh.meshes[i*4+1] = (uint)nverts; dmesh.meshes[i*4+2] = (uint)dmesh.ntris; dmesh.meshes[i*4+3] = (uint)ntris; // Store vertices, allocate more memory if necessary. if (dmesh.nverts+nverts > vcap) { while (dmesh.nverts+nverts > vcap){ vcap += 256; } //float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); float[] newv = new float[vcap*3]; if (newv == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' ("+vcap*3+")."); return false; } if (dmesh.nverts != 0){ //memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts); for (int j=0;j<3*dmesh.nverts;++j){ newv[j] = dmesh.verts[j]; } } //rcFree(dmesh.verts); //dmesh.verts = null; dmesh.verts = newv; } for (int j = 0; j < nverts; ++j) { dmesh.verts[dmesh.nverts*3+0] = verts[j*3+0]; dmesh.verts[dmesh.nverts*3+1] = verts[j*3+1]; dmesh.verts[dmesh.nverts*3+2] = verts[j*3+2]; dmesh.nverts++; } // Store triangles, allocate more memory if necessary. if (dmesh.ntris+ntris > tcap) { while (dmesh.ntris+ntris > tcap){ tcap += 256; } //byte* newt = (byte*)rcAlloc(sizeof(byte)*tcap*4, RC_ALLOC_PERM); byte[] newt = new byte[tcap*4]; if (newt == null) { ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' ("+tcap*4+")."); return false; } if (dmesh.ntris != 0){ //memcpy(newt, dmesh.tris, sizeof(byte)*4*dmesh.ntris); for (int j = 0;j<4*dmesh.ntris;++j){ newt[j] = dmesh.tris[j]; } } //rcFree(dmesh.tris); dmesh.tris = newt; } for (int j = 0; j < ntris; ++j) { //const int* t = &tris[j*4]; int tIndex = j*4; dmesh.tris[dmesh.ntris*4+0] = (byte)tris[tIndex + 0]; dmesh.tris[dmesh.ntris*4+1] = (byte)tris[tIndex + 1]; dmesh.tris[dmesh.ntris*4+2] = (byte)tris[tIndex + 2]; dmesh.tris[dmesh.ntris*4+3] = getTriFlags(verts, tris[tIndex + 0]*3, verts, tris[tIndex + 1]*3, verts, tris[tIndex + 2]*3, poly, 0, npoly); dmesh.ntris++; } } ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); return true; }
static bool floodRegion(int x, int y, int i, ushort level, ushort r, rcCompactHeightfield chf, ushort[] srcReg, ushort[] srcDist, List<int> stack) { int w = chf.width; byte area = chf.areas[i]; // Flood fill mark region. //stack.resize(0); stack.Clear(); stack.Add((int)x); stack.Add((int)y); stack.Add((int)i); srcReg[i] = r; srcDist[i] = 0; ushort lev = (ushort)(level >= 2 ? level-2 : 0); int count = 0; while (stack.Count > 0) { int ci = rccsPop(stack); int cy = rccsPop(stack); int cx = rccsPop(stack); rcCompactSpan cs = chf.spans[ci]; // Check if any of the neighbours already have a valid region set. ushort ar = 0; for (int dir = 0; dir < 4; ++dir) { // 8 connected if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) { int ax = cx + rcGetDirOffsetX(dir); int ay = cy + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); if (chf.areas[ai] != area) continue; ushort nr = srcReg[ai]; if ((nr & RC_BORDER_REG) != 0) // Do not take borders into account. continue; if (nr != 0 && nr != r) { ar = nr; break; } rcCompactSpan aSpan = chf.spans[ai]; int dir2 = (dir+1) & 0x3; if (rcGetCon(aSpan, dir2) != RC_NOT_CONNECTED) { int ax2 = ax + rcGetDirOffsetX(dir2); int ay2 = ay + rcGetDirOffsetY(dir2); int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(aSpan, dir2); if (chf.areas[ai2] != area) continue; ushort nr2 = srcReg[ai2]; if (nr2 != 0 && nr2 != r) { ar = nr2; break; } } } } if (ar != 0) { srcReg[ci] = 0; continue; } count++; // Expand neighbours. for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) { int ax = cx + rcGetDirOffsetX(dir); int ay = cy + rcGetDirOffsetY(dir); int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); if (chf.areas[ai] != area) continue; if (chf.dist[ai] >= lev && srcReg[ai] == 0) { srcReg[ai] = r; srcDist[ai] = 0; stack.Add(ax); stack.Add(ay); stack.Add(ai); } } } } return count > 0; }