/// <summary> /// Insert a face between two edges. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="a">First edge.</param> /// <param name="b">Second edge</param> /// <param name="allowNonManifoldGeometry">If true, this function will allow edges to be bridged that create overlapping (non-manifold) faces.</param> /// <returns>The new face, or null of the action failed.</returns> 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 <int, int> 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 <Face, Edge> 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 Vector4[v.Length], new Vector4[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 Vector4[v.Length], new Vector4[v.Length], new Face(new int[6] { 2, 1, 0, 2, 3, 1 }, submeshIndex, uvs, 0, -1, -1, false), s)); }
/// <summary> /// Extrude a collection of edges. /// </summary> /// <param name="mesh">The source mesh.</param> /// <param name="edges">The edges to extrude.</param> /// <param name="distance">The distance to extrude.</param> /// <param name="extrudeAsGroup">If true adjacent edges will be extruded retaining a shared vertex, if false the shared vertex will be split.</param> /// <param name="enableManifoldExtrude">Pass true to allow this function to extrude manifold edges, false to disallow.</param> /// <returns>The extruded edges, or null if the action failed due to manifold check or an empty edges parameter.</returns> public static Edge[] Extrude(this ProBuilderMesh mesh, IEnumerable <Edge> edges, float distance, bool extrudeAsGroup, bool enableManifoldExtrude) { if (mesh == null) { throw new ArgumentNullException("mesh"); } if (edges == null) { throw new ArgumentNullException("edges"); } SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal; List <Edge> validEdges = new List <Edge>(); List <Face> edgeFaces = new List <Face>(); foreach (Edge e in edges) { int faceCount = 0; Face fa = null; foreach (Face face in mesh.facesInternal) { if (mesh.IndexOf(face.edgesInternal, e) > -1) { fa = face; if (++faceCount > 1) { break; } } } if (enableManifoldExtrude || faceCount < 2) { validEdges.Add(e); edgeFaces.Add(fa); } } if (validEdges.Count < 1) { return(null); } Vector3[] localVerts = mesh.positionsInternal; if (!mesh.HasArrays(MeshArrays.Normal)) { mesh.Refresh(RefreshMask.Normals); } IList <Vector3> oNormals = mesh.normals; int[] allEdgeIndexes = new int[validEdges.Count * 2]; int c = 0; for (int i = 0; i < validEdges.Count; i++) { allEdgeIndexes[c++] = validEdges[i].a; allEdgeIndexes[c++] = validEdges[i].b; } List <Edge> extrudedIndexes = new List <Edge>(); // used to set the editor selection to the newly created edges List <Edge> newEdges = new List <Edge>(); bool hasColors = mesh.HasArrays(MeshArrays.Color); // build out new faces around validEdges for (int i = 0; i < validEdges.Count; i++) { Edge edge = validEdges[i]; Face face = edgeFaces[i]; // Averages the normals using only vertices that are on the edge Vector3 xnorm = extrudeAsGroup ? InternalMeshUtility.AverageNormalWithIndexes(sharedIndexes[mesh.GetSharedVertexHandle(edge.a)], allEdgeIndexes, oNormals) : Math.Normal(mesh, face); Vector3 ynorm = extrudeAsGroup ? InternalMeshUtility.AverageNormalWithIndexes(sharedIndexes[mesh.GetSharedVertexHandle(edge.b)], allEdgeIndexes, oNormals) : Math.Normal(mesh, face); int x_sharedIndex = mesh.GetSharedVertexHandle(edge.a); int y_sharedIndex = mesh.GetSharedVertexHandle(edge.b); var positions = new Vector3[4] { localVerts[edge.a], localVerts[edge.b], localVerts[edge.a] + xnorm.normalized * distance, localVerts[edge.b] + ynorm.normalized * distance }; var colors = hasColors ? new Color[4] { mesh.colorsInternal[edge.a], mesh.colorsInternal[edge.b], mesh.colorsInternal[edge.a], mesh.colorsInternal[edge.b] } : null; Face newFace = mesh.AppendFace( positions, colors, new Vector2[4], new Face(new int[6] { 2, 1, 0, 2, 3, 1 }, face.submeshIndex, AutoUnwrapSettings.tile, 0, -1, -1, false), new int[4] { x_sharedIndex, y_sharedIndex, -1, -1 }); newEdges.Add(new Edge(newFace.indexesInternal[3], newFace.indexesInternal[4])); extrudedIndexes.Add(new Edge(x_sharedIndex, newFace.indexesInternal[3])); extrudedIndexes.Add(new Edge(y_sharedIndex, newFace.indexesInternal[4])); } // merge extruded vertex indexes with each other if (extrudeAsGroup) { for (int i = 0; i < extrudedIndexes.Count; i++) { int val = extrudedIndexes[i].a; for (int n = 0; n < extrudedIndexes.Count; n++) { if (n == i) { continue; } if (extrudedIndexes[n].a == val) { mesh.SetVerticesCoincident(new int[] { extrudedIndexes[n].b, extrudedIndexes[i].b }); break; } } } } // todo Should only need to invalidate caches on affected faces foreach (Face f in mesh.facesInternal) { f.InvalidateCache(); } return(newEdges.ToArray()); }