using System; using System.Collections.Generic; using System.Linq; namespace UnityEngine.ProBuilder.MeshOperations { /// /// Methods for merging multiple objects to a single mesh. /// public static class CombineMeshes { /// /// Merge a collection of objects to as few meshes as possible. This may result in /// more than one mesh due to a max vertex count limit of 65535. /// /// A collection of meshes to be merged. /// /// 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. /// public static List Combine(IEnumerable meshes) { if (meshes == null) throw new ArgumentNullException("meshes"); if (!meshes.Any() || meshes.Count() < 2) return null; var vertices = new List(); var faces = new List(); var autoUvFaces = new List(); var sharedVertices = new List(); var sharedTextures = new List(); int offset = 0; var materialMap = new List(); 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; } static ProBuilderMesh CreateMeshFromSplit(List vertices, List faces, Dictionary sharedVertexLookup, Dictionary sharedTextureLookup, Dictionary remap, Material[] materials) { // finalize mesh var sv = new Dictionary(); var st = new Dictionary(); foreach (var f in faces) { for (int i = 0, c = f.indexesInternal.Length; i < c; i++) f.indexesInternal[i] = remap[f.indexesInternal[i]]; f.InvalidateCache(); } foreach (var kvp in remap) { int v; if (sharedVertexLookup.TryGetValue(kvp.Key, out v)) sv.Add(kvp.Value, v); if (sharedTextureLookup.TryGetValue(kvp.Key, out v)) st.Add(kvp.Value, v); } return ProBuilderMesh.Create( vertices, faces, SharedVertex.ToSharedVertices(sv), st.Any() ? SharedVertex.ToSharedVertices(st) : null, materials); } /// /// Break a ProBuilder mesh into multiple meshes if it's vertex count is greater than maxVertexCount. /// /// internal static List SplitByMaxVertexCount(IList vertices, IList faces, IList sharedVertices, IList sharedTextures, uint maxVertexCount = ProBuilderMesh.maxVertexCount) { uint vertexCount = (uint)vertices.Count; uint meshCount = System.Math.Max(1u, vertexCount / maxVertexCount); var submeshCount = faces.Max(x => x.submeshIndex) + 1; if (meshCount < 2) return new List() { ProBuilderMesh.Create(vertices, faces, sharedVertices, sharedTextures, new Material[submeshCount]) }; var sharedVertexLookup = new Dictionary(); SharedVertex.GetSharedVertexLookup(sharedVertices, sharedVertexLookup); var sharedTextureLookup = new Dictionary(); SharedVertex.GetSharedVertexLookup(sharedTextures, sharedTextureLookup); var meshes = new List(); var mv = new List(); var mf = new List(); var remap = new Dictionary(); foreach (var face in faces) { if (mv.Count + face.distinctIndexes.Count > maxVertexCount) { // finalize mesh meshes.Add(CreateMeshFromSplit(mv, mf, sharedVertexLookup, sharedTextureLookup, remap, new Material[submeshCount])); mv.Clear(); mf.Clear(); remap.Clear(); } foreach (int i in face.distinctIndexes) { mv.Add(vertices[i]); remap.Add(i, mv.Count - 1); } mf.Add(face); } if (mv.Any()) meshes.Add(CreateMeshFromSplit(mv, mf, sharedVertexLookup, sharedTextureLookup, remap, new Material[submeshCount])); return meshes; } } }