diff --git a/Assets/UniGLTF/Editor/MeshUtility/AssetsPath.cs b/Assets/UniGLTF/Editor/MeshUtility/AssetsPath.cs
deleted file mode 100644
index f3bedd50e..000000000
--- a/Assets/UniGLTF/Editor/MeshUtility/AssetsPath.cs
+++ /dev/null
@@ -1,309 +0,0 @@
-using System;
-using System.IO;
-using UnityEngine;
-using System.Collections.Generic;
-using UnityEditor;
-
-
-namespace UniGLTF.MeshUtility
-{
- ///
- /// Application.dataPath を root とするファイルパスを扱う。
- /// (Project/Assets)
- ///
- public struct AssetsPath
- {
- public string RelativePath
- {
- get;
- private set;
- }
-
- public override string ToString()
- {
- return $"assets://{RelativePath}";
- }
-
- public string FileName
- {
- get { return Path.GetFileName(RelativePath); }
- }
-
- public string FileNameWithoutExtension
- {
- get
- {
- return Path.GetFileNameWithoutExtension(RelativePath);
- }
- }
-
- public string Extension
- {
- get { return Path.GetExtension(RelativePath); }
- }
-
- public AssetsPath Parent
- {
- get
- {
- return new AssetsPath(Path.GetDirectoryName(RelativePath));
- }
- }
-
- static readonly char[] EscapeChars = new char[]
- {
- '\\',
- '/',
- ':',
- '*',
- '?',
- '"',
- '<',
- '>',
- '|',
- };
-
- static string EscapeFilePath(string path)
- {
- foreach (var x in EscapeChars)
- {
- path = path.Replace(x, '+');
- }
- return path;
- }
-
- public AssetsPath Child(string name)
- {
- if (RelativePath == null)
- {
- throw new NotImplementedException();
- }
- else if (RelativePath == "")
- {
- return new AssetsPath(name);
- }
- else
- {
- return new AssetsPath(RelativePath + "/" + name);
- }
- }
-
- public override int GetHashCode()
- {
- if (RelativePath == null)
- {
- return 0;
- }
- return RelativePath.GetHashCode();
- }
-
- public override bool Equals(object obj)
- {
- if (obj is AssetsPath)
- {
- var rhs = (AssetsPath)obj;
- if (RelativePath == null && rhs.RelativePath == null)
- {
- return true;
- }
- else if (RelativePath == null)
- {
- return false;
- }
- else if (rhs.RelativePath == null)
- {
- return false;
- }
- else
- {
- return RelativePath == rhs.RelativePath;
- }
- }
- else
- {
- return false;
- }
- }
-
- AssetsPath(string value) : this()
- {
- RelativePath = value.Replace("\\", "/");
- }
-
- #region FullPath
- static string s_basePath;
- static string BaseFullPath
- {
- get
- {
- if (string.IsNullOrEmpty(s_basePath))
- {
- s_basePath = Path.GetFullPath(Application.dataPath).Replace("\\", "/");
- }
- return s_basePath;
- }
- }
-
- public string FullPath
- {
- get
- {
- if (RelativePath == null)
- {
- throw new NotImplementedException();
- }
- return Path.Combine(BaseFullPath, RelativePath).Replace("\\", "/");
- }
- }
-
- public bool IsFileExists
- {
- get { return File.Exists(FullPath); }
- }
-
- public bool IsDirectoryExists
- {
- get { return Directory.Exists(FullPath); }
- }
-
- ///
- ///
- ///
- /// C:/path/to/file
- ///
- public static AssetsPath FromFullpath(string fullPath)
- {
- if (fullPath == null)
- {
- fullPath = "";
- }
- fullPath = fullPath.Replace("\\", "/");
-
- if (fullPath == BaseFullPath)
- {
- return new AssetsPath
- {
- RelativePath = ""
- };
- }
- else if (fullPath.StartsWith(BaseFullPath + "/"))
- {
- return new AssetsPath(fullPath.Substring(BaseFullPath.Length + 1));
- }
- else
- {
- return default(AssetsPath);
- }
- }
- #endregion
-
- public IEnumerable TraverseDir()
- {
- if (IsDirectoryExists)
- {
- yield return this;
-
- foreach (var child in ChildDirs)
- {
- foreach (var x in child.TraverseDir())
- {
- yield return x;
- }
- }
- }
- }
-
- public IEnumerable ChildDirs
- {
- get
- {
- foreach (var x in Directory.GetDirectories(FullPath))
- {
- yield return AssetsPath.FromFullpath(x);
- }
- }
- }
-
- public IEnumerable ChildFiles
- {
- get
- {
- foreach (var x in Directory.GetFiles(FullPath))
- {
- yield return AssetsPath.FromFullpath(x);
- }
- }
- }
-
-#if UNITY_EDITOR
- string UnityPath => $"Assets/{RelativePath}";
-
- public T GetImporter() where T : AssetImporter
- {
- return AssetImporter.GetAtPath(UnityPath) as T;
- }
-
- public static AssetsPath FromAsset(UnityEngine.Object asset)
- {
- // skip Assets/
- return new AssetsPath(AssetDatabase.GetAssetPath(asset).Substring(7));
- }
-
- public void ImportAsset()
- {
- AssetDatabase.ImportAsset(UnityPath);
- }
-
- public void EnsureFolder()
- {
- if (RelativePath == null)
- {
- throw new NotImplementedException();
- }
-
- if (!string.IsNullOrEmpty(Parent.RelativePath))
- {
- Parent.EnsureFolder();
- }
-
- if (!IsDirectoryExists)
- {
- var parent = Parent;
- // ensure parent
- parent.ImportAsset();
- // create
- AssetDatabase.CreateFolder(
- parent.UnityPath,
- Path.GetFileName(RelativePath)
- );
- ImportAsset();
- }
- }
-
- public UnityEngine.Object[] GetSubAssets()
- {
- return AssetDatabase.LoadAllAssetsAtPath(UnityPath);
- }
-
- public void CreateAsset(UnityEngine.Object o)
- {
- AssetDatabase.CreateAsset(o, UnityPath);
- }
-
- public void AddObjectToAsset(UnityEngine.Object o)
- {
- AssetDatabase.AddObjectToAsset(o, UnityPath);
- }
-
- public T LoadAsset() where T : UnityEngine.Object
- {
- return AssetDatabase.LoadAssetAtPath(UnityPath);
- }
-
- public AssetsPath GenerateUniqueAssetPath()
- {
- return new AssetsPath(AssetDatabase.GenerateUniqueAssetPath(UnityPath));
- }
-#endif
- }
-}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs
similarity index 82%
rename from Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs
rename to Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs
index e14659de0..212e77b9b 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs
@@ -15,20 +15,20 @@ namespace UniGLTF.MeshUtility
/// UnityEngine.Object を継承したクラスのフィールドに ユーザー定義 struct を配置する。
/// 持ち主の SerializedObject を経由して EditorGUILayout.PropertyField してる。
///
- [CustomEditor(typeof(MeshProcessDialog), true)]
- class MeshProcessDialogEditor : Editor
+ [CustomEditor(typeof(MeshUtilityDialog), true)]
+ class BoneMeshEraserEditor : Editor
{
- MeshProcessDialog _targetDialog;
+ MeshUtilityDialog _targetDialog;
SerializedProperty _skinnedMesh;
SerializedProperty _eraseBones;
void OnEnable()
{
- _targetDialog = target as MeshProcessDialog;
+ _targetDialog = target as MeshUtilityDialog;
if (_targetDialog)
{
- _skinnedMesh = serializedObject.FindProperty(nameof(MeshProcessDialog._skinnedMeshRenderer));
- _eraseBones = serializedObject.FindProperty(nameof(MeshProcessDialog._eraseBones));
+ _skinnedMesh = serializedObject.FindProperty(nameof(MeshUtilityDialog._skinnedMeshRenderer));
+ _eraseBones = serializedObject.FindProperty(nameof(MeshUtilityDialog._eraseBones));
}
}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs.meta
similarity index 83%
rename from Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta
rename to Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs.meta
index de1678682..d256f2e98 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta
+++ b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserEditor.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 04fa1873cf1c0374aab1670a24d741b5
+guid: 2021859c0d7255643bd93c95ab3fcf3d
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs
deleted file mode 100644
index 87ad28880..000000000
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace UniGLTF.MeshUtility
-{
- public enum MeshProcessDialogTabs
- {
- MeshSeparator,
- MeshIntegrator,
- BoneMeshEraser,
- }
-}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs.meta
deleted file mode 100644
index e3b56a221..000000000
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs.meta
+++ /dev/null
@@ -1,11 +0,0 @@
-fileFormatVersion: 2
-guid: 575d573f28f0e4a48a2a6509e69b7f6f
-MonoImporter:
- externalObjects: {}
- serializedVersion: 2
- defaultReferences: []
- executionOrder: 0
- icon: {instanceID: 0}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs
similarity index 77%
rename from Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs
rename to Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs
index b1fc04feb..36e91ec5c 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs
@@ -5,9 +5,16 @@ using System.Collections.Generic;
namespace UniGLTF.MeshUtility
{
- public class MeshProcessDialog : EditorWindow
+ public class MeshUtilityDialog : EditorWindow
{
- const string TITLE = "Mesh Processing Window";
+ const string TITLE = "Mesh Utility Window";
+
+ enum MeshProcessDialogTabs
+ {
+ MeshSeparator,
+ MeshIntegrator,
+ BoneMeshEraser,
+ }
MeshProcessDialogTabs _tab;
private GameObject _exportTarget;
@@ -21,13 +28,13 @@ namespace UniGLTF.MeshUtility
[SerializeField]
public List _eraseBones;
- private MeshProcessDialogEditor _boneMeshEraserEditor;
+ private BoneMeshEraserEditor _boneMeshEraserEditor;
private Vector2 _scrollPos = new Vector2(0, 0);
public static void OpenWindow()
{
var window =
- (MeshProcessDialog)EditorWindow.GetWindow(typeof(MeshProcessDialog));
+ (MeshUtilityDialog)EditorWindow.GetWindow(typeof(MeshUtilityDialog));
window.titleContent = new GUIContent(TITLE);
window.Show();
}
@@ -36,7 +43,7 @@ namespace UniGLTF.MeshUtility
{
if (!_boneMeshEraserEditor)
{
- _boneMeshEraserEditor = (MeshProcessDialogEditor)Editor.CreateEditor(this);
+ _boneMeshEraserEditor = (BoneMeshEraserEditor)Editor.CreateEditor(this);
}
}
@@ -45,7 +52,7 @@ namespace UniGLTF.MeshUtility
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
EditorGUIUtility.labelWidth = 200;
LanguageGetter.OnGuiSelectLang();
- _exportTarget = (GameObject)EditorGUILayout.ObjectField(MeshProcessingMessages.TARGET_OBJECT.Msg(), _exportTarget, typeof(GameObject), true);
+ _exportTarget = (GameObject)EditorGUILayout.ObjectField(MeshUtilityMessages.TARGET_OBJECT.Msg(), _exportTarget, typeof(GameObject), true);
_tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed);
var processed = false;
@@ -53,7 +60,7 @@ namespace UniGLTF.MeshUtility
{
case MeshProcessDialogTabs.MeshSeparator:
{
- EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_SEPARATOR.Msg(), MessageType.Info);
+ EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_SEPARATOR.Msg(), MessageType.Info);
if (TabMeshSeparator.TryExecutable(_exportTarget, out string msg))
{
processed = TabMeshSeparator.OnGUI(_exportTarget);
@@ -67,8 +74,8 @@ namespace UniGLTF.MeshUtility
case MeshProcessDialogTabs.MeshIntegrator:
{
- EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info);
- _separateByBlendShape = EditorGUILayout.Toggle(MeshProcessingMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg(), _separateByBlendShape);
+ 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 (GUILayout.Button("Process", GUILayout.MinWidth(100)))
@@ -85,7 +92,7 @@ namespace UniGLTF.MeshUtility
case MeshProcessDialogTabs.BoneMeshEraser:
{
- EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info);
+ EditorGUILayout.HelpBox(MeshUtilityMessages.BONE_MESH_ERASER.Msg(), MessageType.Info);
if (_boneMeshEraserEditor)
{
_boneMeshEraserEditor.OnInspectorGUI();
diff --git a/Assets/UniGLTF/Editor/MeshUtility/AssetsPath.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs.meta
similarity index 83%
rename from Assets/UniGLTF/Editor/MeshUtility/AssetsPath.cs.meta
rename to Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs.meta
index 4918b11e4..826fcfe43 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/AssetsPath.cs.meta
+++ b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityDialog.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: da24ccbbed21eb94d902d42337df3699
+guid: 1bdcb928351f92047a91c17c3d629853
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs
similarity index 99%
rename from Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs
rename to Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs
index 965e70846..f87c06c5b 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs
@@ -2,7 +2,7 @@ using UniGLTF.M17N;
namespace UniGLTF.MeshUtility
{
- public enum MeshProcessingMessages
+ public enum MeshUtilityMessages
{
[LangMsg(Languages.ja, "ターゲットオブジェクト")]
[LangMsg(Languages.en, "TargetObject")]
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs.meta
similarity index 83%
rename from Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs.meta
rename to Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs.meta
index 76e129bdc..8d37d7253 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs.meta
+++ b/Assets/UniGLTF/Editor/MeshUtility/MeshUtilityMessages.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 829a678062065f04582189619babae79
+guid: abaf50b8dc367d4448cd5d7c99a4eea3
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs
index 9e7566e78..ac06f7747 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs
@@ -17,13 +17,13 @@ namespace UniGLTF.MeshUtility
{
if (root == null)
{
- msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
+ msg = MeshUtilityMessages.NO_GAMEOBJECT_SELECTED.Msg();
return false;
}
if (smr == null)
{
- msg = MeshProcessingMessages.SELECT_SKINNED_MESH.Msg();
+ msg = MeshUtilityMessages.SELECT_SKINNED_MESH.Msg();
return false;
}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs
index 95c19948b..847773adb 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs
@@ -13,19 +13,19 @@ namespace UniGLTF.MeshUtility
// check
if (root == null)
{
- msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
+ msg = MeshUtilityMessages.NO_GAMEOBJECT_SELECTED.Msg();
return false;
}
if (HasVrm(root))
{
- msg = MeshProcessingMessages.VRM_DETECTED.Msg();
+ msg = MeshUtilityMessages.VRM_DETECTED.Msg();
return false;
}
if (root.GetComponentsInChildren().Length == 0 && root.GetComponentsInChildren().Length == 0)
{
- msg = MeshProcessingMessages.NO_MESH.Msg();
+ msg = MeshUtilityMessages.NO_MESH.Msg();
return false;
}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs
index 6e512c858..992681a57 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs
@@ -24,13 +24,13 @@ namespace UniGLTF.MeshUtility
{
if (root == null)
{
- msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
+ msg = MeshUtilityMessages.NO_GAMEOBJECT_SELECTED.Msg();
return false;
}
if (root.GetComponentsInChildren().Length == 0)
{
- msg = MeshProcessingMessages.NO_SKINNED_MESH.Msg();
+ msg = MeshUtilityMessages.NO_SKINNED_MESH.Msg();
return false;
}
diff --git a/Assets/UniGLTF/Editor/TopMenu.cs b/Assets/UniGLTF/Editor/TopMenu.cs
index f41b442e6..390e155ba 100644
--- a/Assets/UniGLTF/Editor/TopMenu.cs
+++ b/Assets/UniGLTF/Editor/TopMenu.cs
@@ -10,7 +10,6 @@ namespace UniGLTF
public static class TopMenu
{
private const string UserGltfMenuPrefix = UniGLTFVersion.MENU;
- private const string UserMeshUtilityPrefix = UniGLTFVersion.MENU + "/Mesh Utility";
private const string DevelopmentMenuPrefix = UniGLTFVersion.MENU + "/Development";
[MenuItem(UserGltfMenuPrefix + "/Version: " + UniGLTFVersion.UNIGLTF_VERSION, validate = true)]
@@ -25,8 +24,8 @@ namespace UniGLTF
[MenuItem(UserGltfMenuPrefix + "/Import from GLTF (*.gltf|*.glb|*.zip)", priority = 2)]
private static void ImportGltfFile() => TopMenuImplementation.ImportGltfFileToGameObject();
- [MenuItem(UserMeshUtilityPrefix + "/MeshProcessing Wizard", priority = 10)]
- private static void OpenMeshProcessingWindow() => MeshUtility.MeshProcessDialog.OpenWindow();
+ [MenuItem(UserGltfMenuPrefix + "/MeshUtility Wizard", priority = 10)]
+ private static void OpenMeshProcessingWindow() => MeshUtility.MeshUtilityDialog.OpenWindow();
#if VRM_DEVELOP
[MenuItem(DevelopmentMenuPrefix + "/Generate Serialization Code", priority = 20)]
diff --git a/Assets/UniGLTF/Editor/UniGLTF/ExportDialog/TabBar.cs b/Assets/UniGLTF/Editor/UniGLTF/ExportDialog/TabBar.cs
index 8bea7db02..09ffd2264 100644
--- a/Assets/UniGLTF/Editor/UniGLTF/ExportDialog/TabBar.cs
+++ b/Assets/UniGLTF/Editor/UniGLTF/ExportDialog/TabBar.cs
@@ -5,7 +5,6 @@ using UnityEngine;
namespace UniGLTF.MeshUtility
{
-
public static class TabBar
{
///
diff --git a/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs b/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs
index e642d0cba..24d3caad0 100644
--- a/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs
+++ b/Assets/UniGLTF/Runtime/MeshUtility/BoneNormalizer.cs
@@ -11,36 +11,35 @@ namespace UniGLTF.MeshUtility
{
public delegate Avatar CreateAvatarFunc(GameObject original, GameObject normalized, Dictionary boneMap);
- static (GameObject, Dictionary) NormalizeHierarchy(GameObject go, CreateAvatarFunc createAvatar)
+ public static (GameObject, Dictionary) CreateNormalizedHierarchy(GameObject go,
+ bool removeScaling = true,
+ bool removeRotation = true)
{
var boneMap = new Dictionary();
-
- //
- // 回転・スケールの無いヒエラルキーをコピーする
- //
var normalized = new GameObject(go.name + "(normalized)");
normalized.transform.position = go.transform.position;
- CopyAndBuild(go.transform, normalized.transform, boneMap);
- //
- // 新しいヒエラルキーからAvatarを作る
- //
+ if (removeScaling && removeRotation)
{
- var animator = normalized.AddComponent();
- var avatar = createAvatar(go, normalized, boneMap);
- avatar.name = go.name + ".normalized";
- animator.avatar = avatar;
+ 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 CopyAndBuild(Transform src, Transform dst, Dictionary boneMap)
+ static void RemoveScaleRecursive(Transform src, Transform dst, Dictionary boneMap)
{
boneMap[src] = dst;
@@ -50,9 +49,27 @@ namespace UniGLTF.MeshUtility
{
var dstChild = new GameObject(child.name);
dstChild.transform.SetParent(dst);
- dstChild.transform.position = child.position; // copy position only
+ 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);
+ }
+ }
+ }
- CopyAndBuild(child, dstChild.transform, boneMap);
+ 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);
}
}
}
@@ -489,12 +506,12 @@ namespace UniGLTF.MeshUtility
/// BlendShapeを0クリアするか否か。false の場合 BlendShape の現状を Bake する
/// Avatarを作る関数
///
- public static (GameObject, Dictionary) Execute(GameObject go, CreateAvatarFunc createAvatar)
+ public static (GameObject, Dictionary) Execute(GameObject go)
{
//
// 正規化されたヒエラルキーを作る
//
- var (normalized, boneMap) = NormalizeHierarchy(go, createAvatar);
+ var (normalized, boneMap) = CreateNormalizedHierarchy(go);
//
// 各メッシュから回転・スケールを取り除いてBinding行列を再計算する
diff --git a/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs b/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs
index 13b2e76c6..c3412d014 100644
--- a/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs
+++ b/Assets/UniGLTF/Runtime/UniHumanoid/AvatarDescription.cs
@@ -5,6 +5,7 @@ using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
+using UniGLTF.Utils;
namespace UniHumanoid
@@ -271,5 +272,42 @@ namespace UniHumanoid
return false;
}
#endif
+
+ public static void AddAnimator(GameObject _src,
+ GameObject dst,
+ IDictionary boneMap,
+ Action modAvatarDesc = null)
+ {
+ var src = _src.GetComponent();
+
+ var srcHumanBones = CachedEnum.GetValues()
+ .Where(x => x != HumanBodyBones.LastBone)
+ .Select(x => new { Key = x, Value = src.GetBoneTransform(x) })
+ .Where(x => x.Value != null)
+ ;
+
+ var map =
+ srcHumanBones
+ .Where(x => boneMap.ContainsKey(x.Value))
+ .ToDictionary(x => x.Key, x => boneMap[x.Value])
+ ;
+
+ var animator = dst.AddComponent();
+ if (animator == null)
+ {
+ animator = dst.AddComponent();
+ }
+
+ var avatarDescription = UniHumanoid.AvatarDescription.Create();
+ if (modAvatarDesc != null)
+ {
+ modAvatarDesc(avatarDescription);
+ }
+ avatarDescription.SetHumanBones(map);
+ var avatar = avatarDescription.CreateAvatar(dst.transform);
+
+ avatar.name = dst.name;
+ animator.avatar = avatar;
+ }
}
}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs
index f3fe1dca7..56fcca10b 100644
--- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs
+++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs
@@ -65,31 +65,13 @@ namespace VRM
}
}
- //
// 正規化されたヒエラルキーを作る
- //
- var (normalized, bMap) = BoneNormalizer.Execute(go, (_src, dst, boneMap) =>
+ var (normalized, bMap) = BoneNormalizer.Execute(go);
+
+ // 新しいヒエラルキーからAvatarを作る
+ UniHumanoid.AvatarDescription.AddAnimator(go, normalized, bMap, avatarDescription =>
{
- var src = _src.GetComponent();
-
- var srcHumanBones = CachedEnum.GetValues()
- .Where(x => x != HumanBodyBones.LastBone)
- .Select(x => new { Key = x, Value = src.GetBoneTransform(x) })
- .Where(x => x.Value != null)
- ;
-
- var map =
- srcHumanBones
- .Where(x => boneMap.ContainsKey(x.Value))
- .ToDictionary(x => x.Key, x => boneMap[x.Value])
- ;
-
- if (dst.GetComponent() == null)
- {
- var animator = dst.AddComponent();
- }
var vrmHuman = go.GetComponent();
- var avatarDescription = AvatarDescription.Create();
if (vrmHuman != null && vrmHuman.Description != null)
{
avatarDescription.armStretch = vrmHuman.Description.armStretch;
@@ -101,9 +83,6 @@ namespace VRM
avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing;
avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF;
}
- avatarDescription.SetHumanBones(map);
- var avatar = avatarDescription.CreateAvatar(dst.transform);
- return avatar;
});
CopyVRMComponents(go, normalized, bMap);
diff --git a/Assets/VRM10/Editor/MeshUtility.meta b/Assets/VRM10/Editor/MeshUtility.meta
new file mode 100644
index 000000000..a7f3f27f4
--- /dev/null
+++ b/Assets/VRM10/Editor/MeshUtility.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6fb2795d5ca5ce9449de94ea5fb94dde
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs b/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs
new file mode 100644
index 000000000..3812e0413
--- /dev/null
+++ b/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs
@@ -0,0 +1,96 @@
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEditorInternal;
+using UnityEngine;
+
+namespace UniVRM10
+{
+ class MeshIntegrationTab
+ {
+ bool _modified = false;
+ Vrm10MeshUtility _meshUti;
+
+ Splitter _splitter;
+ ReorderableList _groupList;
+ ReorderableList _rendererList;
+ public List _renderers = new List();
+ int _selected = -1;
+ int Selected
+ {
+ set
+ {
+ if (_selected == value)
+ {
+ return;
+ }
+ if (value < 0 || value >= _meshUti.MeshIntegrationGroups.Count)
+ {
+ return;
+ }
+ _selected = value;
+ _renderers.Clear();
+ _renderers.AddRange(_meshUti.MeshIntegrationGroups[_selected].Renderers);
+ }
+ }
+
+ public MeshIntegrationTab(EditorWindow editor, Vrm10MeshUtility meshUtility)
+ {
+ _meshUti = meshUtility;
+ _splitter = new VerticalSplitter(editor, 50, 50);
+
+ _groupList = new ReorderableList(_meshUti.MeshIntegrationGroups, typeof(Vrm10MeshUtility.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];
+ EditorGUI.TextField(rect, group.Name);
+ };
+ _groupList.onSelectCallback = rl =>
+ {
+ Selected = (rl.selectedIndices.Count > 0) ? rl.selectedIndices[0] : -1;
+ };
+
+ _rendererList = new ReorderableList(_renderers, typeof(Renderer));
+ _rendererList.drawHeaderCallback = (Rect rect) =>
+ {
+ GUI.Label(rect, "Renderer");
+ };
+ _rendererList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
+ {
+ var r = _renderers[index];
+ EditorGUI.ObjectField(rect, r, typeof(Renderer), true);
+ };
+ }
+
+ public void UpdateMeshIntegrationList(GameObject root)
+ {
+ _selected = -1;
+ _meshUti.MeshIntegrationGroups.Clear();
+ _meshUti.IntegrateFirstPerson(root);
+ Selected = 0;
+ }
+
+ private void ShowGroup(Rect rect)
+ {
+ _groupList.DoList(rect);
+ }
+
+ private void ShowSelected(Rect rect)
+ {
+ _rendererList.DoList(rect);
+ }
+
+ public bool OnGui(Rect rect)
+ {
+ _modified = false;
+ _splitter.OnGUI(
+ rect,
+ ShowGroup,
+ ShowSelected);
+ return _modified;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs.meta b/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs.meta
similarity index 83%
rename from Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs.meta
rename to Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs.meta
index 1127183e7..91a743257 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs.meta
+++ b/Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 86cbd6470ebef2f4c976410efaf45638
+guid: 07d031dea01a55c43b1ec68cd10bf461
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/VRM10/Editor/MeshUtility/Splitter.cs b/Assets/VRM10/Editor/MeshUtility/Splitter.cs
new file mode 100644
index 000000000..743bae80c
--- /dev/null
+++ b/Assets/VRM10/Editor/MeshUtility/Splitter.cs
@@ -0,0 +1,183 @@
+using UnityEngine;
+using UnityEditor;
+using System;
+
+namespace UniVRM10
+{
+ [Serializable]
+ public abstract class Splitter
+ {
+ internal enum SplitMode
+ {
+ Horizontal,
+ Vertical,
+ }
+
+ protected float _value;
+
+ private EditorWindow _window;
+ private SplitMode _lockMode;
+ private float _lockValues;
+ private MouseCursor _mouseCursor;
+ protected float _barSize;
+
+ private bool _isResize;
+ private Rect _resizeRect;
+ private float _ratio = 0.5f;
+
+ private static readonly Color SPLITTER_COLOR = new Color(0, 0, 0, 1.0f);
+ private static readonly Color SPLITTER_COLOR_FREE = new Color(0.5f, 0.5f, 0.5f, 1.0f);
+
+ internal Splitter(EditorWindow window, float defaultValue, SplitMode lockMode, float minValue, float barSize = 16f)
+ {
+ _window = window;
+ _value = defaultValue;
+ _lockMode = lockMode;
+ _lockValues = minValue;
+ _mouseCursor = lockMode == SplitMode.Vertical ? MouseCursor.ResizeHorizontal : MouseCursor.ResizeVertical;
+ _barSize = barSize;
+ }
+
+ protected abstract RectOffset RectOffset();
+
+ protected abstract Rect MainRect(Rect rect);
+
+ protected abstract Rect SubRect(Rect rect);
+
+ protected abstract Rect BarRect(Rect rect);
+
+ public void OnGUI(Rect rect, Action mainDelegate, Action subDelegate)
+ {
+ var current = Event.current;
+
+ mainDelegate(MainRect(rect));
+ subDelegate(SubRect(rect));
+
+ _resizeRect = BarRect(rect);
+ EditorGUIUtility.AddCursorRect(_resizeRect, _mouseCursor);
+
+ var clampMax = _lockMode == SplitMode.Vertical ? rect.width - _lockValues : rect.height - _lockValues;
+ var targetSplitterValue = _lockMode == SplitMode.Vertical ? rect.width : rect.height;
+
+ _ratio = _lockMode == SplitMode.Vertical ? _value / rect.width : _value / rect.height;
+
+ if (current.type == EventType.MouseDown && _resizeRect.Contains(current.mousePosition))
+ _isResize = true;
+ if (current.type == EventType.MouseUp)
+ _isResize = false;
+
+ if (_isResize && current.type == EventType.MouseDrag)
+ {
+ var targetValue = _lockMode == SplitMode.Vertical
+ ? current.mousePosition.x
+ : current.mousePosition.y;
+ var diffValue = _lockMode == SplitMode.Vertical ? rect.width : rect.height;
+ _ratio = targetValue / diffValue;
+ }
+ else if (current.type != EventType.Layout && Event.current.type != EventType.Used)
+ {
+ _ratio = targetSplitterValue * _ratio / targetSplitterValue;
+ }
+
+ _value = Mathf.Clamp(targetSplitterValue * _ratio, _lockValues, clampMax);
+
+ EditorGUI.DrawRect(RectOffset().Remove(_resizeRect), SPLITTER_COLOR_FREE);
+
+ if (_isResize)
+ _window.Repaint();
+ }
+ }
+
+ [Serializable]
+ public class HorizontalSplitter : Splitter
+ {
+ private static readonly RectOffset RECT_OFFSET = new RectOffset(0, 0, 7, 8);
+
+ public HorizontalSplitter(EditorWindow window, float defaultValue, float minValue, float barSize = 16)
+ : base(window, defaultValue, SplitMode.Horizontal, minValue, barSize)
+ {
+ }
+
+ protected override RectOffset RectOffset()
+ {
+ return RECT_OFFSET;
+ }
+
+ protected override Rect MainRect(Rect rect)
+ {
+ return new Rect(rect)
+ {
+ x = 0,
+ y = 0,
+ height = _value,
+ };
+ }
+
+ protected override Rect SubRect(Rect rect)
+ {
+ return new Rect(rect)
+ {
+ x = 0,
+ y = _value,
+ height = rect.height - _value,
+ };
+ }
+
+ protected override Rect BarRect(Rect rect)
+ {
+ return new Rect(rect)
+ {
+ x = 0,
+ y = _value - _barSize / 2,
+ height = _barSize,
+ };
+ }
+
+ }
+
+ [Serializable]
+ public class VerticalSplitter : Splitter
+ {
+ private static readonly RectOffset RECT_OFFSET = new RectOffset(0, 0, 7, 8);
+
+ public VerticalSplitter(EditorWindow window, float defaultValue, float minValue, float barSize = 4)
+ : base(window, defaultValue, SplitMode.Vertical, minValue, barSize)
+ {
+ }
+
+ protected override RectOffset RectOffset()
+ {
+ return RECT_OFFSET;
+ }
+
+ protected override Rect MainRect(Rect rect)
+ {
+ return new Rect(rect)
+ {
+ x = rect.x + 0,
+ y = rect.y + 0,
+ width = _value,
+ };
+ }
+
+ protected override Rect SubRect(Rect rect)
+ {
+ return new Rect(rect)
+ {
+ x = rect.x + _value,
+ y = rect.y + 0,
+ width = rect.width - _value,
+ };
+ }
+
+ protected override Rect BarRect(Rect rect)
+ {
+ return new Rect(rect)
+ {
+ x = rect.x + _value - _barSize / 2,
+ y = rect.y + 0,
+ width = _barSize,
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta b/Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta
new file mode 100644
index 000000000..57ca79cfc
--- /dev/null
+++ b/Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 385274edf555ed84a9f3706ca2a99023
+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
new file mode 100644
index 000000000..2b485805b
--- /dev/null
+++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs
@@ -0,0 +1,170 @@
+using UnityEngine;
+using UnityEditor;
+using UniGLTF.M17N;
+using System.Collections.Generic;
+using UniGLTF.MeshUtility;
+using UniGLTF;
+using System.Linq;
+
+namespace UniVRM10
+{
+ public class Vrm10MeshUtilityDialog : EditorWindow
+ {
+ const string TITLE = "Vrm10 Mesh Utility Window";
+ enum Tabs
+ {
+ MeshFreeze,
+ MeshIntegration,
+ MeshSplitter,
+ }
+ Tabs _tab;
+
+ public static void OpenWindow()
+ {
+ var window =
+ (Vrm10MeshUtilityDialog)EditorWindow.GetWindow(typeof(Vrm10MeshUtilityDialog));
+ window.titleContent = new GUIContent(TITLE);
+ window.Show();
+ }
+
+ Vrm10MeshUtility _meshUtility = new Vrm10MeshUtility();
+
+ List _validations = new List();
+ private void Validate()
+ {
+ _validations.Clear();
+ if (_exportTarget == null)
+ {
+ _validations.Add(Validation.Error("set vrm1"));
+ return;
+ }
+ 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()
+ {
+ _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)
+ {
+ _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.MeshFreeze:
+ {
+ if (MeshBakeGui())
+ {
+ modified = true;
+ }
+ break;
+ }
+
+ case Tabs.MeshIntegration:
+ {
+ if (MeshIntegrateGui())
+ {
+ modified = true;
+ }
+ break;
+ }
+
+ case Tabs.MeshSplitter:
+ {
+ if (MeshSplitGui())
+ {
+ modified = true;
+ }
+ break;
+ }
+ }
+ EditorGUILayout.EndScrollView();
+
+ if (modified)
+ {
+ Validate();
+ }
+
+ GUI.enabled = IsValid;
+ var pressed = GUILayout.Button("Process", GUILayout.MinWidth(100));
+ GUI.enabled = true;
+ if (pressed)
+ {
+ _meshUtility.Process(exportTarget);
+ // Show Result ?
+ // Close();
+ // GUIUtility.ExitGUI();
+ }
+ }
+
+ bool ToggleIsModified(string label, ref bool value)
+ {
+ var newValue = EditorGUILayout.Toggle(label, value);
+ if (newValue == value)
+ {
+ return false;
+ }
+ value = newValue;
+ return true;
+ }
+
+ bool MeshBakeGui()
+ {
+ 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;
+ }
+
+ bool MeshIntegrateGui()
+ {
+ var firstPerson = ToggleIsModified("FirstPerson == AUTO の生成", ref _meshUtility.GenerateMeshForFirstPersonAuto);
+ 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 || mod;
+ }
+
+ bool MeshSplitGui()
+ {
+ return ToggleIsModified("Separate by BlendShape", ref _meshUtility.SplitByBlendShape);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs.meta b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs.meta
new file mode 100644
index 000000000..829b51745
--- /dev/null
+++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f3af8a1b265d8a4da7ad8c1c29d2585
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM10/Editor/Vrm10TopMenu.cs b/Assets/VRM10/Editor/Vrm10TopMenu.cs
index 914101aa3..81efdc956 100644
--- a/Assets/VRM10/Editor/Vrm10TopMenu.cs
+++ b/Assets/VRM10/Editor/Vrm10TopMenu.cs
@@ -1,3 +1,4 @@
+using UniGLTF.MeshUtility;
using UnityEditor;
namespace UniVRM10
@@ -11,6 +12,9 @@ namespace UniVRM10
[MenuItem(UserMenuPrefix + "/Export VRM-1.0", priority = 1)]
private static void OpenExportDialog() => VRM10ExportDialog.Open();
+ [MenuItem(UserMenuPrefix + "/MeshUtility", priority = 2)]
+ private static void OpenMeshUtility() => Vrm10MeshUtilityDialog.OpenWindow();
+
[MenuItem(ExperimentalMenuPrefix + "/Convert BVH to VRM-Animation", priority = 100)]
private static void ConvertVrmAnimation() => VrmAnimationMenu.BvhToVrmAnimationMenu();
diff --git a/Assets/VRM10/Runtime/MeshUtility.meta b/Assets/VRM10/Runtime/MeshUtility.meta
new file mode 100644
index 000000000..70b8b2c67
--- /dev/null
+++ b/Assets/VRM10/Runtime/MeshUtility.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4eeaac509504ff94e9743df73d3dae42
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs
new file mode 100644
index 000000000..da612bee9
--- /dev/null
+++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs
@@ -0,0 +1,141 @@
+using System.Collections.Generic;
+using System.Linq;
+using UniGLTF.MeshUtility;
+using UniHumanoid;
+using UnityEngine;
+
+namespace UniVRM10
+{
+ ///
+ /// - Freeze
+ /// - Integration
+ /// - Split
+ ///
+ /// - Implement runtime logic => Process a hierarchy in scene. Do not process prefab.
+ /// - Implement undo
+ ///
+ ///
+ public class Vrm10MeshUtility
+ {
+ ///
+ /// 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 class MeshIntegrationGroup
+ {
+ ///
+ /// FirstPerson flag
+ /// TODO: enum
+ ///
+ public string Name;
+ public List Renderers = new List();
+ }
+
+ 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)
+ {
+ if (root == null)
+ {
+ return;
+ }
+ MeshIntegrationGroups.Add(new MeshIntegrationGroup
+ {
+ Name = "ALL",
+ Renderers = root.GetComponentsInChildren().ToList(),
+ });
+ }
+
+ MeshIntegrationGroup GetOrCreateGroup(string name)
+ {
+ foreach (var g in MeshIntegrationGroups)
+ {
+ if (g.Name == name)
+ {
+ return g;
+ }
+ }
+ MeshIntegrationGroups.Add(new MeshIntegrationGroup
+ {
+ Name = name,
+ });
+ return MeshIntegrationGroups.Last();
+ }
+
+ public void IntegrateFirstPerson(GameObject root)
+ {
+ if (root == null)
+ {
+ return;
+ }
+ var vrm1 = root.GetComponent();
+ if (vrm1 == null)
+ {
+ return;
+ }
+ var vrmObject = vrm1.Vrm;
+ if (vrmObject == null)
+ {
+ return;
+ }
+ var fp = vrmObject.FirstPerson;
+ if (fp == null)
+ {
+ return;
+ }
+ foreach (var a in fp.Renderers)
+ {
+ var g = GetOrCreateGroup(a.FirstPersonFlag.ToString());
+ g.Renderers.Add(a.GetRenderer(root.transform));
+ }
+ }
+
+ public void Process(GameObject go)
+ {
+ // TODO: UNDO
+
+ // 正規化されたヒエラルキーを作る
+ var (normalized, boneMap) = BoneNormalizer.CreateNormalizedHierarchy(go,
+ removeScaling: FreezeScaling,
+ removeRotation: FreezeRotation);
+
+ // TODO: update: spring
+ // TODO: update: constraint
+ // TODO: update: firstPoint offset
+
+ AvatarDescription.AddAnimator(go, normalized, boneMap);
+
+ // TODO: write back normalized transform to boneMap
+ }
+ }
+}
diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs.meta b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs.meta
new file mode 100644
index 000000000..60452fa5d
--- /dev/null
+++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0ef8f213cebb99f418c39baa9419296e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: