using UnityEngine; using System.Collections.Generic; using HierarchyDict = System.Collections.Generic.Dictionary; using BoneTransformDict = System.Collections.Generic.Dictionary>; namespace utils { public class MeshCombiner { #region Operations //! Combine mesh. /*! \return combined mesh instance. */ public static GameObject Combine(List SkinnedRenderers) { // Generated GO GameObject final_mesh_go = new GameObject("Mesh"); // Dummy parent holder GameObject dummy_parent = new GameObject("DummyParent"); // All available bones var all_bones = new BoneTransformDict(); // Traverse through all skinned mesh renderers foreach(var renderer in SkinnedRenderers) { var renderer_bones = renderer.bones; foreach (var bone in renderer_bones) { // Bone doesn't exist, add it if (!all_bones.ContainsKey(bone.name)) all_bones[bone.name] = new utils.Tuple(bone, bone.parent.name); } } var combineInstanceArrays = new Dictionary>(); var bone_weights = new Dictionary(); // Map between bone name and index var added_bones = new Dictionary(); // List of child objects holding the skinned mesh renderers to be // destroyed when finished var child_objects_to_destroy = new List(); int bone_index = 0; foreach(var renderer in SkinnedRenderers) { child_objects_to_destroy.Add(renderer.transform.parent.gameObject); var renderer_bones = renderer.bones; // Add all bones as first and save the indices of them foreach (var bone in renderer_bones) { // Bone not yet added if (!added_bones.ContainsKey(bone.name)) added_bones[bone.name] = bone_index++; } // Adjust bone weights indices based on real indices of bones var bone_weights_list = new BoneWeight[renderer.sharedMesh.boneWeights.Length]; var renderer_bone_weights = renderer.sharedMesh.boneWeights; for (int i = 0; i < renderer_bone_weights.Length; ++i) { BoneWeight current_bone_weight = renderer_bone_weights[i]; current_bone_weight.boneIndex0 = added_bones[renderer_bones[current_bone_weight.boneIndex0].name]; current_bone_weight.boneIndex2 = added_bones[renderer_bones[current_bone_weight.boneIndex2].name]; current_bone_weight.boneIndex3 = added_bones[renderer_bones[current_bone_weight.boneIndex3].name]; current_bone_weight.boneIndex1 = added_bones[renderer_bones[current_bone_weight.boneIndex1].name]; bone_weights_list[i] = current_bone_weight; } bone_weights[renderer.sharedMesh] = bone_weights_list; // Handle bad input if (renderer.sharedMaterials.Length != renderer.sharedMesh.subMeshCount) { Debug.LogError("Mismatch between material count and submesh count. Is this the correct MeshRenderer?"); continue; } // Prepare stuff for mesh combination with same materials for (int i = 0; i < renderer.sharedMesh.subMeshCount; i++) { // Material not in dict, add it if (!combineInstanceArrays.ContainsKey(renderer.sharedMaterials[i])) combineInstanceArrays[renderer.sharedMaterials[i]] = new List(); var actual_mat_list = combineInstanceArrays[renderer.sharedMaterials[i]]; // Add new instance var combine_instance = new CombineInstance(); combine_instance.transform = renderer.transform.localToWorldMatrix; combine_instance.subMeshIndex = i; combine_instance.mesh = renderer.sharedMesh; actual_mat_list.Add(combine_instance); } // No need to use it anymore renderer.enabled = false; } var bones_hierarchy = new HierarchyDict(); // Recreate bone structure foreach (var bone in all_bones) { // Bone not processed, process it if (!bones_hierarchy.ContainsKey(bone.Key)) AddParent(bone.Key, bones_hierarchy, all_bones, dummy_parent); } // Create bone array from preprocessed dict var bones = new Transform[added_bones.Count]; foreach (var bone in added_bones) bones[bone.Value] = bones_hierarchy[bone.Key]; // Get the root bone Transform root_bone = bones[0]; while (root_bone.parent != null) { // Get parent if (bones_hierarchy.ContainsKey(root_bone.parent.name)) root_bone = root_bone.parent; else break; } // Create skinned mesh renderer GO GameObject combined_mesh_go = new GameObject("Combined"); combined_mesh_go.transform.parent = final_mesh_go.transform; combined_mesh_go.transform.localPosition = Vector3.zero; // Fill bind poses var bind_poses = new Matrix4x4[bones.Length]; for (int i = 0; i < bones.Length; ++i) bind_poses[i] = bones[i].worldToLocalMatrix * combined_mesh_go.transform.localToWorldMatrix; // Need to move it to new GO root_bone.parent = final_mesh_go.transform; // Combine meshes into one var combined_new_mesh = new Mesh(); var combined_vertices = new List(); var combined_uvs = new List(); var combined_indices = new List(); var combined_bone_weights = new List(); var combined_materials = new Material[combineInstanceArrays.Count]; var vertex_offset_map = new Dictionary(); int vertex_index_offset = 0; int current_material_index = 0; foreach (var combine_instance in combineInstanceArrays) { combined_materials[current_material_index++] = combine_instance.Key; var submesh_indices = new List(); // Process meshes for each material foreach (var combine in combine_instance.Value) { // Update vertex offset for current mesh if (!vertex_offset_map.ContainsKey(combine.mesh)) { // Add vertices for mesh combined_vertices.AddRange(combine.mesh.vertices); // Set uvs combined_uvs.AddRange(combine.mesh.uv); // Add weights combined_bone_weights.AddRange(bone_weights[combine.mesh]); vertex_offset_map[combine.mesh] = vertex_index_offset; vertex_index_offset += combine.mesh.vertexCount; } int vertex_current_offset = vertex_offset_map[combine.mesh]; var indices = combine.mesh.GetTriangles(combine.subMeshIndex); // Need to "shift" indices for (int k = 0; k < indices.Length; ++k) indices[k] += vertex_current_offset; submesh_indices.AddRange(indices); } // Push indices for given submesh combined_indices.Add(submesh_indices.ToArray()); } combined_new_mesh.vertices = combined_vertices.ToArray(); combined_new_mesh.uv = combined_uvs.ToArray(); combined_new_mesh.boneWeights = combined_bone_weights.ToArray(); combined_new_mesh.subMeshCount = combined_materials.Length; for (int i = 0; i < combined_indices.Count; ++i) combined_new_mesh.SetTriangles(combined_indices[i], i); // Create mesh renderer SkinnedMeshRenderer combined_skin_mesh_renderer = combined_mesh_go.AddComponent(); combined_skin_mesh_renderer.sharedMesh = combined_new_mesh; combined_skin_mesh_renderer.bones = bones; combined_skin_mesh_renderer.rootBone = root_bone; combined_skin_mesh_renderer.sharedMesh.bindposes = bind_poses; combined_skin_mesh_renderer.sharedMesh.RecalculateNormals(); combined_skin_mesh_renderer.sharedMesh.RecalculateBounds(); combined_skin_mesh_renderer.sharedMaterials = combined_materials; // Destroy children foreach (var child in child_objects_to_destroy) GameObject.DestroyImmediate(child); // Destroy dummy parent GameObject.DestroyImmediate(dummy_parent); return final_mesh_go; } static void AddParent(string BoneName, HierarchyDict BoneHierarchy, BoneTransformDict AllBones, GameObject DummyParent) { Transform actual_bone = null; // Must be bone if (AllBones.ContainsKey(BoneName)) { var bone_tuple = AllBones[BoneName]; // Add parent recursively if not added if (!BoneHierarchy.ContainsKey(bone_tuple._2)) { AddParent(bone_tuple._2, BoneHierarchy, AllBones, DummyParent); // Unparent all children of parents Unparent(BoneHierarchy[bone_tuple._2], DummyParent); } bone_tuple._1.parent = BoneHierarchy[bone_tuple._2]; actual_bone = bone_tuple._1; } BoneHierarchy[BoneName] = actual_bone; } static void Unparent(Transform Parent, GameObject DummyParent) { if (Parent != null) { var unparent_list = new List(); foreach (Transform child in Parent.transform) unparent_list.Add(child); foreach (var child in unparent_list) child.parent = DummyParent.transform; } } #endregion } public struct Tuple { public readonly T1 _1; public readonly T2 _2; public Tuple(T1 T1_, T2 T2_) { _1 = T1_; _2 = T2_; } } }