#if UNITY_EDITOR using UnityEngine; using UnityEditor; using System.IO; using System.Collections.Generic; namespace O3DWB { public class MeshCombiner { private class MeshCombineMaterial { public Material Material; public List MeshInstances = new List(); } private class MeshInstance { public Mesh SourceMesh; public int SubmeshIndex; public Transform MeshTransform; } private class CombinedMeshData { public List VertTangents; public List VertPositions; public List VertNormals; public List VertUV1; public List VertUV2; public List VertUV3; public List VertUV4; public List VertColors; public List VertIndices; public int CurrentVertIndex; public CombinedMeshData(int combinedNumVertsGuess) { VertTangents = new List(combinedNumVertsGuess); VertPositions = new List(combinedNumVertsGuess); VertNormals = new List(combinedNumVertsGuess); VertUV1 = new List(combinedNumVertsGuess); VertUV2 = new List(combinedNumVertsGuess); VertUV3 = new List(combinedNumVertsGuess); VertUV4 = new List(combinedNumVertsGuess); VertColors = new List(combinedNumVertsGuess); // Assume each group of 3 verts forms a triangle VertIndices = new List(combinedNumVertsGuess / 3); } public void Reset() { VertTangents.Clear(); VertPositions.Clear(); VertNormals.Clear(); VertUV1.Clear(); VertUV2.Clear(); VertUV3.Clear(); VertUV4.Clear(); VertColors.Clear(); VertIndices.Clear(); CurrentVertIndex = 0; } public void AddCurrentVertIndex() { // Note: Assumes that vertices are stored in the combined mesh buffers in the same // way as they are encountered when reading the vertex data using indices // from the source mesh. VertIndices.Add(CurrentVertIndex++); } public void ReverseWindingOrderForLastTriangle() { int lastIndexPtr = VertIndices.Count - 1; int tempIndex = VertIndices[lastIndexPtr]; VertIndices[lastIndexPtr] = VertIndices[lastIndexPtr - 2]; VertIndices[lastIndexPtr - 2] = tempIndex; } } private MeshCombineSettings _combineSettings; public MeshCombineSettings CombineSettings { set { if (value != null) _combineSettings = value; } } public void CombineChildren(List ignoreObjects) { if (_combineSettings == null) return; if (_combineSettings.ChildrenCombineSourceParent == null) { EditorUtility.DisplayDialog("Missing Parent", "Please specify the parent of the objects which must be combined.", "Ok"); return; } List allMeshObjects = _combineSettings.ChildrenCombineSourceParent.GetAllMeshObjectsInHierarchy(); if(ignoreObjects != null && ignoreObjects.Count != 0) allMeshObjects.RemoveAll(item => ignoreObjects.Contains(item)); List meshCombineMaterials = GetMeshCombineMaterials(allMeshObjects, _combineSettings.ChildrenCombineSourceParent); if (meshCombineMaterials.Count == 0) { EditorUtility.DisplayDialog("No Combinable Objects", "The specified parent does not contain any objects which can be combined. Please check the following: \n\r" + "-the parent must contain mesh child objects; \n\r " + "-check the \'Combine static/dynamic meshes\' toggles to ensure that the objects can actually be combined; \n\r" + "-if the objects belong to a hierarchy, make sure to unhceck the \'Ignore objects in hierarchies\' toggle.", "Ok"); return; } Combine(meshCombineMaterials, null); if (_combineSettings.DisableSourceParent) _combineSettings.ChildrenCombineSourceParent.SetActive(false); EditorUtility.DisplayDialog("Done!", "Mesh objects combined successfully!", "Ok"); } public void CombineSelectedObjects(List selectedObjects, List ignoreObjects) { if (_combineSettings == null || selectedObjects == null) return; if(selectedObjects.Count == 0) { EditorUtility.DisplayDialog("No Selected Objects", "The mesh combine process can not start because there are no objects currently selected.", "Ok"); return; } List allMeshObjects = new List(selectedObjects.Count); foreach(var selectedObject in selectedObjects) { if (selectedObject.HasMeshFilterWithValidMesh()) allMeshObjects.Add(selectedObject); } if (ignoreObjects != null && ignoreObjects.Count != 0) allMeshObjects.RemoveAll(item => ignoreObjects.Contains(item)); List meshCombineMaterials = GetMeshCombineMaterials(allMeshObjects, null); if (meshCombineMaterials.Count == 0) { EditorUtility.DisplayDialog("No Combinable Objects", "The current selection does not contain any objects which can be combined. Please check the following: \n\r" + "-the selection must contain mesh objects; \n\r " + "-check the \'Combine static/dynamic meshes\' toggles to ensure that the objects can actually be combined; \n\r" + "-if the objects belong to a hierarchy, make sure to unhceck the \'Ignore objects in hierarchies\' toggle.", "Ok"); return; } Combine(meshCombineMaterials, _combineSettings.SelectionCombineDestinationParent); if(_combineSettings.SelCombineMode == MeshCombineSettings.CombineMode.Replace) { List parents = GameObjectExtensions.GetParents(selectedObjects); foreach(var parent in parents) { UndoEx.DestroyObjectImmediate(parent); } } EditorUtility.DisplayDialog("Done!", "Mesh objects combined successfully!", "Ok"); } private List GetMeshCombineMaterials(List meshObjects, GameObject sourceParent) { var meshCombineMaterialsMap = new Dictionary(); var meshCombineMaterials = new List(); Transform sourceParentTransform = sourceParent != null ? sourceParent.transform : null; foreach (var meshObject in meshObjects) { Transform meshTransform = meshObject.transform; if (_combineSettings.IgnoreObjectsInHierarchies) { if (sourceParentTransform != null) { if (meshTransform.parent != sourceParentTransform || meshTransform.childCount != 0) continue; } else { if (meshTransform.parent != null || meshTransform.childCount != 0) continue; } } if (!_combineSettings.CombineStaticMeshes && meshTransform.gameObject.isStatic) continue; if (!_combineSettings.CombineDynamicMeshes && !meshTransform.gameObject.isStatic) continue; MeshFilter meshFilter = meshObject.GetComponent(); if (meshFilter == null || meshFilter.sharedMesh == null) continue; MeshRenderer meshRenderer = meshObject.GetComponent(); if (meshRenderer == null) continue; Mesh sharedMesh = meshFilter.sharedMesh; int numSharedMaterials = meshRenderer.sharedMaterials.Length; for (int submeshIndex = 0; submeshIndex < sharedMesh.subMeshCount; ++submeshIndex) { // Note: How can this happen? if (submeshIndex >= numSharedMaterials) break; Material subMeshMaterial = meshRenderer.sharedMaterials[submeshIndex]; MeshCombineMaterial meshCombineMaterial = null; if (!meshCombineMaterialsMap.ContainsKey(subMeshMaterial)) { meshCombineMaterial = new MeshCombineMaterial(); meshCombineMaterial.Material = subMeshMaterial; meshCombineMaterialsMap.Add(subMeshMaterial, meshCombineMaterial); meshCombineMaterials.Add(meshCombineMaterial); } else meshCombineMaterial = meshCombineMaterialsMap[subMeshMaterial]; var meshInstance = new MeshInstance(); meshInstance.SourceMesh = sharedMesh; meshInstance.SubmeshIndex = submeshIndex; meshInstance.MeshTransform = meshTransform; meshCombineMaterial.MeshInstances.Add(meshInstance); } } return meshCombineMaterials; } private void Combine(List meshCombineMaterials, GameObject combinedMeshesParent) { List allCombinedMeshObjects = new List(100); if (combinedMeshesParent == null) { combinedMeshesParent = new GameObject(GetNameForCombinedMeshesParent()); UndoEx.RegisterCreatedGameObject(combinedMeshesParent); } for (int combMaterialIndex = 0; combMaterialIndex < meshCombineMaterials.Count; ++combMaterialIndex) { MeshCombineMaterial meshCombMaterial = meshCombineMaterials[combMaterialIndex]; List combineMeshdObjects = Combine(meshCombMaterial, combinedMeshesParent); if (_combineSettings.WeldVertexPositions) { if (_combineSettings.WeldVertexPositionsOnlyForCommonMaterial) WeldVertexPositionsForMeshObjects(combineMeshdObjects); else allCombinedMeshObjects.AddRange(combineMeshdObjects); } } if (_combineSettings.WeldVertexPositions && !_combineSettings.WeldVertexPositionsOnlyForCommonMaterial) WeldVertexPositionsForMeshObjects(allCombinedMeshObjects); if(_combineSettings.CollHandling == MeshCombineSettings.ColliderHandling.Preserve) PreserveColliders(meshCombineMaterials, combinedMeshesParent); } private List Combine(MeshCombineMaterial meshCombineMaterial, GameObject combinedMeshesParent) { List meshInstances = meshCombineMaterial.MeshInstances; if (meshInstances.Count == 0) return new List(); int maxNumMeshVerts = GetMaxNumberOfMeshVerts(); const int numVertsPerMeshGuess = 500; int totalNumVertsGuess = meshCombineMaterial.MeshInstances.Count * numVertsPerMeshGuess; var combinedMeshData = new CombinedMeshData(totalNumVertsGuess); List combinedMeshObjects = new List(100); for (int meshInstanceIndex = 0; meshInstanceIndex < meshInstances.Count; ++meshInstanceIndex) { MeshInstance meshInstance = meshInstances[meshInstanceIndex]; Mesh mesh = meshInstance.SourceMesh; if (mesh.vertexCount == 0) continue; EditorUtility.DisplayProgressBar("Combining Meshes", "Processing material: " + meshCombineMaterial.Material.name, (float)meshInstanceIndex / meshInstances.Count); Matrix4x4 worldMatrix = meshInstance.MeshTransform.localToWorldMatrix; Matrix4x4 worldInverseTranspose = worldMatrix.inverse.transpose; int numNegativeScaleComps = 0; Vector3 worldScale = meshInstance.MeshTransform.lossyScale; if (worldScale[0] < 0.0f) ++numNegativeScaleComps; if (worldScale[1] < 0.0f) ++numNegativeScaleComps; if (worldScale[2] < 0.0f) ++numNegativeScaleComps; bool reverseVertexWindingOrder = (numNegativeScaleComps % 2 != 0); int[] submeshVertIndices = mesh.GetTriangles(meshInstance.SubmeshIndex); if (submeshVertIndices.Length == 0) continue; Vector4[] vertTangents = mesh.tangents; Vector3[] vertPositions = mesh.vertices; Vector3[] vertNormals = mesh.normals; Vector2[] vertUV1 = mesh.uv; Vector2[] vertUV2 = mesh.uv2; Vector2[] vertUV3 = mesh.uv3; Vector2[] vertUV4 = mesh.uv4; Color[] vertColors = mesh.colors; foreach (var vertIndex in submeshVertIndices) { if (vertTangents.Length != 0) { Vector3 transformedTangent = new Vector3(vertTangents[vertIndex].x, vertTangents[vertIndex].y, vertTangents[vertIndex].z); transformedTangent = worldInverseTranspose.MultiplyVector(transformedTangent); transformedTangent.Normalize(); combinedMeshData.VertTangents.Add(new Vector4(transformedTangent.x, transformedTangent.y, transformedTangent.z, vertTangents[vertIndex].w)); } if (vertNormals.Length != 0) { Vector3 transformedNormal = worldInverseTranspose.MultiplyVector(vertNormals[vertIndex]); transformedNormal.Normalize(); combinedMeshData.VertNormals.Add(transformedNormal); } if (vertPositions.Length != 0) combinedMeshData.VertPositions.Add(worldMatrix.MultiplyPoint(vertPositions[vertIndex])); if (vertColors.Length != 0) combinedMeshData.VertColors.Add(vertColors[vertIndex]); if (vertUV1.Length != 0) combinedMeshData.VertUV1.Add(vertUV1[vertIndex]); if (vertUV3.Length != 0) combinedMeshData.VertUV2.Add(vertUV3[vertIndex]); if (vertUV4.Length != 0) combinedMeshData.VertUV3.Add(vertUV4[vertIndex]); if (vertUV2.Length != 0 && !_combineSettings.GenerateLightmapUVs) combinedMeshData.VertUV2.Add(vertUV2[vertIndex]); combinedMeshData.AddCurrentVertIndex(); int numIndices = combinedMeshData.VertIndices.Count; if (reverseVertexWindingOrder && numIndices % 3 == 0) combinedMeshData.ReverseWindingOrderForLastTriangle(); int numMeshVerts = combinedMeshData.VertPositions.Count; if (combinedMeshData.VertIndices.Count % 3 == 0 && (maxNumMeshVerts - numMeshVerts) < 3) { combinedMeshObjects.Add(CreateCombinedMeshObject(combinedMeshData, meshCombineMaterial.Material, combinedMeshesParent)); combinedMeshData.Reset(); } } } combinedMeshObjects.Add(CreateCombinedMeshObject(combinedMeshData, meshCombineMaterial.Material, combinedMeshesParent)); EditorUtility.ClearProgressBar(); return combinedMeshObjects; } private GameObject CreateCombinedMeshObject(CombinedMeshData combinedMeshData, Material meshMaterial, GameObject combinedMeshesParent) { Mesh combinedMesh = CreateCombinedMesh(combinedMeshData); GameObject combinedMeshObject = new GameObject(_combineSettings.CombinedObjectName); UndoEx.RegisterCreatedGameObject(combinedMeshObject); combinedMeshObject.transform.parent = combinedMeshesParent.transform; combinedMeshObject.isStatic = _combineSettings.MakeCombinedMeshesStatic ? true : false; MeshFilter meshFilter = combinedMeshObject.AddComponent(); meshFilter.sharedMesh = combinedMesh; MeshRenderer meshRenderer = combinedMeshObject.AddComponent(); meshRenderer.sharedMaterial = meshMaterial; Vector3 meshPivotPt = MeshCombineSettings.MeshPivotToWorldPoint(combinedMeshObject, _combineSettings.CombinedMeshesPivot); combinedMeshObject.SetMeshPivotPoint(meshPivotPt); if (_combineSettings.CollHandling == MeshCombineSettings.ColliderHandling.CreateNew) { if(_combineSettings.NewCollidersType == MeshCombineSettings.ColliderType.Mesh) { MeshCollider meshCollider = combinedMeshObject.AddComponent(); if (_combineSettings.UseConvexMeshColliders) meshCollider.convex = true; else meshCollider.convex = false; if (_combineSettings.NewCollidersAreTriggers) meshCollider.isTrigger = true; else meshCollider.isTrigger = false; } else if(_combineSettings.NewCollidersType == MeshCombineSettings.ColliderType.Box) { BoxCollider boxCollider = combinedMeshObject.AddComponent(); if (_combineSettings.NewCollidersAreTriggers) boxCollider.isTrigger = true; else boxCollider.isTrigger = false; } } return combinedMeshObject; } private Mesh CreateCombinedMesh(CombinedMeshData combinedMeshData) { Mesh combinedMesh = new Mesh(); combinedMesh.name = _combineSettings.CombinedMeshName; combinedMesh.vertices = combinedMeshData.VertPositions.ToArray(); if (combinedMeshData.VertTangents.Count != 0) combinedMesh.tangents = combinedMeshData.VertTangents.ToArray(); if (combinedMeshData.VertNormals.Count != 0) combinedMesh.normals = combinedMeshData.VertNormals.ToArray(); if (combinedMeshData.VertUV1.Count != 0) combinedMesh.uv = combinedMeshData.VertUV1.ToArray(); if (combinedMeshData.VertUV3.Count != 0) combinedMesh.uv3 = combinedMeshData.VertUV3.ToArray(); if (combinedMeshData.VertUV4.Count != 0) combinedMesh.uv4 = combinedMeshData.VertUV4.ToArray(); if (combinedMeshData.VertColors.Count != 0) combinedMesh.colors = combinedMeshData.VertColors.ToArray(); combinedMesh.SetIndices(combinedMeshData.VertIndices.ToArray(), MeshTopology.Triangles, 0); if (_combineSettings.GenerateLightmapUVs) Unwrapping.GenerateSecondaryUVSet(combinedMesh); else if (combinedMeshData.VertUV2.Count != 0) combinedMesh.uv2 = combinedMeshData.VertUV2.ToArray(); combinedMesh.UploadMeshData(_combineSettings.MarkNoLongerReadable); SaveCombinedMeshAsAsset(combinedMesh); return combinedMesh; } private static void SaveCombinedMeshAsAsset(Mesh combinedMesh) { string absoluteFolderPath = FileSystem.GetToolFolderName() + "/Octave3D Combined Meshes"; if (!Directory.Exists(absoluteFolderPath)) Directory.CreateDirectory(absoluteFolderPath); AssetDatabase.CreateAsset(combinedMesh, absoluteFolderPath + "/" + combinedMesh.name + "_" + combinedMesh.GetHashCode() + ".asset"); AssetDatabase.SaveAssets(); } private int GetMaxNumberOfMeshVerts() { if (_combineSettings.GenerateLightmapUVs) return 32000; return 65000; } private string GetNameForCombinedMeshesParent() { return "cmbParent_Octave3D"; } private void WeldVertexPositionsForMeshObjects(List meshObjects) { if (meshObjects.Count == 0) return; var allMeshes = new List(meshObjects.Count); foreach (var gameObject in meshObjects) { Mesh mesh = gameObject.GetMeshFromMeshFilter(); if (mesh != null) allMeshes.Add(mesh); } WeldVertexPositionsForMeshes(allMeshes); } private void WeldVertexPositionsForMeshes(List meshes) { var meshVertices = new List(meshes.Count * 100); for (int meshIndex = 0; meshIndex < meshes.Count; ++meshIndex) { Mesh mesh = meshes[meshIndex]; meshVertices.AddRange(mesh.vertices); } WeldVertexPositions(meshVertices); int vertexOffset = 0; for (int meshIndex = 0; meshIndex < meshes.Count; ++meshIndex) { Mesh mesh = meshes[meshIndex]; mesh.vertices = (meshVertices.GetRange(vertexOffset, mesh.vertexCount)).ToArray(); vertexOffset += mesh.vertexCount; } } private void WeldVertexPositions(List vertexPositions) { VertexWeldOctree vertexWeldOctree = new VertexWeldOctree(10.0f); vertexWeldOctree.Build(vertexPositions); vertexPositions.Clear(); vertexPositions.AddRange(vertexWeldOctree.WeldVertices(_combineSettings.VertexPositionWeldEpsilon)); } private void PreserveColliders(List meshCombineMaterials, GameObject combinedMeshesParent) { if(meshCombineMaterials.Count == 0 || _combineSettings.CollHandling != MeshCombineSettings.ColliderHandling.Preserve) return; GameObject colliderParent = combinedMeshesParent; Transform colliderParentTransform = colliderParent.transform; if(_combineSettings.CollPreserveParent == MeshCombineSettings.ColliderPreserveParent.OneForAll) { colliderParent = new GameObject(_combineSettings.OneForAllColliderParentName); UndoEx.RegisterCreatedGameObject(colliderParent); colliderParentTransform = colliderParent.transform; colliderParentTransform.parent = combinedMeshesParent.transform; } HashSet processedObjects = new HashSet(); Vector3 positionSum = Vector3.zero; for (int cmbMaterialIndex = 0; cmbMaterialIndex < meshCombineMaterials.Count; ++cmbMaterialIndex ) { MeshCombineMaterial meshCmbMaterial = meshCombineMaterials[cmbMaterialIndex]; EditorUtility.DisplayProgressBar("Collider Handling", "Preserving colliders...", (float)cmbMaterialIndex / meshCombineMaterials.Count); List meshInstances = meshCmbMaterial.MeshInstances; foreach (var meshInstance in meshInstances) { GameObject meshObject = meshInstance.MeshTransform.gameObject; if (processedObjects.Contains(meshObject)) continue; processedObjects.Add(meshObject); positionSum += meshObject.transform.position; BoxCollider[] boxColliders = meshObject.GetComponents(); foreach (var boxCollider in boxColliders) { string colliderName = _combineSettings.CollPreserveName == MeshCombineSettings.ColliderPreserveName.SameAsSource ? meshObject.name : GetDefaultBoxColliderObjectName(); GameObject colliderObject = boxCollider.CloneAsNewObject(colliderName).gameObject; UndoEx.RegisterCreatedGameObject(colliderObject); colliderObject.transform.parent = colliderParentTransform; } SphereCollider[] sphereColliders = meshObject.GetComponents(); foreach (var sphereCollider in sphereColliders) { string colliderName = _combineSettings.CollPreserveName == MeshCombineSettings.ColliderPreserveName.SameAsSource ? meshObject.name : GetDefaultSphereColliderObjectName(); GameObject colliderObject = sphereCollider.CloneAsNewObject(colliderName).gameObject; UndoEx.RegisterCreatedGameObject(colliderObject); colliderObject.transform.parent = colliderParentTransform; } CapsuleCollider[] capsuleColliders = meshObject.GetComponents(); foreach (var capsuleCollider in capsuleColliders) { string colliderName = _combineSettings.CollPreserveName == MeshCombineSettings.ColliderPreserveName.SameAsSource ? meshObject.name : GetDefaultCapsuleColliderObjectName(); GameObject colliderObject = capsuleCollider.CloneAsNewObject(colliderName).gameObject; UndoEx.RegisterCreatedGameObject(colliderObject); colliderObject.transform.parent = colliderParentTransform; } MeshCollider[] meshColliders = meshObject.GetComponents(); foreach (var meshCollider in meshColliders) { string colliderName = _combineSettings.CollPreserveName == MeshCombineSettings.ColliderPreserveName.SameAsSource ? meshObject.name : GetDefaultMeshColliderObjectName(); GameObject colliderObject = meshCollider.CloneAsNewObject(colliderName).gameObject; UndoEx.RegisterCreatedGameObject(colliderObject); colliderObject.transform.parent = colliderParentTransform; } } } if (_combineSettings.CollPreserveParent == MeshCombineSettings.ColliderPreserveParent.OneForAll) colliderParent.SetWorldPosDontAffectChildren(positionSum *= (1.0f / processedObjects.Count)); if (colliderParent.transform.childCount == 0) GameObject.DestroyImmediate(colliderParent); EditorUtility.ClearProgressBar(); } private string GetDefaultBoxColliderObjectName() { return "boxCollider"; } private string GetDefaultSphereColliderObjectName() { return "sphereCollider"; } private string GetDefaultCapsuleColliderObjectName() { return "capsuleCollider"; } private string GetDefaultMeshColliderObjectName() { return "meshCollider"; } } } #endif