From fda9878a482b5d91ce568e7ac4da5a5ca34c4ae5 Mon Sep 17 00:00:00 2001 From: bd_ Date: Sat, 15 Feb 2025 18:59:08 -0800 Subject: [PATCH] port remaining MA passes to anim-api --- Editor/BlendshapeSyncAnimationProcessor.cs | 201 ++++++------------ .../ConstraintConverterPass.cs | 37 ++-- Editor/PluginDefinition/PluginDefinition.cs | 37 ++-- Editor/VisibleHeadAccessoryProcessor.cs | 34 +-- .../ReplaceObject/ReplaceObjectTests.cs | 7 +- 5 files changed, 117 insertions(+), 199 deletions(-) diff --git a/Editor/BlendshapeSyncAnimationProcessor.cs b/Editor/BlendshapeSyncAnimationProcessor.cs index c4db4847..f22d5e80 100644 --- a/Editor/BlendshapeSyncAnimationProcessor.cs +++ b/Editor/BlendshapeSyncAnimationProcessor.cs @@ -1,12 +1,14 @@ #if MA_VRCSDK3_AVATARS +#nullable enable + +using System; using System.Collections.Generic; +using System.Linq; using nadena.dev.modular_avatar.editor.ErrorReporting; +using nadena.dev.ndmf.animator; using UnityEditor; -using UnityEditor.Animations; using UnityEngine; -using VRC.SDK3.Avatars.Components; -using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { @@ -17,11 +19,16 @@ namespace nadena.dev.modular_avatar.core.editor */ internal class BlendshapeSyncAnimationProcessor { - private BuildContext _context; - private Dictionary _motionCache; + private readonly ndmf.BuildContext _context; private Dictionary> _bindingMappings; - private struct SummaryBinding + internal BlendshapeSyncAnimationProcessor(ndmf.BuildContext context) + { + _context = context; + _bindingMappings = new Dictionary>(); + } + + private struct SummaryBinding : IEquatable { private const string PREFIX = "blendShape."; public string path; @@ -33,71 +40,76 @@ namespace nadena.dev.modular_avatar.core.editor this.propertyName = PREFIX + blendShape; } - public static SummaryBinding FromEditorBinding(EditorCurveBinding binding) + public static SummaryBinding? FromEditorBinding(EditorCurveBinding binding) { if (binding.type != typeof(SkinnedMeshRenderer) || !binding.propertyName.StartsWith(PREFIX)) { - return new SummaryBinding(); + return null; } return new SummaryBinding(binding.path, binding.propertyName.Substring(PREFIX.Length)); } + + public EditorCurveBinding ToEditorCurveBinding() + { + return EditorCurveBinding.FloatCurve( + path, + typeof(SkinnedMeshRenderer), + propertyName + ); + } + + public bool Equals(SummaryBinding other) + { + return path == other.path && propertyName == other.propertyName; + } + + public override bool Equals(object? obj) + { + return obj is SummaryBinding other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(path, propertyName); + } } - public void OnPreprocessAvatar(BuildContext context) + public void OnPreprocessAvatar() { - _context = context; - var avatarGameObject = context.AvatarRootObject; - var animDb = _context.AnimationDatabase; - - var avatarDescriptor = context.AvatarDescriptor; + var avatarGameObject = _context.AvatarRootObject; + var animDb = _context.Extension().AnimationIndex; + _bindingMappings = new Dictionary>(); - _motionCache = new Dictionary(); var components = avatarGameObject.GetComponentsInChildren(true); if (components.Length == 0) return; - var layers = avatarDescriptor.baseAnimationLayers; - var fxIndex = -1; - AnimatorController controller = null; - for (int i = 0; i < layers.Length; i++) - { - if (layers[i].type == VRCAvatarDescriptor.AnimLayerType.FX && !layers[i].isDefault) - { - if (layers[i].animatorController is AnimatorController c && c != null) - { - fxIndex = i; - controller = c; - break; - } - } - } - - if (controller == null) - { - // Nothing to do, return - } - foreach (var component in components) { BuildReport.ReportingObject(component, () => ProcessComponent(avatarGameObject, component)); } - // Walk and transform all clips - animDb.ForeachClip(clip => + var clips = new HashSet(); + foreach (var key in _bindingMappings.Keys) { - if (clip.CurrentClip is AnimationClip anim) - { - BuildReport.ReportingObject(clip.CurrentClip, - () => { clip.CurrentClip = TransformMotion(anim); }); - } - }); + var ecb = key.ToEditorCurveBinding(); + clips.UnionWith(animDb.GetClipsForBinding(ecb)); + } + + // Walk and transform all clips + foreach (var clip in clips) + { + ProcessClip(clip); + } } private void ProcessComponent(GameObject avatarGameObject, ModularAvatarBlendshapeSync component) { var targetObj = RuntimeUtil.RelativePath(avatarGameObject, component.gameObject); + if (targetObj == null) return; + foreach (var binding in component.Bindings) { var refObj = binding.ReferenceMesh.Get(component); @@ -106,6 +118,7 @@ namespace nadena.dev.modular_avatar.core.editor if (refSmr == null) continue; var refPath = RuntimeUtil.RelativePath(avatarGameObject, refObj); + if (refPath == null) continue; var srcBinding = new SummaryBinding(refPath, binding.Blendshape); @@ -123,108 +136,20 @@ namespace nadena.dev.modular_avatar.core.editor } } - Motion TransformMotion(Motion motion) + private void ProcessClip(VirtualClip clip) { - if (motion == null) return null; - if (_motionCache.TryGetValue(motion, out var cached)) return cached; - - switch (motion) + foreach (var binding in clip.GetFloatCurveBindings().ToList()) { - case AnimationClip clip: - { - motion = ProcessClip(clip); - - break; - } - - case BlendTree tree: - { - bool anyChanged = false; - var children = tree.children; - - for (int i = 0; i < children.Length; i++) - { - var newM = TransformMotion(children[i].motion); - if (newM != children[i].motion) - { - anyChanged = true; - children[i].motion = newM; - } - } - - if (anyChanged) - { - var newTree = new BlendTree(); - EditorUtility.CopySerialized(tree, newTree); - _context.SaveAsset(newTree); - - newTree.children = children; - motion = newTree; - } - - break; - } - default: - Debug.LogWarning($"Ignoring unsupported motion type {motion.GetType()}"); - break; - } - - _motionCache[motion] = motion; - return motion; - } - - AnimationClip ProcessClip(AnimationClip origClip) - { - var clip = origClip; - EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip); - - foreach (var binding in bindings) - { - if (!_bindingMappings.TryGetValue(SummaryBinding.FromEditorBinding(binding), out var dstBindings)) + var srcBinding = SummaryBinding.FromEditorBinding(binding); + if (srcBinding == null || !_bindingMappings.TryGetValue(srcBinding.Value, out var dstBindings)) { continue; } - if (clip == origClip) - { - clip = Object.Instantiate(clip); - } - + var curve = clip.GetFloatCurve(binding); foreach (var dst in dstBindings) { - clip.SetCurve(dst.path, typeof(SkinnedMeshRenderer), dst.propertyName, - AnimationUtility.GetEditorCurve(origClip, binding)); - } - } - - return clip; - } - - IEnumerable AllStates(AnimatorController controller) - { - HashSet visitedStateMachines = new HashSet(); - Queue stateMachines = new Queue(); - - foreach (var layer in controller.layers) - { - if (layer.stateMachine != null) - stateMachines.Enqueue(layer.stateMachine); - } - - while (stateMachines.Count > 0) - { - var next = stateMachines.Dequeue(); - if (visitedStateMachines.Contains(next)) continue; - visitedStateMachines.Add(next); - - foreach (var state in next.states) - { - yield return state.state; - } - - foreach (var sm in next.stateMachines) - { - stateMachines.Enqueue(sm.stateMachine); + clip.SetFloatCurve(dst.ToEditorCurveBinding(), curve); } } } diff --git a/Editor/OptimizationPasses/ConstraintConverterPass.cs b/Editor/OptimizationPasses/ConstraintConverterPass.cs index 2dfa9a7e..743a1b32 100644 --- a/Editor/OptimizationPasses/ConstraintConverterPass.cs +++ b/Editor/OptimizationPasses/ConstraintConverterPass.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +#nullable enable + +using System.Collections.Generic; using nadena.dev.ndmf; using UnityEditor; #if MA_VRCSDK3_AVATARS_3_7_0_OR_NEWER @@ -6,7 +8,7 @@ using UnityEngine; using UnityEngine.Animations; using VRC.SDK3.Avatars; using System.Linq; -using nadena.dev.modular_avatar.animation; +using nadena.dev.ndmf.animator; using VRC.Dynamics; #endif @@ -60,7 +62,7 @@ namespace nadena.dev.modular_avatar.core.editor AvatarDynamicsSetup.DoConvertUnityConstraints(targetConstraintComponents, null, false); - var asc = context.Extension(); + var asc = context.Extension(); // Also look for preexisting VRCConstraints so we can go fix up any broken animation clips from people who // clicked auto fix :( @@ -70,24 +72,20 @@ namespace nadena.dev.modular_avatar.core.editor var targetPaths = constraintGameObjects .Union(existingVRCConstraints) - .Select(c => asc.PathMappings.GetObjectIdentifier(c)) + .Select(c => asc.ObjectPathRemapper.GetVirtualPathForObject(c)) .ToHashSet(); // Update animation clips - var clips = targetPaths.SelectMany(tp => asc.AnimationDatabase.ClipsForPath(tp)) + + var clips = targetPaths.SelectMany(tp => asc.AnimationIndex.GetClipsForObjectPath(tp)) .ToHashSet(); foreach (var clip in clips) RemapSingleClip(clip, targetPaths); } - private void RemapSingleClip(AnimationDatabase.ClipHolder clip, HashSet targetPaths) + private void RemapSingleClip(VirtualClip clip, HashSet targetPaths) { - var motion = clip.CurrentClip as AnimationClip; - if (motion == null) return; - - var bindings = AnimationUtility.GetCurveBindings(motion); - var toUpdateBindings = new List(); - var toUpdateCurves = new List(); + var bindings = clip.GetFloatCurveBindings().ToList(); foreach (var ecb in bindings) { @@ -102,20 +100,11 @@ namespace nadena.dev.modular_avatar.core.editor type = newType, propertyName = newProp }; - var curve = AnimationUtility.GetEditorCurve(motion, ecb); - if (curve != null) - { - toUpdateBindings.Add(newBinding); - toUpdateCurves.Add(curve); - - toUpdateBindings.Add(ecb); - toUpdateCurves.Add(null); - } + var curve = clip.GetFloatCurve(ecb); + clip.SetFloatCurve(newBinding, curve); + clip.SetFloatCurve(newBinding, null); } } - - if (toUpdateBindings.Count == 0) return; - AnimationUtility.SetEditorCurves(motion, toUpdateBindings.ToArray(), toUpdateCurves.ToArray()); } #else diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index 2c3dd9ff..b85503c6 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -77,28 +77,29 @@ namespace nadena.dev.modular_avatar.core.editor.plugin #endif seq.Run(MergeArmaturePluginPass.Instance); + seq.Run(BoneProxyPluginPass.Instance); + +#if MA_VRCSDK3_AVATARS + seq.Run(VisibleHeadAccessoryPluginPass.Instance); +#endif + + seq.Run("World Fixed Object", + ctx => new WorldFixedObjectProcessor().Process(ctx) + ); + + seq.Run(ReplaceObjectPluginPass.Instance); + +#if MA_VRCSDK3_AVATARS + seq.Run(BlendshapeSyncAnimationPluginPass.Instance); +#endif + + seq.Run(ConstraintConverterPass.Instance); + seq.Run("Prune empty animator layers", ctx => { ctx.Extension().RemoveEmptyLayers(); }); seq.Run("Harmonize animator parameter types", ctx => { ctx.Extension().HarmonizeParameterTypes(); }); }); - - seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 => - { - seq.Run(BoneProxyPluginPass.Instance); -#if MA_VRCSDK3_AVATARS - seq.Run(VisibleHeadAccessoryPluginPass.Instance); -#endif - seq.Run("World Fixed Object", - ctx => new WorldFixedObjectProcessor().Process(ctx) - ); - seq.Run(ReplaceObjectPluginPass.Instance); -#if MA_VRCSDK3_AVATARS - seq.Run(BlendshapeSyncAnimationPluginPass.Instance); - // seq.Run(GameObjectDelayDisablePass.Instance); - TODO, move back here -#endif - seq.Run(ConstraintConverterPass.Instance); - }); #if MA_VRCSDK3_AVATARS seq.Run(PhysbonesBlockerPluginPass.Instance); seq.Run("Fixup Expressions Menu", ctx => @@ -258,7 +259,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin { protected override void Execute(ndmf.BuildContext context) { - new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(MAContext(context)); + new BlendshapeSyncAnimationProcessor(context).OnPreprocessAvatar(); } } diff --git a/Editor/VisibleHeadAccessoryProcessor.cs b/Editor/VisibleHeadAccessoryProcessor.cs index 54bbcb1a..fb68d9a0 100644 --- a/Editor/VisibleHeadAccessoryProcessor.cs +++ b/Editor/VisibleHeadAccessoryProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using nadena.dev.modular_avatar.editor.ErrorReporting; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -164,33 +165,34 @@ namespace nadena.dev.modular_avatar.core.editor private void ProcessAnimations() { - var animdb = _context.AnimationDatabase; - var paths = _context.PathMappings; + var animdb = _context.PluginBuildContext.Extension(); + var paths = animdb.ObjectPathRemapper; Dictionary pathMappings = new Dictionary(); + HashSet clips = new(); foreach (var kvp in _boneShims) { - var orig = paths.GetObjectIdentifier(kvp.Key.gameObject); - var shim = paths.GetObjectIdentifier(kvp.Value.gameObject); + var orig = paths.GetVirtualPathForObject(kvp.Key.gameObject); + var shim = paths.GetVirtualPathForObject(kvp.Value.gameObject); pathMappings[orig] = shim; + + clips.UnionWith(animdb.AnimationIndex.GetClipsForObjectPath(orig)); } - animdb.ForeachClip(motion => + foreach (var clip in clips) { - if (!(motion.CurrentClip is AnimationClip clip)) return; - - var bindings = AnimationUtility.GetCurveBindings(clip); - foreach (var binding in bindings) + foreach (var binding in clip.GetFloatCurveBindings()) { - if (binding.type != typeof(Transform)) continue; - if (!pathMappings.TryGetValue(binding.path, out var newPath)) continue; - - var newBinding = binding; - newBinding.path = newPath; - AnimationUtility.SetEditorCurve(clip, newBinding, AnimationUtility.GetEditorCurve(clip, binding)); + if (binding.type == typeof(Transform) && pathMappings.TryGetValue(binding.path, out var newPath)) + { + clip.SetFloatCurve( + EditorCurveBinding.FloatCurve(newPath, typeof(Transform), binding.propertyName), + clip.GetFloatCurve(binding) + ); + } } - }); + } } private Transform CreateShim(Transform target) diff --git a/UnitTests~/ReplaceObject/ReplaceObjectTests.cs b/UnitTests~/ReplaceObject/ReplaceObjectTests.cs index 0f97e97d..5ea75b32 100644 --- a/UnitTests~/ReplaceObject/ReplaceObjectTests.cs +++ b/UnitTests~/ReplaceObject/ReplaceObjectTests.cs @@ -5,6 +5,7 @@ using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEngine; @@ -12,12 +13,12 @@ namespace modular_avatar_tests.ReplaceObject { public class ReplaceObjectTests : TestBase { - private PathMappings pathMappings; + private ObjectPathRemapper pathMappings; void Process(GameObject root) { var buildContext = new nadena.dev.ndmf.BuildContext(root, null); - pathMappings = buildContext.ActivateExtensionContext().PathMappings; + pathMappings = buildContext.ActivateExtensionContextRecursive().ObjectPathRemapper; new ReplaceObjectPass(buildContext).Process(); } @@ -162,7 +163,7 @@ namespace modular_avatar_tests.ReplaceObject Process(root); - Assert.AreEqual("replacement", pathMappings.MapPath("replacee")); + Assert.AreEqual("replacement", pathMappings.GetVirtualPathForObject(pathMappings.GetObjectForPath("replacee")!)); } } } \ No newline at end of file