2023-11-10 14:37:56 +08:00
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
#nullable enable
|
|
|
|
|
|
|
|
|
|
using System;
|
2023-11-10 14:37:56 +08:00
|
|
|
|
using System.Collections.Generic;
|
2025-02-17 14:25:12 +08:00
|
|
|
|
using System.Linq;
|
2023-01-19 20:32:44 +08:00
|
|
|
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
2025-02-17 14:25:12 +08:00
|
|
|
|
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
|
|
|
|
|
{
|
2025-02-17 14:25:12 +08:00
|
|
|
|
private readonly ndmf.BuildContext _context;
|
2022-10-20 10:42:33 +08:00
|
|
|
|
private Dictionary<SummaryBinding, List<SummaryBinding>> _bindingMappings;
|
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
public static SummaryBinding? FromEditorBinding(EditorCurveBinding binding)
|
2022-10-20 10:42:33 +08:00
|
|
|
|
{
|
|
|
|
|
if (binding.type != typeof(SkinnedMeshRenderer) || !binding.propertyName.StartsWith(PREFIX))
|
|
|
|
|
{
|
2025-02-17 14:25:12 +08:00
|
|
|
|
return null;
|
2022-10-20 10:42:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new SummaryBinding(binding.path, binding.propertyName.Substring(PREFIX.Length));
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
public EditorCurveBinding ToEditorCurveBinding()
|
|
|
|
|
{
|
|
|
|
|
return EditorCurveBinding.FloatCurve(
|
|
|
|
|
path,
|
|
|
|
|
typeof(SkinnedMeshRenderer),
|
|
|
|
|
propertyName
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-10-20 10:42:33 +08:00
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
public bool Equals(SummaryBinding other)
|
|
|
|
|
{
|
|
|
|
|
return path == other.path && propertyName == other.propertyName;
|
|
|
|
|
}
|
2022-10-20 10:42:33 +08:00
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
public override bool Equals(object? obj)
|
2022-10-20 10:42:33 +08:00
|
|
|
|
{
|
2025-02-17 14:25:12 +08:00
|
|
|
|
return obj is SummaryBinding other && Equals(other);
|
2022-10-20 10:42:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
public override int GetHashCode()
|
2022-10-20 10:42:33 +08:00
|
|
|
|
{
|
2025-02-17 14:25:12 +08:00
|
|
|
|
return HashCode.Combine(path, propertyName);
|
2022-10-20 10:42:33 +08:00
|
|
|
|
}
|
2025-02-17 14:25:12 +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)
|
|
|
|
|
{
|
2023-10-15 17:44:53 +08:00
|
|
|
|
BuildReport.ReportingObject(component, () => ProcessComponent(avatarGameObject, component));
|
2022-10-20 10:42:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 14:25:12 +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
|
2025-02-17 14:25:12 +08:00
|
|
|
|
foreach (var clip in clips)
|
2022-10-20 10:42:33 +08:00
|
|
|
|
{
|
2025-02-17 14:25:12 +08:00
|
|
|
|
ProcessClip(clip);
|
|
|
|
|
}
|
2022-10-20 10:42:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-10-15 17:44:53 +08:00
|
|
|
|
private void ProcessComponent(GameObject avatarGameObject, ModularAvatarBlendshapeSync component)
|
2023-01-19 20:32:44 +08:00
|
|
|
|
{
|
2023-10-15 17:44:53 +08:00
|
|
|
|
var targetObj = RuntimeUtil.RelativePath(avatarGameObject, component.gameObject);
|
2023-01-19 20:32:44 +08:00
|
|
|
|
|
2025-02-17 14:25:12 +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;
|
|
|
|
|
|
2023-10-15 17:44:53 +08:00
|
|
|
|
var refPath = RuntimeUtil.RelativePath(avatarGameObject, refObj);
|
2025-02-17 14:25:12 +08:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
private void ProcessClip(VirtualClip clip)
|
2022-10-20 10:42:33 +08:00
|
|
|
|
{
|
2025-02-17 14:25:12 +08:00
|
|
|
|
foreach (var binding in clip.GetFloatCurveBindings().ToList())
|
2022-10-20 10:42:33 +08:00
|
|
|
|
{
|
2025-02-17 14:25:12 +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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 14:25:12 +08:00
|
|
|
|
var curve = clip.GetFloatCurve(binding);
|
2022-10-20 10:42:33 +08:00
|
|
|
|
foreach (var dst in dstBindings)
|
|
|
|
|
{
|
2025-02-17 14:25:12 +08:00
|
|
|
|
clip.SetFloatCurve(dst.ToEditorCurveBinding(), curve);
|
2022-10-20 10:42:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-10 14:37:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|