using UnityEngine; using System.Collections.Generic; using System.Linq; using System; namespace UnityEngine.ProBuilder.MeshOperations { /// /// Store face rebuild data with indexes to mark which vertices are new. /// sealed class ConnectFaceRebuildData { public FaceRebuildData faceRebuildData; public List newVertexIndexes; public ConnectFaceRebuildData(FaceRebuildData faceRebuildData, List newVertexIndexes) { this.faceRebuildData = faceRebuildData; this.newVertexIndexes = newVertexIndexes; } } /// /// Utility class for connecting edges, faces, and vertices. /// public static class ConnectElements { /// /// Insert new edges from the center of each edge on a face to a new vertex in the center of the face. /// /// Target mesh. /// The faces to poke. /// The faces created as a result of inserting new edges. public static Face[] Connect(this ProBuilderMesh mesh, IEnumerable faces) { var split = MeshValidation.EnsureFacesAreComposedOfContiguousTriangles(mesh, faces); HashSet mask = new HashSet(faces); if (split.Count > 0) { foreach (var face in split) mask.Add(face); } IEnumerable edges = mask.SelectMany(x => x.edgesInternal); Edge[] empty; Face[] res; Connect(mesh, edges, out res, out empty, true, false, mask); return res; } /// /// Insert new edges connecting a set of edges. If a face contains more than 2 edges to be connected, a new vertex is inserted at the center of the face and each edge is connected to the center point. /// /// The target mesh. /// A list of edges to connect. /// The faces and edges created as a result of inserting new edges. public static SimpleTuple Connect(this ProBuilderMesh mesh, IEnumerable edges) { Edge[] empty; Face[] faces; Connect(mesh, edges, out faces, out empty, true, true); return new SimpleTuple(faces, empty); } /// /// Inserts edges connecting a list of indexes. /// /// The target mesh. /// A list of indexes (corresponding to the @"UnityEngine.ProBuilder.ProBuilderMesh.positions" array) to connect with new edges. /// Because this function may modify the ordering of the positions array, a new array containing the equivalent values of the passed connected indexes is returned. public static int[] Connect(this ProBuilderMesh mesh, IList indexes) { if (mesh == null) throw new ArgumentNullException("mesh"); if (indexes == null) throw new ArgumentNullException("indexes"); int sharedIndexOffset = mesh.sharedVerticesInternal.Length; Dictionary lookup = mesh.sharedVertexLookup; HashSet distinct = new HashSet(indexes.Select(x => lookup[x])); HashSet affected = new HashSet(); foreach (int i in distinct) affected.UnionWith(mesh.sharedVerticesInternal[i].arrayInternal); Dictionary> splits = new Dictionary>(); List vertices = new List(mesh.GetVertices()); foreach (Face face in mesh.facesInternal) { int[] f = face.distinctIndexesInternal; for (int i = 0; i < f.Length; i++) { if (affected.Contains(f[i])) splits.AddOrAppend(face, f[i]); } } List appendFaces = new List(); List successfulSplits = new List(); HashSet usedTextureGroups = new HashSet(mesh.facesInternal.Select(x => x.textureGroup)); int newTextureGroupIndex = 1; foreach (KeyValuePair> split in splits) { Face face = split.Key; List res = split.Value.Count == 2 ? ConnectIndexesPerFace(face, split.Value[0], split.Value[1], vertices, lookup) : ConnectIndexesPerFace(face, split.Value, vertices, lookup, sharedIndexOffset++); if (res == null) continue; if (face.textureGroup < 0) { while (usedTextureGroups.Contains(newTextureGroupIndex)) newTextureGroupIndex++; usedTextureGroups.Add(newTextureGroupIndex); } foreach (ConnectFaceRebuildData c in res) { c.faceRebuildData.face.textureGroup = face.textureGroup < 0 ? newTextureGroupIndex : face.textureGroup; c.faceRebuildData.face.uv = new AutoUnwrapSettings(face.uv); c.faceRebuildData.face.smoothingGroup = face.smoothingGroup; c.faceRebuildData.face.manualUV = face.manualUV; c.faceRebuildData.face.submeshIndex = face.submeshIndex; } successfulSplits.Add(face); appendFaces.AddRange(res); } FaceRebuildData.Apply(appendFaces.Select(x => x.faceRebuildData), mesh, vertices, null); int removedVertexCount = mesh.DeleteFaces(successfulSplits).Length; lookup = mesh.sharedVertexLookup; HashSet newVertexIndexes = new HashSet(); for (int i = 0; i < appendFaces.Count; i++) for (int n = 0; n < appendFaces[i].newVertexIndexes.Count; n++) newVertexIndexes.Add(lookup[appendFaces[i].newVertexIndexes[n] + (appendFaces[i].faceRebuildData.Offset() - removedVertexCount)]); mesh.ToMesh(); return newVertexIndexes.Select(x => mesh.sharedVerticesInternal[x][0]).ToArray(); } /// /// Inserts new edges connecting the passed edges, optionally restricting new edge insertion to faces in faceMask. /// /// /// /// /// /// /// /// /// internal static ActionResult Connect( this ProBuilderMesh mesh, IEnumerable edges, out Face[] addedFaces, out Edge[] connections, bool returnFaces = false, bool returnEdges = false, HashSet faceMask = null) { Dictionary lookup = mesh.sharedVertexLookup; Dictionary lookupUV = mesh.sharedTextureLookup; HashSet distinctEdges = new HashSet(EdgeLookup.GetEdgeLookup(edges, lookup)); List wings = WingedEdge.GetWingedEdges(mesh); // map each edge to a face so that we have a list of all touched faces with their to-be-subdivided edges Dictionary> touched = new Dictionary>(); foreach (WingedEdge wing in wings) { if (distinctEdges.Contains(wing.edge)) { List faceEdges; if (touched.TryGetValue(wing.face, out faceEdges)) faceEdges.Add(wing); else touched.Add(wing.face, new List() { wing }); } } Dictionary> affected = new Dictionary>(); // weed out edges that won't actually connect to other edges (if you don't play ya' can't stay) foreach (KeyValuePair> kvp in touched) { if (kvp.Value.Count <= 1) { WingedEdge opp = kvp.Value[0].opposite; if (opp == null) continue; List opp_list; if (!touched.TryGetValue(opp.face, out opp_list)) continue; if (opp_list.Count <= 1) continue; } affected.Add(kvp.Key, kvp.Value); } List vertices = new List(mesh.GetVertices()); List results = new List(); // just the faces that where connected with > 1 edge List connectedFaces = new List(); HashSet usedTextureGroups = new HashSet(mesh.facesInternal.Select(x => x.textureGroup)); int newTextureGroupIndex = 1; // do the splits foreach (KeyValuePair> split in affected) { Face face = split.Key; List targetEdges = split.Value; int inserts = targetEdges.Count; Vector3 nrm = Math.Normal(vertices, face.indexesInternal); if (inserts == 1 || (faceMask != null && !faceMask.Contains(face))) { ConnectFaceRebuildData c; if (InsertVertices(face, targetEdges, vertices, out c)) { Vector3 fn = Math.Normal(c.faceRebuildData.vertices, c.faceRebuildData.face.indexesInternal); if (Vector3.Dot(nrm, fn) < 0) c.faceRebuildData.face.Reverse(); results.Add(c); } } else if (inserts > 1) { List res = inserts == 2 ? ConnectEdgesInFace(face, targetEdges[0], targetEdges[1], vertices) : ConnectEdgesInFace(face, targetEdges, vertices); if (face.textureGroup < 0) { while (usedTextureGroups.Contains(newTextureGroupIndex)) newTextureGroupIndex++; usedTextureGroups.Add(newTextureGroupIndex); } if (res == null) { connections = null; addedFaces = null; return new ActionResult(ActionResult.Status.Failure, "Unable to connect faces"); } else { foreach (ConnectFaceRebuildData c in res) { connectedFaces.Add(c.faceRebuildData.face); Vector3 fn = Math.Normal(c.faceRebuildData.vertices, c.faceRebuildData.face.indexesInternal); if (Vector3.Dot(nrm, fn) < 0) c.faceRebuildData.face.Reverse(); c.faceRebuildData.face.textureGroup = face.textureGroup < 0 ? newTextureGroupIndex : face.textureGroup; c.faceRebuildData.face.uv = new AutoUnwrapSettings(face.uv); c.faceRebuildData.face.submeshIndex = face.submeshIndex; c.faceRebuildData.face.smoothingGroup = face.smoothingGroup; c.faceRebuildData.face.manualUV = face.manualUV; } results.AddRange(res); } } } FaceRebuildData.Apply(results.Select(x => x.faceRebuildData), mesh, vertices, null); mesh.sharedTextures = new SharedVertex[0]; int removedVertexCount = mesh.DeleteFaces(affected.Keys).Length; mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal); mesh.ToMesh(); // figure out where the new edges where inserted if (returnEdges) { // offset the newVertexIndexes by whatever the FaceRebuildData did so we can search for the new edges by index var appended = new HashSet(); for (int n = 0; n < results.Count; n++) for (int i = 0; i < results[n].newVertexIndexes.Count; i++) appended.Add((results[n].newVertexIndexes[i] + results[n].faceRebuildData.Offset()) - removedVertexCount); Dictionary lup = mesh.sharedVertexLookup; IEnumerable newEdges = results.SelectMany(x => x.faceRebuildData.face.edgesInternal).Where(x => appended.Contains(x.a) && appended.Contains(x.b)); IEnumerable distNewEdges = EdgeLookup.GetEdgeLookup(newEdges, lup); connections = distNewEdges.Distinct().Select(x => x.local).ToArray(); } else { connections = null; } if (returnFaces) addedFaces = connectedFaces.ToArray(); else addedFaces = null; return new ActionResult(ActionResult.Status.Success, string.Format("Connected {0} Edges", results.Count / 2)); } /// /// Accepts a face and set of edges to split on. /// /// /// /// /// /// static List ConnectEdgesInFace( Face face, WingedEdge a, WingedEdge b, List vertices) { List perimeter = WingedEdge.SortEdgesByAdjacency(face); List[] n_vertices = new List[2] { new List(), new List() }; List[] n_indexes = new List[2] { new List(), new List() }; int index = 0; // creates two new polygon perimeter lines by stepping the current face perimeter and inserting new vertices where edges match for (int i = 0; i < perimeter.Count; i++) { n_vertices[index % 2].Add(vertices[perimeter[i].a]); if (perimeter[i].Equals(a.edge.local) || perimeter[i].Equals(b.edge.local)) { Vertex mix = Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f); n_indexes[index % 2].Add(n_vertices[index % 2].Count); n_vertices[index % 2].Add(mix); index++; n_indexes[index % 2].Add(n_vertices[index % 2].Count); n_vertices[index % 2].Add(mix); } } List faces = new List(); for (int i = 0; i < n_vertices.Length; i++) { FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false); faces.Add(new ConnectFaceRebuildData(f, n_indexes[i])); } return faces; } /// /// Insert a new vertex at the center of a face and connect the center of all edges to it. /// /// /// /// /// static List ConnectEdgesInFace( Face face, List edges, List vertices) { List perimeter = WingedEdge.SortEdgesByAdjacency(face); int splitCount = edges.Count; Vertex centroid = Vertex.Average(vertices, face.distinctIndexesInternal); List> n_vertices = ArrayUtility.Fill>(x => { return new List(); }, splitCount); List> n_indexes = ArrayUtility.Fill>(x => { return new List(); }, splitCount); HashSet edgesToSplit = new HashSet(edges.Select(x => x.edge.local)); int index = 0; // creates two new polygon perimeter lines by stepping the current face perimeter and inserting new vertices where edges match for (int i = 0; i < perimeter.Count; i++) { n_vertices[index % splitCount].Add(vertices[perimeter[i].a]); if (edgesToSplit.Contains(perimeter[i])) { Vertex mix = Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f); // split current poly line n_indexes[index].Add(n_vertices[index].Count); n_vertices[index].Add(mix); // add the centroid vertex n_indexes[index].Add(n_vertices[index].Count); n_vertices[index].Add(centroid); // advance the poly line index index = (index + 1) % splitCount; // then add the edge center vertex and move on n_vertices[index].Add(mix); } } List faces = new List(); for (int i = 0; i < n_vertices.Count; i++) { FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false); if (f == null) { faces.Clear(); return null; } faces.Add(new ConnectFaceRebuildData(f, n_indexes[i])); } return faces; } static bool InsertVertices(Face face, List edges, List vertices, out ConnectFaceRebuildData data) { List perimeter = WingedEdge.SortEdgesByAdjacency(face); List n_vertices = new List(); List newVertexIndexes = new List(); HashSet affected = new HashSet(edges.Select(x => x.edge.local)); for (int i = 0; i < perimeter.Count; i++) { n_vertices.Add(vertices[perimeter[i].a]); if (affected.Contains(perimeter[i])) { newVertexIndexes.Add(n_vertices.Count); n_vertices.Add(Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f)); } } FaceRebuildData res = AppendElements.FaceWithVertices(n_vertices, false); if (res != null) { res.face.textureGroup = face.textureGroup; res.face.uv = new AutoUnwrapSettings(face.uv); res.face.smoothingGroup = face.smoothingGroup; res.face.manualUV = face.manualUV; res.face.submeshIndex = face.submeshIndex; data = new ConnectFaceRebuildData(res, newVertexIndexes); return true; } data = null; return false; } static List ConnectIndexesPerFace( Face face, int a, int b, List vertices, Dictionary lookup) { List perimeter = WingedEdge.SortEdgesByAdjacency(face); List[] n_vertices = new List[] { new List(), new List() }; List[] n_sharedIndexes = new List[] { new List(), new List() }; List[] n_indexes = new List[] { new List(), new List() }; int index = 0; for (int i = 0; i < perimeter.Count; i++) { // trying to connect two vertices that are already connected if (perimeter[i].Contains(a) && perimeter[i].Contains(b)) return null; int cur = perimeter[i].a; n_vertices[index].Add(vertices[cur]); n_sharedIndexes[index].Add(lookup[cur]); if (cur == a || cur == b) { index = (index + 1) % 2; n_indexes[index].Add(n_vertices[index].Count); n_vertices[index].Add(vertices[cur]); n_sharedIndexes[index].Add(lookup[cur]); } } List faces = new List(); Vector3 nrm = Math.Normal(vertices, face.indexesInternal); for (int i = 0; i < n_vertices.Length; i++) { FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false); f.sharedIndexes = n_sharedIndexes[i]; Vector3 fn = Math.Normal(n_vertices[i], f.face.indexesInternal); if (Vector3.Dot(nrm, fn) < 0) f.face.Reverse(); faces.Add(new ConnectFaceRebuildData(f, n_indexes[i])); } return faces; } static List ConnectIndexesPerFace( Face face, List indexes, List vertices, Dictionary lookup, int sharedIndexOffset) { if (indexes.Count < 3) return null; List perimeter = WingedEdge.SortEdgesByAdjacency(face); int splitCount = indexes.Count; List> n_vertices = ArrayUtility.Fill>(x => { return new List(); }, splitCount); List> n_sharedIndexes = ArrayUtility.Fill>(x => { return new List(); }, splitCount); List> n_indexes = ArrayUtility.Fill>(x => { return new List(); }, splitCount); Vertex center = Vertex.Average(vertices, indexes); Vector3 nrm = Math.Normal(vertices, face.indexesInternal); int index = 0; for (int i = 0; i < perimeter.Count; i++) { int cur = perimeter[i].a; n_vertices[index].Add(vertices[cur]); n_sharedIndexes[index].Add(lookup[cur]); if (indexes.Contains(cur)) { n_indexes[index].Add(n_vertices[index].Count); n_vertices[index].Add(center); n_sharedIndexes[index].Add(sharedIndexOffset); index = (index + 1) % splitCount; n_indexes[index].Add(n_vertices[index].Count); n_vertices[index].Add(vertices[cur]); n_sharedIndexes[index].Add(lookup[cur]); } } List faces = new List(); for (int i = 0; i < n_vertices.Count; i++) { if (n_vertices[i].Count < 3) continue; FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false); f.sharedIndexes = n_sharedIndexes[i]; Vector3 fn = Math.Normal(n_vertices[i], f.face.indexesInternal); if (Vector3.Dot(nrm, fn) < 0) f.face.Reverse(); faces.Add(new ConnectFaceRebuildData(f, n_indexes[i])); } return faces; } } }