From 6dd4320656945e823e46bbf1085e7f6ae5a4f237 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 21 Nov 2023 18:19:18 +0900 Subject: [PATCH] TargetIsPrefab and DialogMessage --- .../UniGLTF/Editor/MeshUtility/MaterialKey.cs | 82 ++++ .../Editor/MeshUtility/MaterialKey.cs.meta | 11 + .../Editor/MeshUtility/MeshUtilityDialog.cs | 270 ++++++++++-- .../Editor/MeshUtility/MeshUtilityMessages.cs | 93 ++-- .../Runtime/MeshUtility/GltfMeshUtility.cs | 47 +- .../VrmMeshIntegratorWizard.cs | 406 +++--------------- Assets/VRM/Editor/VrmTopMenu.cs | 2 +- .../MeshUtility/Vrm10MeshUtilityDialog.cs | 47 ++ .../Runtime/MeshUtility/Vrm10MeshUtility.cs | 16 +- 9 files changed, 534 insertions(+), 440 deletions(-) create mode 100644 Assets/UniGLTF/Editor/MeshUtility/MaterialKey.cs create mode 100644 Assets/UniGLTF/Editor/MeshUtility/MaterialKey.cs.meta diff --git a/Assets/UniGLTF/Editor/MeshUtility/MaterialKey.cs b/Assets/UniGLTF/Editor/MeshUtility/MaterialKey.cs new file mode 100644 index 000000000..f7f2832d3 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/MaterialKey.cs @@ -0,0 +1,82 @@ +// namespace UniGLTF.MeshUtility +// { +// [Serializable] +// struct MaterialKey +// { +// public string Shader; +// public KeyValuePair[] Properties; + +// public override bool Equals(object obj) +// { +// if (!(obj is MaterialKey)) +// { +// return base.Equals(obj); +// } + +// var key = (MaterialKey)obj; + +// return Shader == key.Shader +// && Properties.SequenceEqual(key.Properties) +// ; +// } + +// public override int GetHashCode() +// { +// return base.GetHashCode(); +// } +// } + +// [Serializable] +// struct MaterialList +// { +// public Material[] Materials; + +// public MaterialList(Material[] list) +// { +// Materials = list; +// } +// } + +// static object GetPropertyValue(Shader shader, int i, Material m) +// { +// var propType = ShaderUtil.GetPropertyType(shader, i); +// switch (propType) +// { +// case ShaderUtil.ShaderPropertyType.Color: +// return m.GetColor(ShaderUtil.GetPropertyName(shader, i)); + +// case ShaderUtil.ShaderPropertyType.Range: +// case ShaderUtil.ShaderPropertyType.Float: +// return m.GetFloat(ShaderUtil.GetPropertyName(shader, i)); + +// case ShaderUtil.ShaderPropertyType.Vector: +// return m.GetVector(ShaderUtil.GetPropertyName(shader, i)); + +// case ShaderUtil.ShaderPropertyType.TexEnv: +// return m.GetTexture(ShaderUtil.GetPropertyName(shader, i)); + +// default: +// throw new NotImplementedException(propType.ToString()); +// } +// } + +// static MaterialKey GetMaterialKey(Material m) +// { +// var key = new MaterialKey +// { +// Shader = m.shader.name, +// }; + +// key.Properties = Enumerable.Range(0, ShaderUtil.GetPropertyCount(m.shader)) +// .Select(x => new KeyValuePair( +// ShaderUtil.GetPropertyName(m.shader, x), +// GetPropertyValue(m.shader, x, m)) +// ) +// .OrderBy(x => x.Key) +// .ToArray() +// ; + +// return key; +// } + +// } \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/MaterialKey.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MaterialKey.cs.meta new file mode 100644 index 000000000..e6645775a --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/MaterialKey.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91c86a6f42c50c9438c124f0dbd048a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs index ba7ff48e9..07faa5bfd 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs @@ -3,12 +3,15 @@ using UnityEditor; using UniGLTF.M17N; using System.Collections.Generic; using System.Linq; +using System.IO; + namespace UniGLTF.MeshUtility { public class MeshUtilityDialog : EditorWindow { public const string MENU_NAME = "glTF MeshUtility"; + protected const string ASSET_SUFFIX = ".mesh.asset"; public static void OpenWindow() { var window = @@ -21,7 +24,6 @@ namespace UniGLTF.MeshUtility { Freeze, IntegrateSplit, - BoneMeshEraser, } protected Tabs _tab; protected GameObject _exportTarget; @@ -62,10 +64,109 @@ namespace UniGLTF.MeshUtility } } bool IsValid => !_validations.Any(v => !v.CanExport); + + MeshInfo[] integrationResults; + Vector2 _scrollPos; void OnEnable() { + // Clear(HelpMessage.Ready, ValidationError.None); + // OnValidate(); + } + + // 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() + // { + // isValid = false; + // if (m_root == null) + // { + // Clear(HelpMessage.SetTarget, ValidationError.NoTarget); + // return; + // } + + // if (m_root.GetGameObjectType() != GameObjectType.AssetPrefab) + // { + // Clear(HelpMessage.SetTarget, ValidationError.NotPrefab); + // return; + // } + + // if (m_root.transform.parent != null) + // { + // Clear(HelpMessage.InvalidTarget, ValidationError.HasParent); + // return; + // } + + // var backup = m_excludes.ToArray(); + // Clear(HelpMessage.Ready, ValidationError.None); + // isValid = true; + // m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, MeshEnumerateOption.OnlyWithoutBlendShape) + // .SelectMany(x => x.sharedMaterials) + // .Distinct() + // .ToArray(); + + // m_duplicateMaterials = m_uniqueMaterials + // .GroupBy(x => GetMaterialKey(x), x => x) + // .Select(x => new MaterialList(x.ToArray())) + // .Where(x => x.Materials.Length > 1) + // .ToArray() + // ; + + // UpdateExcludes(backup); + // } + + // void UpdateExcludes(ExcludeItem[] backup) + // { + // var exclude_map = new Dictionary(); + // var excludes = new List(); + // foreach (var x in m_root.GetComponentsInChildren()) + // { + // var mesh = x.GetMesh(); + // if (mesh == null) + // { + // continue; + // } + // if (exclude_map.ContainsKey(mesh)) + // { + // continue; + // } + + // var item = new ExcludeItem + // { + // Mesh = mesh, + // }; + // var found = backup.FirstOrDefault(y => y.Mesh == mesh); + // if (found != null) + // { + // item.Exclude = found.Exclude; + // } + // excludes.Add(item); + // exclude_map[mesh] = item; + // } + // m_excludes.AddRange(excludes); + // } + + /// + /// Scene と Prefab で挙動をスイッチする。 + /// + /// - Scene: ヒエラルキーを操作する。Asset の 書き出しはしない。UNDO はする。TODO: 明示的な Asset の書き出し。 + /// - Prefab: 対象をコピーして処理する。Undo は実装しない。結果を Asset として書き出し、処理後にコピーは削除する。 + /// + /// + bool TargetIsPrefab => _exportTarget != null && _exportTarget.scene.name == null; + + protected virtual void DialogMessage() + { + EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_UTILITY.Msg(), MessageType.Info); } private void OnGUI() @@ -74,6 +175,9 @@ namespace UniGLTF.MeshUtility _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); EditorGUIUtility.labelWidth = 200; LanguageGetter.OnGuiSelectLang(); + + DialogMessage(); + var exportTarget = (GameObject)EditorGUILayout.ObjectField( MeshUtilityMessages.TARGET_OBJECT.Msg(), _exportTarget, typeof(GameObject), true); @@ -83,6 +187,14 @@ namespace UniGLTF.MeshUtility MeshIntegration.UpdateMeshIntegrationList(_exportTarget); modified = true; } + + // GameObject or Prefab ? + if (TargetIsPrefab) + { + EditorGUILayout.HelpBox(MeshUtilityMessages.PREFAB_TARGET.Msg(), MessageType.Warning); + } + + // tab bar _tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed); foreach (var validation in _validations) @@ -90,9 +202,6 @@ namespace UniGLTF.MeshUtility validation.DrawGUI(); } - EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_SEPARATOR.Msg(), MessageType.Info); - EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_INTEGRATOR.Msg(), MessageType.Info); - switch (_tab) { case Tabs.Freeze: @@ -113,24 +222,26 @@ namespace UniGLTF.MeshUtility break; } - case Tabs.BoneMeshEraser: - { - // TODO: FirstPerson 処理と統合する - EditorGUILayout.HelpBox(MeshUtilityMessages.BONE_MESH_ERASER.Msg(), MessageType.Info); - // if (_boneMeshEraserEditor) - // { - // _boneMeshEraserEditor.OnInspectorGUI(); - // } - // if (TabBoneMeshRemover.TryExecutable(_exportTarget, _skinnedMeshRenderer, out string msg)) - // { - // processed = TabBoneMeshRemover.OnGUI(_exportTarget, _skinnedMeshRenderer, _eraseBones); - // } - // else - // { - // EditorGUILayout.HelpBox(msg, MessageType.Error); - // } - break; - } + // TODO: + // Mesh統合のオプション + // case Tabs.BoneMeshEraser: + // { + // // TODO: FirstPerson 処理と統合する + // EditorGUILayout.HelpBox(MeshUtilityMessages.BONE_MESH_ERASER.Msg(), MessageType.Info); + // // if (_boneMeshEraserEditor) + // // { + // // _boneMeshEraserEditor.OnInspectorGUI(); + // // } + // // if (TabBoneMeshRemover.TryExecutable(_exportTarget, _skinnedMeshRenderer, out string msg)) + // // { + // // processed = TabBoneMeshRemover.OnGUI(_exportTarget, _skinnedMeshRenderer, _eraseBones); + // // } + // // else + // // { + // // EditorGUILayout.HelpBox(msg, MessageType.Error); + // // } + // break; + // } } EditorGUILayout.EndScrollView(); @@ -144,18 +255,121 @@ namespace UniGLTF.MeshUtility GUI.enabled = true; if (pressed) { - Undo.RegisterFullObjectHierarchyUndo(exportTarget, "MeshUtility"); - foreach (var go in MeshUtility.Process(exportTarget)) + if (TargetIsPrefab) { - Undo.RegisterCreatedObjectUndo(go, "MeshUtility"); + /// [prefab] + /// + /// * backup するのではなく 変更した copy を作成する。元は変えない + /// * copy 先の統合前の renderer を disable で残さず destroy する + /// * 実行すると mesh, blendshape, blendShape を新規に作成する + /// * 新しいヒエラルキーを prefab に保存してから削除して終了する + + // 出力フォルダを決める + var folder = "Assets"; + var prefab = _exportTarget.GetPrefab(); + if (prefab != null) + { + folder = AssetDatabase.GetAssetPath(prefab); + // Debug.Log(folder); + } + // 新規で作成されるアセットはすべてこのフォルダの中に作る。上書きチェックはしない + var assetFolder = EditorUtility.SaveFolderPanel("select asset save folder", Path.GetDirectoryName(folder), "VrmIntegrated"); + var unityPath = UniGLTF.UnityPath.FromFullpath(assetFolder); + if (!unityPath.IsUnderWritableFolder) + { + EditorUtility.DisplayDialog("asset folder", "Target folder must be in the Assets or writable Packages folder", "cancel"); + return; + } + assetFolder = unityPath.Value; + + var copy = GameObject.Instantiate(_exportTarget); + + var (results, created) = MeshUtility.Process(copy); + + WriteAssets(copy, assetFolder, results); + + // destroy scene + UnityEngine.Object.DestroyImmediate(copy); } + else + { + Undo.RegisterFullObjectHierarchyUndo(_exportTarget, "MeshUtility"); + var (results, created) = MeshUtility.Process(_exportTarget); + foreach (var go in created) + { + Undo.RegisterCreatedObjectUndo(go, "MeshUtility"); + } + } + + // TODO: Show Result ? _exportTarget = null; - // Show Result ? - // Close(); - // GUIUtility.ExitGUI(); } } + void WriteAssets(GameObject copy, string assetFolder, List results) + { + // + // write mesh asset + foreach (var result in results) + { + var childAssetPath = $"{assetFolder}/{result.Integrated.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"; + Debug.LogFormat("CreateAsset: {0}", childAssetPath); + AssetDatabase.CreateAsset(result.Integrated.IntegratedRenderer.sharedMesh, childAssetPath); + } + + // 統合した結果をヒエラルキーに追加する + foreach (var result in results) + { + if (result.Integrated.IntegratedRenderer != null) + { + result.Integrated.IntegratedRenderer.transform.SetParent(copy.transform, false); + } + } + + // 統合した結果を反映した BlendShapeClip を作成して置き換える + // var clips = VRMMeshIntegratorUtility.FollowBlendshapeRendererChange(results, copy, assetFolder); + + // 用が済んだ 統合前 の renderer を削除する + foreach (var result in results) + { + foreach (var renderer in result.SourceMeshRenderers) + { + GameObject.DestroyImmediate(renderer); + } + foreach (var renderer in result.SourceSkinnedMeshRenderers) + { + GameObject.DestroyImmediate(renderer); + } + } + + // reset firstperson + // var firstperson = copy.GetComponent(); + // if (firstperson != null) + // { + // firstperson.Reset(); + // } + + // prefab + var prefabPath = $"{assetFolder}/VrmIntegrated.prefab"; + Debug.Log(prefabPath); + PrefabUtility.SaveAsPrefabAsset(copy, prefabPath, out bool success); + if (!success) + { + throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {prefabPath}"); + } + + // var prefabReference = AssetDatabase.LoadAssetAtPath(prefabPath); + // foreach (var clip in clips) + // { + // var so = new SerializedObject(clip); + // so.Update(); + // // clip.Prefab = copy; + // var prop = so.FindProperty("m_prefab"); + // prop.objectReferenceValue = prefabReference; + // so.ApplyModifiedProperties(); + // } + } + protected bool ToggleIsModified(string label, ref bool value) { var newValue = EditorGUILayout.Toggle(label, value); diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs index f87c06c5b..c48251e22 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs @@ -8,61 +8,80 @@ namespace UniGLTF.MeshUtility [LangMsg(Languages.en, "TargetObject")] TARGET_OBJECT, - [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer にアタッチされたメッシュを、 BlendShape の有無で分割します。 + [LangMsg(Languages.ja, @"凍結 > 統合 > 分割 という一連の処理を実行します。 -* Asset: 新しい Mesh Asset が元と同じフォルダに作成されます。例: Original -> Original_WithBlendShape.mesh & Original_WithoutBlendShape.mesh -* Scene: コピーされたヒエラルキーでは、分割された Mesh は BlendShape のある Mesh で置き換えられて、BlendShape の無い Mesh を使った SkinnedMeshRenderer が追加されます。 +[凍結] +- ヒエラルキーの 回転・拡縮を Mesh に焼き付けます。 +- BlendShape の現状を Mesh に焼き付けます。 + +[統合] +- ヒエラルキーに含まれる MeshRenderer と SkinnedMeshRenderer をひとつの SkinnedMeshRenderer に統合します。 + +[分割] +- 統合結果を BlendShape の有無を基準に分割します。 + +[Scene と Prefab] +Scene と Prefab で挙動が異なります。 + +(Scene/Runtime) +- 対象のヒエラルキーを変更します。UNDO可能。 +- Asset の書き出しはしません。Unityを再起動すると、書き出していない Mesh などの Asset が消滅します。 + +(Prefab/Editor) +- 対象の prefab をシーンにコピーして処理を実行し、生成する Asset を指定されたフォルダに書き出します。 +- Asset 書き出し後にコピーを削除します。 +- Undo はありません。 ")] - [LangMsg(Languages.en, @"Separate the mesh attached to the SkinnedMeshRenderer under the target object with or without BlendShape. + [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, - + MESH_UTILITY, [LangMsg(Languages.ja, "ブレンドシェイプの有無で分割する")] [LangMsg(Languages.en, "Divide by the presence or absence of `blendshape`")] MESH_SEPARATOR_BY_BLENDSHAPE, - [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュを統合します。 +// [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュを統合します。 -* Asset: Assets/MeshIntegrated.mesh が作成されます(上書きされるので注意してください)。 -* Scene: コピーされたヒエラルキーでは、統合された Mesh は除去されます。新しく MeshIntegrator ノードが追加されます。 -* VRMではBlendShapeClipの統合など追加の処理が必要です。`VRM0-MeshIntegratorWizard` を使ってください。 -")] - [LangMsg(Languages.en, @"Integrates the attached mesh into the SkinnedMeshRenderer or MeshFilter under the target object. +// * Asset: Assets/MeshIntegrated.mesh が作成されます(上書きされるので注意してください)。 +// * Scene: コピーされたヒエラルキーでは、統合された Mesh は除去されます。新しく MeshIntegrator ノードが追加されます。 +// * VRMではBlendShapeClipの統合など追加の処理が必要です。`VRM0-MeshIntegratorWizard` を使ってください。 +// ")] +// [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 `VRM0-MeshIntegratorWizard` integration feature. -")] - MESH_INTEGRATOR, +// * 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 `VRM0-MeshIntegratorWizard` integration feature. +// ")] +// MESH_INTEGRATOR, - // [LangMsg(Languages.ja, "静的メッシュを一つに統合します")] - // [LangMsg(Languages.en, "Integrate static meshes into one")] - // STATIC_MESH_INTEGRATOR, +// // [LangMsg(Languages.ja, "静的メッシュを一つに統合します")] +// // [LangMsg(Languages.en, "Integrate static meshes into one")] +// // STATIC_MESH_INTEGRATOR, - [LangMsg(Languages.ja, @"指定された SkinnedMeshRenderer から、指定されたボーンに対する Weight を保持する三角形を除去します。 +// [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. +// * 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. -")] +// * 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, +// 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, "Erase Rootを選んでください")] + // [LangMsg(Languages.en, "Select a erase root")] + // SELECT_ERASE_ROOT, [LangMsg(Languages.ja, "GameObjectを選んでください")] [LangMsg(Languages.en, "Select a GameObject first")] @@ -72,9 +91,9 @@ namespace UniGLTF.MeshUtility [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, "No static mesh is contained")] + // NO_STATIC_MESH, [LangMsg(Languages.ja, "GameObjectにスキンメッシュ・静的メッシュが含まれていません")] [LangMsg(Languages.en, "Skinned/Static mesh is not contained")] @@ -83,5 +102,9 @@ namespace UniGLTF.MeshUtility [LangMsg(Languages.ja, "BlendShapeClipが不整合を起こすので、`VRM0-> MeshIntegrator`を使ってください")] [LangMsg(Languages.en, "Because BlendShapeClip causes inconsistency , use `VRM0 -> MeshIntegrator` instead")] VRM_DETECTED, + + [LangMsg(Languages.ja, "対象は, Prefab です。実行時に書き出しファイルの指定があります。")] + [LangMsg(Languages.en, "The target is prefab. A temporary file is specified during execution.")] + PREFAB_TARGET, } } \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs index 41e1132f5..c7ae49c62 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs @@ -155,7 +155,7 @@ namespace UniGLTF.MeshUtility return MeshIntegrationGroups.ToList(); } - public virtual List Process(GameObject go) + public virtual (List, List) Process(GameObject go) { // TODO unpack prefab @@ -181,48 +181,45 @@ namespace UniGLTF.MeshUtility var copy = CopyMeshIntegrationGroups(); - var newGo = new List(); + var newList = new List(); + + var empty = GetOrCreateEmpty(go, "mesh"); + + var results = new List(); + foreach (var group in copy) { - var empty = GetOrCreateEmpty(go, "mesh"); + var (result, newGo) = Integrate(empty, group); + results.Add(result); + newList.AddRange(newGo); + } - var results = new List(); - foreach (var group in copy) + foreach (var result in results) + { + foreach (var r in result.SourceMeshRenderers) { - Integrate(newGo, empty, results, group); + RemoveComponent(r); } - - foreach (var result in results) + foreach (var r in result.SourceSkinnedMeshRenderers) { - foreach (var r in result.SourceMeshRenderers) - { - RemoveComponent(r); - } - foreach (var r in result.SourceSkinnedMeshRenderers) - { - RemoveComponent(r); - } + RemoveComponent(r); } } MeshIntegrationGroups.Clear(); - return newGo; + return (results, newList); } - protected virtual MeshIntegrationResult Integrate(List newGo, - GameObject empty, List results, MeshIntegrationGroup group) + protected virtual (MeshIntegrationResult, GameObject[]) Integrate(GameObject empty, + MeshIntegrationGroup group) { var result = MeshIntegrator.Integrate(group, SplitByBlendShape ? MeshIntegrator.BlendShapeOperation.Split : MeshIntegrator.BlendShapeOperation.Use); - results.Add(result); - foreach (var created in result.AddIntegratedRendererTo(empty)) - { - newGo.Add(created); - } + var newGo = result.AddIntegratedRendererTo(empty).ToArray(); - return result; + return (result, newGo); } } } diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs index 6249dcbf3..73610670f 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs @@ -1,370 +1,88 @@ #pragma warning disable 0414, 0649 using UnityEditor; using UnityEngine; -using System.Linq; -using System; -using System.Collections.Generic; -using UniGLTF.MeshUtility; -using System.IO; using UniGLTF.M17N; + namespace VRM { - public class VrmMeshIntegratorWizard : ScriptableWizard + public class VrmMeshIntegratorWizard : UniGLTF.MeshUtility.MeshUtilityDialog { - public const string MENU_NAME = "VRM 0.x MeshUtility"; - const string ASSET_SUFFIX = ".mesh.asset"; + public new const string MENU_NAME = "VRM 0.x MeshUtility"; - enum HelpMessage + public new static void OpenWindow() { - Ready, - SetTarget, - InvalidTarget, + var window = + (VrmMeshIntegratorWizard)EditorWindow.GetWindow(typeof(VrmMeshIntegratorWizard)); + window.titleContent = new GUIContent(MENU_NAME); + window.Show(); } - enum ValidationError + // void Integrate() + // { + // // 統合 + // var excludes = m_excludes.Where(x => x.Exclude).Select(x => x.Mesh); + // var results = Integrate(copy, excludes, m_separateByBlendShape); + + // } + + // static List Integrate(GameObject root, IEnumerable excludes, bool separateByBlendShape) + // { + // var results = new List(); + // if (separateByBlendShape) + // { + // results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes)); + // results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes)); + // } + // else + // { + // results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes)); + // } + // return results; + // } + + protected override void DialogMessage() { - None, - NoTarget, - HasParent, - NotPrefab, + EditorGUILayout.HelpBox(Message.MESH_UTILITY.Msg(), MessageType.Info); } - - [SerializeField] - GameObject m_root; - - [SerializeField] - bool m_separateByBlendShape = true; - - [Header("Validation")] - [SerializeField] - Material[] m_uniqueMaterials; - - [Serializable] - struct MaterialKey + enum Message { - public string Shader; - public KeyValuePair[] Properties; + [LangMsg(Languages.ja, @"(VRM-0.x専用) 凍結 > 統合 > 分割 という一連の処理を実行します。 - public override bool Equals(object obj) - { - if (!(obj is MaterialKey)) - { - return base.Equals(obj); - } +[凍結] +- ヒエラルキーの 回転・拡縮を Mesh に焼き付けます。 +- BlendShape の現状を Mesh に焼き付けます。 - var key = (MaterialKey)obj; +- VRM-0.x の正規化処理です。 +- HumanoidAvatar の再生成。 +- BlendShapeClip, SpringBone, Constraint なども影響を受けます。 - return Shader == key.Shader - && Properties.SequenceEqual(key.Properties) - ; - } +[統合] +- ヒエラルキーに含まれる MeshRenderer と SkinnedMeshRenderer をひとつの SkinnedMeshRenderer に統合します。 - public override int GetHashCode() - { - return base.GetHashCode(); - } - } +- VRM の FirstPerson 設定に応じて3種類(BOTH, FirstPerson, ThirdPerson) にグループ化して統合します。 +- FirstPerson=AUTO を前処理できます。 + - 元の Mesh は ThirdPerson として処理されます。頭なしのモデルを追加生成して FirstPersonOnly とします。 - [Serializable] - struct MaterialList - { - public Material[] Materials; +[分割] +- 統合結果を BlendShape の有無を基準に分割します。 +- BOTH, FirstPerson, ThirdPerson x 2 で、最大で 6Mesh になります。空の部分ができることが多いので 3Mesh くらいが多くなります。 - public MaterialList(Material[] list) - { - Materials = list; - } - } - [SerializeField] - MaterialList[] m_duplicateMaterials; +[Scene と Prefab] +Scene と Prefab で挙動が異なります。 - [Serializable] - public class ExcludeItem - { - public Mesh Mesh; - public bool Exclude; - } +(Scene/Runtime) +- 対象のヒエラルキーを変更します。UNDO可能。 +- Asset の書き出しはしません。Unityを再起動すると、書き出していない Mesh などの Asset が消滅します。 - [Header("Options")] - [SerializeField] - List m_excludes = new List(); - - [Header("Result")] - [SerializeField] - MeshInfo[] integrationResults; - - public static void CreateWizard() - { - ScriptableWizard.DisplayWizard(MENU_NAME, "Integrate and close window", "Integrate"); - } - - private void OnEnable() - { - Clear(HelpMessage.Ready, ValidationError.None); - OnValidate(); - } - - protected override bool DrawWizardGUI() - { - var t = m_root.GetGameObjectType(); - EditorGUILayout.HelpBox($"{t}", MessageType.Info); - return base.DrawWizardGUI(); - } - - static object GetPropertyValue(Shader shader, int i, Material m) - { - var propType = ShaderUtil.GetPropertyType(shader, i); - switch (propType) - { - case ShaderUtil.ShaderPropertyType.Color: - return m.GetColor(ShaderUtil.GetPropertyName(shader, i)); - - case ShaderUtil.ShaderPropertyType.Range: - case ShaderUtil.ShaderPropertyType.Float: - return m.GetFloat(ShaderUtil.GetPropertyName(shader, i)); - - case ShaderUtil.ShaderPropertyType.Vector: - return m.GetVector(ShaderUtil.GetPropertyName(shader, i)); - - case ShaderUtil.ShaderPropertyType.TexEnv: - return m.GetTexture(ShaderUtil.GetPropertyName(shader, i)); - - default: - throw new NotImplementedException(propType.ToString()); - } - } - - static MaterialKey GetMaterialKey(Material m) - { - var key = new MaterialKey - { - Shader = m.shader.name, - }; - - key.Properties = Enumerable.Range(0, ShaderUtil.GetPropertyCount(m.shader)) - .Select(x => new KeyValuePair( - ShaderUtil.GetPropertyName(m.shader, x), - GetPropertyValue(m.shader, x, m)) - ) - .OrderBy(x => x.Key) - .ToArray() - ; - - 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() - { - isValid = false; - if (m_root == null) - { - Clear(HelpMessage.SetTarget, ValidationError.NoTarget); - return; - } - - if (m_root.GetGameObjectType() != GameObjectType.AssetPrefab) - { - Clear(HelpMessage.SetTarget, ValidationError.NotPrefab); - return; - } - - if (m_root.transform.parent != null) - { - Clear(HelpMessage.InvalidTarget, ValidationError.HasParent); - return; - } - - var backup = m_excludes.ToArray(); - Clear(HelpMessage.Ready, ValidationError.None); - isValid = true; - m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, MeshEnumerateOption.OnlyWithoutBlendShape) - .SelectMany(x => x.sharedMaterials) - .Distinct() - .ToArray(); - - m_duplicateMaterials = m_uniqueMaterials - .GroupBy(x => GetMaterialKey(x), x => x) - .Select(x => new MaterialList(x.ToArray())) - .Where(x => x.Materials.Length > 1) - .ToArray() - ; - - UpdateExcludes(backup); - } - - void UpdateExcludes(ExcludeItem[] backup) - { - var exclude_map = new Dictionary(); - var excludes = new List(); - foreach (var x in m_root.GetComponentsInChildren()) - { - var mesh = x.GetMesh(); - if (mesh == null) - { - continue; - } - if (exclude_map.ContainsKey(mesh)) - { - continue; - } - - var item = new ExcludeItem - { - Mesh = mesh, - }; - var found = backup.FirstOrDefault(y => y.Mesh == mesh); - if (found != null) - { - item.Exclude = found.Exclude; - } - excludes.Add(item); - exclude_map[mesh] = item; - } - m_excludes.AddRange(excludes); - } - - void OnWizardUpdate() - { - } - - /// 2022.05 仕様変更 - /// - /// * prefab 専用 - /// * backup するのではなく 変更した copy を作成する。元は変えない - /// * copy 先の統合前の renderer を disable で残さず destroy する - /// * 実行すると mesh, blendshape, blendShape を新規に作成する - /// * 新しいヒエラルキーを prefab に保存してから削除して終了する - /// - void Integrate() - { - if (m_root.GetGameObjectType() != GameObjectType.AssetPrefab) - { - throw new Exception("for prefab only"); - } - - String folder = "Assets"; - var prefab = m_root.GetPrefab(); - if (prefab != null) - { - folder = AssetDatabase.GetAssetPath(prefab); - Debug.Log(folder); - } - - // 新規で作成されるアセットはすべてこのフォルダの中に作る。上書きチェックはしない - var assetFolder = EditorUtility.SaveFolderPanel("select asset save folder", Path.GetDirectoryName(folder), "VrmIntegrated"); - var unityPath = UniGLTF.UnityPath.FromFullpath(assetFolder); - if (!unityPath.IsUnderWritableFolder) - { - EditorUtility.DisplayDialog("asset folder", "Target folder must be in the Assets or writable Packages folder", "cancel"); - return; - } - assetFolder = unityPath.Value; - - var copy = GameObject.Instantiate(m_root); - - // 統合 - var excludes = m_excludes.Where(x => x.Exclude).Select(x => x.Mesh); - var results = Integrate(copy, excludes, m_separateByBlendShape); - - // write mesh asset - foreach (var result in results) - { - var childAssetPath = $"{assetFolder}/{result.Integrated.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"; - Debug.LogFormat("CreateAsset: {0}", childAssetPath); - AssetDatabase.CreateAsset(result.Integrated.IntegratedRenderer.sharedMesh, childAssetPath); - } - - // 統合した結果をヒエラルキーに追加する - foreach (var result in results) - { - if (result.Integrated.IntegratedRenderer != null) - { - result.Integrated.IntegratedRenderer.transform.SetParent(copy.transform, false); - } - } - - // 統合した結果を反映した BlendShapeClip を作成して置き換える - var clips = VRMMeshIntegratorUtility.FollowBlendshapeRendererChange(results, copy, assetFolder); - - // 用が済んだ 統合前 の renderer を削除する - foreach (var result in results) - { - foreach (var renderer in result.SourceMeshRenderers) - { - GameObject.DestroyImmediate(renderer); - } - foreach (var renderer in result.SourceSkinnedMeshRenderers) - { - GameObject.DestroyImmediate(renderer); - } - } - - // reset firstperson - var firstperson = copy.GetComponent(); - if (firstperson != null) - { - firstperson.Reset(); - } - - // prefab - var prefabPath = $"{assetFolder}/VrmIntegrated.prefab"; - Debug.Log(prefabPath); - PrefabUtility.SaveAsPrefabAsset(copy, prefabPath, out bool success); - if (!success) - { - throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {prefabPath}"); - } - - // destroy scene - UnityEngine.Object.DestroyImmediate(copy); - - var prefabReference = AssetDatabase.LoadAssetAtPath(prefabPath); - foreach (var clip in clips) - { - var so = new SerializedObject(clip); - so.Update(); - // clip.Prefab = copy; - var prop = so.FindProperty("m_prefab"); - prop.objectReferenceValue = prefabReference; - so.ApplyModifiedProperties(); - } - } - - static List Integrate(GameObject root, IEnumerable excludes, bool separateByBlendShape) - { - var results = new List(); - if (separateByBlendShape) - { - results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes)); - results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes)); - } - else - { - results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes)); - } - return results; - } - - void OnWizardCreate() - { - Integrate(); - // close - } - - void OnWizardOtherButton() - { - Integrate(); +(Prefab/Editor) +- 対象の prefab をシーンにコピーして処理を実行し、生成する Asset を指定されたフォルダに書き出します。 +- Asset 書き出し後にコピーを削除します。 +- Undo はありません。 +")] + [LangMsg(Languages.en, @"TODO +")] + MESH_UTILITY, } } } diff --git a/Assets/VRM/Editor/VrmTopMenu.cs b/Assets/VRM/Editor/VrmTopMenu.cs index 6f130f3fc..7bcfe8c36 100644 --- a/Assets/VRM/Editor/VrmTopMenu.cs +++ b/Assets/VRM/Editor/VrmTopMenu.cs @@ -25,7 +25,7 @@ namespace VRM [MenuItem(UserMenuPrefix + "/" + VrmMeshIntegratorWizard.MENU_NAME, false, 3)] - private static void OpenMeshIntegratorWizard() => VrmMeshIntegratorWizard.CreateWizard(); + private static void OpenMeshIntegratorWizard() => VrmMeshIntegratorWizard.OpenWindow(); [MenuItem(UserMenuPrefix + "/" + VRMHumanoidNormalizerMenu.MENU_NAME, true, 51)] diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs index 138421761..eb4afcbdd 100644 --- a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs @@ -1,6 +1,8 @@ using UnityEngine; using UnityEditor; using UniGLTF; +using UniGLTF.M17N; + namespace UniVRM10 { @@ -57,5 +59,50 @@ namespace UniVRM10 var mod = base.MeshIntegrateGui(); return firstPerson || mod; } + + protected override void DialogMessage() + { + EditorGUILayout.HelpBox(Message.MESH_UTILITY.Msg(), MessageType.Info); + } + enum Message + { + [LangMsg(Languages.ja, @"(VRM-1.0専用) 凍結 > 統合 > 分割 という一連の処理を実行します。 + +[凍結] +- ヒエラルキーの 回転・拡縮を Mesh に焼き付けます。 +- BlendShape の現状を Mesh に焼き付けます。 + +- VRM-1.0 では正規化は必須でなくなりました。任意のオプションです。 +- VRM-1.0 でも拡縮の凍結は推奨しています。 +- HumanoidAvatar の再生成。 +- Expression, SpringBone, Constraint なども影響を受けます。 + +[統合] +- ヒエラルキーに含まれる MeshRenderer と SkinnedMeshRenderer をひとつの SkinnedMeshRenderer に統合します。 + +- VRM の FirstPerson 設定に応じて3種類(BOTH, FirstPerson, ThirdPerson) にグループ化して統合します。 +- FirstPerson=AUTO を前処理できます。 + - 元の Mesh は ThirdPerson として処理されます。頭なしのモデルを追加生成して FirstPersonOnly とします。 + +[分割] +- 統合結果を BlendShape の有無を基準に分割します。 +- BOTH, FirstPerson, ThirdPerson x 2 で、最大で 6Mesh になります。空の部分ができることが多いので 3Mesh くらいが多くなります。 + +[Scene と Prefab] +Scene と Prefab で挙動が異なります。 + +(Scene/Runtime) +- 対象のヒエラルキーを変更します。UNDO可能。 +- Asset の書き出しはしません。Unityを再起動すると、書き出していない Mesh などの Asset が消滅します。 + +(Prefab/Editor) +- 対象の prefab をシーンにコピーして処理を実行し、生成する Asset を指定されたフォルダに書き出します。 +- Asset 書き出し後にコピーを削除します。 +- Undo はありません。 +")] + [LangMsg(Languages.en, @"TODO +")] + MESH_UTILITY, + } } } \ No newline at end of file diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs index 60e665dd5..d9e17f84a 100644 --- a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs +++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using UniGLTF.MeshUtility; using UniHumanoid; using UnityEngine; +using UnityEngine.Rendering; namespace UniVRM10 { @@ -37,12 +39,12 @@ namespace UniVRM10 return copy; } - protected override UniGLTF.MeshUtility.MeshIntegrationResult Integrate(List newGo, + protected override + (UniGLTF.MeshUtility.MeshIntegrationResult, GameObject[]) Integrate( GameObject empty, - List results, UniGLTF.MeshUtility.MeshIntegrationGroup group) { - var result = base.Integrate(newGo, empty, results, group); + var (result, newList) = base.Integrate(empty, group); if (_generateFirstPerson && group.Name == "auto") { @@ -57,14 +59,14 @@ namespace UniVRM10 } } - return result; + return (result, newList); } Vrm10Instance _vrmInstance = null; /// /// glTF に比べて Humanoid や FirstPerson の処理が追加される /// - public override List Process(GameObject go) + public override (List, List) Process(GameObject go) { _vrmInstance = go.GetComponent(); if (_vrmInstance == null) @@ -85,7 +87,7 @@ namespace UniVRM10 // TODO: update: spring // TODO: update: constraint // TODO: update: firstPerson offset - var list = base.Process(go); + var (list, newList) = base.Process(go); if (FreezeBlendShape || FreezeRotation || FreezeScaling) { @@ -94,7 +96,7 @@ namespace UniVRM10 animator.avatar = newAvatar; } - return list; + return (list, newList); } void _ProcessFirstPerson(Vrm10Instance vrmInstance, UniGLTF.MeshUtility.MeshInfo info)