modular-avatar/Editor/BlendshapeSyncAnimationProcessor.cs

159 lines
5.4 KiB
C#
Raw Normal View History

#if MA_VRCSDK3_AVATARS
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
2023-01-19 20:32:44 +08:00
using nadena.dev.modular_avatar.editor.ErrorReporting;
using nadena.dev.ndmf.animator;
2022-10-20 10:42:33 +08:00
using UnityEditor;
using UnityEngine;
2022-11-11 12:39:58 +08:00
namespace nadena.dev.modular_avatar.core.editor
2022-10-20 10:42:33 +08:00
{
/**
* Ensures that any blendshapes marked for syncing by BlendshapeSync propagate values in all animation clips.
*
* Note that we only look at the FX layer, as any other layer won't work properly with mirror reflections anyway.
*/
internal class BlendshapeSyncAnimationProcessor
{
private readonly ndmf.BuildContext _context;
2022-10-20 10:42:33 +08:00
private Dictionary<SummaryBinding, List<SummaryBinding>> _bindingMappings;
internal BlendshapeSyncAnimationProcessor(ndmf.BuildContext context)
{
_context = context;
_bindingMappings = new Dictionary<SummaryBinding, List<SummaryBinding>>();
}
private struct SummaryBinding : IEquatable<SummaryBinding>
2022-10-20 10:42:33 +08:00
{
private const string PREFIX = "blendShape.";
public string path;
public string propertyName;
public SummaryBinding(string path, string blendShape)
{
this.path = path;
this.propertyName = PREFIX + blendShape;
}
public static SummaryBinding? FromEditorBinding(EditorCurveBinding binding)
2022-10-20 10:42:33 +08:00
{
if (binding.type != typeof(SkinnedMeshRenderer) || !binding.propertyName.StartsWith(PREFIX))
{
return null;
2022-10-20 10:42:33 +08:00
}
return new SummaryBinding(binding.path, binding.propertyName.Substring(PREFIX.Length));
}
public EditorCurveBinding ToEditorCurveBinding()
{
return EditorCurveBinding.FloatCurve(
path,
typeof(SkinnedMeshRenderer),
propertyName
);
}
2022-10-20 10:42:33 +08:00
public bool Equals(SummaryBinding other)
{
return path == other.path && propertyName == other.propertyName;
}
2022-10-20 10:42:33 +08:00
public override bool Equals(object? obj)
2022-10-20 10:42:33 +08:00
{
return obj is SummaryBinding other && Equals(other);
2022-10-20 10:42:33 +08:00
}
public override int GetHashCode()
2022-10-20 10:42:33 +08:00
{
return HashCode.Combine(path, propertyName);
2022-10-20 10:42:33 +08:00
}
}
public void OnPreprocessAvatar()
{
var avatarGameObject = _context.AvatarRootObject;
var animDb = _context.Extension<AnimatorServicesContext>().AnimationIndex;
_bindingMappings = new Dictionary<SummaryBinding, List<SummaryBinding>>();
var components = avatarGameObject.GetComponentsInChildren<ModularAvatarBlendshapeSync>(true);
if (components.Length == 0) return;
2022-10-20 10:42:33 +08:00
foreach (var component in components)
{
BuildReport.ReportingObject(component, () => ProcessComponent(avatarGameObject, component));
2022-10-20 10:42:33 +08:00
}
var clips = new HashSet<VirtualClip>();
foreach (var key in _bindingMappings.Keys)
{
var ecb = key.ToEditorCurveBinding();
clips.UnionWith(animDb.GetClipsForBinding(ecb));
}
2022-10-20 10:42:33 +08:00
// Walk and transform all clips
foreach (var clip in clips)
2022-10-20 10:42:33 +08:00
{
ProcessClip(clip);
}
2022-10-20 10:42:33 +08:00
}
private void ProcessComponent(GameObject avatarGameObject, ModularAvatarBlendshapeSync component)
2023-01-19 20:32:44 +08:00
{
var targetObj = RuntimeUtil.RelativePath(avatarGameObject, component.gameObject);
2023-01-19 20:32:44 +08:00
if (targetObj == null) return;
2023-01-19 20:32:44 +08:00
foreach (var binding in component.Bindings)
{
var refObj = binding.ReferenceMesh.Get(component);
if (refObj == null) continue;
var refSmr = refObj.GetComponent<SkinnedMeshRenderer>();
if (refSmr == null) continue;
var refPath = RuntimeUtil.RelativePath(avatarGameObject, refObj);
if (refPath == null) continue;
2023-01-19 20:32:44 +08:00
var srcBinding = new SummaryBinding(refPath, binding.Blendshape);
if (!_bindingMappings.TryGetValue(srcBinding, out var dstBindings))
{
dstBindings = new List<SummaryBinding>();
_bindingMappings[srcBinding] = dstBindings;
}
var targetBlendshapeName = string.IsNullOrWhiteSpace(binding.LocalBlendshape)
? binding.Blendshape
: binding.LocalBlendshape;
dstBindings.Add(new SummaryBinding(targetObj, targetBlendshapeName));
}
}
private void ProcessClip(VirtualClip clip)
2022-10-20 10:42:33 +08:00
{
foreach (var binding in clip.GetFloatCurveBindings().ToList())
2022-10-20 10:42:33 +08:00
{
var srcBinding = SummaryBinding.FromEditorBinding(binding);
if (srcBinding == null || !_bindingMappings.TryGetValue(srcBinding.Value, out var dstBindings))
2022-10-20 10:42:33 +08:00
{
continue;
}
var curve = clip.GetFloatCurve(binding);
2022-10-20 10:42:33 +08:00
foreach (var dst in dstBindings)
{
clip.SetFloatCurve(dst.ToEditorCurveBinding(), curve);
2022-10-20 10:42:33 +08:00
}
}
}
}
}
#endif