/// <summary> /// Gets the <see cref="CompactSpan"/> specified by the reference. /// </summary> /// <param name="spanRef">A reference to a span in this <see cref="CompactHeightfield"/>.</param> /// <returns>The referenced span.</returns> public CompactSpan this[CompactSpanReference spanRef] { get { return spans[spanRef.Index]; } }
/// <summary> /// Builds a set of <see cref="Contour"/>s around the generated regions. Must be called after regions are generated. /// </summary> /// <param name="maxError">The maximum allowed deviation in a simplified contour from a raw one.</param> /// <param name="maxEdgeLength">The maximum edge length.</param> /// <param name="buildFlags">Flags that change settings for the build process.</param> /// <returns>A <see cref="ContourSet"/> containing one contour per region.</returns> public ContourSet BuildContourSet(float maxError, int maxEdgeLength, ContourBuildFlags buildFlags) { BBox3 contourSetBounds = bounds; if (borderSize > 0) { //remove offset float pad = borderSize * cellSize; contourSetBounds.Min.X += pad; contourSetBounds.Min.Z += pad; contourSetBounds.Max.X -= pad; contourSetBounds.Max.Z -= pad; } int contourSetWidth = width - borderSize * 2; int contourSetLength = length - borderSize * 2; int maxContours = Math.Max(maxRegions, 8); var contours = new List<Contour>(maxContours); EdgeFlags[] flags = new EdgeFlags[spans.Length]; //Modify flags array by using the CompactHeightfield data //mark boundaries for (int z = 0; z < length; z++) { for (int x = 0; x < width; x++) { //loop through all the spans in the cell CompactCell c = cells[x + z * width]; for (int i = c.StartIndex, end = c.StartIndex + c.Count; i < end; i++) { CompactSpan s = spans[i]; //set the flag to 0 if the region is a border region or null. if (s.Region.IsNull || RegionId.HasFlags(s.Region, RegionFlags.Border)) { flags[i] = 0; continue; } //go through all the neighboring cells for (var dir = Direction.West; dir <= Direction.South; dir++) { //obtain region id RegionId r = RegionId.Null; if (s.IsConnected(dir)) { int dx = x + dir.GetHorizontalOffset(); int dz = z + dir.GetVerticalOffset(); int di = cells[dx + dz * width].StartIndex + CompactSpan.GetConnection(ref s, dir); r = spans[di].Region; } //region ids are equal if (r == s.Region) { //res marks all the internal edges EdgeFlagsHelper.AddEdge(ref flags[i], dir); } } //flags represents all the nonconnected edges, edges that are only internal //the edges need to be between different regions EdgeFlagsHelper.FlipEdges(ref flags[i]); } } } var verts = new List<ContourVertex>(); var simplified = new List<ContourVertex>(); for (int z = 0; z < length; z++) { for (int x = 0; x < width; x++) { CompactCell c = cells[x + z * width]; for (int i = c.StartIndex, end = c.StartIndex + c.Count; i < end; i++) { //flags is either 0000 or 1111 //in other words, not connected at all //or has all connections, which means span is in the middle and thus not an edge. if (flags[i] == EdgeFlags.None || flags[i] == EdgeFlags.All) { flags[i] = EdgeFlags.None; continue; } var spanRef = new CompactSpanReference(x, z, i); RegionId reg = this[spanRef].Region; if (reg.IsNull || RegionId.HasFlags(reg, RegionFlags.Border)) continue; //reset each iteration verts.Clear(); simplified.Clear(); //Walk along a contour, then build it WalkContour(spanRef, flags, verts); Contour.Simplify(verts, simplified, maxError, maxEdgeLength, buildFlags); Contour.RemoveDegenerateSegments(simplified); Contour contour = new Contour(simplified, reg, areas[i], borderSize); if (!contour.IsNull) contours.Add(contour); } } } //Check and merge bad contours for (int i = 0; i < contours.Count; i++) { Contour cont = contours[i]; //Check if contour is backwards if (cont.Area2D < 0) { //Find another contour to merge with int mergeIndex = -1; for (int j = 0; j < contours.Count; j++) { if (i == j) continue; //Must have at least one vertex, the same region ID, and be going forwards. Contour contj = contours[j]; if (contj.Vertices.Length != 0 && contj.RegionId == cont.RegionId && contj.Area2D > 0) { mergeIndex = j; break; } } //Merge if found. if (mergeIndex != -1) { contours[mergeIndex].MergeWith(cont); contours.RemoveAt(i); i--; } } } return new ContourSet(contours, contourSetBounds, contourSetWidth, contourSetLength); }
/// <summary> /// Try to visit all the spans. May be needed in filtering small regions. /// </summary> /// <param name="regions">an array of region values</param> /// <param name="spanRef">The span to start walking from.</param> /// <param name="dir">The direction to start walking in.</param> /// <param name="cont">A collection of regions to append to.</param> private void WalkContour(RegionId[] regions, CompactSpanReference spanRef, Direction dir, List<RegionId> cont) { Direction startDir = dir; int starti = spanRef.Index; CompactSpan ss = spans[starti]; RegionId curReg = RegionId.Null; if (ss.IsConnected(dir)) { int dx = spanRef.X + dir.GetHorizontalOffset(); int dy = spanRef.Y + dir.GetVerticalOffset(); int di = cells[dx + dy * width].StartIndex + CompactSpan.GetConnection(ref ss, dir); curReg = regions[di]; } cont.Add(curReg); int iter = 0; while (++iter < 40000) { CompactSpan s = spans[spanRef.Index]; if (IsSolidEdge(regions, ref spanRef, dir)) { //choose the edge corner RegionId r = RegionId.Null; if (s.IsConnected(dir)) { int dx = spanRef.X + dir.GetHorizontalOffset(); int dy = spanRef.Y + dir.GetVerticalOffset(); int di = cells[dx + dy * width].StartIndex + CompactSpan.GetConnection(ref s, dir); r = regions[di]; } if (r != curReg) { curReg = r; cont.Add(curReg); } dir = dir.NextClockwise(); //rotate clockwise } else { int di = -1; int dx = spanRef.X + dir.GetHorizontalOffset(); int dy = spanRef.Y + dir.GetVerticalOffset(); if (s.IsConnected(dir)) { CompactCell dc = cells[dx + dy * width]; di = dc.StartIndex + CompactSpan.GetConnection(ref s, dir); } if (di == -1) { //shouldn't happen return; } spanRef = new CompactSpanReference(dx, dy, di); dir = dir.NextCounterClockwise(); //rotate counterclockwise } if (starti == spanRef.Index && startDir == dir) break; } //remove adjacent duplicates if (cont.Count > 1) { for (int j = 0; j < cont.Count;) { //next element int nj = (j + 1) % cont.Count; //adjacent duplicate found if (cont[j] == cont[nj]) cont.RemoveAt(j); else j++; } } }
/// <summary> /// Initial generation of the contours /// </summary> /// <param name="spanReference">A referecne to the span to start walking from.</param> /// <param name="flags">An array of flags determinining </param> /// <param name="points">The vertices of a contour.</param> private void WalkContour(CompactSpanReference spanReference, EdgeFlags[] flags, List<ContourVertex> points) { Direction dir = Direction.West; //find the first direction that has a connection while (!EdgeFlagsHelper.IsConnected(ref flags[spanReference.Index], dir)) dir++; Direction startDir = dir; int startIndex = spanReference.Index; Area area = areas[startIndex]; //TODO make the max iterations value a variable int iter = 0; while (++iter < 40000) { // this direction is connected if (EdgeFlagsHelper.IsConnected(ref flags[spanReference.Index], dir)) { // choose the edge corner bool isBorderVertex; bool isAreaBorder = false; int px = spanReference.X; int py = GetCornerHeight(spanReference, dir, out isBorderVertex); int pz = spanReference.Y; switch (dir) { case Direction.West: pz++; break; case Direction.North: px++; pz++; break; case Direction.East: px++; break; } RegionId r = RegionId.Null; CompactSpan s = this[spanReference]; if (s.IsConnected(dir)) { int dx = spanReference.X + dir.GetHorizontalOffset(); int dy = spanReference.Y + dir.GetVerticalOffset(); int di = cells[dx + dy * width].StartIndex + CompactSpan.GetConnection(ref s, dir); r = spans[di].Region; if (area != areas[di]) isAreaBorder = true; } // apply flags if neccessary if (isBorderVertex) r = RegionId.WithFlags(r, RegionFlags.VertexBorder); if (isAreaBorder) r = RegionId.WithFlags(r, RegionFlags.AreaBorder); //save the point points.Add(new ContourVertex(px, py, pz, r)); EdgeFlagsHelper.RemoveEdge(ref flags[spanReference.Index], dir); // remove visited edges dir = dir.NextClockwise(); // rotate clockwise } else { //get a new cell(x, y) and span index(i) int di = -1; int dx = spanReference.X + dir.GetHorizontalOffset(); int dy = spanReference.Y + dir.GetVerticalOffset(); CompactSpan s = this[spanReference]; if (s.IsConnected(dir)) { CompactCell dc = cells[dx + dy * width]; di = dc.StartIndex + CompactSpan.GetConnection(ref s, dir); } if (di == -1) { // shouldn't happen // TODO if this shouldn't happen, this check shouldn't be necessary. throw new InvalidOperationException("Something went wrong"); } spanReference = new CompactSpanReference(dx, dy, di); dir = dir.NextCounterClockwise(); // rotate counterclockwise } if (startIndex == spanReference.Index && startDir == dir) break; } }
/// <summary> /// Checks whether the edge from a span in a direction is a solid edge. /// A solid edge is an edge between two regions. /// </summary> /// <param name="regions">The region ID array.</param> /// <param name="spanRef">A reference to the span connected to the edge.</param> /// <param name="dir">The direction of the edge.</param> /// <returns>A value indicating whether the described edge is solid.</returns> private bool IsSolidEdge(RegionId[] regions, ref CompactSpanReference spanRef, Direction dir) { CompactSpan s = spans[spanRef.Index]; RegionId r = RegionId.Null; if (s.IsConnected(dir)) { int dx = spanRef.X + dir.GetHorizontalOffset(); int dy = spanRef.Y + dir.GetVerticalOffset(); int di = cells[dx + dy * width].StartIndex + CompactSpan.GetConnection(ref s, dir); r = regions[di]; } if (r == regions[spanRef.Index]) return false; return true; }
/// <summary> /// Helper method for WalkContour function /// </summary> /// <param name="sr">The span to get the corner height for.</param> /// <param name="dir">The direction to get the corner height from.</param> /// <param name="isBorderVertex">Determine whether the vertex is a border or not.</param> /// <returns>The corner height.</returns> private int GetCornerHeight(CompactSpanReference sr, Direction dir, out bool isBorderVertex) { isBorderVertex = false; CompactSpan s = this[sr]; int cornerHeight = s.Minimum; Direction dirp = dir.NextClockwise(); //new clockwise direction RegionId[] cornerRegs = new RegionId[4]; Area[] cornerAreas = new Area[4]; //combine region and area codes in order to prevent border vertices, which are in between two areas, to be removed cornerRegs[0] = s.Region; cornerAreas[0] = areas[sr.Index]; if (s.IsConnected(dir)) { //get neighbor span int dx = sr.X + dir.GetHorizontalOffset(); int dy = sr.Y + dir.GetVerticalOffset(); int di = cells[dx + dy * width].StartIndex + CompactSpan.GetConnection(ref s, dir); CompactSpan ds = spans[di]; cornerHeight = Math.Max(cornerHeight, ds.Minimum); cornerRegs[1] = spans[di].Region; cornerAreas[1] = areas[di]; //get neighbor of neighbor's span if (ds.IsConnected(dirp)) { int dx2 = dx + dirp.GetHorizontalOffset(); int dy2 = dy + dirp.GetVerticalOffset(); int di2 = cells[dx2 + dy2 * width].StartIndex + CompactSpan.GetConnection(ref ds, dirp); CompactSpan ds2 = spans[di2]; cornerHeight = Math.Max(cornerHeight, ds2.Minimum); cornerRegs[2] = ds2.Region; cornerAreas[2] = areas[di2]; } } //get neighbor span if (s.IsConnected(dirp)) { int dx = sr.X + dirp.GetHorizontalOffset(); int dy = sr.Y + dirp.GetVerticalOffset(); int di = cells[dx + dy * width].StartIndex + CompactSpan.GetConnection(ref s, dirp); CompactSpan ds = spans[di]; cornerHeight = Math.Max(cornerHeight, ds.Minimum); cornerRegs[3] = ds.Region; cornerAreas[3] = areas[di]; //get neighbor of neighbor's span if (ds.IsConnected(dir)) { int dx2 = dx + dir.GetHorizontalOffset(); int dy2 = dy + dir.GetVerticalOffset(); int di2 = cells[dx2 + dy2 * width].StartIndex + CompactSpan.GetConnection(ref ds, dir); CompactSpan ds2 = spans[di2]; cornerHeight = Math.Max(cornerHeight, ds2.Minimum); cornerRegs[2] = ds2.Region; cornerAreas[2] = areas[di2]; } } //check if vertex is special edge vertex //if so, these vertices will be removed later for (int j = 0; j < 4; j++) { int a = j; int b = (j + 1) % 4; int c = (j + 2) % 4; int d = (j + 3) % 4; RegionId ra = cornerRegs[a], rb = cornerRegs[b], rc = cornerRegs[c], rd = cornerRegs[d]; Area aa = cornerAreas[a], ab = cornerAreas[b], ac = cornerAreas[c], ad = cornerAreas[d]; //the vertex is a border vertex if: //two same exterior cells in a row followed by two interior cells and none of the regions are out of bounds bool twoSameExteriors = RegionId.HasFlags(ra, RegionFlags.Border) && RegionId.HasFlags(rb, RegionFlags.Border) && (ra == rb && aa == ab); bool twoSameInteriors = !(RegionId.HasFlags(rc, RegionFlags.Border) || RegionId.HasFlags(rd, RegionFlags.Border)); bool intsSameArea = ac == ad; bool noZeros = ra != 0 && rb != 0 && rc != 0 && rd != 0 && aa != 0 && ab != 0 && ac != 0 && ad != 0; if (twoSameExteriors && twoSameInteriors && intsSameArea && noZeros) { isBorderVertex = true; break; } } return cornerHeight; }
/// <summary> /// Floods the regions at a certain level /// </summary> /// <param name="regions">source region</param> /// <param name="floodDistances">source distances</param> /// <param name="regionIndex">region id</param> /// <param name="level">current level</param> /// <param name="start">A reference to the starting span.</param> /// <returns>Always true.</returns> private bool FloodRegion(RegionId[] regions, int[] floodDistances, int regionIndex, int level, ref CompactSpanReference start) { //TODO this method should always return true, make it not return a bool? //flood fill mark region Stack<CompactSpanReference> stack = new Stack<CompactSpanReference>(); stack.Push(start); Area area = areas[start.Index]; regions[start.Index] = new RegionId(regionIndex); floodDistances[start.Index] = 0; int lev = level >= 2 ? level - 2 : 0; int count = 0; while (stack.Count > 0) { CompactSpanReference cell = stack.Pop(); CompactSpan cs = spans[cell.Index]; //check if any of the neighbors already have a valid reigon set RegionId ar = RegionId.Null; for (var dir = Direction.West; dir <= Direction.South; dir++) { //8 connected if (cs.IsConnected(dir)) { int dx = cell.X + dir.GetHorizontalOffset(); int dy = cell.Y + dir.GetVerticalOffset(); int di = cells[dx + dy * width].StartIndex + CompactSpan.GetConnection(ref cs, dir); if (areas[di] != area) continue; RegionId nr = regions[di]; if (RegionId.HasFlags(nr, RegionFlags.Border)) //skip borders continue; if (nr != 0 && nr != regionIndex) { ar = nr; break; } CompactSpan ds = spans[di]; Direction dir2 = dir.NextClockwise(); if (ds.IsConnected(dir2)) { int dx2 = dx + dir2.GetHorizontalOffset(); int dy2 = dy + dir2.GetVerticalOffset(); int di2 = cells[dx2 + dy2 * width].StartIndex + CompactSpan.GetConnection(ref ds, dir2); if (areas[di2] != area) continue; RegionId nr2 = regions[di2]; if (nr2 != 0 && nr2 != regionIndex) { ar = nr2; break; } } } } if (ar != 0) { regions[cell.Index] = RegionId.Null; continue; } count++; //expand neighbors for (var dir = Direction.West; dir <= Direction.South; dir++) { if (cs.IsConnected(dir)) { int dx = cell.X + dir.GetHorizontalOffset(); int dy = cell.Y + dir.GetVerticalOffset(); int di = cells[dx + dy * width].StartIndex + CompactSpan.GetConnection(ref cs, dir); if (areas[di] != area) continue; if (distances[di] >= lev && regions[di] == 0) { regions[di] = new RegionId(regionIndex); floodDistances[di] = 0; stack.Push(new CompactSpanReference(dx, dy, di)); } } } } return count > 0; }
/// <summary> /// Discards regions that are too small. /// </summary> /// <param name="regionIds">region data</param> /// <param name="minRegionArea">The minimum area a region can have</param> /// <param name="mergeRegionSize">The size of the regions after merging</param> /// <param name="maxRegionId">determines the number of regions available</param> /// <returns>The reduced number of regions.</returns> private int FilterSmallRegions(RegionId[] regionIds, int minRegionArea, int mergeRegionSize, int maxRegionId) { int numRegions = maxRegionId + 1; Region[] regions = new Region[numRegions]; //construct regions for (int i = 0; i < numRegions; i++) regions[i] = new Region(i); //find edge of a region and find connections around a contour for (int y = 0; y < length; y++) { for (int x = 0; x < width; x++) { CompactCell c = cells[x + y * width]; for (int i = c.StartIndex, end = c.StartIndex + c.Count; i < end; i++) { CompactSpanReference spanRef = new CompactSpanReference(x, y, i); //HACK since the border region flag makes r negative, I changed r == 0 to r <= 0. Figure out exactly what maxRegionId's purpose is and see if Region.IsBorderOrNull is all we need. int r = (int)regionIds[i]; if (r <= 0 || (int)r >= numRegions) continue; Region reg = regions[(int)r]; reg.SpanCount++; //update floors for (int j = c.StartIndex; j < end; j++) { if (i == j) continue; RegionId floorId = regionIds[j]; if (floorId == 0 || (int)floorId >= numRegions) continue; reg.AddUniqueFloorRegion(floorId); } //have found contour if (reg.Connections.Count > 0) continue; reg.AreaType = areas[i]; //check if this cell is next to a border for (var dir = Direction.West; dir <= Direction.South; dir++) { if (IsSolidEdge(regionIds, ref spanRef, dir)) { //The cell is at a border. //Walk around contour to find all neighbors WalkContour(regionIds, spanRef, dir, reg.Connections); break; } } } } } //Remove too small regions Stack<RegionId> stack = new Stack<RegionId>(); List<RegionId> trace = new List<RegionId>(); for (int i = 0; i < numRegions; i++) { Region reg = regions[i]; if (reg.IsBorderOrNull || reg.SpanCount == 0 || reg.Visited) continue; //count the total size of all connected regions //also keep track of the regions connections to a tile border bool connectsToBorder = false; int spanCount = 0; stack.Clear(); trace.Clear(); reg.Visited = true; stack.Push(reg.Id); while (stack.Count > 0) { //pop RegionId ri = stack.Pop(); Region creg = regions[(int)ri]; spanCount += creg.SpanCount; trace.Add(ri); for (int j = 0; j < creg.Connections.Count; j++) { if (RegionId.HasFlags(creg.Connections[j], RegionFlags.Border)) { connectsToBorder = true; continue; } Region neiReg = regions[(int)creg.Connections[j]]; if (neiReg.Visited || neiReg.IsBorderOrNull) continue; //visit stack.Push(neiReg.Id); neiReg.Visited = true; } } //if the accumulated region size is too small, remove it //do not remove areas which connect to tile borders as their size can't 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++) { int index = (int)trace[j]; regions[index].SpanCount = 0; regions[index].Id = RegionId.Null; } } } //Merge too small regions to neighbor regions int mergeCount = 0; do { mergeCount = 0; for (int i = 0; i < numRegions; i++) { Region reg = regions[i]; if (reg.IsBorderOrNull || reg.SpanCount == 0) continue; //check to see if region should be merged if (reg.SpanCount > mergeRegionSize && reg.IsConnectedToBorder()) continue; //small region with more than one connection or region which is not connected to border at all //find smallest neighbor that connects to this one int smallest = int.MaxValue; RegionId mergeId = reg.Id; for (int j = 0; j < reg.Connections.Count; j++) { if (RegionId.HasFlags(reg.Connections[j], RegionFlags.Border)) continue; Region mreg = regions[(int)reg.Connections[j]]; if (mreg.IsBorderOrNull) continue; if (mreg.SpanCount < smallest && reg.CanMergeWith(mreg) && mreg.CanMergeWith(reg)) { smallest = mreg.SpanCount; mergeId = mreg.Id; } } //found new id if (mergeId != reg.Id) { RegionId oldId = reg.Id; Region target = regions[(int)mergeId]; //merge regions if (target.MergeWithRegion(reg)) { //fix regions pointing to current region for (int j = 0; j < numRegions; j++) { if (regions[j].IsBorderOrNull) continue; //if another regions was already merged into current region //change the nid of the previous region too if (regions[j].Id == oldId) regions[j].Id = mergeId; //replace current region with new one if current region is neighbor regions[j].ReplaceNeighbour(oldId, mergeId); } mergeCount++; } } } } while (mergeCount > 0); //Compress region ids for (int i = 0; i < numRegions; i++) { regions[i].Remap = false; if (regions[i].IsBorderOrNull) continue; regions[i].Remap = true; } int regIdGen = 0; for (int i = 0; i < numRegions; i++) { if (!regions[i].Remap) continue; RegionId oldId = regions[i].Id; RegionId newId = new RegionId(++regIdGen); for (int j = i; j < numRegions; j++) { if (regions[j].Id == oldId) { regions[j].Id = newId; regions[j].Remap = false; } } } //Remap regions for (int i = 0; i < spans.Length; i++) { if (!RegionId.HasFlags(regionIds[i], RegionFlags.Border)) regionIds[i] = regions[(int)regionIds[i]].Id; } return regIdGen; }
/// <summary> /// Floodfill heightfield to get 2D height data, starting at vertex locations /// </summary> /// <param name="compactField">Original heightfield data</param> /// <param name="poly">Polygon in PolyMesh</param> /// <param name="polyCount">Number of vertices per polygon</param> /// <param name="verts">PolyMesh Vertices</param> /// <param name="borderSize">Heightfield border size</param> /// <param name="hp">HeightPatch which extracts heightfield data</param> /// <param name="stack">Temporary stack of CompactSpanReferences</param> private void GetHeightDataSeedsFromVertices(CompactHeightfield compactField, PolyMesh.Polygon poly, int polyCount, PolyVertex[] verts, int borderSize, HeightPatch hp, List<CompactSpanReference> stack) { hp.SetAll(0); //use poly vertices as seed points for (int j = 0; j < polyCount; j++) { var csr = new CompactSpanReference(0, 0, -1); int dmin = int.MaxValue; var v = verts[poly.Vertices[j]]; for (int k = 0; k < 9; k++) { //get vertices and offset x and z coordinates depending on current drection int ax = v.X + VertexOffset[k * 2 + 0]; int ay = v.Y; int az = v.Z + VertexOffset[k * 2 + 1]; //skip if out of bounds if (ax < hp.X || ax >= hp.X + hp.Width || az < hp.Y || az >= hp.Y + hp.Length) continue; //get new cell CompactCell c = compactField.Cells[(az + borderSize) * compactField.Width + (ax + borderSize)]; //loop through all the spans for (int i = c.StartIndex, end = c.StartIndex + c.Count; i < end; i++) { CompactSpan s = compactField.Spans[i]; //find minimum y-distance int d = Math.Abs(ay - s.Minimum); if (d < dmin) { csr = new CompactSpanReference(ax, az, i); dmin = d; } } } //only add if something new found if (csr.Index != -1) { stack.Add(csr); } } //find center of polygon using flood fill int pcx = 0, pcz = 0; for (int j = 0; j < polyCount; j++) { var v = verts[poly.Vertices[j]]; pcx += v.X; pcz += v.Z; } pcx /= polyCount; pcz /= polyCount; //stack groups 3 elements as one part foreach (var cell in stack) { int idx = (cell.Y - hp.Y) * hp.Width + (cell.X - hp.X); hp[idx] = 1; } //process the entire stack while (stack.Count > 0) { var cell = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); //check if close to center of polygon if (Math.Abs(cell.X - pcx) <= 1 && Math.Abs(cell.Y - pcz) <= 1) { //clear the stack and add a new group stack.Clear(); stack.Add(cell); break; } CompactSpan cs = compactField[cell]; //check all four directions for (var dir = Direction.West; dir <= Direction.South; dir++) { //skip if disconnected if (!cs.IsConnected(dir)) continue; //get neighbor int ax = cell.X + dir.GetHorizontalOffset(); int ay = cell.Y + dir.GetVerticalOffset(); //skip if out of bounds if (ax < hp.X || ax >= (hp.X + hp.Width) || ay < hp.Y || ay >= (hp.Y + hp.Length)) continue; if (hp[(ay - hp.Y) * hp.Width + (ax - hp.X)] != 0) continue; //get the new index int ai = compactField.Cells[(ay + borderSize) * compactField.Width + (ax + borderSize)].StartIndex + CompactSpan.GetConnection(ref cs, dir); //save data int idx = (ay - hp.Y) * hp.Width + (ax - hp.X); hp[idx] = 1; //push to stack stack.Add(new CompactSpanReference(ax, ay, ai)); } } //clear the heightpatch hp.Clear(); //mark start locations for (int i = 0; i < stack.Count; i++) { var c = stack[i]; //set new heightpatch data int idx = (c.Y - hp.Y) * hp.Width + (c.X - hp.X); CompactSpan cs = compactField.Spans[c.Index]; hp[idx] = cs.Minimum; stack[i] = new CompactSpanReference(c.X + borderSize, c.Y + borderSize, c.Index); } }