diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs deleted file mode 100644 index 4e0c71201..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using UnityEditor; -using UnityEngine; - - -namespace UniGLTF.MeshUtility -{ - [CustomPropertyDrawer(typeof(BoneMeshEraser.EraseBone))] - public class EraseBoneDrawer : PropertyDrawer - { - public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) - { - //EditorGUI.BeginProperty(position, label, property); - - var leftWidth = 0.6f; - var rightWidth = 1.0f - leftWidth; - - var leftSide = new Rect(position.x, position.y, position.width * leftWidth, position.height); - var rightSide = new Rect(position.width * leftWidth, position.y, position.width * rightWidth, position.height); - { - EditorGUI.PropertyField(leftSide, property.FindPropertyRelative("Bone"), new GUIContent("", "")); - EditorGUI.PropertyField(rightSide, property.FindPropertyRelative("Erase")); - } - - //EditorGUI.EndProperty(); - } - - public override float GetPropertyHeight(SerializedProperty property, GUIContent label) - { - var height = base.GetPropertyHeight(property, label); - return height; - } - } - - public class BoneMeshEraserWizard : ScriptableWizard - { - public const string BONE_MESH_ERASER_NAME = "BoneMeshEraser"; - const string ASSET_SUFFIX = ".asset"; - - [SerializeField] - SkinnedMeshRenderer m_skinnedMesh; - - [SerializeField] - Animator m_animator; - - [SerializeField] - Transform EraseRoot; - - [SerializeField] - BoneMeshEraser.EraseBone[] m_eraseBones; - - private void OnEnable() - { - var root = Selection.activeGameObject; - if (root != null) - { - m_animator = root.GetComponent(); - m_skinnedMesh = root.GetComponent(); - OnValidate(); - } - } - - void OnValidate() - { - //Debug.Log("OnValidate"); - if (m_skinnedMesh == null) - { - m_eraseBones = new BoneMeshEraser.EraseBone[] { }; - return; - } - - if (EraseRoot == null) - { - if (m_animator != null) - { - EraseRoot = m_animator.GetBoneTransform(HumanBodyBones.Head); - //Debug.LogFormat("head: {0}", EraseRoot); - } - } - - m_eraseBones = m_skinnedMesh.bones.Select(x => - { - var eb = new BoneMeshEraser.EraseBone - { - Bone = x, - }; - - if (EraseRoot != null) - { - // 首の子孫を消去 - if (eb.Bone.Ancestor().Any(y => y == EraseRoot)) - { - //Debug.LogFormat("erase {0}", x); - eb.Erase = true; - } - } - - return eb; - }) - .ToArray(); - } - - void OnWizardUpdate() - { - helpString = "select target skinnedMesh and animator"; - } - - SkinnedMeshRenderer _Erase(GameObject go) - { - if (go == null) - { - Debug.LogWarning("select root object in hierarchy"); - return null; - } - if (m_skinnedMesh == null) - { - Debug.LogWarning("no skinnedmesh"); - return null; - } - - var bones = m_skinnedMesh.bones; - var eraseBones = m_eraseBones - .Where(x => x.Erase) - .Select(x => Array.IndexOf(bones, x.Bone)) - .ToArray(); - - var meshNode = new GameObject(BONE_MESH_ERASER_NAME); - meshNode.transform.SetParent(go.transform, false); - - var erased = meshNode.AddComponent(); - erased.sharedMesh = BoneMeshEraser.CreateErasedMesh(m_skinnedMesh.sharedMesh, eraseBones); - erased.sharedMaterials = m_skinnedMesh.sharedMaterials; - erased.bones = m_skinnedMesh.bones; - - return erased; - } - - public static UnityEngine.Object GetPrefab(GameObject instance) - { -#if UNITY_2018_2_OR_NEWER - return PrefabUtility.GetCorrespondingObjectFromSource(instance); -#else - return PrefabUtility.GetPrefabParent(go); -#endif - } - - void Erase() - { - var go = Selection.activeGameObject; - var renderer = _Erase(go); - if (renderer == null) - { - return; - } - - // save mesh to Assets - var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX); - var prefab = GetPrefab(go); - if (prefab != null) - { - var prefabPath = AssetDatabase.GetAssetPath(prefab); - assetPath = string.Format("{0}/{1}{2}", - Path.GetDirectoryName(prefabPath), - Path.GetFileNameWithoutExtension(prefabPath), - ASSET_SUFFIX - ); - } - - Debug.LogFormat("CreateAsset: {0}", assetPath); - AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath); - } - - void OnWizardCreate() - { - //Debug.Log("OnWizardCreate"); - Erase(); - - // close - } - - void OnWizardOtherButton() - { - //Debug.Log("OnWizardOtherButton"); - Erase(); - } - } -} diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs.meta deleted file mode 100644 index ef9f39767..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs.meta +++ /dev/null @@ -1,13 +0,0 @@ -fileFormatVersion: 2 -guid: 881b00db73f639c48a3f043a775fa61a -timeCreated: 1518503829 -licenseType: Free -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs b/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs new file mode 100644 index 000000000..04acaa8d2 --- /dev/null +++ b/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs @@ -0,0 +1,57 @@ +using UnityEditor; +using UnityEngine; + +namespace UniGLTF.MeshUtility +{ + public enum GameObjectType + { + Null, + SceneInstance, + PrefabInstance, + AssetPrefab, + } + + public static class GameObjectTypeUtility + { + public static Object GetPrefab(this GameObject instance) + { + if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(instance))) + { + return instance; + } + +#if UNITY_2018_2_OR_NEWER + return PrefabUtility.GetCorrespondingObjectFromSource(instance); +#else + return PrefabUtility.GetPrefabParent(go); +#endif + } + + public static GameObjectType GetGameObjectType(this GameObject go) + { + if (go == null) + { + return GameObjectType.Null; + } + + if (!go.scene.IsValid()) + { + if (PrefabUtility.IsPartOfAnyPrefab(go)) + { + return GameObjectType.AssetPrefab; + } + else + { + throw new System.NotSupportedException("Unknown prefab status. The target does not exist in a valid scene and is not a prefab."); + } + } + + if (PrefabUtility.IsPartOfAnyPrefab(go)) + { + return GameObjectType.PrefabInstance; + } + + return GameObjectType.SceneInstance; + } + } +} diff --git a/Assets/UniGLTF/Editor/MeshUtility/TextureSaver.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs.meta similarity index 83% rename from Assets/UniGLTF/Editor/MeshUtility/TextureSaver.cs.meta rename to Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs.meta index 733e8bdc1..721cf19ee 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TextureSaver.cs.meta +++ b/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 01c8541cb3fd27f4882e3d32c37a45aa +guid: 95cda911ba335b449901767f9bee8ca8 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs index a5a4c97e7..b1fc04feb 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs @@ -1,48 +1,34 @@ -using System.Linq; -using System.Reflection; using UnityEngine; using UnityEditor; -using UniGLTF; using UniGLTF.M17N; +using System.Collections.Generic; namespace UniGLTF.MeshUtility { public class MeshProcessDialog : EditorWindow { - private Tabs _tab; + const string TITLE = "Mesh Processing Window"; + MeshProcessDialogTabs _tab; private GameObject _exportTarget; + [SerializeField] + public bool _separateByBlendShape = true; + + [SerializeField] + public SkinnedMeshRenderer _skinnedMeshRenderer = null; + + [SerializeField] + public List _eraseBones; + private MeshProcessDialogEditor _boneMeshEraserEditor; - - private SkinnedMeshRenderer _pSkinnedMesh; - private Animator _pAnimator; - private Transform _pEraseRoot; private Vector2 _scrollPos = new Vector2(0, 0); - [SerializeField] - private SkinnedMeshRenderer _cSkinnedMesh = null; - - [SerializeField] - private bool _separateByBlendShape = true; - - private Animator _cAnimator = null; - private Transform _cEraseRoot = null; - - [SerializeField] - private BoneMeshEraser.EraseBone[] _eraseBones; - - private MethodInfo _processFunction; - - GUIStyle _tabButtonStyle => "LargeButton"; - GUI.ToolbarButtonSize _tabButtonSize => GUI.ToolbarButtonSize.Fixed; - - public static void OpenWindow() { var window = (MeshProcessDialog)EditorWindow.GetWindow(typeof(MeshProcessDialog)); - window.titleContent = new GUIContent("Mesh Processing Window"); + window.titleContent = new GUIContent(TITLE); window.Show(); } @@ -54,66 +40,66 @@ namespace UniGLTF.MeshUtility } } - static bool IsGameObjectSelected() - { - return Selection.activeObject != null && Selection.activeObject is GameObject; - } - private void OnGUI() { _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); - EditorGUIUtility.labelWidth = 300; - // lang + EditorGUIUtility.labelWidth = 200; LanguageGetter.OnGuiSelectLang(); + _exportTarget = (GameObject)EditorGUILayout.ObjectField(MeshProcessingMessages.TARGET_OBJECT.Msg(), _exportTarget, typeof(GameObject), true); + _tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed); - { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField(MeshProcessingMessages.TARGET_OBJECT.Msg(), GUILayout.MaxWidth(146.0f)); - _exportTarget = (GameObject)EditorGUILayout.ObjectField(_exportTarget, typeof(GameObject), true); - EditorGUILayout.EndHorizontal(); - if (_exportTarget == null && IsGameObjectSelected()) - { - _exportTarget = Selection.activeObject as GameObject; - } - } - - // tab - _tab = TabBar.OnGUI(_tab, _tabButtonStyle, _tabButtonSize); var processed = false; switch (_tab) { - case Tabs.MeshSeparator: - EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_SEPARATOR.Msg(), MessageType.Info); - processed = TabMeshSeparator.OnGUI(_exportTarget); - break; + case MeshProcessDialogTabs.MeshSeparator: + { + EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_SEPARATOR.Msg(), MessageType.Info); + if (TabMeshSeparator.TryExecutable(_exportTarget, out string msg)) + { + processed = TabMeshSeparator.OnGUI(_exportTarget); + } + else + { + EditorGUILayout.HelpBox(msg, MessageType.Error); + } + break; + } - case Tabs.MeshIntegrator: - EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info); - _boneMeshEraserEditor.Tabs = _tab; - if (_boneMeshEraserEditor) + case MeshProcessDialogTabs.MeshIntegrator: { - _boneMeshEraserEditor.OnInspectorGUI(); + EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info); + _separateByBlendShape = EditorGUILayout.Toggle(MeshProcessingMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg(), _separateByBlendShape); + if (TabMeshIntegrator.TryExecutable(_exportTarget, out string msg)) + { + if (GUILayout.Button("Process", GUILayout.MinWidth(100))) + { + processed = TabMeshIntegrator.Execute(_exportTarget, _separateByBlendShape); + } + } + else + { + EditorGUILayout.HelpBox(msg, MessageType.Error); + } + break; } - processed = TabMeshIntegrator.OnGUI(_exportTarget, _separateByBlendShape); - break; - case Tabs.BoneMeshEraser: - EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info); - _boneMeshEraserEditor.Tabs = _tab; - if (_boneMeshEraserEditor) + case MeshProcessDialogTabs.BoneMeshEraser: { - _boneMeshEraserEditor.OnInspectorGUI(); + EditorGUILayout.HelpBox(MeshProcessingMessages.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; } - // any better way we can detect component change? - if (_cSkinnedMesh != _pSkinnedMesh || _cAnimator != _pAnimator || _cEraseRoot != _pEraseRoot) - { - BoneMeshEraserValidate(); - } - _pSkinnedMesh = _cSkinnedMesh; - _pAnimator = _cAnimator; - _pEraseRoot = _cEraseRoot; - processed = TabBoneMeshRemover.OnGUI(_exportTarget, _cSkinnedMesh, _eraseBones); - break; } EditorGUILayout.EndScrollView(); @@ -123,45 +109,5 @@ namespace UniGLTF.MeshUtility GUIUtility.ExitGUI(); } } - - - private void BoneMeshEraserValidate() - { - if (_cSkinnedMesh == null) - { - _eraseBones = new BoneMeshEraser.EraseBone[] { }; - return; - } - - if (_cEraseRoot == null) - { - if (_cAnimator != null) - { - _cEraseRoot = _cAnimator.GetBoneTransform(HumanBodyBones.Head); - //Debug.LogFormat("head: {0}", EraseRoot); - } - } - - _eraseBones = _cSkinnedMesh.bones.Select(x => - { - var eb = new BoneMeshEraser.EraseBone - { - Bone = x, - }; - - if (_cEraseRoot != null) - { - // 首の子孫を消去 - if (eb.Bone.Ancestor().Any(y => y == _cEraseRoot)) - { - //Debug.LogFormat("erase {0}", x); - eb.Erase = true; - } - } - - return eb; - }) - .ToArray(); - } } } \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs index db7929a0a..e14659de0 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs @@ -4,32 +4,39 @@ using UnityEngine; namespace UniGLTF.MeshUtility { + /// + /// BoneMeshRemover 向けのエディタ。 + /// + /// SerializedProperty 経由で ユーザー定義 struct のフィールド + /// public List _eraseBones; + /// を EditorGUILayout.PropertyField するための細工である。 + /// + /// SerializedObject は UnityEngine.Object から作成するので、 + /// UnityEngine.Object を継承したクラスのフィールドに ユーザー定義 struct を配置する。 + /// 持ち主の SerializedObject を経由して EditorGUILayout.PropertyField してる。 + /// [CustomEditor(typeof(MeshProcessDialog), true)] class MeshProcessDialogEditor : Editor { - public Tabs Tabs; + MeshProcessDialog _targetDialog; + SerializedProperty _skinnedMesh; + SerializedProperty _eraseBones; + + void OnEnable() + { + _targetDialog = target as MeshProcessDialog; + if (_targetDialog) + { + _skinnedMesh = serializedObject.FindProperty(nameof(MeshProcessDialog._skinnedMeshRenderer)); + _eraseBones = serializedObject.FindProperty(nameof(MeshProcessDialog._eraseBones)); + } + } public override void OnInspectorGUI() { serializedObject.Update(); - switch (Tabs) - { - case Tabs.MeshIntegrator: - { - var skinnedMesh = serializedObject.FindProperty("_separateByBlendShape"); - EditorGUILayout.PropertyField(skinnedMesh, new GUIContent(MeshProcessingMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg())); - break; - } - - case Tabs.BoneMeshEraser: - { - var skinnedMesh = serializedObject.FindProperty("_cSkinnedMesh"); - EditorGUILayout.PropertyField(skinnedMesh, new GUIContent("Skinned Mesh"), true); - var list = serializedObject.FindProperty("_eraseBones"); - EditorGUILayout.PropertyField(list, new GUIContent("Erase Bones"), true); - break; - } - } + EditorGUILayout.PropertyField(_skinnedMesh); + EditorGUILayout.PropertyField(_eraseBones); serializedObject.ApplyModifiedProperties(); } } diff --git a/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs similarity index 75% rename from Assets/UniGLTF/Editor/MeshUtility/Tabs.cs rename to Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs index dbc064734..87ad28880 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs @@ -1,6 +1,6 @@ namespace UniGLTF.MeshUtility { - enum Tabs + public enum MeshProcessDialogTabs { MeshSeparator, MeshIntegrator, diff --git a/Assets/UniGLTF/Runtime/MeshUtility/StaticMeshIntegrator.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs.meta similarity index 83% rename from Assets/UniGLTF/Runtime/MeshUtility/StaticMeshIntegrator.cs.meta rename to Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs.meta index b9b730e00..76e129bdc 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/StaticMeshIntegrator.cs.meta +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogTabs.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 5d049a3daed1255499aaec8b6d1a2afe +guid: 829a678062065f04582189619babae79 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs index 3b63cc538..965e70846 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs @@ -80,8 +80,8 @@ namespace UniGLTF.MeshUtility [LangMsg(Languages.en, "Skinned/Static mesh is not contained")] NO_MESH, - [LangMsg(Languages.ja, "ターゲットオブジェクトはVRMモデルです。`VRM0-> MeshIntegrator`を使ってください")] - [LangMsg(Languages.en, "Target object is VRM model, use `VRM0 -> MeshIntegrator` instead")] + [LangMsg(Languages.ja, "BlendShapeClipが不整合を起こすので、`VRM0-> MeshIntegrator`を使ってください")] + [LangMsg(Languages.en, "Because BlendShapeClip causes inconsistency , use `VRM0 -> MeshIntegrator` instead")] VRM_DETECTED, } } \ No newline at end of file diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs index 812fd8a52..9e7566e78 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using UniGLTF.M17N; @@ -9,9 +10,28 @@ namespace UniGLTF.MeshUtility { public static class TabBoneMeshRemover { + const string BONE_MESH_ERASER_NAME = "BoneMeshEraser"; const string ASSET_SUFFIX = ".mesh.asset"; - public static bool OnGUI(GameObject root, SkinnedMeshRenderer smr, BoneMeshEraser.EraseBone[] eraseBones) + public static bool TryExecutable(GameObject root, SkinnedMeshRenderer smr, out string msg) + { + if (root == null) + { + msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(); + return false; + } + + if (smr == null) + { + msg = MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(); + return false; + } + + msg = ""; + return true; + } + + public static bool OnGUI(GameObject root, SkinnedMeshRenderer smr, List eraseBones) { var _isInvokeSuccess = false; GUILayout.BeginVertical(); @@ -28,23 +48,11 @@ namespace UniGLTF.MeshUtility return _isInvokeSuccess; } - private static bool Execute(GameObject root, SkinnedMeshRenderer smr, BoneMeshEraser.EraseBone[] eraseBones) + private static bool Execute(GameObject root, SkinnedMeshRenderer smr, List eraseBones) { - if (root == null) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); - return false; - } - - if (smr == null) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(), "ok"); - return false; - } - var bones = smr.bones; - var meshNode = new GameObject(BoneMeshEraserWizard.BONE_MESH_ERASER_NAME); + var meshNode = new GameObject(BONE_MESH_ERASER_NAME); meshNode.transform.SetParent(root.transform, false); var erased = meshNode.AddComponent(); @@ -74,7 +82,7 @@ namespace UniGLTF.MeshUtility // destroy BoneMeshEraser in the source foreach (var skinnedMesh in root.GetComponentsInChildren()) { - if (skinnedMesh.gameObject.name == BoneMeshEraserWizard.BONE_MESH_ERASER_NAME) + if (skinnedMesh.gameObject.name == BONE_MESH_ERASER_NAME) { GameObject.DestroyImmediate(skinnedMesh.gameObject); } diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs index ed366f531..7b8d8c8cf 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.Collections.Generic; using UniGLTF.M17N; using UnityEditor; using UnityEngine; @@ -7,23 +7,29 @@ namespace UniGLTF.MeshUtility { public static class TabMeshIntegrator { - const string ASSET_SUFFIX = ".mesh.asset"; - - public static bool OnGUI(GameObject root, bool onlyBlendShapeRenderers) + public static bool TryExecutable(GameObject root, out string msg) { - var _isInvokeSuccess = false; - GUILayout.BeginVertical(); + // check + if (root == null) { - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("Process", GUILayout.MinWidth(100))) - { - _isInvokeSuccess = TabMeshIntegrator.Execute(root, onlyBlendShapeRenderers); - } - GUILayout.EndHorizontal(); + msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(); + return false; } - GUILayout.EndVertical(); - return _isInvokeSuccess; + + if (HasVrm(root)) + { + msg = MeshProcessingMessages.VRM_DETECTED.Msg(); + return false; + } + + if (root.GetComponentsInChildren().Length == 0 && root.GetComponentsInChildren().Length == 0) + { + msg = MeshProcessingMessages.NO_MESH.Msg(); + return false; + } + + msg = ""; + return true; } const string VRM_META = "VRMMeta"; @@ -42,107 +48,57 @@ namespace UniGLTF.MeshUtility return false; } - static bool Execute(GameObject root, bool onlyBlendShapeRenderers) + /// GameObject instance in scene or prefab + public static bool Execute(GameObject src, bool onlyBlendShapeRenderers) { - if (root == null) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); - return false; - } + var results = new List(); - if (HasVrm(root)) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.VRM_DETECTED.Msg(), "ok"); - return false; - } - - if (root.GetComponentsInChildren().Length == 0 && root.GetComponentsInChildren().Length == 0) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_MESH.Msg(), "ok"); - return false; - } + // instance or prefab => copy + var copy = GameObject.Instantiate(src); + copy.name = copy.name + "_mesh_integration"; + // integrate if (onlyBlendShapeRenderers) { - MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape); - MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape); + results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape)); + results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape)); } else { - MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All); + results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.All)); } - CopyAndSaveAssetEtc(root); + // replace + MeshIntegratorUtility.ReplaceMeshWithResults(copy, results); + + // write mesh asset. + foreach (var result in results) + { + var mesh = result.IntegratedRenderer.sharedMesh; + var assetPath = MeshIntegratorUtility.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; } - - static void CopyAndSaveAssetEtc(GameObject root) - { - // copy hierarchy - var outputObject = GameObject.Instantiate(root); - outputObject.name = outputObject.name + "_mesh_integration"; - var skinnedMeshes = outputObject.GetComponentsInChildren(); - - // destroy integrated meshes in the source - foreach (var skinnedMesh in root.GetComponentsInChildren()) - { - if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME || - skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME) - { - GameObject.DestroyImmediate(skinnedMesh.gameObject); - } - } - foreach (var skinnedMesh in skinnedMeshes) - { - // destroy original meshes in the copied GameObject - if (!(skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME || - skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME)) - { - GameObject.DestroyImmediate(skinnedMesh); - } - // check if the integrated mesh is empty - else if (skinnedMesh.sharedMesh.subMeshCount == 0) - { - GameObject.DestroyImmediate(skinnedMesh.gameObject); - } - // save mesh data - else if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME || - skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME) - { - SaveMeshData(skinnedMesh.sharedMesh); - } - } - - var normalMeshes = outputObject.GetComponentsInChildren(); - foreach (var normalMesh in normalMeshes) - { - if (normalMesh.sharedMesh.name != MeshIntegratorUtility.INTEGRATED_MESH_NAME) - { - if (normalMesh.gameObject.GetComponent()) - { - GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent()); - } - GameObject.DestroyImmediate(normalMesh); - } - } - } - - static void SaveMeshData(Mesh mesh) - { - var assetPath = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX); - Debug.Log(assetPath); - if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh)))) - { - var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/"); - assetPath = string.Format("{0}/{1}{2}", directory, Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX); - } - else - { - assetPath = string.Format("Assets/{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX); - } - Debug.LogFormat("CreateAsset: {0}", assetPath); - AssetDatabase.CreateAsset(mesh, assetPath); - } } } diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs index 6e8caf398..3dab965b2 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs @@ -12,15 +12,32 @@ namespace UniGLTF.MeshUtility /// public static class TabMeshSeparator { - private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero; - const string ASSET_SUFFIX = ".mesh.asset"; + private const string ASSET_SUFFIX = ".mesh.asset"; - enum BlendShapeLogic + private enum BlendShapeLogic { WithBlendShape, WithoutBlendShape, } + public static bool TryExecutable(GameObject root, out string msg) + { + if (root == null) + { + msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(); + return false; + } + + if (root.GetComponentsInChildren().Length == 0) + { + msg = MeshProcessingMessages.NO_SKINNED_MESH.Msg(); + return false; + } + + msg = ""; + return true; + } + public static bool OnGUI(GameObject root) { var _isInvokeSuccess = false; @@ -38,36 +55,25 @@ namespace UniGLTF.MeshUtility return _isInvokeSuccess; } - static bool Execute(GameObject root) + private static bool Execute(GameObject root) { - if (root == null) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok"); - return false; - } - - if (root.GetComponentsInChildren().Length == 0) - { - EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_SKINNED_MESH.Msg(), "ok"); - return false; - } - // copy var outputObject = GameObject.Instantiate(root); outputObject.name = outputObject.name + "_mesh_separation"; // 改変と asset の作成 var list = SeparationProcessing(outputObject); + + // asset の永続化 foreach (var (src, with, without) in list) { - // asset の永続化 SaveMesh(src, with, BlendShapeLogic.WithBlendShape); SaveMesh(src, without, BlendShapeLogic.WithoutBlendShape); } return true; } - static void SaveMesh(Mesh mesh, Mesh newMesh, BlendShapeLogic blendShapeLabel) + private static void SaveMesh(Mesh mesh, Mesh newMesh, BlendShapeLogic blendShapeLabel) { // save mesh as asset var assetPath = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX); @@ -98,7 +104,7 @@ namespace UniGLTF.MeshUtility /// /// /// (Mesh 分割前, Mesh BlendShape有り、Mesh BlendShape無し)のリストを返す - public static List<(Mesh Src, Mesh With, Mesh Without)> SeparationProcessing(GameObject go) + private static List<(Mesh Src, Mesh With, Mesh Without)> SeparationProcessing(GameObject go) { var list = new List<(Mesh Src, Mesh With, Mesh Without)>(); var skinnedMeshRenderers = go.GetComponentsInChildren(); @@ -131,7 +137,7 @@ namespace UniGLTF.MeshUtility for (int j = 0; j < deltaVertices.Length; j++) { - if (!deltaVertices[j].Equals(ZERO_MOVEMENT)) + if (!deltaVertices[j].Equals(Vector3.zero)) { if (!indicesUsedByBlendShape.Values.Contains(j)) { diff --git a/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta deleted file mode 100644 index 485c04f70..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 57ace5e28eb8e7746b00eaa9405197a9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/MeshUtility/TextureSaver.cs b/Assets/UniGLTF/Editor/MeshUtility/TextureSaver.cs deleted file mode 100644 index 02bec1797..000000000 --- a/Assets/UniGLTF/Editor/MeshUtility/TextureSaver.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.IO; -using UnityEditor; -using UnityEngine; -using VRMShaders; -using ColorSpace = VRMShaders.ColorSpace; - -namespace UniGLTF.MeshUtility -{ - public static class EditorChangeTextureType - { - public static void SaveAsPng(bool sRGB) - { - var texture = Selection.activeObject as Texture2D; - var path = SaveDialog(AssetsPath.FromAsset(texture)); - if (string.IsNullOrEmpty(path)) - { - return; - } - - var (tex, mime) = new EditorTextureSerializer().ExportBytesWithMime(texture, sRGB ? ColorSpace.sRGB : ColorSpace.Linear); - - File.WriteAllBytes(path, tex); - Debug.Log($"save: {path}"); - - var assetsPath = AssetsPath.FromFullpath(path); - - EditorApplication.delayCall += () => - { - assetsPath.ImportAsset(); - var importer = assetsPath.GetImporter(); - if (importer == null) - { - Debug.LogWarningFormat("fail to get TextureImporter: {0}", assetsPath); - } - importer.sRGBTexture = sRGB; - importer.SaveAndReimport(); - Debug.Log($"sRGB: {sRGB}"); - }; - } - - private static string m_lastExportDir; - static string SaveDialog(AssetsPath assetsPath) - { - // save dialog - var path = EditorUtility.SaveFilePanel( - "Save png", - assetsPath.Parent.FullPath, - $"{assetsPath.FileNameWithoutExtension}.png", - "png"); - if (!string.IsNullOrEmpty(path)) - { - m_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/"); - } - return path; - } - } -} diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs index 4e2db9cdb..30fc53a90 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs @@ -6,8 +6,9 @@ namespace UniGLTF.MeshUtility { public class MeshIntegrator { - public const string INTEGRATED_MESH_NAME = "MeshesIntegrated"; - public const string INTEGRATED_MESH_BLENDSHAPE_NAME = "MeshesBlendShapeIntegrated"; + 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)"; struct SubMesh { @@ -25,7 +26,7 @@ namespace UniGLTF.MeshUtility public Vector3[] Tangents; } - public MeshIntegrationResult Result { get; } = new MeshIntegrationResult(); + MeshIntegrationResult Result { get; } = new MeshIntegrationResult(); List Positions { get; } = new List(); List Normals { get; } = new List(); List UV { get; } = new List(); @@ -230,7 +231,7 @@ namespace UniGLTF.MeshUtility } } - public void Intgrate(MeshEnumerateOption onlyBlendShapeRenderers) + public MeshIntegrationResult Integrate(MeshEnumerateOption onlyBlendShapeRenderers) { var mesh = new Mesh(); @@ -252,34 +253,47 @@ namespace UniGLTF.MeshUtility } mesh.bindposes = BindPoses.ToArray(); + // blendshape switch (onlyBlendShapeRenderers) { case MeshEnumerateOption.OnlyWithBlendShape: { AddBlendShapesToMesh(mesh); - mesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME; + mesh.name = INTEGRATED_MESH_WITH_BLENDSHAPE_NAME; break; } - case MeshEnumerateOption.OnlyWithoutBlendShape: + case MeshEnumerateOption.All: { - mesh.name = INTEGRATED_MESH_NAME; + AddBlendShapesToMesh(mesh); + mesh.name = INTEGRATED_MESH_ALL_NAME; + break; + } + + case MeshEnumerateOption.OnlyWithoutBlendShape: + { + mesh.name = INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME; break; } } + // meshName var meshNode = new GameObject(); switch (onlyBlendShapeRenderers) { case MeshEnumerateOption.OnlyWithBlendShape: { - meshNode.name = "MeshIntegrator(BlendShape)"; + meshNode.name = INTEGRATED_MESH_WITH_BLENDSHAPE_NAME; break; } case MeshEnumerateOption.OnlyWithoutBlendShape: + { + meshNode.name = INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME; + break; + } case MeshEnumerateOption.All: { - meshNode.name = "MeshIntegrator"; + meshNode.name = INTEGRATED_MESH_ALL_NAME; break; } } @@ -290,6 +304,7 @@ namespace UniGLTF.MeshUtility integrated.bones = Bones.ToArray(); Result.IntegratedRenderer = integrated; Result.MeshMap.Integrated = mesh; + return Result; } } } diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs index 87f44f189..49958e176 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs @@ -1,13 +1,13 @@ using System.Collections.Generic; +using System.IO; +using UnityEditor; using UnityEngine; namespace UniGLTF.MeshUtility { public static class MeshIntegratorUtility { - public static string INTEGRATED_MESH_NAME => MeshIntegrator.INTEGRATED_MESH_NAME; - public static string INTEGRATED_MESH_BLENDSHAPE_NAME => MeshIntegrator.INTEGRATED_MESH_BLENDSHAPE_NAME; - + const string ASSET_SUFFIX = ".mesh.asset"; /// /// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する @@ -19,7 +19,9 @@ namespace UniGLTF.MeshUtility /// null: すべてのSkinnedMeshRenderer + MeshRenderer /// /// - public static MeshIntegrationResult Integrate(GameObject go, MeshEnumerateOption onlyBlendShapeRenderers, IEnumerable excludes = null) + public static MeshIntegrationResult Integrate(GameObject go, MeshEnumerateOption onlyBlendShapeRenderers, + IEnumerable excludes = null, + bool destroyIntegratedRenderer = false) { var exclude = new MeshExclude(excludes); @@ -87,9 +89,7 @@ namespace UniGLTF.MeshUtility } } - integrator.Intgrate(onlyBlendShapeRenderers); - integrator.Result.IntegratedRenderer.transform.SetParent(go.transform, false); - return integrator.Result; + return integrator.Integrate(onlyBlendShapeRenderers); } public static IEnumerable EnumerateSkinnedMeshRenderer(Transform root, MeshEnumerateOption hasBlendShape) @@ -160,5 +160,41 @@ namespace UniGLTF.MeshUtility } } } + + 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.IntegratedRenderer.transform.SetParent(copy.transform, false); + } + } + + public 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}"; + } + } } } \ No newline at end of file diff --git a/Assets/UniGLTF/Runtime/MeshUtility/StaticMeshIntegrator.cs b/Assets/UniGLTF/Runtime/MeshUtility/StaticMeshIntegrator.cs deleted file mode 100644 index 342dae79c..000000000 --- a/Assets/UniGLTF/Runtime/MeshUtility/StaticMeshIntegrator.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using System.IO; -using System.Linq; -#if UNITY_EDITOR -using UnityEditor; -#endif - - -namespace UniGLTF.MeshUtility -{ - public static class StaticMeshIntegrator - { - const string ASSET_SUFFIX = ".mesh.asset"; - - class Integrator - { - List m_positions = new List(); - List m_normals = new List(); - List m_uv = new List(); - /* - List m_uv2 = new List(); // ToDo - List m_uv3 = new List(); // ToDo - List m_uv4 = new List(); // ToDo - List m_colors = new List(); // ToDo - */ - - List m_subMeshes = new List(); - - List m_materials = new List(); - public List Materials - { - get { return m_materials; } - } - - public void Push(Matrix4x4 localToRoot, Mesh mesh, Material[] materials) - { - var offset = m_positions.Count; - - var hasNormal = m_normals.Count == m_positions.Count; - var hasUv = m_uv.Count == m_positions.Count; - - // attributes - m_positions.AddRange(mesh.vertices.Select(x => localToRoot.MultiplyPoint(x))); - if(mesh.normals!=null && mesh.normals.Length == mesh.vertexCount) - { - if (!hasNormal) for (int i = m_normals.Count; i < m_positions.Count; ++i) m_normals.Add(Vector3.zero); - m_normals.AddRange(mesh.normals.Select(x => localToRoot.MultiplyVector(x))); - } - if (mesh.uv != null && mesh.uv.Length == mesh.vertexCount) - { - if (!hasUv) for (int i = m_uv.Count; i < m_positions.Count; ++i) m_uv.Add(Vector2.zero); - m_uv.AddRange(mesh.uv); - } - - // indices - for (int i = 0; i < mesh.subMeshCount; ++i) - { - m_subMeshes.Add(mesh.GetIndices(i).Select(x => offset + x).ToArray()); - } - - // materials - m_materials.AddRange(materials); - } - - public Mesh ToMesh() - { - var mesh = new Mesh(); - mesh.name = MeshIntegratorUtility.INTEGRATED_MESH_NAME; - - mesh.vertices = m_positions.ToArray(); - if (m_normals.Count > 0) - { - if (m_normals.Count < m_positions.Count) for (int i = m_normals.Count; i < m_positions.Count; ++i) m_normals.Add(Vector3.zero); - mesh.normals = m_normals.ToArray(); - } - if (m_uv.Count > 0) - { - if (m_uv.Count < m_positions.Count) for (int i = m_uv.Count; i < m_positions.Count; ++i) m_uv.Add(Vector2.zero); - mesh.uv = m_uv.ToArray(); - } - - mesh.subMeshCount = m_subMeshes.Count; - for(int i=0; i(); - var filter = t.GetComponent(); - if (renderer != null && filter != null && filter.sharedMesh != null - && renderer.sharedMaterials!=null && renderer.sharedMaterials.Length == filter.sharedMesh.subMeshCount) - { - integrator.Push(root.worldToLocalMatrix * t.localToWorldMatrix, filter.sharedMesh, renderer.sharedMaterials); - } - } - - return new MeshWithMaterials - { - Mesh = integrator.ToMesh(), - Materials = integrator.Materials.ToArray(), - }; - } - - } -} diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/UnityPath.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/UnityPath.cs index 11cccb200..ff74aeedf 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/UnityPath.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/UnityPath.cs @@ -343,8 +343,13 @@ namespace UniGLTF } public static UnityPath FromAsset(UnityEngine.Object asset) - { - return new UnityPath(AssetDatabase.GetAssetPath(asset)); + { + var assetPath = AssetDatabase.GetAssetPath(asset); + if (string.IsNullOrEmpty(assetPath)) + { + throw new System.ArgumentNullException(); + } + return new UnityPath(assetPath); } public void ImportAsset() diff --git a/Assets/VRM/Editor/BlendShape/PreviewEditor.cs b/Assets/VRM/Editor/BlendShape/PreviewEditor.cs index c12f8d6d3..6a27374ff 100644 --- a/Assets/VRM/Editor/BlendShape/PreviewEditor.cs +++ b/Assets/VRM/Editor/BlendShape/PreviewEditor.cs @@ -4,6 +4,7 @@ using UnityEditorInternal; using System; using System.Linq; using System.Collections.Generic; +using System.IO; namespace VRM { @@ -88,26 +89,7 @@ namespace VRM protected virtual GameObject GetPrefab() { - var assetPath = AssetDatabase.GetAssetPath(target); - if (string.IsNullOrEmpty(assetPath)) - { - return null; - } - - var prefab = AssetDatabase.LoadAssetAtPath(assetPath); - // search prefab if nothing - if (prefab == null && 0 < (target as BlendShapeAvatar).Clips.Count) - { - prefab = (target as BlendShapeAvatar).Clips[0].Prefab; - } - // once more, with string-based method - if (prefab == null) - { - var parent = UniGLTF.UnityPath.FromUnityPath(assetPath).Parent; - var prefabPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".prefab"); - prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(prefabPath.Value); - } - return prefab; + return BlendShapeClip.VrmPrefabSearch(target); } protected virtual void OnEnable() diff --git a/Assets/VRM/Editor/BlendShape/SerializedBlendShapeClipEditor.cs b/Assets/VRM/Editor/BlendShape/SerializedBlendShapeClipEditor.cs index 17112291f..8bf3dddb4 100644 --- a/Assets/VRM/Editor/BlendShape/SerializedBlendShapeClipEditor.cs +++ b/Assets/VRM/Editor/BlendShape/SerializedBlendShapeClipEditor.cs @@ -231,6 +231,10 @@ namespace VRM // すべてのSkinnedMeshRendererを列挙する foreach (var renderer in m_items.Select(x => x.SkinnedMeshRenderer)) { + if (renderer == null) + { + continue; + } var mesh = renderer.sharedMesh; if (mesh != null && mesh.blendShapeCount > 0) { diff --git a/Assets/VRM/Editor/FirstPerson/VRMFirstPersonValidator.cs b/Assets/VRM/Editor/FirstPerson/VRMFirstPersonValidator.cs index 048159d81..d9ab8fe0d 100644 --- a/Assets/VRM/Editor/FirstPerson/VRMFirstPersonValidator.cs +++ b/Assets/VRM/Editor/FirstPerson/VRMFirstPersonValidator.cs @@ -25,11 +25,11 @@ namespace VRM return false; } - if (!r.Renderer.EnableForExport()) - { - validation = Validation.Error($"{name}.Renderer is not active", ValidationContext.Create(extended)); - return false; - } + // if (!r.Renderer.EnableForExport()) + // { + // validation = Validation.Error($"{name}.Renderer is not active", ValidationContext.Create(extended)); + // return false; + // } validation = default; return true; diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs new file mode 100644 index 000000000..af74ab1bd --- /dev/null +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs @@ -0,0 +1,93 @@ +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.IntegratedRenderer.sharedMesh.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.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.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/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta similarity index 83% rename from Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta rename to Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta index 49f2e8d74..84182f709 100644 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8eebeb093136b7f429c0e9e7295816b3 +guid: fb47e24fc1463584fa0b6b685d75f25e MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs index 3f991627b..2f81c5571 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs @@ -26,6 +26,7 @@ namespace VRM None, NoTarget, HasParent, + NotPrefab, } [SerializeField] @@ -100,10 +101,16 @@ namespace VRM private void OnEnable() { Clear(HelpMessage.Ready, ValidationError.None); - m_root = Selection.activeGameObject; 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); @@ -158,12 +165,19 @@ namespace VRM void OnValidate() { + isValid = false; if (m_root == null) { Clear(HelpMessage.SetTarget, ValidationError.NoTarget); return; } + if (m_root.GetGameObjectType() != GameObjectType.AssetPrefab) + { + Clear(HelpMessage.SetTarget, ValidationError.NotPrefab); + return; + } + if (m_root.transform.parent != null) { Clear(HelpMessage.InvalidTarget, ValidationError.HasParent); @@ -214,92 +228,124 @@ namespace VRM void OnWizardUpdate() { - // helpString = "Set target gameobject `in scene`. Prefab not supported."; } + /// 2022.05 仕様変更 + /// + /// * prefab 専用 + /// * backup するのではなく 変更した copy を作成する。元は変えない + /// * copy 先の統合前の renderer を disable で残さず destroy する + /// * 実行すると mesh, blendshape, blendShape を新規に作成する + /// * 新しいヒエラルキーを prefab に保存してから削除して終了する + /// void Integrate() { - var prefabPath = AssetDatabase.GetAssetPath(m_root); - Debug.Log(prefabPath); - var path = EditorUtility.SaveFilePanel("save prefab", Path.GetDirectoryName(prefabPath), m_root.name, "prefab"); - if (string.IsNullOrEmpty(path)) + if (m_root.GetGameObjectType() != GameObjectType.AssetPrefab) { - return; + throw new Exception("for prefab only"); } - var assetPath = UniGLTF.UnityPath.FromFullpath(path); - if (!assetPath.IsUnderAssetsFolder) + String folder = "Assets"; + var prefab = m_root.GetPrefab(); + if (prefab != null) { - Debug.LogWarning($"{path} is not asset path"); - return; + 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.IsUnderAssetsFolder) + { + EditorUtility.DisplayDialog("asset folder", "Target folder must be in the `Assets` 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); - // Backup Exists - VrmPrefabUtility.BackupVrmPrefab(m_root); - - Undo.RecordObject(m_root, "Mesh Integration"); - var instance = VrmPrefabUtility.InstantiatePrefab(m_root); - - var clips = new List(); - var proxy = instance.GetComponent(); - if (proxy != null && proxy.BlendShapeAvatar != null) - { - clips = proxy.BlendShapeAvatar.Clips; - } - foreach (var clip in clips) - { - Undo.RecordObject(clip, "Mesh Integration"); - } - - // Execute - var results = VRMMeshIntegratorUtility.Integrate(instance, clips, excludes, m_separateByBlendShape); - // integrationResults = MeshIntegratorEditor.Integrate(m_root, assetPath, excludes, m_separateByBlendShape).Select(x => x.MeshMap).ToArray(); - // public static List Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable excludes, bool separateByBlendShape) - - // disable source renderer - foreach (var res in results) - { - foreach (var renderer in res.SourceSkinnedMeshRenderers) - { - Undo.RecordObject(renderer.gameObject, "Deactivate old renderer"); - renderer.gameObject.SetActive(false); - } - - foreach (var renderer in res.SourceMeshRenderers) - { - Undo.RecordObject(renderer.gameObject, "Deactivate old renderer"); - renderer.gameObject.SetActive(false); - } - } - + // write mesh asset foreach (var result in results) { - if (result.IntegratedRenderer == null) continue; - - var childAssetPath = assetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"); + var childAssetPath = $"{assetFolder}/{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"; Debug.LogFormat("CreateAsset: {0}", childAssetPath); - childAssetPath.CreateAsset(result.IntegratedRenderer.sharedMesh); - Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers"); + AssetDatabase.CreateAsset(result.IntegratedRenderer.sharedMesh, childAssetPath); } - // Apply to Prefab - if (UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(m_root)).Equals(assetPath)) + // 統合した結果をヒエラルキーに追加する + foreach (var result in results) { - VrmPrefabUtility.ApplyChangesToPrefab(instance); + if (result.IntegratedRenderer != null) + { + result.IntegratedRenderer.transform.SetParent(copy.transform, false); + } + } + + // 統合した結果を反映した BlendShapeClip を作成して置き換える + var clips = VRMMeshIntegratorUtility.FollowBlendshapeRendererChange(results, copy, assetFolder); + + // 用が済んだ 統合前 の renderer を削除する + foreach (var result in results) + { + foreach (var renderer in result.SourceMeshRenderers) + { + GameObject.DestroyImmediate(renderer); + } + foreach (var renderer in result.SourceSkinnedMeshRenderers) + { + GameObject.DestroyImmediate(renderer); + } + } + + // reset firstperson + var firstperson = copy.GetComponent(); + if (firstperson != null) + { + firstperson.Reset(); + } + + // prefab + var prefabPath = $"{assetFolder}/VrmIntegrated.prefab"; + Debug.Log(prefabPath); + PrefabUtility.SaveAsPrefabAsset(copy, prefabPath, out bool success); + if (!success) + { + throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {prefabPath}"); + } + + // destroy scene + UnityEngine.Object.DestroyImmediate(copy); + + var prefabReference = AssetDatabase.LoadAssetAtPath(prefabPath); + foreach (var clip in clips) + { + var so = new SerializedObject(clip); + so.Update(); + // clip.Prefab = copy; + var prop = so.FindProperty("m_prefab"); + prop.objectReferenceValue = prefabReference; + so.ApplyModifiedProperties(); + } + } + + static List Integrate(GameObject root, IEnumerable excludes, bool separateByBlendShape) + { + var results = new List(); + if (separateByBlendShape) + { + results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes)); + results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes)); } else { - PrefabUtility.SaveAsPrefabAsset(instance, assetPath.Value, out bool success); - if (!success) - { - throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {assetPath}"); - } + results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes)); } - - // destroy source renderers - UnityEngine.Object.DestroyImmediate(instance); + return results; } void OnWizardCreate() diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs index 574c7121c..dd834276a 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs @@ -2,6 +2,7 @@ using System.Linq; using UniGLTF; using UnityEditor; using UnityEngine; +using UniGLTF.MeshUtility; namespace VRM { @@ -31,7 +32,7 @@ namespace VRM public static void ApplyChangesToPrefab(GameObject instance) { - var prefab = GetPrefab(instance); + var prefab = instance.GetPrefab(); if (prefab == null) { return; @@ -45,61 +46,5 @@ namespace VRM PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction); } - - static Object GetPrefab(GameObject instance) - { -#if UNITY_2018_2_OR_NEWER - return PrefabUtility.GetCorrespondingObjectFromSource(instance); -#else - return PrefabUtility.GetPrefabParent(go); -#endif - } - - /// - /// VRM prefab を ${prefab_dir}/MeshIntegratorBackup/ に複製する。 - /// - /// * prefab - /// * BlendShapeAvatar - /// * BlendShapeClip - /// - /// が複製される。 - /// - /// - public static void BackupVrmPrefab(GameObject rootPrefab) - { - var proxy = rootPrefab.GetComponent(); - - var srcAvatar = proxy.BlendShapeAvatar; - var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, rootPrefab); - - var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, rootPrefab)); - dstAvatar.Clips = clipMapper.Values.ToList(); - - var dstPrefab = BackupAsset(rootPrefab, rootPrefab); - var dstInstance = InstantiatePrefab(dstPrefab); - dstInstance.GetComponent().BlendShapeAvatar = dstAvatar; - ApplyChangesToPrefab(dstInstance); - Object.DestroyImmediate(dstInstance); - } - - /// - /// asset を ${prefab_dir}/MeshIntegratorBackup/ にコピーし、コピーしたアセットをロードして返す - /// - /// - /// - /// - /// - private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityEngine.Object - { - var srcAssetPath = UnityPath.FromAsset(asset); - var assetName = srcAssetPath.FileName; - - var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(BACKUP_DIR); - backupPath.EnsureFolder(); - var dstAssetPath = backupPath.Child(assetName); - - AssetDatabase.CopyAsset(srcAssetPath.Value, dstAssetPath.Value); - return dstAssetPath.LoadAsset(); - } } } diff --git a/Assets/VRM/Runtime/BlendShape/BlendShapeClip.cs b/Assets/VRM/Runtime/BlendShape/BlendShapeClip.cs index ab560ca8e..481383512 100644 --- a/Assets/VRM/Runtime/BlendShape/BlendShapeClip.cs +++ b/Assets/VRM/Runtime/BlendShape/BlendShapeClip.cs @@ -1,5 +1,10 @@ using System; +using System.IO; +using System.Linq; using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif namespace VRM @@ -75,6 +80,45 @@ namespace VRM public class BlendShapeClip : ScriptableObject { #if UNITY_EDITOR + /// + /// Inspector preview 用の prefab をがんばってサーチする + /// + /// + /// + public static GameObject VrmPrefabSearch(UnityEngine.Object target) + { + var assetPath = AssetDatabase.GetAssetPath(target); + if (string.IsNullOrEmpty(assetPath)) + { + return null; + } + + var prefab = AssetDatabase.LoadAssetAtPath(assetPath); + // once more, with string-based method + if (prefab == null) + { + var parent = UniGLTF.UnityPath.FromUnityPath(assetPath).Parent; + var prefabPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".prefab"); + prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(prefabPath.Value); + } + // once more, with string-based method. search same folder *.prefab + if (prefab == null) + { + var parent = UniGLTF.UnityPath.FromUnityPath(assetPath).Parent; + foreach (var file in Directory.EnumerateFiles(parent.FullPath)) + { + var ext = Path.GetExtension(file).ToLower(); + if (ext == ".prefab") + { + var prefabPath = UniGLTF.UnityPath.FromFullpath(file); + prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(prefabPath.Value); + break; + } + } + } + return prefab; + } + /// /// Preview 用のObject参照 /// @@ -87,18 +131,7 @@ namespace VRM { if (m_prefab == null) { - var assetPath = UnityEditor.AssetDatabase.GetAssetPath(this); - if (!string.IsNullOrEmpty(assetPath)) - { - // if asset is subasset of prefab - m_prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath); - if (m_prefab == null) - { - var parent = UniGLTF.UnityPath.FromAsset(this).Parent; - var prefabPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".prefab"); - m_prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(prefabPath.Value); - } - } + m_prefab = VrmPrefabSearch(this); } return m_prefab; } @@ -142,5 +175,15 @@ namespace VRM /// [SerializeField] public bool IsBinary; + + public void CopyFrom(BlendShapeClip src) + { + IsBinary = src.IsBinary; + MaterialValues = src.MaterialValues.ToArray(); + Values = src.Values.ToArray(); + Preset = src.Preset; + name = src.name; + Prefab = src.Prefab; + } } } diff --git a/Assets/VRM/Runtime/FirstPerson/VRMFirstPerson.cs b/Assets/VRM/Runtime/FirstPerson/VRMFirstPerson.cs index d326b9959..1b600df6b 100644 --- a/Assets/VRM/Runtime/FirstPerson/VRMFirstPerson.cs +++ b/Assets/VRM/Runtime/FirstPerson/VRMFirstPerson.cs @@ -91,6 +91,12 @@ namespace VRM var rendererComponents = transform.GetComponentsInChildren(); foreach (var renderer in rendererComponents) { + // renderer が !enabled/!activeSelf なのがロード中なのか否か区別がつかないような気がするので + // チェックしない。 + // if(!renderer.enabled) + // { + // continue; + // } var flags = new RendererFirstPersonFlags { Renderer = renderer, diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs deleted file mode 100644 index fea51d76c..000000000 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using UniGLTF; -using UniGLTF.MeshUtility; -using UnityEngine; - -namespace VRM -{ - /// - /// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する - /// - public static class VRMMeshIntegratorUtility - { - public static List Integrate(GameObject root, List blendshapeClips, IEnumerable excludes, bool separateByBlendShape) - { - var result = new List(); - - if (separateByBlendShape) - { - var withoutBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes); - if (withoutBlendShape.IntegratedRenderer != null) - { - result.Add(withoutBlendShape); - } - - var onlyBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes); - if (onlyBlendShape.IntegratedRenderer != null) - { - result.Add(onlyBlendShape); - FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root); - } - } - else - { - var integrated = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes); - if (integrated.IntegratedRenderer != null) - { - result.Add(integrated); - } - } - - return result; - } - - private static void FollowBlendshapeRendererChange(List clips, MeshIntegrationResult result, GameObject root) - { - if (clips == null || result == null || result.IntegratedRenderer == null || root == null) return; - - var rendererDict = result.SourceSkinnedMeshRenderers - .ToDictionary(x => x.transform.RelativePathFrom(root.transform), x => x); - - var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform); - - foreach (var clip in clips) - { - if (clip == null) continue; - - for (var i = 0; i < clip.Values.Length; ++i) - { - var val = clip.Values[i]; - if (rendererDict.ContainsKey(val.RelativePath)) - { - var srcRenderer = rendererDict[val.RelativePath]; - var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index); - var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name); - - val.RelativePath = dstPath; - val.Index = newIndex; - } - - clip.Values[i] = val; - } - } - } - } -} \ No newline at end of file