static List <ProBuilderMesh> CombineToNewMeshes(IEnumerable <ProBuilderMesh> meshes) { if (meshes == null) { throw new ArgumentNullException("meshes"); } if (!meshes.Any() || meshes.Count() < 2) { return(null); } var vertices = new List <Vertex>(); var faces = new List <Face>(); var autoUvFaces = new List <Face>(); var sharedVertices = new List <SharedVertex>(); var sharedTextures = new List <SharedVertex>(); int offset = 0; var materialMap = new List <Material>(); AccumulateMeshesInfo( meshes, offset, ref vertices, ref faces, ref autoUvFaces, ref sharedVertices, ref sharedTextures, ref materialMap ); var res = SplitByMaxVertexCount(vertices, faces, sharedVertices, sharedTextures); var pivot = meshes.LastOrDefault().transform.position; foreach (var m in res) { m.renderer.sharedMaterials = materialMap.ToArray(); InternalMeshUtility.FilterUnusedSubmeshIndexes(m); m.SetPivot(pivot); UVEditing.SetAutoAndAlignUnwrapParamsToUVs(m, autoUvFaces); } return(res); }
/// <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()); }
/// <summary> /// Merge a collection of <see cref="ProBuilderMesh"/> objects to as few meshes as possible. This may result in /// more than one mesh due to a max vertex count limit of 65535. /// </summary> /// <param name="meshes">A collection of meshes to be merged.</param> /// <returns> /// A list of merged meshes. In most cases this will be a single mesh. However it can be multiple in cases /// where the resulting vertex count exceeds the maximum allowable value. /// </returns> public static List <ProBuilderMesh> Combine(IEnumerable <ProBuilderMesh> meshes) { if (meshes == null) { throw new ArgumentNullException("meshes"); } if (!meshes.Any() || meshes.Count() < 2) { return(null); } var vertices = new List <Vertex>(); var faces = new List <Face>(); var autoUvFaces = new List <Face>(); var sharedVertices = new List <SharedVertex>(); var sharedTextures = new List <SharedVertex>(); int offset = 0; var materialMap = new List <Material>(); foreach (var mesh in meshes) { var meshVertexCount = mesh.vertexCount; var transform = mesh.transform; var meshVertices = mesh.GetVertices(); var meshFaces = mesh.facesInternal; var meshSharedVertices = mesh.sharedVertices; var meshSharedTextures = mesh.sharedTextures; var materials = mesh.renderer.sharedMaterials; var materialCount = materials.Length; for (int i = 0; i < meshVertexCount; i++) { vertices.Add(transform.TransformVertex(meshVertices[i])); } foreach (var face in meshFaces) { var newFace = new Face(face); newFace.ShiftIndexes(offset); // prevents uvs from shifting when being converted from local coords to world space if (!newFace.manualUV && !newFace.uv.useWorldSpace) { newFace.manualUV = true; autoUvFaces.Add(newFace); } var material = materials[Math.Clamp(face.submeshIndex, 0, materialCount - 1)]; var submeshIndex = materialMap.IndexOf(material); if (submeshIndex > -1) { newFace.submeshIndex = submeshIndex; } else { if (material == null) { newFace.submeshIndex = 0; } else { newFace.submeshIndex = materialMap.Count; materialMap.Add(material); } } faces.Add(newFace); } foreach (var sv in meshSharedVertices) { var nsv = new SharedVertex(sv); nsv.ShiftIndexes(offset); sharedVertices.Add(nsv); } foreach (var st in meshSharedTextures) { var nst = new SharedVertex(st); nst.ShiftIndexes(offset); sharedTextures.Add(nst); } offset += meshVertexCount; } var res = SplitByMaxVertexCount(vertices, faces, sharedVertices, sharedTextures); var pivot = meshes.LastOrDefault().transform.position; foreach (var m in res) { m.renderer.sharedMaterials = materialMap.ToArray(); InternalMeshUtility.FilterUnusedSubmeshIndexes(m); m.SetPivot(pivot); UVEditing.SetAutoAndAlignUnwrapParamsToUVs(m, autoUvFaces); } return(res); }