/// <summary> /// Given a path of vertices, inserts a new vertex in the center inserts triangles along the path. /// </summary> /// <param name="path"></param> /// <returns></returns> internal static List <FaceRebuildData> TentCapWithVertices(List <Vertex> path) { int count = path.Count; Vertex center = Vertex.Average(path); List <FaceRebuildData> faces = new List <FaceRebuildData>(); for (int i = 0; i < count; i++) { List <Vertex> vertices = new List <Vertex>() { 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); }
static bool InsertVertices(Face face, List <WingedEdge> edges, List <Vertex> vertices, out ConnectFaceRebuildData data) { List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face); List <Vertex> n_vertices = new List <Vertex>(); List <int> newVertexIndexes = new List <int>(); HashSet <Edge> affected = new HashSet <Edge>(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); }
/// <summary> /// Apply a bevel to a set of edges. /// </summary> /// <param name="mesh">Target mesh.</param> /// <param name="edges">A set of edges to apply bevelling to.</param> /// <param name="amount">A value from 0 (bevel not at all) to 1 (bevel entire face).</param> /// <returns>The new faces created to form the bevel.</returns> public static List <Face> BevelEdges(ProBuilderMesh mesh, IList <Edge> edges, float amount) { if (mesh == null) { throw new ArgumentNullException("mesh"); } Dictionary <int, int> lookup = mesh.sharedVertexLookup; List <Vertex> vertices = new List <Vertex>(mesh.GetVertices()); List <EdgeLookup> m_edges = EdgeLookup.GetEdgeLookup(edges, lookup).Distinct().ToList(); List <WingedEdge> wings = WingedEdge.GetWingedEdges(mesh); List <FaceRebuildData> appendFaces = new List <FaceRebuildData>(); Dictionary <Face, List <int> > ignore = new Dictionary <Face, List <int> >(); HashSet <int> slide = new HashSet <int>(); int beveled = 0; Dictionary <int, List <SimpleTuple <FaceRebuildData, List <int> > > > holes = new Dictionary <int, List <SimpleTuple <FaceRebuildData, List <int> > > >(); // test every edge that will be moved along to make sure the bevel distance is appropriate. if it's not, adjust the max bevel amount // to suit. Dictionary <int, List <WingedEdge> > spokes = WingedEdge.GetSpokes(wings); HashSet <int> tested_common = new HashSet <int>(); foreach (EdgeLookup e in m_edges) { if (tested_common.Add(e.common.a)) { foreach (WingedEdge w in spokes[e.common.a]) { Edge le = w.edge.local; amount = Mathf.Min(Vector3.Distance(vertices[le.a].position, vertices[le.b].position) - .001f, amount); } } if (tested_common.Add(e.common.b)) { foreach (WingedEdge w in spokes[e.common.b]) { Edge le = w.edge.local; amount = Mathf.Min(Vector3.Distance(vertices[le.a].position, vertices[le.b].position) - .001f, amount); } } } if (amount < .001f) { Log.Info("Bevel Distance > Available Surface"); return(null); } // iterate selected edges and move each leading edge back along it's direction // storing information about adjacent faces in the process foreach (EdgeLookup lup in m_edges) { WingedEdge we = wings.FirstOrDefault(x => x.edge.Equals(lup)); if (we == null || we.opposite == null) { continue; } beveled++; ignore.AddOrAppend(we.face, we.edge.common.a); ignore.AddOrAppend(we.face, we.edge.common.b); ignore.AddOrAppend(we.opposite.face, we.edge.common.a); ignore.AddOrAppend(we.opposite.face, we.edge.common.b); // after initial slides go back and split indirect triangles at the intersecting index into two vertices slide.Add(we.edge.common.a); slide.Add(we.edge.common.b); SlideEdge(vertices, we, amount); SlideEdge(vertices, we.opposite, amount); appendFaces.AddRange(GetBridgeFaces(vertices, we, we.opposite, holes)); } if (beveled < 1) { Log.Info("Cannot Bevel Open Edges"); return(null); } // grab the "createdFaces" array now so that the selection returned is just the bridged faces // then add holes later var createdFaces = new List <Face>(appendFaces.Select(x => x.face)); Dictionary <Face, List <SimpleTuple <WingedEdge, int> > > sorted = new Dictionary <Face, List <SimpleTuple <WingedEdge, int> > >(); // sort the adjacent but affected faces into winged edge groups where each group contains a set of // unique winged edges pointing to the same face foreach (int c in slide) { IEnumerable <WingedEdge> matches = wings.Where(x => x.edge.common.Contains(c) && !(ignore.ContainsKey(x.face) && ignore[x.face].Contains(c))); HashSet <Face> used = new HashSet <Face>(); foreach (WingedEdge match in matches) { if (!used.Add(match.face)) { continue; } sorted.AddOrAppend(match.face, new SimpleTuple <WingedEdge, int>(match, c)); } } // now go through those sorted faces and apply the vertex exploding, keeping track of any holes created foreach (KeyValuePair <Face, List <SimpleTuple <WingedEdge, int> > > kvp in sorted) { // common index & list of vertices it was split into Dictionary <int, List <int> > appended; FaceRebuildData f = VertexEditing.ExplodeVertex(vertices, kvp.Value, amount, out appended); if (f == null) { continue; } appendFaces.Add(f); foreach (var apv in appended) { // organize holes by new face so that later we can compare the winding of the new face to the hole face // holes are sorted by key: common index value: face, vertex list holes.AddOrAppend(apv.Key, new SimpleTuple <FaceRebuildData, List <int> >(f, apv.Value)); } } FaceRebuildData.Apply(appendFaces, mesh, vertices); int removed = mesh.DeleteFaces(sorted.Keys).Length; mesh.sharedTextures = new SharedVertex[0]; mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal); // @todo don't rebuild indexes, keep 'em cached SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal; lookup = mesh.sharedVertexLookup; List <HashSet <int> > holesCommonIndexes = new List <HashSet <int> >(); // offset the indexes of holes and cull any potential holes that are less than 3 indexes (not a hole :) foreach (KeyValuePair <int, List <SimpleTuple <FaceRebuildData, List <int> > > > hole in holes) { // less than 3 indexes in hole path; ain't a hole if (hole.Value.Sum(x => x.item2.Count) < 3) { continue; } HashSet <int> holeCommon = new HashSet <int>(); foreach (SimpleTuple <FaceRebuildData, List <int> > path in hole.Value) { int offset = path.item1.Offset() - removed; for (int i = 0; i < path.item2.Count; i++) { holeCommon.Add(lookup[path.item2[i] + offset]); } } holesCommonIndexes.Add(holeCommon); } List <WingedEdge> modified = WingedEdge.GetWingedEdges(mesh, appendFaces.Select(x => x.face)); // now go through the holes and create faces for them vertices = new List <Vertex>(mesh.GetVertices()); List <FaceRebuildData> holeFaces = new List <FaceRebuildData>(); foreach (HashSet <int> h in holesCommonIndexes) { // even if a set of hole indexes made it past the initial culling, the distinct part // may have reduced the index count if (h.Count < 3) { continue; } // skip sorting the path if it's just a triangle if (h.Count < 4) { List <Vertex> v = new List <Vertex>(mesh.GetVertices(h.Select(x => sharedIndexes[x][0]).ToList())); holeFaces.Add(AppendElements.FaceWithVertices(v)); } // if this hole has > 3 indexes, it needs a tent pole triangulation, which requires sorting into the perimeter order else { List <int> holePath = WingedEdge.SortCommonIndexesByAdjacency(modified, h); List <Vertex> v = new List <Vertex>(mesh.GetVertices(holePath.Select(x => sharedIndexes[x][0]).ToList())); holeFaces.AddRange(AppendElements.TentCapWithVertices(v)); } } FaceRebuildData.Apply(holeFaces, mesh, vertices); mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal); // go through new faces and conform hole normals // get a hash of just the adjacent and bridge faces // HashSet<pb_Face> adjacent = new HashSet<pb_Face>(appendFaces.Select(x => x.face)); // and also just the filled holes HashSet <Face> newHoles = new HashSet <Face>(holeFaces.Select(x => x.face)); // now append filled holes to the full list of added faces appendFaces.AddRange(holeFaces); List <WingedEdge> allNewFaceEdges = WingedEdge.GetWingedEdges(mesh, appendFaces.Select(x => x.face)); for (int i = 0; i < allNewFaceEdges.Count && newHoles.Count > 0; i++) { WingedEdge wing = allNewFaceEdges[i]; if (newHoles.Contains(wing.face)) { newHoles.Remove(wing.face); // find first edge whose opposite face isn't a filled hole* then // conform normal by that. // *or is a filled hole but has already been conformed using (var it = new WingedEdgeEnumerator(wing)) { while (it.MoveNext()) { var w = it.Current; if (!newHoles.Contains(w.opposite.face)) { w.face.submeshIndex = w.opposite.face.submeshIndex; w.face.uv = new AutoUnwrapSettings(w.opposite.face.uv); SurfaceTopology.ConformOppositeNormal(w.opposite); break; } } } } } mesh.ToMesh(); return(createdFaces); }
/// <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> /// 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> /// Inserts edges connecting a list of indices. /// /// This is the equivalent of the [Connect Edges](../manual/Edge_Connect.html) action. /// </summary> /// <param name="mesh">The target mesh.</param> /// <param name="indexes">A list of indices (corresponding to the <see cref="ProBuilderMesh.positions"/> array) to connect to the new edges.</param> /// <returns>A new array containing the indices of the newly connected positions. This method rebuilds the `indexes` array because it might modify the ordering of the original array.</returns> public static int[] Connect(this ProBuilderMesh mesh, IList <int> indexes) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (indexes == null) { throw new ArgumentNullException("indexes"); } int sharedIndexOffset = mesh.sharedVerticesInternal.Length; Dictionary <int, int> lookup = mesh.sharedVertexLookup; HashSet <int> distinct = new HashSet <int>(indexes.Select(x => lookup[x])); HashSet <int> affected = new HashSet <int>(); foreach (int i in distinct) { affected.UnionWith(mesh.sharedVerticesInternal[i].arrayInternal); } Dictionary <Face, List <int> > splits = new Dictionary <Face, List <int> >(); List <Vertex> vertices = new List <Vertex>(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 <ConnectFaceRebuildData> appendFaces = new List <ConnectFaceRebuildData>(); List <Face> successfulSplits = new List <Face>(); HashSet <int> usedTextureGroups = new HashSet <int>(mesh.facesInternal.Select(x => x.textureGroup)); int newTextureGroupIndex = 1; foreach (KeyValuePair <Face, List <int> > split in splits) { Face face = split.Key; List <ConnectFaceRebuildData> 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 <int> newVertexIndexes = new HashSet <int>(); 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()); }
static List <ConnectFaceRebuildData> ConnectIndexesPerFace( Face face, List <int> indexes, List <Vertex> vertices, Dictionary <int, int> lookup, int sharedIndexOffset) { if (indexes.Count < 3) { return(null); } List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face); int splitCount = indexes.Count; List <List <Vertex> > n_vertices = ArrayUtility.Fill <List <Vertex> >(x => { return(new List <Vertex>()); }, splitCount); List <List <int> > n_sharedIndexes = ArrayUtility.Fill <List <int> >(x => { return(new List <int>()); }, splitCount); List <List <int> > n_indexes = ArrayUtility.Fill <List <int> >(x => { return(new List <int>()); }, 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 <ConnectFaceRebuildData> faces = new List <ConnectFaceRebuildData>(); 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); }
static List <ConnectFaceRebuildData> ConnectIndexesPerFace( Face face, int a, int b, List <Vertex> vertices, Dictionary <int, int> lookup) { List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face); List <Vertex>[] n_vertices = new List <Vertex>[] { new List <Vertex>(), new List <Vertex>() }; List <int>[] n_sharedIndexes = new List <int>[] { new List <int>(), new List <int>() }; List <int>[] n_indexes = new List <int>[] { new List <int>(), new List <int>() }; 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 <ConnectFaceRebuildData> faces = new List <ConnectFaceRebuildData>(); 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); }
/// <summary> /// Inserts new edges connecting the passed edges, optionally restricting new edge insertion to faces in faceMask. /// </summary> /// <param name="mesh"></param> /// <param name="edges"></param> /// <param name="addedFaces"></param> /// <param name="connections"></param> /// <param name="returnFaces"></param> /// <param name="returnEdges"></param> /// <param name="faceMask"></param> /// <returns></returns> internal static ActionResult Connect( this ProBuilderMesh mesh, IEnumerable <Edge> edges, out Face[] addedFaces, out Edge[] connections, bool returnFaces = false, bool returnEdges = false, HashSet <Face> faceMask = null) { Dictionary <int, int> lookup = mesh.sharedVertexLookup; Dictionary <int, int> lookupUV = mesh.sharedTextureLookup; HashSet <EdgeLookup> distinctEdges = new HashSet <EdgeLookup>(EdgeLookup.GetEdgeLookup(edges, lookup)); List <WingedEdge> 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 <Face, List <WingedEdge> > touched = new Dictionary <Face, List <WingedEdge> >(); foreach (WingedEdge wing in wings) { if (distinctEdges.Contains(wing.edge)) { List <WingedEdge> faceEdges; if (touched.TryGetValue(wing.face, out faceEdges)) { faceEdges.Add(wing); } else { touched.Add(wing.face, new List <WingedEdge>() { wing }); } } } Dictionary <Face, List <WingedEdge> > affected = new Dictionary <Face, List <WingedEdge> >(); // weed out edges that won't actually connect to other edges (if you don't play ya' can't stay) foreach (KeyValuePair <Face, List <WingedEdge> > kvp in touched) { if (kvp.Value.Count <= 1) { WingedEdge opp = kvp.Value[0].opposite; if (opp == null) { continue; } List <WingedEdge> opp_list; if (!touched.TryGetValue(opp.face, out opp_list)) { continue; } if (opp_list.Count <= 1) { continue; } } affected.Add(kvp.Key, kvp.Value); } List <Vertex> vertices = new List <Vertex>(mesh.GetVertices()); List <ConnectFaceRebuildData> results = new List <ConnectFaceRebuildData>(); // just the faces that where connected with > 1 edge List <Face> connectedFaces = new List <Face>(); HashSet <int> usedTextureGroups = new HashSet <int>(mesh.facesInternal.Select(x => x.textureGroup)); int newTextureGroupIndex = 1; // do the splits foreach (KeyValuePair <Face, List <WingedEdge> > split in affected) { Face face = split.Key; List <WingedEdge> 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 <ConnectFaceRebuildData> 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 <int>(); 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 <int, int> lup = mesh.sharedVertexLookup; IEnumerable <Edge> newEdges = results.SelectMany(x => x.faceRebuildData.face.edgesInternal).Where(x => appended.Contains(x.a) && appended.Contains(x.b)); IEnumerable <EdgeLookup> 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))); }
public ConnectFaceRebuildData(FaceRebuildData faceRebuildData, List <int> newVertexIndexes) { this.faceRebuildData = faceRebuildData; this.newVertexIndexes = newVertexIndexes; }
/// <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); }
/// <summary> /// Split a common index on a face into two vertices and slide each vertex backwards along it's feeding edge by distance. /// This method does not perform any input validation, so make sure edgeAndCommonIndex is distinct and all winged edges belong /// to the same face. ///<pre> /// `appendedVertices` is common index and a list of the new face indexes it was split into. /// /// _ _ _ _ _ _ _ /// | / /// | -> | /// | | /// </pre> /// </summary> /// <param name="vertices"></param> /// <param name="edgeAndCommonIndex"></param> /// <param name="distance"></param> /// <param name="appendedVertices"></param> /// <returns></returns> internal static FaceRebuildData ExplodeVertex( IList <Vertex> vertices, IList <SimpleTuple <WingedEdge, int> > edgeAndCommonIndex, float distance, out Dictionary <int, List <int> > appendedVertices) { Face face = edgeAndCommonIndex.FirstOrDefault().item1.face; List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face); appendedVertices = new Dictionary <int, List <int> >(); Vector3 oldNormal = Math.Normal(vertices, face.indexesInternal); // store local and common index of split points Dictionary <int, int> toSplit = new Dictionary <int, int>(); foreach (SimpleTuple <WingedEdge, int> v in edgeAndCommonIndex) { if (v.item2 == v.item1.edge.common.a) { toSplit.Add(v.item1.edge.local.a, v.item2); } else { toSplit.Add(v.item1.edge.local.b, v.item2); } } int pc = perimeter.Count; List <Vertex> n_vertices = new List <Vertex>(); for (int i = 0; i < pc; i++) { int index = perimeter[i].b; // split this index into two if (toSplit.ContainsKey(index)) { // a --- b --- c Vertex a = vertices[perimeter[i].a]; Vertex b = vertices[perimeter[i].b]; Vertex c = vertices[perimeter[(i + 1) % pc].b]; Vertex leading_dir = a - b; Vertex following_dir = c - b; leading_dir.Normalize(); following_dir.Normalize(); Vertex leading_insert = vertices[index] + leading_dir * distance; Vertex following_insert = vertices[index] + following_dir * distance; appendedVertices.AddOrAppend(toSplit[index], n_vertices.Count); n_vertices.Add(leading_insert); appendedVertices.AddOrAppend(toSplit[index], n_vertices.Count); n_vertices.Add(following_insert); } else { n_vertices.Add(vertices[index]); } } List <int> triangles; if (Triangulation.TriangulateVertices(n_vertices, out triangles, false)) { FaceRebuildData data = new FaceRebuildData(); data.vertices = n_vertices; data.face = new Face(face); Vector3 newNormal = Math.Normal(n_vertices, triangles); if (Vector3.Dot(oldNormal, newNormal) < 0f) { triangles.Reverse(); } data.face.indexesInternal = triangles.ToArray(); return(data); } return(null); }