148 lines
5.4 KiB
C#
148 lines
5.4 KiB
C#
using System;
|
|
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 class VrmBlendShapeUpdater
|
|
{
|
|
// BlendShapeBinding.RelativePath からの逆引き
|
|
Dictionary<string, List<MeshIntegrationResult>> _rendererPathMap = new();
|
|
GameObject _root;
|
|
|
|
VrmBlendShapeUpdater(GameObject root, List<MeshIntegrationResult> results)
|
|
{
|
|
_root = root;
|
|
foreach (var result in results)
|
|
{
|
|
foreach (var x in result.SourceSkinnedMeshRenderers)
|
|
{
|
|
var srcPath = x.transform.RelativePathFrom(root.transform);
|
|
if (_rendererPathMap.TryGetValue(srcPath, out var value))
|
|
{
|
|
value.Add(result);
|
|
}
|
|
else
|
|
{
|
|
value = new List<MeshIntegrationResult>();
|
|
value.Add(result);
|
|
_rendererPathMap.Add(srcPath, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 分割されて増える => 増えない BlendShape のある方にいく
|
|
// 統合されて減る => 名前が同じものが統合される
|
|
private IEnumerable<BlendShapeBinding> ReplaceBlendShapeBinding(IEnumerable<BlendShapeBinding> values)
|
|
{
|
|
var used = new HashSet<BlendShapeBinding>();
|
|
foreach (var val in values)
|
|
{
|
|
if (_rendererPathMap.TryGetValue(val.RelativePath, out var results))
|
|
{
|
|
foreach (var result in results)
|
|
{
|
|
if (result.Integrated == null)
|
|
{
|
|
continue;
|
|
}
|
|
var name = result.Integrated.Mesh.GetBlendShapeName(val.Index);
|
|
var newIndex = result.Integrated.Mesh.GetBlendShapeIndex(name);
|
|
if (newIndex == -1)
|
|
{
|
|
throw new KeyNotFoundException($"blendshape:{name} not found");
|
|
}
|
|
|
|
var dstPath = result.Integrated.IntegratedRenderer.transform.RelativePathFrom(_root.transform);
|
|
var binding = new BlendShapeBinding
|
|
{
|
|
RelativePath = dstPath,
|
|
Index = newIndex,
|
|
Weight = val.Weight,
|
|
};
|
|
if (used.Contains(binding))
|
|
{
|
|
Debug.LogWarning($"duplicated: {binding}");
|
|
}
|
|
else
|
|
{
|
|
#if VRM_DEVELOP
|
|
Debug.Log($"{val} >> {binding}");
|
|
#endif
|
|
used.Add(binding);
|
|
yield return binding;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// skip
|
|
Debug.LogWarning($"SkinnedMeshRenderer not found: {val.RelativePath}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static List<BlendShapeClip> FollowBlendshapeRendererChange(string assetFolder,
|
|
GameObject root,
|
|
List<MeshIntegrationResult> results)
|
|
{
|
|
var clips = new List<BlendShapeClip>();
|
|
var proxy = root.GetComponent<VRMBlendShapeProxy>();
|
|
if (proxy == null || proxy.BlendShapeAvatar == null)
|
|
{
|
|
return clips;
|
|
}
|
|
|
|
var util = new VrmBlendShapeUpdater(root, results);
|
|
|
|
// create modified BlendShapeClip
|
|
var clipAssetPathList = new List<string>();
|
|
foreach (var src in proxy.BlendShapeAvatar.Clips.Where(x => x != null))
|
|
{
|
|
var copy = util.RecreateBlendShapeClip(src, assetFolder);
|
|
var assetPath = $"{assetFolder}/{copy.name}.asset";
|
|
AssetDatabase.CreateAsset(copy, assetPath);
|
|
clipAssetPathList.Add(assetPath);
|
|
clips.Add(copy);
|
|
}
|
|
|
|
// create BlendShapeAvatar
|
|
proxy.BlendShapeAvatar = RecreateBlendShapeAvatar(clips, assetFolder);
|
|
|
|
return clips;
|
|
}
|
|
|
|
BlendShapeClip RecreateBlendShapeClip(BlendShapeClip src, string assetFolder)
|
|
{
|
|
if (src == null)
|
|
{
|
|
throw new ArgumentNullException();
|
|
}
|
|
|
|
// copy
|
|
var copy = ScriptableObject.CreateInstance<BlendShapeClip>();
|
|
copy.CopyFrom(src);
|
|
copy.Prefab = null;
|
|
copy.Values = ReplaceBlendShapeBinding(copy.Values).ToArray();
|
|
return copy;
|
|
}
|
|
|
|
static BlendShapeAvatar RecreateBlendShapeAvatar(IReadOnlyCollection<BlendShapeClip> clips, string assetFolder)
|
|
{
|
|
var copy = ScriptableObject.CreateInstance<BlendShapeAvatar>();
|
|
copy.Clips.AddRange(clips);
|
|
var assetPath = $"{assetFolder}/blendshape.asset";
|
|
AssetDatabase.CreateAsset(copy, assetPath);
|
|
return copy;
|
|
}
|
|
}
|
|
} |