diff --git a/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs b/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs index 9f1d09db0..9199406e5 100644 --- a/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs +++ b/Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs @@ -13,6 +13,20 @@ namespace UniGLTF.MeshUtility 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) 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/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..ee054a596 --- /dev/null +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.Linq; +using UniGLTF; +using UniGLTF.MeshUtility; +using UnityEditor; +using UnityEngine; + +namespace VRM +{ + /// + /// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する + /// + public static class VRMMeshIntegratorUtility + { + public static void FollowBlendshapeRendererChange(List results, GameObject root, string assetFolder) + { + var proxy = root.GetComponent(); + if (proxy == null || proxy.BlendShapeAvatar == null) + { + return; + } + var result = results.FirstOrDefault(x => x.SourceSkinnedMeshRenderers.Count > 0); + if (result == null) + { + return; + } + + 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 + var clips = new List(); + foreach (var src in proxy.BlendShapeAvatar.Clips) + { + if (src == null) continue; + + var clip = BlendShapeClip.Instantiate(src); + clips.Add(clip); + 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; + } + + var assetPath = $"{assetFolder}/{clip.name}.asset"; + Debug.Log($"write: {assetPath}"); + AssetDatabase.CreateAsset(clip, assetPath); + } + + { + // create blendshape avatar & replace + var blendShapeAvatar = BlendShapeAvatar.CreateInstance(); + blendShapeAvatar.Clips.AddRange(clips); + var assetPath = $"{assetFolder}/blendshape.asset"; + AssetDatabase.CreateAsset(blendShapeAvatar, assetPath); + proxy.BlendShapeAvatar = blendShapeAvatar; + } + } + } +} \ 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 86f2fcd86..3797ba473 100644 --- a/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs +++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs @@ -221,90 +221,103 @@ namespace VRM void OnWizardUpdate() { - // helpString = "Set target gameobject `in scene`. Prefab not supported."; } - /// - /// Prefab に対する操作として実装されている - /// + /// 2022.05 仕様変更 + /// + /// * scene or prefab どっちでも動作する + /// * backup するのではなく 変更した copy を作成する。元は変えない + /// * 実行すると mesh, blendshape, blendShape を新規に作成する + /// * scene のときは新しいヒエラルキーが出現する + /// * prefab のときは新しいヒエラルキーを prefab 保存して、scene の方を削除して終了する + /// 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)) + String folder = "Assets"; + var prefab = m_root.GetPrefab(); + if (prefab != null) { - return; + folder = AssetDatabase.GetAssetPath(prefab); + Debug.Log(folder); } - var assetPath = UniGLTF.UnityPath.FromFullpath(path); - if (!assetPath.IsUnderAssetsFolder) + // 新規で作成されるアセットはすべてこのフォルダの中に作る。上書きチェックはしない + var assetFolder = EditorUtility.SaveFolderPanel("select asset save folder", Path.GetDirectoryName(folder), "VrmIntegrated"); + var unityPath = UniGLTF.UnityPath.FromFullpath(assetFolder); + if (!unityPath.IsUnderAssetsFolder) { - Debug.LogWarning($"{path} is not asset path"); + 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); + // 統合前のMeshを非表示にして、統合した結果をヒエラルキーに追加する + DeactivateOldRendererAndAddIntegrated(copy, results); - 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) + // write mesh asset + foreach (var result in results) { - clips.AddRange(proxy.BlendShapeAvatar.Clips); - } - foreach (var clip in clips) - { - Undo.RecordObject(clip, "Mesh Integration"); + var childAssetPath = $"{assetFolder}/{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"; + Debug.LogFormat("CreateAsset: {0}", childAssetPath); + AssetDatabase.CreateAsset(result.IntegratedRenderer.sharedMesh, childAssetPath); } - _Integrate(instance, excludes, assetPath, clips); + // 統合した結果を反映した BlendShapeClip を作成して置き換える + VRMMeshIntegratorUtility.FollowBlendshapeRendererChange(results, copy, assetFolder); - // destroy source renderers - UnityEngine.Object.DestroyImmediate(instance); - } - - void _Integrate(GameObject instance, IEnumerable excludes, UniGLTF.UnityPath assetPath, List clips) - { - // Execute - var results = new List(); - if (m_separateByBlendShape) + // reset firstperson + var firstperson = copy.GetComponent(); + if (firstperson != null) { - var withoutBlendShape = MeshIntegratorUtility.Integrate(instance, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes); - if (withoutBlendShape.IntegratedRenderer != null) + firstperson.Reset(); + } + + if (prefab != null) + { + // prefab + var prefabPath = $"{assetFolder}/VrmIntegrated.prefab"; + Debug.Log(prefabPath); + PrefabUtility.SaveAsPrefabAsset(copy, prefabPath, out bool success); + if (!success) { - results.Add(withoutBlendShape); + throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {prefabPath}"); } - var onlyBlendShape = MeshIntegratorUtility.Integrate(instance, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes); - if (onlyBlendShape.IntegratedRenderer != null) - { - results.Add(onlyBlendShape); - VRMMeshIntegratorUtility.FollowBlendshapeRendererChange(clips, onlyBlendShape, instance); - } + // destroy scene + UnityEngine.Object.DestroyImmediate(copy); } else { - var integrated = MeshIntegratorUtility.Integrate(instance, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes); - if (integrated.IntegratedRenderer != null) - { - results.Add(integrated); - } + // do nothing. keep scene } - - DeactivateOldRendererAndAddIntegrated(instance, results, assetPath, - UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(m_root)).Equals(assetPath)); } - static void DeactivateOldRendererAndAddIntegrated(GameObject instance, List results, UniGLTF.UnityPath assetPath, - bool applyToPrefab) + static List Integrate(GameObject root, IEnumerable excludes, bool separateByBlendShape) { - // TODO: add integrated + var results = new List(); + if (separateByBlendShape) + { + results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes)); + results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes)); + } + else + { + results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes)); + } + return results; + } + /// + /// 古いMeshを disable にし、新しい統合済み mesh を追加する + /// + static void DeactivateOldRendererAndAddIntegrated(GameObject root, List results) + { // disable source renderer foreach (var result in results) { @@ -321,31 +334,12 @@ namespace VRM } } + // Add integrated foreach (var result in results) { - if (result.IntegratedRenderer == null) continue; - - // Add integrated - result.IntegratedRenderer.transform.SetParent(instance.transform, false); - - // save as asset mesh - var childAssetPath = assetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}"); - Debug.LogFormat("CreateAsset: {0}", childAssetPath); - childAssetPath.CreateAsset(result.IntegratedRenderer.sharedMesh); - Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers"); - } - - // Apply to Prefab - if (applyToPrefab) - { - VrmPrefabUtility.ApplyChangesToPrefab(instance); - } - else - { - PrefabUtility.SaveAsPrefabAsset(instance, assetPath.Value, out bool success); - if (!success) + if (result.IntegratedRenderer != null) { - throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {assetPath}"); + result.IntegratedRenderer.transform.SetParent(root.transform, false); } } } diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs index 574c7121c..0f203752b 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; @@ -46,15 +47,6 @@ 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/ に複製する。 /// @@ -64,18 +56,21 @@ namespace VRM /// /// が複製される。 /// - /// - public static void BackupVrmPrefab(GameObject rootPrefab) + /// + public static void BackupVrmPrefab(GameObject go) { - var proxy = rootPrefab.GetComponent(); + var prefab = go.GetGameObjectType() == GameObjectType.AssetPrefab ? go : PrefabUtility.GetCorrespondingObjectFromSource(go); + var prefabPath = UnityPath.FromAsset(prefab); + + var proxy = go.GetComponent(); var srcAvatar = proxy.BlendShapeAvatar; - var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, rootPrefab); + var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, prefabPath); - var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, rootPrefab)); + var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, prefabPath)); dstAvatar.Clips = clipMapper.Values.ToList(); - var dstPrefab = BackupAsset(rootPrefab, rootPrefab); + var dstPrefab = BackupAsset(prefab, prefabPath); var dstInstance = InstantiatePrefab(dstPrefab); dstInstance.GetComponent().BlendShapeAvatar = dstAvatar; ApplyChangesToPrefab(dstInstance); @@ -89,12 +84,12 @@ namespace VRM /// /// /// - private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityEngine.Object + private static T BackupAsset(T asset, UnityPath prefabPath) where T : UnityEngine.Object { var srcAssetPath = UnityPath.FromAsset(asset); var assetName = srcAssetPath.FileName; - var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(BACKUP_DIR); + var backupPath = prefabPath.Parent.Child(BACKUP_DIR); backupPath.EnsureFolder(); var dstAssetPath = backupPath.Child(assetName); 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 066b55fbd..000000000 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs +++ /dev/null @@ -1,45 +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 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