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