static ConnectFaceRebuildData InsertVertices(Face face, List <WingedEdge> edges, List <Vertex> vertices) { 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); 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; return(new ConnectFaceRebuildData(res, newVertexIndexes)); }
/// <summary> /// Insert a new vertex at the center of a face and connect the center of all edges to it. /// </summary> /// <param name="face"></param> /// <param name="edges"></param> /// <param name="vertices"></param> /// <returns></returns> static List <ConnectFaceRebuildData> ConnectEdgesInFace( Face face, List <WingedEdge> edges, List <Vertex> vertices) { List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face); int splitCount = edges.Count; Vertex centroid = Vertex.Average(vertices, face.distinctIndexesInternal); List <List <Vertex> > n_vertices = ArrayUtility.Fill <List <Vertex> >(x => { return(new List <Vertex>()); }, splitCount); List <List <int> > n_indexes = ArrayUtility.Fill <List <int> >(x => { return(new List <int>()); }, splitCount); HashSet <Edge> edgesToSplit = new HashSet <Edge>(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 <ConnectFaceRebuildData> faces = new List <ConnectFaceRebuildData>(); 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); }
/// <summary> /// Accepts a face and set of edges to split on. /// </summary> /// <param name="face"></param> /// <param name="a"></param> /// <param name="b"></param> /// <param name="vertices"></param> /// <returns></returns> static List <ConnectFaceRebuildData> ConnectEdgesInFace( Face face, WingedEdge a, WingedEdge b, List <Vertex> vertices) { List <Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face); List <Vertex>[] n_vertices = new List <Vertex>[2] { new List <Vertex>(), new List <Vertex>() }; List <int>[] n_indexes = new List <int>[2] { new List <int>(), new List <int>() }; 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 <ConnectFaceRebuildData> faces = new List <ConnectFaceRebuildData>(); 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); }
/// <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); }
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); }