using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; using UnityEditor; namespace UniGLTF.MeshUtility { public class MeshUtility { public const string ASSET_SUFFIX = ".mesh.asset"; private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero; public static Object GetPrefab(GameObject instance) { #if UNITY_2018_2_OR_NEWER return PrefabUtility.GetCorrespondingObjectFromSource(instance); #else return PrefabUtility.GetPrefabParent(go); #endif } private enum BlendShapeLogic { WithBlendShape, WithoutBlendShape, } public static void SeparationProcessing(GameObject go) { var outputObject = GameObject.Instantiate(go); outputObject.name = outputObject.name + "_mesh_separation"; var skinnedMeshRenderers = outputObject.GetComponentsInChildren(); foreach (var skinnedMeshRenderer in skinnedMeshRenderers) { if (skinnedMeshRenderer.sharedMesh.blendShapeCount > 0) { SeparatePolyWithBlendShape(skinnedMeshRenderer); } } } private static void SeparatePolyWithBlendShape(SkinnedMeshRenderer skinnedMeshRendererInput) { var indicesUsedByBlendShape = new Dictionary(); var mesh = skinnedMeshRendererInput.sharedMesh; // retrieve the original BlendShape data for (int i = 0; i < mesh.blendShapeCount; ++i) { var deltaVertices = new Vector3[mesh.vertexCount]; var deltaNormals = new Vector3[mesh.vertexCount]; var deltaTangents = new Vector3[mesh.vertexCount]; mesh.GetBlendShapeFrameVertices(i, 0, deltaVertices, deltaNormals, deltaTangents); for (int j = 0; j < deltaVertices.Length; j++) { if (!deltaVertices[j].Equals(ZERO_MOVEMENT)) { if (!indicesUsedByBlendShape.Values.Contains(j)) { indicesUsedByBlendShape.Add(indicesUsedByBlendShape.Count, j); } } } } var subMeshCount = mesh.subMeshCount; var submeshesWithBlendShape = new Dictionary(); var submeshesWithoutBlendShape = new Dictionary(); var vertexIndexWithBlendShape = new Dictionary(); var vertexCounterWithBlendShape = 0; var vertexIndexWithoutBlendShape = new Dictionary(); var vertexCounterWithoutBlendShape = 0; // check blendshape's vertex index from submesh for (int i = 0; i < subMeshCount; i++) { var triangle = mesh.GetTriangles(i); var submeshWithBlendShape = new List(); var submeshWithoutBlendShape = new List(); for (int j = 0; j < triangle.Length; j += 3) { if (indicesUsedByBlendShape.Values.Contains(triangle[j]) || indicesUsedByBlendShape.Values.Contains(triangle[j + 1]) || indicesUsedByBlendShape.Values.Contains(triangle[j + 2])) { BuildNewTriangleList(vertexIndexWithBlendShape, triangle, j, submeshWithBlendShape, ref vertexCounterWithBlendShape); } else { BuildNewTriangleList(vertexIndexWithoutBlendShape, triangle, j, submeshWithoutBlendShape, ref vertexCounterWithoutBlendShape); } } if (submeshWithBlendShape.Count > 0) submeshesWithBlendShape.Add(i, submeshWithBlendShape.ToArray()); if (submeshWithoutBlendShape.Count > 0) submeshesWithoutBlendShape.Add(i, submeshWithoutBlendShape.ToArray()); ; } // check if any BlendShape exists if (submeshesWithoutBlendShape.Count > 0) { // put the mesh without BlendShape in a new SkinnedMeshRenderer var srcGameObject = skinnedMeshRendererInput.gameObject; var srcTransform = skinnedMeshRendererInput.transform.parent; var targetObjectForMeshWithoutBS = GameObject.Instantiate(srcGameObject); targetObjectForMeshWithoutBS.name = srcGameObject.name + "_WithoutBlendShape"; targetObjectForMeshWithoutBS.transform.SetParent(srcTransform); var skinnedMeshRendererWithoutBS = targetObjectForMeshWithoutBS.GetComponent(); // build meshes with/without BlendShape BuildNewMesh(skinnedMeshRendererInput, vertexIndexWithBlendShape, submeshesWithBlendShape, BlendShapeLogic.WithBlendShape); BuildNewMesh(skinnedMeshRendererWithoutBS, vertexIndexWithoutBlendShape, submeshesWithoutBlendShape, BlendShapeLogic.WithoutBlendShape); } } private static void BuildNewTriangleList(Dictionary newVerticesListLookUp, int[] triangleList, int index, List newTriangleList, ref int vertexCounter) { // build new vertex list and triangle list // vertex 1 if (!newVerticesListLookUp.Keys.Contains(triangleList[index])) { newVerticesListLookUp.Add(triangleList[index], vertexCounter); newTriangleList.Add(vertexCounter); vertexCounter++; } else { var newVertexIndex = newVerticesListLookUp[triangleList[index]]; newTriangleList.Add(newVertexIndex); } // vertex 2 if (!newVerticesListLookUp.Keys.Contains(triangleList[index + 1])) { newVerticesListLookUp.Add(triangleList[index + 1], vertexCounter); newTriangleList.Add(vertexCounter); vertexCounter++; } else { var newVertexIndex = newVerticesListLookUp[triangleList[index + 1]]; newTriangleList.Add(newVertexIndex); } // vertex 3 if (!newVerticesListLookUp.Keys.Contains(triangleList[index + 2])) { newVerticesListLookUp.Add(triangleList[index + 2], vertexCounter); newTriangleList.Add(vertexCounter); vertexCounter++; } else { var newVertexIndex = newVerticesListLookUp[triangleList[index + 2]]; newTriangleList.Add(newVertexIndex); } } private static void BuildNewMesh(SkinnedMeshRenderer skinnedMeshRenderer, Dictionary newIndexLookUpDict, Dictionary subMeshes, BlendShapeLogic blendShapeLabel) { // get original mesh data var materialList = new List(); skinnedMeshRenderer.GetSharedMaterials(materialList); var mesh = skinnedMeshRenderer.sharedMesh; var meshVertices = mesh.vertices; var meshNormals = mesh.normals; var meshTangents = mesh.tangents; var meshColors = mesh.colors; var meshBoneWeights = mesh.boneWeights; var meshUVs = mesh.uv; // build new mesh var materialListNew = new List(); var newMesh = new Mesh(); if (mesh.vertexCount > ushort.MaxValue) { #if UNITY_2017_3_OR_NEWER Debug.LogFormat("exceed 65535 vertices: {0}", mesh.vertexCount); newMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; #else throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString())); #endif } var newDataLength = newIndexLookUpDict.Count; var newIndexLookUp = newIndexLookUpDict.Keys.ToArray(); newMesh.vertices = newIndexLookUp.Select(x => meshVertices[x]).ToArray(); if (meshNormals.Length > 0) newMesh.normals = newIndexLookUp.Select(x => meshNormals[x]).ToArray(); if (meshTangents.Length > 0) newMesh.tangents = newIndexLookUp.Select(x => meshTangents[x]).ToArray(); if (meshColors.Length > 0) newMesh.colors = newIndexLookUp.Select(x => meshColors[x]).ToArray(); if (meshBoneWeights.Length > 0) newMesh.boneWeights = newIndexLookUp.Select(x => meshBoneWeights[x]).ToArray(); if (meshUVs.Length > 0) newMesh.uv = newIndexLookUp.Select(x => meshUVs[x]).ToArray(); newMesh.bindposes = mesh.bindposes; // add BlendShape data if (blendShapeLabel == BlendShapeLogic.WithBlendShape) { for (int i = 0; i < mesh.blendShapeCount; i++) { // get original BlendShape data var srcVertices = new Vector3[mesh.vertexCount]; var srcNormals = new Vector3[mesh.vertexCount]; var srcTangents = new Vector3[mesh.vertexCount]; mesh.GetBlendShapeFrameVertices(i, 0, srcVertices, srcNormals, srcTangents); // declare the size for the destination array var dstVertices = new Vector3[newDataLength]; var dstNormals = new Vector3[newDataLength]; var dstTangents = new Vector3[newDataLength]; dstVertices = newIndexLookUp.Select(x => srcVertices[x]).ToArray(); dstNormals = newIndexLookUp.Select(x => srcNormals[x]).ToArray(); dstTangents = newIndexLookUp.Select(x => srcTangents[x]).ToArray(); newMesh.AddBlendShapeFrame(mesh.GetBlendShapeName(i), mesh.GetBlendShapeFrameWeight(i, 0), dstVertices, dstNormals, dstTangents); } } newMesh.subMeshCount = subMeshes.Count; var cosMaterialIndex = subMeshes.Keys.ToArray(); // build material list for (int i = 0; i < subMeshes.Count; i++) { newMesh.SetTriangles(subMeshes[cosMaterialIndex[i]], i); materialListNew.Add(materialList[cosMaterialIndex[i]]); } skinnedMeshRenderer.sharedMaterials = materialListNew.ToArray(); skinnedMeshRenderer.sharedMesh = newMesh; // save mesh as asset var assetPath = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX); Debug.Log(assetPath); if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh)))) { var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/"); assetPath = string.Format("{0}/{1}{2}", directory, Path.GetFileNameWithoutExtension(mesh.name) + "_" + blendShapeLabel.ToString(), ASSET_SUFFIX); } else { assetPath = string.Format("Assets/{0}{1}", Path.GetFileNameWithoutExtension(mesh.name) + "_" + blendShapeLabel.ToString(), ASSET_SUFFIX); } Debug.LogFormat("CreateAsset: {0}", assetPath); AssetDatabase.CreateAsset(newMesh, assetPath); } #if UNITY_EDITOR public static bool IsGameObjectSelected() { return Selection.activeObject != null && Selection.activeObject is GameObject; } public static void IntegrateSelected(GameObject go) { var meshWithMaterials = StaticMeshIntegrator.Integrate(go.transform); // save as asset var assetPath = ""; #if UNITY_2018_2_OR_NEWER var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go); #else var prefab = PrefabUtility.GetPrefabParent(go); #endif if (prefab != null) { var prefabPath = AssetDatabase.GetAssetPath(prefab); assetPath = string.Format("{0}/{1}_{2}{3}", Path.GetDirectoryName(prefabPath), Path.GetFileNameWithoutExtension(prefabPath), go.name, ASSET_SUFFIX ); } else { var path = EditorUtility.SaveFilePanel( "Save mesh", "Assets", go.name + ".asset", "asset"); if (string.IsNullOrEmpty(path)) { return; } assetPath = UnityPath.FromFullpath(path).Value; } assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath); Debug.LogFormat("CreateAsset: {0}", assetPath); AssetDatabase.CreateAsset(meshWithMaterials.Mesh, assetPath); // add component var meshObject = new GameObject(go.name + ".static_meshes_integrated"); if (go.transform.parent != null) { meshObject.transform.SetParent(go.transform.parent, false); } meshObject.transform.localPosition = go.transform.localPosition; meshObject.transform.localRotation = go.transform.localRotation; meshObject.transform.localScale = go.transform.localScale; var filter = meshObject.AddComponent(); filter.sharedMesh = meshWithMaterials.Mesh; var renderer = meshObject.AddComponent(); renderer.sharedMaterials = meshWithMaterials.Materials; } #endif public static void MeshIntegrator(GameObject go) { MeshIntegratorUtility.Integrate(go, onlyBlendShapeRenderers: true); MeshIntegratorUtility.Integrate(go, onlyBlendShapeRenderers: false); var outputObject = GameObject.Instantiate(go); outputObject.name = outputObject.name + "_mesh_integration"; var skinnedMeshes = outputObject.GetComponentsInChildren(); var normalMeshes = outputObject.GetComponentsInChildren(); // destroy integrated meshes in the source foreach (var skinnedMesh in go.GetComponentsInChildren()) { if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME || skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME) { GameObject.DestroyImmediate(skinnedMesh.gameObject); } } foreach (var skinnedMesh in skinnedMeshes) { // destroy original meshes in the copied GameObject if (!(skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME || skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME)) { GameObject.DestroyImmediate(skinnedMesh); } // check if the integrated mesh is empty else if (skinnedMesh.sharedMesh.subMeshCount == 0) { GameObject.DestroyImmediate(skinnedMesh.gameObject); } // save mesh data else if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME || skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME) { SaveMeshData(skinnedMesh.sharedMesh); } } foreach (var normalMesh in normalMeshes) { if (normalMesh.sharedMesh.name != MeshIntegratorUtility.INTEGRATED_MESH_NAME) { if (normalMesh.gameObject.GetComponent()) { GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent()); } GameObject.DestroyImmediate(normalMesh); } } } private static void SaveMeshData(Mesh mesh) { var assetPath = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX); Debug.Log(assetPath); if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh)))) { var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/"); assetPath = string.Format("{0}/{1}{2}", directory, Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX); } else { assetPath = string.Format("Assets/{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX); } Debug.LogFormat("CreateAsset: {0}", assetPath); AssetDatabase.CreateAsset(mesh, assetPath); } } }