private static void HandleFlagGUI(ref ContourBuildFlags flags , string label , ContourBuildFlags flag , bool isInspector) { if (isInspector) { flags = EditorGUILayout.Toggle(label, (flags & flag) != 0) ? (flags | flag) : (flags & ~flag); } else { flags = GUILayout.Toggle((flags & flag) != 0, label) ? (flags | flag) : (flags & ~flag); } }
/// <summary> /// Builds a contour set from the region outlines in the provided <see cref="CompactHeightfield"/>. /// </summary> /// <remarks> /// <para> /// The raw contours will match the region outlines exactly. The edgeMaxDeviation /// and maxEdgeLength parameters control how closely the simplified contours will match /// the raw contours. /// </para> /// <para> /// Simplified contours are generated such that the vertices for portals between areas /// match up. (They are considered mandatory vertices.) /// </para> /// <para> /// Setting maxEdgeLength to zero will disabled the feature. /// </para> /// </remarks> /// <param name="context">The context to use for the build.</param> /// <param name="field">The field to use for the build.(Must have region data.)</param> /// <param name="edgeMaxDeviation"> /// The maximum distance a simplified edge may deviate from the raw contour's vertices. /// [Limit: >= 0] /// </param> /// <param name="maxEdgeLength"> /// The maximum allowed length of a simplified edge. [Limit: >= 0] /// </param> /// <param name="flags">The build flags.</param> /// <returns>The contour set, or null on failure.</returns> public static ContourSet Build(BuildContext context, CompactHeightfield field , float edgeMaxDeviation, int maxEdgeLength, ContourBuildFlags flags) { if (context == null || field == null || field.IsDisposed || edgeMaxDeviation < 0 || maxEdgeLength < 0) { return(null); } ContourSetEx root = new ContourSetEx(); if (ContourSetEx.nmcsBuildSet(context.root, field , edgeMaxDeviation, maxEdgeLength , root , flags)) { return(new ContourSet(root)); } return(null); }
/// <summary> /// Simplify the contours by reducing the number of edges /// </summary> /// <param name="rawVerts">Initial vertices</param> /// <param name="simplified">New and simplified vertices</param> /// <param name="maxError">Maximum error allowed</param> /// <param name="maxEdgeLen">The maximum edge length allowed</param> /// <param name="buildFlags">Flags determines how to split the long edges</param> public static void Simplify(List <ContourVertex> rawVerts, List <ContourVertex> simplified, float maxError, int maxEdgeLen, ContourBuildFlags buildFlags) { bool tesselateWallEdges = (buildFlags & ContourBuildFlags.TessellateWallEdges) == ContourBuildFlags.TessellateWallEdges; bool tesselateAreaEdges = (buildFlags & ContourBuildFlags.TessellateAreaEdges) == ContourBuildFlags.TessellateAreaEdges; //add initial points bool hasConnections = false; for (int i = 0; i < rawVerts.Count; i++) { if (rawVerts[i].RegionId.Id != 0) { hasConnections = true; break; } } if (hasConnections) { //contour has some portals to other regions //add new point to every location where region changes for (int i = 0, end = rawVerts.Count; i < end; i++) { int ii = (i + 1) % end; bool differentRegions = rawVerts[i].RegionId.Id != rawVerts[ii].RegionId.Id; bool areaBorders = RegionId.HasFlags(rawVerts[i].RegionId, RegionFlags.AreaBorder) != RegionId.HasFlags(rawVerts[ii].RegionId, RegionFlags.AreaBorder); if (differentRegions || areaBorders) { simplified.Add(new ContourVertex(rawVerts[i], i)); } } } //add some points if thhere are no connections if (simplified.Count == 0) { //find lower-left and upper-right vertices of contour int lowerLeftX = rawVerts[0].X; int lowerLeftY = rawVerts[0].Y; int lowerLeftZ = rawVerts[0].Z; RegionId lowerLeftI = RegionId.Null; int upperRightX = rawVerts[0].X; int upperRightY = rawVerts[0].Y; int upperRightZ = rawVerts[0].Z; RegionId upperRightI = RegionId.Null; //iterate through points for (int i = 0; i < rawVerts.Count; i++) { int x = rawVerts[i].X; int y = rawVerts[i].Y; int z = rawVerts[i].Z; if (x < lowerLeftX || (x == lowerLeftX && z < lowerLeftZ)) { lowerLeftX = x; lowerLeftY = y; lowerLeftZ = z; lowerLeftI = new RegionId(i); } if (x > upperRightX || (x == upperRightX && z > upperRightZ)) { upperRightX = x; upperRightY = y; upperRightZ = z; upperRightI = new RegionId(i); } } //save the points simplified.Add(new ContourVertex(lowerLeftX, lowerLeftY, lowerLeftZ, lowerLeftI)); simplified.Add(new ContourVertex(upperRightX, upperRightY, upperRightZ, upperRightI)); } //add points until all points are within error tolerance of simplified slope int numPoints = rawVerts.Count; for (int i = 0; i < simplified.Count;) { int ii = (i + 1) % simplified.Count; //obtain (x, z) coordinates, along with region id int ax = simplified[i].X; int az = simplified[i].Z; int ai = (int)simplified[i].RegionId; int bx = simplified[ii].X; int bz = simplified[ii].Z; int bi = (int)simplified[ii].RegionId; float maxDeviation = 0; int maxi = -1; int ci, countIncrement, endi; //traverse segment in lexilogical order (try to go from smallest to largest coordinates?) if (bx > ax || (bx == ax && bz > az)) { countIncrement = 1; ci = (int)(ai + countIncrement) % numPoints; endi = (int)bi; } else { countIncrement = numPoints - 1; ci = (int)(bi + countIncrement) % numPoints; endi = (int)ai; } //tessellate only outer edges or edges between areas if (rawVerts[ci].RegionId.Id == 0 || RegionId.HasFlags(rawVerts[ci].RegionId, RegionFlags.AreaBorder)) { //find the maximum deviation while (ci != endi) { float deviation = Distance.PointToSegment2DSquared(rawVerts[ci].X, rawVerts[ci].Z, ax, az, bx, bz); if (deviation > maxDeviation) { maxDeviation = deviation; maxi = ci; } ci = (ci + countIncrement) % numPoints; } } //If max deviation is larger than accepted error, add new point if (maxi != -1 && maxDeviation > (maxError * maxError)) { simplified.Insert(i + 1, new ContourVertex(rawVerts[maxi], maxi)); } else { i++; } } //split too long edges if (maxEdgeLen > 0 && (tesselateAreaEdges || tesselateWallEdges)) { for (int i = 0; i < simplified.Count;) { int ii = (i + 1) % simplified.Count; //get (x, z) coordinates along with region id int ax = simplified[i].X; int az = simplified[i].Z; int ai = (int)simplified[i].RegionId; int bx = simplified[ii].X; int bz = simplified[ii].Z; int bi = (int)simplified[ii].RegionId; //find maximum deviation from segment int maxi = -1; int ci = (int)(ai + 1) % numPoints; //tessellate only outer edges or edges between areas bool tess = false; //wall edges if (tesselateWallEdges && rawVerts[ci].RegionId.Id == 0) { tess = true; } //edges between areas if (tesselateAreaEdges && RegionId.HasFlags(rawVerts[ci].RegionId, RegionFlags.AreaBorder)) { tess = true; } if (tess) { int dx = bx - ax; int dz = bz - az; if (dx * dx + dz * dz > maxEdgeLen * maxEdgeLen) { //round based on lexilogical direction (smallest to largest cooridinates, first by x. //if x coordinates are equal, then compare z coordinates) int n = bi < ai ? (bi + numPoints - ai) : (bi - ai); if (n > 1) { if (bx > ax || (bx == ax && bz > az)) { maxi = (int)(ai + n / 2) % numPoints; } else { maxi = (int)(ai + (n + 1) / 2) % numPoints; } } } } //add new point if (maxi != -1) { simplified.Insert(i + 1, new ContourVertex(rawVerts[maxi], maxi)); } else { i++; } } } for (int i = 0; i < simplified.Count; i++) { ContourVertex sv = simplified[i]; //take edge vertex flag from current raw point and neighbor region from next raw point int ai = ((int)sv.RegionId + 1) % numPoints; RegionId bi = sv.RegionId; //save new region id sv.RegionId = RegionId.FromRawBits(((int)rawVerts[ai].RegionId & (RegionId.MaskId | (int)RegionFlags.AreaBorder)) | ((int)rawVerts[(int)bi].RegionId & (int)RegionFlags.VertexBorder)); simplified[i] = sv; } }
/// <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> /// Builds a contour set from the region outlines in the provided <see cref="CompactHeightfield"/>. /// </summary> /// <remarks> /// <para> /// The raw contours will match the region outlines exactly. The edgeMaxDeviation /// and maxEdgeLength parameters control how closely the simplified contours will match /// the raw contours. /// </para> /// <para> /// Simplified contours are generated such that the vertices for portals between areas /// match up. (They are considered mandatory vertices.) /// </para> /// <para> /// Setting maxEdgeLength to zero will disabled the feature. /// </para> /// </remarks> /// <param name="context">The context to use for the build.</param> /// <param name="field">The field to use for the build.(Must have region data.)</param> /// <param name="edgeMaxDeviation"> /// The maximum distance a simplified edge may deviate from the raw contour's vertices. /// [Limit: >= 0] /// </param> /// <param name="maxEdgeLength"> /// The maximum allowed length of a simplified edge. [Limit: >= 0] /// </param> /// <param name="flags">The build flags.</param> /// <returns>The contour set, or null on failure.</returns> public static ContourSet Build(BuildContext context, CompactHeightfield field , float edgeMaxDeviation, int maxEdgeLength, ContourBuildFlags flags) { if (context == null || field == null || field.IsDisposed || edgeMaxDeviation < 0 || maxEdgeLength < 0) { return null; } ContourSetEx root = new ContourSetEx(); if (ContourSetEx.nmcsBuildSet(context.root, field , edgeMaxDeviation, maxEdgeLength , root , flags)) { return new ContourSet(root); } return null; }
/// <summary> /// Simplify the contours by reducing the number of edges /// </summary> /// <param name="rawVerts">Initial vertices</param> /// <param name="simplified">New and simplified vertices</param> /// <param name="maxError">Maximum error allowed</param> /// <param name="maxEdgeLen">The maximum edge length allowed</param> /// <param name="buildFlags">Flags determines how to split the long edges</param> public static void Simplify(List<ContourVertex> rawVerts, List<ContourVertex> simplified, float maxError, int maxEdgeLen, ContourBuildFlags buildFlags) { bool tesselateWallEdges = (buildFlags & ContourBuildFlags.TessellateWallEdges) == ContourBuildFlags.TessellateWallEdges; bool tesselateAreaEdges = (buildFlags & ContourBuildFlags.TessellateAreaEdges) == ContourBuildFlags.TessellateAreaEdges; //add initial points bool hasConnections = false; for (int i = 0; i < rawVerts.Count; i++) { if (rawVerts[i].RegionId.Id != 0) { hasConnections = true; break; } } if (hasConnections) { //contour has some portals to other regions //add new point to every location where region changes for (int i = 0, end = rawVerts.Count; i < end; i++) { int ii = (i + 1) % end; bool differentRegions = rawVerts[i].RegionId.Id != rawVerts[ii].RegionId.Id; bool areaBorders = RegionId.HasFlags(rawVerts[i].RegionId, RegionFlags.AreaBorder) != RegionId.HasFlags(rawVerts[ii].RegionId, RegionFlags.AreaBorder); if (differentRegions || areaBorders) { simplified.Add(new ContourVertex(rawVerts[i], i)); } } } //add some points if thhere are no connections if (simplified.Count == 0) { //find lower-left and upper-right vertices of contour int lowerLeftX = rawVerts[0].X; int lowerLeftY = rawVerts[0].Y; int lowerLeftZ = rawVerts[0].Z; RegionId lowerLeftI = RegionId.Null; int upperRightX = rawVerts[0].X; int upperRightY = rawVerts[0].Y; int upperRightZ = rawVerts[0].Z; RegionId upperRightI = RegionId.Null; //iterate through points for (int i = 0; i < rawVerts.Count; i++) { int x = rawVerts[i].X; int y = rawVerts[i].Y; int z = rawVerts[i].Z; if (x < lowerLeftX || (x == lowerLeftX && z < lowerLeftZ)) { lowerLeftX = x; lowerLeftY = y; lowerLeftZ = z; lowerLeftI = new RegionId(i); } if (x > upperRightX || (x == upperRightX && z > upperRightZ)) { upperRightX = x; upperRightY = y; upperRightZ = z; upperRightI = new RegionId(i); } } //save the points simplified.Add(new ContourVertex(lowerLeftX, lowerLeftY, lowerLeftZ, lowerLeftI)); simplified.Add(new ContourVertex(upperRightX, upperRightY, upperRightZ, upperRightI)); } //add points until all points are within error tolerance of simplified slope int numPoints = rawVerts.Count; for (int i = 0; i < simplified.Count;) { int ii = (i + 1) % simplified.Count; //obtain (x, z) coordinates, along with region id int ax = simplified[i].X; int az = simplified[i].Z; int ai = (int)simplified[i].RegionId; int bx = simplified[ii].X; int bz = simplified[ii].Z; int bi = (int)simplified[ii].RegionId; float maxDeviation = 0; int maxi = -1; int ci, countIncrement, endi; //traverse segment in lexilogical order (try to go from smallest to largest coordinates?) if (bx > ax || (bx == ax && bz > az)) { countIncrement = 1; ci = (int)(ai + countIncrement) % numPoints; endi = (int)bi; } else { countIncrement = numPoints - 1; ci = (int)(bi + countIncrement) % numPoints; endi = (int)ai; } //tessellate only outer edges or edges between areas if (rawVerts[ci].RegionId.Id == 0 || RegionId.HasFlags(rawVerts[ci].RegionId, RegionFlags.AreaBorder)) { //find the maximum deviation while (ci != endi) { float deviation = Distance.PointToSegment2DSquared(rawVerts[ci].X, rawVerts[ci].Z, ax, az, bx, bz); if (deviation > maxDeviation) { maxDeviation = deviation; maxi = ci; } ci = (ci + countIncrement) % numPoints; } } //If max deviation is larger than accepted error, add new point if (maxi != -1 && maxDeviation > (maxError * maxError)) { simplified.Insert(i + 1, new ContourVertex(rawVerts[maxi], maxi)); } else { i++; } } //split too long edges if (maxEdgeLen > 0 && (tesselateAreaEdges || tesselateWallEdges)) { for (int i = 0; i < simplified.Count;) { int ii = (i + 1) % simplified.Count; //get (x, z) coordinates along with region id int ax = simplified[i].X; int az = simplified[i].Z; int ai = (int)simplified[i].RegionId; int bx = simplified[ii].X; int bz = simplified[ii].Z; int bi = (int)simplified[ii].RegionId; //find maximum deviation from segment int maxi = -1; int ci = (int)(ai + 1) % numPoints; //tessellate only outer edges or edges between areas bool tess = false; //wall edges if (tesselateWallEdges && rawVerts[ci].RegionId.Id == 0) tess = true; //edges between areas if (tesselateAreaEdges && RegionId.HasFlags(rawVerts[ci].RegionId, RegionFlags.AreaBorder)) tess = true; if (tess) { int dx = bx - ax; int dz = bz - az; if (dx * dx + dz * dz > maxEdgeLen * maxEdgeLen) { //round based on lexilogical direction (smallest to largest cooridinates, first by x. //if x coordinates are equal, then compare z coordinates) int n = bi < ai ? (bi + numPoints - ai) : (bi - ai); if (n > 1) { if (bx > ax || (bx == ax && bz > az)) maxi = (int)(ai + n / 2) % numPoints; else maxi = (int)(ai + (n + 1) / 2) % numPoints; } } } //add new point if (maxi != -1) { simplified.Insert(i + 1, new ContourVertex(rawVerts[maxi], maxi)); } else { i++; } } } for (int i = 0; i < simplified.Count; i++) { ContourVertex sv = simplified[i]; //take edge vertex flag from current raw point and neighbor region from next raw point int ai = ((int)sv.RegionId + 1) % numPoints; RegionId bi = sv.RegionId; //save new region id sv.RegionId = RegionId.FromRawBits(((int)rawVerts[ai].RegionId & (RegionId.MaskId | (int)RegionFlags.AreaBorder)) | ((int)rawVerts[(int)bi].RegionId & (int)RegionFlags.VertexBorder)); simplified[i] = sv; } }
public static extern bool nmcsBuildSet(IntPtr context , [In] CompactHeightfield chf , float maxError , int maxEdgeLen , [In, Out] ContourSetEx cset , ContourBuildFlags flags);
public static void OnGUIAdvanced(NMGenConfig config , bool isInspector) { GUILayout.Label("Advanced Settings"); EditorGUIUtility.LookLikeControls(170); float xz = config.XZCellSize; float a = xz * xz; float effective; ///////////////////////////////////////////////////////////// GUILayout.Space(MarginSize); effective = Mathf.Ceil(config.MaxEdgeLength / xz) * xz; config.MaxEdgeLength = EditorGUILayout.FloatField( NMGenConfig.EdgeLenLabel + Effective(effective) , config.MaxEdgeLength); config.EdgeMaxDeviation = EditorGUILayout.FloatField( NMGenConfig.EdgeDevLabel , config.EdgeMaxDeviation); config.MaxVertsPerPoly = EditorGUILayout.IntSlider( NMGenConfig.MaxPolyVertLabel , config.MaxVertsPerPoly , 3 , NMGen.MaxAllowedVertsPerPoly); effective = Mathf.Ceil(config.MergeRegionArea / a) * a; config.MergeRegionArea = EditorGUILayout.FloatField( NMGenConfig.MergeSizeLabel + Effective(effective) , config.MergeRegionArea); GUILayout.Space(MarginSize * 2); NMGenBuildFlag flags = config.BuildFlags; HandleFlagGUI(ref flags , NMGenConfig.LedgeSpansLabel , NMGenBuildFlag.LedgeSpansNotWalkable , isInspector); HandleFlagGUI(ref flags , NMGenConfig.LowHeightLabel , NMGenBuildFlag.LowHeightSpansNotWalkable , isInspector); HandleFlagGUI(ref flags , NMGenConfig.LowObstacleLabel , NMGenBuildFlag.LowObstaclesWalkable , isInspector); ContourBuildFlags cflags = config.ContourOptions; HandleFlagGUI(ref cflags , NMGenConfig.TessWallsLabel , ContourBuildFlags.TessellateWallEdges , isInspector); HandleFlagGUI(ref cflags , NMGenConfig.TessAreasLabel , ContourBuildFlags.TessellateAreaEdges , isInspector); config.ContourOptions = cflags; if (isInspector) { config.UseMonotone = EditorGUILayout.Toggle(NMGenConfig.UseMonoLabel , config.UseMonotone); } else { config.UseMonotone = GUILayout.Toggle(config.UseMonotone , NMGenConfig.UseMonoLabel); } HandleFlagGUI(ref flags , NMGenConfig.FlagPolysLabel , NMGenBuildFlag.ApplyPolyFlags , isInspector); bool includeDetail; if (isInspector) { includeDetail = EditorGUILayout.Toggle("Include Detail Mesh" , (config.ResultOptions & NMGenAssetFlag.DetailMesh) != 0); } else { includeDetail = GUILayout.Toggle( (config.ResultOptions & NMGenAssetFlag.DetailMesh) != 0 , "Include Detail Mesh"); } if (includeDetail) { config.ResultOptions |= NMGenAssetFlag.DetailMesh; } else { config.ResultOptions &= ~NMGenAssetFlag.DetailMesh; } HandleFlagGUI(ref flags , NMGenConfig.BVTreeLabel , NMGenBuildFlag.BVTreeEnabled , isInspector); config.BuildFlags = flags; }