using System; using System.Collections.Generic; using System.Linq; namespace UnityEngine.ProBuilder.MeshOperations { /// /// Provides methods for merging multiple objects into a single mesh. /// public static class CombineMeshes { /// /// Merges a collection of objects to create as few meshes as possible. This may result in /// more than one mesh due to a max vertex count limit of 65535. /// /// The collection of meshes to merge. /// /// 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. /// [Obsolete("Combine(IEnumerable meshes) is deprecated. Plase use Combine(IEnumerable meshes, ProBuilderMesh meshTarget).")] public static List Combine(IEnumerable meshes) { return CombineToNewMeshes(meshes); } /// /// Merges a collection of objects into as few meshes as possible. It re-uses the `meshTarget` object as the first /// destination for the first -1 vertices. If the sum of vertices is above - 1, /// it generates new meshes unless there is a single mesh left. In that case it appends it to the return list. /// /// A collection of meshes to merge. This collection should include the `meshTarget` object. /// A mesh to use as the starting point for merging and which will be kept as a reference (target). This mesh must be present in the `meshes` collection. /// /// A list of merged meshes. In most cases this is a single mesh corresponding to `meshTarget`. However it can be multiple in cases /// where the resulting vertex count exceeds the maximum allowable value. /// public static List Combine(IEnumerable meshes, ProBuilderMesh meshTarget) { if (meshes == null) throw new ArgumentNullException("meshes"); if (meshTarget == null) throw new ArgumentNullException("meshTarget"); if (!meshes.Any() || meshes.Count() < 2 ) return null; if (!meshes.Contains(meshTarget)) return null; var vertices = new List(meshTarget.GetVertices()); var faces = new List(meshTarget.facesInternal); var sharedVertices = new List(meshTarget.sharedVertices); var sharedTextures = new List(meshTarget.sharedTextures); int offset = meshTarget.vertexCount; var materialMap = new List(meshTarget.renderer.sharedMaterials); var targetTransform = meshTarget.transform; var firstMeshContributors = new List(); var remainderMeshContributors = new List(); var currentMeshVertexCount = offset; foreach (var mesh in meshes) { if (mesh != meshTarget) { if (currentMeshVertexCount + mesh.vertexCount < ProBuilderMesh.maxVertexCount) { currentMeshVertexCount += mesh.vertexCount; firstMeshContributors.Add(mesh); } else { remainderMeshContributors.Add(mesh); } } } var autoUvFaces = new List(); AccumulateMeshesInfo( firstMeshContributors, offset, ref vertices, ref faces, ref autoUvFaces, ref sharedVertices, ref sharedTextures, ref materialMap, targetTransform ); meshTarget.SetVertices(vertices); meshTarget.faces = faces; meshTarget.sharedVertices = sharedVertices; meshTarget.sharedTextures = sharedTextures != null ? sharedTextures.ToArray() : null; meshTarget.renderer.sharedMaterials = materialMap.ToArray(); meshTarget.ToMesh(); meshTarget.Refresh(); UvUnwrapping.SetAutoAndAlignUnwrapParamsToUVs(meshTarget, autoUvFaces); MeshValidation.EnsureMeshIsValid(meshTarget, out int removedVertices); var returnedMesh = new List() { meshTarget }; if (remainderMeshContributors.Count > 1) { var newMeshes = CombineToNewMeshes(remainderMeshContributors); foreach (var mesh in newMeshes) { MeshValidation.EnsureMeshIsValid(mesh, out removedVertices); returnedMesh.Add(mesh); } } else if (remainderMeshContributors.Count == 1) { returnedMesh.Add(remainderMeshContributors[0]); } return returnedMesh; } static List CombineToNewMeshes(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(); 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); UvUnwrapping.SetAutoAndAlignUnwrapParamsToUVs(m, autoUvFaces); } return res; } static void AccumulateMeshesInfo( IEnumerable meshes, int offset, ref List vertices, ref List faces, ref List autoUvFaces, ref List sharedVertices, ref List sharedTextures, ref List materialMap, Transform targetTransform = null ) { 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++) { var worldVertex = transform.TransformVertex(meshVertices[i]); if (targetTransform != null) vertices.Add(targetTransform.InverseTransformVertex(worldVertex)); else vertices.Add(worldVertex); } 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 = materialCount > 0 ? materials[Math.Clamp(face.submeshIndex, 0, materialCount - 1)] : null; 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; } } 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.Count > 0 ? 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.Count > 0) meshes.Add(CreateMeshFromSplit(mv, mf, sharedVertexLookup, sharedTextureLookup, remap, new Material[submeshCount])); return meshes; } } }