diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs new file mode 100644 index 000000000..897bfad0e --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs @@ -0,0 +1,19 @@ +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/BoneMeshEraserGUI.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta new file mode 100644 index 000000000..eddf8f87d --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cdbec3de528c61448aeb739ffe1f79e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs index c42b5a8cd..d7c352b6c 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs @@ -1,5 +1,3 @@ -using System; -using System.IO; using System.Linq; using System.Reflection; using UnityEngine; @@ -9,31 +7,13 @@ using UniGLTF.M17N; 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 animator = serializedObject.FindProperty("_cAnimator"); - EditorGUILayout.PropertyField(animator, new GUIContent("Animator"), false); - var eraseRoot = serializedObject.FindProperty("_cEraseRoot"); - EditorGUILayout.PropertyField(eraseRoot, new GUIContent("Erase Root"), false); - var list = serializedObject.FindProperty("_eraseBones"); - EditorGUILayout.PropertyField(list, new GUIContent("Erase Bones"), true); - serializedObject.ApplyModifiedProperties(); - } - } - public class MeshProcessDialog : EditorWindow { enum Tabs { MeshSeparator, MeshIntegrator, - StaticMeshIntegrator, + // StaticMeshIntegrator, BoneMeshEraser, } private Tabs _tab; @@ -47,68 +27,25 @@ namespace UniGLTF.MeshUtility [SerializeField] private SkinnedMeshRenderer _cSkinnedMesh = null; - [SerializeField] + private Animator _cAnimator = null; - [SerializeField] private Transform _cEraseRoot = null; + [SerializeField] private BoneMeshEraser.EraseBone[] _eraseBones; private MethodInfo _processFunction; - private bool _isInvokeSuccess = false; GUIStyle _tabButtonStyle => "LargeButton"; GUI.ToolbarButtonSize _tabButtonSize => GUI.ToolbarButtonSize.Fixed; - private enum MeshProcessingMessages + + public static void OpenWindow() { - [LangMsg(Languages.ja, "ターゲットオブジェクト")] - [LangMsg(Languages.en, "TargetObject")] - TARGET_OBJECT, - - [LangMsg(Languages.ja, "BlendShapeを含むメッシュは分割されます")] - [LangMsg(Languages.en, "Meshes containing BlendShape will be split")] - MESH_SEPARATOR, - - [LangMsg(Languages.ja, "メッシュを統合します。BlendShapeを含むメッシュは独立して統合されます")] - [LangMsg(Languages.en, "Generate a single mesh. Meshes w/ BlendShape will be grouped into another one")] - MESH_INTEGRATOR, - - [LangMsg(Languages.ja, "静的メッシュを一つに統合します")] - [LangMsg(Languages.en, "Integrate static meshes into one")] - STATIC_MESH_INTEGRATOR, - - [LangMsg(Languages.ja, "ボーン(Erase Rootのヒエラルキー)に関連するメッシュを削除します")] - [LangMsg(Languages.en, "Eliminate meshes associated with the bones in EraseRoot hierarchy")] - BONE_MESH_ERASER, - - [LangMsg(Languages.ja, "Skinned Meshを選んでください")] - [LangMsg(Languages.en, "Select a skinned mesh")] - SELECT_SKINNED_MESH, - - [LangMsg(Languages.ja, "Erase Rootを選んでください")] - [LangMsg(Languages.en, "Select a erase root")] - SELECT_ERASE_ROOT, - - [LangMsg(Languages.ja, "GameObjectを選んでください")] - [LangMsg(Languages.en, "Select a GameObject first")] - NO_GAMEOBJECT_SELECTED, - - [LangMsg(Languages.ja, "GameObjectにスキンメッシュが含まれていません")] - [LangMsg(Languages.en, "No skinned mesh is contained")] - NO_SKINNED_MESH, - - [LangMsg(Languages.ja, "GameObjectに静的メッシュが含まれていません")] - [LangMsg(Languages.en, "No static mesh is contained")] - NO_STATIC_MESH, - - [LangMsg(Languages.ja, "GameObjectにスキンメッシュ・静的メッシュが含まれていません")] - [LangMsg(Languages.en, "Skinned/Static mesh is not contained")] - NO_MESH, - - [LangMsg(Languages.ja, "ターゲットオブジェクトはVRMモデルです。`VRM0-> MeshIntegrator`を使ってください")] - [LangMsg(Languages.en, "Target object is VRM model, use `VRM0 -> MeshIntegrator` instead")] - VRM_DETECTED, + var window = + (MeshProcessDialog)EditorWindow.GetWindow(typeof(MeshProcessDialog)); + window.titleContent = new GUIContent("Mesh Processing Window"); + window.Show(); } private void OnEnable() @@ -126,202 +63,64 @@ namespace UniGLTF.MeshUtility // lang LanguageGetter.OnGuiSelectLang(); - _tab = TabBar.OnGUI(_tab, _tabButtonStyle, _tabButtonSize); + { + EditorGUILayout.BeginHorizontal(); + 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()) + { + _exportTarget = Selection.activeObject as GameObject; + } + } + // tab + _tab = TabBar.OnGUI(_tab, _tabButtonStyle, _tabButtonSize); + var processed = false; switch (_tab) { case Tabs.MeshSeparator: - EditorGUILayout.LabelField(MeshProcessingMessages.MESH_SEPARATOR.Msg()); + EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_SEPARATOR.Msg(), MessageType.Info); + processed = TabMeshSeparator.OnGUI(_exportTarget); break; + case Tabs.MeshIntegrator: - EditorGUILayout.LabelField(MeshProcessingMessages.MESH_INTEGRATOR.Msg()); - break; - case Tabs.StaticMeshIntegrator: - EditorGUILayout.LabelField(MeshProcessingMessages.STATIC_MESH_INTEGRATOR.Msg()); + EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info); + processed = TabMeshIntegrator.OnGUI(_exportTarget); break; + + // MeshIntegrator と機能が重複しているのと正常に動作しなかった + // case Tabs.StaticMeshIntegrator: + // EditorGUILayout.HelpBox(MeshProcessingMessages.STATIC_MESH_INTEGRATOR.Msg(), MessageType.Info); + // processed = TabStaticMeshIntegrator.OnGUI(_exportTarget); + // break; + case Tabs.BoneMeshEraser: - EditorGUILayout.LabelField(MeshProcessingMessages.BONE_MESH_ERASER.Msg()); - break; - } - - EditorGUILayout.BeginHorizontal(); - 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()) - { - _exportTarget = Selection.activeObject as GameObject; - } - - if (_tab == Tabs.BoneMeshEraser) - { - if (_boneMeshEraserEditor) - { - _boneMeshEraserEditor.OnInspectorGUI(); - } - // any better way we can detect component change? - if (_cSkinnedMesh != _pSkinnedMesh || _cAnimator != _pAnimator || _cEraseRoot != _pEraseRoot) - { - BoneMeshEraserValidate(); - } - _pSkinnedMesh = _cSkinnedMesh; - _pAnimator = _cAnimator; - _pEraseRoot = _cEraseRoot; - } - - // Create Other Buttons - { - GUILayout.BeginVertical(); - { - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - - if (GUILayout.Button("Process", GUILayout.MinWidth(100))) + EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info); + if (_boneMeshEraserEditor) { - switch (_tab) - { - case Tabs.MeshSeparator: - _isInvokeSuccess = InvokeWizardUpdate("MeshSeparator"); - break; - case Tabs.MeshIntegrator: - _isInvokeSuccess = InvokeWizardUpdate("MeshIntegrator"); - break; - case Tabs.StaticMeshIntegrator: - _isInvokeSuccess = InvokeWizardUpdate("StaticMeshIntegrator"); - break; - case Tabs.BoneMeshEraser: - _isInvokeSuccess = InvokeWizardUpdate("BoneMeshRemover"); - break; - } - if (_isInvokeSuccess) - { - Close(); - GUIUtility.ExitGUI(); - } + _boneMeshEraserEditor.OnInspectorGUI(); } - GUI.enabled = true; - - GUILayout.EndHorizontal(); - } - GUILayout.EndVertical(); + // any better way we can detect component change? + if (_cSkinnedMesh != _pSkinnedMesh || _cAnimator != _pAnimator || _cEraseRoot != _pEraseRoot) + { + BoneMeshEraserValidate(); + } + _pSkinnedMesh = _cSkinnedMesh; + _pAnimator = _cAnimator; + _pEraseRoot = _cEraseRoot; + processed = TabBoneMeshRemover.OnGUI(_exportTarget, _cSkinnedMesh, _eraseBones); + break; } EditorGUILayout.EndScrollView(); - } - private bool InvokeWizardUpdate(string processFuntion) - { - const BindingFlags kInstanceInvokeFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy; - _processFunction = GetType().GetMethod(processFuntion, kInstanceInvokeFlags); - if (_processFunction != null) + if (processed) { - return (Boolean)_processFunction.Invoke(this, null); - } - else - { - Debug.LogError("This function has not been implemented in script"); - return false; + Close(); + GUIUtility.ExitGUI(); } } - private bool GameObjectNull() - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); - return false; - } - - private bool MeshSeparator() - { - if (_exportTarget == null) return GameObjectNull(); - var go = _exportTarget; - - if (go.GetComponentsInChildren().Length > 0) - { - // copy - var outputObject = GameObject.Instantiate(go); - outputObject.name = outputObject.name + "_mesh_separation"; - // 改変と asset の作成 - var list = MeshUtility.SeparationProcessing(outputObject); - foreach (var (src, with, without) in list) - { - // asset の永続化 - MeshUtility.SaveMesh(src, with, MeshUtility.BlendShapeLogic.WithBlendShape); - MeshUtility.SaveMesh(src, without, MeshUtility.BlendShapeLogic.WithoutBlendShape); - } - return true; - } - else - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_SKINNED_MESH.Msg(), "ok"); - return false; - } - } - - private bool MeshIntegrator() - { - if (_exportTarget == null) return GameObjectNull(); - var go = _exportTarget; - - Component[] allComponents = go.GetComponents(typeof(Component)); - var keyWord = "VRMMeta"; - - foreach (var component in allComponents) - { - if (component == null) continue; - var sourceString = component.ToString(); - if (sourceString.Contains(keyWord)) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.VRM_DETECTED.Msg(), "ok"); - return false; - } - } - - if (go.GetComponentsInChildren().Length > 0 || go.GetComponentsInChildren().Length > 0) - { - MeshUtility.MeshIntegrator(go); - return true; - } - else - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_MESH.Msg(), "ok"); - return false; - } - } - - private bool StaticMeshIntegrator() - { - if (_exportTarget == null) return GameObjectNull(); - var go = _exportTarget; - if (go.GetComponentsInChildren().Length > 0) - { - MeshUtility.IntegrateSelected(go); - return true; - } - else - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_STATIC_MESH.Msg(), "ok"); - return false; - } - } - - private bool BoneMeshRemover() - { - if (_exportTarget == null) return GameObjectNull(); - var go = _exportTarget; - - if (_cSkinnedMesh == null) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(), "ok"); - return false; - } - else if (_cEraseRoot == null) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_ERASE_ROOT.Msg(), "ok"); - return false; - } - BoneMeshRemove(go); - - return true; - } private void BoneMeshEraserValidate() { @@ -361,68 +160,5 @@ namespace UniGLTF.MeshUtility }) .ToArray(); } - - private void BoneMeshRemove(GameObject go) - { - var renderer = Remove(go); - 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); - if (prefab != null) - { - var prefabPath = AssetDatabase.GetAssetPath(prefab); - assetPath = string.Format("{0}/{1}{2}", - Path.GetDirectoryName(prefabPath), - Path.GetFileNameWithoutExtension(prefabPath), - MeshUtility.ASSET_SUFFIX - ); - } - - Debug.LogFormat("CreateAsset: {0}", assetPath); - AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath); - - // destroy BoneMeshEraser in the source - foreach (var skinnedMesh in go.GetComponentsInChildren()) - { - if (skinnedMesh.gameObject.name == BoneMeshEraserWizard.BONE_MESH_ERASER_NAME) - { - GameObject.DestroyImmediate(skinnedMesh.gameObject); - } - } - // destroy the original mesh in the copied GameObject - foreach (var skinnedMesh in outputObject.GetComponentsInChildren()) - { - if (skinnedMesh.sharedMesh == _cSkinnedMesh.sharedMesh) - { - GameObject.DestroyImmediate(skinnedMesh); - } - } - } - - private SkinnedMeshRenderer Remove(GameObject go) - { - 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; - } } } \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs new file mode 100644 index 000000000..3fe4dcfdb --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs @@ -0,0 +1,82 @@ +using UniGLTF.M17N; + +namespace UniGLTF.MeshUtility +{ + public enum MeshProcessingMessages + { + [LangMsg(Languages.ja, "ターゲットオブジェクト")] + [LangMsg(Languages.en, "TargetObject")] + TARGET_OBJECT, + + [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer にアタッチされたメッシュを、 BlendShape の有無で分割します。 + +* Asset: 新しい Mesh Asset が元と同じフォルダに作成されます。例: Original -> Original_WithBlendShape.mesh & Original_WithoutBlendShape.mesh +* Scene: コピーされたヒエラルキーでは、分割された Mesh は BlendShape のある Mesh で置き換えられて、BlendShape の無い Mesh を使った SkinnedMeshRenderer が追加されます。 +")] + [LangMsg(Languages.en, @"Separate the mesh attached to the SkinnedMeshRenderer under the target object with or without BlendShape. + +* Asset: A new Mesh Asset will be created in the same folder as the original. Example: Original-> Original_WithBlendShape.mesh & Original_WithoutBlendShape.mesh +* Scene: In the copied hierarchy, the split Mesh is replaced with a Mesh that holds the BlendShape, and a SkinnedMeshRenderer with a Mesh without BlendShape is added. +")] + MESH_SEPARATOR, + + [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュをを統合します。BlendShape の有無で2つ作成されます。 + +* Asset: Assets/MeshIntegrated.mesh が作成されます(上書きされるので注意してください)。 +* Scene: コピーされたヒエラルキーでは、統合された Mesh は除去されます。新しく MeshIntegrator ノードが追加されます。 +* VRMではBlendShapeClipの統合など追加の処理が必要でる。VRMの統合機能を使ってください。 +")] + [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. + +* 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. +")] + MESH_INTEGRATOR, + + // [LangMsg(Languages.ja, "静的メッシュを一つに統合します")] + // [LangMsg(Languages.en, "Integrate static meshes into one")] + // STATIC_MESH_INTEGRATOR, + + [LangMsg(Languages.ja, @"指定された SkinnedMeshRenderer から、指定されたボーンに対する Weight を保持する三角形を除去します。 + +* Asset: 元の Mesh と同じフォルダに、三角形を除去した Mesh を保存します。 +* Scene: コピーされたヒエラルキーでは、三角形が除去された Mesh に差し替えられます。 +")] + [LangMsg(Languages.en, @"Removes the triangle that holds the weight for the specified bone from the specified SkinnedMeshRenderer. + +* Assets: Save the mesh with the triangles removed in the same folder as the original mesh. +* Scene: In the copied hierarchy, it will be replaced with a Mesh with the triangles removed. +")] + + BONE_MESH_ERASER, + + [LangMsg(Languages.ja, "Skinned Meshを選んでください")] + [LangMsg(Languages.en, "Select a skinned mesh")] + SELECT_SKINNED_MESH, + + [LangMsg(Languages.ja, "Erase Rootを選んでください")] + [LangMsg(Languages.en, "Select a erase root")] + SELECT_ERASE_ROOT, + + [LangMsg(Languages.ja, "GameObjectを選んでください")] + [LangMsg(Languages.en, "Select a GameObject first")] + NO_GAMEOBJECT_SELECTED, + + [LangMsg(Languages.ja, "GameObjectにスキンメッシュが含まれていません")] + [LangMsg(Languages.en, "No skinned mesh is contained")] + NO_SKINNED_MESH, + + [LangMsg(Languages.ja, "GameObjectに静的メッシュが含まれていません")] + [LangMsg(Languages.en, "No static mesh is contained")] + NO_STATIC_MESH, + + [LangMsg(Languages.ja, "GameObjectにスキンメッシュ・静的メッシュが含まれていません")] + [LangMsg(Languages.en, "Skinned/Static mesh is not contained")] + NO_MESH, + + [LangMsg(Languages.ja, "ターゲットオブジェクトはVRMモデルです。`VRM0-> MeshIntegrator`を使ってください")] + [LangMsg(Languages.en, "Target object is VRM model, use `VRM0 -> MeshIntegrator` instead")] + VRM_DETECTED, + } +} \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs.meta new file mode 100644 index 000000000..e3b56a221 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 575d573f28f0e4a48a2a6509e69b7f6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs index b8b888c62..fd45864d8 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs @@ -264,6 +264,10 @@ namespace UniGLTF.MeshUtility return Selection.activeObject != null && Selection.activeObject is GameObject; } + /// + /// from dialog + /// + /// public static void IntegrateSelected(GameObject go) { var meshWithMaterials = StaticMeshIntegrator.Integrate(go.transform); @@ -319,6 +323,11 @@ namespace UniGLTF.MeshUtility renderer.sharedMaterials = meshWithMaterials.Materials; } #endif + + /// + /// from dialog + /// + /// public static void MeshIntegrator(GameObject go) { MeshIntegratorUtility.Integrate(go, onlyBlendShapeRenderers: true); @@ -330,6 +339,7 @@ namespace UniGLTF.MeshUtility var normalMeshes = outputObject.GetComponentsInChildren(); // destroy integrated meshes in the source + // ? foreach (var skinnedMesh in go.GetComponentsInChildren()) { if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME || diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs new file mode 100644 index 000000000..c0dc39b59 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Linq; +using UniGLTF.M17N; +using UnityEditor; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + public static class TabBoneMeshRemover + { + public static bool OnGUI(GameObject _exportTarget, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones) + { + var _isInvokeSuccess = false; + GUILayout.BeginVertical(); + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Process", GUILayout.MinWidth(100))) + { + _isInvokeSuccess = TabBoneMeshRemover.Execute(_exportTarget, _cSkinnedMesh, _eraseBones); + } + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + return _isInvokeSuccess; + } + + public static bool Execute(GameObject _exportTarget, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones) + { + if (_exportTarget == null) + { + EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); + return false; + } + var go = _exportTarget; + + if (_cSkinnedMesh == null) + { + EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(), "ok"); + return false; + } + + BoneMeshRemove(go, _cSkinnedMesh, _eraseBones); + return true; + } + + 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); + if (prefab != null) + { + var prefabPath = AssetDatabase.GetAssetPath(prefab); + assetPath = string.Format("{0}/{1}{2}", + Path.GetDirectoryName(prefabPath), + Path.GetFileNameWithoutExtension(prefabPath), + MeshUtility.ASSET_SUFFIX + ); + } + + Debug.LogFormat("CreateAsset: {0}", assetPath); + AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath); + + // destroy BoneMeshEraser in the source + foreach (var skinnedMesh in go.GetComponentsInChildren()) + { + if (skinnedMesh.gameObject.name == BoneMeshEraserWizard.BONE_MESH_ERASER_NAME) + { + GameObject.DestroyImmediate(skinnedMesh.gameObject); + } + } + // destroy the original mesh in the copied GameObject + foreach (var skinnedMesh in outputObject.GetComponentsInChildren()) + { + if (skinnedMesh.sharedMesh == _cSkinnedMesh.sharedMesh) + { + GameObject.DestroyImmediate(skinnedMesh); + } + } + } + + private static SkinnedMeshRenderer Remove(GameObject go, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones) + { + 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; + } + } +} \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs.meta new file mode 100644 index 000000000..29e107335 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4094f82d7ee108a4997d80a692116033 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs new file mode 100644 index 000000000..839d425a8 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs @@ -0,0 +1,67 @@ +using UniGLTF.M17N; +using UnityEditor; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + public static class TabMeshIntegrator + { + public static bool OnGUI(GameObject _exportTarget) + { + var _isInvokeSuccess = false; + GUILayout.BeginVertical(); + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Process", GUILayout.MinWidth(100))) + { + _isInvokeSuccess = TabMeshIntegrator.Execute(_exportTarget); + } + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + return _isInvokeSuccess; + } + + static string VRM_META = "VRMMeta"; + static bool HasVrm(GameObject go) + { + var allComponents = go.GetComponents(typeof(Component)); + foreach (var component in allComponents) + { + if (component == null) continue; + var sourceString = component.ToString(); + if (sourceString.Contains(VRM_META)) + { + return true; + } + } + return false; + } + + static bool Execute(GameObject _exportTarget) + { + if (_exportTarget == null) + { + EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); + return false; + } + var go = _exportTarget; + + if (HasVrm(go)) + { + EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.VRM_DETECTED.Msg(), "ok"); + return false; + } + + if (go.GetComponentsInChildren().Length == 0 && go.GetComponentsInChildren().Length == 0) + { + EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_MESH.Msg(), "ok"); + return false; + } + + MeshUtility.MeshIntegrator(go); + return true; + } + } +} diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs.meta new file mode 100644 index 000000000..5c60a51fe --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65a227dcf3cb5f34085bd6829894fb64 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs new file mode 100644 index 000000000..bef557520 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs @@ -0,0 +1,59 @@ +using UniGLTF.M17N; +using UnityEditor; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + /// + /// BlendShape の有無で Mesh を分割する + /// + public static class TabMeshSeparator + { + public static bool OnGUI(GameObject _exportTarget) + { + var _isInvokeSuccess = false; + GUILayout.BeginVertical(); + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Process", GUILayout.MinWidth(100))) + { + _isInvokeSuccess = TabMeshSeparator.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_SKINNED_MESH.Msg(), "ok"); + return false; + } + + // copy + var outputObject = GameObject.Instantiate(go); + outputObject.name = outputObject.name + "_mesh_separation"; + + // 改変と asset の作成 + var list = MeshUtility.SeparationProcessing(outputObject); + foreach (var (src, with, without) in list) + { + // asset の永続化 + MeshUtility.SaveMesh(src, with, MeshUtility.BlendShapeLogic.WithBlendShape); + MeshUtility.SaveMesh(src, without, MeshUtility.BlendShapeLogic.WithoutBlendShape); + } + return true; + } + } +} diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs.meta new file mode 100644 index 000000000..8fb9a175b --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8921d732b66ae134db97faa11eaa9b17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs new file mode 100644 index 000000000..9cdfd3e3f --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs @@ -0,0 +1,45 @@ +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/TabStaticMeshIntegrator.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs.meta new file mode 100644 index 000000000..4c0e30384 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9701e1f6aaed8d14283243a7a50be2df +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/TopMenu.cs b/Assets/UniGLTF/Editor/TopMenu.cs index 2a7083cf5..f41b442e6 100644 --- a/Assets/UniGLTF/Editor/TopMenu.cs +++ b/Assets/UniGLTF/Editor/TopMenu.cs @@ -26,10 +26,7 @@ namespace UniGLTF private static void ImportGltfFile() => TopMenuImplementation.ImportGltfFileToGameObject(); [MenuItem(UserMeshUtilityPrefix + "/MeshProcessing Wizard", priority = 10)] - private static void OpenMeshProcessingWindow() => TopMenuImplementation.OpenMeshProcessingWindow(); - - [MenuItem(UserMeshUtilityPrefix + "/Open Documents", priority = 11)] - private static void MeshUtilityDocs() => Application.OpenURL("https://vrm.dev/en/docs/univrm/gltf/mesh_utility/"); + private static void OpenMeshProcessingWindow() => MeshUtility.MeshProcessDialog.OpenWindow(); #if VRM_DEVELOP [MenuItem(DevelopmentMenuPrefix + "/Generate Serialization Code", priority = 20)] diff --git a/Assets/UniGLTF/Editor/TopMenuImplementation.cs b/Assets/UniGLTF/Editor/TopMenuImplementation.cs index a9ec44f1f..6a5cf2ad6 100644 --- a/Assets/UniGLTF/Editor/TopMenuImplementation.cs +++ b/Assets/UniGLTF/Editor/TopMenuImplementation.cs @@ -8,7 +8,7 @@ namespace UniGLTF { public static void ExportGameObjectToGltfFile() { - var window = (GltfExportWindow) GltfExportWindow.GetWindow(typeof(GltfExportWindow)); + var window = (GltfExportWindow)GltfExportWindow.GetWindow(typeof(GltfExportWindow)); window.titleContent = new GUIContent("Gltf Exporter"); window.Show(); } @@ -92,19 +92,10 @@ namespace UniGLTF Selection.activeObject = asset; } - public static void OpenMeshProcessingWindow() - { - var window = - (MeshUtility.MeshProcessDialog) EditorWindow.GetWindowWithRect(typeof(MeshUtility.MeshProcessDialog), - new Rect(0, 0, 650, 500)); - window.titleContent = new GUIContent("Mesh Processing Window"); - window.Show(); - } - public static void GenerateSerializationCode() { SerializerGenerator.GenerateSerializer(); DeserializerGenerator.GenerateSerializer(); } } -} \ No newline at end of file +}