diff --git a/.vscode/settings.json b/.vscode/settings.json index b0ac5b0e0..1b47395b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -54,6 +54,7 @@ }, "cSpell.words": [ "GLTF", + "Scriptable", "UNIVRM" ] } \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs deleted file mode 100644 index 212e77b9b..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs +++ /dev/null @@ -1,43 +0,0 @@ -using UniGLTF.M17N; -using UnityEditor; -using UnityEngine; - -namespace UniGLTF.MeshUtility -{ - /// - /// BoneMeshRemover 向けのエディタ。 - /// - /// SerializedProperty 経由で ユーザー定義 struct のフィールド - /// public List _eraseBones; - /// を EditorGUILayout.PropertyField するための細工である。 - /// - /// SerializedObject は UnityEngine.Object から作成するので、 - /// UnityEngine.Object を継承したクラスのフィールドに ユーザー定義 struct を配置する。 - /// 持ち主の SerializedObject を経由して EditorGUILayout.PropertyField してる。 - /// - [CustomEditor(typeof(MeshUtilityDialog), true)] - class BoneMeshEraserEditor : Editor - { - MeshUtilityDialog _targetDialog; - SerializedProperty _skinnedMesh; - SerializedProperty _eraseBones; - - void OnEnable() - { - _targetDialog = target as MeshUtilityDialog; - if (_targetDialog) - { - _skinnedMesh = serializedObject.FindProperty(nameof(MeshUtilityDialog._skinnedMeshRenderer)); - _eraseBones = serializedObject.FindProperty(nameof(MeshUtilityDialog._eraseBones)); - } - } - - public override void OnInspectorGUI() - { - serializedObject.Update(); - EditorGUILayout.PropertyField(_skinnedMesh); - EditorGUILayout.PropertyField(_eraseBones); - serializedObject.ApplyModifiedProperties(); - } - } -} diff --git a/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshIntegrationTab.cs similarity index 75% rename from Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs rename to Assets/UniGLTF/Editor/MeshUtility/MeshIntegrationTab.cs index fb3d5c0fb..6c9c6ec93 100644 --- a/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshIntegrationTab.cs @@ -1,22 +1,21 @@ using System.Collections.Generic; -using UniGLTF.MeshUtility; using UnityEditor; using UnityEditorInternal; using UnityEngine; -namespace UniVRM10 +namespace UniGLTF.MeshUtility { - class MeshIntegrationTab + public class MeshIntegrationTab { bool _modified = false; - Vrm10MeshUtility _meshUti; + protected GltfMeshUtility _meshUtil; Splitter _splitter; ReorderableList _groupList; ReorderableList _rendererList; public List _renderers = new List(); - int _selected = -1; - int Selected + protected int _selected = -1; + protected int Selected { set { @@ -24,29 +23,29 @@ namespace UniVRM10 { return; } - if (value < 0 || value >= _meshUti.MeshIntegrationGroups.Count) + if (value < 0 || value >= _meshUtil.MeshIntegrationGroups.Count) { return; } _selected = value; _renderers.Clear(); - _renderers.AddRange(_meshUti.MeshIntegrationGroups[_selected].Renderers); + _renderers.AddRange(_meshUtil.MeshIntegrationGroups[_selected].Renderers); } } - public MeshIntegrationTab(EditorWindow editor, Vrm10MeshUtility meshUtility) + public MeshIntegrationTab(EditorWindow editor, GltfMeshUtility meshUtility) { - _meshUti = meshUtility; + _meshUtil = meshUtility; _splitter = new VerticalSplitter(editor, 200, 50); - _groupList = new ReorderableList(_meshUti.MeshIntegrationGroups, typeof(MeshIntegrationGroup)); + _groupList = new ReorderableList(_meshUtil.MeshIntegrationGroups, typeof(MeshIntegrationGroup)); _groupList.drawHeaderCallback = (Rect rect) => { GUI.Label(rect, "Integration group"); }; _groupList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { - var group = _meshUti.MeshIntegrationGroups[index]; + var group = _meshUtil.MeshIntegrationGroups[index]; EditorGUI.TextField(rect, group.Name); }; _groupList.onSelectCallback = rl => @@ -69,8 +68,7 @@ namespace UniVRM10 public void UpdateMeshIntegrationList(GameObject root) { _selected = -1; - _meshUti.MeshIntegrationGroups.Clear(); - _meshUti.IntegrateFirstPerson(root); + _meshUtil.UpdateMeshIntegrationGroups(root); Selected = 0; } diff --git a/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MeshIntegrationTab.cs.meta similarity index 83% rename from Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs.meta rename to Assets/UniGLTF/Editor/MeshUtility/MeshIntegrationTab.cs.meta index 91a743257..3ed1e2492 100644 --- a/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs.meta +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshIntegrationTab.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 07d031dea01a55c43b1ec68cd10bf461 +guid: 27b74253f485b4b45a8d2847ec3c2b34 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs index 17eeca36a..3ef6ff0de 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs @@ -2,34 +2,16 @@ using UnityEngine; using UnityEditor; using UniGLTF.M17N; using System.Collections.Generic; +using System.Linq; +using System; + namespace UniGLTF.MeshUtility { public class MeshUtilityDialog : EditorWindow { public const string MENU_NAME = "glTF MeshUtility"; - enum MeshProcessDialogTabs - { - MeshSeparator, - MeshIntegrator, - BoneMeshEraser, - } - MeshProcessDialogTabs _tab; - - private GameObject _exportTarget; - - [SerializeField] - public bool _separateByBlendShape = true; - - [SerializeField] - public SkinnedMeshRenderer _skinnedMeshRenderer = null; - - [SerializeField] - public List _eraseBones; - - private BoneMeshEraserEditor _boneMeshEraserEditor; - private Vector2 _scrollPos = new Vector2(0, 0); - + protected const string ASSET_SUFFIX = ".mesh.asset"; public static void OpenWindow() { var window = @@ -38,82 +20,301 @@ namespace UniGLTF.MeshUtility window.Show(); } - private void OnEnable() + protected enum Tabs { - if (!_boneMeshEraserEditor) + Freeze, + IntegrateSplit, + } + protected Tabs _tab; + protected GameObject _exportTarget; + + GltfMeshUtility _meshUtil; + protected virtual GltfMeshUtility MeshUtility + { + get { - _boneMeshEraserEditor = (BoneMeshEraserEditor)Editor.CreateEditor(this); + if (_meshUtil == null) + { + _meshUtil = new GltfMeshUtility(); + } + return _meshUtil; } } + MeshIntegrationTab _integrationTab; + protected virtual MeshIntegrationTab MeshIntegration + { + get + { + if (_integrationTab == null) + { + _integrationTab = new MeshIntegrationTab(this, MeshUtility); + } + return _integrationTab; + } + } + + protected List _validations = new List(); + protected virtual void Validate() + { + _validations.Clear(); + if (_exportTarget == null) + { + _validations.Add(Validation.Error("set target GameObject")); + return; + } + } + bool IsValid => !_validations.Any(v => !v.CanExport); + + MeshInfo[] integrationResults; + + Vector2 _scrollPos; + + void OnEnable() + { + } + + protected virtual void DialogMessage() + { + EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_UTILITY.Msg(), MessageType.Info); + } private void OnGUI() { - _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); + var modified = false; EditorGUIUtility.labelWidth = 200; LanguageGetter.OnGuiSelectLang(); - _exportTarget = (GameObject)EditorGUILayout.ObjectField(MeshUtilityMessages.TARGET_OBJECT.Msg(), _exportTarget, typeof(GameObject), true); + + DialogMessage(); + + var exportTarget = (GameObject)EditorGUILayout.ObjectField( + MeshUtilityMessages.TARGET_OBJECT.Msg(), + _exportTarget, typeof(GameObject), true); + if (exportTarget != _exportTarget) + { + _exportTarget = exportTarget; + MeshIntegration.UpdateMeshIntegrationList(_exportTarget); + modified = true; + } + if (_exportTarget == null) + { + return; + } + + _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); + + // GameObject or Prefab ? + switch (_exportTarget.GetPrefabType()) + { + case UnityExtensions.PrefabType.PrefabAsset: + EditorGUILayout.HelpBox(MeshUtilityMessages.PREFAB_ASSET.Msg(), MessageType.Warning); + break; + + case UnityExtensions.PrefabType.PrefabInstance: + EditorGUILayout.HelpBox(MeshUtilityMessages.PREFAB_INSTANCE.Msg(), MessageType.Warning); + break; + } + + // tab bar _tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed); - var processed = false; + foreach (var validation in _validations) + { + validation.DrawGUI(); + } + switch (_tab) { - case MeshProcessDialogTabs.MeshSeparator: + case Tabs.Freeze: { - EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_SEPARATOR.Msg(), MessageType.Info); - if (TabMeshSeparator.TryExecutable(_exportTarget, out string msg)) + if (MeshFreezeGui()) { - processed = TabMeshSeparator.OnGUI(_exportTarget); - } - else - { - EditorGUILayout.HelpBox(msg, MessageType.Error); + modified = true; } break; } - case MeshProcessDialogTabs.MeshIntegrator: + case Tabs.IntegrateSplit: { - EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_INTEGRATOR.Msg(), MessageType.Info); - _separateByBlendShape = EditorGUILayout.Toggle(MeshUtilityMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg(), _separateByBlendShape); - if (TabMeshIntegrator.TryExecutable(_exportTarget, out string msg)) + if (MeshIntegrateGui()) { - if (GUILayout.Button("Process", GUILayout.MinWidth(100))) - { - processed = TabMeshIntegrator.Execute(_exportTarget, _separateByBlendShape); - } - } - else - { - EditorGUILayout.HelpBox(msg, MessageType.Error); + modified = true; } break; } - case MeshProcessDialogTabs.BoneMeshEraser: - { - 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(); - if (processed) + if (modified) { - Close(); - GUIUtility.ExitGUI(); + Validate(); } + + GUI.enabled = IsValid; + var pressed = GUILayout.Button("Process", GUILayout.MinWidth(100)); + GUI.enabled = true; + if (pressed) + { + if (_exportTarget.GetPrefabType() == UnityExtensions.PrefabType.PrefabAsset) + { + /// [prefab] + /// + /// * prefab から instance を作る + /// * instance に対して 焼き付け, 統合, 分離 を実行する + /// * instance のヒエラルキーが改変され、mesh 等のアセットは改変版が作成される(元は変わらない) + /// * instance を asset に保存してから prefab を削除して終了する + /// + UnityPath assetFolder = default; + try + { + assetFolder = PrefabContext.GetOutFolder(_exportTarget); + } + catch (Exception) + { + EditorUtility.DisplayDialog("asset folder", "Target folder must be in the Assets or writable Packages folder", "cancel"); + return; + } + + using (var context = new PrefabContext(_exportTarget, assetFolder)) + { + try + { + // prefab が instantiate されていた場合に + // Mesh統合設定を instantiate に置き換える + var groupCopy = MeshUtility.CopyInstantiate(_exportTarget, context.Instance); + + var (results, created) = MeshUtility.Process(context.Instance, groupCopy); + + // TODO: this should be replaced export and reimport ? + WriteAssets(context.AssetFolder, context.Instance, results); + WritePrefab(context.AssetFolder, context.Instance); + } + catch (Exception ex) + { +#if DEBUG + Debug.LogException(ex, context.Instance); + context.Keep = true; +#endif + } + } + } + else + { + using (var context = new UndoContext("MeshUtility", _exportTarget)) + { + var (results, created) = MeshUtility.Process(_exportTarget, MeshUtility.MeshIntegrationGroups); + MeshUtility.Clear(results); + + foreach (var go in created) + { + // 処理後の mesh をアタッチした Renderer.gameobject + Undo.RegisterCreatedObjectUndo(go, "MeshUtility"); + } + } + } + + // TODO: Show Result ? + _exportTarget = null; + } + } + + /// + /// Write Mesh + /// + protected virtual void WriteAssets(string assetFolder, GameObject instance, List results) + { + foreach (var result in results) + { + if (result.Integrated != null) + { + var childAssetPath = $"{assetFolder}/{result.Integrated.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"; + Debug.LogFormat("CreateAsset: {0}", childAssetPath); + AssetDatabase.CreateAsset(result.Integrated.IntegratedRenderer.sharedMesh, childAssetPath); + result.Integrated.Reload(childAssetPath); + } + if (result.IntegratedNoBlendShape != null) + { + var childAssetPath = $"{assetFolder}/{result.IntegratedNoBlendShape.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"; + Debug.LogFormat("CreateAsset: {0}", childAssetPath); + AssetDatabase.CreateAsset(result.Integrated.IntegratedRenderer.sharedMesh, childAssetPath); + result.IntegratedNoBlendShape.Reload(childAssetPath); + } + } + + MeshUtility.Clear(results); + } + + /// + /// Write Prefab + /// + protected virtual string WritePrefab(string assetFolder, GameObject instance) + { + var prefabPath = $"{assetFolder}/Integrated.prefab"; + Debug.Log(prefabPath); + PrefabUtility.SaveAsPrefabAsset(instance, prefabPath, out bool success); + if (!success) + { + throw new Exception($"PrefabUtility.SaveAsPrefabAsset: {prefabPath}"); + } + return prefabPath; + } + + protected bool ToggleIsModified(string label, ref bool value) + { + var newValue = EditorGUILayout.Toggle(label, value); + if (newValue == value) + { + return false; + } + value = newValue; + return true; + } + + bool MeshFreezeGui() + { + var blendShape = ToggleIsModified("BlendShape", ref MeshUtility.FreezeBlendShape); + var scale = ToggleIsModified("Scale", ref MeshUtility.FreezeScaling); + var rotation = ToggleIsModified("Rotation", ref MeshUtility.FreezeRotation); + return blendShape || scale || rotation; + } + + protected virtual bool MeshIntegrateGui() + { + var split = ToggleIsModified("Separate by BlendShape", ref MeshUtility.SplitByBlendShape); + var p = position; + var last = GUILayoutUtility.GetLastRect(); + var y = last.y + last.height; + var rect = new Rect + { + x = last.x, + y = y, + width = p.width, + height = p.height - y + // process button の高さ + - 30 + }; + var mod = MeshIntegration.OnGui(rect); + return split || mod; } } } \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs index f87c06c5b..a516e0c96 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,13 @@ 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 Asset です。実行時に書き出しファイルの指定があります。")] + [LangMsg(Languages.en, "The target is prefab asset. A temporary file is specified during execution.")] + PREFAB_ASSET, + + [LangMsg(Languages.ja, "対象は, Prefab Instance です。Unpack されます。")] + [LangMsg(Languages.en, "The target is prefab asset. A temporary file is specified during execution.")] + PREFAB_INSTANCE, } } \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/PrefabContext.cs b/Assets/UniGLTF/Editor/MeshUtility/PrefabContext.cs new file mode 100644 index 000000000..df80d3f3b --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/PrefabContext.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + // Instantiate + class PrefabContext : IDisposable + { + public readonly GameObject Instance; + + readonly UnityPath _assetFolder; + + public bool Keep = false; + + public string AssetFolder => _assetFolder.Value; + + public PrefabContext(GameObject prefab, UnityPath assetFolder) + { + this._assetFolder = assetFolder; + this.Instance = GameObject.Instantiate(prefab); + if (PrefabUtility.IsOutermostPrefabInstanceRoot(this.Instance)) + { + // どういう条件でここに来るかはよくわからない + PrefabUtility.UnpackPrefabInstance(this.Instance, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); + } + } + + // - Instance を Asset に書き出す + // - Instance を削除する + public void Dispose() + { + if (Keep) + { + // for debug + return; + } + UnityEngine.Object.DestroyImmediate(Instance); + } + + public static UnityPath GetOutFolder(GameObject _exportTarget) + { + // 出力フォルダを決める + 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), "Integrated"); + var unityPath = UniGLTF.UnityPath.FromFullpath(assetFolder); + if (!unityPath.IsUnderWritableFolder) + { + throw new Exception("not in asset folder"); + } + return unityPath; + } + } +} \ No newline at end of file diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/PrefabContext.cs.meta similarity index 83% rename from Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta rename to Assets/UniGLTF/Editor/MeshUtility/PrefabContext.cs.meta index 84182f709..6cc2fba37 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta +++ b/Assets/UniGLTF/Editor/MeshUtility/PrefabContext.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: fb47e24fc1463584fa0b6b685d75f25e +guid: 948458ced6bcc904781eed04ebe7cd01 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM10/Editor/MeshUtility/Splitter.cs b/Assets/UniGLTF/Editor/MeshUtility/Splitter.cs similarity index 99% rename from Assets/VRM10/Editor/MeshUtility/Splitter.cs rename to Assets/UniGLTF/Editor/MeshUtility/Splitter.cs index 743bae80c..f1fd2e221 100644 --- a/Assets/VRM10/Editor/MeshUtility/Splitter.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/Splitter.cs @@ -2,7 +2,7 @@ using UnityEngine; using UnityEditor; using System; -namespace UniVRM10 +namespace UniGLTF.MeshUtility { [Serializable] public abstract class Splitter diff --git a/Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/Splitter.cs.meta similarity index 83% rename from Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta rename to Assets/UniGLTF/Editor/MeshUtility/Splitter.cs.meta index 57ca79cfc..4ef6a69d1 100644 --- a/Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta +++ b/Assets/UniGLTF/Editor/MeshUtility/Splitter.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 385274edf555ed84a9f3706ca2a99023 +guid: dbb5f5bceb86592499a56fc18011553e MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs deleted file mode 100644 index 035c1a6f2..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using UniGLTF.M17N; -using UnityEditor; -using UnityEngine; - -namespace UniGLTF.MeshUtility -{ - public static class TabMeshIntegrator - { - public static bool TryExecutable(GameObject root, out string msg) - { - // check - if (root == null) - { - msg = MeshUtilityMessages.NO_GAMEOBJECT_SELECTED.Msg(); - return false; - } - - if (HasVrm(root)) - { - msg = MeshUtilityMessages.VRM_DETECTED.Msg(); - return false; - } - - if (root.GetComponentsInChildren().Length == 0 && root.GetComponentsInChildren().Length == 0) - { - msg = MeshUtilityMessages.NO_MESH.Msg(); - return false; - } - - msg = ""; - return true; - } - - const string VRM_META = "VRMMeta"; - static bool HasVrm(GameObject root) - { - var allComponents = root.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; - } - - const string ASSET_SUFFIX = ".mesh.asset"; - - static string GetMeshWritePath(Mesh mesh) - { - if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh)))) - { - var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/"); - return $"{directory}/{Path.GetFileNameWithoutExtension(mesh.name)}{ASSET_SUFFIX}"; - } - else - { - return $"Assets/{Path.GetFileNameWithoutExtension(mesh.name)}{ASSET_SUFFIX}"; - } - } - - /// GameObject instance in scene or prefab - public static bool Execute(GameObject src, bool onlyBlendShapeRenderers) - { - var results = new List(); - - // instance or prefab => copy - var copy = GameObject.Instantiate(src); - copy.name = copy.name + "_mesh_integration"; - - // integrate - if (onlyBlendShapeRenderers) - { - results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape)); - results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape)); - } - else - { - results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.All)); - } - - // replace - MeshIntegratorUtility.ReplaceMeshWithResults(copy, results); - - // write mesh asset. - foreach (var result in results) - { - var mesh = result.Integrated.Mesh; - var assetPath = GetMeshWritePath(mesh); - Debug.LogFormat("CreateAsset: {0}", assetPath); - AssetDatabase.CreateAsset(mesh, assetPath); - } - - if (src.GetGameObjectType() == GameObjectType.AssetPrefab) - { - // write prefab. - { - var prefabPath = UnityPath.FromAsset(src); - prefabPath = prefabPath.Parent.Child($"{prefabPath.FileNameWithoutExtension}_integrated.prefab"); - Debug.LogFormat("WritePrefab: {0}", prefabPath); - PrefabUtility.SaveAsPrefabAsset(copy, prefabPath.Value); - } - - // destroy copy in scene. - GameObject.DestroyImmediate(copy); - } - else - { - // do nothing. keep copy. - } - - return true; - } - } -} diff --git a/Assets/UniGLTF/Editor/MeshUtility/UndoContext.cs b/Assets/UniGLTF/Editor/MeshUtility/UndoContext.cs new file mode 100644 index 000000000..eaf53f0f8 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/UndoContext.cs @@ -0,0 +1,27 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + // Instantiate + class UndoContext : IDisposable + { + public UndoContext(string undoName, GameObject go) + { + Undo.RegisterFullObjectHierarchyUndo(go, undoName); + if (go.GetPrefabType() == UnityExtensions.PrefabType.PrefabInstance) + { + PrefabUtility.UnpackPrefabInstance(go, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); + } + } + + public void Dispose() + { + // 特に何もしない + // Undo すると元に戻ってしまう + + // TODO: あれば一時オブジェクトの破棄 + } + } +} diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/UndoContext.cs.meta similarity index 83% rename from Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs.meta rename to Assets/UniGLTF/Editor/MeshUtility/UndoContext.cs.meta index d256f2e98..bf4202411 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs.meta +++ b/Assets/UniGLTF/Editor/MeshUtility/UndoContext.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2021859c0d7255643bd93c95ab3fcf3d +guid: 5019966789c0e094a8f58fc2734c9a35 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Editor/TopMenu.cs b/Assets/UniGLTF/Editor/TopMenu.cs index c5efaf46d..cb98950bf 100644 --- a/Assets/UniGLTF/Editor/TopMenu.cs +++ b/Assets/UniGLTF/Editor/TopMenu.cs @@ -1,4 +1,6 @@ using UnityEditor; +using UnityEngine; + namespace UniGLTF { @@ -25,19 +27,33 @@ namespace UniGLTF private static void ImportGltfFile() => GltfImportMenu.ImportGltfFileToGameObject(); - [MenuItem(UserGltfMenuPrefix + "/" + MeshUtility.MeshUtilityDialog.MENU_NAME, priority = 3)] + [MenuItem(UserGltfMenuPrefix + "/" + MeshUtility.MeshUtilityDialog.MENU_NAME, priority = 31)] private static void OpenMeshProcessingWindow() => MeshUtility.MeshUtilityDialog.OpenWindow(); #if VRM_DEVELOP - [MenuItem(DevelopmentMenuPrefix + "/Generate Serialization Code", priority = 40)] + [MenuItem(DevelopmentMenuPrefix + "/Generate Serialization Code", priority = 51)] private static void GenerateSerializationCode() { SerializerGenerator.GenerateSerializer(); DeserializerGenerator.GenerateSerializer(); } - [MenuItem(DevelopmentMenuPrefix + "/Generate UniJSON ConcreteCast", priority = 41)] + [MenuItem(DevelopmentMenuPrefix + "/Generate UniJSON ConcreteCast", priority = 52)] private static void GenerateUniJsonConcreteCastCode() => UniJSON.ConcreteCast.GenerateGenericCast(); + + [MenuItem("GameObject/CheckPrefabType", false, 53)] + [MenuItem("Assets/CheckPrefabType", false, 53)] + private static void CheckPrefabType() + { + if (Selection.activeObject is GameObject go) + { + Debug.Log(go.GetPrefabType()); + } + else + { + Debug.Log(Selection.activeContext.GetType()); + } + } #endif } } diff --git a/Assets/UniGLTF/Runtime/Extensions/UnityExtensions.cs b/Assets/UniGLTF/Runtime/Extensions/UnityExtensions.cs index 58321b08f..17bbb3bba 100644 --- a/Assets/UniGLTF/Runtime/Extensions/UnityExtensions.cs +++ b/Assets/UniGLTF/Runtime/Extensions/UnityExtensions.cs @@ -449,5 +449,40 @@ namespace UniGLTF } return true; } + + public enum PrefabType + { + PrefabAsset, + PrefabInstance, + NotPrefab, + } + + /// + /// Scene と Prefab で挙動をスイッチする。 + /// + /// - Scene: ヒエラルキーを操作する。Asset の 書き出しはしない。UNDO はする。TODO: 明示的な Asset の書き出し。 + /// - Prefab: 対象をコピーして処理する。Undo は実装しない。結果を Asset として書き出し、処理後にコピーは削除する。 + /// + /// + public static PrefabType GetPrefabType(this GameObject go) + { + if (go == null) + { + throw new ArgumentNullException(); + } + if (!go.scene.IsValid()) + { + return PrefabType.PrefabAsset; + } + +#if UNITY_EDITOR + if (PrefabUtility.GetOutermostPrefabInstanceRoot(go) != null) + { + return PrefabType.PrefabInstance; + } +#endif + + return PrefabType.NotPrefab; + } } -} +} \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs b/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs index 40981e773..a5f6891cd 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using UniGLTF.Utils; using UnityEngine; @@ -8,170 +9,98 @@ namespace UniGLTF.MeshUtility { public static class BoneNormalizer { - public static (GameObject, Dictionary) CreateNormalizedHierarchy(GameObject go, - bool removeScaling = true, - bool removeRotation = true) + private static MeshAttachInfo CreateMeshInfo(Transform src, bool freezeRotation) { - var boneMap = new Dictionary(); - var normalized = new GameObject(go.name + "(normalized)"); - normalized.transform.position = go.transform.position; - - if (removeScaling && removeRotation) + // SkinnedMeshRenderer + var smr = src.GetComponent(); + var mesh = MeshFreezer.NormalizeSkinnedMesh(smr); + if (mesh != null) { - RemoveScaleAndRotationRecursive(go.transform, normalized.transform, boneMap); - } - else if (removeScaling) - { - RemoveScaleAndRotationRecursive(go.transform, normalized.transform, boneMap); - } - else if (removeRotation) - { - throw new NotImplementedException(); - } - else - { - throw new ArgumentNullException(); - } - - return (normalized, boneMap); - } - - static void RemoveScaleRecursive(Transform src, Transform dst, Dictionary boneMap) - { - boneMap[src] = dst; - - foreach (Transform child in src) - { - if (child.gameObject.activeSelf) + return new MeshAttachInfo { - var dstChild = new GameObject(child.name); - dstChild.transform.SetParent(dst); - dstChild.transform.position = child.position; // copy world position - dstChild.transform.rotation = child.localToWorldMatrix.rotation; // copy world rotation - // scale is removed - RemoveScaleRecursive(child, dstChild.transform, boneMap); + Mesh = mesh, + Materials = smr.sharedMaterials, + Bones = smr.bones, + RootBone = smr.rootBone, + }; + } + + // MeshRenderer + var mr = src.GetComponent(); + if (mr != null) + { + var dstMesh = MeshFreezer.NormalizeNoneSkinnedMesh(mr, freezeRotation); + if (dstMesh != null) + { + return new MeshAttachInfo + { + Mesh = dstMesh, + Materials = mr.sharedMaterials, + }; } } - } - static void RemoveScaleAndRotationRecursive(Transform src, Transform dst, Dictionary boneMap) - { - boneMap[src] = dst; - - foreach (Transform child in src) - { - if (child.gameObject.activeSelf) - { - var dstChild = new GameObject(child.name); - dstChild.transform.SetParent(dst); - dstChild.transform.position = child.position; // copy world position - - RemoveScaleAndRotationRecursive(child, dstChild.transform, boneMap); - } - } + return default; } /// - /// 回転とスケールを除去したヒエラルキーのコピーを作成する(MeshをBakeする) + /// 各レンダラー(SkinnedMeshRenderer と MeshRenderer)にアタッチされた sharedMesh に対して + /// 回転とスケールを除去し、BlendShape の現状を焼き付けた版を作成する(まだ、アタッチしない) /// - /// 対象のヒエラルキーのルート - /// BlendShapeを0クリアするか否か。false の場合 BlendShape の現状を Bake する - /// Avatarを作る関数 - /// - public static (GameObject, Dictionary) NormalizeHierarchyFreezeMesh(GameObject go, - bool removeScaling = true, - bool removeRotation = true, - bool freezeBlendShape = true - ) + public static Dictionary NormalizeHierarchyFreezeMesh( + GameObject go, bool freezeRotation) { - // - // 正規化されたヒエラルキーを作る - // - var (normalized, boneMap) = CreateNormalizedHierarchy(go, removeScaling, removeRotation); - - // - // 各メッシュから回転・スケールを取り除いてBinding行列を再計算する - // + var result = new Dictionary(); foreach (var src in go.transform.Traverse()) { - Transform dst; - if (!boneMap.TryGetValue(src, out dst)) + var info = CreateMeshInfo(src, freezeRotation); + if (info != null) { - continue; - } - - { - // SkinnedMeshRenderer - var srcRenderer = src.GetComponent(); - var (mesh, dstBones) = MeshFreezer.NormalizeSkinnedMesh( - srcRenderer, - boneMap, - dst.localToWorldMatrix, - freezeBlendShape); - if (mesh != null) - { - var dstRenderer = dst.gameObject.AddComponent(); - dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; - if (srcRenderer.rootBone != null) - { - if (boneMap.TryGetValue(srcRenderer.rootBone, out Transform found)) - { - dstRenderer.rootBone = found; - } - } - dstRenderer.bones = dstBones; - dstRenderer.sharedMesh = mesh; - } - } - - { - // MeshRenderer - var srcRenderer = src.GetComponent(); - if (srcRenderer != null) - { - var dstMesh = MeshFreezer.NormalizeNoneSkinnedMesh(srcRenderer); - if (dstMesh != null) - { - var dstFilter = dst.gameObject.AddComponent(); - dstFilter.sharedMesh = dstMesh; - // Materialをコピー - var dstRenderer = dst.gameObject.AddComponent(); - dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; - } - } + result.Add(src, info); } } - - return (normalized, boneMap); + return result; } - public static void WriteBackResult(GameObject go, GameObject normalized, Dictionary boneMap) + public static void Replace(GameObject go, Dictionary newMesh, + bool FreezeRotation, bool FreezeScaling) { - Func getSrc = dst => + var boneMap = go.transform.Traverse().ToDictionary(x => x, x => new EuclideanTransform(x.rotation, x.position)); + + // first, update hierarchy + foreach (var src in go.transform.Traverse()) { - foreach (var (k, v) in boneMap) + var tr = boneMap[src]; + if (FreezeScaling) { - if (v == dst) - { - return k; - } + src.localScale = Vector3.one; } - throw new NotImplementedException(); - }; - foreach (var (src, dst) in boneMap) - { - src.localPosition = dst.localPosition; - src.localRotation = dst.localRotation; - src.localScale = dst.localScale; - var srcR = src.GetComponent(); - var dstR = dst.GetComponent(); - if (srcR != null && dstR != null) + else { - srcR.sharedMesh = dstR.sharedMesh; - srcR.bones = dstR.bones.Select(x => getSrc(x)).ToArray(); + throw new NotImplementedException(); + } + + if (FreezeRotation) + { + src.rotation = Quaternion.identity; + } + else + { + src.rotation = tr.Rotation; + } + + src.position = tr.Translation; + } + + // second, replace mesh + foreach (var (src, tr) in boneMap) + { + if (newMesh.TryGetValue(src, out var info)) + { + info.ReplaceMesh(src.gameObject); } } } } -} +} \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs new file mode 100644 index 000000000..0ce1cad6e --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + + +namespace UniGLTF.MeshUtility +{ + /// + /// - Freeze + /// - Integration + /// - Split + /// + /// - Implement runtime logic => Process a hierarchy in scene. Do not process prefab. + /// - Implement undo + /// + /// + public class GltfMeshUtility + { + /// + /// Same as VRM-0 normalization + /// - Mesh + /// - Node + /// - InverseBindMatrices + /// + public bool FreezeBlendShape = false; + + /// + /// Same as VRM-0 normalization + /// - Mesh + /// - Node + /// - InverseBindMatrices + /// + public bool FreezeScaling = true; + + /// + /// Same as VRM-0 normalization + /// - Mesh + /// - Node + /// - InverseBindMatrices + /// + public bool FreezeRotation = false; + + public List MeshIntegrationGroups = new List(); + + /// + /// Create a headless model and solve VRM.FirstPersonFlag.Auto + /// + public bool GenerateMeshForFirstPersonAuto = false; + + /// + /// Split into having and not having BlendShape + /// + public bool SplitByBlendShape = false; + + protected UniGLTF.MeshUtility.MeshIntegrationGroup _GetOrCreateGroup(string name) + { + foreach (var g in MeshIntegrationGroups) + { + if (g.Name == name) + { + return g; + } + } + MeshIntegrationGroups.Add(new UniGLTF.MeshUtility.MeshIntegrationGroup + { + Name = name, + }); + return MeshIntegrationGroups.Last(); + } + + public virtual void UpdateMeshIntegrationGroups(GameObject root) + { + MeshIntegrationGroups.Clear(); + if (root == null) + { + return; + } + var group = _GetOrCreateGroup("all mesh"); + group.Renderers.AddRange(root.GetComponentsInChildren()); + } + + public void IntegrateAll(GameObject root) + { + if (root == null) + { + return; + } + MeshIntegrationGroups.Add(new MeshIntegrationGroup + { + Name = "ALL", + Renderers = root.GetComponentsInChildren().ToList(), + }); + } + + static GameObject GetOrCreateEmpty(GameObject go, string name) + { + foreach (var child in go.transform.GetChildren()) + { + if (child.name == name + && child.localPosition == Vector3.zero + && child.localScale == Vector3.one + && child.localRotation == Quaternion.identity) + { + return child.gameObject; + } + } + var empty = new GameObject(name); + empty.transform.SetParent(go.transform, false); + return empty; + } + + /// + /// + /// + /// MeshIntegrationGroup を作ったとき root + /// go が prefab だった場合に instance されたもの + /// + public virtual IEnumerable CopyInstantiate(GameObject go, GameObject instance) + { + if (instance == null) + { + foreach (var g in MeshIntegrationGroups) + { + yield return g; + } + } + else + { + foreach (var g in MeshIntegrationGroups) + { + yield return g.CopyInstantiate(go, instance); + } + } + } + + public virtual (List, List) Process( + GameObject target, IEnumerable groupCopy) + { + if (FreezeBlendShape || FreezeRotation || FreezeScaling) + { + // MeshをBakeする + var newMesh = BoneNormalizer.NormalizeHierarchyFreezeMesh(target, FreezeRotation); + + // - ヒエラルキーから回転・拡縮を除去する + // - BakeされたMeshで置き換える + // - bindPoses を再計算する + BoneNormalizer.Replace(target, newMesh, FreezeRotation, FreezeScaling); + } + + var newList = new List(); + + var empty = GetOrCreateEmpty(target, "mesh"); + + var results = new List(); + foreach (var group in groupCopy) + { + if (TryIntegrate(empty, group, out var resultAndAdded)) + { + var (result, newGo) = resultAndAdded; + results.Add(result); + newList.AddRange(newGo); + } + } + + return (results, newList); + } + + public void Clear(List results) + { + // 用が済んだ 統合前 の renderer を削除する + foreach (var result in results) + { + foreach (var r in result.SourceMeshRenderers) + { + if (Application.isPlaying) + { + GameObject.Destroy(r.gameObject.GetComponent()); + GameObject.Destroy(r); + } + else + { + GameObject.DestroyImmediate(r.gameObject.GetComponent()); + GameObject.DestroyImmediate(r); + } + } + foreach (var r in result.SourceSkinnedMeshRenderers) + { + if (Application.isPlaying) + { + GameObject.Destroy(r); + } + else + { + GameObject.DestroyImmediate(r, true); + } + } + } + + MeshIntegrationGroups.Clear(); + } + + protected virtual bool TryIntegrate(GameObject empty, + MeshIntegrationGroup group, out (MeshIntegrationResult, GameObject[]) resultAndAdded) + { + if (MeshIntegrator.TryIntegrate(group, SplitByBlendShape + ? MeshIntegrator.BlendShapeOperation.Split + : MeshIntegrator.BlendShapeOperation.Use, out var result)) + { + var newGo = result.AddIntegratedRendererTo(empty).ToArray(); + resultAndAdded = (result, newGo); + return true; + } + + resultAndAdded = default; + return false; + } + } +} diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs.meta similarity index 83% rename from Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs.meta rename to Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs.meta index 5c60a51fe..0ad3bff31 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs.meta +++ b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 65a227dcf3cb5f34085bd6829894fb64 +guid: e2425cf6ac1f2434986968ff0d4a4755 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshAttachInfo.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshAttachInfo.cs new file mode 100644 index 000000000..54f36ee4f --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshAttachInfo.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + public class MeshAttachInfo + { + public Mesh Mesh; + public Material[] Materials; + public Transform[] Bones; + public Transform RootBone; + public void ReplaceMesh(GameObject dst) + { + if (dst == null) + { + throw new ArgumentNullException(); + } + + if (Bones != null) + { + // recalc bindposes + Mesh.bindposes = Bones.Select(x => x.worldToLocalMatrix * dst.transform.localToWorldMatrix).ToArray(); + + if (dst.GetComponent() is SkinnedMeshRenderer dstRenderer) + { + dstRenderer.sharedMesh = Mesh; + dstRenderer.sharedMaterials = Materials; + dstRenderer.bones = Bones; + dstRenderer.rootBone = RootBone; + } + else + { + Debug.LogError($"SkinnedMeshRenderer not found", dst); + } + } + else + { + if (dst.GetComponent() is MeshFilter dstFilter) + { + dstFilter.sharedMesh = Mesh; + if (dst.gameObject.GetComponent() is MeshRenderer dstRenderer) + { + dstRenderer.sharedMaterials = Materials; + } + else + { + Debug.LogError($"MeshRenderer not found", dst); + } + } + else + { + Debug.LogError($"MeshFilter not found", dst); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshAttachInfo.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/MeshAttachInfo.cs.meta new file mode 100644 index 000000000..0db6210ad --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshAttachInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e6eff3bee96e61849ac190387ec28542 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs index 98c4f8af1..fcd185a04 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshFreezer.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using UnityEngine; +using UnityEngine.UIElements; using VRMShaders; namespace UniGLTF.MeshUtility @@ -126,14 +127,9 @@ namespace UniGLTF.MeshUtility /// /// /// 正規化前のボーンから正規化後のボーンを得る - /// /// /// - public static (Mesh, Transform[]) NormalizeSkinnedMesh( - SkinnedMeshRenderer src, - Dictionary boneMap, - Matrix4x4 dstLocalToWorldMatrix, - bool FreezeBlendShape = true) + public static Mesh NormalizeSkinnedMesh(SkinnedMeshRenderer src) { if (src == null || !src.enabled @@ -147,12 +143,6 @@ namespace UniGLTF.MeshUtility var srcMesh = src.sharedMesh; var originalSrcMesh = srcMesh; - // 元の Transform[] bones から、無効なboneを取り除いて前に詰めた配列を作る - var dstBones = src.bones - .Where(x => x != null && boneMap.ContainsKey(x)) - .Select(x => boneMap[x]) - .ToArray(); - var hasBoneWeight = src.bones != null && src.bones.Length > 0; if (!hasBoneWeight) { @@ -172,34 +162,17 @@ namespace UniGLTF.MeshUtility weight3 = 0.0f, }; srcMesh.boneWeights = Enumerable.Range(0, srcMesh.vertexCount).Select(x => bw).ToArray(); - srcMesh.bindposes = new Matrix4x4[] { Matrix4x4.identity }; - src.rootBone = src.transform; - dstBones = new[] { boneMap[src.transform] }; src.bones = new[] { src.transform }; src.sharedMesh = srcMesh; } - var blendShapeBackup = new List(); - if (!FreezeBlendShape) - { - for (int i = 0; i < srcMesh.blendShapeCount; ++i) - { - blendShapeBackup.Add(src.GetBlendShapeWeight(i)); - src.SetBlendShapeWeight(i, 0); - } - } - // BakeMesh var mesh = srcMesh.Copy(false); mesh.name = srcMesh.name + ".baked"; src.BakeMesh(mesh); - // 新しい骨格のボーンウェイトを作成する - mesh.boneWeights = MapBoneWeight(srcMesh.boneWeights, boneMap, src.bones, dstBones); - - // recalc bindposes - mesh.bindposes = dstBones.Select(x => x.worldToLocalMatrix * dstLocalToWorldMatrix).ToArray(); + mesh.boneWeights = srcMesh.boneWeights; //var m = src.localToWorldMatrix; // include scaling var m = default(Matrix4x4); @@ -211,11 +184,6 @@ namespace UniGLTF.MeshUtility // CopyBlendShapes(src, srcMesh, mesh, m); - for (int i = 0; i < blendShapeBackup.Count; ++i) - { - src.SetBlendShapeWeight(i, blendShapeBackup[i]); - } - if (!hasBoneWeight) { // restore bones @@ -223,7 +191,7 @@ namespace UniGLTF.MeshUtility src.sharedMesh = originalSrcMesh; } - return (mesh, dstBones); + return mesh; } private static void CopyBlendShapes(SkinnedMeshRenderer src, Mesh srcMesh, Mesh mesh, Matrix4x4 m) @@ -363,7 +331,7 @@ namespace UniGLTF.MeshUtility } } - public static Mesh NormalizeNoneSkinnedMesh(MeshRenderer srcRenderer) + public static Mesh NormalizeNoneSkinnedMesh(MeshRenderer srcRenderer, bool freezeRotation) { if (srcRenderer == null || !srcRenderer.enabled) { @@ -380,7 +348,15 @@ namespace UniGLTF.MeshUtility var dstMesh = srcFilter.sharedMesh.Copy(false); // Meshに乗っているボーンの姿勢を適用する - dstMesh.ApplyRotationAndScale(srcRenderer.transform.localToWorldMatrix); + if (freezeRotation) + { + dstMesh.ApplyRotationAndScale(srcRenderer.transform.localToWorldMatrix); + } + else + { + var (t, r, s) = srcRenderer.transform.localToWorldMatrix.Decompose(); + dstMesh.ApplyRotationAndScale(Matrix4x4.TRS(t, Quaternion.identity, s)); + } return dstMesh; } } diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationGroup.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationGroup.cs index 64a49c3e3..bf0b969e4 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationGroup.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationGroup.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using UnityEngine; namespace UniGLTF.MeshUtility @@ -8,5 +7,26 @@ namespace UniGLTF.MeshUtility { public string Name; public List Renderers = new List(); + + public MeshIntegrationGroup CopyInstantiate(GameObject go, GameObject instance) + { + var copy = new MeshIntegrationGroup + { + Name = Name + }; + foreach (var r in Renderers) + { + var relative = r.transform.RelativePathFrom(go.transform); + if (r is SkinnedMeshRenderer smr) + { + copy.Renderers.Add(instance.transform.GetFromPath(relative).GetComponent()); + } + else if (r is MeshRenderer mr) + { + copy.Renderers.Add(instance.transform.GetFromPath(relative).GetComponent()); + } + } + return copy; + } } } \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationResult.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationResult.cs index 493ca7071..a87c8ad31 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationResult.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationResult.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; + namespace UniGLTF.MeshUtility { public struct DrawCount @@ -29,6 +30,15 @@ namespace UniGLTF.MeshUtility smr.bones = bones; IntegratedRenderer = smr; } + + public void Reload(string assetPath) + { + var unityPath = UnityPath.FromUnityPath(assetPath); + unityPath.ImportAsset(); + var mesh = unityPath.LoadAsset(); + // replace reloaded + IntegratedRenderer.sharedMesh = mesh; + } } public class MeshIntegrationResult @@ -48,16 +58,24 @@ namespace UniGLTF.MeshUtility public IEnumerable AddIntegratedRendererTo(GameObject parent) { + int count = 0; if (Integrated != null) { Integrated.AddIntegratedRendererTo(parent, Bones); + ++count; yield return Integrated.IntegratedRenderer.gameObject; } if (IntegratedNoBlendShape != null) { IntegratedNoBlendShape.AddIntegratedRendererTo(parent, Bones); + ++count; yield return IntegratedNoBlendShape.IntegratedRenderer.gameObject; } + + if (count == 0) + { + throw new NotImplementedException(); + } } } } diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs index 9dd280350..1f9c1c89e 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs @@ -128,11 +128,10 @@ namespace UniGLTF.MeshUtility }) ); - var self = renderer.transform; - var bone = self.parent; + var bone = renderer.transform; if (bone == null) { - Debug.LogWarningFormat("{0} is root gameobject.", self.name); + Debug.LogWarningFormat("{0} is root gameobject.", bone.name); return; } var boneIndex = AddBoneIfUnique(bone); @@ -275,7 +274,8 @@ namespace UniGLTF.MeshUtility return found; } - public static MeshIntegrationResult Integrate(MeshIntegrationGroup group, BlendShapeOperation op) + public static bool TryIntegrate(MeshIntegrationGroup group, BlendShapeOperation op, + out MeshIntegrationResult result) { var integrator = new MeshUtility.MeshIntegrator(); foreach (var x in group.Renderers) @@ -289,7 +289,15 @@ namespace UniGLTF.MeshUtility integrator.Push(mr); } } - return integrator.Integrate(group.Name, op); + result = integrator.Integrate(group.Name, op); + if (result.Integrated != null || result.IntegratedNoBlendShape != null) + { + return true; + } + else + { + return false; + } } delegate bool TriangleFilter(int i0, int i1, int i2); @@ -351,7 +359,7 @@ namespace UniGLTF.MeshUtility return mesh; } - public MeshIntegrationResult Integrate(string name, BlendShapeOperation op) + MeshIntegrationResult Integrate(string name, BlendShapeOperation op) { if (_Bones.Count != _BindPoses.Count) { diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs deleted file mode 100644 index 283fe3fc8..000000000 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs +++ /dev/null @@ -1,199 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace UniGLTF.MeshUtility -{ - public static class MeshIntegratorUtility - { - public const string INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME = "Integrated(WithoutBlendShape)"; - public const string INTEGRATED_MESH_WITH_BLENDSHAPE_NAME = "Integrated(WithBlendShape)"; - public const string INTEGRATED_MESH_ALL_NAME = "Integrated(All)"; - - /// - /// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する - /// - /// - /// - /// true: BlendShapeを保持するSkinnedMeshRendererのみ - /// false: BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer - /// null: すべてのSkinnedMeshRenderer + MeshRenderer - /// - /// - public static MeshIntegrationResult Integrate(GameObject go, MeshEnumerateOption onlyBlendShapeRenderers, - IEnumerable excludes = null, - bool destroyIntegratedRenderer = false) - { - var exclude = new MeshExclude(excludes); - - var group = new MeshIntegrationGroup(); - bool useBlendShape = false; - - switch (onlyBlendShapeRenderers) - { - case MeshEnumerateOption.OnlyWithBlendShape: - { - group.Name = INTEGRATED_MESH_WITH_BLENDSHAPE_NAME; - useBlendShape = true; - - foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers)) - { - if (exclude.IsExcluded(x)) - { - continue; - } - group.Renderers.Add(x); - } - break; - } - - case MeshEnumerateOption.OnlyWithoutBlendShape: - { - group.Name = INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME; - useBlendShape = false; - - foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers)) - { - if (exclude.IsExcluded(x)) - { - continue; - } - group.Renderers.Add(x); - } - - foreach (var x in EnumerateMeshRenderer(go.transform)) - { - if (exclude.IsExcluded(x)) - { - continue; - } - group.Renderers.Add(x); - } - - break; - } - - case MeshEnumerateOption.All: - { - group.Name = INTEGRATED_MESH_ALL_NAME; - useBlendShape = true; - - foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers)) - { - if (exclude.IsExcluded(x)) - { - continue; - } - group.Renderers.Add(x); - } - - foreach (var x in EnumerateMeshRenderer(go.transform)) - { - if (exclude.IsExcluded(x)) - { - continue; - } - group.Renderers.Add(x); - } - - break; - } - } - - return MeshIntegrator.Integrate(group, useBlendShape - ? MeshIntegrator.BlendShapeOperation.Use - : MeshIntegrator.BlendShapeOperation.None); - } - - public static IEnumerable EnumerateSkinnedMeshRenderer(Transform root, MeshEnumerateOption hasBlendShape) - { - foreach (var x in Traverse(root)) - { - var renderer = x.GetComponent(); - if (renderer != null && - renderer.gameObject.activeInHierarchy && - renderer.sharedMesh != null && - renderer.enabled) - { - 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; - } - } - } - } - } - - public static IEnumerable EnumerateMeshRenderer(Transform root) - { - foreach (var x in Traverse(root)) - { - var renderer = x.GetComponent(); - var filter = x.GetComponent(); - - if (renderer != null && - filter != null && - renderer.gameObject.activeInHierarchy && - filter.sharedMesh != null) - { - yield return renderer; - } - } - } - - private static IEnumerable Traverse(Transform parent) - { - if (parent.gameObject.activeSelf) - { - yield return parent; - - foreach (Transform child in parent) - { - foreach (var x in Traverse(child)) - { - yield return x; - } - } - } - } - - public static void ReplaceMeshWithResults(GameObject copy, List results) - { - // destroy original meshes - foreach (var skinnedMesh in copy.GetComponentsInChildren()) - { - GameObject.DestroyImmediate(skinnedMesh); - } - foreach (var normalMesh in copy.GetComponentsInChildren()) - { - if (normalMesh.gameObject.GetComponent()) - { - GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent()); - } - GameObject.DestroyImmediate(normalMesh); - } - - // Add integrated - foreach (var result in results) - { - result.AddIntegratedRendererTo(copy); - } - } - } -} \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs.meta deleted file mode 100644 index b9075e71b..000000000 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: a982d9d30c0145038245b0214dc2f2e4 -timeCreated: 1560190306 \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs b/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs index c4224252c..5a7f24a5b 100644 --- a/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs +++ b/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs @@ -327,6 +327,7 @@ namespace UniHumanoid var avatarDescription = UniHumanoid.AvatarDescription.Create(); avatarDescription.SetHumanBones(map); + var avatar = avatarDescription.CreateAvatar(src.transform); avatar.name = "created"; return avatar; diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs deleted file mode 100644 index 33faa8359..000000000 --- a/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UniGLTF; -using UniGLTF.MeshUtility; -using UnityEditor; -using UnityEngine; - -namespace VRM -{ - /// - /// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する - /// - public static class VRMMeshIntegratorUtility - { - public static List FollowBlendshapeRendererChange(List results, GameObject root, string assetFolder) - { - var clips = new List(); - var proxy = root.GetComponent(); - if (proxy == null || proxy.BlendShapeAvatar == null) - { - return clips; - } - var result = results.FirstOrDefault(x => x.Integrated.Mesh.blendShapeCount > 0); - if (result == null) - { - return clips; - } - - var rendererDict = new Dictionary(); - foreach (var x in result.SourceSkinnedMeshRenderers) - { - rendererDict.Add(x.transform.RelativePathFrom(root.transform), x); - } - var dstPath = result.Integrated.IntegratedRenderer.transform.RelativePathFrom(root.transform); - - // copy modify and write - var clipAssetPathList = new List(); - var sb = new StringBuilder(); - foreach (var src in proxy.BlendShapeAvatar.Clips) - { - if (src == null) continue; - - // copy - var copy = ScriptableObject.CreateInstance(); - copy.CopyFrom(src); - copy.Prefab = null; - - // modify - for (var i = 0; i < copy.Values.Length; ++i) - { - var val = copy.Values[i]; - if (rendererDict.ContainsKey(val.RelativePath)) - { - var srcRenderer = rendererDict[val.RelativePath]; - var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index); - var newIndex = result.Integrated.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name); - if (newIndex == -1) - { - throw new KeyNotFoundException($"blendshape:{name} not found"); - } - - val.RelativePath = dstPath; - val.Index = newIndex; - } - copy.Values[i] = val; - } - - // write - var assetPath = $"{assetFolder}/{copy.name}.asset"; - sb.AppendLine($"write: {assetPath}"); - AssetDatabase.CreateAsset(copy, assetPath); - - clipAssetPathList.Add(assetPath); - clips.Add(copy); - } - Debug.Log(sb.ToString()); - - { - // create blendshape avatar & replace - var copy = ScriptableObject.CreateInstance(); - copy.Clips.AddRange(clips); - var assetPath = $"{assetFolder}/blendshape.asset"; - AssetDatabase.CreateAsset(copy, assetPath); - - // assign - proxy.BlendShapeAvatar = copy; - } - - return clips; - } - } -} \ No newline at end of file diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmBlendShapeUpdater.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmBlendShapeUpdater.cs new file mode 100644 index 000000000..99a7162bc --- /dev/null +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmBlendShapeUpdater.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UniGLTF; +using UniGLTF.MeshUtility; +using UnityEditor; +using UnityEngine; + + +namespace VRM +{ + /// + /// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する + /// + public class VrmBlendShapeUpdater + { + // BlendShapeBinding.RelativePath からの逆引き + Dictionary> _rendererPathMap = new(); + GameObject _root; + + VrmBlendShapeUpdater(GameObject root, List results) + { + _root = root; + foreach (var result in results) + { + foreach (var x in result.SourceSkinnedMeshRenderers) + { + var srcPath = x.transform.RelativePathFrom(root.transform); + if (_rendererPathMap.TryGetValue(srcPath, out var value)) + { + value.Add(result); + } + else + { + value = new List(); + value.Add(result); + _rendererPathMap.Add(srcPath, value); + } + } + } + } + + // 分割されて増える => 増えない BlendShape のある方にいく + // 統合されて減る => 名前が同じものが統合される + private IEnumerable ReplaceBlendShapeBinding(IEnumerable values) + { + var used = new HashSet(); + foreach (var val in values) + { + if (_rendererPathMap.TryGetValue(val.RelativePath, out var results)) + { + foreach (var result in results) + { + if (result.Integrated == null) + { + continue; + } + var name = result.Integrated.Mesh.GetBlendShapeName(val.Index); + var newIndex = result.Integrated.Mesh.GetBlendShapeIndex(name); + if (newIndex == -1) + { + throw new KeyNotFoundException($"blendshape:{name} not found"); + } + + var dstPath = result.Integrated.IntegratedRenderer.transform.RelativePathFrom(_root.transform); + var binding = new BlendShapeBinding + { + RelativePath = dstPath, + Index = newIndex, + Weight = val.Weight, + }; + if (used.Contains(binding)) + { + Debug.LogWarning($"duplicated: {binding}"); + } + else + { +#if VRM_DEVELOP + Debug.Log($"{val} >> {binding}"); +#endif + used.Add(binding); + yield return binding; + } + } + } + else + { + // skip + Debug.LogWarning($"SkinnedMeshRenderer not found: {val.RelativePath}"); + } + } + } + + public static List FollowBlendshapeRendererChange(string assetFolder, + GameObject root, + List results) + { + var clips = new List(); + var proxy = root.GetComponent(); + if (proxy == null || proxy.BlendShapeAvatar == null) + { + return clips; + } + + var util = new VrmBlendShapeUpdater(root, results); + + // create modified BlendShapeClip + var clipAssetPathList = new List(); + foreach (var src in proxy.BlendShapeAvatar.Clips.Where(x => x != null)) + { + var copy = util.RecreateBlendShapeClip(src, assetFolder); + var assetPath = $"{assetFolder}/{copy.name}.asset"; + AssetDatabase.CreateAsset(copy, assetPath); + clipAssetPathList.Add(assetPath); + clips.Add(copy); + } + + // create BlendShapeAvatar + proxy.BlendShapeAvatar = RecreateBlendShapeAvatar(clips, assetFolder); + + return clips; + } + + BlendShapeClip RecreateBlendShapeClip(BlendShapeClip src, string assetFolder) + { + if (src == null) + { + throw new ArgumentNullException(); + } + + // copy + var copy = ScriptableObject.CreateInstance(); + copy.CopyFrom(src); + copy.Prefab = null; + copy.Values = ReplaceBlendShapeBinding(copy.Values).ToArray(); + return copy; + } + + static BlendShapeAvatar RecreateBlendShapeAvatar(IReadOnlyCollection clips, string assetFolder) + { + var copy = ScriptableObject.CreateInstance(); + copy.Clips.AddRange(clips); + var assetPath = $"{assetFolder}/blendshape.asset"; + AssetDatabase.CreateAsset(copy, assetPath); + return copy; + } + } +} \ No newline at end of file diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmBlendShapeUpdater.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/VrmBlendShapeUpdater.cs.meta new file mode 100644 index 000000000..697660578 --- /dev/null +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmBlendShapeUpdater.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 488693244cf1ec548a9eb4d81164706f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs index 6249dcbf3..d6fe6949a 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs @@ -1,370 +1,135 @@ #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; +using UniGLTF; +using System.Collections.Generic; + 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"; - - enum HelpMessage + public new const string MENU_NAME = "VRM 0.x MeshUtility"; + 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 + protected override void Validate() { - None, - NoTarget, - HasParent, - NotPrefab, - } - - [SerializeField] - GameObject m_root; - - [SerializeField] - bool m_separateByBlendShape = true; - - [Header("Validation")] - [SerializeField] - Material[] m_uniqueMaterials; - - [Serializable] - struct MaterialKey - { - public string Shader; - public KeyValuePair[] Properties; - - public override bool Equals(object obj) + base.Validate(); + if (_exportTarget.GetComponent() == null) { - 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; - } - } - [SerializeField] - MaterialList[] m_duplicateMaterials; - - [Serializable] - public class ExcludeItem - { - public Mesh Mesh; - public bool Exclude; - } - - [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); + _validations.Add(Validation.Error("target is not vrm1")); 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) + VrmMeshUtility _meshUtil; + VrmMeshUtility VrmMeshUtility { - var exclude_map = new Dictionary(); - var excludes = new List(); - foreach (var x in m_root.GetComponentsInChildren()) + get { - var mesh = x.GetMesh(); - if (mesh == null) + if (_meshUtil == null) { - continue; + _meshUtil = new VrmMeshUtility(); } - 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; + return _meshUtil; } - m_excludes.AddRange(excludes); + } + protected override UniGLTF.MeshUtility.GltfMeshUtility MeshUtility => VrmMeshUtility; + + protected override bool MeshIntegrateGui() + { + var firstPerson = ToggleIsModified("FirstPerson == AUTO の生成", ref MeshUtility.GenerateMeshForFirstPersonAuto); + var mod = base.MeshIntegrateGui(); + return firstPerson || mod; } - void OnWizardUpdate() + List _clips; + protected override void WriteAssets(string assetFolder, + GameObject instance, List results) { - } - - /// 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); + _clips = VrmBlendShapeUpdater.FollowBlendshapeRendererChange(assetFolder, instance, results); - // 用が済んだ 統合前 の renderer を削除する - foreach (var result in results) + // write mesh + base.WriteAssets(assetFolder, instance, results); + + // reset firstPerson + if (instance.GetComponent() is VRMFirstPerson firstPerson) { - foreach (var renderer in result.SourceMeshRenderers) - { - GameObject.DestroyImmediate(renderer); - } - foreach (var renderer in result.SourceSkinnedMeshRenderers) - { - GameObject.DestroyImmediate(renderer); - } + // TODO: + firstPerson.Reset(); } + } - // 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); + protected override string WritePrefab(string assetFolder, + GameObject instance) + { + var prefabPath = base.WritePrefab(assetFolder, instance); + // PostProcess + // update prefab reference of BlendShapeClip var prefabReference = AssetDatabase.LoadAssetAtPath(prefabPath); - foreach (var clip in clips) + 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(); } + + return prefabPath; } - static List Integrate(GameObject root, IEnumerable excludes, bool separateByBlendShape) + protected override void DialogMessage() { - 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; + EditorGUILayout.HelpBox(Message.MESH_UTILITY.Msg(), MessageType.Info); } - void OnWizardCreate() + enum Message { - Integrate(); - // close - } + [LangMsg(Languages.ja, @"(VRM-0.x専用) 凍結 > 統合 > 分割 という一連の処理を実行します。 - void OnWizardOtherButton() - { - Integrate(); +[凍結] +- ヒエラルキーの 回転・拡縮を Mesh に焼き付けます。 +- BlendShape の現状を Mesh に焼き付けます。 + +- VRM-0.x の正規化処理です。 +- HumanoidAvatar の再生成。 +- BlendShapeClip, 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, } } } diff --git a/Assets/VRM/Editor/VrmTopMenu.cs b/Assets/VRM/Editor/VrmTopMenu.cs index 6f130f3fc..663159a61 100644 --- a/Assets/VRM/Editor/VrmTopMenu.cs +++ b/Assets/VRM/Editor/VrmTopMenu.cs @@ -24,11 +24,11 @@ namespace VRM private static void ImportFromVrmFile() => VRMImporterMenu.OpenImportMenu(); - [MenuItem(UserMenuPrefix + "/" + VrmMeshIntegratorWizard.MENU_NAME, false, 3)] - private static void OpenMeshIntegratorWizard() => VrmMeshIntegratorWizard.CreateWizard(); + [MenuItem(UserMenuPrefix + "/" + VrmMeshIntegratorWizard.MENU_NAME, false, 51)] + private static void OpenMeshIntegratorWizard() => VrmMeshIntegratorWizard.OpenWindow(); - [MenuItem(UserMenuPrefix + "/" + VRMHumanoidNormalizerMenu.MENU_NAME, true, 51)] + [MenuItem(UserMenuPrefix + "/" + VRMHumanoidNormalizerMenu.MENU_NAME, true, 52)] private static bool FreezeTPoseValidation() => VRMHumanoidNormalizerMenu.NormalizeValidation(); [MenuItem(UserMenuPrefix + "/" + VRMHumanoidNormalizerMenu.MENU_NAME, false, 52)] private static void FreezeTPose() => VRMHumanoidNormalizerMenu.Normalize(); diff --git a/Assets/VRM/Runtime/FirstPerson/VRMFirstPerson.cs b/Assets/VRM/Runtime/FirstPerson/VRMFirstPerson.cs index 5f9e6a3fd..4bd4ff060 100644 --- a/Assets/VRM/Runtime/FirstPerson/VRMFirstPerson.cs +++ b/Assets/VRM/Runtime/FirstPerson/VRMFirstPerson.cs @@ -367,6 +367,17 @@ namespace VRM } } + /// + /// for MeshUtility interface + /// + public Mesh ProcessFirstPerson(Transform firstPersonBone, SkinnedMeshRenderer smr) + { + SetVisibilityFunc dummy = (Renderer renderer, bool firstPerson, bool thirdPerson) => + { + }; + return CreateHeadlessModel(smr, FirstPersonBone, dummy); + } + void OnDestroy() { foreach (var mesh in m_headlessMeshes) diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs index e0901616a..81f630c52 100644 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs +++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -66,32 +66,19 @@ namespace VRM } } - // 正規化されたヒエラルキーを作る - var (normalized, bMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go); + // Meshの焼きこみ + var newMesh = BoneNormalizer.NormalizeHierarchyFreezeMesh(go, true); + // 焼いたMeshで置き換える + BoneNormalizer.Replace(go, newMesh, true, true); // 新しいヒエラルキーからAvatarを作る - var newAvatar = UniHumanoid.AvatarDescription.CreateAvatarForCopyHierarchy( - go.GetComponent(), normalized, bMap, avatarDescription => - { - var vrmHuman = go.GetComponent(); - if (vrmHuman != null && vrmHuman.Description != null) - { - avatarDescription.armStretch = vrmHuman.Description.armStretch; - avatarDescription.legStretch = vrmHuman.Description.legStretch; - avatarDescription.upperArmTwist = vrmHuman.Description.upperArmTwist; - avatarDescription.lowerArmTwist = vrmHuman.Description.lowerArmTwist; - avatarDescription.upperLegTwist = vrmHuman.Description.upperLegTwist; - avatarDescription.lowerLegTwist = vrmHuman.Description.lowerLegTwist; - avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing; - avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF; - } - }); - var newAnimator = normalized.GetOrAddComponent(); + var newAnimator = go.GetComponent(); + var newAvatar = UniHumanoid.AvatarDescription.RecreateAvatar(newAnimator); newAnimator.avatar = newAvatar; - CopyVRMComponents(go, normalized, bMap); + // CopyVRMComponents(go, normalized, bMap); - return normalized; + return go; } /// diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VrmMeshUtility.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VrmMeshUtility.cs new file mode 100644 index 000000000..21d9bdd4f --- /dev/null +++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VrmMeshUtility.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UniHumanoid; +using UnityEngine; + + +namespace VRM +{ + public class VrmMeshUtility : UniGLTF.MeshUtility.GltfMeshUtility + { + bool _generateFirstPerson = false; + public override IEnumerable CopyInstantiate(GameObject go, GameObject instance) + { + _generateFirstPerson = false; + + var copy = base.CopyInstantiate(go, instance); + if (GenerateMeshForFirstPersonAuto) + { + foreach (var g in copy) + { + if (g.Name == "auto") + { + _generateFirstPerson = true; + // 元のメッシュを三人称に変更 + yield return new UniGLTF.MeshUtility.MeshIntegrationGroup + { + Name = FirstPersonFlag.ThirdPersonOnly.ToString(), + Renderers = g.Renderers.ToList(), + }; + } + yield return g; + } + } + else + { + foreach (var g in copy) + { + yield return g; + } + } + } + + protected override bool + TryIntegrate( + GameObject empty, + UniGLTF.MeshUtility.MeshIntegrationGroup group, + out (UniGLTF.MeshUtility.MeshIntegrationResult, GameObject[]) resultAndAdded) + { + if (!base.TryIntegrate(empty, group, out resultAndAdded)) + { + resultAndAdded = default; + return false; + } + + var (result, newGo) = resultAndAdded; + if (_generateFirstPerson && group.Name == nameof(FirstPersonFlag.Auto)) + { + // Mesh 統合の後処理 + // FirstPerson == "auto" の場合に + // 頭部の無いモデルを追加で作成する + Debug.Log("generateFirstPerson"); + if (result.Integrated.Mesh != null) + { + // BlendShape 有り + _ProcessFirstPerson(_vrmInstance.FirstPersonBone, result.Integrated.IntegratedRenderer); + } + if (result.IntegratedNoBlendShape.Mesh != null) + { + // BlendShape 無しの方 + _ProcessFirstPerson(_vrmInstance.FirstPersonBone, result.IntegratedNoBlendShape.IntegratedRenderer); + } + } + return true; + } + + private void _ProcessFirstPerson(Transform firstPersonBone, SkinnedMeshRenderer smr) + { + var mesh = _vrmInstance.ProcessFirstPerson(firstPersonBone, smr); + if (mesh != null) + { + smr.sharedMesh = mesh; + smr.name = "auto.headless"; + } + else + { + Debug.LogWarning("no result"); + } + } + + VRMFirstPerson _vrmInstance = null; + /// + /// glTF に比べて Humanoid や FirstPerson の処理が追加される + /// + public override (List, List) Process( + GameObject target, IEnumerable copyGroup) + { + _vrmInstance = target.GetComponent(); + if (_vrmInstance == null) + { + throw new ArgumentException(); + } + + // TODO: update: spring + // TODO: update: constraint + // TODO: update: firstPerson offset + var (list, newList) = base.Process(target, copyGroup); + + if (FreezeBlendShape || FreezeRotation || FreezeScaling) + { + var animator = target.GetComponent(); + var newAvatar = AvatarDescription.RecreateAvatar(animator); + + // ??? clear old avatar ??? + var t = animator.gameObject; + if (Application.isPlaying) + { + GameObject.Destroy(animator); + } + else + { + GameObject.DestroyImmediate(animator); + } + + t.AddComponent().avatar = newAvatar; + } + + return (list, newList); + } + + public override void UpdateMeshIntegrationGroups(GameObject root) + { + if (root == null) + { + return; + } + var vrm0 = root.GetComponent(); + if (vrm0 == null) + { + return; + } + foreach (var a in vrm0.Renderers) + { + var g = _GetOrCreateGroup(a.FirstPersonFlag.ToString()); + g.Renderers.Add(a.Renderer); + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VrmMeshUtility.cs.meta b/Assets/VRM/Runtime/SkinnedMeshUtility/VrmMeshUtility.cs.meta new file mode 100644 index 000000000..851cb3e0c --- /dev/null +++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VrmMeshUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb346da8b7688c74eb627bebe1b21060 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10ExpressionUpdater.cs b/Assets/VRM10/Editor/MeshUtility/Vrm10ExpressionUpdater.cs new file mode 100644 index 000000000..e4c1c6ff2 --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10ExpressionUpdater.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UniGLTF.MeshUtility; +using UnityEditor; +using UnityEngine; + + +namespace UniVRM10 +{ + public class Vrm10ExpressionUpdater + { + // BlendShapeBinding.RelativePath からの逆引き + Dictionary> _rendererPathMap = new(); + GameObject _root; + + Vrm10ExpressionUpdater(GameObject root, List results) + { + _root = root; + foreach (var result in results) + { + foreach (var x in result.SourceSkinnedMeshRenderers) + { + var srcPath = x.transform.RelativePathFrom(root.transform); + if (_rendererPathMap.TryGetValue(srcPath, out var value)) + { + value.Add(result); + } + else + { + value = new List(); + value.Add(result); + _rendererPathMap.Add(srcPath, value); + } + } + } + } + + // 分割されて増える => 増えない BlendShape のある方にいく + // 統合されて減る => 名前が同じものが統合される + IEnumerable ReplaceBlendShapeBinding(IEnumerable values) + { + var used = new HashSet(); + foreach (var val in values) + { + if (_rendererPathMap.TryGetValue(val.RelativePath, out var results)) + { + foreach (var result in results) + { + if (result.Integrated == null) + { + continue; + } + var name = result.Integrated.Mesh.GetBlendShapeName(val.Index); + var newIndex = result.Integrated.Mesh.GetBlendShapeIndex(name); + if (newIndex == -1) + { + throw new KeyNotFoundException($"blendshape:{name} not found"); + } + + var dstPath = result.Integrated.IntegratedRenderer.transform.RelativePathFrom(_root.transform); + var binding = new MorphTargetBinding + { + RelativePath = dstPath, + Index = newIndex, + Weight = val.Weight, + }; + if (used.Contains(binding)) + { + Debug.LogWarning($"duplicated: {binding}"); + } + else + { + if (VRMShaders.Symbols.VRM_DEVELOP) + { + Debug.Log($"{val} >> {binding}"); + } + used.Add(binding); + yield return binding; + } + } + } + else + { + // skip + Debug.LogWarning($"SkinnedMeshRenderer not found: {val.RelativePath}"); + } + } + } + + public static Dictionary Update(string assetFolder, GameObject instance, + List results) + { + var vrm = instance.GetComponent(); + var util = new Vrm10ExpressionUpdater(instance, results); + + // write Vrm10Expressions + var copyMap = new Dictionary(); + foreach (var (preset, clip) in vrm.Vrm.Expression.Clips) + { + var copy = ScriptableObject.Instantiate(clip); + copy.MorphTargetBindings = util.ReplaceBlendShapeBinding(clip.MorphTargetBindings).ToArray(); + var assetPath = $"{assetFolder}/{copy.name}.asset"; + AssetDatabase.CreateAsset(copy, assetPath); + copyMap.Add(clip, copy); + } + + // write Vrm10Object + { + var copy = ScriptableObject.Instantiate(vrm.Vrm); + var assetPath = $"{assetFolder}/{copy.name}.asset"; + copy.Expression.Replace(copyMap); + AssetDatabase.CreateAsset(copy, assetPath); + vrm.Vrm = copy; + } + + return copyMap; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10ExpressionUpdater.cs.meta b/Assets/VRM10/Editor/MeshUtility/Vrm10ExpressionUpdater.cs.meta new file mode 100644 index 000000000..4f97c265e --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10ExpressionUpdater.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d74b29fa7bb59ae4a9f641bf16468848 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshIntegrationTab.cs b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshIntegrationTab.cs new file mode 100644 index 000000000..4bee935da --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshIntegrationTab.cs @@ -0,0 +1,15 @@ +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + class Vrm10MeshIntegrationTab : UniGLTF.MeshUtility.MeshIntegrationTab + { + Vrm10MeshUtility _vrmMeshUtil; + + public Vrm10MeshIntegrationTab(EditorWindow editor, Vrm10MeshUtility meshUtility) : base(editor, meshUtility) + { + _vrmMeshUtil = meshUtility; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshIntegrationTab.cs.meta b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshIntegrationTab.cs.meta new file mode 100644 index 000000000..078ce5177 --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshIntegrationTab.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec2a4df10a08bab4a980b7203d341bda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs index 1ac07d0b4..e96caf019 100644 --- a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs @@ -1,163 +1,140 @@ using UnityEngine; using UnityEditor; +using UniGLTF; using UniGLTF.M17N; using System.Collections.Generic; -using UniGLTF.MeshUtility; -using UniGLTF; using System.Linq; + namespace UniVRM10 { - public class Vrm10MeshUtilityDialog : EditorWindow + public class Vrm10MeshUtilityDialog : UniGLTF.MeshUtility.MeshUtilityDialog { - public const string MENU_NAME = "VRM 1.0 MeshUtility"; - - enum Tabs - { - Freeze, - IntegrateSplit, - } - Tabs _tab; - - public static void OpenWindow() + public new const string MENU_NAME = "VRM 1.0 MeshUtility"; + public new static void OpenWindow() { var window = (Vrm10MeshUtilityDialog)EditorWindow.GetWindow(typeof(Vrm10MeshUtilityDialog)); window.titleContent = new GUIContent(MENU_NAME); window.Show(); } - - Vrm10MeshUtility _meshUtility = new Vrm10MeshUtility(); - - List _validations = new List(); - private void Validate() + protected override void Validate() { - _validations.Clear(); - if (_exportTarget == null) - { - _validations.Add(Validation.Error("set vrm1")); - return; - } + base.Validate(); if (_exportTarget.GetComponent() == null) { _validations.Add(Validation.Error("target is not vrm1")); return; } } - bool IsValid => !_validations.Any(v => !v.CanExport); - Vector2 _scrollPos; - GameObject _exportTarget; - MeshIntegrationTab _meshIntegration; - void OnEnable() + Vrm10MeshUtility _meshUtil; + Vrm10MeshUtility Vrm10MeshUtility { - _meshIntegration = new MeshIntegrationTab(this, _meshUtility); - } - - private void OnGUI() - { - var modified = false; - _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); - EditorGUIUtility.labelWidth = 200; - LanguageGetter.OnGuiSelectLang(); - var exportTarget = (GameObject)EditorGUILayout.ObjectField( - MeshUtilityMessages.TARGET_OBJECT.Msg(), - _exportTarget, typeof(GameObject), true); - if (exportTarget != _exportTarget) + get { - _exportTarget = exportTarget; - _meshIntegration.UpdateMeshIntegrationList(_exportTarget); - modified = true; - } - _tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed); - - foreach (var validation in _validations) - { - validation.DrawGUI(); - } - - switch (_tab) - { - case Tabs.Freeze: - { - if (MeshFreezeGui()) - { - modified = true; - } - break; - } - - case Tabs.IntegrateSplit: - { - if (MeshIntegrateGui()) - { - modified = true; - } - break; - } - } - EditorGUILayout.EndScrollView(); - - if (modified) - { - Validate(); - } - - GUI.enabled = IsValid; - var pressed = GUILayout.Button("Process", GUILayout.MinWidth(100)); - GUI.enabled = true; - if (pressed) - { - Undo.RegisterFullObjectHierarchyUndo(exportTarget, "MeshUtility"); - foreach (var go in _meshUtility.Process(exportTarget)) + if (_meshUtil == null) { - Undo.RegisterCreatedObjectUndo(go, "MeshUtility"); + _meshUtil = new Vrm10MeshUtility(); } - _exportTarget = null; - // Show Result ? - // Close(); - // GUIUtility.ExitGUI(); + return _meshUtil; + } + } + protected override UniGLTF.MeshUtility.GltfMeshUtility MeshUtility => Vrm10MeshUtility; + + Vrm10MeshIntegrationTab _integrationTab; + protected override UniGLTF.MeshUtility.MeshIntegrationTab MeshIntegration + { + get + { + if (_integrationTab == null) + { + _integrationTab = new Vrm10MeshIntegrationTab(this, Vrm10MeshUtility); + } + return _integrationTab; } } - bool ToggleIsModified(string label, ref bool value) + protected override bool MeshIntegrateGui() { - var newValue = EditorGUILayout.Toggle(label, value); - if (newValue == value) + var firstPerson = ToggleIsModified("FirstPerson == AUTO の生成", ref MeshUtility.GenerateMeshForFirstPersonAuto); + var mod = base.MeshIntegrateGui(); + return firstPerson || mod; + } + + List _clips; + protected override void WriteAssets(string assetFolder, GameObject instance, + List results) + { + _clips = Vrm10ExpressionUpdater.Update(assetFolder, instance, results).Values.ToList(); + + // write mesh + base.WriteAssets(assetFolder, instance, results); + } + + protected override string WritePrefab(string assetFolder, + GameObject instance) + { + var prefabPath = base.WritePrefab(assetFolder, instance); + + // PostProcess + // update prefab reference of BlendShapeClip + var prefabReference = AssetDatabase.LoadAssetAtPath(prefabPath); + foreach (var clip in _clips) { - return false; + var so = new SerializedObject(clip); + so.Update(); + var prop = so.FindProperty("m_prefab"); + prop.objectReferenceValue = prefabReference; + so.ApplyModifiedProperties(); } - value = newValue; - return true; + + return prefabPath; } - bool MeshFreezeGui() + protected override void DialogMessage() { - var forceUniqueName = ToggleIsModified("ForceUniqueName", ref _meshUtility.ForceUniqueName); - var blendShape = ToggleIsModified("BlendShape", ref _meshUtility.FreezeBlendShape); - var scale = ToggleIsModified("Scale", ref _meshUtility.FreezeScaling); - var rotation = ToggleIsModified("Rotation", ref _meshUtility.FreezeRotation); - return forceUniqueName || blendShape || scale || rotation; + EditorGUILayout.HelpBox(Message.MESH_UTILITY.Msg(), MessageType.Info); } - - bool MeshIntegrateGui() + enum Message { - var firstPerson = ToggleIsModified("FirstPerson == AUTO の生成", ref _meshUtility.GenerateMeshForFirstPersonAuto); - var split = ToggleIsModified("Separate by BlendShape", ref _meshUtility.SplitByBlendShape); - var p = position; - var last = GUILayoutUtility.GetLastRect(); - var y = last.y + last.height; - var rect = new Rect - { - x = last.x, - y = y, - width = p.width, - height = p.height - y - // process button の高さ - - 30 - }; - var mod = _meshIntegration.OnGui(rect); - return firstPerson || split || mod; + [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/Editor/Vrm10TopMenu.cs b/Assets/VRM10/Editor/Vrm10TopMenu.cs index 999283894..3749c5e63 100644 --- a/Assets/VRM10/Editor/Vrm10TopMenu.cs +++ b/Assets/VRM10/Editor/Vrm10TopMenu.cs @@ -13,15 +13,15 @@ namespace UniVRM10 [MenuItem(UserMenuPrefix + "/" + VRM10ExportDialog.MENU_NAME, priority = 1)] private static void OpenExportDialog() => VRM10ExportDialog.Open(); - [MenuItem(UserMenuPrefix + "/" + Vrm10MeshUtilityDialog.MENU_NAME, priority = 2)] + [MenuItem(UserMenuPrefix + "/" + Vrm10MeshUtilityDialog.MENU_NAME, priority = 21)] private static void OpenMeshUtility() => Vrm10MeshUtilityDialog.OpenWindow(); - [MenuItem(ExperimentalMenuPrefix + "/" + VrmAnimationMenu.MENU_NAME, priority = 21)] + [MenuItem(ExperimentalMenuPrefix + "/" + VrmAnimationMenu.MENU_NAME, priority = 22)] private static void ConvertVrmAnimation() => VrmAnimationMenu.BvhToVrmAnimationMenu(); #if VRM_DEVELOP - [MenuItem(ExperimentalMenuPrefix + "/" + VRM10Window.MENU_NAME, false, 22)] + [MenuItem(ExperimentalMenuPrefix + "/" + VRM10Window.MENU_NAME, false, 23)] private static void OpenWindow() => VRM10Window.Open(); [MenuItem(DevelopmentMenuPrefix + "/Generate from JsonSchema", false, 100)] diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs index e66253d80..1bae527f2 100644 --- a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs +++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs @@ -1,96 +1,125 @@ using System; using System.Collections.Generic; using System.Linq; -using UniGLTF.MeshUtility; using UniHumanoid; using UnityEngine; -using VRMShaders; + namespace UniVRM10 { - /// - /// - Freeze - /// - Integration - /// - Split - /// - /// - Implement runtime logic => Process a hierarchy in scene. Do not process prefab. - /// - Implement undo - /// - /// - public class Vrm10MeshUtility + public class Vrm10MeshUtility : UniGLTF.MeshUtility.GltfMeshUtility { - /// - /// GameObject 名が重複している場合にリネームする。 - /// 最初に実行(Avatar生成時のエラーを回避?) - /// - public bool ForceUniqueName = false; - - /// - /// Same as VRM-0 normalization - /// - Mesh - /// - Node - /// - InverseBindMatrices - /// - public bool FreezeBlendShape = false; - - /// - /// Same as VRM-0 normalization - /// - Mesh - /// - Node - /// - InverseBindMatrices - /// - public bool FreezeScaling = true; - - /// - /// Same as VRM-0 normalization - /// - Mesh - /// - Node - /// - InverseBindMatrices - /// - public bool FreezeRotation = false; - - public List MeshIntegrationGroups = new List(); - - /// - /// Create a headless model and solve VRM.FirstPersonFlag.Auto - /// - public bool GenerateMeshForFirstPersonAuto = false; - - /// - /// Split into having and not having BlendShape - /// - public bool SplitByBlendShape = false; - - public void IntegrateAll(GameObject root) + bool _generateFirstPerson = false; + public override IEnumerable CopyInstantiate(GameObject go, GameObject instance) { - if (root == null) + var copy = base.CopyInstantiate(go, instance); + _generateFirstPerson = false; + if (GenerateMeshForFirstPersonAuto) { - return; - } - MeshIntegrationGroups.Add(new MeshIntegrationGroup - { - Name = "ALL", - Renderers = root.GetComponentsInChildren().ToList(), - }); - } - - MeshIntegrationGroup GetOrCreateGroup(string name) - { - foreach (var g in MeshIntegrationGroups) - { - if (g.Name == name) + foreach (var g in copy) { - return g; + if (g.Name == "auto") + { + _generateFirstPerson = true; + // 元のメッシュを三人称に変更 + yield return new UniGLTF.MeshUtility.MeshIntegrationGroup + { + Name = UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly.ToString(), + Renderers = g.Renderers.ToList(), + }; + } + yield return g; } } - MeshIntegrationGroups.Add(new MeshIntegrationGroup + else { - Name = name, - }); - return MeshIntegrationGroups.Last(); + foreach (var g in copy) + { + yield return g; + } + } } - public void IntegrateFirstPerson(GameObject root) + protected override + bool TryIntegrate( + GameObject empty, + UniGLTF.MeshUtility.MeshIntegrationGroup group, + out (UniGLTF.MeshUtility.MeshIntegrationResult, GameObject[]) resultAndAdded) + { + if (!base.TryIntegrate(empty, group, out resultAndAdded)) + { + return false; + } + var (result, newList) = resultAndAdded; + + if (_generateFirstPerson && group.Name == nameof(UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto)) + { + // Mesh 統合の後処理 + // FirstPerson == "auto" の場合に + // 頭部の無いモデルを追加で作成する + Debug.Log("generateFirstPerson"); + if (result.Integrated.Mesh != null) + { + // BlendShape 有り + _ProcessFirstPerson(_vrmInstance.Humanoid.Head, result.Integrated.IntegratedRenderer); + } + if (result.IntegratedNoBlendShape.Mesh != null) + { + // BlendShape 無しの方 + _ProcessFirstPerson(_vrmInstance.Humanoid.Head, result.IntegratedNoBlendShape.IntegratedRenderer); + } + } + return true; + } + + private void _ProcessFirstPerson(Transform firstPersonBone, SkinnedMeshRenderer smr) + { + var task = VRM10ObjectFirstPerson.CreateErasedMeshAsync( + smr, + firstPersonBone, + new VRMShaders.ImmediateCaller()); + task.Wait(); + var mesh = task.Result; + if (mesh != null) + { + smr.sharedMesh = mesh; + smr.name = "auto.headless"; + } + else + { + Debug.LogWarning("no result"); + } + } + + Vrm10Instance _vrmInstance = null; + /// + /// glTF に比べて Humanoid や FirstPerson の処理が追加される + /// + public override (List, List) Process( + GameObject target, IEnumerable groupCopy) + { + _vrmInstance = target.GetComponent(); + if (_vrmInstance == null) + { + throw new ArgumentException(); + } + + // TODO: update: spring + // TODO: update: constraint + // TODO: update: firstPerson offset + var (list, newList) = base.Process(target, groupCopy); + + if (FreezeBlendShape || FreezeRotation || FreezeScaling) + { + var animator = target.GetComponent(); + var newAvatar = AvatarDescription.RecreateAvatar(animator); + animator.avatar = newAvatar; + } + + return (list, newList); + } + + public override void UpdateMeshIntegrationGroups(GameObject root) { if (root == null) { @@ -113,202 +142,9 @@ namespace UniVRM10 } foreach (var a in fp.Renderers) { - var g = GetOrCreateGroup(a.FirstPersonFlag.ToString()); + var g = _GetOrCreateGroup(a.FirstPersonFlag.ToString()); g.Renderers.Add(a.GetRenderer(root.transform)); } } - - void RemoveComponent(T c) where T : Component - { - if (c == null) - { - return; - } - var t = c.transform; - if (Application.isPlaying) - { - GameObject.Destroy(c); - } - else - { - GameObject.DestroyImmediate(c); - } - - if (t.childCount == 0) - { - var list = t.GetComponents(); - // Debug.Log($"{list[0].GetType().Name}"); - if (list.Length == 1 && list[0] == t) - { - if (Application.isPlaying) - { - GameObject.Destroy(t.gameObject); - } - else - { - GameObject.DestroyImmediate(t.gameObject); - } - } - } - } - - static GameObject GetOrCreateEmpty(GameObject go, string name) - { - foreach (var child in go.transform.GetChildren()) - { - if (child.name == name - && child.localPosition == Vector3.zero - && child.localScale == Vector3.one - && child.localRotation == Quaternion.identity) - { - return child.gameObject; - } - } - var empty = new GameObject(name); - empty.transform.SetParent(go.transform, false); - return empty; - } - - public List Process(GameObject go) - { - var vrmInstance = go.GetComponent(); - if (vrmInstance == null) - { - throw new ArgumentException(); - } - - // TODO unpack prefab - - if (ForceUniqueName) - { - throw new NotImplementedException(); - - // 必用? - var animator = go.GetComponent(); - var newAvatar = AvatarDescription.RecreateAvatar(animator); - animator.avatar = newAvatar; - } - - // 正規化されたヒエラルキーを作る - if (FreezeBlendShape || FreezeRotation || FreezeScaling) - { - // TODO: UNDO - var (normalized, boneMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go, - removeScaling: FreezeScaling, - removeRotation: FreezeRotation, - freezeBlendShape: FreezeBlendShape); - - // TODO: update: spring - // TODO: update: constraint - // TODO: update: firstPerson offset - - // write back normalized transform to boneMap - BoneNormalizer.WriteBackResult(go, normalized, boneMap); - if (Application.isPlaying) - { - GameObject.Destroy(normalized); - } - else - { - GameObject.DestroyImmediate(normalized); - } - - var animator = go.GetComponent(); - var newAvatar = AvatarDescription.RecreateAvatar(animator); - animator.avatar = newAvatar; - } - - var copy = new List(); - var generateFirstPerson = false; - if (GenerateMeshForFirstPersonAuto) - { - foreach (var g in MeshIntegrationGroups) - { - if (g.Name == "auto") - { - generateFirstPerson = true; - // 元のメッシュを三人称に変更 - copy.Add(new MeshIntegrationGroup - { - Name = UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly.ToString(), - Renderers = g.Renderers.ToList(), - }); - } - copy.Add(g); - } - } - else - { - copy.AddRange(MeshIntegrationGroups); - } - - var newGo = new List(); - { - var empty = GetOrCreateEmpty(go, "mesh"); - - var results = new List(); - foreach (var group in copy) - { - 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); - } - - if (generateFirstPerson && group.Name == "auto") - { - Debug.Log("generateFirstPerson"); - if (result.Integrated.Mesh != null) - { - ProcessFirstPerson(vrmInstance, result.Integrated); - } - if (result.IntegratedNoBlendShape.Mesh != null) - { - ProcessFirstPerson(vrmInstance, result.IntegratedNoBlendShape); - } - } - } - - foreach (var result in results) - { - foreach (var r in result.SourceMeshRenderers) - { - RemoveComponent(r); - } - foreach (var r in result.SourceSkinnedMeshRenderers) - { - RemoveComponent(r); - } - } - } - - MeshIntegrationGroups.Clear(); - - return newGo; - } - - void ProcessFirstPerson(Vrm10Instance vrmInstance, MeshInfo info) - { - var firstPersonBone = vrmInstance.Humanoid.Head; - var task = VRM10ObjectFirstPerson.CreateErasedMeshAsync( - info.IntegratedRenderer, - firstPersonBone, - new ImmediateCaller()); - task.Wait(); - - if (task.Result != null) - { - info.IntegratedRenderer.sharedMesh = task.Result; - info.IntegratedRenderer.name = "auto.headless"; - } - else - { - Debug.LogWarning("no result"); - } - } } -} +} \ No newline at end of file