diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs deleted file mode 100644 index 897bfad0e..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs +++ /dev/null @@ -1,19 +0,0 @@ -using UnityEditor; -using UnityEngine; - -namespace UniGLTF.MeshUtility -{ - [CustomEditor(typeof(MeshProcessDialog), true)] - public class BoneMeshEraserGUI : Editor - { - public override void OnInspectorGUI() - { - serializedObject.Update(); - var skinnedMesh = serializedObject.FindProperty("_cSkinnedMesh"); - EditorGUILayout.PropertyField(skinnedMesh, new GUIContent("Skinned Mesh"), true); - var list = serializedObject.FindProperty("_eraseBones"); - EditorGUILayout.PropertyField(list, new GUIContent("Erase Bones"), true); - serializedObject.ApplyModifiedProperties(); - } - } -} diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs index a3650f74a..4e0c71201 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs @@ -138,6 +138,15 @@ namespace UniGLTF.MeshUtility return erased; } + public static UnityEngine.Object GetPrefab(GameObject instance) + { +#if UNITY_2018_2_OR_NEWER + return PrefabUtility.GetCorrespondingObjectFromSource(instance); +#else + return PrefabUtility.GetPrefabParent(go); +#endif + } + void Erase() { var go = Selection.activeGameObject; @@ -149,7 +158,7 @@ namespace UniGLTF.MeshUtility // save mesh to Assets var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX); - var prefab = MeshUtility.GetPrefab(go); + var prefab = GetPrefab(go); if (prefab != null) { var prefabPath = AssetDatabase.GetAssetPath(prefab); diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs index d7c352b6c..a5a4c97e7 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs @@ -9,17 +9,12 @@ namespace UniGLTF.MeshUtility { public class MeshProcessDialog : EditorWindow { - enum Tabs - { - MeshSeparator, - MeshIntegrator, - // StaticMeshIntegrator, - BoneMeshEraser, - } private Tabs _tab; private GameObject _exportTarget; - private Editor _boneMeshEraserEditor; + + private MeshProcessDialogEditor _boneMeshEraserEditor; + private SkinnedMeshRenderer _pSkinnedMesh; private Animator _pAnimator; private Transform _pEraseRoot; @@ -28,6 +23,9 @@ namespace UniGLTF.MeshUtility [SerializeField] private SkinnedMeshRenderer _cSkinnedMesh = null; + [SerializeField] + private bool _separateByBlendShape = true; + private Animator _cAnimator = null; private Transform _cEraseRoot = null; @@ -52,14 +50,19 @@ namespace UniGLTF.MeshUtility { if (!_boneMeshEraserEditor) { - _boneMeshEraserEditor = Editor.CreateEditor(this); + _boneMeshEraserEditor = (MeshProcessDialogEditor)Editor.CreateEditor(this); } } + static bool IsGameObjectSelected() + { + return Selection.activeObject != null && Selection.activeObject is GameObject; + } + private void OnGUI() { _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); - EditorGUIUtility.labelWidth = 150; + EditorGUIUtility.labelWidth = 300; // lang LanguageGetter.OnGuiSelectLang(); @@ -68,7 +71,7 @@ namespace UniGLTF.MeshUtility EditorGUILayout.LabelField(MeshProcessingMessages.TARGET_OBJECT.Msg(), GUILayout.MaxWidth(146.0f)); _exportTarget = (GameObject)EditorGUILayout.ObjectField(_exportTarget, typeof(GameObject), true); EditorGUILayout.EndHorizontal(); - if (_exportTarget == null && MeshUtility.IsGameObjectSelected()) + if (_exportTarget == null && IsGameObjectSelected()) { _exportTarget = Selection.activeObject as GameObject; } @@ -86,17 +89,17 @@ namespace UniGLTF.MeshUtility case Tabs.MeshIntegrator: EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info); - processed = TabMeshIntegrator.OnGUI(_exportTarget); + _boneMeshEraserEditor.Tabs = _tab; + if (_boneMeshEraserEditor) + { + _boneMeshEraserEditor.OnInspectorGUI(); + } + processed = TabMeshIntegrator.OnGUI(_exportTarget, _separateByBlendShape); break; - // MeshIntegrator と機能が重複しているのと正常に動作しなかった - // case Tabs.StaticMeshIntegrator: - // EditorGUILayout.HelpBox(MeshProcessingMessages.STATIC_MESH_INTEGRATOR.Msg(), MessageType.Info); - // processed = TabStaticMeshIntegrator.OnGUI(_exportTarget); - // break; - case Tabs.BoneMeshEraser: EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info); + _boneMeshEraserEditor.Tabs = _tab; if (_boneMeshEraserEditor) { _boneMeshEraserEditor.OnInspectorGUI(); diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs new file mode 100644 index 000000000..db7929a0a --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs @@ -0,0 +1,36 @@ +using UniGLTF.M17N; +using UnityEditor; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + [CustomEditor(typeof(MeshProcessDialog), true)] + class MeshProcessDialogEditor : Editor + { + public Tabs Tabs; + + public override void OnInspectorGUI() + { + serializedObject.Update(); + switch (Tabs) + { + case Tabs.MeshIntegrator: + { + var skinnedMesh = serializedObject.FindProperty("_separateByBlendShape"); + EditorGUILayout.PropertyField(skinnedMesh, new GUIContent(MeshProcessingMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg())); + break; + } + + case Tabs.BoneMeshEraser: + { + var skinnedMesh = serializedObject.FindProperty("_cSkinnedMesh"); + EditorGUILayout.PropertyField(skinnedMesh, new GUIContent("Skinned Mesh"), true); + var list = serializedObject.FindProperty("_eraseBones"); + EditorGUILayout.PropertyField(list, new GUIContent("Erase Bones"), true); + break; + } + } + serializedObject.ApplyModifiedProperties(); + } + } +} diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta similarity index 83% rename from Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs.meta rename to Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta index 4c0e30384..de1678682 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs.meta +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 9701e1f6aaed8d14283243a7a50be2df +guid: 04fa1873cf1c0374aab1670a24d741b5 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs index 3fe4dcfdb..3b63cc538 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs @@ -20,17 +20,22 @@ namespace UniGLTF.MeshUtility ")] MESH_SEPARATOR, - [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュをを統合します。BlendShape の有無で2つ作成されます。 + + [LangMsg(Languages.ja, "ブレンドシェイプの有無で分割する")] + [LangMsg(Languages.en, "Divide by the presence or absence of `blendshape`")] + MESH_SEPARATOR_BY_BLENDSHAPE, + + [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュを統合します。 * Asset: Assets/MeshIntegrated.mesh が作成されます(上書きされるので注意してください)。 * Scene: コピーされたヒエラルキーでは、統合された Mesh は除去されます。新しく MeshIntegrator ノードが追加されます。 -* VRMではBlendShapeClipの統合など追加の処理が必要でる。VRMの統合機能を使ってください。 +* VRMではBlendShapeClipの統合など追加の処理が必要です。`VRM0-MeshIntegratorWizard` を使ってください。 ")] - [LangMsg(Languages.en, @"Integrates the attached mesh into the SkinnedMeshRenderer or MeshFilter under the target object. A mesh that holds BlendShape and a mesh that does not hold BlendShape are created. + [LangMsg(Languages.en, @"Integrates the attached mesh into the SkinnedMeshRenderer or MeshFilter under the target object. * Asset: Assets/MeshIntegrated.mesh is created (note that it will be overwritten). * Scene: In the copied hierarchy, the integrated mesh is removed. A new MeshIntegrator node is added. -* VRM requires additional processing such as BlendShapeClip integration. Use the VRM integration feature. +* VRM requires additional processing such as BlendShapeClip integration. Use the `VRM0-MeshIntegratorWizard` integration feature. ")] MESH_INTEGRATOR, diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs deleted file mode 100644 index fd45864d8..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs +++ /dev/null @@ -1,419 +0,0 @@ -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 - } - - public enum BlendShapeLogic - { - WithBlendShape, - WithoutBlendShape, - } - - /// - /// 対象のヒエラルキーに含まれるすべての SkinnedMeshRenderer に対して、 - /// BlendShape を含む Mesh と 含まない Mesh への分割を実施する。 - /// - /// 各 SkinnedMeshRenderer(smr) は、 - /// - /// smr - mesh(with blendshape) - /// + smr(without) - mesh(without blendshape) - /// - /// のように変化する。 - /// - /// - /// (Mesh 分割前, Mesh BlendShape有り、Mesh BlendShape無し)のリストを返す - public static List<(Mesh Src, Mesh With, Mesh Without)> SeparationProcessing(GameObject go) - { - var list = new List<(Mesh Src, Mesh With, Mesh Without)>(); - var skinnedMeshRenderers = go.GetComponentsInChildren(); - foreach (var skinnedMeshRenderer in skinnedMeshRenderers) - { - if (skinnedMeshRenderer.sharedMesh.blendShapeCount > 0) - { - var (mesh, with, without) = SeparatePolyWithBlendShape(skinnedMeshRenderer); - if (mesh != null) - { - list.Add((mesh, with, without)); - } - } - } - return list; - } - - private static (Mesh mesh, Mesh With, Mesh Without) 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 - var with = BuildNewMesh(skinnedMeshRendererInput, vertexIndexWithBlendShape, submeshesWithBlendShape, BlendShapeLogic.WithBlendShape); - var without = BuildNewMesh(skinnedMeshRendererWithoutBS, vertexIndexWithoutBlendShape, submeshesWithoutBlendShape, BlendShapeLogic.WithoutBlendShape); - return (mesh, with, without); - } - else - { - return default; - } - } - - 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 Mesh 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; - - return newMesh; - } - -#if UNITY_EDITOR - public static bool IsGameObjectSelected() - { - return Selection.activeObject != null && Selection.activeObject is GameObject; - } - - /// - /// from dialog - /// - /// - 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 - - /// - /// from dialog - /// - /// - 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); - } - } - } - - public static void SaveMesh(Mesh mesh, Mesh newMesh, BlendShapeLogic blendShapeLabel) - { - // 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); - } - - 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); - } - } -} \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs index c0dc39b59..812fd8a52 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs @@ -9,7 +9,9 @@ namespace UniGLTF.MeshUtility { public static class TabBoneMeshRemover { - public static bool OnGUI(GameObject _exportTarget, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones) + const string ASSET_SUFFIX = ".mesh.asset"; + + public static bool OnGUI(GameObject root, SkinnedMeshRenderer smr, BoneMeshEraser.EraseBone[] eraseBones) { var _isInvokeSuccess = false; GUILayout.BeginVertical(); @@ -18,7 +20,7 @@ namespace UniGLTF.MeshUtility GUILayout.FlexibleSpace(); if (GUILayout.Button("Process", GUILayout.MinWidth(100))) { - _isInvokeSuccess = TabBoneMeshRemover.Execute(_exportTarget, _cSkinnedMesh, _eraseBones); + _isInvokeSuccess = TabBoneMeshRemover.Execute(root, smr, eraseBones); } GUILayout.EndHorizontal(); } @@ -26,86 +28,79 @@ namespace UniGLTF.MeshUtility return _isInvokeSuccess; } - public static bool Execute(GameObject _exportTarget, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones) + private static bool Execute(GameObject root, SkinnedMeshRenderer smr, BoneMeshEraser.EraseBone[] eraseBones) { - if (_exportTarget == null) + if (root == null) { EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); return false; } - var go = _exportTarget; - if (_cSkinnedMesh == null) + if (smr == null) { EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(), "ok"); return false; } - BoneMeshRemove(go, _cSkinnedMesh, _eraseBones); - return true; - } + var bones = smr.bones; + + var meshNode = new GameObject(BoneMeshEraserWizard.BONE_MESH_ERASER_NAME); + meshNode.transform.SetParent(root.transform, false); + + var erased = meshNode.AddComponent(); + erased.sharedMesh = BoneMeshEraser.CreateErasedMesh(smr.sharedMesh, eraseBones + .Where(x => x.Erase) + .Select(x => Array.IndexOf(bones, x.Bone)) + .ToArray()); + erased.sharedMaterials = smr.sharedMaterials; + erased.bones = smr.bones; - private static void BoneMeshRemove(GameObject go, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones) - { - var renderer = Remove(go, _cSkinnedMesh, _eraseBones); - var outputObject = GameObject.Instantiate(go); - outputObject.name = outputObject.name + "_bone_mesh_erase"; - if (renderer == null) - { - return; - } // save mesh to Assets - var assetPath = string.Format("{0}{1}", go.name, MeshUtility.ASSET_SUFFIX); - var prefab = MeshUtility.GetPrefab(go); + var assetPath = string.Format("{0}{1}", root.name, ASSET_SUFFIX); + var prefab = GetPrefab(root); if (prefab != null) { var prefabPath = AssetDatabase.GetAssetPath(prefab); assetPath = string.Format("{0}/{1}{2}", Path.GetDirectoryName(prefabPath), Path.GetFileNameWithoutExtension(prefabPath), - MeshUtility.ASSET_SUFFIX + ASSET_SUFFIX ); } - Debug.LogFormat("CreateAsset: {0}", assetPath); - AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath); + AssetDatabase.CreateAsset(erased.sharedMesh, assetPath); // destroy BoneMeshEraser in the source - foreach (var skinnedMesh in go.GetComponentsInChildren()) + foreach (var skinnedMesh in root.GetComponentsInChildren()) { if (skinnedMesh.gameObject.name == BoneMeshEraserWizard.BONE_MESH_ERASER_NAME) { GameObject.DestroyImmediate(skinnedMesh.gameObject); } } + // destroy the original mesh in the copied GameObject + var outputObject = GameObject.Instantiate(root); + outputObject.name = outputObject.name + "_bone_mesh_erase"; foreach (var skinnedMesh in outputObject.GetComponentsInChildren()) { - if (skinnedMesh.sharedMesh == _cSkinnedMesh.sharedMesh) + if (skinnedMesh.sharedMesh == smr.sharedMesh) { GameObject.DestroyImmediate(skinnedMesh); } } + + return true; } - private static SkinnedMeshRenderer Remove(GameObject go, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones) + public static UnityEngine.Object GetPrefab(GameObject instance) { - var bones = _cSkinnedMesh.bones; - var eraseBones = _eraseBones - .Where(x => x.Erase) - .Select(x => Array.IndexOf(bones, x.Bone)) - .ToArray(); - - var meshNode = new GameObject(BoneMeshEraserWizard.BONE_MESH_ERASER_NAME); - meshNode.transform.SetParent(go.transform, false); - - var erased = meshNode.AddComponent(); - erased.sharedMesh = BoneMeshEraser.CreateErasedMesh(_cSkinnedMesh.sharedMesh, eraseBones); - erased.sharedMaterials = _cSkinnedMesh.sharedMaterials; - erased.bones = _cSkinnedMesh.bones; - - return erased; +#if UNITY_2018_2_OR_NEWER + return PrefabUtility.GetCorrespondingObjectFromSource(instance); +#else + return PrefabUtility.GetPrefabParent(go); +#endif } } } \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs index 839d425a8..ed366f531 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs @@ -1,3 +1,4 @@ +using System.IO; using UniGLTF.M17N; using UnityEditor; using UnityEngine; @@ -6,7 +7,9 @@ namespace UniGLTF.MeshUtility { public static class TabMeshIntegrator { - public static bool OnGUI(GameObject _exportTarget) + const string ASSET_SUFFIX = ".mesh.asset"; + + public static bool OnGUI(GameObject root, bool onlyBlendShapeRenderers) { var _isInvokeSuccess = false; GUILayout.BeginVertical(); @@ -15,7 +18,7 @@ namespace UniGLTF.MeshUtility GUILayout.FlexibleSpace(); if (GUILayout.Button("Process", GUILayout.MinWidth(100))) { - _isInvokeSuccess = TabMeshIntegrator.Execute(_exportTarget); + _isInvokeSuccess = TabMeshIntegrator.Execute(root, onlyBlendShapeRenderers); } GUILayout.EndHorizontal(); } @@ -23,10 +26,10 @@ namespace UniGLTF.MeshUtility return _isInvokeSuccess; } - static string VRM_META = "VRMMeta"; - static bool HasVrm(GameObject go) + const string VRM_META = "VRMMeta"; + static bool HasVrm(GameObject root) { - var allComponents = go.GetComponents(typeof(Component)); + var allComponents = root.GetComponents(typeof(Component)); foreach (var component in allComponents) { if (component == null) continue; @@ -39,29 +42,107 @@ namespace UniGLTF.MeshUtility return false; } - static bool Execute(GameObject _exportTarget) + static bool Execute(GameObject root, bool onlyBlendShapeRenderers) { - if (_exportTarget == null) + if (root == null) { EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); return false; } - var go = _exportTarget; - if (HasVrm(go)) + if (HasVrm(root)) { EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.VRM_DETECTED.Msg(), "ok"); return false; } - if (go.GetComponentsInChildren().Length == 0 && go.GetComponentsInChildren().Length == 0) + if (root.GetComponentsInChildren().Length == 0 && root.GetComponentsInChildren().Length == 0) { EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_MESH.Msg(), "ok"); return false; } - MeshUtility.MeshIntegrator(go); + if (onlyBlendShapeRenderers) + { + MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape); + MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape); + } + else + { + MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All); + } + + CopyAndSaveAssetEtc(root); + return true; } + + static void CopyAndSaveAssetEtc(GameObject root) + { + // copy hierarchy + var outputObject = GameObject.Instantiate(root); + outputObject.name = outputObject.name + "_mesh_integration"; + var skinnedMeshes = outputObject.GetComponentsInChildren(); + + // destroy integrated meshes in the source + foreach (var skinnedMesh in root.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); + } + } + + var normalMeshes = outputObject.GetComponentsInChildren(); + 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); + } + } + } + + 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); + } } } diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs index bef557520..6e8caf398 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; using UniGLTF.M17N; using UnityEditor; using UnityEngine; @@ -9,7 +12,16 @@ namespace UniGLTF.MeshUtility /// public static class TabMeshSeparator { - public static bool OnGUI(GameObject _exportTarget) + private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero; + const string ASSET_SUFFIX = ".mesh.asset"; + + enum BlendShapeLogic + { + WithBlendShape, + WithoutBlendShape, + } + + public static bool OnGUI(GameObject root) { var _isInvokeSuccess = false; GUILayout.BeginVertical(); @@ -18,7 +30,7 @@ namespace UniGLTF.MeshUtility GUILayout.FlexibleSpace(); if (GUILayout.Button("Process", GUILayout.MinWidth(100))) { - _isInvokeSuccess = TabMeshSeparator.Execute(_exportTarget); + _isInvokeSuccess = TabMeshSeparator.Execute(root); } GUILayout.EndHorizontal(); } @@ -26,34 +38,283 @@ namespace UniGLTF.MeshUtility return _isInvokeSuccess; } - static bool Execute(GameObject _exportTarget) + static bool Execute(GameObject root) { - if (_exportTarget == null) + if (root == null) { EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); return false; } - var go = _exportTarget; - if (go.GetComponentsInChildren().Length == 0) + if (root.GetComponentsInChildren().Length == 0) { EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_SKINNED_MESH.Msg(), "ok"); return false; } // copy - var outputObject = GameObject.Instantiate(go); + var outputObject = GameObject.Instantiate(root); outputObject.name = outputObject.name + "_mesh_separation"; // 改変と asset の作成 - var list = MeshUtility.SeparationProcessing(outputObject); + var list = SeparationProcessing(outputObject); foreach (var (src, with, without) in list) { // asset の永続化 - MeshUtility.SaveMesh(src, with, MeshUtility.BlendShapeLogic.WithBlendShape); - MeshUtility.SaveMesh(src, without, MeshUtility.BlendShapeLogic.WithoutBlendShape); + SaveMesh(src, with, BlendShapeLogic.WithBlendShape); + SaveMesh(src, without, BlendShapeLogic.WithoutBlendShape); } return true; } + + static void SaveMesh(Mesh mesh, Mesh newMesh, BlendShapeLogic blendShapeLabel) + { + // 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); + } + + /// + /// 対象のヒエラルキーに含まれるすべての SkinnedMeshRenderer に対して、 + /// BlendShape を含む Mesh と 含まない Mesh への分割を実施する。 + /// + /// 各 SkinnedMeshRenderer(smr) は、 + /// + /// smr - mesh(with blendshape) + /// + smr(without) - mesh(without blendshape) + /// + /// のように変化する。 + /// + /// + /// (Mesh 分割前, Mesh BlendShape有り、Mesh BlendShape無し)のリストを返す + public static List<(Mesh Src, Mesh With, Mesh Without)> SeparationProcessing(GameObject go) + { + var list = new List<(Mesh Src, Mesh With, Mesh Without)>(); + var skinnedMeshRenderers = go.GetComponentsInChildren(); + foreach (var skinnedMeshRenderer in skinnedMeshRenderers) + { + if (skinnedMeshRenderer.sharedMesh.blendShapeCount > 0) + { + var (mesh, with, without) = SeparatePolyWithBlendShape(skinnedMeshRenderer); + if (mesh != null) + { + list.Add((mesh, with, without)); + } + } + } + return list; + } + + private static (Mesh mesh, Mesh With, Mesh Without) 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 + var with = BuildNewMesh(skinnedMeshRendererInput, vertexIndexWithBlendShape, submeshesWithBlendShape, BlendShapeLogic.WithBlendShape); + var without = BuildNewMesh(skinnedMeshRendererWithoutBS, vertexIndexWithoutBlendShape, submeshesWithoutBlendShape, BlendShapeLogic.WithoutBlendShape); + return (mesh, with, without); + } + else + { + return default; + } + } + + 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 Mesh 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; + + return newMesh; + } } } diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs deleted file mode 100644 index 9cdfd3e3f..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs +++ /dev/null @@ -1,45 +0,0 @@ -using UniGLTF.M17N; -using UnityEditor; -using UnityEngine; - -namespace UniGLTF.MeshUtility -{ - public static class TabStaticMeshIntegrator - { - public static bool OnGUI(GameObject _exportTarget) - { - var _isInvokeSuccess = false; - GUILayout.BeginVertical(); - { - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("Process", GUILayout.MinWidth(100))) - { - _isInvokeSuccess = TabStaticMeshIntegrator.Execute(_exportTarget); - } - GUILayout.EndHorizontal(); - } - GUILayout.EndVertical(); - return _isInvokeSuccess; - } - - static bool Execute(GameObject _exportTarget) - { - if (_exportTarget == null) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); - return false; - } - var go = _exportTarget; - - if (go.GetComponentsInChildren().Length == 0) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_STATIC_MESH.Msg(), "ok"); - return false; - } - - MeshUtility.IntegrateSelected(go); - return true; - } - } -} diff --git a/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs new file mode 100644 index 000000000..dbc064734 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs @@ -0,0 +1,9 @@ +namespace UniGLTF.MeshUtility +{ + enum Tabs + { + MeshSeparator, + MeshIntegrator, + BoneMeshEraser, + } +} diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta similarity index 83% rename from Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta rename to Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta index eddf8f87d..485c04f70 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta +++ b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 5cdbec3de528c61448aeb739ffe1f79e +guid: 57ace5e28eb8e7746b00eaa9405197a9 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs new file mode 100644 index 000000000..a4021e5a7 --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs @@ -0,0 +1,9 @@ +namespace UniGLTF.MeshUtility +{ + public enum MeshEnumerateOption + { + OnlyWithBlendShape, + OnlyWithoutBlendShape, + All, + } +} diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs.meta similarity index 83% rename from Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs.meta rename to Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs.meta index 2fb0aadd1..566aaa57a 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs.meta +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c1bbe850b95e44740bbbb44064e17d25 +guid: 862f202d0be779744a4954a77cb88a89 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs index 569fa4bdf..4e2db9cdb 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs @@ -230,7 +230,7 @@ namespace UniGLTF.MeshUtility } } - public void Intgrate(bool onlyBlendShapeRenderers) + public void Intgrate(MeshEnumerateOption onlyBlendShapeRenderers) { var mesh = new Mesh(); @@ -252,24 +252,36 @@ namespace UniGLTF.MeshUtility } mesh.bindposes = BindPoses.ToArray(); - if (onlyBlendShapeRenderers) + switch (onlyBlendShapeRenderers) { - AddBlendShapesToMesh(mesh); - mesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME; - } - else - { - mesh.name = INTEGRATED_MESH_NAME; + case MeshEnumerateOption.OnlyWithBlendShape: + { + AddBlendShapesToMesh(mesh); + mesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME; + break; + } + case MeshEnumerateOption.OnlyWithoutBlendShape: + case MeshEnumerateOption.All: + { + mesh.name = INTEGRATED_MESH_NAME; + break; + } } var meshNode = new GameObject(); - if (onlyBlendShapeRenderers) + switch (onlyBlendShapeRenderers) { - meshNode.name = "MeshIntegrator(BlendShape)"; - } - else - { - meshNode.name = "MeshIntegrator"; + case MeshEnumerateOption.OnlyWithBlendShape: + { + meshNode.name = "MeshIntegrator(BlendShape)"; + break; + } + case MeshEnumerateOption.OnlyWithoutBlendShape: + case MeshEnumerateOption.All: + { + meshNode.name = "MeshIntegrator"; + break; + } } var integrated = meshNode.AddComponent(); diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs index 48bffe5fe..87f44f189 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs @@ -8,48 +8,83 @@ namespace UniGLTF.MeshUtility public static string INTEGRATED_MESH_NAME => MeshIntegrator.INTEGRATED_MESH_NAME; public static string INTEGRATED_MESH_BLENDSHAPE_NAME => MeshIntegrator.INTEGRATED_MESH_BLENDSHAPE_NAME; + /// /// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する /// /// - /// BlendShapeを保持するSkinnedMeshRendererのみ/BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer + /// + /// true: BlendShapeを保持するSkinnedMeshRendererのみ + /// false: BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer + /// null: すべてのSkinnedMeshRenderer + MeshRenderer + /// /// - public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers, IEnumerable excludes = null) + public static MeshIntegrationResult Integrate(GameObject go, MeshEnumerateOption onlyBlendShapeRenderers, IEnumerable excludes = null) { var exclude = new MeshExclude(excludes); var integrator = new MeshUtility.MeshIntegrator(); - if (onlyBlendShapeRenderers) + switch (onlyBlendShapeRenderers) { - foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, true)) - { - if (exclude.IsExcluded(x)) + case MeshEnumerateOption.OnlyWithBlendShape: { - continue; + foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers)) + { + if (exclude.IsExcluded(x)) + { + continue; + } + integrator.Push(x); + } + break; } - integrator.Push(x); - } - } - else - { - foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, false)) - { - if (exclude.IsExcluded(x)) - { - continue; - } - integrator.Push(x); - } - foreach (var x in EnumerateMeshRenderer(go.transform)) - { - if (exclude.IsExcluded(x)) + case MeshEnumerateOption.OnlyWithoutBlendShape: { - continue; + foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers)) + { + if (exclude.IsExcluded(x)) + { + continue; + } + integrator.Push(x); + } + + foreach (var x in EnumerateMeshRenderer(go.transform)) + { + if (exclude.IsExcluded(x)) + { + continue; + } + integrator.Push(x); + } + + break; + } + + case MeshEnumerateOption.All: + { + foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers)) + { + if (exclude.IsExcluded(x)) + { + continue; + } + integrator.Push(x); + } + + foreach (var x in EnumerateMeshRenderer(go.transform)) + { + if (exclude.IsExcluded(x)) + { + continue; + } + integrator.Push(x); + } + + break; } - integrator.Push(x); - } } integrator.Intgrate(onlyBlendShapeRenderers); @@ -57,7 +92,7 @@ namespace UniGLTF.MeshUtility return integrator.Result; } - public static IEnumerable EnumerateSkinnedMeshRenderer(Transform root, bool hasBlendShape) + public static IEnumerable EnumerateSkinnedMeshRenderer(Transform root, MeshEnumerateOption hasBlendShape) { foreach (var x in Traverse(root)) { @@ -65,10 +100,30 @@ namespace UniGLTF.MeshUtility if (renderer != null && renderer.gameObject.activeInHierarchy && renderer.sharedMesh != null && - renderer.enabled && - renderer.sharedMesh.blendShapeCount > 0 == hasBlendShape) + renderer.enabled) { - yield return renderer; + switch (hasBlendShape) + { + case MeshEnumerateOption.OnlyWithBlendShape: + if (renderer.sharedMesh.blendShapeCount > 0) + { + yield return renderer; + } + break; + + case MeshEnumerateOption.OnlyWithoutBlendShape: + if (renderer.sharedMesh.blendShapeCount == 0) + { + yield return renderer; + } + break; + + case MeshEnumerateOption.All: + { + yield return renderer; + break; + } + } } } } diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs b/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs deleted file mode 100644 index 793972b4c..000000000 --- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using UniGLTF; -using UniGLTF.MeshUtility; -using UnityEditor; -using UnityEngine; -using Object = UnityEngine.Object; - - -namespace VRM -{ - /// - /// 複数のメッシュを統合する。 - /// VRM の場合、BlendShapeClip の調整が必用なのでこれも実行する。 - /// - [DisallowMultipleComponent] - public static class MeshIntegratorEditor - { - const string ASSET_SUFFIX = ".mesh.asset"; - - public static bool IntegrateValidation() - { - return Selection.activeObject != null && - Selection.activeObject is GameObject && - SkinnedMeshUtility.IsPrefab(Selection.activeObject); - } - - public static List Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable excludes) - { - Undo.RecordObject(prefab, "Mesh Integration"); - var instance = SkinnedMeshUtility.InstantiatePrefab(prefab); - - var clips = new List(); - var proxy = instance.GetComponent(); - if (proxy != null && proxy.BlendShapeAvatar != null) - { - clips = proxy.BlendShapeAvatar.Clips; - } - foreach (var clip in clips) - { - Undo.RecordObject(clip, "Mesh Integration"); - } - - // Backup Exists - BackupVrmPrefab(prefab); - - // Execute - var results = VRMMeshIntegratorUtility.Integrate(instance, clips, excludes); - - // disable source renderer - foreach (var res in results) - { - foreach (var renderer in res.SourceSkinnedMeshRenderers) - { - Undo.RecordObject(renderer.gameObject, "Deactivate old renderer"); - renderer.gameObject.SetActive(false); - } - - foreach (var renderer in res.SourceMeshRenderers) - { - Undo.RecordObject(renderer.gameObject, "Deactivate old renderer"); - renderer.gameObject.SetActive(false); - } - } - - foreach (var result in results) - { - if (result.IntegratedRenderer == null) continue; - - var assetPath = writeAssetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"); - Debug.LogFormat("CreateAsset: {0}", assetPath); - assetPath.CreateAsset(result.IntegratedRenderer.sharedMesh); - Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers"); - } - - // Apply to Prefab - var prefabPath = UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(prefab)); - if (prefabPath.Equals(writeAssetPath)) - { - SkinnedMeshUtility.ApplyChangesToPrefab(instance); - } - else - { - PrefabUtility.SaveAsPrefabAsset(instance, writeAssetPath.Value, out bool success); - if (!success) - { - throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {writeAssetPath}"); - } - } - - // destroy source renderers - Object.DestroyImmediate(instance); - - return results; - } - - private static void BackupVrmPrefab(GameObject rootPrefab) - { - var proxy = rootPrefab.GetComponent(); - - var srcAvatar = proxy.BlendShapeAvatar; - var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, rootPrefab); - - var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, rootPrefab)); - dstAvatar.Clips = clipMapper.Values.ToList(); - - var dstPrefab = BackupAsset(rootPrefab, rootPrefab); - var dstInstance = SkinnedMeshUtility.InstantiatePrefab(dstPrefab); - dstInstance.GetComponent().BlendShapeAvatar = dstAvatar; - SkinnedMeshUtility.ApplyChangesToPrefab(dstInstance); - Object.DestroyImmediate(dstInstance); - } - - private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityEngine.Object - { - var srcAssetPath = UnityPath.FromAsset(asset); - var assetName = srcAssetPath.FileName; - - var backupDir = "MeshIntegratorBackup"; - var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(backupDir); - backupPath.EnsureFolder(); - var dstAssetPath = backupPath.Child(assetName); - - AssetDatabase.CopyAsset(srcAssetPath.Value, dstAssetPath.Value); - return dstAssetPath.LoadAsset(); - } - - private static string GetRootPrefabPath(GameObject go) - { - var prefab = SkinnedMeshUtility.IsPrefab(go) ? go : SkinnedMeshUtility.GetPrefab(go); - var assetPath = ""; - if (prefab != null) - { - var prefabPath = AssetDatabase.GetAssetPath(prefab); - assetPath = string.Format("{0}/", Path.GetDirectoryName(prefabPath)); - } - else - { - assetPath = string.Format("Assets/"); - } - assetPath = assetPath.Replace(@"\", @"/"); - return assetPath; - } - } -} diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs.meta deleted file mode 100644 index 19c7bdf94..000000000 --- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs.meta +++ /dev/null @@ -1,13 +0,0 @@ -fileFormatVersion: 2 -guid: 744f66c7013807b4cae1d38d2ecfff38 -timeCreated: 1518503829 -licenseType: Free -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs b/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs deleted file mode 100644 index 20c05a315..000000000 --- a/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using UnityEditor; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace VRM -{ - public static class SkinnedMeshUtility - { - public const string MENU_KEY = "GameObject/UnityEditorScripts/"; - public const int MENU_PRIORITY = 11; - - public static Object GetPrefab(GameObject instance) - { -#if UNITY_2018_2_OR_NEWER - return PrefabUtility.GetCorrespondingObjectFromSource(instance); -#else - return PrefabUtility.GetPrefabParent(go); -#endif - } - - public static bool IsPrefab(Object instance) - { - if (instance == null) - { - return false; - } - if (PrefabUtility.GetPrefabAssetType(instance) != PrefabAssetType.Regular) - { - return false; - } - return true; - } - - public static void ApplyChangesToPrefab(GameObject instance) - { - var prefab = GetPrefab(instance); - if (prefab == null) - { - return; - } - - var path = AssetDatabase.GetAssetPath(prefab); - if (string.IsNullOrEmpty(path)) - { - return; - } - - PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction); - } - - public static GameObject InstantiatePrefab(GameObject prefab) - { - if (!IsPrefab(prefab)) return null; - - return (GameObject)PrefabUtility.InstantiatePrefab(prefab); - } - } -} \ No newline at end of file diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs.meta deleted file mode 100644 index 6cda243d5..000000000 --- a/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: d66a91f4b51e42a6b165fe39fdb48d64 -timeCreated: 1559541677 \ No newline at end of file diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs similarity index 57% rename from Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs rename to Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs index 9a1df032a..3f991627b 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs @@ -6,14 +6,34 @@ using System; using System.Collections.Generic; using UniGLTF.MeshUtility; using System.IO; +using UniGLTF.M17N; namespace VRM { - public class MeshIntegratorWizard : ScriptableWizard + public class VrmMeshIntegratorWizard : ScriptableWizard { + const string ASSET_SUFFIX = ".mesh.asset"; + + enum HelpMessage + { + Ready, + SetTarget, + InvalidTarget, + } + + enum ValidationError + { + None, + NoTarget, + HasParent, + } + [SerializeField] GameObject m_root; + [SerializeField] + bool m_separateByBlendShape = true; + [Header("Validation")] [SerializeField] Material[] m_uniqueMaterials; @@ -74,11 +94,12 @@ namespace VRM public static void CreateWizard() { - ScriptableWizard.DisplayWizard("MeshIntegratorWizard", "Integrate and close window", "Integrate"); + ScriptableWizard.DisplayWizard("MeshIntegratorWizard", "Integrate and close window", "Integrate"); } private void OnEnable() { + Clear(HelpMessage.Ready, ValidationError.None); m_root = Selection.activeGameObject; OnValidate(); } @@ -125,20 +146,33 @@ namespace VRM return key; } + void Clear(HelpMessage help, ValidationError error) + { + helpString = help.Msg(); + errorString = error != ValidationError.None ? error.Msg() : null; + m_uniqueMaterials = new Material[] { }; + m_duplicateMaterials = new MaterialList[] { }; + m_excludes.Clear(); + isValid = false; + } + void OnValidate() { - if (m_root == null - || !PrefabUtility.IsPartOfAnyPrefab(m_root) || m_root.transform.parent != null) + if (m_root == null) { - Debug.LogWarning("Invalidate"); - m_uniqueMaterials = new Material[] { }; - m_duplicateMaterials = new MaterialList[] { }; - m_excludes.Clear(); + Clear(HelpMessage.SetTarget, ValidationError.NoTarget); return; } - Debug.Log("OnValidate"); - m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false) + if (m_root.transform.parent != null) + { + Clear(HelpMessage.InvalidTarget, ValidationError.HasParent); + return; + } + + Clear(HelpMessage.Ready, ValidationError.None); + isValid = true; + m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, MeshEnumerateOption.OnlyWithoutBlendShape) .SelectMany(x => x.sharedMaterials) .Distinct() .ToArray(); @@ -180,18 +214,13 @@ namespace VRM void OnWizardUpdate() { - helpString = "select target mesh root"; + // helpString = "Set target gameobject `in scene`. Prefab not supported."; } void Integrate() { - if (m_root == null) - { - Debug.LogWarning("no root object"); - return; - } - var prefabPath = AssetDatabase.GetAssetPath(m_root); + Debug.Log(prefabPath); var path = EditorUtility.SaveFilePanel("save prefab", Path.GetDirectoryName(prefabPath), m_root.name, "prefab"); if (string.IsNullOrEmpty(path)) { @@ -206,20 +235,81 @@ namespace VRM } var excludes = m_excludes.Where(x => x.Exclude).Select(x => x.Mesh); - integrationResults = MeshIntegratorEditor.Integrate(m_root, assetPath, excludes).Select(x => x.MeshMap).ToArray(); + + // Backup Exists + VrmPrefabUtility.BackupVrmPrefab(m_root); + + Undo.RecordObject(m_root, "Mesh Integration"); + var instance = VrmPrefabUtility.InstantiatePrefab(m_root); + + var clips = new List(); + var proxy = instance.GetComponent(); + if (proxy != null && proxy.BlendShapeAvatar != null) + { + clips = proxy.BlendShapeAvatar.Clips; + } + foreach (var clip in clips) + { + Undo.RecordObject(clip, "Mesh Integration"); + } + + // Execute + var results = VRMMeshIntegratorUtility.Integrate(instance, clips, excludes, m_separateByBlendShape); + // integrationResults = MeshIntegratorEditor.Integrate(m_root, assetPath, excludes, m_separateByBlendShape).Select(x => x.MeshMap).ToArray(); + // public static List Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable excludes, bool separateByBlendShape) + + // disable source renderer + foreach (var res in results) + { + foreach (var renderer in res.SourceSkinnedMeshRenderers) + { + Undo.RecordObject(renderer.gameObject, "Deactivate old renderer"); + renderer.gameObject.SetActive(false); + } + + foreach (var renderer in res.SourceMeshRenderers) + { + Undo.RecordObject(renderer.gameObject, "Deactivate old renderer"); + renderer.gameObject.SetActive(false); + } + } + + foreach (var result in results) + { + if (result.IntegratedRenderer == null) continue; + + var childAssetPath = assetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"); + Debug.LogFormat("CreateAsset: {0}", childAssetPath); + childAssetPath.CreateAsset(result.IntegratedRenderer.sharedMesh); + Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers"); + } + + // Apply to Prefab + if (UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(m_root)).Equals(assetPath)) + { + VrmPrefabUtility.ApplyChangesToPrefab(instance); + } + else + { + PrefabUtility.SaveAsPrefabAsset(instance, assetPath.Value, out bool success); + if (!success) + { + throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {assetPath}"); + } + } + + // destroy source renderers + UnityEngine.Object.DestroyImmediate(instance); } void OnWizardCreate() { - Debug.Log("OnWizardCreate"); Integrate(); - // close } void OnWizardOtherButton() { - Debug.Log("OnWizardOtherButton"); Integrate(); } } diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs.meta similarity index 71% rename from Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs.meta rename to Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs.meta index f87becc3d..aec3bc01f 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs.meta +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs.meta @@ -1,7 +1,5 @@ fileFormatVersion: 2 -guid: 648920943c26bde4b920119bdbb38f70 -timeCreated: 1518194696 -licenseType: Free +guid: 0c6fe206e704d2148be9fd6ede8ca4f0 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs new file mode 100644 index 000000000..574c7121c --- /dev/null +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs @@ -0,0 +1,105 @@ +using System.Linq; +using UniGLTF; +using UnityEditor; +using UnityEngine; + +namespace VRM +{ + public static class VrmPrefabUtility + { + const string BACKUP_DIR = "MeshIntegratorBackup"; + + public static GameObject InstantiatePrefab(GameObject prefab) + { + if (!IsPrefab(prefab)) return null; + + return (GameObject)PrefabUtility.InstantiatePrefab(prefab); + } + + static bool IsPrefab(Object instance) + { + if (instance == null) + { + return false; + } + if (PrefabUtility.GetPrefabAssetType(instance) != PrefabAssetType.Regular) + { + return false; + } + return true; + } + + public static void ApplyChangesToPrefab(GameObject instance) + { + var prefab = GetPrefab(instance); + if (prefab == null) + { + return; + } + + var path = AssetDatabase.GetAssetPath(prefab); + if (string.IsNullOrEmpty(path)) + { + return; + } + + PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction); + } + + static Object GetPrefab(GameObject instance) + { +#if UNITY_2018_2_OR_NEWER + return PrefabUtility.GetCorrespondingObjectFromSource(instance); +#else + return PrefabUtility.GetPrefabParent(go); +#endif + } + + /// + /// VRM prefab を ${prefab_dir}/MeshIntegratorBackup/ に複製する。 + /// + /// * prefab + /// * BlendShapeAvatar + /// * BlendShapeClip + /// + /// が複製される。 + /// + /// + public static void BackupVrmPrefab(GameObject rootPrefab) + { + var proxy = rootPrefab.GetComponent(); + + var srcAvatar = proxy.BlendShapeAvatar; + var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, rootPrefab); + + var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, rootPrefab)); + dstAvatar.Clips = clipMapper.Values.ToList(); + + var dstPrefab = BackupAsset(rootPrefab, rootPrefab); + var dstInstance = InstantiatePrefab(dstPrefab); + dstInstance.GetComponent().BlendShapeAvatar = dstAvatar; + ApplyChangesToPrefab(dstInstance); + Object.DestroyImmediate(dstInstance); + } + + /// + /// asset を ${prefab_dir}/MeshIntegratorBackup/ にコピーし、コピーしたアセットをロードして返す + /// + /// + /// + /// + /// + private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityEngine.Object + { + var srcAssetPath = UnityPath.FromAsset(asset); + var assetName = srcAssetPath.FileName; + + var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(BACKUP_DIR); + backupPath.EnsureFolder(); + var dstAssetPath = backupPath.Child(assetName); + + AssetDatabase.CopyAsset(srcAssetPath.Value, dstAssetPath.Value); + return dstAssetPath.LoadAsset(); + } + } +} diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs.meta new file mode 100644 index 000000000..9534b545b --- /dev/null +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d3e24cadf1393841988d0c53830b67e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/Editor/VrmTopMenu.cs b/Assets/VRM/Editor/VrmTopMenu.cs index a79dc302a..3ed060112 100644 --- a/Assets/VRM/Editor/VrmTopMenu.cs +++ b/Assets/VRM/Editor/VrmTopMenu.cs @@ -28,7 +28,7 @@ namespace VRM private static void FreezeTPose() => VRMHumanoidNormalizerMenu.Normalize(); [MenuItem(UserMenuPrefix + "/MeshIntegratorWizard", priority = 21)] - private static void OpenMeshIntegratorWizard() => MeshIntegratorWizard.CreateWizard(); + private static void OpenMeshIntegratorWizard() => VrmMeshIntegratorWizard.CreateWizard(); [MenuItem(UserMenuPrefix + "/Save SpringBone to JSON", validate = true)] private static bool SaveSpringBoneToJsonValidation() => VRMSpringBoneUtilityEditor.SaveSpringBoneToJsonValidation(); diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs index 81f61e0d5..fea51d76c 100644 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs +++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs @@ -11,49 +11,32 @@ namespace VRM /// public static class VRMMeshIntegratorUtility { - public static bool IntegrateRuntime(GameObject vrmRootObject) - { - if (vrmRootObject == null) return false; - var proxy = vrmRootObject.GetComponent(); - if (proxy == null) return false; - var avatar = proxy.BlendShapeAvatar; - if (avatar == null) return false; - var clips = avatar.Clips; - - var results = Integrate(vrmRootObject, clips, null); - if (results.Any(x => x.IntegratedRenderer == null)) return false; - - foreach (var result in results) - { - foreach (var renderer in result.SourceSkinnedMeshRenderers) - { - Object.Destroy(renderer); - } - - foreach (var renderer in result.SourceMeshRenderers) - { - Object.Destroy(renderer); - } - } - - return true; - } - - public static List Integrate(GameObject root, List blendshapeClips, IEnumerable excludes) + public static List Integrate(GameObject root, List blendshapeClips, IEnumerable excludes, bool separateByBlendShape) { var result = new List(); - var withoutBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: false, excludes: excludes); - if (withoutBlendShape.IntegratedRenderer != null) + if (separateByBlendShape) { - result.Add(withoutBlendShape); - } + var withoutBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes); + if (withoutBlendShape.IntegratedRenderer != null) + { + result.Add(withoutBlendShape); + } - var onlyBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: true, excludes: excludes); - if (onlyBlendShape.IntegratedRenderer != null) + var onlyBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes); + if (onlyBlendShape.IntegratedRenderer != null) + { + result.Add(onlyBlendShape); + FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root); + } + } + else { - result.Add(onlyBlendShape); - FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root); + var integrated = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes); + if (integrated.IntegratedRenderer != null) + { + result.Add(integrated); + } } return result;