/// <summary> /// Finds the closest indices between two contours. Useful for merging contours. /// </summary> /// <param name="a">A contour.</param> /// <param name="b">Another contour.</param> /// <param name="indexA">The nearest index on contour A.</param> /// <param name="indexB">The nearest index on contour B.</param> private static void GetClosestIndices(Contour a, Contour b, out int indexA, out int indexB) { int closestDistance = int.MaxValue; int lengthA = a.vertices.Length; int lengthB = b.vertices.Length; indexA = -1; indexB = -1; for (int i = 0; i < lengthA; i++) { int vertA = i; int vertANext = (i + 1) % lengthA; int vertAPrev = (i + lengthA - 1) % lengthA; for (int j = 0; j < lengthB; j++) { int vertB = j; //vertB must be infront of vertA if (ContourVertex.IsLeft(ref a.vertices[vertAPrev], ref a.vertices[vertA], ref b.vertices[vertB]) && ContourVertex.IsLeft(ref a.vertices[vertA], ref a.vertices[vertANext], ref b.vertices[vertB])) { int dx = b.vertices[vertB].X - a.vertices[vertA].X; int dz = b.vertices[vertB].Z - a.vertices[vertA].Z; int tempDist = dx * dx + dz * dz; if (tempDist < closestDistance) { indexA = i; indexB = j; closestDistance = tempDist; } } } } }
/// <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> /// Merges another contour into this instance. /// </summary> /// <param name="contour">The contour to merge.</param> public void MergeWith(Contour contour) { int lengthA = vertices.Length; int lengthB = contour.vertices.Length; int ia, ib; GetClosestIndices(this, contour, out ia, out ib); //create a list with the capacity set to the max number of possible verts to avoid expanding the list. var newVerts = new List<ContourVertex>(vertices.Length + contour.vertices.Length + 2); //copy contour A for (int i = 0; i <= lengthA; i++) newVerts.Add(vertices[(ia + i) % lengthA]); //add contour B (other contour) to contour A (this contour) for (int i = 0; i <= lengthB; i++) newVerts.Add(contour.vertices[(ib + i) % lengthB]); vertices = newVerts.ToArray(); //delete the other contour contour.vertices = null; }