/// <summary> /// Insert a number of new points to each edge. Points are evenly spaced out along the edge. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="edges">The edges to split with points.</param> /// <param name="count">The number of new points to insert. Must be greater than 0.</param> /// <returns>The new edges created by inserting points.</returns> public static List <Edge> AppendVerticesToEdge(this ProBuilderMesh mesh, IList <Edge> edges, int count) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (edges == null) { throw new ArgumentNullException("edges"); } if (count < 1 || count > 512) { Log.Error("New edge vertex count is less than 1 or greater than 512."); return(null); } List <Vertex> vertices = new List <Vertex>(mesh.GetVertices()); Dictionary <int, int> lookup = mesh.sharedVertexLookup; Dictionary <int, int> lookupUV = mesh.sharedTextureLookup; List <int> indexesToDelete = new List <int>(); IEnumerable <Edge> commonEdges = EdgeUtility.GetSharedVertexHandleEdges(mesh, edges); List <Edge> distinctEdges = commonEdges.Distinct().ToList(); Dictionary <Face, FaceRebuildData> modifiedFaces = new Dictionary <Face, FaceRebuildData>(); int originalSharedIndexesCount = lookup.Count(); int sharedIndexesCount = originalSharedIndexesCount; foreach (Edge edge in distinctEdges) { Edge localEdge = EdgeUtility.GetEdgeWithSharedVertexHandles(mesh, edge); // Generate the new vertices that will be inserted on this edge List <Vertex> verticesToAppend = new List <Vertex>(count); for (int i = 0; i < count; i++) { verticesToAppend.Add(Vertex.Mix(vertices[localEdge.a], vertices[localEdge.b], (i + 1) / ((float)count + 1))); } List <SimpleTuple <Face, Edge> > adjacentFaces = ElementSelection.GetNeighborFaces(mesh, localEdge); Edge edgeLookUp = new Edge(lookup[localEdge.a], lookup[localEdge.b]); Edge e = new Edge(); // foreach face attached to common edge, append vertices foreach (SimpleTuple <Face, Edge> tup in adjacentFaces) { Face face = tup.item1; FaceRebuildData data; if (!modifiedFaces.TryGetValue(face, out data)) { data = new FaceRebuildData(); data.face = new Face(new int[0], face.submeshIndex, new AutoUnwrapSettings(face.uv), face.smoothingGroup, face.textureGroup, -1, face.manualUV); data.vertices = new List <Vertex>(ArrayUtility.ValuesWithIndexes(vertices, face.distinctIndexesInternal)); data.sharedIndexes = new List <int>(); data.sharedIndexesUV = new List <int>(); foreach (int i in face.distinctIndexesInternal) { int shared; if (lookup.TryGetValue(i, out shared)) { data.sharedIndexes.Add(shared); } if (lookupUV.TryGetValue(i, out shared)) { data.sharedIndexesUV.Add(shared); } } indexesToDelete.AddRange(face.distinctIndexesInternal); modifiedFaces.Add(face, data); //Ordering vertices in the new face List <Vertex> orderedVertices = new List <Vertex>(); List <int> orderedSharedIndexes = new List <int>(); List <int> orderedSharedUVIndexes = new List <int>(); List <Edge> peripheralEdges = WingedEdge.SortEdgesByAdjacency(face); for (int i = 0; i < peripheralEdges.Count; i++) { e.a = peripheralEdges[i].a; e.b = peripheralEdges[i].b; orderedVertices.Add(vertices[e.a]); int shared; if (lookup.TryGetValue(e.a, out shared)) { orderedSharedIndexes.Add(shared); } if (lookupUV.TryGetValue(i, out shared)) { data.sharedIndexesUV.Add(shared); } if (edgeLookUp.a == lookup[e.a] && edgeLookUp.b == lookup[e.b]) { for (int j = 0; j < count; j++) { orderedVertices.Add(verticesToAppend[j]); orderedSharedIndexes.Add(sharedIndexesCount + j); orderedSharedUVIndexes.Add(-1); } } else if (edgeLookUp.a == lookup[e.b] && edgeLookUp.b == lookup[e.a]) { for (int j = count - 1; j >= 0; j--) { orderedVertices.Add(verticesToAppend[j]); orderedSharedIndexes.Add(sharedIndexesCount + j); orderedSharedUVIndexes.Add(-1); } } } data.vertices = orderedVertices; data.sharedIndexes = orderedSharedIndexes; data.sharedIndexesUV = orderedSharedUVIndexes; } else { //Get ordered vertices in the existing face and add new ones List <Vertex> orderedVertices = data.vertices; List <int> orderedSharedIndexes = data.sharedIndexes; List <int> orderedSharedUVIndexes = data.sharedIndexesUV; for (int i = 0; i < orderedVertices.Count; i++) { Vertex edgeStart = orderedVertices[i]; int edgeStartIndex = vertices.IndexOf(edgeStart); Vertex edgeEnd = orderedVertices[(i + 1) % orderedVertices.Count]; int edgeEndIndex = vertices.IndexOf(edgeEnd); if (edgeStartIndex == -1 || edgeEndIndex == -1) { continue; } if (lookup[edgeStartIndex] == lookup[localEdge.a] && lookup[edgeEndIndex] == lookup[localEdge.b]) { orderedVertices.InsertRange(i + 1, verticesToAppend); for (int j = 0; j < count; j++) { orderedSharedIndexes.Insert(i + j + 1, sharedIndexesCount + j); orderedSharedUVIndexes.Add(-1); } } else if (lookup[edgeStartIndex] == lookup[localEdge.b] && lookup[edgeEndIndex] == lookup[localEdge.a]) { verticesToAppend.Reverse(); orderedVertices.InsertRange(i + 1, verticesToAppend); for (int j = count - 1; j >= 0; j--) { orderedSharedIndexes.Insert(i + 1, sharedIndexesCount + j); orderedSharedUVIndexes.Add(-1); } } } data.vertices = orderedVertices; data.sharedIndexes = orderedSharedIndexes; data.sharedIndexesUV = orderedSharedUVIndexes; } } sharedIndexesCount += count; } // now apply the changes List <Face> dic_face = modifiedFaces.Keys.ToList(); List <FaceRebuildData> dic_data = modifiedFaces.Values.ToList(); List <EdgeLookup> appendedEdges = new List <EdgeLookup>(); for (int i = 0; i < dic_face.Count; i++) { Face face = dic_face[i]; FaceRebuildData data = dic_data[i]; int vertexCount = vertices.Count; // triangulate and set new face indexes to end of current vertex list List <int> triangles; if (Triangulation.TriangulateVertices(data.vertices, out triangles, false)) { data.face = new Face(triangles); } else { continue; } data.face.ShiftIndexes(vertexCount); face.CopyFrom(data.face); for (int n = 0; n < data.vertices.Count; n++) { lookup.Add(vertexCount + n, data.sharedIndexes[n]); } if (data.sharedIndexesUV.Count == data.vertices.Count) { for (int n = 0; n < data.vertices.Count; n++) { lookupUV.Add(vertexCount + n, data.sharedIndexesUV[n]); } } vertices.AddRange(data.vertices); foreach (Edge e in face.edgesInternal) { EdgeLookup el = new EdgeLookup(new Edge(lookup[e.a], lookup[e.b]), e); if (el.common.a >= originalSharedIndexesCount || el.common.b >= originalSharedIndexesCount) { appendedEdges.Add(el); } } } indexesToDelete = indexesToDelete.Distinct().ToList(); int delCount = indexesToDelete.Count; var newEdges = appendedEdges.Distinct().Select(x => x.local - delCount).ToList(); mesh.SetVertices(vertices); mesh.SetSharedVertices(lookup); mesh.SetSharedTextures(lookupUV); mesh.DeleteVertices(indexesToDelete); return(newEdges); }
/// <summary> /// Rebuild a mesh from an ordered set of points. /// </summary> /// <param name="mesh">The target mesh. The mesh values will be cleared and repopulated with the shape extruded from points.</param> /// <param name="points">A path of points to triangulate and extrude.</param> /// <param name="extrude">The distance to extrude.</param> /// <param name="flipNormals">If true the faces will be inverted at creation.</param> /// <param name="holePoints">Holes in the polygon. If null this will be ignored.</param> /// <returns>An ActionResult with the status of the operation.</returns> public static ActionResult CreateShapeFromPolygon(this ProBuilderMesh mesh, IList <Vector3> points, float extrude, bool flipNormals, IList <IList <Vector3> > holePoints) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (points == null || points.Count < 3) { ClearAndRefreshMesh(mesh); return(new ActionResult(ActionResult.Status.NoChange, "Too Few Points")); } Vector3[] vertices = points.ToArray(); Vector3[][] holeVertices = null; if (holePoints != null && holePoints.Count > 0) { holeVertices = new Vector3[holePoints.Count][]; for (int i = 0; i < holePoints.Count; i++) { if (holePoints[i] == null || holePoints[i].Count < 3) { ClearAndRefreshMesh(mesh); return(new ActionResult(ActionResult.Status.NoChange, "Too Few Points in hole " + i)); } holeVertices[i] = holePoints[i].ToArray(); } } List <int> triangles; Log.PushLogLevel(LogLevel.Error); if (Triangulation.TriangulateVertices(vertices, out triangles, holeVertices)) { Vector3[] combinedVertices = null; if (holeVertices != null) { combinedVertices = new Vector3[vertices.Length + holeVertices.Sum(arr => arr.Length)]; Array.Copy(vertices, combinedVertices, vertices.Length); int destinationIndex = vertices.Length; foreach (var hole in holeVertices) { Array.ConstrainedCopy(hole, 0, combinedVertices, destinationIndex, hole.Length); destinationIndex += hole.Length; } } else { combinedVertices = vertices; } int[] indexes = triangles.ToArray(); if (Math.PolygonArea(combinedVertices, indexes) < Mathf.Epsilon) { ClearAndRefreshMesh(mesh); Log.PopLogLevel(); return(new ActionResult(ActionResult.Status.Failure, "Polygon Area < Epsilon")); } mesh.Clear(); mesh.positionsInternal = combinedVertices; var newFace = new Face(indexes); mesh.facesInternal = new[] { newFace }; mesh.sharedVerticesInternal = SharedVertex.GetSharedVerticesWithPositions(combinedVertices); mesh.InvalidateCaches(); // check that all points are represented in the triangulation if (newFace.distinctIndexesInternal.Length != combinedVertices.Length) { ClearAndRefreshMesh(mesh); Log.PopLogLevel(); return(new ActionResult(ActionResult.Status.Failure, "Triangulation missing points")); } Vector3 nrm = Math.Normal(mesh, mesh.facesInternal[0]); nrm = mesh.gameObject.transform.TransformDirection(nrm); if ((flipNormals ? Vector3.Dot(mesh.gameObject.transform.up, nrm) > 0f : Vector3.Dot(mesh.gameObject.transform.up, nrm) < 0f)) { mesh.facesInternal[0].Reverse(); } if (extrude != 0.0f) { mesh.DuplicateAndFlip(mesh.facesInternal); mesh.Extrude(new Face[] { (flipNormals ? mesh.facesInternal[1] : mesh.facesInternal[0]) }, ExtrudeMethod.IndividualFaces, extrude); if ((extrude < 0f && !flipNormals) || (extrude > 0f && flipNormals)) { foreach (var face in mesh.facesInternal) { face.Reverse(); } } } mesh.ToMesh(); mesh.Refresh(); } else { // clear mesh instead of showing an invalid one ClearAndRefreshMesh(mesh); Log.PopLogLevel(); return(new ActionResult(ActionResult.Status.Failure, "Failed Triangulating Points")); } Log.PopLogLevel(); return(new ActionResult(ActionResult.Status.Success, "Create Polygon Shape")); }
/// <summary> /// Add a set of points to a face and retriangulate. Points are added to the nearest edge. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="face">The face to append points to.</param> /// <param name="points">Points to added to the face.</param> /// <returns>The face created by appending the points.</returns> public static Face AppendVerticesToFace(this ProBuilderMesh mesh, Face face, Vector3[] points) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (face == null) { throw new ArgumentNullException("face"); } if (points == null) { throw new ArgumentNullException("points"); } List <Vertex> vertices = mesh.GetVertices().ToList(); List <Face> faces = new List <Face>(mesh.facesInternal); Dictionary <int, int> lookup = mesh.sharedVertexLookup; Dictionary <int, int> lookupUV = null; if (mesh.sharedTextures != null) { lookupUV = new Dictionary <int, int>(); SharedVertex.GetSharedVertexLookup(mesh.sharedTextures, lookupUV); } List <Edge> wound = WingedEdge.SortEdgesByAdjacency(face); List <Vertex> n_vertices = new List <Vertex>(); List <int> n_shared = new List <int>(); List <int> n_sharedUV = lookupUV != null ? new List <int>() : null; for (int i = 0; i < wound.Count; i++) { n_vertices.Add(vertices[wound[i].a]); n_shared.Add(lookup[wound[i].a]); if (lookupUV != null) { int uv; if (lookupUV.TryGetValue(wound[i].a, out uv)) { n_sharedUV.Add(uv); } else { n_sharedUV.Add(-1); } } } // now insert the new points on the nearest edge for (int i = 0; i < points.Length; i++) { int index = -1; float best = Mathf.Infinity; Vector3 p = points[i]; int vc = n_vertices.Count; for (int n = 0; n < vc; n++) { Vector3 v = n_vertices[n].position; Vector3 w = n_vertices[(n + 1) % vc].position; float dist = Math.DistancePointLineSegment(p, v, w); if (dist < best) { best = dist; index = n; } } Vertex left = n_vertices[index], right = n_vertices[(index + 1) % vc]; float x = (p - left.position).sqrMagnitude; float y = (p - right.position).sqrMagnitude; Vertex insert = Vertex.Mix(left, right, x / (x + y)); n_vertices.Insert((index + 1) % vc, insert); n_shared.Insert((index + 1) % vc, -1); if (n_sharedUV != null) { n_sharedUV.Insert((index + 1) % vc, -1); } } List <int> triangles; try { Triangulation.TriangulateVertices(n_vertices, out triangles, false); } catch { Debug.Log("Failed triangulating face after appending vertices."); return(null); } FaceRebuildData data = new FaceRebuildData(); data.face = new Face(triangles.ToArray(), face.submeshIndex, new AutoUnwrapSettings(face.uv), face.smoothingGroup, face.textureGroup, -1, face.manualUV); data.vertices = n_vertices; data.sharedIndexes = n_shared; data.sharedIndexesUV = n_sharedUV; FaceRebuildData.Apply(new List <FaceRebuildData>() { data }, vertices, faces, lookup, lookupUV); var newFace = data.face; mesh.SetVertices(vertices); mesh.faces = faces; mesh.SetSharedVertices(lookup); mesh.SetSharedTextures(lookupUV); // check old normal and make sure this new face is pointing the same direction Vector3 oldNrm = Math.Normal(mesh, face); Vector3 newNrm = Math.Normal(mesh, newFace); if (Vector3.Dot(oldNrm, newNrm) < 0) { newFace.Reverse(); } mesh.DeleteFace(face); return(newFace); }
/// <summary> /// Insert a number of new points to each edge. Points are evenly spaced out along the edge. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="edges">The edges to split with points.</param> /// <param name="count">The number of new points to insert. Must be greater than 0.</param> /// <returns>The new edges created by inserting points.</returns> public static List <Edge> AppendVerticesToEdge(this ProBuilderMesh mesh, IList <Edge> edges, int count) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (edges == null) { throw new ArgumentNullException("edges"); } if (count < 1 || count > 512) { Log.Error("New edge vertex count is less than 1 or greater than 512."); return(null); } List <Vertex> vertices = new List <Vertex>(mesh.GetVertices()); Dictionary <int, int> lookup = mesh.sharedVertexLookup; Dictionary <int, int> lookupUV = mesh.sharedTextureLookup; List <int> indexesToDelete = new List <int>(); IEnumerable <Edge> commonEdges = EdgeUtility.GetSharedVertexHandleEdges(mesh, edges); List <Edge> distinctEdges = commonEdges.Distinct().ToList(); Dictionary <Face, FaceRebuildData> modifiedFaces = new Dictionary <Face, FaceRebuildData>(); int originalSharedIndexesCount = lookup.Count(); int sharedIndexesCount = originalSharedIndexesCount; foreach (Edge edge in distinctEdges) { Edge localEdge = EdgeUtility.GetEdgeWithSharedVertexHandles(mesh, edge); // Generate the new vertices that will be inserted on this edge List <Vertex> verticesToAppend = new List <Vertex>(count); for (int i = 0; i < count; i++) { verticesToAppend.Add(Vertex.Mix(vertices[localEdge.a], vertices[localEdge.b], (i + 1) / ((float)count + 1))); } List <SimpleTuple <Face, Edge> > adjacentFaces = ElementSelection.GetNeighborFaces(mesh, localEdge); // foreach face attached to common edge, append vertices foreach (SimpleTuple <Face, Edge> tup in adjacentFaces) { Face face = tup.item1; FaceRebuildData data; if (!modifiedFaces.TryGetValue(face, out data)) { data = new FaceRebuildData(); data.face = new Face(new int[0], face.submeshIndex, new AutoUnwrapSettings(face.uv), face.smoothingGroup, face.textureGroup, -1, face.manualUV); data.vertices = new List <Vertex>(ArrayUtility.ValuesWithIndexes(vertices, face.distinctIndexesInternal)); data.sharedIndexes = new List <int>(); data.sharedIndexesUV = new List <int>(); foreach (int i in face.distinctIndexesInternal) { int shared; if (lookup.TryGetValue(i, out shared)) { data.sharedIndexes.Add(shared); } if (lookupUV.TryGetValue(i, out shared)) { data.sharedIndexesUV.Add(shared); } } indexesToDelete.AddRange(face.distinctIndexesInternal); modifiedFaces.Add(face, data); } data.vertices.AddRange(verticesToAppend); for (int i = 0; i < count; i++) { data.sharedIndexes.Add(sharedIndexesCount + i); data.sharedIndexesUV.Add(-1); } } sharedIndexesCount += count; } // now apply the changes List <Face> dic_face = modifiedFaces.Keys.ToList(); List <FaceRebuildData> dic_data = modifiedFaces.Values.ToList(); List <EdgeLookup> appendedEdges = new List <EdgeLookup>(); for (int i = 0; i < dic_face.Count; i++) { Face face = dic_face[i]; FaceRebuildData data = dic_data[i]; Vector3 nrm = Math.Normal(mesh, face); Vector2[] projection = Projection.PlanarProject(data.vertices.Select(x => x.position).ToArray(), null, nrm); int vertexCount = vertices.Count; // triangulate and set new face indexes to end of current vertex list List <int> indexes; if (Triangulation.SortAndTriangulate(projection, out indexes)) { data.face.indexesInternal = indexes.ToArray(); } else { continue; } data.face.ShiftIndexes(vertexCount); face.CopyFrom(data.face); for (int n = 0; n < data.vertices.Count; n++) { lookup.Add(vertexCount + n, data.sharedIndexes[n]); } if (data.sharedIndexesUV.Count == data.vertices.Count) { for (int n = 0; n < data.vertices.Count; n++) { lookupUV.Add(vertexCount + n, data.sharedIndexesUV[n]); } } vertices.AddRange(data.vertices); foreach (Edge e in face.edgesInternal) { EdgeLookup el = new EdgeLookup(new Edge(lookup[e.a], lookup[e.b]), e); if (el.common.a >= originalSharedIndexesCount || el.common.b >= originalSharedIndexesCount) { appendedEdges.Add(el); } } } indexesToDelete = indexesToDelete.Distinct().ToList(); int delCount = indexesToDelete.Count; var newEdges = appendedEdges.Distinct().Select(x => x.local - delCount).ToList(); mesh.SetVertices(vertices); mesh.SetSharedVertices(lookup); mesh.SetSharedTextures(lookupUV); mesh.DeleteVertices(indexesToDelete); return(newEdges); }