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: