From 59cc94efa3032d48485c126401a25d410780d952 Mon Sep 17 00:00:00 2001 From: bd_ Date: Sat, 15 Feb 2025 19:16:40 -0800 Subject: [PATCH] finish cutover to NDMF anim-api --- Editor/Animation/AnimationDatabase.cs | 430 ------------ Editor/Animation/AnimationDatabase.cs.meta | 3 - Editor/Animation/AnimationServicesContext.cs | 82 --- .../AnimationServicesContext.cs.meta | 3 - Editor/Animation/AnimationUtil.cs | 224 ------- Editor/Animation/AnimationUtil.cs.meta | 3 - Editor/Animation/AnimatorCombiner.cs | 625 ------------------ Editor/Animation/AnimatorCombiner.cs.meta | 3 - Editor/Animation/DeepClone.cs | 296 --------- Editor/Animation/DeepClone.cs.meta | 3 - .../Animation/EditorCurveBindingComparer.cs | 18 - .../EditorCurveBindingComparer.cs.meta | 3 - Editor/Animation/IOnCommitObjectRenames.cs | 17 - .../Animation/IOnCommitObjectRenames.cs.meta | 3 - Editor/Animation/PathMappings.cs | 418 ------------ Editor/Animation/PathMappings.cs.meta | 3 - Editor/BuildContext.cs | 66 -- Editor/PluginDefinition/PluginDefinition.cs | 2 - Editor/RenameParametersHook.cs | 6 - Editor/ReplaceObjectPass.cs | 7 +- .../AnimationDatabaseCloningTest.cs | 58 -- .../AnimationDatabaseCloningTest.cs.meta | 11 - UnitTests~/Animation/MiscAnimationTests.cs | 6 +- .../TrackObjectRenamesContextTests.cs | 152 ----- .../TrackObjectRenamesContextTests.cs.meta | 3 - 25 files changed, 8 insertions(+), 2437 deletions(-) delete mode 100644 Editor/Animation/AnimationDatabase.cs delete mode 100644 Editor/Animation/AnimationDatabase.cs.meta delete mode 100644 Editor/Animation/AnimationServicesContext.cs delete mode 100644 Editor/Animation/AnimationServicesContext.cs.meta delete mode 100644 Editor/Animation/AnimationUtil.cs delete mode 100644 Editor/Animation/AnimationUtil.cs.meta delete mode 100644 Editor/Animation/AnimatorCombiner.cs delete mode 100644 Editor/Animation/AnimatorCombiner.cs.meta delete mode 100644 Editor/Animation/DeepClone.cs delete mode 100644 Editor/Animation/DeepClone.cs.meta delete mode 100644 Editor/Animation/EditorCurveBindingComparer.cs delete mode 100644 Editor/Animation/EditorCurveBindingComparer.cs.meta delete mode 100644 Editor/Animation/IOnCommitObjectRenames.cs delete mode 100644 Editor/Animation/IOnCommitObjectRenames.cs.meta delete mode 100644 Editor/Animation/PathMappings.cs delete mode 100644 Editor/Animation/PathMappings.cs.meta delete mode 100644 UnitTests~/Animation/AnimationDatabaseCloning/AnimationDatabaseCloningTest.cs delete mode 100644 UnitTests~/Animation/AnimationDatabaseCloning/AnimationDatabaseCloningTest.cs.meta delete mode 100644 UnitTests~/Animation/TrackObjectRenamesContextTests.cs delete mode 100644 UnitTests~/Animation/TrackObjectRenamesContextTests.cs.meta diff --git a/Editor/Animation/AnimationDatabase.cs b/Editor/Animation/AnimationDatabase.cs deleted file mode 100644 index d34ddff8..00000000 --- a/Editor/Animation/AnimationDatabase.cs +++ /dev/null @@ -1,430 +0,0 @@ -#region - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using nadena.dev.modular_avatar.core.editor; -using nadena.dev.modular_avatar.editor.ErrorReporting; -using nadena.dev.ndmf; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; -using UnityEngine.Profiling; -using BuildContext = nadena.dev.ndmf.BuildContext; -#if MA_VRCSDK3_AVATARS -using VRC.SDK3.Avatars.Components; -#endif - -#endregion - -namespace nadena.dev.modular_avatar.animation -{ - /// - /// The animation database records the set of all clips which are used in the avatar, and which paths they - /// manipulate. - /// - internal class AnimationDatabase - { - internal class ClipHolder - { - private readonly AnimationDatabase ParentDatabase; - - private Motion _currentClip; - - internal Motion CurrentClip - { - get - { - ParentDatabase.InvalidateCaches(); - return _currentClip; - } - set - { - ParentDatabase.InvalidateCaches(); - _currentClip = value; - } - } - - private Motion _originalClip; - - internal Motion OriginalClip - { - get => _originalClip; - set - { - _originalClip = value; - - var baseClip = ObjectRegistry.GetReference(value)?.Object as AnimationClip; - - IsProxyAnimation = false; - if (value != null && Util.IsProxyAnimation(value)) - { - IsProxyAnimation = true; - } - else if (baseClip != null && Util.IsProxyAnimation(baseClip)) - { - // RenameParametersPass replaces proxy clips outside of the purview of the animation database, - // so trace this using ObjectRegistry and correct the reference. - IsProxyAnimation = true; - _originalClip = baseClip; - } - } - } - - internal bool IsProxyAnimation { private set; get; } - - internal ClipHolder(AnimationDatabase parentDatabase, Motion clip) - { - ParentDatabase = parentDatabase; - CurrentClip = OriginalClip = clip; - } - - /// - /// Returns the current clip without invalidating caches. Do not modify this clip without taking extra - /// steps to invalidate caches on the AnimationDatabase. - /// - /// - internal Motion GetCurrentClipUnsafe() - { - return _currentClip; - } - - public void SetCurrentNoInvalidate(Motion newMotion) - { - _currentClip = newMotion; - } - } - - private BuildContext _context; - - private List _clipCommitActions = new List(); - private List _clips = new List(); -#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER - private HashSet _playAudios = new HashSet(); -#endif - - private Dictionary> _pathToClip = null; - - internal AnimationDatabase() - { - Debug.Log("Creating animation database"); - } - - internal void Commit() - { - Profiler.BeginSample("AnimationDatabase.Commit"); - foreach (var clip in _clips) - { - if (clip.IsProxyAnimation) clip.CurrentClip = clip.OriginalClip; - } - - Profiler.BeginSample("UpdateClipProperties"); - foreach (var clip in _clips) - { - // Changing the "high quality curve" setting can result in behavior changes (but can happen accidentally - // as we manipulate curves) - if (clip.CurrentClip != clip.OriginalClip && clip.CurrentClip != null && clip.OriginalClip != null) - { - SerializedObject before = new SerializedObject(clip.OriginalClip); - SerializedObject after = new SerializedObject(clip.CurrentClip); - - var before_prop = before.FindProperty("m_UseHighQualityCurve"); - var after_prop = after.FindProperty("m_UseHighQualityCurve"); - - if (after_prop.boolValue != before_prop.boolValue) - { - after_prop.boolValue = before_prop.boolValue; - after.ApplyModifiedPropertiesWithoutUndo(); - } - } - } - Profiler.EndSample(); - - Profiler.BeginSample("ClipCommitActions"); - foreach (var action in _clipCommitActions) - { - action(); - } - Profiler.EndSample(); - - Profiler.EndSample(); - } - - internal void OnActivate(BuildContext context) - { - _context = context; - - AnimationUtil.CloneAllControllers(context); - -#if MA_VRCSDK3_AVATARS - var avatarDescriptor = context.AvatarDescriptor; - if (!avatarDescriptor) return; - - foreach (var layer in avatarDescriptor.baseAnimationLayers) - { - BootstrapLayer(layer); - } - - foreach (var layer in avatarDescriptor.specialAnimationLayers) - { - BootstrapLayer(layer); - } - - void BootstrapLayer(VRCAvatarDescriptor.CustomAnimLayer layer) - { - if (!layer.isDefault && layer.animatorController is AnimatorController ac && - context.IsTemporaryAsset(ac)) - { - BuildReport.ReportingObject(ac, () => - { - foreach (var state in Util.States(ac)) - { - RegisterState(state); - } - }); - } - } -#endif - } - - /// - /// Registers a motion and all its reachable submotions with the animation database. The processClip callback, - /// if provided, will be invoked for each newly discovered clip. - /// - /// - /// - /// - internal void RegisterState(AnimatorState state, Action processClip = null) - { - Dictionary _originalToHolder = new Dictionary(); - - if (processClip == null) processClip = (_) => { }; - -#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER - foreach (var behavior in state.behaviours) - { - if (behavior is VRCAnimatorPlayAudio playAudio) - { - _playAudios.Add(playAudio); - } - } -#endif - - if (state.motion == null) return; - - var clipHolder = RegisterMotion(state.motion, state, processClip, _originalToHolder); - state.motion = clipHolder.CurrentClip; - - _clipCommitActions.Add(() => - { - state.motion = clipHolder.CurrentClip; - MaybeSaveClip(clipHolder.CurrentClip); - }); - } - - internal void ForeachClip(Action processClip) - { - foreach (var clipHolder in _clips) - { - processClip(clipHolder); - } - } - -#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER - internal void ForeachPlayAudio(Action processPlayAudio) - { - foreach (var playAudioHolder in _playAudios) - { - processPlayAudio(playAudioHolder); - } - } -#endif - - /// - /// Returns a list of clips which touched the given _original_ path. This path is subject to basepath remapping, - /// but not object movement remapping. - /// - /// - /// - internal ImmutableArray ClipsForPath(string path) - { - HydrateCaches(); - - if (_pathToClip.TryGetValue(path, out var clips)) - { - return clips.ToImmutableArray(); - } - else - { - return ImmutableArray.Empty; - } - } - - private ClipHolder RegisterMotion( - Motion motion, - AnimatorState state, - Action processClip, - Dictionary originalToHolder - ) - { - if (motion == null) - { - return new ClipHolder(this, null); - } - - if (originalToHolder.TryGetValue(motion, out var holder)) - { - return holder; - } - - InvalidateCaches(); - - Motion cloned = motion; - if (!_context.IsTemporaryAsset(motion)) - { - // Protect the original animations from mutations by creating temporary clones; in the case of a proxy - // animation, we'll restore the original in a later pass - // cloned = Object.Instantiate(motion); - Object.Instantiate can't be used on AnimationClips and BlendTrees - - cloned = (Motion)motion.GetType().GetConstructor(new Type[0]).Invoke(new object[0]); - EditorUtility.CopySerialized(motion, cloned); - - ObjectRegistry.RegisterReplacedObject(motion, cloned); - } - - switch (cloned) - { - case AnimationClip clip: - { - holder = new ClipHolder(this, clip); - processClip(holder); - _clips.Add(holder); - break; - } - case BlendTree tree: - { - holder = RegisterBlendtree(tree, state, processClip, originalToHolder); - break; - } - } - - holder.OriginalClip = motion; - - originalToHolder[motion] = holder; - return holder; - } - - private void InvalidateCaches() - { - _pathToClip = null; - } - - private void HydrateCaches() - { - if (_pathToClip == null) - { - _pathToClip = new Dictionary>(); - foreach (var clip in _clips) - { - RecordPaths(clip); - } - } - } - - private void RecordPaths(ClipHolder holder) - { - var clip = holder.GetCurrentClipUnsafe() as AnimationClip; - - foreach (var binding in AnimationUtility.GetCurveBindings(clip)) - { - var path = binding.path; - AddPath(path); - } - - foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) - { - var path = binding.path; - AddPath(path); - } - - void AddPath(string p0) - { - if (!_pathToClip.TryGetValue(p0, out var clips)) - { - clips = new HashSet(); - _pathToClip[p0] = clips; - } - - clips.Add(holder); - } - } - - private ClipHolder RegisterBlendtree( - BlendTree tree, - AnimatorState state, - Action processClip, - Dictionary originalToHolder - ) - { - if (!_context.IsTemporaryAsset(tree)) - { - throw new Exception("Blendtree must be a temporary asset"); - } - - var treeHolder = new ClipHolder(this, tree); - - var children = tree.children; - var holders = new ClipHolder[children.Length]; - - for (int i = 0; i < children.Length; i++) - { - holders[i] = RegisterMotion(children[i].motion, state, processClip, originalToHolder); - children[i].motion = holders[i].CurrentClip; - } - - tree.children = children; - - _clipCommitActions.Add(() => - { - var dirty = false; - for (int i = 0; i < children.Length; i++) - { - var curClip = holders[i].CurrentClip; - if (children[i].motion != curClip) - { - children[i].motion = curClip; - dirty = true; - } - - MaybeSaveClip(curClip); - } - - if (dirty) - { - tree.children = children; - EditorUtility.SetDirty(tree); - } - }); - - return treeHolder; - } - - private void MaybeSaveClip(Motion curClip) - { - Profiler.BeginSample("MaybeSaveClip"); - if (curClip != null && !EditorUtility.IsPersistent(curClip) && EditorUtility.IsPersistent(_context.AssetContainer) && _context.AssetContainer != null) - { - try - { - _context.AssetSaver.SaveAsset(curClip); - } - catch (Exception e) - { - Debug.LogException(e); - throw; - } - } - Profiler.EndSample(); - } - } -} \ No newline at end of file diff --git a/Editor/Animation/AnimationDatabase.cs.meta b/Editor/Animation/AnimationDatabase.cs.meta deleted file mode 100644 index 05c197ba..00000000 --- a/Editor/Animation/AnimationDatabase.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 11130986120e452b8dc8db0d19aa71fc -timeCreated: 1671624207 \ No newline at end of file diff --git a/Editor/Animation/AnimationServicesContext.cs b/Editor/Animation/AnimationServicesContext.cs deleted file mode 100644 index 642f7573..00000000 --- a/Editor/Animation/AnimationServicesContext.cs +++ /dev/null @@ -1,82 +0,0 @@ -#region - -using System; -using System.Collections.Generic; -using nadena.dev.ndmf; -using UnityEngine; -#if MA_VRCSDK3_AVATARS -#endif - -#endregion - -namespace nadena.dev.modular_avatar.animation -{ - /// - /// This extension context amortizes a number of animation-related processing steps - notably, - /// collecting the set of all animation clips from the animators, and committing changes to them - /// in a deferred manner. - /// - /// Restrictions: While this context is active, any changes to clips must be done by editing them via - /// ClipHolders in the AnimationDatabase. Any newly added clips must be registered in the AnimationDatabase, - /// and any new references to clips require setting appropriate ClipCommitActions. - /// - /// New references to objects created in clips must use paths obtained from the - /// ObjectRenameTracker.GetObjectIdentifier method. - /// - internal sealed class AnimationServicesContext : IExtensionContext - { - private BuildContext _context; - private AnimationDatabase _animationDatabase; - private PathMappings _pathMappings; - - private Dictionary _selfProxies = new(); - - public void OnActivate(BuildContext context) - { - _context = context; - - _animationDatabase = new AnimationDatabase(); - _animationDatabase.OnActivate(context); - - _pathMappings = new PathMappings(); - _pathMappings.OnActivate(context, _animationDatabase); - } - - public void OnDeactivate(BuildContext context) - { - _pathMappings.OnDeactivate(context); - _animationDatabase.Commit(); - - _pathMappings = null; - _animationDatabase = null; - } - - public AnimationDatabase AnimationDatabase - { - get - { - if (_animationDatabase == null) - { - throw new InvalidOperationException( - "AnimationDatabase is not available outside of the AnimationServicesContext"); - } - - return _animationDatabase; - } - } - - public PathMappings PathMappings - { - get - { - if (_pathMappings == null) - { - throw new InvalidOperationException( - "ObjectRenameTracker is not available outside of the AnimationServicesContext"); - } - - return _pathMappings; - } - } - } -} \ No newline at end of file diff --git a/Editor/Animation/AnimationServicesContext.cs.meta b/Editor/Animation/AnimationServicesContext.cs.meta deleted file mode 100644 index 8eefb71b..00000000 --- a/Editor/Animation/AnimationServicesContext.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: c2c26040d44d4dacb838aceced3b3e52 -timeCreated: 1696063949 \ No newline at end of file diff --git a/Editor/Animation/AnimationUtil.cs b/Editor/Animation/AnimationUtil.cs deleted file mode 100644 index 1016cba9..00000000 --- a/Editor/Animation/AnimationUtil.cs +++ /dev/null @@ -1,224 +0,0 @@ -#region - -using System; -using System.Collections.Generic; -using nadena.dev.ndmf; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; - -#if MA_VRCSDK3_AVATARS -using VRC.SDK3.Avatars.Components; -#endif - -#endregion - -namespace nadena.dev.modular_avatar.animation -{ - internal static class AnimationUtil - { - 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"; - - - public static AnimatorController DeepCloneAnimator(BuildContext context, RuntimeAnimatorController controller) - { - if (controller == null) return null; - - var merger = new AnimatorCombiner(context, controller.name + " (cloned)"); - switch (controller) - { - case AnimatorController ac: - merger.AddController("", ac, null); - break; - case AnimatorOverrideController oac: - merger.AddOverrideController("", oac, null); - break; - default: - throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType()); - } - - var clone = merger.Finish(); - ObjectRegistry.RegisterReplacedObject(controller, clone); - return clone; - } - - internal static void CloneAllControllers(BuildContext context) - { - // Ensure all of the controllers on the avatar descriptor point to temporary assets. - // This helps reduce the risk that we'll accidentally modify the original assets. - -#if MA_VRCSDK3_AVATARS - if (!context.AvatarDescriptor) return; - - context.AvatarDescriptor.baseAnimationLayers = - CloneLayers(context, context.AvatarDescriptor.baseAnimationLayers); - context.AvatarDescriptor.specialAnimationLayers = - CloneLayers(context, context.AvatarDescriptor.specialAnimationLayers); -#endif - } - -#if MA_VRCSDK3_AVATARS - private static VRCAvatarDescriptor.CustomAnimLayer[] CloneLayers( - BuildContext context, - VRCAvatarDescriptor.CustomAnimLayer[] layers - ) - { - if (layers == null) return null; - - for (int i = 0; i < layers.Length; i++) - { - var layer = layers[i]; - if (layer.animatorController != null && !context.IsTemporaryAsset(layer.animatorController)) - { - layer.animatorController = DeepCloneAnimator(context, layer.animatorController); - } - - layers[i] = layer; - } - - return layers; - } - - public static AnimatorController GetOrInitializeController( - this BuildContext context, - VRCAvatarDescriptor.AnimLayerType type) - { - return FindLayer(context.AvatarDescriptor.baseAnimationLayers) - ?? FindLayer(context.AvatarDescriptor.specialAnimationLayers); - - AnimatorController FindLayer(VRCAvatarDescriptor.CustomAnimLayer[] layers) - { - for (int i = 0; i < layers.Length; i++) - { - var layer = layers[i]; - if (layer.type == type) - { - if (layer.animatorController == null || layer.isDefault) - { - layer.animatorController = ResolveLayerController(layer); - if (type == VRCAvatarDescriptor.AnimLayerType.Gesture) - { - layer.mask = AssetDatabase.LoadAssetAtPath( - AssetDatabase.GUIDToAssetPath(GUID_GESTURE_HANDSONLY_MASK) - ); - } - - layers[i] = layer; - } - - return layer.animatorController as AnimatorController; - } - } - - return null; - } - } - - - private static AnimatorController ResolveLayerController(VRCAvatarDescriptor.CustomAnimLayer layer) - { - AnimatorController controller = null; - - if (!layer.isDefault && layer.animatorController != null && - layer.animatorController is AnimatorController c) - { - controller = c; - } - else - { - 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); - } - } - } - - return controller; - } -#endif - - public static bool IsProxyAnimation(this Motion m) - { - var path = AssetDatabase.GetAssetPath(m); - - // This is a fairly wide condition in order to deal with: - // 1. Future additions of proxy animations (so GUIDs are out) - // 2. Unitypackage based installations of the VRCSDK - // 3. VCC based installations of the VRCSDK - // 4. Very old VCC based installations of the VRCSDK where proxy animations were copied into Assets - return path.Contains("/AV3 Demo Assets/Animation/ProxyAnim/proxy") - || path.Contains("/VRCSDK/Examples3/Animation/ProxyAnim/proxy") - || path.StartsWith("Packages/com.vrchat."); - } - - - /// - /// Enumerates all state machines and sub-state machines starting from a specific starting ASM - /// - /// - /// - internal static IEnumerable ReachableStateMachines(this AnimatorStateMachine asm) - { - HashSet visitedStateMachines = new HashSet(); - Queue pending = new Queue(); - - pending.Enqueue(asm); - - while (pending.Count > 0) - { - var next = pending.Dequeue(); - if (visitedStateMachines.Contains(next)) continue; - visitedStateMachines.Add(next); - - foreach (var child in next.stateMachines) - { - if (child.stateMachine != null) pending.Enqueue(child.stateMachine); - } - - yield return next; - } - } - } -} \ No newline at end of file diff --git a/Editor/Animation/AnimationUtil.cs.meta b/Editor/Animation/AnimationUtil.cs.meta deleted file mode 100644 index 53df83a0..00000000 --- a/Editor/Animation/AnimationUtil.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: af583e8ac3104fa4f8466741614219a0 -timeCreated: 1691238553 \ No newline at end of file diff --git a/Editor/Animation/AnimatorCombiner.cs b/Editor/Animation/AnimatorCombiner.cs deleted file mode 100644 index ea886afa..00000000 --- a/Editor/Animation/AnimatorCombiner.cs +++ /dev/null @@ -1,625 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2022 bd_ - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#region - -using System; -using System.Collections.Generic; -using System.Linq; -using nadena.dev.modular_avatar.editor.ErrorReporting; -using nadena.dev.ndmf; -using nadena.dev.ndmf.util; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; -using Object = UnityEngine.Object; - -#if MA_VRCSDK3_AVATARS -using VRC.SDK3.Avatars.Components; -using VRC.SDKBase; -#endif - -#endregion - -namespace nadena.dev.modular_avatar.animation -{ - internal class AnimatorCombiner - { - private readonly BuildContext _context; - private readonly AnimatorController _combined; - - private readonly DeepClone _deepClone; - - private List _layers = new List(); - - private Dictionary _parameters = - new Dictionary(); - - private Dictionary _parameterSource = - new Dictionary(); - - private Dictionary, Motion> _motions = - new Dictionary, Motion>(); - - private Dictionary, AnimatorStateMachine> _stateMachines = - new Dictionary, AnimatorStateMachine>(); - - private Dictionary _cloneMap; - - private int _controllerBaseLayer = 0; - -#if MA_VRCSDK3_AVATARS - public VRC_AnimatorLayerControl.BlendableLayer? BlendableLayer; -#endif - - public AnimatorCombiner(BuildContext context, String assetName) - { - _combined = new AnimatorController(); - if (context.AssetContainer != null && EditorUtility.IsPersistent(context.AssetContainer)) - { - context.AssetSaver.SaveAsset(_combined); - } - - _combined.name = assetName; - - _context = context; - _deepClone = new DeepClone(context); - } - - public AnimatorController Finish() - { - FixTransitionTypeConflicts(); - PruneEmptyLayers(); - - _combined.parameters = _parameters.Values.ToArray(); - _combined.layers = _layers.ToArray(); - return _combined; - } - - public void MergeTypes(Dictionary types) - { - foreach (var p in _parameters.ToList()) - { - if (types.TryGetValue(p.Key, out var outerValue)) - { - if (outerValue == p.Value.type) continue; - - if (outerValue == AnimatorControllerParameterType.Trigger - || p.Value.type == AnimatorControllerParameterType.Trigger) - { - BuildReport.LogFatal("error.merge_animator.param_type_mismatch", - p.Key, - p.Value.type, - outerValue - ); - } - - _parameters[p.Key].type = AnimatorControllerParameterType.Float; - types[p.Key] = AnimatorControllerParameterType.Float; - } - else - { - types.Add(p.Key, p.Value.type); - } - } - } - - /// - /// When we merge multiple controllers with different types for the same parameter, we merge - /// them all into using floats; thanks to VRChat's implicit typecasting, we can do this even for - /// parameters registered as being ints or bools in the expressions parameter asset. However, - /// we do need to fix any transitions to use the right transition types after this conversion. - /// - private void FixTransitionTypeConflicts() - { - foreach (var layer in _layers) - { - foreach (var asm in layer.stateMachine.ReachableStateMachines()) - { - foreach (ChildAnimatorState s in asm.states) - { - s.state.transitions = s.state.transitions.SelectMany(FixupTransition).ToArray(); - } - - asm.entryTransitions = asm.entryTransitions - .SelectMany(FixupTransition).ToArray(); - asm.anyStateTransitions = asm.anyStateTransitions - .SelectMany(FixupTransition).ToArray(); - - foreach (var stateMachine in asm.stateMachines) - { - var ssm = stateMachine.stateMachine; - - var stateMachineTransitions = asm.GetStateMachineTransitions(ssm); - if (stateMachineTransitions.Length > 0) - { - asm.SetStateMachineTransitions(ssm, - stateMachineTransitions.SelectMany(FixupTransition).ToArray()); - } - } - } - } - } - - private IEnumerable FixupTransition(T t) where T: AnimatorTransitionBase, new() - { - if (!NeedsFixing(t.conditions)) - { - yield return t; - yield break; - } - - AnimatorCondition[][][] combinations = t.conditions.Select(c => FixupCondition(c).ToArray()).ToArray(); - - // Generate the combinatorial explosion of conditions needed to emulate NotEquals with floats... - var conditions = ExplodeConditions(combinations).ToArray(); - - if (conditions.Length == 1) - { - t.conditions = conditions[0]; - yield return t; - } - else - { - foreach (var conditionGroup in conditions) - { - t.conditions = conditionGroup; - yield return t; - - var newTransition = new T(); - EditorUtility.CopySerialized(t, newTransition); - if (_context.AssetContainer != null) - { - _context.AssetSaver.SaveAsset(newTransition); - } - t = newTransition; - } - } - } - - private bool NeedsFixing(AnimatorCondition[] conditions) - { - return conditions.Any(c => - { - if (!_parameters.TryGetValue(c.parameter, out var param)) return false; - - switch (c.mode) - { - case AnimatorConditionMode.If when param.type != AnimatorControllerParameterType.Bool: - case AnimatorConditionMode.IfNot when param.type != AnimatorControllerParameterType.Bool: - case AnimatorConditionMode.Equals when param.type != AnimatorControllerParameterType.Int: - case AnimatorConditionMode.NotEqual when param.type != AnimatorControllerParameterType.Int: - return true; - default: - return false; - } - }); - } - - private IEnumerable ExplodeConditions(AnimatorCondition[][][] conditions) - { - int[] indices = new int[conditions.Length]; - - while (true) - { - yield return conditions.SelectMany((group, i_) => group[indices[i_]]).ToArray(); - - // Increment the rightmost possible counter - int i; - for (i = indices.Length - 1; i >= 0; i--) - { - if (indices[i] < conditions[i].Length - 1) - { - indices[i]++; - // Unity 2019..... - // System.Array.Fill(indices, 0, i + 1, indices.Length - i - 1); - for (int j = i + 1; j < indices.Length; j++) - { - indices[j] = 0; - } - break; - } - } - - if (i < 0) break; - } - } - - private IEnumerable FixupCondition(AnimatorCondition c) - { - if (!_parameters.TryGetValue(c.parameter, out var paramDef)) - { - // Parameter is undefined, don't touch this condition - yield return new[] { c }; - yield break; - } - - switch (c.mode) - { - case AnimatorConditionMode.If when paramDef.type == AnimatorControllerParameterType.Float: - { - c.mode = AnimatorConditionMode.Greater; - c.threshold = 0.5f; - yield return new[] { c }; - break; - } - case AnimatorConditionMode.IfNot when paramDef.type == AnimatorControllerParameterType.Float: - { - c.mode = AnimatorConditionMode.Less; - c.threshold = 0.5f; - yield return new[] { c }; - break; - } - case AnimatorConditionMode.Equals when paramDef.type == AnimatorControllerParameterType.Float: - { - var c1 = c; - var c2 = c; - c1.mode = AnimatorConditionMode.Greater; - c1.threshold -= 0.1f; - c2.mode = AnimatorConditionMode.Less; - c2.threshold += 0.1f; - yield return new[] { c1, c2 }; - break; - } - case AnimatorConditionMode.NotEqual when paramDef.type == AnimatorControllerParameterType.Float: - { - var origThresh = c.threshold; - c.mode = AnimatorConditionMode.Greater; - c.threshold = origThresh + 0.1f; - yield return new[] { c }; - - c.mode = AnimatorConditionMode.Less; - c.threshold = origThresh - 0.1f; - yield return new[] { c }; - break; - } - default: - yield return new[] { c }; - break; - } - } - - private void PruneEmptyLayers() - { -#if MA_VRCSDK3_AVATARS - // We can't safely correct the layer index of a VRCAnimatorLayerControl without knowing if it refers to - // _this_ animator controller, so just skip this. We'll do the empty layer pruning later when we merge - // everything together. - if (BlendableLayer == null) return; -#endif - - var originalLayers = _layers; - int[] layerIndexMappings = new int[originalLayers.Count]; - - List newLayers = new List(); - - for (int i = 0; i < originalLayers.Count; i++) - { - if (i > 0 && IsEmptyLayer(originalLayers[i])) - { - layerIndexMappings[i] = -1; - } - else - { - layerIndexMappings[i] = newLayers.Count; - newLayers.Add(originalLayers[i]); - } - } - - foreach (var layer in newLayers) - { - if (layer.stateMachine == null) continue; - - foreach (var asset in layer.stateMachine.ReferencedAssets(includeScene: false)) - { - if (asset is AnimatorState alc) - { - alc.behaviours = AdjustStateBehaviors(alc.behaviours); - } - else if (asset is AnimatorStateMachine asm) - { - asm.behaviours = AdjustStateBehaviors(asm.behaviours); - } - } - } - - _layers = newLayers; - - StateMachineBehaviour[] AdjustStateBehaviors(StateMachineBehaviour[] behaviours) - { - if (behaviours.Length == 0) return behaviours; - - var newBehaviors = new List(); - foreach (var b in behaviours) - { - switch (b) - { -#if MA_VRCSDK3_AVATARS - case VRCAnimatorLayerControl alc when alc.playable == BlendableLayer: - int newLayer = -1; - if (alc.layer >= 0 && alc.layer < layerIndexMappings.Length) - { - newLayer = layerIndexMappings[alc.layer]; - } - - if (newLayer != -1) - { - alc.layer = newLayer; - newBehaviors.Add(alc); - } - - break; -#endif - default: - newBehaviors.Add(b); - break; - } - } - - return newBehaviors.ToArray(); - } - } - - private bool IsEmptyLayer(AnimatorControllerLayer layer) - { - if (layer.syncedLayerIndex >= 0) return false; - if (layer.avatarMask != null) return false; - - return layer.stateMachine == null - || (layer.stateMachine.states.Length == 0 && layer.stateMachine.stateMachines.Length == 0); - } - - public void AddController(string basePath, AnimatorController controller, bool? writeDefaults, - bool forceFirstLayerWeight = false) - { - _controllerBaseLayer = _layers.Count; - _cloneMap = new Dictionary(); - - foreach (var param in controller.parameters) - { - if (_parameters.TryGetValue(param.name, out var acp)) - { - if (acp.type == param.type) continue; - - if (acp.type != param.type && - (acp.type == AnimatorControllerParameterType.Trigger || - param.type == AnimatorControllerParameterType.Trigger)) - { - BuildReport.LogFatal("error.merge_animator.param_type_mismatch", - param.name, - acp.type.ToString(), - param.type.ToString(), - controller, - _parameterSource[param.name] - ); - } - - acp.type = AnimatorControllerParameterType.Float; - - continue; - } - - var clonedParameter = new AnimatorControllerParameter() - { - name = param.name, - type = param.type, - defaultBool = param.defaultBool, - defaultFloat = param.defaultFloat, - defaultInt = param.defaultInt - }; - - _parameters.Add(param.name, clonedParameter); - _parameterSource.Add(param.name, controller); - } - - bool first = true; - var layers = controller.layers; - foreach (var layer in layers) - { - insertLayer(basePath, layer, first, writeDefaults, layers); - if (first && forceFirstLayerWeight) - { - _layers[_layers.Count - 1].defaultWeight = 1; - } - - first = false; - } - } - - public void AddOverrideController(string basePath, AnimatorOverrideController overrideController, - bool? writeDefaults) - { - AnimatorController controller = overrideController.runtimeAnimatorController as AnimatorController; - if (controller == null) return; - _deepClone.OverrideController = overrideController; - try - { - this.AddController(basePath, controller, writeDefaults); - } - finally - { - } - } - - private void insertLayer( - string basePath, - AnimatorControllerLayer layer, - bool first, - bool? writeDefaults, - AnimatorControllerLayer[] layers - ) - { - var newLayer = new AnimatorControllerLayer() - { - name = layer.name, - avatarMask = _deepClone.DoClone(layer.avatarMask, basePath, _cloneMap), - blendingMode = layer.blendingMode, - defaultWeight = first ? 1 : layer.defaultWeight, - syncedLayerIndex = layer.syncedLayerIndex, - syncedLayerAffectsTiming = layer.syncedLayerAffectsTiming, - iKPass = layer.iKPass, - stateMachine = mapStateMachine(basePath, layer.stateMachine), - }; - - UpdateWriteDefaults(newLayer.stateMachine, writeDefaults); - - if (newLayer.syncedLayerIndex != -1 && newLayer.syncedLayerIndex >= 0 && - newLayer.syncedLayerIndex < layers.Length) - { - // Transfer any motion overrides onto the new synced layer - var baseLayer = layers[newLayer.syncedLayerIndex]; - foreach (var state in WalkAllStates(baseLayer.stateMachine)) - { - var overrideMotion = layer.GetOverrideMotion(state); - if (overrideMotion != null) - { - var newMotion = _deepClone.DoClone(overrideMotion, basePath, _cloneMap); - newLayer.SetOverrideMotion((AnimatorState)_cloneMap[state], newMotion); - } - - var overrideBehaviors = (StateMachineBehaviour[])layer.GetOverrideBehaviours(state)?.Clone(); - if (overrideBehaviors != null) - { - for (int i = 0; i < overrideBehaviors.Length; i++) - { - overrideBehaviors[i] = _deepClone.DoClone(overrideBehaviors[i]); - AdjustBehavior(overrideBehaviors[i], basePath); - } - - newLayer.SetOverrideBehaviours((AnimatorState)_cloneMap[state], overrideBehaviors); - } - } - - newLayer.syncedLayerIndex += _controllerBaseLayer; - } - - _layers.Add(newLayer); - } - - IEnumerable WalkAllStates(AnimatorStateMachine animatorStateMachine) - { - HashSet visited = new HashSet(); - - foreach (var state in VisitStateMachine(animatorStateMachine)) - { - yield return state; - } - - IEnumerable VisitStateMachine(AnimatorStateMachine layerStateMachine) - { - if (!visited.Add(layerStateMachine)) yield break; - - foreach (var state in layerStateMachine.states) - { - if (state.state == null) continue; - - yield return state.state; - } - - foreach (var child in layerStateMachine.stateMachines) - { - if (child.stateMachine == null) continue; - - if (visited.Contains(child.stateMachine)) continue; - foreach (var state in VisitStateMachine(child.stateMachine)) - { - yield return state; - } - } - } - } - - private void UpdateWriteDefaults(AnimatorStateMachine stateMachine, bool? writeDefaults) - { - if (!writeDefaults.HasValue) return; - - var queue = new Queue(); - queue.Enqueue(stateMachine); - while (queue.Count > 0) - { - var sm = queue.Dequeue(); - foreach (var state in sm.states) - { - state.state.writeDefaultValues = writeDefaults.Value; - } - - foreach (var child in sm.stateMachines) - { - queue.Enqueue(child.stateMachine); - } - } - } - - private AnimatorStateMachine mapStateMachine(string basePath, AnimatorStateMachine layerStateMachine) - { - if (layerStateMachine == null) return null; - - var cacheKey = new KeyValuePair(basePath, layerStateMachine); - - if (_stateMachines.TryGetValue(cacheKey, out var asm)) - { - return asm; - } - - asm = _deepClone.DoClone(layerStateMachine, basePath, _cloneMap); - - foreach (var state in WalkAllStates(asm)) - { - foreach (var behavior in state.behaviours) - { - AdjustBehavior(behavior, basePath); - } - } - - _stateMachines[cacheKey] = asm; - return asm; - } - - private void AdjustBehavior(StateMachineBehaviour behavior, string basePath) - { -#if MA_VRCSDK3_AVATARS - switch (behavior) - { - case VRCAnimatorLayerControl layerControl: - { - // TODO - need to figure out how to handle cross-layer references. For now this will handle - // intra-animator cases. - layerControl.layer += _controllerBaseLayer; - break; - } -#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER - case VRCAnimatorPlayAudio playAudio: - { - if (!string.IsNullOrEmpty(playAudio.SourcePath) && !string.IsNullOrEmpty(basePath) && !playAudio.SourcePath.StartsWith(basePath)) - { - playAudio.SourcePath = $"{basePath}{playAudio.SourcePath}"; - } - break; - } -#endif - } -#endif - } - } -} \ No newline at end of file diff --git a/Editor/Animation/AnimatorCombiner.cs.meta b/Editor/Animation/AnimatorCombiner.cs.meta deleted file mode 100644 index 27e393c7..00000000 --- a/Editor/Animation/AnimatorCombiner.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 614457d82b1a4b109788029754c9fc1a -timeCreated: 1703674134 \ No newline at end of file diff --git a/Editor/Animation/DeepClone.cs b/Editor/Animation/DeepClone.cs deleted file mode 100644 index 50caadca..00000000 --- a/Editor/Animation/DeepClone.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Generic; -using nadena.dev.modular_avatar.core.editor; -using nadena.dev.ndmf; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; -using BuildContext = nadena.dev.ndmf.BuildContext; -using Object = UnityEngine.Object; - -namespace nadena.dev.modular_avatar.animation -{ - using UnityObject = Object; - - internal class DeepClone - { - private BuildContext _context; - private bool _isSaved; - private UnityObject _combined; - - public AnimatorOverrideController OverrideController { get; set; } - - public DeepClone(BuildContext context) - { - _context = context; - _isSaved = context.AssetContainer != null && EditorUtility.IsPersistent(context.AssetContainer); - _combined = context.AssetContainer; - } - - public T DoClone(T original, - string basePath = null, - Dictionary cloneMap = null - ) where T : UnityObject - { - if (original == null) return null; - if (cloneMap == null) cloneMap = new Dictionary(); - - using var scope = _context.OpenSerializationScope(); - - Func visitor = null; - if (basePath != null) - { - visitor = o => CloneWithPathMapping(o, basePath); - } - - // We want to avoid trying to copy assets not part of the animation system (eg - textures, meshes, - // MonoScripts...), so check for the types we care about here - switch (original) - { - // Any object referenced by an animator that we intend to mutate needs to be listed here. - case Motion _: - case AnimatorController _: - case AnimatorState _: - case AnimatorStateMachine _: - case AnimatorTransitionBase _: - case StateMachineBehaviour _: - case AvatarMask _: - break; // We want to clone these types - - case AudioClip _: //Used in VRC Animator Play Audio State Behavior - // Leave textures, materials, and script definitions alone - case Texture2D _: - case MonoScript _: - case Material _: - return original; - - // Also avoid copying unknown scriptable objects. - // This ensures compatibility with e.g. avatar remote, which stores state information in a state - // behaviour referencing a custom ScriptableObject - case ScriptableObject _: - return original; - - default: - throw new Exception($"Unknown type referenced from animator: {original.GetType()}"); - } - - // When using AnimatorOverrideController, replace the original AnimationClip based on AnimatorOverrideController. - if (OverrideController != null && original is AnimationClip srcClip) - { - T overrideClip = OverrideController[srcClip] as T; - if (overrideClip != null) - { - original = overrideClip; - } - } - - if (cloneMap.ContainsKey(original)) - { - return (T)cloneMap[original]; - } - - var obj = visitor?.Invoke(original); - if (obj != null) - { - cloneMap[original] = obj; - if (obj != original) - { - ObjectRegistry.RegisterReplacedObject(original, obj); - } - - if (_isSaved && !EditorUtility.IsPersistent(obj)) - { - scope.SaveAsset(obj); - } - - return (T)obj; - } - - var ctor = original.GetType().GetConstructor(Type.EmptyTypes); - if (ctor == null || original is ScriptableObject) - { - obj = UnityObject.Instantiate(original); - } - else - { - obj = (T)ctor.Invoke(Array.Empty()); - EditorUtility.CopySerialized(original, obj); - } - - cloneMap[original] = obj; - ObjectRegistry.RegisterReplacedObject(original, obj); - - if (_isSaved) - { - scope.SaveAsset(obj); - } - - SerializedObject so = new SerializedObject(obj); - SerializedProperty prop = so.GetIterator(); - - bool enterChildren = true; - while (prop.Next(enterChildren)) - { - enterChildren = true; - switch (prop.propertyType) - { - case SerializedPropertyType.ObjectReference: - { - if (prop.objectReferenceValue != null && prop.objectReferenceValue != obj) - { - var newObj = DoClone(prop.objectReferenceValue, basePath, cloneMap); - prop.objectReferenceValue = newObj; - } - - break; - } - // Iterating strings can get super slow... - case SerializedPropertyType.String: - enterChildren = false; - break; - } - } - - so.ApplyModifiedPropertiesWithoutUndo(); - - return (T)obj; - } - - // internal for testing - internal static AvatarMask CloneAvatarMask(AvatarMask mask, string basePath) - { - if (basePath.EndsWith("/")) basePath = basePath.Substring(0, basePath.Length - 1); - - var newMask = new AvatarMask(); - - // Transfer first the humanoid mask data - EditorUtility.CopySerialized(mask, newMask); - - var srcSo = new SerializedObject(mask); - var dstSo = new SerializedObject(newMask); - var srcElements = srcSo.FindProperty("m_Elements"); - - if (basePath == "" || srcElements.arraySize == 0) return newMask; // no changes required - - // We now need to prefix the elements of basePath (with weight zero) - - var newElements = new List(); - - var accum = ""; - foreach (var element in basePath.Split("/")) - { - if (accum != "") accum += "/"; - accum += element; - - newElements.Add(accum); - } - - var dstElements = dstSo.FindProperty("m_Elements"); - - // We'll need to create new array elements by using DuplicateCommand. We'll then rewrite the whole - // list to keep things in traversal order. - for (var i = 0; i < newElements.Count; i++) dstElements.GetArrayElementAtIndex(0).DuplicateCommand(); - - var totalElements = srcElements.arraySize + newElements.Count; - for (var i = 0; i < totalElements; i++) - { - var dstElem = dstElements.GetArrayElementAtIndex(i); - var dstPath = dstElem.FindPropertyRelative("m_Path"); - var dstWeight = dstElem.FindPropertyRelative("m_Weight"); - - var srcIndex = i - newElements.Count; - if (srcIndex < 0) - { - dstPath.stringValue = newElements[i]; - dstWeight.floatValue = 0; - } - else - { - var srcElem = srcElements.GetArrayElementAtIndex(srcIndex); - dstPath.stringValue = basePath + "/" + srcElem.FindPropertyRelative("m_Path").stringValue; - dstWeight.floatValue = srcElem.FindPropertyRelative("m_Weight").floatValue; - } - } - - dstSo.ApplyModifiedPropertiesWithoutUndo(); - - return newMask; - } - - private UnityObject CloneWithPathMapping(UnityObject o, string basePath) - { - if (o is AvatarMask mask) - { - return CloneAvatarMask(mask, basePath); - } - - if (o is AnimationClip clip) - { - // We'll always rebase if the asset is non-persistent, because we can't reference a nonpersistent asset - // from a persistent asset. If the asset is persistent, skip cases where path editing isn't required, - // or where this is one of the special VRC proxy animations. - if (EditorUtility.IsPersistent(o) && (basePath == "" || Util.IsProxyAnimation(clip))) return clip; - - AnimationClip newClip = new AnimationClip(); - newClip.name = "rebased " + clip.name; - if (_isSaved) - { - _context.AssetSaver.SaveAsset(newClip); - } - - foreach (var binding in AnimationUtility.GetCurveBindings(clip)) - { - var newBinding = binding; - newBinding.path = MapPath(binding, basePath); - // https://github.com/bdunderscore/modular-avatar/issues/950 - // It's reported that sometimes using SetObjectReferenceCurve right after SetCurve might cause the - // curves to be forgotten; use SetEditorCurve instead. - AnimationUtility.SetEditorCurve(newClip, newBinding, - AnimationUtility.GetEditorCurve(clip, binding)); - } - - foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) - { - var newBinding = objBinding; - newBinding.path = MapPath(objBinding, basePath); - AnimationUtility.SetObjectReferenceCurve(newClip, newBinding, - AnimationUtility.GetObjectReferenceCurve(clip, objBinding)); - } - - newClip.wrapMode = clip.wrapMode; - newClip.legacy = clip.legacy; - newClip.frameRate = clip.frameRate; - newClip.localBounds = clip.localBounds; - AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(clip)); - - return newClip; - } - else if (o is Texture) - { - return o; - } - else - { - return null; - } - } - - private static string MapPath(EditorCurveBinding binding, string basePath) - { - if (binding.type == typeof(Animator) && binding.path == "") - { - return ""; - } - else - { - var newPath = binding.path == "" ? basePath : basePath + binding.path; - if (newPath.EndsWith("/")) - { - newPath = newPath.Substring(0, newPath.Length - 1); - } - - return newPath; - } - } - } -} diff --git a/Editor/Animation/DeepClone.cs.meta b/Editor/Animation/DeepClone.cs.meta deleted file mode 100644 index 0dccb26b..00000000 --- a/Editor/Animation/DeepClone.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: b33090a3e763464ab05f3efe07e0cbd3 -timeCreated: 1703148770 \ No newline at end of file diff --git a/Editor/Animation/EditorCurveBindingComparer.cs b/Editor/Animation/EditorCurveBindingComparer.cs deleted file mode 100644 index 55d6665e..00000000 --- a/Editor/Animation/EditorCurveBindingComparer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using UnityEditor; - -namespace nadena.dev.modular_avatar.animation -{ - internal class EditorCurveBindingComparer : IEqualityComparer - { - public bool Equals(UnityEditor.EditorCurveBinding x, UnityEditor.EditorCurveBinding y) - { - return x.path == y.path && x.type == y.type && x.propertyName == y.propertyName; - } - - public int GetHashCode(UnityEditor.EditorCurveBinding obj) - { - return obj.path.GetHashCode() ^ obj.type.GetHashCode() ^ obj.propertyName.GetHashCode(); - } - } -} \ No newline at end of file diff --git a/Editor/Animation/EditorCurveBindingComparer.cs.meta b/Editor/Animation/EditorCurveBindingComparer.cs.meta deleted file mode 100644 index f69c8463..00000000 --- a/Editor/Animation/EditorCurveBindingComparer.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: e751f7889323485bbe202285a47cb0d4 -timeCreated: 1719196767 \ No newline at end of file diff --git a/Editor/Animation/IOnCommitObjectRenames.cs b/Editor/Animation/IOnCommitObjectRenames.cs deleted file mode 100644 index a8a832f0..00000000 --- a/Editor/Animation/IOnCommitObjectRenames.cs +++ /dev/null @@ -1,17 +0,0 @@ -#region - -using nadena.dev.ndmf; - -#endregion - -namespace nadena.dev.modular_avatar.animation -{ - /// - /// This interface tags components which supply additional animation controllers for merging. They will be given - /// an opportunity to apply animation path updates when the TrackObjectRenamesContext is committed. - /// - internal interface IOnCommitObjectRenames - { - void OnCommitObjectRenames(BuildContext buildContext, PathMappings renameContext); - } -} \ No newline at end of file diff --git a/Editor/Animation/IOnCommitObjectRenames.cs.meta b/Editor/Animation/IOnCommitObjectRenames.cs.meta deleted file mode 100644 index 235d4967..00000000 --- a/Editor/Animation/IOnCommitObjectRenames.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 6a66f552b8b334a45a986bfcf6767200 -timeCreated: 1692511752 \ No newline at end of file diff --git a/Editor/Animation/PathMappings.cs b/Editor/Animation/PathMappings.cs deleted file mode 100644 index 4d53f6a2..00000000 --- a/Editor/Animation/PathMappings.cs +++ /dev/null @@ -1,418 +0,0 @@ -#region - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using nadena.dev.ndmf; -using nadena.dev.ndmf.util; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; -using UnityEngine.Profiling; -#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER -#endif - -#endregion - -namespace nadena.dev.modular_avatar.animation -{ - #region - - #endregion - - /// - /// This extension context tracks when objects are renamed, and updates animations accordingly. - /// Users of this context need to be aware that, when creating new curves (or otherwise introducing new motions, - /// use context.ObjectPath to obtain a suitable path for the target objects). - /// - internal sealed class PathMappings - { - private AnimationDatabase _animationDatabase; - - private Dictionary> - _objectToOriginalPaths = new Dictionary>(); - - private HashSet _transformLookthroughObjects = new HashSet(); - private ImmutableDictionary _originalPathToMappedPath = null; - private ImmutableDictionary _transformOriginalPathToMappedPath = null; - private ImmutableDictionary _pathToObject = null; - - internal void OnActivate(BuildContext context, AnimationDatabase animationDatabase) - { - _animationDatabase = animationDatabase; - _objectToOriginalPaths.Clear(); - _transformLookthroughObjects.Clear(); - ClearCache(); - - foreach (var xform in context.AvatarRootTransform.GetComponentsInChildren(true)) - { - _objectToOriginalPaths.Add(xform.gameObject, new List {xform.gameObject.AvatarRootPath()}); - } - } - - public void ClearCache() - { - _originalPathToMappedPath = null; - _transformOriginalPathToMappedPath = null; - _pathToObject = null; - } - - /// - /// Sets the "transform lookthrough" flag for an object. Any transform animations on this object will be - /// redirected to its parent. This is used in Modular Avatar as part of bone merging logic. - /// - /// - public void MarkTransformLookthrough(GameObject obj) - { - _transformLookthroughObjects.Add(obj); - } - - /// - /// Returns a path for use in dynamically generated animations for a given object. This can include objects not - /// present at the time of context activation; in this case, they will be assigned a randomly-generated internal - /// path and replaced during path remapping with the true path. - /// - /// - /// - public string GetObjectIdentifier(GameObject obj) - { - if (_objectToOriginalPaths.TryGetValue(obj, out var paths)) - { - return paths[0]; - } - else - { - var internalPath = "_NewlyCreatedObject/" + GUID.Generate() + "/" + obj.AvatarRootPath(); - _objectToOriginalPaths.Add(obj, new List {internalPath}); - return internalPath; - } - } - - /// - /// Marks an object as having been removed. Its paths will be remapped to its parent. - /// - /// - public void MarkRemoved(GameObject obj) - { - ClearCache(); - if (_objectToOriginalPaths.TryGetValue(obj, out var paths)) - { - var parent = obj.transform.parent.gameObject; - if (_objectToOriginalPaths.TryGetValue(parent, out var parentPaths)) - { - parentPaths.AddRange(paths); - } - - _objectToOriginalPaths.Remove(obj); - _transformLookthroughObjects.Remove(obj); - } - } - - - /// - /// Marks an object as having been replaced by another object. All references to the old object will be replaced - /// by the new object. References originally to the new object will continue to point to the new object. - /// - /// - /// - public void ReplaceObject(GameObject old, GameObject newObject) - { - ClearCache(); - - if (_objectToOriginalPaths.TryGetValue(old, out var paths)) - { - if (!_objectToOriginalPaths.TryGetValue(newObject, out var newObjectPaths)) - { - newObjectPaths = new List(); - _objectToOriginalPaths.Add(newObject, newObjectPaths); - } - - newObjectPaths.AddRange(paths); - - _objectToOriginalPaths.Remove(old); - } - - - if (_transformLookthroughObjects.Contains(old)) - { - _transformLookthroughObjects.Remove(old); - _transformLookthroughObjects.Add(newObject); - } - } - - - private ImmutableDictionary BuildMapping(ref ImmutableDictionary cache, - bool transformLookup) - { - if (cache != null) return cache; - - ImmutableDictionary dict = ImmutableDictionary.Empty; - - foreach (var kvp in _objectToOriginalPaths) - { - var obj = kvp.Key; - var paths = kvp.Value; - - if (transformLookup) - { - while (_transformLookthroughObjects.Contains(obj)) - { - obj = obj.transform.parent.gameObject; - } - } - - var newPath = obj.AvatarRootPath(); - foreach (var origPath in paths) - { - if (!dict.ContainsKey(origPath)) - { - dict = dict.Add(origPath, newPath); - } - } - } - - cache = dict; - return cache; - } - - public string MapPath(string path, bool isTransformMapping = false) - { - ImmutableDictionary mappings; - - if (isTransformMapping) - { - mappings = BuildMapping(ref _originalPathToMappedPath, true); - } - else - { - mappings = BuildMapping(ref _transformOriginalPathToMappedPath, false); - } - - if (mappings.TryGetValue(path, out var mappedPath)) - { - return mappedPath; - } - else - { - return path; - } - } - - private string MapPath(EditorCurveBinding binding) - { - if (binding.type == typeof(Animator) && binding.path == "") - { - return ""; - } - else - { - return MapPath(binding.path, binding.type == typeof(Transform)); - } - } - - private AnimationClip ApplyMappingsToClip(AnimationClip originalClip, - Dictionary clipCache) - { - if (originalClip == null) return null; - if (clipCache != null && clipCache.TryGetValue(originalClip, out var cachedClip)) return cachedClip; - - if (originalClip.IsProxyAnimation()) return originalClip; - - var curveBindings = AnimationUtility.GetCurveBindings(originalClip); - var objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(originalClip); - - bool hasMapping = false; - foreach (var binding in curveBindings.Concat(objectBindings)) - { - if (MapPath(binding) != binding.path) - { - hasMapping = true; - break; - } - } - - if (!hasMapping) return originalClip; - - - var newClip = new AnimationClip(); - newClip.name = originalClip.name; - - SerializedObject before = new SerializedObject(originalClip); - SerializedObject after = new SerializedObject(newClip); - - var before_hqCurve = before.FindProperty("m_UseHighQualityCurve"); - var after_hqCurve = after.FindProperty("m_UseHighQualityCurve"); - - after_hqCurve.boolValue = before_hqCurve.boolValue; - after.ApplyModifiedPropertiesWithoutUndo(); - - // TODO - should we use direct SerializedObject manipulation to avoid missing script issues? - foreach (var binding in curveBindings) - { - var newBinding = binding; - newBinding.path = MapPath(binding); - // https://github.com/bdunderscore/modular-avatar/issues/950 - // It's reported that sometimes using SetObjectReferenceCurve right after SetCurve might cause the - // curves to be forgotten; use SetEditorCurve instead. - AnimationUtility.SetEditorCurve(newClip, newBinding, - AnimationUtility.GetEditorCurve(originalClip, binding)); - } - - foreach (var objBinding in objectBindings) - { - var newBinding = objBinding; - newBinding.path = MapPath(objBinding); - AnimationUtility.SetObjectReferenceCurve(newClip, newBinding, - AnimationUtility.GetObjectReferenceCurve(originalClip, objBinding)); - } - - newClip.wrapMode = originalClip.wrapMode; - newClip.legacy = originalClip.legacy; - newClip.frameRate = originalClip.frameRate; - newClip.localBounds = originalClip.localBounds; - AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(originalClip)); - - if (clipCache != null) - { - clipCache.Add(originalClip, newClip); - } - - return newClip; - } - - private void ApplyMappingsToAvatarMask(AvatarMask mask) - { - if (mask == null) return; - - var maskSo = new SerializedObject(mask); - - var seenTransforms = new Dictionary(); - var transformOrder = new List(); - var m_Elements = maskSo.FindProperty("m_Elements"); - var elementCount = m_Elements.arraySize; - - for (var i = 0; i < elementCount; i++) - { - var element = m_Elements.GetArrayElementAtIndex(i); - var path = element.FindPropertyRelative("m_Path").stringValue; - var weight = element.FindPropertyRelative("m_Weight").floatValue; - - path = MapPath(path); - - // ensure all parent elements are present - EnsureParentsPresent(path); - - if (!seenTransforms.ContainsKey(path)) transformOrder.Add(path); - seenTransforms[path] = weight; - } - - transformOrder.Sort(); - m_Elements.arraySize = transformOrder.Count; - - for (var i = 0; i < transformOrder.Count; i++) - { - var element = m_Elements.GetArrayElementAtIndex(i); - var path = transformOrder[i]; - - element.FindPropertyRelative("m_Path").stringValue = path; - element.FindPropertyRelative("m_Weight").floatValue = seenTransforms[path]; - } - - maskSo.ApplyModifiedPropertiesWithoutUndo(); - - void EnsureParentsPresent(string path) - { - var nextSlash = -1; - - while ((nextSlash = path.IndexOf('/', nextSlash + 1)) != -1) - { - var parentPath = path.Substring(0, nextSlash); - if (!seenTransforms.ContainsKey(parentPath)) - { - seenTransforms[parentPath] = 0; - transformOrder.Add(parentPath); - } - } - } - } - - internal void OnDeactivate(BuildContext context) - { - Profiler.BeginSample("PathMappings.OnDeactivate"); - Dictionary clipCache = new Dictionary(); - - Profiler.BeginSample("ApplyMappingsToClip"); - _animationDatabase.ForeachClip(holder => - { - if (holder.CurrentClip is AnimationClip clip) - { - holder.CurrentClip = ApplyMappingsToClip(clip, clipCache); - } - }); - Profiler.EndSample(); - -#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER - Profiler.BeginSample("MapPlayAudio"); - _animationDatabase.ForeachPlayAudio(playAudio => - { - if (playAudio == null) return; - playAudio.SourcePath = MapPath(playAudio.SourcePath, true); - }); - Profiler.EndSample(); -#endif - - Profiler.BeginSample("InvokeIOnCommitObjectRenamesCallbacks"); - foreach (var listener in context.AvatarRootObject.GetComponentsInChildren()) - { - listener.OnCommitObjectRenames(context, this); - } - Profiler.EndSample(); - -#if MA_VRCSDK3_AVATARS - if (context.AvatarDescriptor) - { - var layers = context.AvatarDescriptor.baseAnimationLayers - .Concat(context.AvatarDescriptor.specialAnimationLayers); - - Profiler.BeginSample("ApplyMappingsToAvatarMasks"); - foreach (var layer in layers) - { - ApplyMappingsToAvatarMask(layer.mask); - - if (layer.animatorController is AnimatorController ac) - // By this point, all AnimationOverrideControllers have been collapsed into an ephemeral - // AnimatorController so we can safely modify the controller in-place. - foreach (var acLayer in ac.layers) - ApplyMappingsToAvatarMask(acLayer.avatarMask); - } - Profiler.EndSample(); - } -#endif - - Profiler.EndSample(); - } - - public GameObject PathToObject(string path) - { - if (_pathToObject == null) - { - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var kvp in _objectToOriginalPaths) - foreach (var p in kvp.Value) - builder[p] = kvp.Key; - - _pathToObject = builder.ToImmutable(); - } - - if (_pathToObject.TryGetValue(path, out var obj)) - { - return obj; - } - else - { - return null; - } - } - } -} \ No newline at end of file diff --git a/Editor/Animation/PathMappings.cs.meta b/Editor/Animation/PathMappings.cs.meta deleted file mode 100644 index 5d2a212d..00000000 --- a/Editor/Animation/PathMappings.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: f60ee78d127fda546a84d5396edfc8b2 -timeCreated: 1691237971 \ No newline at end of file diff --git a/Editor/BuildContext.cs b/Editor/BuildContext.cs index 1783dd85..37e91d5d 100644 --- a/Editor/BuildContext.cs +++ b/Editor/BuildContext.cs @@ -4,10 +4,7 @@ using VRC.SDK3.Avatars.ScriptableObjects; #endif using System; using System.Collections.Generic; -using nadena.dev.modular_avatar.animation; -using nadena.dev.ndmf; using UnityEditor; -using UnityEditor.Animations; using UnityEngine; using Object = UnityEngine.Object; @@ -23,14 +20,6 @@ namespace nadena.dev.modular_avatar.core.editor internal GameObject AvatarRootObject => PluginBuildContext.AvatarRootObject; internal Transform AvatarRootTransform => PluginBuildContext.AvatarRootTransform; - internal AnimationDatabase AnimationDatabase => - PluginBuildContext.Extension().AnimationDatabase; - - internal PathMappings PathMappings => - PluginBuildContext.Extension().PathMappings; - - internal Object AssetContainer => PluginBuildContext.AssetContainer; - private bool SaveImmediate = false; #if MA_VRCSDK3_AVATARS @@ -71,61 +60,6 @@ namespace nadena.dev.modular_avatar.core.editor PluginBuildContext.AssetSaver.SaveAsset(obj); } - public AnimatorController CreateAnimator(AnimatorController toClone = null) - { - AnimatorController controller; - if (toClone != null) - { - controller = Object.Instantiate(toClone); - } - else - { - controller = new AnimatorController(); - } - - SaveAsset(controller); - - return controller; - } - - public AnimatorController DeepCloneAnimator(RuntimeAnimatorController controller) - { - if (controller == null) return null; - - var merger = new AnimatorCombiner(PluginBuildContext, controller.name + " (clone)"); - switch (controller) - { - case AnimatorController ac: - merger.AddController("", ac, null); - break; - case AnimatorOverrideController oac: - merger.AddOverrideController("", oac, null); - break; - default: - throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType()); - } - - var result = merger.Finish(); - - ObjectRegistry.RegisterReplacedObject(controller, result); - - return result; - } - - public AnimatorController ConvertAnimatorController(RuntimeAnimatorController anyController) - { - switch (anyController) - { - case AnimatorController ac: - return ac; - case AnimatorOverrideController aoc: - var merger = new AnimatorCombiner(PluginBuildContext, anyController.name + " (clone)"); - merger.AddOverrideController("", aoc, null); - return merger.Finish(); - default: - throw new Exception("Unknown RuntimeAnimatorContoller type " + anyController.GetType()); - } - } #if MA_VRCSDK3_AVATARS public VRCExpressionsMenu CloneMenu(VRCExpressionsMenu menu) diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index b85503c6..330f5df2 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -37,8 +37,6 @@ namespace nadena.dev.modular_avatar.core.editor.plugin { Sequence seq = InPhase(BuildPhase.Resolving); seq.Run(ResolveObjectReferences.Instance); - // Protect against accidental destructive edits by cloning the input controllers ASAP - seq.Run("Clone animators", AnimationUtil.CloneAllControllers); seq = InPhase(BuildPhase.Transforming); seq.Run("Validate configuration", diff --git a/Editor/RenameParametersHook.cs b/Editor/RenameParametersHook.cs index 8e9c6064..7c63f9d0 100644 --- a/Editor/RenameParametersHook.cs +++ b/Editor/RenameParametersHook.cs @@ -408,12 +408,6 @@ namespace nadena.dev.modular_avatar.core.editor case ModularAvatarMergeAnimator merger: { - // RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController - if (merger.animator is AnimatorOverrideController overrideController) - { - merger.animator = _context.ConvertAnimatorController(overrideController); - } - var mappings = paramInfo.GetParameterRemappingsAt(obj); var remap = mappings.SelectMany(item => { diff --git a/Editor/ReplaceObjectPass.cs b/Editor/ReplaceObjectPass.cs index 34bc4792..9e982c87 100644 --- a/Editor/ReplaceObjectPass.cs +++ b/Editor/ReplaceObjectPass.cs @@ -1,14 +1,15 @@ 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 Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { - using UnityObject = UnityEngine.Object; + using UnityObject = Object; // ReSharper disable once RedundantUsingDirective using Object = System.Object; @@ -128,7 +129,7 @@ namespace nadena.dev.modular_avatar.core.editor } } - _buildContext.Extension().PathMappings + _buildContext.Extension().ObjectPathRemapper .ReplaceObject(original, replacement); // Destroy original diff --git a/UnitTests~/Animation/AnimationDatabaseCloning/AnimationDatabaseCloningTest.cs b/UnitTests~/Animation/AnimationDatabaseCloning/AnimationDatabaseCloningTest.cs deleted file mode 100644 index 42f82d88..00000000 --- a/UnitTests~/Animation/AnimationDatabaseCloning/AnimationDatabaseCloningTest.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using modular_avatar_tests; -using nadena.dev.modular_avatar.animation; -using nadena.dev.ndmf; -using NUnit.Framework; -using UnityEditor.Animations; -using UnityEngine; - - -namespace _ModularAvatar.EditModeTests -{ - public class AnimationDatabaseCloningTest : TestBase - { - [Test] - public void TestAnimationDatabaseCloningLogic() - { - var root = CreateRoot("root"); - var context = CreateContext(root); - - var origController = LoadAsset("ac.controller"); - var state = origController.layers[0].stateMachine.defaultState; - var clonedState = Object.Instantiate(state); - - var origAnimation = LoadAsset("anim.anim"); - - using (new ObjectRegistryScope(new ObjectRegistry(root.transform))) - { - var db = new AnimationDatabase(); - db.OnActivate(context); - db.RegisterState(clonedState); - - var newBlendTree = clonedState.motion as BlendTree; - var origBlendTree = state.motion as BlendTree; - - Assert.NotNull(newBlendTree); - Assert.NotNull(origBlendTree); - - Assert.AreNotSame(newBlendTree, origBlendTree); - Assert.AreNotSame(newBlendTree.children[1].motion, origBlendTree.children[1].motion); - - // Before commit, proxy animations are replaced. - Assert.AreNotSame(newBlendTree.children[0].motion, origBlendTree.children[0].motion); - - Assert.AreSame(ObjectRegistry.GetReference(origAnimation), - ObjectRegistry.GetReference(newBlendTree.children[1].motion)); - - db.Commit(); - - Assert.AreNotSame(newBlendTree, origBlendTree); - Assert.AreNotSame(newBlendTree.children[1].motion, origBlendTree.children[1].motion); - - // After commit, proxy animations are restored to the original assets. - Assert.AreSame(newBlendTree.children[0].motion, origBlendTree.children[0].motion); - } - } - } -} \ No newline at end of file diff --git a/UnitTests~/Animation/AnimationDatabaseCloning/AnimationDatabaseCloningTest.cs.meta b/UnitTests~/Animation/AnimationDatabaseCloning/AnimationDatabaseCloningTest.cs.meta deleted file mode 100644 index d8bd5575..00000000 --- a/UnitTests~/Animation/AnimationDatabaseCloning/AnimationDatabaseCloningTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d86c7d257d78fff4d8fdf56e2954a5c9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/UnitTests~/Animation/MiscAnimationTests.cs b/UnitTests~/Animation/MiscAnimationTests.cs index 26a8f3b5..9cfd41a6 100644 --- a/UnitTests~/Animation/MiscAnimationTests.cs +++ b/UnitTests~/Animation/MiscAnimationTests.cs @@ -3,6 +3,7 @@ using nadena.dev.ndmf; using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEditor; using UnityEngine; @@ -16,8 +17,9 @@ namespace modular_avatar_tests { var prefab = CreatePrefab("HighQualityCurvesSettingPreserved.prefab"); var context = new BuildContext(prefab, null); - context.ActivateExtensionContext(); - context.DeactivateExtensionContext(); + context.ActivateExtensionContextRecursive(); + context.DeactivateExtensionContext(); + context.DeactivateExtensionContext(); var layer = findFxLayer(prefab, "Base Layer"); diff --git a/UnitTests~/Animation/TrackObjectRenamesContextTests.cs b/UnitTests~/Animation/TrackObjectRenamesContextTests.cs deleted file mode 100644 index 554d05d2..00000000 --- a/UnitTests~/Animation/TrackObjectRenamesContextTests.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Linq; -using nadena.dev.modular_avatar.animation; -using NUnit.Framework; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; - -#if MA_VRCSDK3_AVATARS -using VRC.SDK3.Avatars.Components; -#endif - -namespace modular_avatar_tests -{ - using UnityObject = UnityEngine.Object; - - public class TrackObjectRenamesContextTests : TestBase - { - [Test] - public void testBasicContextInitialization() - { - var av = CreateRoot("root"); - - var bc = CreateContext(av); - var toc = new AnimationServicesContext(); - - toc.OnActivate(bc); - toc.OnDeactivate(bc); - } - - - [Test] - public void TracksSimpleRenames() - { - var root = CreateRoot("root"); - var a = CreateChild(root, "a"); - - var toc = new AnimationServicesContext(); - toc.OnActivate(CreateContext(root)); - Assert.AreEqual("a", toc.PathMappings.MapPath("a")); - a.name = "b"; - toc.PathMappings.ClearCache(); - Assert.AreEqual("b", toc.PathMappings.MapPath("a")); - } - - [Test] - public void TracksObjectMoves() - { - var root = CreateRoot("root"); - var a = CreateChild(root, "a"); - var b = CreateChild(root, "b"); - - var toc = new AnimationServicesContext(); - toc.OnActivate(CreateContext(root)); - - Assert.AreEqual("a", toc.PathMappings.MapPath("a")); - a.transform.parent = b.transform; - toc.PathMappings.ClearCache(); - Assert.AreEqual("b/a", toc.PathMappings.MapPath("a")); - } - - [Test] - public void TracksCollapses() - { - var root = CreateRoot("root"); - var a = CreateChild(root, "a"); - var b = CreateChild(a, "b"); - var c = CreateChild(b, "c"); - - var toc = new AnimationServicesContext(); - toc.OnActivate(CreateContext(root)); - - toc.PathMappings.MarkRemoved(b); - c.transform.parent = a.transform; - UnityObject.DestroyImmediate(b); - - Assert.AreEqual("a/c", toc.PathMappings.MapPath("a/b/c")); - } - - [Test] - public void TransformLookthrough() - { - var root = CreateRoot("root"); - var a = CreateChild(root, "a"); - var b = CreateChild(a, "b"); - var c = CreateChild(b, "c"); - var d = CreateChild(c, "d"); - - var toc = new AnimationServicesContext(); - toc.OnActivate(CreateContext(root)); - - toc.PathMappings.MarkTransformLookthrough(b); - toc.PathMappings.MarkTransformLookthrough(c); - Assert.AreEqual("a/b/c", toc.PathMappings.MapPath("a/b/c")); - Assert.AreEqual("a", toc.PathMappings.MapPath("a/b/c", true)); - Assert.AreEqual("a/b/c/d", toc.PathMappings.MapPath("a/b/c/d", true)); - } - -#if MA_VRCSDK3_AVATARS - [Test] - public void TestAnimatorControllerUpdates() - { - var root = CreatePrefab("BasicObjectReferenceTest.prefab"); - var parent = root.transform.Find("parent").gameObject; - var child = parent.transform.Find("child").gameObject; - - var descriptor = root.GetComponent(); - var oldFx = descriptor.baseAnimationLayers.First(l => - l.type == VRCAvatarDescriptor.AnimLayerType.FX); - var oldIk = descriptor.specialAnimationLayers.First(l => - l.type == VRCAvatarDescriptor.AnimLayerType.IKPose); - - var toc = new AnimationServicesContext(); - var buildContext = CreateContext(root); - toc.OnActivate(buildContext); - toc.PathMappings.MarkTransformLookthrough(child); - - parent.name = "p2"; - - toc.OnDeactivate(buildContext); - - var newFx = buildContext.AvatarDescriptor.baseAnimationLayers.First(l => - l.type == VRCAvatarDescriptor.AnimLayerType.FX); - var newIk = buildContext.AvatarDescriptor.specialAnimationLayers.First(l => - l.type == VRCAvatarDescriptor.AnimLayerType.IKPose); - - Assert.AreNotEqual(oldFx.animatorController, newFx.animatorController); - Assert.AreNotEqual(oldIk.animatorController, newIk.animatorController); - - CheckClips(newFx.animatorController as AnimatorController); - CheckClips(newIk.animatorController as AnimatorController); - - void CheckClips(AnimatorController controller) - { - var clip = controller.layers[0].stateMachine.states[0].state.motion - as AnimationClip; - - foreach (var binding in AnimationUtility.GetCurveBindings(clip)) - { - if (binding.type == typeof(Transform)) - { - Assert.AreEqual("p2", binding.path); - } - else - { - Assert.AreEqual("p2/child", binding.path); - } - } - } - } -#endif - } -} \ No newline at end of file diff --git a/UnitTests~/Animation/TrackObjectRenamesContextTests.cs.meta b/UnitTests~/Animation/TrackObjectRenamesContextTests.cs.meta deleted file mode 100644 index 6d50ff12..00000000 --- a/UnitTests~/Animation/TrackObjectRenamesContextTests.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: e71c9edfe1c94c5c93603ce45a1e2310 -timeCreated: 1692516261 \ No newline at end of file