VrmMeshIntegratorWizard

This commit is contained in:
ousttrue
2022-05-17 16:38:31 +09:00
parent 4a41d42e02
commit 4d2047d5ff
9 changed files with 190 additions and 147 deletions

View File

@@ -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)

View File

@@ -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()

View File

@@ -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;

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UniGLTF.MeshUtility;
using UnityEditor;
using UnityEngine;
namespace VRM
{
/// <summary>
/// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する
/// </summary>
public static class VRMMeshIntegratorUtility
{
public static void FollowBlendshapeRendererChange(List<MeshIntegrationResult> results, GameObject root, string assetFolder)
{
var proxy = root.GetComponent<VRMBlendShapeProxy>();
if (proxy == null || proxy.BlendShapeAvatar == null)
{
return;
}
var result = results.FirstOrDefault(x => x.SourceSkinnedMeshRenderers.Count > 0);
if (result == null)
{
return;
}
var rendererDict = new Dictionary<string, SkinnedMeshRenderer>();
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<BlendShapeClip>();
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>();
blendShapeAvatar.Clips.AddRange(clips);
var assetPath = $"{assetFolder}/blendshape.asset";
AssetDatabase.CreateAsset(blendShapeAvatar, assetPath);
proxy.BlendShapeAvatar = blendShapeAvatar;
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8eebeb093136b7f429c0e9e7295816b3
guid: fb47e24fc1463584fa0b6b685d75f25e
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -221,90 +221,103 @@ namespace VRM
void OnWizardUpdate()
{
// helpString = "Set target gameobject `in scene`. Prefab not supported.";
}
/// <summary>
/// Prefab に対する操作として実装されている
/// </summary>
/// 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<BlendShapeClip>();
var proxy = instance.GetComponent<VRMBlendShapeProxy>();
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<Mesh> excludes, UniGLTF.UnityPath assetPath, List<BlendShapeClip> clips)
{
// Execute
var results = new List<UniGLTF.MeshUtility.MeshIntegrationResult>();
if (m_separateByBlendShape)
// reset firstperson
var firstperson = copy.GetComponent<VRMFirstPerson>();
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<MeshIntegrationResult> results, UniGLTF.UnityPath assetPath,
bool applyToPrefab)
static List<UniGLTF.MeshUtility.MeshIntegrationResult> Integrate(GameObject root, IEnumerable<Mesh> excludes, bool separateByBlendShape)
{
// TODO: add integrated
var results = new List<UniGLTF.MeshUtility.MeshIntegrationResult>();
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;
}
/// <summary>
/// 古いMeshを disable にし、新しい統合済み mesh を追加する
/// </summary>
static void DeactivateOldRendererAndAddIntegrated(GameObject root, List<MeshIntegrationResult> 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);
}
}
}

View File

@@ -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
}
/// <summary>
/// VRM prefab を ${prefab_dir}/MeshIntegratorBackup/ に複製する。
///
@@ -64,18 +56,21 @@ namespace VRM
///
/// が複製される。
/// </summary>
/// <param name="rootPrefab"></param>
public static void BackupVrmPrefab(GameObject rootPrefab)
/// <param name="go"></param>
public static void BackupVrmPrefab(GameObject go)
{
var proxy = rootPrefab.GetComponent<VRMBlendShapeProxy>();
var prefab = go.GetGameObjectType() == GameObjectType.AssetPrefab ? go : PrefabUtility.GetCorrespondingObjectFromSource(go);
var prefabPath = UnityPath.FromAsset(prefab);
var proxy = go.GetComponent<VRMBlendShapeProxy>();
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<VRMBlendShapeProxy>().BlendShapeAvatar = dstAvatar;
ApplyChangesToPrefab(dstInstance);
@@ -89,12 +84,12 @@ namespace VRM
/// <param name="rootPrefab"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private static T BackupAsset<T>(T asset, GameObject rootPrefab) where T : UnityEngine.Object
private static T BackupAsset<T>(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);

View File

@@ -91,6 +91,12 @@ namespace VRM
var rendererComponents = transform.GetComponentsInChildren<Renderer>();
foreach (var renderer in rendererComponents)
{
// renderer が !enabled/!activeSelf なのがロード中なのか否か区別がつかないような気がするので
// チェックしない。
// if(!renderer.enabled)
// {
// continue;
// }
var flags = new RendererFirstPersonFlags
{
Renderer = renderer,

View File

@@ -1,45 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UniGLTF.MeshUtility;
using UnityEngine;
namespace VRM
{
/// <summary>
/// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する
/// </summary>
public static class VRMMeshIntegratorUtility
{
public static void FollowBlendshapeRendererChange(List<BlendShapeClip> 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;
}
}
}
}
}