using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine.ProBuilder; using System; namespace UnityEngine.ProBuilder.MeshOperations { /// /// Functions for appending elements to meshes. /// public static class AppendElements { /// /// Append a new face to the ProBuilderMesh. /// /// The mesh target. /// The new vertex positions to add. /// The new colors to add (must match positions length). /// The new uvs to add (must match positions length). /// A face with the new triangle indexes. The indexes should be 0 indexed. /// /// The new face as referenced on the mesh. internal static Face AppendFace(this ProBuilderMesh mesh, Vector3[] positions, Color[] colors, Vector2[] uvs, Face face, int[] common) { if (mesh == null) throw new ArgumentNullException("mesh"); if (positions == null) throw new ArgumentNullException("positions"); if (face == null) throw new ArgumentNullException("face"); int faceVertexCount = positions.Length; if (common == null) { common = new int[faceVertexCount]; for (int i = 0; i < faceVertexCount; i++) common[i] = -1; } int vertexCount = mesh.vertexCount; var mc = mesh.HasArrays(MeshArrays.Color); var fc = colors != null; var mt = mesh.HasArrays(MeshArrays.Texture0); var ft = uvs != null; Vector3[] newPositions = new Vector3[vertexCount + faceVertexCount]; Color[] newColors = (mc || fc) ? new Color[vertexCount + faceVertexCount] : null; Vector2[] newTextures = (mt || ft) ? new Vector2[vertexCount + faceVertexCount] : null; List faces = new List(mesh.facesInternal); Array.Copy(mesh.positionsInternal, 0, newPositions, 0, vertexCount); Array.Copy(positions, 0, newPositions, vertexCount, faceVertexCount); if (mc || fc) { Array.Copy(mc ? mesh.colorsInternal : ArrayUtility.Fill(Color.white, vertexCount), 0, newColors, 0, vertexCount); Array.Copy(fc ? colors : ArrayUtility.Fill(Color.white, faceVertexCount), 0, newColors, vertexCount, colors.Length); } if (mt || ft) { Array.Copy(mt ? mesh.texturesInternal : ArrayUtility.Fill(Vector2.zero, vertexCount), 0, newTextures, 0, vertexCount); Array.Copy(ft ? uvs : ArrayUtility.Fill(Vector2.zero, faceVertexCount), 0, newTextures, mesh.texturesInternal.Length, faceVertexCount); } face.ShiftIndexesToZero(); face.ShiftIndexes(vertexCount); faces.Add(face); for (int i = 0; i < common.Length; i++) { if (common[i] < 0) mesh.AddSharedVertex(new SharedVertex(new int[] { i + vertexCount })); else mesh.AddToSharedVertex(common[i], i + vertexCount); } mesh.positions = newPositions; mesh.colors = newColors; mesh.textures = newTextures; mesh.faces = faces; return face; } /// /// Append a group of new faces to the mesh. Significantly faster than calling AppendFace multiple times. /// /// The source mesh to append new faces to. /// An array of position arrays, where indexes correspond to the appendedFaces parameter. /// An array of colors arrays, where indexes correspond to the appendedFaces parameter. /// An array of uvs arrays, where indexes correspond to the appendedFaces parameter. /// An array of faces arrays, which contain the triangle winding information for each new face. Face index values are 0 indexed. /// An optional mapping of each new vertex's common index. Common index refers to a triangle's index in the @"UnityEngine.ProBuilder.ProBuilderMesh.sharedIndexes" array. If this value is provided, it must contain entries for each vertex position. Ex, if there are 4 vertices in this face, there must be shared index entries for { 0, 1, 2, 3 }. /// An array of the new faces that where successfully appended to the mesh. public static Face[] AppendFaces( this ProBuilderMesh mesh, Vector3[][] positions, Color[][] colors, Vector2[][] uvs, Face[] faces, int[][] shared) { if (mesh == null) throw new ArgumentNullException("mesh"); if (positions == null) throw new ArgumentNullException("positions"); if (colors == null) throw new ArgumentNullException("colors"); if (uvs == null) throw new ArgumentNullException("uvs"); if (faces == null) throw new ArgumentNullException("faces"); var newPositions = new List(mesh.positionsInternal); var newColors = new List(mesh.colorsInternal); var newTextures = new List(mesh.texturesInternal); var newFaces = new List(mesh.facesInternal); var lookup = mesh.sharedVertexLookup; int vc = mesh.vertexCount; for (int i = 0; i < faces.Length; i++) { newPositions.AddRange(positions[i]); newColors.AddRange(colors[i]); newTextures.AddRange(uvs[i]); faces[i].ShiftIndexesToZero(); faces[i].ShiftIndexes(vc); newFaces.Add(faces[i]); if (shared != null && positions[i].Length != shared[i].Length) { Debug.LogError("Append Face failed because shared array does not match new vertex array."); return null; } var hasCommon = shared != null; for (int j = 0; j < shared[i].Length; j++) lookup.Add(j + vc, hasCommon ? shared[i][j] : -1); vc = newPositions.Count; } mesh.positions = newPositions; mesh.colors = newColors; mesh.textures = newTextures; mesh.faces = newFaces; mesh.SetSharedVertices(lookup); return faces; } /// /// Create a new face connecting existing vertices. /// /// The source mesh. /// The indexes of the vertices to join with the new polygon. /// Are the indexes in an ordered path (false), or not (true)? If indexes are not ordered this function will treat the polygon as a convex shape. Ordered paths will be triangulated allowing concave shapes. /// The new face created if the action was successfull, null if action failed. public static Face CreatePolygon(this ProBuilderMesh mesh, IList indexes, bool unordered) { if (mesh == null) throw new ArgumentNullException("mesh"); SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal; Dictionary lookup = mesh.sharedVertexLookup; HashSet common = mesh.GetSharedVertexHandles(indexes); List vertices = new List(mesh.GetVertices()); List appendVertices = new List(); foreach (int i in common) { int index = sharedIndexes[i][0]; appendVertices.Add(new Vertex(vertices[index])); } FaceRebuildData data = FaceWithVertices(appendVertices, unordered); if (data != null) { data.sharedIndexes = common.ToList(); List faces = new List(mesh.facesInternal); FaceRebuildData.Apply(new FaceRebuildData[] { data }, vertices, faces, lookup, null); mesh.SetVertices(vertices); mesh.faces = faces; mesh.SetSharedVertices(lookup); return data.face; } const string insufficientPoints = "Too Few Unique Points Selected"; const string badWinding = "Points not ordered correctly"; Log.Info(unordered ? insufficientPoints : badWinding); return null; } /// /// Create a poly shape from a set of points on a plane. The points must be ordered. /// /// /// An action result indicating the status of the operation. internal static ActionResult CreateShapeFromPolygon(this PolyShape poly) { return poly.mesh.CreateShapeFromPolygon(poly.m_Points, poly.extrude, poly.flipNormals); } /// /// Rebuild a mesh from an ordered set of points. /// /// The target mesh. The mesh values will be cleared and repopulated with the shape extruded from points. /// A path of points to triangulate and extrude. /// The distance to extrude. /// If true the faces will be inverted at creation. /// An ActionResult with the status of the operation. public static ActionResult CreateShapeFromPolygon(this ProBuilderMesh mesh, IList points, float extrude, bool flipNormals) { if (mesh == null) throw new ArgumentNullException("mesh"); if (points == null || points.Count < 3) { mesh.Clear(); mesh.ToMesh(); mesh.Refresh(); return new ActionResult(ActionResult.Status.NoChange, "Too Few Points"); } Vector3[] vertices = points.ToArray(); List triangles; Log.PushLogLevel(LogLevel.Error); if (Triangulation.TriangulateVertices(vertices, out triangles, false)) { int[] indexes = triangles.ToArray(); if (Math.PolygonArea(vertices, indexes) < Mathf.Epsilon) { mesh.Clear(); Log.PopLogLevel(); return new ActionResult(ActionResult.Status.Failure, "Polygon Area < Epsilon"); } mesh.Clear(); mesh.positionsInternal = vertices; mesh.facesInternal = new[] { new Face(indexes) }; mesh.sharedVerticesInternal = SharedVertex.GetSharedVerticesWithPositions(vertices); mesh.InvalidateCaches(); Vector3 nrm = Math.Normal(mesh, mesh.facesInternal[0]); if (Vector3.Dot(Vector3.up, nrm) > 0f) mesh.facesInternal[0].Reverse(); mesh.DuplicateAndFlip(mesh.facesInternal); mesh.Extrude(new Face[] { mesh.facesInternal[1] }, ExtrudeMethod.IndividualFaces, extrude); if ((extrude < 0f && !flipNormals) || (extrude > 0f && flipNormals)) { foreach (var face in mesh.facesInternal) face.Reverse(); } mesh.ToMesh(); mesh.Refresh(); } else { Log.PopLogLevel(); return new ActionResult(ActionResult.Status.Failure, "Failed Triangulating Points"); } Log.PopLogLevel(); return new ActionResult(ActionResult.Status.Success, "Create Polygon Shape"); } /// /// Create a new face given a set of unordered vertices (or ordered, if unordered param is set to false). /// /// /// /// internal static FaceRebuildData FaceWithVertices(List vertices, bool unordered = true) { List triangles; if (Triangulation.TriangulateVertices(vertices, out triangles, unordered)) { FaceRebuildData data = new FaceRebuildData(); data.vertices = vertices; data.face = new Face(triangles.ToArray()); return data; } return null; } /// /// Given a path of vertices, inserts a new vertex in the center inserts triangles along the path. /// /// /// internal static List TentCapWithVertices(List path) { int count = path.Count; Vertex center = Vertex.Average(path); List faces = new List(); for (int i = 0; i < count; i++) { List vertices = new List() { path[i], center, path[(i + 1) % count] }; FaceRebuildData data = new FaceRebuildData(); data.vertices = vertices; data.face = new Face(new int[] {0 , 1, 2}); faces.Add(data); } return faces; } /// /// Duplicate and reverse the winding direction for each face. /// /// The target mesh. /// The faces to duplicate, reverse triangle winding order, and append to mesh. public static void DuplicateAndFlip(this ProBuilderMesh mesh, Face[] faces) { if (mesh == null) throw new ArgumentNullException("mesh"); if (faces == null) throw new ArgumentNullException("faces"); List rebuild = new List(); List vertices = new List(mesh.GetVertices()); Dictionary lookup = mesh.sharedVertexLookup; foreach (Face face in faces) { FaceRebuildData data = new FaceRebuildData(); data.vertices = new List(); data.face = new Face(face); data.sharedIndexes = new List(); Dictionary map = new Dictionary(); int len = data.face.indexesInternal.Length; for (int i = 0; i < len; i++) { if (map.ContainsKey(face.indexesInternal[i])) continue; map.Add(face.indexesInternal[i], map.Count); data.vertices.Add(vertices[face.indexesInternal[i]]); data.sharedIndexes.Add(lookup[face.indexesInternal[i]]); } int[] tris = new int[len]; for (var i = 0; i < len; i++) tris[len - (i + 1)] = map[data.face[i]]; data.face.SetIndexes(tris); rebuild.Add(data); } FaceRebuildData.Apply(rebuild, mesh, vertices); } /// /// Insert a face between two edges. /// /// The source mesh. /// First edge. /// Second edge /// If true, this function will allow edges to be bridged that create overlapping (non-manifold) faces. /// The new face, or null of the action failed. public static Face Bridge(this ProBuilderMesh mesh, Edge a, Edge b, bool allowNonManifoldGeometry = false) { if (mesh == null) throw new ArgumentNullException("mesh"); SharedVertex[] sharedVertices = mesh.sharedVerticesInternal; Dictionary lookup = mesh.sharedVertexLookup; // Check to see if a face already exists if (!allowNonManifoldGeometry) { if (ElementSelection.GetNeighborFaces(mesh, a).Count > 1 || ElementSelection.GetNeighborFaces(mesh, b).Count > 1) { return null; } } foreach (Face face in mesh.facesInternal) { if (mesh.IndexOf(face.edgesInternal, a) >= 0 && mesh.IndexOf(face.edgesInternal, b) >= 0) { Log.Warning("Face already exists between these two edges!"); return null; } } Vector3[] positions = mesh.positionsInternal; bool hasColors = mesh.HasArrays(MeshArrays.Color); Color[] colors = hasColors ? mesh.colorsInternal : null; Vector3[] v; Color[] c; int[] s; AutoUnwrapSettings uvs = AutoUnwrapSettings.tile; int submeshIndex = 0; // Get material and UV stuff from the first edge face SimpleTuple faceAndEdge; if (EdgeUtility.ValidateEdge(mesh, a, out faceAndEdge) || EdgeUtility.ValidateEdge(mesh, b, out faceAndEdge)) { uvs = new AutoUnwrapSettings(faceAndEdge.item1.uv); submeshIndex = faceAndEdge.item1.submeshIndex; } // Bridge will form a triangle if (a.Contains(b.a, lookup) || a.Contains(b.b, lookup)) { v = new Vector3[3]; c = new Color[3]; s = new int[3]; bool axbx = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.a)].arrayInternal, b.a) > -1; bool axby = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.a)].arrayInternal, b.b) > -1; bool aybx = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.b)].arrayInternal, b.a) > -1; bool ayby = Array.IndexOf(sharedVertices[mesh.GetSharedVertexHandle(a.b)].arrayInternal, b.b) > -1; if (axbx) { v[0] = positions[a.a]; if (hasColors) c[0] = colors[a.a]; s[0] = mesh.GetSharedVertexHandle(a.a); v[1] = positions[a.b]; if (hasColors) c[1] = colors[a.b]; s[1] = mesh.GetSharedVertexHandle(a.b); v[2] = positions[b.b]; if (hasColors) c[2] = colors[b.b]; s[2] = mesh.GetSharedVertexHandle(b.b); } else if (axby) { v[0] = positions[a.a]; if (hasColors) c[0] = colors[a.a]; s[0] = mesh.GetSharedVertexHandle(a.a); v[1] = positions[a.b]; if (hasColors) c[1] = colors[a.b]; s[1] = mesh.GetSharedVertexHandle(a.b); v[2] = positions[b.a]; if (hasColors) c[2] = colors[b.a]; s[2] = mesh.GetSharedVertexHandle(b.a); } else if (aybx) { v[0] = positions[a.b]; if (hasColors) c[0] = colors[a.b]; s[0] = mesh.GetSharedVertexHandle(a.b); v[1] = positions[a.a]; if (hasColors) c[1] = colors[a.a]; s[1] = mesh.GetSharedVertexHandle(a.a); v[2] = positions[b.b]; if (hasColors) c[2] = colors[b.b]; s[2] = mesh.GetSharedVertexHandle(b.b); } else if (ayby) { v[0] = positions[a.b]; if (hasColors) c[0] = colors[a.b]; s[0] = mesh.GetSharedVertexHandle(a.b); v[1] = positions[a.a]; if (hasColors) c[1] = colors[a.a]; s[1] = mesh.GetSharedVertexHandle(a.a); v[2] = positions[b.a]; if (hasColors) c[2] = colors[b.a]; s[2] = mesh.GetSharedVertexHandle(b.a); } return mesh.AppendFace( v, hasColors ? c : null, new Vector2[v.Length], new Face(axbx || axby ? new int[3] {2, 1, 0} : new int[3] {0, 1, 2}, submeshIndex, uvs, 0, -1, -1, false), s); } // Else, bridge will form a quad v = new Vector3[4]; c = new Color[4]; s = new int[4]; // shared indexes index to add to v[0] = positions[a.a]; if (hasColors) c[0] = mesh.colorsInternal[a.a]; s[0] = mesh.GetSharedVertexHandle(a.a); v[1] = positions[a.b]; if (hasColors) c[1] = mesh.colorsInternal[a.b]; s[1] = mesh.GetSharedVertexHandle(a.b); Vector3 nrm = Vector3.Cross(positions[b.a] - positions[a.a], positions[a.b] - positions[a.a]).normalized; Vector2[] planed = Projection.PlanarProject(new Vector3[4] { positions[a.a], positions[a.b], positions[b.a], positions[b.b] }, null, nrm); Vector2 ipoint = Vector2.zero; bool intersects = Math.GetLineSegmentIntersect(planed[0], planed[2], planed[1], planed[3], ref ipoint); if (!intersects) { v[2] = positions[b.a]; if (hasColors) c[2] = mesh.colorsInternal[b.a]; s[2] = mesh.GetSharedVertexHandle(b.a); v[3] = positions[b.b]; if (hasColors) c[3] = mesh.colorsInternal[b.b]; s[3] = mesh.GetSharedVertexHandle(b.b); } else { v[2] = positions[b.b]; if (hasColors) c[2] = mesh.colorsInternal[b.b]; s[2] = mesh.GetSharedVertexHandle(b.b); v[3] = positions[b.a]; if (hasColors) c[3] = mesh.colorsInternal[b.a]; s[3] = mesh.GetSharedVertexHandle(b.a); } return mesh.AppendFace( v, hasColors ? c : null, new Vector2[v.Length], new Face(new int[6] {2, 1, 0, 2, 3, 1 }, submeshIndex, uvs, 0, -1, -1, false), s); } /// /// Add a set of points to a face and retriangulate. Points are added to the nearest edge. /// /// The source mesh. /// The face to append points to. /// Points to added to the face. /// The face created by appending the points. 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 vertices = mesh.GetVertices().ToList(); List faces = new List(mesh.facesInternal); Dictionary lookup = mesh.sharedVertexLookup; Dictionary lookupUV = null; if (mesh.sharedTextures != null) { lookupUV = new Dictionary(); SharedVertex.GetSharedVertexLookup(mesh.sharedTextures, lookupUV); } List wound = WingedEdge.SortEdgesByAdjacency(face); List n_vertices = new List(); List n_shared = new List(); List n_sharedUV = lookupUV != null ? new List() : 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 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() { 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; } /// /// Insert a number of new points to an edge. Points are evenly spaced out along the edge. /// /// The source mesh. /// The edge to split with points. /// The number of new points to insert. Must be greater than 0. /// The new edges created by inserting points. public static List AppendVerticesToEdge(this ProBuilderMesh mesh, Edge edge, int count) { return AppendVerticesToEdge(mesh, new Edge[] { edge }, count); } /// /// Insert a number of new points to each edge. Points are evenly spaced out along the edge. /// /// The source mesh. /// The edges to split with points. /// The number of new points to insert. Must be greater than 0. /// The new edges created by inserting points. public static List AppendVerticesToEdge(this ProBuilderMesh mesh, IList 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 vertices = new List(mesh.GetVertices()); Dictionary lookup = mesh.sharedVertexLookup; Dictionary lookupUV = mesh.sharedTextureLookup; List indexesToDelete = new List(); IEnumerable commonEdges = EdgeUtility.GetSharedVertexHandleEdges(mesh, edges); List distinctEdges = commonEdges.Distinct().ToList(); Dictionary modifiedFaces = new Dictionary(); 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 verticesToAppend = new List(count); for (int i = 0; i < count; i++) verticesToAppend.Add(Vertex.Mix(vertices[localEdge.a], vertices[localEdge.b], (i + 1) / ((float)count + 1))); List> adjacentFaces = ElementSelection.GetNeighborFaces(mesh, localEdge); // foreach face attached to common edge, append vertices foreach (SimpleTuple 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(ArrayUtility.ValuesWithIndexes(vertices, face.distinctIndexesInternal)); data.sharedIndexes = new List(); data.sharedIndexesUV = new List(); 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 dic_face = modifiedFaces.Keys.ToList(); List dic_data = modifiedFaces.Values.ToList(); List appendedEdges = new List(); 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 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; } } }