using System.Collections.Generic; using System.Linq; using UnityEngine; [RequireComponent (typeof (MeshFilter))] [RequireComponent (typeof (MeshRenderer))] public class combineMeshSystem : MonoBehaviour { [Header ("Main Settings")] [Space] public bool useMeshFilterList; public List listMeshFilter; [Space] public bool useCustomParentListToGetMeshFilterList; public List customParentListToGetMeshFilterList; [Space] [SerializeField] [Tooltip ("MeshFilters with Meshes which we don't want to combine into one Mesh.")] private MeshFilter [] meshFiltersToSkip = new MeshFilter [0]; [Space] [Header ("Other Settings")] [Space] public bool createMultiMaterialMesh = false; public bool combineInactiveChildren = false; public bool deactivateCombinedChildren = true; public bool deactivateCombinedChildrenMeshRenderers = false; public bool generateUVMap = false; public bool destroyCombinedChildren = false; [Space] [Header ("Debug")] [Space] public bool showDebugPrint; [Space] [Header ("Events Settings")] [Space] public bool sendCombinedObjectOnEvent; public eventParameters.eventToCallWithGameObject eventToSendCombinedObject; private const int Mesh16BitBufferVertexLimit = 65535; public bool CreateMultiMaterialMesh { get { return createMultiMaterialMesh; } set { createMultiMaterialMesh = value; } } public bool CombineInactiveChildren { get { return combineInactiveChildren; } set { combineInactiveChildren = value; } } public bool DeactivateCombinedChildren { get { return deactivateCombinedChildren; } set { deactivateCombinedChildren = value; CheckDeactivateCombinedChildren (); } } public bool DeactivateCombinedChildrenMeshRenderers { get { return deactivateCombinedChildrenMeshRenderers; } set { deactivateCombinedChildrenMeshRenderers = value; CheckDeactivateCombinedChildren (); } } public bool GenerateUVMap { get { return generateUVMap; } set { generateUVMap = value; } } public bool DestroyCombinedChildren { get { return destroyCombinedChildren; } set { destroyCombinedChildren = value; CheckDestroyCombinedChildren (); } } private void CheckDeactivateCombinedChildren () { if (deactivateCombinedChildren || deactivateCombinedChildrenMeshRenderers) { destroyCombinedChildren = false; } } private void CheckDestroyCombinedChildren () { if (destroyCombinedChildren) { deactivateCombinedChildren = false; deactivateCombinedChildrenMeshRenderers = false; } } public void CombineMeshes (bool showCreatedMeshInfo) { #region Save our parent scale and our Transform and reset it temporarily: // When we are unparenting and get parent again then sometimes scale is a little bit different so save scale before unparenting: Vector3 oldScaleAsChild = transform.localScale; // If we have parent then his scale will affect to our new combined Mesh scale so unparent us: int positionInParentHierarchy = transform.GetSiblingIndex (); Transform parent = transform.parent; transform.parent = null; // Thanks to this the new combined Mesh will have same position and scale in the world space like its children: Quaternion oldRotation = transform.rotation; Vector3 oldPosition = transform.position; Vector3 oldScale = transform.localScale; transform.rotation = Quaternion.identity; transform.position = Vector3.zero; transform.localScale = Vector3.one; #endregion Save Transform and reset it temporarily. #region Combine Meshes into one Mesh: if (!createMultiMaterialMesh) { CombineMeshesWithSingleMaterial (showCreatedMeshInfo); } else { CombineMeshesWithMutliMaterial (showCreatedMeshInfo); } #endregion Combine Meshes into one Mesh. #region Set old Transform values: // Bring back the Transform values: transform.rotation = oldRotation; transform.position = oldPosition; transform.localScale = oldScale; // Get back parent and same hierarchy position: transform.parent = parent; transform.SetSiblingIndex (positionInParentHierarchy); // Set back the scale value as child: transform.localScale = oldScaleAsChild; if (sendCombinedObjectOnEvent) { eventToSendCombinedObject.Invoke (gameObject); } if (showDebugPrint) { print ("objects meshes combined"); } #endregion Set old Transform values. } void checkNullElementsOnListMeshFilter () { for (int i = listMeshFilter.Count - 1; i >= 0; i--) { if (listMeshFilter [i] == null) { listMeshFilter.RemoveAt (i); } } } private MeshFilter [] GetMeshFiltersToCombine () { // Get all MeshFilters belongs to this GameObject and its children: MeshFilter [] meshFilters = new MeshFilter [0]; if (useMeshFilterList) { checkNullElementsOnListMeshFilter (); int listMeshFilterCount = listMeshFilter.Count + 1; meshFilters = new MeshFilter [listMeshFilterCount]; MeshFilter mainMeshFilter = gameObject.GetComponent (); meshFilters [0] = mainMeshFilter; for (int i = 0; i < listMeshFilter.Count; i++) { meshFilters [i + 1] = listMeshFilter [i]; } } else { meshFilters = GetComponentsInChildren (combineInactiveChildren); } // Delete first MeshFilter belongs to this GameObject in meshFiltersToSkip array: meshFiltersToSkip = meshFiltersToSkip.Where ((meshFilter) => meshFilter != meshFilters [0]).ToArray (); // Delete null values in meshFiltersToSkip array: meshFiltersToSkip = meshFiltersToSkip.Where ((meshFilter) => meshFilter != null).ToArray (); for (int i = 0; i < meshFiltersToSkip.Length; i++) { meshFilters = meshFilters.Where ((meshFilter) => meshFilter != meshFiltersToSkip [i]).ToArray (); } return meshFilters; } private void CombineMeshesWithSingleMaterial (bool showCreatedMeshInfo) { // Get all MeshFilters belongs to this GameObject and its children: MeshFilter [] meshFilters = GetMeshFiltersToCombine (); // First MeshFilter belongs to this GameObject so we don't need it: CombineInstance [] combineInstances = new CombineInstance [meshFilters.Length - 1]; // If it will be over 65535 then use the 32 bit index buffer: long verticesLength = 0; for (int i = 0; i < meshFilters.Length - 1; i++) // Skip first MeshFilter belongs to this GameObject in this loop. { combineInstances [i].subMeshIndex = 0; combineInstances [i].mesh = meshFilters [i + 1].sharedMesh; combineInstances [i].transform = meshFilters [i + 1].transform.localToWorldMatrix; verticesLength += combineInstances [i].mesh.vertices.Length; } // Set Material from child: MeshRenderer [] meshRenderers = new MeshRenderer [0]; if (useMeshFilterList) { checkNullElementsOnListMeshFilter (); meshRenderers = new MeshRenderer [listMeshFilter.Count]; int meshRenderersLength = meshRenderers.Length; for (int i = 0; i < meshRenderersLength; i++) { meshRenderers [i] = listMeshFilter [i].GetComponent (); } MeshRenderer mainMeshRenderer = gameObject.GetComponent (); meshRenderers = meshRenderers.Append (mainMeshRenderer).ToArray (); } else { meshRenderers = GetComponentsInChildren (combineInactiveChildren); } if (meshRenderers.Length >= 2) { meshRenderers [0].sharedMaterials = new Material [1]; meshRenderers [0].sharedMaterial = meshRenderers [1].sharedMaterial; } else { meshRenderers [0].sharedMaterials = new Material [0]; // Reset the MeshRenderer's Materials array. } // Create Mesh from combineInstances: Mesh combinedMesh = new Mesh (); combinedMesh.name = name; #if UNITY_2017_3_OR_NEWER if (verticesLength > Mesh16BitBufferVertexLimit) { combinedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; // Only works on Unity 2017.3 or higher. } combinedMesh.CombineMeshes (combineInstances); GenerateUV (combinedMesh); meshFilters [0].sharedMesh = combinedMesh; DeactivateCombinedGameObjects (meshFilters); if (showCreatedMeshInfo) { if (verticesLength <= Mesh16BitBufferVertexLimit) { Debug.Log ("Mesh \"" + name + "\" was created from " + combineInstances.Length + " children meshes and has " + verticesLength + " vertices."); } else { Debug.Log ("Mesh \"" + name + "\" was created from " + combineInstances.Length + " children meshes and has " + verticesLength + " vertices. Some old devices, like Android with Mali-400 GPU, do not support over 65535 vertices."); } } #else if(verticesLength <= Mesh16BitBufferVertexLimit) { combinedMesh.CombineMeshes(combineInstances); GenerateUV(combinedMesh); meshFilters[0].sharedMesh = combinedMesh; DeactivateCombinedGameObjects(meshFilters); if(showCreatedMeshInfo) { Debug.Log("Mesh \""+name+"\" was created from "+combineInstances.Length+" children meshes and has "+verticesLength +" vertices."); } } else if(showCreatedMeshInfo) { Debug.Log("The mesh vertex limit is 65535! The created mesh had "+verticesLength+" vertices. Upgrade Unity version to" +" 2017.3 or higher to avoid this limit (some old devices, like Android with Mali-400 GPU, do not support over 65535 vertices)."); } #endif } private void CombineMeshesWithMutliMaterial (bool showCreatedMeshInfo) { #region Get MeshFilters, MeshRenderers and unique Materials from all children: MeshFilter [] meshFilters = GetMeshFiltersToCombine (); MeshRenderer [] meshRenderers = new MeshRenderer [meshFilters.Length]; meshRenderers [0] = GetComponent (); // Our (parent) MeshRenderer. List uniqueMaterialsList = new List (); for (int i = 0; i < meshFilters.Length - 1; i++) { meshRenderers [i + 1] = meshFilters [i + 1].GetComponent (); if (meshRenderers [i + 1] != null) { Material [] materials = meshRenderers [i + 1].sharedMaterials; // Get all Materials from child Mesh. for (int j = 0; j < materials.Length; j++) { if (!uniqueMaterialsList.Contains (materials [j])) // If Material doesn't exists in the list then add it. { uniqueMaterialsList.Add (materials [j]); } } } } #endregion Get MeshFilters, MeshRenderers and unique Materials from all children. #region Combine children Meshes with the same Material to create submeshes for final Mesh: List finalMeshCombineInstancesList = new List (); // If it will be over 65535 then use the 32 bit index buffer: long verticesLength = 0; for (int i = 0; i < uniqueMaterialsList.Count; i++) // Create each Mesh (submesh) from Meshes with the same Material. { List submeshCombineInstancesList = new List (); for (int j = 0; j < meshFilters.Length - 1; j++) // Get only childeren Meshes (skip our Mesh). { if (meshRenderers [j + 1] != null && meshFilters [j + 1].sharedMesh != null) { Material [] submeshMaterials = meshRenderers [j + 1].sharedMaterials; // Get all Materials from child Mesh. for (int k = 0; k < submeshMaterials.Length; k++) { // If Materials are equal, combine Mesh from this child: if (uniqueMaterialsList [i] == submeshMaterials [k]) { CombineInstance combineInstance = new CombineInstance (); combineInstance.subMeshIndex = k; // Mesh may consist of smaller parts - submeshes. // Every part have different index. If there are 3 submeshes // in Mesh then MeshRender needs 3 Materials to render them. combineInstance.mesh = meshFilters [j + 1].sharedMesh; combineInstance.transform = meshFilters [j + 1].transform.localToWorldMatrix; submeshCombineInstancesList.Add (combineInstance); verticesLength += combineInstance.mesh.vertices.Length; } } } } // Create new Mesh (submesh) from Meshes with the same Material: Mesh submesh = new Mesh (); #if UNITY_2017_3_OR_NEWER if (verticesLength > Mesh16BitBufferVertexLimit) { submesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; // Only works on Unity 2017.3 or higher. } submesh.CombineMeshes (submeshCombineInstancesList.ToArray (), true); #else // Below Unity 2017.3 if vertices count is above the limit then an error appears in the console when we use the below method. // Anyway we don't stop the algorithm here beacuse we want to count the entire number of vertices in the children meshes: if(verticesLength <= Mesh16BitBufferVertexLimit) { submesh.CombineMeshes(submeshCombineInstancesList.ToArray(), true); } #endif CombineInstance finalCombineInstance = new CombineInstance (); finalCombineInstance.subMeshIndex = 0; finalCombineInstance.mesh = submesh; finalCombineInstance.transform = Matrix4x4.identity; finalMeshCombineInstancesList.Add (finalCombineInstance); } #endregion Combine submeshes (children Meshes) with the same Material. #region Set Materials array & combine submeshes into one multimaterial Mesh: meshRenderers [0].sharedMaterials = uniqueMaterialsList.ToArray (); Mesh combinedMesh = new Mesh (); combinedMesh.name = name; #if UNITY_2017_3_OR_NEWER if (verticesLength > Mesh16BitBufferVertexLimit) { combinedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; // Only works on Unity 2017.3 or higher. } combinedMesh.CombineMeshes (finalMeshCombineInstancesList.ToArray (), false); GenerateUV (combinedMesh); meshFilters [0].sharedMesh = combinedMesh; DeactivateCombinedGameObjects (meshFilters); if (showCreatedMeshInfo) { if (verticesLength <= Mesh16BitBufferVertexLimit) { Debug.Log ("Mesh \"" + name + "\" was created from " + (meshFilters.Length - 1) + " children meshes and has " + finalMeshCombineInstancesList.Count + " submeshes, and " + verticesLength + " vertices."); } else { Debug.Log ("Mesh \"" + name + "\" was created from " + (meshFilters.Length - 1) + " children meshes and has " + finalMeshCombineInstancesList.Count + " submeshes, and " + verticesLength + " vertices. Some old devices, like Android with Mali-400 GPU, do not support over 65535 vertices."); } } #else if(verticesLength <= Mesh16BitBufferVertexLimit) { combinedMesh.CombineMeshes(finalMeshCombineInstancesList.ToArray(), false); GenerateUV(combinedMesh); meshFilters[0].sharedMesh = combinedMesh; DeactivateCombinedGameObjects(meshFilters); if(showCreatedMeshInfo) { Debug.Log("Mesh \""+name+"\" was created from "+(meshFilters.Length-1)+" children meshes and has " +finalMeshCombineInstancesList.Count+" submeshes, and "+verticesLength+" vertices."); } } else if(showCreatedMeshInfo) { Debug.Log("The mesh vertex limit is 65535! The created mesh had "+verticesLength+" vertices. Upgrade Unity version to" +" 2017.3 or higher to avoid this limit (some old devices, like Android with Mali-400 GPU, do not support over 65535 vertices)."); } #endif #endregion Set Materials array & combine submeshes into one multimaterial Mesh. } private void DeactivateCombinedGameObjects (MeshFilter [] meshFilters) { for (int i = 0; i < meshFilters.Length - 1; i++) // Skip first MeshFilter belongs to this GameObject in this loop. { if (!destroyCombinedChildren) { if (deactivateCombinedChildren) { meshFilters [i + 1].gameObject.SetActive (false); } if (deactivateCombinedChildrenMeshRenderers) { MeshRenderer meshRenderer = meshFilters [i + 1].gameObject.GetComponent (); if (meshRenderer != null) { meshRenderer.enabled = false; } } } else { DestroyImmediate (meshFilters [i + 1].gameObject); } } } private void GenerateUV (Mesh combinedMesh) { #if UNITY_EDITOR if (generateUVMap) { UnityEditor.UnwrapParam unwrapParam = new UnityEditor.UnwrapParam (); UnityEditor.UnwrapParam.SetDefaults (out unwrapParam); UnityEditor.Unwrapping.GenerateSecondaryUVSet (combinedMesh, unwrapParam); } #endif } public void getObjectMeshes () { listMeshFilter.Clear (); if (useCustomParentListToGetMeshFilterList) { for (int i = 0; i < customParentListToGetMeshFilterList.Count; i++) { if (customParentListToGetMeshFilterList [i] != null) { MeshFilter [] meshFilters = customParentListToGetMeshFilterList [i].GetComponentsInChildren (combineInactiveChildren); for (int j = 0; j < meshFilters.Length; j++) { if (meshFilters [j] != null) { listMeshFilter.Add (meshFilters [j]); } } } } } else { MeshFilter [] meshFilters = GetComponentsInChildren (combineInactiveChildren); for (int i = 0; i < meshFilters.Length; i++) { if (meshFilters [i] != null) { listMeshFilter.Add (meshFilters [i]); } } } MeshFilter mainMeshFilter = gameObject.GetComponent (); for (int i = listMeshFilter.Count - 1; i >= 0; i--) { if (listMeshFilter [i].GetComponent () != null) { listMeshFilter.RemoveAt (i); } if (mainMeshFilter != null && mainMeshFilter == listMeshFilter [i]) { listMeshFilter.RemoveAt (i); } } updateComponent (); } public void clearMeshList () { listMeshFilter.Clear (); updateComponent (); } void updateComponent () { GKC_Utils.updateComponent (this); GKC_Utils.updateDirtyScene ("Update Combine Mesh System", gameObject); } }