From 567cad4eddaf78c2d407e700e41b573527332d9e Mon Sep 17 00:00:00 2001 From: bd_ Date: Sun, 15 Dec 2024 19:51:11 -0800 Subject: [PATCH] wip --- Editor/ActiveAnimationRetargeter.cs | 27 +- Editor/Animation/ReadablePropertyExtension.cs | 2 +- Editor/ApplyAnimatorDefaultValuesPass.cs | 33 +-- Editor/MergeAnimatorProcessor.cs | 279 ++++-------------- Editor/MergeArmatureHook.cs | 75 ++++- Editor/MergeBlendTreePass.cs | 163 +++++----- Editor/MeshRetargeter.cs | 12 +- Editor/PluginDefinition/PluginDefinition.cs | 26 +- .../AnimationGeneration/ReactiveObjectPass.cs | 9 +- .../ReactiveObjects/ParameterAssignerPass.cs | 23 +- Editor/RenameParametersHook.cs | 248 +++++++--------- .../ActiveAnimationRetargeterTests.cs | 16 +- .../MergeAnimatorTests/MergeSingleTests.cs | 19 +- .../PreexistingParamsTest.cs | 2 + .../MergeArmatureTests/MultiLevelMergeTest.cs | 7 +- .../RenameParametersTests.cs | 4 + UnitTests~/RetargetMeshesTest.cs | 11 +- .../VirtualMenuTests/VirtualMenuTests.cs | 4 + .../WorldFixedObjectTest.cs | 7 +- 19 files changed, 402 insertions(+), 565 deletions(-) diff --git a/Editor/ActiveAnimationRetargeter.cs b/Editor/ActiveAnimationRetargeter.cs index 8c515b16..5e1f96cf 100644 --- a/Editor/ActiveAnimationRetargeter.cs +++ b/Editor/ActiveAnimationRetargeter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using nadena.dev.modular_avatar.animation; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEngine; using EditorCurveBinding = UnityEditor.EditorCurveBinding; @@ -16,7 +17,7 @@ internal class ActiveAnimationRetargeter { private readonly BuildContext _context; private readonly BoneDatabase _boneDatabase; - private readonly PathMappings _pathMappings; + private readonly AnimatorServicesContext _asc; private readonly List _intermediateObjs = new List(); /// @@ -55,15 +56,15 @@ Transform root { _context = context; _boneDatabase = boneDatabase; - _pathMappings = context.PluginBuildContext.Extension().PathMappings; + _asc = context.PluginBuildContext.Extension(); while (root != null && !RuntimeUtil.IsAvatarRoot(root)) { var originalPath = RuntimeUtil.AvatarRootPath(root.gameObject); System.Diagnostics.Debug.Assert(originalPath != null); - if (context.AnimationDatabase.ClipsForPath(originalPath).Any(clip => - GetActiveBinding(clip.CurrentClip as AnimationClip, originalPath) != null + if (_asc.AnimationIndex.GetClipsForObjectPath(originalPath).Any(clip => + GetActiveBinding(clip, originalPath) != null )) { _intermediateObjs.Add(new IntermediateObj @@ -118,7 +119,6 @@ public GameObject CreateIntermediateObjects(GameObject sourceBone) // Ensure mesh retargeting looks through this _boneDatabase.AddMergedBone(sourceBone.transform); _boneDatabase.RetainMergedBone(sourceBone.transform); - _pathMappings.MarkTransformLookthrough(sourceBone); } return sourceBone; @@ -130,22 +130,14 @@ public void FixupAnimations() { var path = intermediate.OriginalPath; - foreach (var holder in _context.AnimationDatabase.ClipsForPath(path)) + foreach (var clip in _asc.AnimationIndex.GetClipsForObjectPath(path)) { - if (!_context.PluginBuildContext.IsTemporaryAsset(holder.CurrentClip)) - { - holder.CurrentClip = Object.Instantiate(holder.CurrentClip); - } - - var clip = holder.CurrentClip as AnimationClip; - if (clip == null) continue; - var curve = GetActiveBinding(clip, path); if (curve != null) { foreach (var mapping in intermediate.Created) { - clip.SetCurve(_pathMappings.GetObjectIdentifier(mapping), typeof(GameObject), "m_IsActive", + clip.SetFloatCurve(_asc.ObjectPathRemapper.GetVirtualPathForObject(mapping), typeof(GameObject), "m_IsActive", curve); } } @@ -153,10 +145,9 @@ public void FixupAnimations() } } - private AnimationCurve GetActiveBinding(AnimationClip clip, string path) + private AnimationCurve GetActiveBinding(VirtualClip clip, string path) { - return AnimationUtility.GetEditorCurve(clip, - EditorCurveBinding.FloatCurve(path, typeof(GameObject), "m_IsActive")); + return clip.GetFloatCurve(EditorCurveBinding.FloatCurve(path, typeof(GameObject), "m_IsActive")); } } } \ No newline at end of file diff --git a/Editor/Animation/ReadablePropertyExtension.cs b/Editor/Animation/ReadablePropertyExtension.cs index 8df3e333..a4807d8b 100644 --- a/Editor/Animation/ReadablePropertyExtension.cs +++ b/Editor/Animation/ReadablePropertyExtension.cs @@ -20,7 +20,7 @@ public class Retained } private AnimatorServicesContext? _asc; - private Retained _retained; + private Retained _retained = null!; private AnimatorServicesContext asc => _asc ?? throw new InvalidOperationException("ActiveSelfProxyExtension is not active"); diff --git a/Editor/ApplyAnimatorDefaultValuesPass.cs b/Editor/ApplyAnimatorDefaultValuesPass.cs index 0f9cb15e..84056413 100644 --- a/Editor/ApplyAnimatorDefaultValuesPass.cs +++ b/Editor/ApplyAnimatorDefaultValuesPass.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Linq; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using UnityEditor.Animations; using UnityEngine; @@ -21,40 +22,34 @@ protected override void Execute(ndmf.BuildContext context) var values = context.GetState()?.InitialValueOverrides ?? ImmutableDictionary.Empty; - foreach (var layer in context.AvatarDescriptor.baseAnimationLayers - .Concat(context.AvatarDescriptor.specialAnimationLayers)) - { - if (layer.isDefault || layer.animatorController == null) continue; - - // We should have converted anything that's not an AnimationController by now - var controller = layer.animatorController as AnimatorController; - if (controller == null || !context.IsTemporaryAsset(controller)) - { - throw new Exception("Leaked unexpected controller: " + layer.animatorController + " (type " + layer.animatorController?.GetType() + ")"); - } + var asc = context.Extension(); - var parameters = controller.parameters; - for (int i = 0; i < parameters.Length; i++) + foreach (var controller in asc.ControllerContext.GetAllControllers()) + { + var parameters = controller.Parameters; + foreach (var (name, parameter) in parameters) { - if (!values.TryGetValue(parameters[i].name, out var defaultValue)) continue; + if (!values.TryGetValue(name, out var defaultValue)) continue; - switch (parameters[i].type) + switch (parameter.type) { case AnimatorControllerParameterType.Bool: - parameters[i].defaultBool = defaultValue != 0.0f; + parameter.defaultBool = defaultValue != 0.0f; break; case AnimatorControllerParameterType.Int: - parameters[i].defaultInt = Mathf.RoundToInt(defaultValue); + parameter.defaultInt = Mathf.RoundToInt(defaultValue); break; case AnimatorControllerParameterType.Float: - parameters[i].defaultFloat = defaultValue; + parameter.defaultFloat = defaultValue; break; default: continue; // unhandled type, e.g. trigger } + + parameters = parameters.SetItem(name, parameter); } - controller.parameters = parameters; + controller.Parameters = parameters; } } } diff --git a/Editor/MergeAnimatorProcessor.cs b/Editor/MergeAnimatorProcessor.cs index aa4dbcbc..1002360f 100644 --- a/Editor/MergeAnimatorProcessor.cs +++ b/Editor/MergeAnimatorProcessor.cs @@ -24,52 +24,23 @@ #if MA_VRCSDK3_AVATARS -using System; using System.Collections.Generic; using System.Linq; -using nadena.dev.modular_avatar.animation; -using UnityEditor; -using UnityEditor.Animations; +using nadena.dev.ndmf.animator; using UnityEngine; using VRC.SDK3.Avatars.Components; -using VRC.SDKBase; using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { internal class MergeAnimatorProcessor { - private const string SAMPLE_PATH_PACKAGE = - "Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Animation/Controllers"; - - private const string SAMPLE_PATH_LEGACY = "Assets/VRCSDK/Examples3/Animation/Controllers"; - - private const string GUID_GESTURE_HANDSONLY_MASK = "b2b8bad9583e56a46a3e21795e96ad92"; - - private BuildContext _context; - - private Dictionary defaultControllers_ = - new Dictionary(); - - private Dictionary writeDefaults_ = - new Dictionary(); - - Dictionary mergeSessions = - new Dictionary(); + private AnimatorServicesContext _asc; internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context) { - _context = context; - - defaultControllers_.Clear(); - mergeSessions.Clear(); - - var descriptor = avatarGameObject.GetComponent(); - if (!descriptor) return; - - if (descriptor.baseAnimationLayers != null) InitSessions(descriptor.baseAnimationLayers); - if (descriptor.specialAnimationLayers != null) InitSessions(descriptor.specialAnimationLayers); - + _asc = context.PluginBuildContext.Extension(); + var toMerge = avatarGameObject.transform.GetComponentsInChildren(true); Dictionary> byLayerType = new Dictionary>(); @@ -89,10 +60,6 @@ internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext conte { ProcessLayerType(context, entry.Key, entry.Value); } - - descriptor.baseAnimationLayers = FinishSessions(descriptor.baseAnimationLayers); - descriptor.specialAnimationLayers = FinishSessions(descriptor.specialAnimationLayers); - descriptor.customizeAnimationLayers = true; } private void ProcessLayerType( @@ -109,34 +76,34 @@ List toMerge var afterOriginal = sorted.Where(x => x.layerPriority >= 0) .ToList(); - var session = new AnimatorCombiner(context.PluginBuildContext, layerType.ToString() + " (merged)"); - mergeSessions[layerType] = session; - mergeSessions[layerType].BlendableLayer = BlendableLayerFor(layerType); - - foreach (var component in beforeOriginal) - { - MergeSingle(context, session, component); - } - - if (defaultControllers_.TryGetValue(layerType, out var defaultController) && - defaultController.layers.Length > 0) - { - session.AddController("", defaultController, null, forceFirstLayerWeight: true); - } + var controller = _asc.ControllerContext[layerType]; + + var wdStateCounter = controller.Layers.SelectMany(l => l.StateMachine.AllStates()) + .Select(s => s.WriteDefaultValues) + .GroupBy(b => b) + .ToDictionary(g => g.Key, g => g.Count()); - foreach (var component in afterOriginal) + bool? writeDefaults = null; + if (wdStateCounter.Count == 1) writeDefaults = wdStateCounter.First().Key; + + foreach (var component in sorted) { - MergeSingle(context, session, component); + MergeSingle(context, controller, component, writeDefaults); } } - private void MergeSingle(BuildContext context, AnimatorCombiner session, ModularAvatarMergeAnimator merge) + private void MergeSingle(BuildContext context, VirtualAnimatorController controller, ModularAvatarMergeAnimator merge, bool? initialWriteDefaults) { if (merge.animator == null) { return; } + var stash = context.PluginBuildContext.GetState(); + + var clonedController = stash.Controllers.GetValueOrDefault(merge) + ?? _asc.ControllerContext.CloneContext.Clone(merge.animator); + string basePath; if (merge.pathMode == MergeAnimatorPathMode.Relative) { @@ -145,199 +112,59 @@ private void MergeSingle(BuildContext context, AnimatorCombiner session, Modular var relativePath = RuntimeUtil.RelativePath(context.AvatarRootObject, targetObject); basePath = relativePath != "" ? relativePath + "/" : ""; + + var animationIndex = new AnimationIndex(new[] { clonedController }); + animationIndex.RewritePaths(p => p == "" ? relativePath : basePath + p); } else { basePath = ""; } - var writeDefaults = merge.matchAvatarWriteDefaults - ? writeDefaults_.GetValueOrDefault(merge.layerType) - : null; - var controller = _context.ConvertAnimatorController(merge.animator); - session.AddController(basePath, controller, writeDefaults); - - if (merge.deleteAttachedAnimator) - { - var animator = merge.GetComponent(); - if (animator != null) Object.DestroyImmediate(animator); - } - } - - private VRCAvatarDescriptor.CustomAnimLayer[] FinishSessions( - VRCAvatarDescriptor.CustomAnimLayer[] layers - ) - { - layers = (VRCAvatarDescriptor.CustomAnimLayer[])layers.Clone(); - - // Ensure types are consistent across layers - Dictionary types = - new Dictionary(); - // Learn types... - foreach (var session in mergeSessions.Values) - { - session.MergeTypes(types); - } - // And propagate them - foreach (var session in mergeSessions.Values) + foreach (var l in clonedController.Layers) { - session.MergeTypes(types); - } - - for (int i = 0; i < layers.Length; i++) - { - if (mergeSessions.TryGetValue(layers[i].type, out var session)) + if (initialWriteDefaults != null) { - if (layers[i].type == VRCAvatarDescriptor.AnimLayerType.Gesture && layers[i].isDefault) + foreach (var s in l.StateMachine.AllStates()) { - // We need to set the mask field for the gesture layer on initial configuration - layers[i].mask = AssetDatabase.LoadAssetAtPath( - AssetDatabase.GUIDToAssetPath(GUID_GESTURE_HANDSONLY_MASK) - ); + s.WriteDefaultValues = initialWriteDefaults.Value; } - - layers[i].isDefault = false; - layers[i].animatorController = session.Finish(); - } - } - - return layers; - } - - private void InitSessions(VRCAvatarDescriptor.CustomAnimLayer[] layers) - { - foreach (var layer in layers) - { - var controller = ResolveLayerController(layer); - if (controller == null) controller = new AnimatorController(); - - defaultControllers_[layer.type] = controller; - writeDefaults_[layer.type] = ProbeWriteDefaults(controller); - if (!layer.isDefault) - { - // For non-default layers, ensure we always clone the controller for the benefit of subsequent - // processing phases - mergeSessions[layer.type] = - new AnimatorCombiner(_context.PluginBuildContext, layer.type.ToString()); - mergeSessions[layer.type].BlendableLayer = BlendableLayerFor(layer.type); - mergeSessions[layer.type].AddController("", controller, null); } + controller.AddLayer(new LayerPriority(merge.layerPriority), l); } - } - - private VRC_AnimatorLayerControl.BlendableLayer? BlendableLayerFor(VRCAvatarDescriptor.AnimLayerType layerType) - { - if (Enum.TryParse(layerType.ToString(), out VRC_AnimatorLayerControl.BlendableLayer result)) - { - return result; - } - else - { - return null; - } - } - - internal static bool? ProbeWriteDefaults(AnimatorController controller) - { - if (controller == null) return null; - - bool hasWDOn = false; - bool hasWDOff = false; - var stateMachineQueue = new Queue(); - foreach (var layer in controller.layers) + foreach (var (name, parameter) in clonedController.Parameters) { - // Special case: A layer with a single state, which contains a blend tree, is ignored for WD analysis. - // This is because WD ON blend trees have different behavior from most WD ON states, and can be safely - // used in a WD OFF animator. - - if (layer.stateMachine.states.Length == 1 - && layer.stateMachine.states[0].state.motion is BlendTree - && layer.stateMachine.stateMachines.Length == 0 - ) + if (controller.Parameters.TryGetValue(name, out var existingParam)) { + if (existingParam.type != parameter.type) + { + // Force to float + switch (parameter.type) + { + case AnimatorControllerParameterType.Bool: + existingParam.defaultFloat = existingParam.defaultBool ? 1.0f : 0.0f; + break; + case AnimatorControllerParameterType.Int: + existingParam.defaultFloat = existingParam.defaultInt; + break; + } + + existingParam.type = AnimatorControllerParameterType.Float; + + controller.Parameters = controller.Parameters.SetItem(name, existingParam); + } continue; } - stateMachineQueue.Enqueue(layer.stateMachine); + controller.Parameters = controller.Parameters.Add(name, parameter); } - - while (stateMachineQueue.Count > 0) - { - var stateMachine = stateMachineQueue.Dequeue(); - foreach (var state in stateMachine.states) - { - if (state.state.writeDefaultValues) hasWDOn = true; - else hasWDOff = true; - } - - foreach (var child in stateMachine.stateMachines) - { - stateMachineQueue.Enqueue(child.stateMachine); - } - } - - if (hasWDOn == hasWDOff) return null; - return hasWDOn; - } - - - private static AnimatorController ResolveLayerController(VRCAvatarDescriptor.CustomAnimLayer layer) - { - AnimatorController controller = null; - - if (!layer.isDefault && layer.animatorController != null && - layer.animatorController is AnimatorController c) - { - controller = c; - } - else + + if (merge.deleteAttachedAnimator) { - string name; - switch (layer.type) - { - case VRCAvatarDescriptor.AnimLayerType.Action: - name = "Action"; - break; - case VRCAvatarDescriptor.AnimLayerType.Additive: - name = "Idle"; - break; - case VRCAvatarDescriptor.AnimLayerType.Base: - name = "Locomotion"; - break; - case VRCAvatarDescriptor.AnimLayerType.Gesture: - name = "Hands"; - break; - case VRCAvatarDescriptor.AnimLayerType.Sitting: - name = "Sitting"; - break; - case VRCAvatarDescriptor.AnimLayerType.FX: - name = "Face"; - break; - case VRCAvatarDescriptor.AnimLayerType.TPose: - name = "UtilityTPose"; - break; - case VRCAvatarDescriptor.AnimLayerType.IKPose: - name = "UtilityIKPose"; - break; - default: - name = null; - break; - } - - if (name != null) - { - name = "/vrc_AvatarV3" + name + "Layer.controller"; - - controller = AssetDatabase.LoadAssetAtPath(SAMPLE_PATH_PACKAGE + name); - if (controller == null) - { - controller = AssetDatabase.LoadAssetAtPath(SAMPLE_PATH_LEGACY + name); - } - } + var animator = merge.GetComponent(); + if (animator != null) Object.DestroyImmediate(animator); } - - return controller; } } } diff --git a/Editor/MergeArmatureHook.cs b/Editor/MergeArmatureHook.cs index cd0d05cc..6d8bdcf3 100644 --- a/Editor/MergeArmatureHook.cs +++ b/Editor/MergeArmatureHook.cs @@ -31,8 +31,8 @@ using System; using System.Collections.Generic; using System.Linq; -using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.editor.ErrorReporting; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEngine; using UnityEngine.Animations; @@ -54,12 +54,13 @@ internal class #endif private BoneDatabase BoneDatabase = new BoneDatabase(); - private PathMappings PathMappings => frameworkContext.Extension() - .PathMappings; + private AnimatorServicesContext AnimatorServices => frameworkContext.Extension(); private HashSet humanoidBones = new HashSet(); private HashSet mergedObjects = new HashSet(); private HashSet thisPassAdded = new HashSet(); + + private HashSet transformLookthrough = new HashSet(); internal void OnPreprocessAvatar(ndmf.BuildContext context, GameObject avatarGameObject) { @@ -117,7 +118,68 @@ internal void OnPreprocessAvatar(ndmf.BuildContext context, GameObject avatarGam RetainBoneReferences(c as Component); } - new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, BoneDatabase, PathMappings); + new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, BoneDatabase, AnimatorServices); + + ProcessTransformLookthrough(); + } + + private void ProcessTransformLookthrough() + { + var asc = frameworkContext.Extension(); + + transformLookthrough.RemoveWhere(t => !t); + + var clipsToEdit = transformLookthrough.SelectMany( + xform => + { + var path = asc.ObjectPathRemapper.GetVirtualPathForObject(xform); + return asc.AnimationIndex.GetClipsForObjectPath(path); + }); + + Dictionary parentCache = new(); + + foreach (var clip in clipsToEdit) + { + foreach (var binding in clip.GetFloatCurveBindings()) + { + if (binding.type == typeof(Transform)) + { + var newPath = GetReplacementPath(binding.path); + + var newBinding = EditorCurveBinding.FloatCurve(newPath, binding.type, binding.propertyName); + clip.SetFloatCurve(newBinding, clip.GetFloatCurve(binding)); + clip.SetFloatCurve(binding, null); + } + } + } + + string GetReplacementPath(string bindingPath) + { + if (parentCache.TryGetValue(bindingPath, out var cached)) + { + return cached; + } + + var obj = asc.ObjectPathRemapper.GetObjectForPath(bindingPath)!.transform; + while (obj != null && transformLookthrough.Contains(obj)) + { + obj = obj.parent; + } + + string path; + if (obj == null) + { + path = bindingPath; + } + else + { + path = asc.ObjectPathRemapper.GetVirtualPathForObject(obj); + } + + parentCache[bindingPath] = path; + + return path; + } } private void TopoProcessMergeArmatures(ModularAvatarMergeArmature[] mergeArmatures) @@ -276,6 +338,7 @@ private void MergeArmature(ModularAvatarMergeArmature mergeArmature, GameObject _activeRetargeter.FixupAnimations(); thisPassAdded.UnionWith(_activeRetargeter.AddedGameObjects.Select(x => x.transform)); + transformLookthrough.UnionWith(_activeRetargeter.AddedGameObjects.Select(x => x.transform)); } /** @@ -339,7 +402,7 @@ private void RecursiveMerge(ModularAvatarMergeArmature config, BoneDatabase.AddMergedBone(mergedSrcBone.transform); BoneDatabase.RetainMergedBone(mergedSrcBone.transform); - PathMappings.MarkTransformLookthrough(mergedSrcBone); + transformLookthrough.Add(mergedSrcBone.transform); thisPassAdded.Add(mergedSrcBone.transform); } @@ -354,7 +417,7 @@ private void RecursiveMerge(ModularAvatarMergeArmature config, if (zipMerge) { - PathMappings.MarkTransformLookthrough(src); + transformLookthrough.Add(src.transform); BoneDatabase.AddMergedBone(src.transform); } diff --git a/Editor/MergeBlendTreePass.cs b/Editor/MergeBlendTreePass.cs index 89e5ffe5..cccd53f8 100644 --- a/Editor/MergeBlendTreePass.cs +++ b/Editor/MergeBlendTreePass.cs @@ -4,10 +4,13 @@ using System; using System.Collections.Generic; +using System.Linq; using nadena.dev.modular_avatar.animation; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using nadena.dev.ndmf.util; using UnityEditor.Animations; +using UnityEditor.Search; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -20,56 +23,49 @@ internal class MergeBlendTreePass : Pass internal const string ALWAYS_ONE = "__ModularAvatarInternal/One"; internal const string BlendTreeLayerName = "ModularAvatar: Merge Blend Tree"; - private AnimatorController _controller; - private BlendTree _rootBlendTree; - private GameObject _mergeHost; + private AnimatorServicesContext _asc; + private VirtualBlendTree _rootBlendTree; private HashSet _parameterNames; protected override void Execute(ndmf.BuildContext context) { + _asc = context.Extension(); _rootBlendTree = null; _parameterNames = new HashSet(); - _controller = new AnimatorController(); + var fx = _asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX]; + foreach (var component in context.AvatarRootObject.GetComponentsInChildren(true)) { ErrorReport.WithContextObject(component, () => ProcessComponent(context, component)); } - - List parameters = new List(_parameterNames.Count + 1); - if (_mergeHost != null) + + // always add the ALWAYS_ONE parameter + fx.Parameters = fx.Parameters.SetItem(ALWAYS_ONE, new AnimatorControllerParameter() { - _parameterNames.Remove(ALWAYS_ONE); + name = ALWAYS_ONE, + type = AnimatorControllerParameterType.Float, + defaultFloat = 1 + }); - parameters.Add(new AnimatorControllerParameter() + foreach (var name in _parameterNames) + { + if (fx.Parameters.ContainsKey(name)) continue; + + fx.Parameters = fx.Parameters.SetItem(name, new AnimatorControllerParameter() { - name = ALWAYS_ONE, + name = name, type = AnimatorControllerParameterType.Float, - defaultFloat = 1 + defaultFloat = 1.0f }); - - foreach (var name in _parameterNames) - { - parameters.Add(new AnimatorControllerParameter() - { - name = name, - type = AnimatorControllerParameterType.Float, - defaultFloat = 0 - }); - } - - var paramsAnimator = new AnimatorController(); - paramsAnimator.parameters = parameters.ToArray(); - - var paramsComponent = _mergeHost.AddComponent(); - paramsComponent.animator = paramsAnimator; - paramsComponent.layerPriority = Int32.MaxValue; } } - private void ProcessComponent(ndmf.BuildContext context, ModularAvatarMergeBlendTree component) + private void ProcessComponent(BuildContext context, ModularAvatarMergeBlendTree component) { + var stash = context.PluginBuildContext.GetState(); + BlendTree componentBlendTree = component.BlendTree as BlendTree; if (componentBlendTree == null) @@ -79,46 +75,60 @@ private void ProcessComponent(ndmf.BuildContext context, ModularAvatarMergeBlend } string basePath = null; + string rootPath = null; if (component.PathMode == MergeAnimatorPathMode.Relative) { var root = component.RelativePathRoot.Get(context.AvatarRootTransform); if (root == null) root = component.gameObject; - basePath = RuntimeUtil.AvatarRootPath(root) + "/"; + rootPath = RuntimeUtil.AvatarRootPath(root); + basePath = rootPath + "/"; + } + + var bt = stash.BlendTrees.GetValueOrDefault(component) + ?? _asc.ControllerContext.CloneContext.Clone(componentBlendTree); + + if (basePath != null) + { + var animationIndex = new AnimationIndex(new[] { bt }); + animationIndex.RewritePaths(p => p == "" ? rootPath : basePath + p); } - var bt = new DeepClone(context).DoClone(componentBlendTree, basePath); - var rootBlend = GetRootBlendTree(context); + var rootBlend = GetRootBlendTree(); - rootBlend.AddChild(bt); - var children = rootBlend.children; - children[children.Length - 1].directBlendParameter = ALWAYS_ONE; - rootBlend.children = children; - - foreach (var asset in bt.ReferencedAssets(includeScene: false)) + rootBlend.Children = rootBlend.Children.Add(new() { - if (asset is BlendTree bt2) + Motion = bt, + DirectBlendParameter = ALWAYS_ONE, + Threshold = 1, + CycleOffset = 1, + TimeScale = 1, + }); + + foreach (var asset in bt.AllReachableNodes()) + { + if (asset is VirtualBlendTree bt2) { - if (!string.IsNullOrEmpty(bt2.blendParameter) && bt2.blendType != BlendTreeType.Direct) + if (!string.IsNullOrEmpty(bt2.BlendParameter) && bt2.BlendType != BlendTreeType.Direct) { - _parameterNames.Add(bt2.blendParameter); + _parameterNames.Add(bt2.BlendParameter); } - if (bt2.blendType != BlendTreeType.Direct && bt2.blendType != BlendTreeType.Simple1D) + if (bt2.BlendType != BlendTreeType.Direct && bt2.BlendType != BlendTreeType.Simple1D) { - if (!string.IsNullOrEmpty(bt2.blendParameterY)) + if (!string.IsNullOrEmpty(bt2.BlendParameterY)) { - _parameterNames.Add(bt2.blendParameterY); + _parameterNames.Add(bt2.BlendParameterY); } } - if (bt2.blendType == BlendTreeType.Direct) + if (bt2.BlendType == BlendTreeType.Direct) { - foreach (var childMotion in bt2.children) + foreach (var childMotion in bt2.Children) { - if (!string.IsNullOrEmpty(childMotion.directBlendParameter)) + if (!string.IsNullOrEmpty(childMotion.DirectBlendParameter)) { - _parameterNames.Add(childMotion.directBlendParameter); + _parameterNames.Add(childMotion.DirectBlendParameter); } } } @@ -126,59 +136,22 @@ private void ProcessComponent(ndmf.BuildContext context, ModularAvatarMergeBlend } } - private BlendTree GetRootBlendTree(ndmf.BuildContext context) + private VirtualBlendTree GetRootBlendTree() { if (_rootBlendTree != null) return _rootBlendTree; - var newController = new AnimatorController(); - var newStateMachine = new AnimatorStateMachine(); - var newState = new AnimatorState(); - - _rootBlendTree = new BlendTree(); - _controller = newController; - - newController.layers = new[] - { - new AnimatorControllerLayer - { - blendingMode = AnimatorLayerBlendingMode.Override, - defaultWeight = 1, - name = BlendTreeLayerName, - stateMachine = newStateMachine - } - }; - - newStateMachine.name = "ModularAvatarMergeBlendTree"; - newStateMachine.states = new[] - { - new ChildAnimatorState - { - state = newState, - position = Vector3.zero - } - }; - newStateMachine.defaultState = newState; + var fx = _asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX]; + var controller = fx.AddLayer(new LayerPriority(int.MinValue), BlendTreeLayerName); + var stateMachine = controller.StateMachine; - newState.writeDefaultValues = true; - newState.motion = _rootBlendTree; - - _rootBlendTree.blendType = BlendTreeType.Direct; - _rootBlendTree.blendParameter = ALWAYS_ONE; + _rootBlendTree = VirtualBlendTree.Create("Root"); + var state = stateMachine.AddState("State", _rootBlendTree); + stateMachine.DefaultState = state; + state.WriteDefaultValues = true; - var mergeObject = new GameObject("ModularAvatarMergeBlendTree"); - var merger = mergeObject.AddComponent(); - merger.animator = newController; - merger.pathMode = MergeAnimatorPathMode.Absolute; - merger.matchAvatarWriteDefaults = false; - merger.layerType = VRCAvatarDescriptor.AnimLayerType.FX; - merger.deleteAttachedAnimator = false; - merger.layerPriority = Int32.MinValue; + _rootBlendTree.BlendType = BlendTreeType.Direct; + _rootBlendTree.BlendParameter = ALWAYS_ONE; - mergeObject.transform.SetParent(context.AvatarRootTransform, false); - mergeObject.transform.SetSiblingIndex(0); - - _mergeHost = mergeObject; - return _rootBlendTree; } } diff --git a/Editor/MeshRetargeter.cs b/Editor/MeshRetargeter.cs index 96e6cb91..3a1e34cc 100644 --- a/Editor/MeshRetargeter.cs +++ b/Editor/MeshRetargeter.cs @@ -27,6 +27,7 @@ using JetBrains.Annotations; using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.editor.ErrorReporting; +using nadena.dev.ndmf.animator; using UnityEngine; namespace nadena.dev.modular_avatar.core.editor @@ -84,13 +85,15 @@ public Transform GetRetargetedBone(Transform bone, bool fallbackToOriginal) internal class RetargetMeshes { private BoneDatabase _boneDatabase; - private PathMappings _pathTracker; + private AnimationIndex _animationIndex; + private ObjectPathRemapper _pathRemapper; internal void OnPreprocessAvatar(GameObject avatarGameObject, BoneDatabase boneDatabase, - PathMappings pathMappings) + AnimatorServicesContext pathMappings) { this._boneDatabase = boneDatabase; - this._pathTracker = pathMappings; + this._animationIndex = pathMappings.AnimationIndex; + this._pathRemapper = pathMappings.ObjectPathRemapper; foreach (var renderer in avatarGameObject.GetComponentsInChildren(true)) { @@ -153,7 +156,8 @@ internal void OnPreprocessAvatar(GameObject avatarGameObject, BoneDatabase boneD child.SetParent(destBone, true); } - _pathTracker.MarkRemoved(sourceBone.gameObject); + // Remap any animation clips that reference this bone into its parent + _pathRemapper.ReplaceObject(sourceBone.gameObject, sourceBone.transform.parent.gameObject); UnityEngine.Object.DestroyImmediate(sourceBone.gameObject); } } diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index 4637429b..0610ad01 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -48,17 +48,20 @@ protected override void Configure() seq.Run(ClearEditorOnlyTags.Instance); seq.Run(MeshSettingsPluginPass.Instance); seq.Run(ScaleAdjusterPass.Instance).PreviewingWith(new ScaleAdjusterPreview()); + + // All these need to move to the new ASC #if MA_VRCSDK3_AVATARS seq.Run(ReactiveObjectPrepass.Instance); - seq.Run(RenameParametersPluginPass.Instance); - seq.Run(ParameterAssignerPass.Instance); - seq.Run(MergeBlendTreePass.Instance); - seq.Run(MergeAnimatorPluginPass.Instance); - seq.Run(ApplyAnimatorDefaultValuesPass.Instance); #endif seq.WithRequiredExtension(typeof(AnimatorServicesContext), _s2 => { #if MA_VRCSDK3_AVATARS + seq.Run(RenameParametersPluginPass.Instance); + seq.Run(ParameterAssignerPass.Instance); + seq.Run(MergeBlendTreePass.Instance); + seq.Run(MergeAnimatorPluginPass.Instance); + seq.Run(ApplyAnimatorDefaultValuesPass.Instance); + seq.WithRequiredExtension(typeof(ReadablePropertyExtension), _s3 => { seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute()) @@ -66,19 +69,18 @@ protected override void Configure() new MaterialSetterPreview()); }); seq.Run(GameObjectDelayDisablePass.Instance); -#endif - }); - - seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 => - { -#if MA_VRCSDK3_AVATARS + // TODO: We currently run this above MergeArmaturePlugin, because Merge Armature might destroy // game objects which contain Menu Installers. It'd probably be better however to teach Merge Armature // to retain those objects? maybe? seq.Run(MenuInstallPluginPass.Instance); #endif - seq.Run(MergeArmaturePluginPass.Instance); + + }); + + seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 => + { seq.Run(BoneProxyPluginPass.Instance); #if MA_VRCSDK3_AVATARS seq.Run(VisibleHeadAccessoryPluginPass.Instance); diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs index 1c0f3fb2..96c6462d 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs @@ -39,7 +39,14 @@ internal void Execute() // Having a WD OFF layer after WD ON layers can break WD. We match the behavior of the existing states, // and if mixed, use WD ON to maximize compatibility. - _writeDefaults = MergeAnimatorProcessor.ProbeWriteDefaults(FindFxController().animatorController as AnimatorController) ?? true; + var asc = context.Extension(); + _writeDefaults = asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX]?.Layers.Any( + l => l.StateMachine.StateMachines.Any( + sm => sm.StateMachine.AllStates().Any( + s => s.WriteDefaultValues && s.Motion is not VirtualBlendTree + ) + ) + ) ?? true; var analysis = new ReactiveObjectAnalyzer(context).Analyze(context.AvatarRootObject); diff --git a/Editor/ReactiveObjects/ParameterAssignerPass.cs b/Editor/ReactiveObjects/ParameterAssignerPass.cs index f7512050..cb0b039f 100644 --- a/Editor/ReactiveObjects/ParameterAssignerPass.cs +++ b/Editor/ReactiveObjects/ParameterAssignerPass.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using UnityEditor.Animations; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -201,18 +202,20 @@ protected override void Execute(ndmf.BuildContext context) if (mamiWithRC.Count > 0) { - // This make sures the parameters are correctly merged into the FX layer. - var mergeAnimator = context.AvatarRootObject.AddComponent(); - mergeAnimator.layerType = VRCAvatarDescriptor.AnimLayerType.FX; - mergeAnimator.deleteAttachedAnimator = false; - mergeAnimator.animator = new AnimatorController + var asc = context.Extension(); + var fx = asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX]; + + foreach (var (name, _) in mamiWithRC) { - parameters = mamiWithRC.Select(kvp => new AnimatorControllerParameter + if (!fx.Parameters.ContainsKey(name)) { - name = kvp.Key, - type = AnimatorControllerParameterType.Float, - }).ToArray(), - }; + fx.Parameters = fx.Parameters.SetItem(name, new() + { + name = name, + type = AnimatorControllerParameterType.Float, + }); + } + } } } diff --git a/Editor/RenameParametersHook.cs b/Editor/RenameParametersHook.cs index afbf83ba..d48e6aa4 100644 --- a/Editor/RenameParametersHook.cs +++ b/Editor/RenameParametersHook.cs @@ -9,6 +9,7 @@ using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEditor.Animations; using UnityEngine; @@ -55,6 +56,43 @@ internal class DefaultValues { public ImmutableDictionary InitialValueOverrides; } + + internal class RenamedMergeAnimators + { + public AnimatorServicesContext AnimatorServices; + public Dictionary Controllers = new(); + public Dictionary BlendTrees = new(); + + public VirtualAnimatorController Clone(ModularAvatarMergeAnimator mama) + { + if (Controllers.TryGetValue(mama, out var controller)) + { + return controller; + } + + if (mama.animator == null) return null; + + var cloned = AnimatorServices.ControllerContext.CloneContext.Clone(mama.animator); + Controllers[mama] = cloned; + + return cloned; + } + + public VirtualBlendTree Clone(ModularAvatarMergeBlendTree mbt) + { + if (BlendTrees.TryGetValue(mbt, out var blendTree)) + { + return blendTree; + } + + if (mbt.BlendTree is not BlendTree bt) return null; + + var cloned = (VirtualBlendTree)AnimatorServices.ControllerContext.CloneContext.Clone(bt); + BlendTrees[mbt] = cloned; + + return cloned; + } + } internal class RenameParametersHook { @@ -163,6 +201,10 @@ public void OnPreprocessAvatar(GameObject avatar, BuildContext context) if (!context.AvatarDescriptor) return; _context = context; + + var stash = _context.PluginBuildContext.GetState(); + var asc = _context.PluginBuildContext.Extension(); + stash.AnimatorServices = asc; var syncParams = WalkTree(avatar); @@ -389,11 +431,13 @@ GameObject obj if (merger.animator != null) { - Profiler.BeginSample("DeepCloneAnimator"); - merger.animator = new DeepClone(_context.PluginBuildContext).DoClone(merger.animator); - Profiler.EndSample(); + var stash = _context.PluginBuildContext.GetState(); - ProcessRuntimeAnimatorController(merger.animator, remap); + var controller = stash.Clone(merger); + + ProcessVirtualAnimatorController(controller, remap); + + stash.Controllers[merger] = controller; } break; @@ -404,8 +448,12 @@ GameObject obj var bt = merger.BlendTree as BlendTree; if (bt != null) { - merger.BlendTree = bt = new DeepClone(_context.PluginBuildContext).DoClone(bt); - ProcessBlendtree(bt, paramInfo.GetParameterRemappingsAt(obj)); + var stash = _context.PluginBuildContext.GetState(); + + var virtualbt = stash.Clone(merger); + ProcessBlendtree(virtualbt, paramInfo.GetParameterRemappingsAt(obj)); + + stash.BlendTrees[merger] = virtualbt; } break; @@ -485,28 +533,6 @@ GameObject obj return rv; } - private void ProcessRuntimeAnimatorController(RuntimeAnimatorController controller, - ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remap) - { - if (controller is AnimatorController ac) - { - ProcessAnimator(ac, remap); - } - else if (controller is AnimatorOverrideController aoc) - { - var list = new List>(); - aoc.GetOverrides(list); - - for (var i = 0; i < list.Count; i++) - { - var kvp = list[i]; - if (kvp.Value != null) ProcessClip(kvp.Value, remap); - } - - ProcessRuntimeAnimatorController(aoc.runtimeAnimatorController, remap); - } - } - private void ProcessMenuInstaller(ModularAvatarMenuInstaller installer, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) { @@ -525,113 +551,57 @@ private void ProcessMenuInstaller(ModularAvatarMenuInstaller installer, }); } - private void ProcessAnimator(AnimatorController controller, - ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) + private void ProcessVirtualAnimatorController(VirtualAnimatorController controller, + ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remap) { - if (remaps.IsEmpty) return; - - var visited = new HashSet(); - var queue = new Queue(); - - - var parameters = controller.parameters; - for (int i = 0; i < parameters.Length; i++) + foreach (var node in controller.AllReachableNodes()) { - if (remaps.TryGetValue((ParameterNamespace.Animator, parameters[i].name), out var newName)) + switch (node) { - parameters[i].name = newName.ParameterName; + case VirtualState vs: ProcessState(vs, remap); break; + case VirtualTransition vt: ProcessTransition(vt, remap); break; + case VirtualClip vc: ProcessClip(vc, remap); break; + case VirtualBlendTree bt: ProcessBlendtree(bt, remap); break; } } - controller.parameters = parameters; - - foreach (var layer in controller.layers) - { - if (layer.stateMachine != null) - { - queue.Enqueue(layer.stateMachine); - } - } + var newParameters = controller.Parameters.Clear(); - Profiler.BeginSample("Walk animator graph"); - while (queue.Count > 0) + foreach (var (name, parameter) in controller.Parameters) { - var sm = queue.Dequeue(); - if (visited.Contains(sm)) continue; - visited.Add(sm); - - foreach (var behavior in sm.behaviours) - { - if (behavior is VRCAvatarParameterDriver driver) - { - ProcessDriver(driver, remaps); - } - } - - foreach (var t in sm.anyStateTransitions) - { - ProcessTransition(t, remaps); - } - - foreach (var t in sm.entryTransitions) - { - ProcessTransition(t, remaps); - } - - foreach (var sub in sm.stateMachines) + if (remap.TryGetValue((ParameterNamespace.Animator, name), out var newParam)) { - queue.Enqueue(sub.stateMachine); - - - foreach (var t in sm.GetStateMachineTransitions(sub.stateMachine)) - { - ProcessTransition(t, remaps); - } + newParameters = newParameters.Add(newParam.ParameterName, parameter); } - - foreach (var st in sm.states) + else { - ProcessState(st.state, remaps); + newParameters = newParameters.Add(name, parameter); } } - Profiler.EndSample(); + + controller.Parameters = newParameters; } - private void ProcessState(AnimatorState state, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) + private void ProcessState(VirtualState state, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) { - state.mirrorParameter = remap(remaps, state.mirrorParameter); - state.timeParameter = remap(remaps, state.timeParameter); - state.speedParameter = remap(remaps, state.speedParameter); - state.cycleOffsetParameter = remap(remaps, state.cycleOffsetParameter); + state.MirrorParameter = remap(remaps, state.MirrorParameter); + state.TimeParameter = remap(remaps, state.TimeParameter); + state.SpeedParameter = remap(remaps, state.SpeedParameter); + state.CycleOffsetParameter = remap(remaps, state.CycleOffsetParameter); - foreach (var t in state.transitions) - { - ProcessTransition(t, remaps); - } - - foreach (var behavior in state.behaviours) + foreach (var behavior in state.Behaviours) { if (behavior is VRCAvatarParameterDriver driver) { ProcessDriver(driver, remaps); } } - - ProcessMotion(state.motion, remaps); } - private void ProcessMotion(Motion motion, + private void ProcessClip(VirtualClip clip, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) { - if (motion is BlendTree blendTree) ProcessBlendtree(blendTree, remaps); - - if (motion is AnimationClip clip) ProcessClip(clip, remaps); - } - - private void ProcessClip(AnimationClip clip, - ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) - { - var curveBindings = AnimationUtility.GetCurveBindings(clip); + var curveBindings = clip.GetFloatCurveBindings(); var bindingsToUpdate = new List(); var newCurves = new List(); @@ -641,48 +611,30 @@ private void ProcessClip(AnimationClip clip, if (binding.path != "" || binding.type != typeof(Animator)) continue; if (remaps.TryGetValue((ParameterNamespace.Animator, binding.propertyName), out var newBinding)) { - var curCurve = AnimationUtility.GetEditorCurve(clip, binding); - - bindingsToUpdate.Add(binding); - newCurves.Add(null); - - bindingsToUpdate.Add(new EditorCurveBinding + var curCurve = clip.GetFloatCurve(binding); + var newECB = new EditorCurveBinding { path = "", type = typeof(Animator), propertyName = newBinding.ParameterName - }); - newCurves.Add(curCurve); + }; + + clip.SetFloatCurve(binding, null); + clip.SetFloatCurve(newECB, curCurve); } } - - if (bindingsToUpdate.Any()) - { - AnimationUtility.SetEditorCurves(clip, bindingsToUpdate.ToArray(), newCurves.ToArray()); - - // Workaround apparent unity bug where the clip's curves are not deleted - for (var i = 0; i < bindingsToUpdate.Count; i++) - if (newCurves[i] == null && AnimationUtility.GetEditorCurve(clip, bindingsToUpdate[i]) != null) - AnimationUtility.SetEditorCurve(clip, bindingsToUpdate[i], newCurves[i]); - } } - private void ProcessBlendtree(BlendTree blendTree, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) + private void ProcessBlendtree(VirtualBlendTree blendTree, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) { - blendTree.blendParameter = remap(remaps, blendTree.blendParameter); - blendTree.blendParameterY = remap(remaps, blendTree.blendParameterY); + blendTree.BlendParameter = remap(remaps, blendTree.BlendParameter); + blendTree.BlendParameterY = remap(remaps, blendTree.BlendParameterY); - var children = blendTree.children; - for (int i = 0; i < children.Length; i++) + var children = blendTree.Children; + foreach (var child in children) { - var childMotion = children[i]; - ProcessMotion(childMotion.motion, remaps); - - childMotion.directBlendParameter = remap(remaps, childMotion.directBlendParameter); - children[i] = childMotion; + child.DirectBlendParameter = remap(remaps, child.DirectBlendParameter); } - - blendTree.children = children; } private void ProcessDriver(VRCAvatarParameterDriver driver, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) @@ -698,19 +650,17 @@ private void ProcessDriver(VRCAvatarParameterDriver driver, ImmutableDictionary< } } - private void ProcessTransition(AnimatorTransitionBase t, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) + private void ProcessTransition(VirtualTransitionBase t, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) { bool dirty = false; - var conditions = t.conditions; - - for (int i = 0; i < conditions.Length; i++) - { - var cond = conditions[i]; - cond.parameter = remap(remaps, cond.parameter, ref dirty); - conditions[i] = cond; - } - - if (dirty) t.conditions = conditions; + var conditions = t.Conditions + .Select(cond => + { + cond.parameter = remap(remaps, cond.parameter, ref dirty); + return cond; + }) + .ToImmutableList(); + t.Conditions = conditions; } private ImmutableDictionary CollectParameters(ModularAvatarParameters p, diff --git a/UnitTests~/ActiveAnimationRetargeterTests/ActiveAnimationRetargeterTests.cs b/UnitTests~/ActiveAnimationRetargeterTests/ActiveAnimationRetargeterTests.cs index e689cffd..ffa29e98 100644 --- a/UnitTests~/ActiveAnimationRetargeterTests/ActiveAnimationRetargeterTests.cs +++ b/UnitTests~/ActiveAnimationRetargeterTests/ActiveAnimationRetargeterTests.cs @@ -1,11 +1,14 @@ #if MA_VRCSDK3_AVATARS +using System.Linq; using modular_avatar_tests; using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEditor; using UnityEngine; +using VRC.SDK3.Avatars.Components; using EditorCurveBinding = UnityEditor.EditorCurveBinding; public class ActiveAnimationRetargeterTests : TestBase @@ -17,8 +20,7 @@ public void SimpleRetarget() // initialize context var buildContext = new BuildContext(avatar); - var pathMappings = buildContext.PluginBuildContext.ActivateExtensionContext() - .PathMappings; + var asc = buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); // get game objects var changedChild = avatar.transform.Find("Toggled/Child"); @@ -29,18 +31,16 @@ public void SimpleRetarget() var created = retargeter.CreateIntermediateObjects(newParent.gameObject); retargeter.FixupAnimations(); - // commit - buildContext.AnimationDatabase.Commit(); - - var clip = findFxClip(avatar, layerName: "retarget"); - var curveBindings = AnimationUtility.GetCurveBindings(clip); + var fx = asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX]!; + var clip = (VirtualClip) fx.Layers.First(l => l.Name == "retarget").StateMachine.DefaultState!.Motion; + var curveBindings = clip!.GetFloatCurveBindings(); // Intermediate object must be created Assert.That(created, Is.Not.EqualTo(newParent.gameObject)); // The created animation must have m_IsActive of intermediate object Assert.That(curveBindings, Does.Contain(EditorCurveBinding.FloatCurve( - pathMappings.GetObjectIdentifier(created), typeof(GameObject), "m_IsActive"))); + asc.ObjectPathRemapper.GetVirtualPathForObject(created), typeof(GameObject), "m_IsActive"))); } } diff --git a/UnitTests~/MergeAnimatorTests/MergeSingleTests.cs b/UnitTests~/MergeAnimatorTests/MergeSingleTests.cs index 2da5afbf..71c5bb8e 100644 --- a/UnitTests~/MergeAnimatorTests/MergeSingleTests.cs +++ b/UnitTests~/MergeAnimatorTests/MergeSingleTests.cs @@ -5,6 +5,7 @@ using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEngine; using BuildContext = nadena.dev.ndmf.BuildContext; @@ -22,9 +23,13 @@ public void NoErrorWhenAnimatorIsNull() var ctx = new BuildContext(av, null); ctx.ActivateExtensionContext(); - ctx.ActivateExtensionContext(); + ctx.ActivateExtensionContextRecursive(); - var errors = ErrorReport.CaptureErrors(() => new MergeAnimatorProcessor().OnPreprocessAvatar(av, ctx)); + var errors = ErrorReport.CaptureErrors(() => + { + new MergeAnimatorProcessor().OnPreprocessAvatar(av, ctx); + ctx.DeactivateAllExtensionContexts(); + }); Assert.IsEmpty(errors); } @@ -39,9 +44,15 @@ public void MergeAnimationOverrideController() var ctx = new BuildContext(av, null); ctx.ActivateExtensionContext(); - ctx.ActivateExtensionContext(); + ctx.ActivateExtensionContextRecursive(); - var errors = ErrorReport.CaptureErrors(() => new MergeAnimatorProcessor().OnPreprocessAvatar(av, ctx)); + var errors = ErrorReport.CaptureErrors(() => + { + new MergeAnimatorProcessor().OnPreprocessAvatar(av, ctx); + ctx.DeactivateAllExtensionContexts(); + }); + + ctx.DeactivateAllExtensionContexts(); Assert.IsEmpty(errors); diff --git a/UnitTests~/MergeAnimatorTests/PreexistingAnimatorParams/PreexistingParamsTest.cs b/UnitTests~/MergeAnimatorTests/PreexistingAnimatorParams/PreexistingParamsTest.cs index 3f1739d7..7c4bce15 100644 --- a/UnitTests~/MergeAnimatorTests/PreexistingAnimatorParams/PreexistingParamsTest.cs +++ b/UnitTests~/MergeAnimatorTests/PreexistingAnimatorParams/PreexistingParamsTest.cs @@ -25,6 +25,8 @@ public void TestPreexistingParameterOverwritePolicy() foreach (var kvp in paramDict) { + if (kvp.Key.StartsWith("__ModularAvatarInternal/")) continue; + if (kvp.Key == "default_override" || kvp.Key == "animator_only") { Assert.AreEqual(1, kvp.Value); diff --git a/UnitTests~/MergeArmatureTests/MultiLevelMergeTest.cs b/UnitTests~/MergeArmatureTests/MultiLevelMergeTest.cs index dc65c5d1..3020d64e 100644 --- a/UnitTests~/MergeArmatureTests/MultiLevelMergeTest.cs +++ b/UnitTests~/MergeArmatureTests/MultiLevelMergeTest.cs @@ -2,6 +2,7 @@ using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEngine; @@ -54,7 +55,7 @@ public void mergeProcessesInTopoOrder() nadena.dev.ndmf.BuildContext context = new nadena.dev.ndmf.BuildContext(root, null); context.ActivateExtensionContext(); - context.ActivateExtensionContext(); + context.ActivateExtensionContextRecursive(); new MergeArmatureHook().OnPreprocessAvatar(context, root); Assert.IsTrue(bone.GetComponentInChildren() != null); @@ -82,7 +83,7 @@ public void canDisableNameMangling() nadena.dev.ndmf.BuildContext context = new nadena.dev.ndmf.BuildContext(root, null); context.ActivateExtensionContext(); - context.ActivateExtensionContext(); + context.ActivateExtensionContextRecursive(); new MergeArmatureHook().OnPreprocessAvatar(context, root); Assert.IsTrue(m_bone == null); // destroyed by retargeting pass @@ -106,7 +107,7 @@ public void manglesByDefault() nadena.dev.ndmf.BuildContext context = new nadena.dev.ndmf.BuildContext(root, null); context.ActivateExtensionContext(); - context.ActivateExtensionContext(); + context.ActivateExtensionContextRecursive(); new MergeArmatureHook().OnPreprocessAvatar(context, root); Assert.IsTrue(m_bone == null); // destroyed by retargeting pass diff --git a/UnitTests~/RenameParametersTests/RenameParametersTests.cs b/UnitTests~/RenameParametersTests/RenameParametersTests.cs index 8942b5e4..8fb914e0 100644 --- a/UnitTests~/RenameParametersTests/RenameParametersTests.cs +++ b/UnitTests~/RenameParametersTests/RenameParametersTests.cs @@ -7,6 +7,7 @@ using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEditor.Animations; using UnityEngine; @@ -79,6 +80,7 @@ public void TestParameterConflicts() var context = CreateContext(prefab); var maContext = context.ActivateExtensionContext().BuildContext; + context.ActivateExtensionContextRecursive(); var errors = ErrorReport.CaptureErrors( () => @@ -209,6 +211,7 @@ public void TestMultipleRemappings() var context = CreateContext(av); var maContext = context.ActivateExtensionContext().BuildContext; + context.ActivateExtensionContextRecursive(); var errors = ErrorReport.CaptureErrors(() => new RenameParametersHook().OnPreprocessAvatar(av, maContext)); @@ -243,6 +246,7 @@ public void TestMultipleRemappings_WithConflict() var context = CreateContext(av); var maContext = context.ActivateExtensionContext().BuildContext; + context.ActivateExtensionContextRecursive(); var errors = ErrorReport.CaptureErrors(() => new RenameParametersHook().OnPreprocessAvatar(av, maContext)); diff --git a/UnitTests~/RetargetMeshesTest.cs b/UnitTests~/RetargetMeshesTest.cs index b3fc1d57..38c31573 100644 --- a/UnitTests~/RetargetMeshesTest.cs +++ b/UnitTests~/RetargetMeshesTest.cs @@ -1,5 +1,6 @@ using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEngine; @@ -21,13 +22,12 @@ public void RootBoneOnly() Debug.Assert(skinnedMeshRenderer.bones.Length == 0); var build_context = new nadena.dev.ndmf.BuildContext(root, null); - var torc = new AnimationServicesContext(); - torc.OnActivate(build_context); + var asc = build_context.ActivateExtensionContextRecursive(); var bonedb = new BoneDatabase(); bonedb.AddMergedBone(b.transform); - new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc.PathMappings); + new RetargetMeshes().OnPreprocessAvatar(root, bonedb, asc); Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone); } @@ -47,13 +47,12 @@ public void NoMeshRootBoneOnly() Debug.Assert(skinnedMeshRenderer.bones.Length == 0); var build_context = new nadena.dev.ndmf.BuildContext(root, null); - var torc = new AnimationServicesContext(); - torc.OnActivate(build_context); + var asc = build_context.ActivateExtensionContextRecursive(); var bonedb = new BoneDatabase(); bonedb.AddMergedBone(b.transform); - new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc.PathMappings); + new RetargetMeshes().OnPreprocessAvatar(root, bonedb, asc); Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone); Assert.AreEqual(new Bounds(new Vector3(0, 0, 0), new Vector3(2, 2, 2)), diff --git a/UnitTests~/VirtualMenuTests/VirtualMenuTests.cs b/UnitTests~/VirtualMenuTests/VirtualMenuTests.cs index 5450dba0..fa7d46c3 100644 --- a/UnitTests~/VirtualMenuTests/VirtualMenuTests.cs +++ b/UnitTests~/VirtualMenuTests/VirtualMenuTests.cs @@ -7,6 +7,7 @@ using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor.menu; using nadena.dev.modular_avatar.core.menu; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEditor; using UnityEngine; @@ -644,6 +645,7 @@ public void remapParams_isAppliedSeparatelyForEachDedup() }; var buildContext = new BuildContext(av_root.GetComponent()); + buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new RenameParametersHook().OnPreprocessAvatar(av_root, buildContext); var virtualMenu = VirtualMenu.ForAvatar(av_root.GetComponent(), buildContext); @@ -663,6 +665,7 @@ public void internalParameterTest() var root = CreatePrefab("InternalParameterTest.prefab"); BuildContext buildContext = new BuildContext(root.GetComponent()); + buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new RenameParametersHook().OnPreprocessAvatar(root, buildContext); var virtualMenu = VirtualMenu.ForAvatar(root.GetComponent(), buildContext); @@ -676,6 +679,7 @@ public void UnusedSubParametersAreStripped() var root = CreatePrefab("UnusedSubParametersAreStripped.prefab"); BuildContext buildContext = new BuildContext(root.GetComponent()); + buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new RenameParametersHook().OnPreprocessAvatar(root, buildContext); var virtualMenu = VirtualMenu.ForAvatar(root.GetComponent(), buildContext); diff --git a/UnitTests~/WorldFixedObjectTest/WorldFixedObjectTest.cs b/UnitTests~/WorldFixedObjectTest/WorldFixedObjectTest.cs index ba838bda..89d59df1 100644 --- a/UnitTests~/WorldFixedObjectTest/WorldFixedObjectTest.cs +++ b/UnitTests~/WorldFixedObjectTest/WorldFixedObjectTest.cs @@ -2,6 +2,7 @@ using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEngine.Animations; @@ -16,7 +17,7 @@ public void SimpleTest() // initialize context var buildContext = new BuildContext(avatar); - buildContext.PluginBuildContext.ActivateExtensionContext(); + buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new WorldFixedObjectProcessor().Process(buildContext); @@ -42,7 +43,7 @@ public void NestedTest() // initialize context var buildContext = new BuildContext(avatar); - buildContext.PluginBuildContext.ActivateExtensionContext(); + buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new WorldFixedObjectProcessor().Process(buildContext); @@ -75,7 +76,7 @@ public void NameCollisions() // initialize context var buildContext = new BuildContext(avatar); - var animationServices = buildContext.PluginBuildContext.ActivateExtensionContext(); + var animationServices = buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new WorldFixedObjectProcessor().Process(buildContext);