diff --git a/.github/ProjectRoot/vpm-manifest-2022.json b/.github/ProjectRoot/vpm-manifest-2022.json index 0c39da67..fa5803c6 100644 --- a/.github/ProjectRoot/vpm-manifest-2022.json +++ b/.github/ProjectRoot/vpm-manifest-2022.json @@ -4,7 +4,7 @@ "version": "3.7.4" }, "nadena.dev.ndmf": { - "version": "1.6.0" + "version": "1.7.0-alpha.0" } }, "locked": { @@ -19,7 +19,7 @@ "dependencies": {} }, "nadena.dev.ndmf": { - "version": "1.6.0" + "version": "1.7.0-alpha.0" } } } \ No newline at end of file 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; 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 @@ namespace nadena.dev.modular_avatar.core.editor { 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 @@ namespace nadena.dev.modular_avatar.core.editor { _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 @@ namespace nadena.dev.modular_avatar.core.editor // Ensure mesh retargeting looks through this _boneDatabase.AddMergedBone(sourceBone.transform); _boneDatabase.RetainMergedBone(sourceBone.transform); - _pathMappings.MarkTransformLookthrough(sourceBone); } return sourceBone; @@ -130,22 +130,14 @@ namespace nadena.dev.modular_avatar.core.editor { 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 @@ namespace nadena.dev.modular_avatar.core.editor } } - 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/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 57ad5dd0..00000000 --- a/Editor/Animation/AnimationServicesContext.cs +++ /dev/null @@ -1,125 +0,0 @@ -#region - -using System; -using System.Collections.Generic; -using System.Linq; -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 -{ - /// - /// 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 ReadableProperty _readableProperty; - - private Dictionary _selfProxies = new(); - - public void OnActivate(BuildContext context) - { - _context = context; - - _animationDatabase = new AnimationDatabase(); - _animationDatabase.OnActivate(context); - - _pathMappings = new PathMappings(); - _pathMappings.OnActivate(context, _animationDatabase); - - _readableProperty = new ReadableProperty(_context, _animationDatabase, this); - } - - 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; - } - } - - public IEnumerable<(EditorCurveBinding, string)> BoundReadableProperties => _readableProperty.BoundProperties; - - // HACK: This is a temporary crutch until we rework the entire animator services system - public void AddPropertyDefinition(AnimatorControllerParameter paramDef) - { -#if MA_VRCSDK3_AVATARS - if (!_context.AvatarDescriptor) return; - - var fx = (AnimatorController) - _context.AvatarDescriptor.baseAnimationLayers - .First(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX) - .animatorController; - - fx.parameters = fx.parameters.Concat(new[] { paramDef }).ToArray(); -#endif - } - - public string GetActiveSelfProxy(GameObject obj) - { - if (_selfProxies.TryGetValue(obj, out var paramName) && !string.IsNullOrEmpty(paramName)) return paramName; - - var path = PathMappings.GetObjectIdentifier(obj); - - paramName = _readableProperty.ForActiveSelf(path); - _selfProxies[obj] = paramName; - - return paramName; - } - - public bool ObjectHasAnimations(GameObject obj) - { - var path = PathMappings.GetObjectIdentifier(obj); - var clips = AnimationDatabase.ClipsForPath(path); - return clips != null && !clips.IsEmpty; - } - } -} \ 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/GameObjectDisableDelayPass.cs b/Editor/Animation/GameObjectDisableDelayPass.cs index 87692657..33e5b37f 100644 --- a/Editor/Animation/GameObjectDisableDelayPass.cs +++ b/Editor/Animation/GameObjectDisableDelayPass.cs @@ -2,6 +2,7 @@ using System.Linq; using nadena.dev.modular_avatar.core.editor; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEditor.Animations; using UnityEngine; @@ -18,14 +19,13 @@ namespace nadena.dev.modular_avatar.animation { protected override void Execute(BuildContext context) { - var asc = context.Extension(); - if (!asc.BoundReadableProperties.Any()) return; - - var fx = (AnimatorController)context.AvatarDescriptor.baseAnimationLayers - .FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX).animatorController; + var asc = context.Extension(); + var activeProxies = context.GetState().proxyProps; + if (activeProxies.Count == 0) return; + var fx = asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX]; if (fx == null) return; - + var nullMotion = new AnimationClip(); nullMotion.name = "NullMotion"; @@ -33,48 +33,31 @@ namespace nadena.dev.modular_avatar.animation blendTree.blendType = BlendTreeType.Direct; blendTree.useAutomaticThresholds = false; - blendTree.children = asc.BoundReadableProperties - .Select(prop => GenerateDelayChild(nullMotion, prop)) + blendTree.children = activeProxies + .Select(prop => GenerateDelayChild(nullMotion, (prop.Key, prop.Value))) .ToArray(); - var asm = new AnimatorStateMachine(); - var state = new AnimatorState(); - state.name = "DelayDisable"; - state.motion = blendTree; - state.writeDefaultValues = true; + var layer = fx.AddLayer(LayerPriority.Default, "DelayDisable"); + var state = layer.StateMachine.AddState("DelayDisable"); + layer.StateMachine.DefaultState = state; - asm.defaultState = state; - asm.states = new[] - { - new ChildAnimatorState - { - state = state, - position = Vector3.zero - } - }; - - fx.layers = fx.layers.Append(new AnimatorControllerLayer - { - name = "DelayDisable", - stateMachine = asm, - defaultWeight = 1, - blendingMode = AnimatorLayerBlendingMode.Override - }).ToArray(); + state.WriteDefaultValues = true; + state.Motion = asc.ControllerContext.Clone(blendTree); // Ensure the initial state of readable props matches the actual state of the gameobject - var parameters = fx.parameters; - var paramToIndex = parameters.Select((p, i) => (p, i)).ToDictionary(x => x.p.name, x => x.i); - foreach (var (binding, prop) in asc.BoundReadableProperties) + foreach (var controller in asc.ControllerContext.GetAllControllers()) { - var obj = asc.PathMappings.PathToObject(binding.path); - - if (obj != null && paramToIndex.TryGetValue(prop, out var index)) + foreach (var (binding, prop) in activeProxies) { - parameters[index].defaultFloat = obj.activeSelf ? 1 : 0; + var obj = asc.ObjectPathRemapper.GetObjectForPath(binding.path); + + if (obj != null && controller.Parameters.TryGetValue(prop, out var p)) + { + p.defaultFloat = obj.activeSelf ? 1 : 0; + controller.Parameters = controller.Parameters.SetItem(prop, p); + } } } - - fx.parameters = parameters; } private ChildMotion GenerateDelayChild(Motion nullMotion, (EditorCurveBinding, string) binding) 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/Animation/ReadableProperty.cs b/Editor/Animation/ReadableProperty.cs deleted file mode 100644 index 5931f6b6..00000000 --- a/Editor/Animation/ReadableProperty.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using nadena.dev.ndmf; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace nadena.dev.modular_avatar.animation -{ - internal class ReadableProperty - { - private readonly BuildContext _context; - private readonly AnimationDatabase _animDB; - private readonly AnimationServicesContext _asc; - private readonly Dictionary _alreadyBound = new(); - private long _nextIndex; - - public ReadableProperty(BuildContext context, AnimationDatabase animDB, AnimationServicesContext asc) - { - _context = context; - _animDB = animDB; - _asc = asc; - } - - public IEnumerable<(EditorCurveBinding, string)> BoundProperties => - _alreadyBound.Select(kv => (kv.Key, kv.Value)); - - /// - /// Creates an animator parameter which tracks the effective value of a property on a component. This only - /// tracks FX layer properties. - /// - /// - /// - public string ForBinding(string path, Type componentType, string property) - { - var ecb = new EditorCurveBinding - { - path = path, - type = componentType, - propertyName = property - }; - - if (_alreadyBound.TryGetValue(ecb, out var reader)) - { - return reader; - } - - var lastComponent = path.Split("/")[^1]; - var emuPropName = $"__MA/ReadableProp/{lastComponent}/{componentType}/{property}#{_nextIndex++}"; - - float initialValue = 0; - var gameObject = _asc.PathMappings.PathToObject(path); - Object component = componentType == typeof(GameObject) - ? gameObject - : gameObject?.GetComponent(componentType); - if (component != null) - { - var so = new SerializedObject(component); - var prop = so.FindProperty(property); - if (prop != null) - switch (prop.propertyType) - { - case SerializedPropertyType.Boolean: - initialValue = prop.boolValue ? 1 : 0; - break; - case SerializedPropertyType.Float: - initialValue = prop.floatValue; - break; - case SerializedPropertyType.Integer: - initialValue = prop.intValue; - break; - default: throw new NotImplementedException($"Property type {prop.type} not supported"); - } - } - - - _asc.AddPropertyDefinition(new AnimatorControllerParameter - { - defaultFloat = initialValue, - name = emuPropName, - type = AnimatorControllerParameterType.Float - }); - - BindProperty(ecb, emuPropName); - - _alreadyBound[ecb] = emuPropName; - - return emuPropName; - } - - private void BindProperty(EditorCurveBinding ecb, string propertyName) - { - var boundProp = new EditorCurveBinding - { - path = "", - type = typeof(Animator), - propertyName = propertyName - }; - - foreach (var clip in _animDB.ClipsForPath(ecb.path)) ProcessAnyClip(clip); - - void ProcessBlendTree(BlendTree blendTree) - { - foreach (var child in blendTree.children) - switch (child.motion) - { - case AnimationClip animationClip: - ProcessAnimationClip(animationClip); - break; - - case BlendTree subBlendTree: - ProcessBlendTree(subBlendTree); - break; - } - } - - void ProcessAnimationClip(AnimationClip animationClip) - { - var curve = AnimationUtility.GetEditorCurve(animationClip, ecb); - if (curve == null) return; - - AnimationUtility.SetEditorCurve(animationClip, boundProp, curve); - } - - void ProcessAnyClip(AnimationDatabase.ClipHolder clip) - { - switch (clip.CurrentClip) - { - case AnimationClip animationClip: - ProcessAnimationClip(animationClip); - break; - - case BlendTree blendTree: - ProcessBlendTree(blendTree); - break; - } - } - } - - public string ForActiveSelf(string path) - { - return ForBinding(path, typeof(GameObject), "m_IsActive"); - } - } -} \ No newline at end of file diff --git a/Editor/Animation/ReadableProperty.cs.meta b/Editor/Animation/ReadableProperty.cs.meta deleted file mode 100644 index e4399110..00000000 --- a/Editor/Animation/ReadableProperty.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 1074339e2a59465ba585cb8cbbc4a88c -timeCreated: 1719195449 \ No newline at end of file diff --git a/Editor/Animation/ReadablePropertyExtension.cs b/Editor/Animation/ReadablePropertyExtension.cs new file mode 100644 index 00000000..a4807d8b --- /dev/null +++ b/Editor/Animation/ReadablePropertyExtension.cs @@ -0,0 +1,82 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; +using UnityEditor; +using UnityEngine; + +namespace nadena.dev.modular_avatar.animation +{ + [DependsOnContext(typeof(AnimatorServicesContext))] + internal class ReadablePropertyExtension : IExtensionContext + { + // This is a temporary hack for GameObjectDelayDisablePass + public class Retained + { + public Dictionary proxyProps = new(); + } + + private AnimatorServicesContext? _asc; + private Retained _retained = null!; + + private AnimatorServicesContext asc => + _asc ?? throw new InvalidOperationException("ActiveSelfProxyExtension is not active"); + + private Dictionary proxyProps => _retained.proxyProps; + private int index; + + public IEnumerable<(EditorCurveBinding, string)> ActiveProxyProps => + proxyProps.Select(kvp => (kvp.Key, kvp.Value)); + + public string GetActiveSelfProxy(GameObject obj) + { + var path = asc.ObjectPathRemapper.GetVirtualPathForObject(obj); + var ecb = EditorCurveBinding.FloatCurve(path, typeof(GameObject), "m_IsActive"); + + if (proxyProps.TryGetValue(ecb, out var prop)) return prop; + + prop = $"__MA/ActiveSelfProxy/{obj.name}##{index++}"; + proxyProps[ecb] = prop; + + // Add prop to all animators + foreach (var animator in asc.ControllerContext.GetAllControllers()) + { + animator.Parameters = animator.Parameters.SetItem( + prop, + new AnimatorControllerParameter + { + name = prop, + type = AnimatorControllerParameterType.Float, + defaultFloat = obj.activeSelf ? 1 : 0 + } + ); + } + + return prop; + } + + public void OnActivate(BuildContext context) + { + _asc = context.Extension(); + _retained = context.GetState(); + } + + public void OnDeactivate(BuildContext context) + { + asc.AnimationIndex.EditClipsByBinding(proxyProps.Keys, clip => + { + foreach (var b in clip.GetFloatCurveBindings().ToList()) + { + if (proxyProps.TryGetValue(b, out var proxyProp)) + { + var curve = clip.GetFloatCurve(b); + clip.SetFloatCurve("", typeof(Animator), proxyProp, curve); + } + } + }); + } + } +} \ No newline at end of file diff --git a/Editor/Animation/ReadablePropertyExtension.cs.meta b/Editor/Animation/ReadablePropertyExtension.cs.meta new file mode 100644 index 00000000..93a503c5 --- /dev/null +++ b/Editor/Animation/ReadablePropertyExtension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 511cbc0373a2469192e0351e2222a203 +timeCreated: 1732496091 \ No newline at end of file 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; 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 @@ namespace nadena.dev.modular_avatar.core.editor var values = context.GetState()?.InitialValueOverrides ?? ImmutableDictionary.Empty; - foreach (var layer in context.AvatarDescriptor.baseAnimationLayers - .Concat(context.AvatarDescriptor.specialAnimationLayers)) + var asc = context.Extension(); + + foreach (var controller in asc.ControllerContext.GetAllControllers()) { - 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)) + var parameters = controller.Parameters; + foreach (var (name, parameter) in parameters) { - throw new Exception("Leaked unexpected controller: " + layer.animatorController + " (type " + layer.animatorController?.GetType() + ")"); - } + if (!values.TryGetValue(name, out var defaultValue)) continue; - var parameters = controller.parameters; - for (int i = 0; i < parameters.Length; i++) - { - if (!values.TryGetValue(parameters[i].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/BlendshapeSyncAnimationProcessor.cs b/Editor/BlendshapeSyncAnimationProcessor.cs index c4db4847..f22d5e80 100644 --- a/Editor/BlendshapeSyncAnimationProcessor.cs +++ b/Editor/BlendshapeSyncAnimationProcessor.cs @@ -1,12 +1,14 @@ #if MA_VRCSDK3_AVATARS +#nullable enable + +using System; using System.Collections.Generic; +using System.Linq; using nadena.dev.modular_avatar.editor.ErrorReporting; +using nadena.dev.ndmf.animator; using UnityEditor; -using UnityEditor.Animations; using UnityEngine; -using VRC.SDK3.Avatars.Components; -using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { @@ -17,11 +19,16 @@ namespace nadena.dev.modular_avatar.core.editor */ internal class BlendshapeSyncAnimationProcessor { - private BuildContext _context; - private Dictionary _motionCache; + private readonly ndmf.BuildContext _context; private Dictionary> _bindingMappings; - private struct SummaryBinding + internal BlendshapeSyncAnimationProcessor(ndmf.BuildContext context) + { + _context = context; + _bindingMappings = new Dictionary>(); + } + + private struct SummaryBinding : IEquatable { private const string PREFIX = "blendShape."; public string path; @@ -33,71 +40,76 @@ namespace nadena.dev.modular_avatar.core.editor this.propertyName = PREFIX + blendShape; } - public static SummaryBinding FromEditorBinding(EditorCurveBinding binding) + public static SummaryBinding? FromEditorBinding(EditorCurveBinding binding) { if (binding.type != typeof(SkinnedMeshRenderer) || !binding.propertyName.StartsWith(PREFIX)) { - return new SummaryBinding(); + return null; } return new SummaryBinding(binding.path, binding.propertyName.Substring(PREFIX.Length)); } + + public EditorCurveBinding ToEditorCurveBinding() + { + return EditorCurveBinding.FloatCurve( + path, + typeof(SkinnedMeshRenderer), + propertyName + ); + } + + public bool Equals(SummaryBinding other) + { + return path == other.path && propertyName == other.propertyName; + } + + public override bool Equals(object? obj) + { + return obj is SummaryBinding other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(path, propertyName); + } } - public void OnPreprocessAvatar(BuildContext context) + public void OnPreprocessAvatar() { - _context = context; - var avatarGameObject = context.AvatarRootObject; - var animDb = _context.AnimationDatabase; - - var avatarDescriptor = context.AvatarDescriptor; + var avatarGameObject = _context.AvatarRootObject; + var animDb = _context.Extension().AnimationIndex; + _bindingMappings = new Dictionary>(); - _motionCache = new Dictionary(); var components = avatarGameObject.GetComponentsInChildren(true); if (components.Length == 0) return; - var layers = avatarDescriptor.baseAnimationLayers; - var fxIndex = -1; - AnimatorController controller = null; - for (int i = 0; i < layers.Length; i++) - { - if (layers[i].type == VRCAvatarDescriptor.AnimLayerType.FX && !layers[i].isDefault) - { - if (layers[i].animatorController is AnimatorController c && c != null) - { - fxIndex = i; - controller = c; - break; - } - } - } - - if (controller == null) - { - // Nothing to do, return - } - foreach (var component in components) { BuildReport.ReportingObject(component, () => ProcessComponent(avatarGameObject, component)); } - // Walk and transform all clips - animDb.ForeachClip(clip => + var clips = new HashSet(); + foreach (var key in _bindingMappings.Keys) { - if (clip.CurrentClip is AnimationClip anim) - { - BuildReport.ReportingObject(clip.CurrentClip, - () => { clip.CurrentClip = TransformMotion(anim); }); - } - }); + var ecb = key.ToEditorCurveBinding(); + clips.UnionWith(animDb.GetClipsForBinding(ecb)); + } + + // Walk and transform all clips + foreach (var clip in clips) + { + ProcessClip(clip); + } } private void ProcessComponent(GameObject avatarGameObject, ModularAvatarBlendshapeSync component) { var targetObj = RuntimeUtil.RelativePath(avatarGameObject, component.gameObject); + if (targetObj == null) return; + foreach (var binding in component.Bindings) { var refObj = binding.ReferenceMesh.Get(component); @@ -106,6 +118,7 @@ namespace nadena.dev.modular_avatar.core.editor if (refSmr == null) continue; var refPath = RuntimeUtil.RelativePath(avatarGameObject, refObj); + if (refPath == null) continue; var srcBinding = new SummaryBinding(refPath, binding.Blendshape); @@ -123,108 +136,20 @@ namespace nadena.dev.modular_avatar.core.editor } } - Motion TransformMotion(Motion motion) + private void ProcessClip(VirtualClip clip) { - if (motion == null) return null; - if (_motionCache.TryGetValue(motion, out var cached)) return cached; - - switch (motion) + foreach (var binding in clip.GetFloatCurveBindings().ToList()) { - case AnimationClip clip: - { - motion = ProcessClip(clip); - - break; - } - - case BlendTree tree: - { - bool anyChanged = false; - var children = tree.children; - - for (int i = 0; i < children.Length; i++) - { - var newM = TransformMotion(children[i].motion); - if (newM != children[i].motion) - { - anyChanged = true; - children[i].motion = newM; - } - } - - if (anyChanged) - { - var newTree = new BlendTree(); - EditorUtility.CopySerialized(tree, newTree); - _context.SaveAsset(newTree); - - newTree.children = children; - motion = newTree; - } - - break; - } - default: - Debug.LogWarning($"Ignoring unsupported motion type {motion.GetType()}"); - break; - } - - _motionCache[motion] = motion; - return motion; - } - - AnimationClip ProcessClip(AnimationClip origClip) - { - var clip = origClip; - EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip); - - foreach (var binding in bindings) - { - if (!_bindingMappings.TryGetValue(SummaryBinding.FromEditorBinding(binding), out var dstBindings)) + var srcBinding = SummaryBinding.FromEditorBinding(binding); + if (srcBinding == null || !_bindingMappings.TryGetValue(srcBinding.Value, out var dstBindings)) { continue; } - if (clip == origClip) - { - clip = Object.Instantiate(clip); - } - + var curve = clip.GetFloatCurve(binding); foreach (var dst in dstBindings) { - clip.SetCurve(dst.path, typeof(SkinnedMeshRenderer), dst.propertyName, - AnimationUtility.GetEditorCurve(origClip, binding)); - } - } - - return clip; - } - - IEnumerable AllStates(AnimatorController controller) - { - HashSet visitedStateMachines = new HashSet(); - Queue stateMachines = new Queue(); - - foreach (var layer in controller.layers) - { - if (layer.stateMachine != null) - stateMachines.Enqueue(layer.stateMachine); - } - - while (stateMachines.Count > 0) - { - var next = stateMachines.Dequeue(); - if (visitedStateMachines.Contains(next)) continue; - visitedStateMachines.Add(next); - - foreach (var state in next.states) - { - yield return state.state; - } - - foreach (var sm in next.stateMachines) - { - stateMachines.Enqueue(sm.stateMachine); + clip.SetFloatCurve(dst.ToEditorCurveBinding(), curve); } } } diff --git a/Editor/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/MergeAnimatorProcessor.cs b/Editor/MergeAnimatorProcessor.cs index aa4dbcbc..47429d62 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 @@ namespace nadena.dev.modular_avatar.core.editor { 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 @@ namespace nadena.dev.modular_avatar.core.editor 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); + 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 beforeOriginal) + bool? writeDefaults = null; + if (wdStateCounter.Count == 1) writeDefaults = wdStateCounter.First().Key; + + foreach (var component in sorted) { - MergeSingle(context, session, component); - } - - if (defaultControllers_.TryGetValue(layerType, out var defaultController) && - defaultController.layers.Length > 0) - { - session.AddController("", defaultController, null, forceFirstLayerWeight: true); - } - - foreach (var component in afterOriginal) - { - 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.CloneDistinct(merge.animator); + string basePath; if (merge.pathMode == MergeAnimatorPathMode.Relative) { @@ -145,200 +112,60 @@ namespace nadena.dev.modular_avatar.core.editor 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); + foreach (var l in clonedController.Layers) + { + if (initialWriteDefaults != null) + { + foreach (var s in l.StateMachine.AllStates()) + { + s.WriteDefaultValues = initialWriteDefaults.Value; + } + } + controller.AddLayer(new LayerPriority(merge.layerPriority), l); + } + foreach (var (name, parameter) in clonedController.Parameters) + { + 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; + } + + controller.Parameters = controller.Parameters.Add(name, parameter); + } + 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) - { - session.MergeTypes(types); - } - - for (int i = 0; i < layers.Length; i++) - { - if (mergeSessions.TryGetValue(layers[i].type, out var session)) - { - if (layers[i].type == VRCAvatarDescriptor.AnimLayerType.Gesture && layers[i].isDefault) - { - // 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) - ); - } - - 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); - } - } - } - - 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) - { - // 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 - ) - { - continue; - } - - stateMachineQueue.Enqueue(layer.stateMachine); - } - - 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 - { - 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; - } } } diff --git a/Editor/MergeArmatureHook.cs b/Editor/MergeArmatureHook.cs index e9896fdb..b6ba5870 100644 --- a/Editor/MergeArmatureHook.cs +++ b/Editor/MergeArmatureHook.cs @@ -31,8 +31,8 @@ using VRC.SDK3.Dynamics.PhysBone.Components; 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 @@ namespace nadena.dev.modular_avatar.core.editor #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) { @@ -135,7 +136,68 @@ namespace nadena.dev.modular_avatar.core.editor } } - 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) @@ -294,6 +356,7 @@ namespace nadena.dev.modular_avatar.core.editor _activeRetargeter.FixupAnimations(); thisPassAdded.UnionWith(_activeRetargeter.AddedGameObjects.Select(x => x.transform)); + transformLookthrough.UnionWith(_activeRetargeter.AddedGameObjects.Select(x => x.transform)); } /** @@ -357,7 +420,7 @@ namespace nadena.dev.modular_avatar.core.editor BoneDatabase.AddMergedBone(mergedSrcBone.transform); BoneDatabase.RetainMergedBone(mergedSrcBone.transform); - PathMappings.MarkTransformLookthrough(mergedSrcBone); + transformLookthrough.Add(mergedSrcBone.transform); thisPassAdded.Add(mergedSrcBone.transform); } @@ -372,7 +435,7 @@ namespace nadena.dev.modular_avatar.core.editor 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..fa8b113e 100644 --- a/Editor/MergeBlendTreePass.cs +++ b/Editor/MergeBlendTreePass.cs @@ -2,11 +2,9 @@ #region -using System; using System.Collections.Generic; -using nadena.dev.modular_avatar.animation; using nadena.dev.ndmf; -using nadena.dev.ndmf.util; +using nadena.dev.ndmf.animator; using UnityEditor.Animations; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -20,56 +18,49 @@ namespace nadena.dev.modular_avatar.core.editor 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() - { - name = ALWAYS_ONE, - type = AnimatorControllerParameterType.Float, - defaultFloat = 1 - }); + foreach (var name in _parameterNames) + { + if (fx.Parameters.ContainsKey(name)) continue; - foreach (var name in _parameterNames) + fx.Parameters = fx.Parameters.SetItem(name, new AnimatorControllerParameter() { - 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; + name = name, + type = AnimatorControllerParameterType.Float, + defaultFloat = 0.0f + }); } } - 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 +70,60 @@ namespace nadena.dev.modular_avatar.core.editor } 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 +131,22 @@ namespace nadena.dev.modular_avatar.core.editor } } - 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 System.Linq; 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 @@ namespace nadena.dev.modular_avatar.core.editor 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 @@ namespace nadena.dev.modular_avatar.core.editor 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/OptimizationPasses/ConstraintConverterPass.cs b/Editor/OptimizationPasses/ConstraintConverterPass.cs index 2dfa9a7e..743a1b32 100644 --- a/Editor/OptimizationPasses/ConstraintConverterPass.cs +++ b/Editor/OptimizationPasses/ConstraintConverterPass.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +#nullable enable + +using System.Collections.Generic; using nadena.dev.ndmf; using UnityEditor; #if MA_VRCSDK3_AVATARS_3_7_0_OR_NEWER @@ -6,7 +8,7 @@ using UnityEngine; using UnityEngine.Animations; using VRC.SDK3.Avatars; using System.Linq; -using nadena.dev.modular_avatar.animation; +using nadena.dev.ndmf.animator; using VRC.Dynamics; #endif @@ -60,7 +62,7 @@ namespace nadena.dev.modular_avatar.core.editor AvatarDynamicsSetup.DoConvertUnityConstraints(targetConstraintComponents, null, false); - var asc = context.Extension(); + var asc = context.Extension(); // Also look for preexisting VRCConstraints so we can go fix up any broken animation clips from people who // clicked auto fix :( @@ -70,24 +72,20 @@ namespace nadena.dev.modular_avatar.core.editor var targetPaths = constraintGameObjects .Union(existingVRCConstraints) - .Select(c => asc.PathMappings.GetObjectIdentifier(c)) + .Select(c => asc.ObjectPathRemapper.GetVirtualPathForObject(c)) .ToHashSet(); // Update animation clips - var clips = targetPaths.SelectMany(tp => asc.AnimationDatabase.ClipsForPath(tp)) + + var clips = targetPaths.SelectMany(tp => asc.AnimationIndex.GetClipsForObjectPath(tp)) .ToHashSet(); foreach (var clip in clips) RemapSingleClip(clip, targetPaths); } - private void RemapSingleClip(AnimationDatabase.ClipHolder clip, HashSet targetPaths) + private void RemapSingleClip(VirtualClip clip, HashSet targetPaths) { - var motion = clip.CurrentClip as AnimationClip; - if (motion == null) return; - - var bindings = AnimationUtility.GetCurveBindings(motion); - var toUpdateBindings = new List(); - var toUpdateCurves = new List(); + var bindings = clip.GetFloatCurveBindings().ToList(); foreach (var ecb in bindings) { @@ -102,20 +100,11 @@ namespace nadena.dev.modular_avatar.core.editor type = newType, propertyName = newProp }; - var curve = AnimationUtility.GetEditorCurve(motion, ecb); - if (curve != null) - { - toUpdateBindings.Add(newBinding); - toUpdateCurves.Add(curve); - - toUpdateBindings.Add(ecb); - toUpdateCurves.Add(null); - } + var curve = clip.GetFloatCurve(ecb); + clip.SetFloatCurve(newBinding, curve); + clip.SetFloatCurve(newBinding, null); } } - - if (toUpdateBindings.Count == 0) return; - AnimationUtility.SetEditorCurves(motion, toUpdateBindings.ToArray(), toUpdateCurves.ToArray()); } #else diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index ffc77033..330f5df2 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -5,7 +5,9 @@ using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core.editor.plugin; using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using nadena.dev.ndmf.fluent; +using nadena.dev.ndmf.util; using UnityEngine; using Object = UnityEngine.Object; @@ -35,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", @@ -46,40 +46,57 @@ namespace nadena.dev.modular_avatar.core.editor.plugin 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(AnimationServicesContext), _s2 => + seq.WithRequiredExtension(typeof(AnimatorServicesContext), _s2 => { #if MA_VRCSDK3_AVATARS - seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute()) - .PreviewingWith(new ShapeChangerPreview(), new ObjectSwitcherPreview(), new MaterialSetterPreview()); + 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()) + .PreviewingWith(new ShapeChangerPreview(), new ObjectSwitcherPreview(), + new MaterialSetterPreview()); + }); + seq.Run(GameObjectDelayDisablePass.Instance); // 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.Run(BoneProxyPluginPass.Instance); + #if MA_VRCSDK3_AVATARS seq.Run(VisibleHeadAccessoryPluginPass.Instance); #endif + seq.Run("World Fixed Object", ctx => new WorldFixedObjectProcessor().Process(ctx) ); + seq.Run(ReplaceObjectPluginPass.Instance); + #if MA_VRCSDK3_AVATARS seq.Run(BlendshapeSyncAnimationPluginPass.Instance); - seq.Run(GameObjectDelayDisablePass.Instance); #endif + seq.Run(ConstraintConverterPass.Instance); + + seq.Run("Prune empty animator layers", + ctx => { ctx.Extension().RemoveEmptyLayers(); }); + seq.Run("Harmonize animator parameter types", + ctx => { ctx.Extension().HarmonizeParameterTypes(); }); }); #if MA_VRCSDK3_AVATARS seq.Run(PhysbonesBlockerPluginPass.Instance); @@ -240,7 +257,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin { protected override void Execute(ndmf.BuildContext context) { - new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(MAContext(context)); + new BlendshapeSyncAnimationProcessor(context).OnPreprocessAvatar(); } } diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs index 01e12505..a6334612 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs @@ -31,7 +31,7 @@ namespace nadena.dev.modular_avatar.core.editor { if (_asc != null) { - return _asc.GetActiveSelfProxy(obj); + return _rpe.GetActiveSelfProxy(obj); } else { diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs index 228f215c..986499fe 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Linq; using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core.editor.Simulator; +using nadena.dev.ndmf.animator; using nadena.dev.ndmf.preview; using UnityEngine; @@ -17,7 +18,9 @@ namespace nadena.dev.modular_avatar.core.editor { private readonly ComputeContext _computeContext; private readonly ndmf.BuildContext _context; - private readonly AnimationServicesContext _asc; + private readonly AnimatorServicesContext _asc; + private readonly ReadablePropertyExtension _rpe; + private Dictionary _simulationInitialStates; public const string BlendshapePrefix = "blendShape."; @@ -34,7 +37,8 @@ namespace nadena.dev.modular_avatar.core.editor { _computeContext = ComputeContext.NullContext; _context = context; - _asc = context.Extension(); + _asc = context.Extension(); + _rpe = context.Extension(); _simulationInitialStates = null; } @@ -145,7 +149,7 @@ namespace nadena.dev.modular_avatar.core.editor /// private void AnalyzeConstants(Dictionary shapes) { - var asc = _context?.Extension(); + var asc = _context?.Extension(); HashSet toggledObjects = new(); if (asc == null) return; @@ -160,7 +164,10 @@ namespace nadena.dev.modular_avatar.core.editor { foreach (var condition in actionGroup.ControllingConditions) if (condition.ReferenceObject != null && !toggledObjects.Contains(condition.ReferenceObject)) - condition.IsConstant = asc.AnimationDatabase.ClipsForPath(asc.PathMappings.GetObjectIdentifier(condition.ReferenceObject)).IsEmpty; + condition.IsConstant = !asc.AnimationIndex.GetClipsForObjectPath( + asc.ObjectPathRemapper.GetVirtualPathForObject(condition.ReferenceObject) ?? + "___NONEXISTENT___" + ).Any(); // Remove redundant active conditions. actionGroup.ControllingConditions.RemoveAll(c => c.IsConstant && c.InitiallyActive); @@ -187,7 +194,7 @@ namespace nadena.dev.modular_avatar.core.editor /// private void ResolveToggleInitialStates(Dictionary groups) { - var asc = _context?.Extension(); + var asc = _context?.Extension(); Dictionary propStates = new(); Dictionary nextPropStates = new(); diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs index 00b4173f..4ecc35b2 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using nadena.dev.modular_avatar.animation; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEditor.Animations; using UnityEngine; @@ -23,8 +24,8 @@ namespace nadena.dev.modular_avatar.core.editor // Properties that are being driven, either by foreign animations or Object Toggles private HashSet activeProps = new(); - - private AnimationClip _initialStateClip; + + private VirtualClip _initialStateClip; private bool _writeDefaults; public ReactiveObjectPass(ndmf.BuildContext context) @@ -38,7 +39,14 @@ namespace nadena.dev.modular_avatar.core.editor // 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); @@ -60,7 +68,7 @@ namespace nadena.dev.modular_avatar.core.editor private void GenerateActiveSelfProxies(Dictionary shapes) { - var asc = context.Extension(); + var rpe = context.Extension(); foreach (var prop in shapes.Keys) { @@ -68,7 +76,7 @@ namespace nadena.dev.modular_avatar.core.editor { // Ensure a proxy exists for each object we're going to be toggling. // TODO: is this still needed? - asc.GetActiveSelfProxy(go); + rpe.GetActiveSelfProxy(go); } } } @@ -91,19 +99,19 @@ namespace nadena.dev.modular_avatar.core.editor private void ProcessInitialStates(Dictionary initialStates, Dictionary shapes) { - var asc = context.Extension(); + var asc = context.Extension(); + var rpe = context.Extension(); // We need to track _two_ initial states: the initial state we'll apply at build time (which applies // when animations are disabled) and the animation base state. Confusingly, the animation base state // should be the state that is currently applied to the object... - - var clips = context.Extension().AnimationDatabase; - var initialStateHolder = clips.ClipsForPath(ReactiveObjectPrepass.TAG_PATH).FirstOrDefault(); - if (initialStateHolder == null) return; - _initialStateClip = new AnimationClip(); - _initialStateClip.name = "Reactive Component Defaults"; - initialStateHolder.CurrentClip = _initialStateClip; + var clips = asc.AnimationIndex; + _initialStateClip = clips.GetClipsForObjectPath(ReactiveObjectPrepass.TAG_PATH).FirstOrDefault(); + + if (_initialStateClip == null) return; + + _initialStateClip.Name = "Reactive Component Defaults"; foreach (var (key, initialState) in initialStates) { @@ -186,17 +194,17 @@ namespace nadena.dev.modular_avatar.core.editor curve.AddKey(0, f); curve.AddKey(1, f); - AnimationUtility.SetEditorCurve(_initialStateClip, binding, curve); + _initialStateClip.SetFloatCurve(binding, curve); if (componentType == typeof(GameObject) && key.PropertyName == "m_IsActive") { binding = EditorCurveBinding.FloatCurve( "", typeof(Animator), - asc.GetActiveSelfProxy((GameObject)key.TargetObject) + rpe.GetActiveSelfProxy((GameObject)key.TargetObject) ); - AnimationUtility.SetEditorCurve(_initialStateClip, binding, curve); + _initialStateClip.SetFloatCurve(binding, curve); } } else if (animBaseState is Object obj) @@ -206,8 +214,8 @@ namespace nadena.dev.modular_avatar.core.editor componentType, key.PropertyName ); - - AnimationUtility.SetObjectReferenceCurve(_initialStateClip, binding, new [] + + _initialStateClip.SetObjectCurve(binding, new[] { new ObjectReferenceKeyframe() { @@ -308,7 +316,7 @@ namespace nadena.dev.modular_avatar.core.editor private AnimatorStateMachine GenerateStateMachine(AnimatedProperty info) { - var asc = context.Extension(); + var asc = context.Extension(); var asm = new AnimatorStateMachine(); // Workaround for the warning: "'.' is not allowed in State name" @@ -333,7 +341,6 @@ namespace nadena.dev.modular_avatar.core.editor position = new Vector3(x, y), state = initialState }); - asc.AnimationDatabase.RegisterState(states[^1].state); var lastConstant = info.actionGroups.FindLastIndex(agk => agk.IsConstant); var transitionBuffer = new List<(AnimatorState, List)>(); @@ -363,7 +370,7 @@ namespace nadena.dev.modular_avatar.core.editor clip.name = "Property Overlay controlled by " + group.ControllingConditions[0].DebugName + " " + group.Value; - var conditions = GetTransitionConditions(asc, group); + var conditions = GetTransitionConditions(group); foreach (var (st, transitions) in transitionBuffer) { @@ -407,7 +414,6 @@ namespace nadena.dev.modular_avatar.core.editor position = new Vector3(x, y), state = state }); - asc.AnimationDatabase.RegisterState(states[^1].state); var transitionList = new List(); transitionBuffer.Add((state, transitionList)); @@ -488,7 +494,7 @@ namespace nadena.dev.modular_avatar.core.editor }; } - private AnimatorCondition[] GetTransitionConditions(AnimationServicesContext asc, ReactionRule group) + private AnimatorCondition[] GetTransitionConditions(ReactionRule group) { var conditions = new List(); @@ -574,8 +580,8 @@ namespace nadena.dev.modular_avatar.core.editor if (key.TargetObject is GameObject targetObject && key.PropertyName == "m_IsActive") { - var asc = context.Extension(); - var propName = asc.GetActiveSelfProxy(targetObject); + var rpe = context.Extension(); + var propName = rpe.GetActiveSelfProxy(targetObject); binding = EditorCurveBinding.FloatCurve("", typeof(Animator), propName); AnimationUtility.SetEditorCurve(clip, binding, curve); } @@ -586,47 +592,29 @@ namespace nadena.dev.modular_avatar.core.editor private void ApplyController(AnimatorStateMachine asm, string layerName) { - var fx = FindFxController(); - - if (fx.animatorController == null) + var asc = context.Extension(); + var fx = asc.ControllerContext[ + VRCAvatarDescriptor.AnimLayerType.FX + ]; + + if (fx == null) { throw new InvalidOperationException("No FX layer found"); } - - if (!context.IsTemporaryAsset(fx.animatorController)) - { - throw new InvalidOperationException("FX layer is not a temporary asset"); - } - if (!(fx.animatorController is AnimatorController animController)) + foreach (var paramName in initialValues.Keys.Except(fx.Parameters.Keys)) { - throw new InvalidOperationException("FX layer is not an animator controller"); - } - - var paramList = animController.parameters.ToList(); - var paramSet = paramList.Select(p => p.name).ToHashSet(); - - foreach (var paramName in initialValues.Keys.Except(paramSet)) - { - paramList.Add(new AnimatorControllerParameter() + var parameter = new AnimatorControllerParameter { name = paramName, type = AnimatorControllerParameterType.Float, defaultFloat = initialValues[paramName], // TODO - }); - paramSet.Add(paramName); + }; + fx.Parameters = fx.Parameters.SetItem(paramName, parameter); } - animController.parameters = paramList.ToArray(); - - animController.layers = animController.layers.Append( - new AnimatorControllerLayer - { - stateMachine = asm, - name = "RC " + layerName, - defaultWeight = 1 - } - ).ToArray(); + fx.AddLayer(LayerPriority.Default, "RC " + layerName).StateMachine = + asc.ControllerContext.Clone(asm); } private VRCAvatarDescriptor.CustomAnimLayer FindFxController() 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 @@ namespace nadena.dev.modular_avatar.core.editor 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 02de614c..7c63f9d0 100644 --- a/Editor/RenameParametersHook.cs +++ b/Editor/RenameParametersHook.cs @@ -6,13 +6,12 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -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; -using UnityEngine.Profiling; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.ScriptableObjects; using VRC.SDK3.Dynamics.Contact.Components; @@ -55,6 +54,43 @@ namespace nadena.dev.modular_avatar.core.editor { 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.CloneDistinct(mama.animator, mama.layerType); + 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 +199,10 @@ namespace nadena.dev.modular_avatar.core.editor if (!context.AvatarDescriptor) return; _context = context; + + var stash = _context.PluginBuildContext.GetState(); + var asc = _context.PluginBuildContext.Extension(); + stash.AnimatorServices = asc; var syncParams = WalkTree(avatar); @@ -368,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 => { @@ -389,11 +423,13 @@ namespace nadena.dev.modular_avatar.core.editor 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 +440,12 @@ namespace nadena.dev.modular_avatar.core.editor 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; @@ -497,28 +537,6 @@ namespace nadena.dev.modular_avatar.core.editor 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) { @@ -537,113 +555,70 @@ namespace nadena.dev.modular_avatar.core.editor }); } - private void ProcessAnimator(AnimatorController controller, - ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) + private void ProcessVirtualAnimatorController(VirtualAnimatorController controller, + ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remap) { - if (remaps.IsEmpty) return; + foreach (var node in controller.AllReachableNodes()) + { + switch (node) + { + case VirtualStateMachine vsm: ProcessStateMachine(vsm, remap); break; + 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; + } + } + + var newParameters = controller.Parameters.Clear(); + + foreach (var (name, parameter) in controller.Parameters) + { + if (remap.TryGetValue((ParameterNamespace.Animator, name), out var newParam)) + { + newParameters = newParameters.Add(newParam.ParameterName, parameter); + } + else + { + newParameters = newParameters.Add(name, parameter); + } + } - var visited = new HashSet(); - var queue = new Queue(); - - - var parameters = controller.parameters; - for (int i = 0; i < parameters.Length; i++) - { - if (remaps.TryGetValue((ParameterNamespace.Animator, parameters[i].name), out var newName)) - { - parameters[i].name = newName.ParameterName; - } - } - - controller.parameters = parameters; - - foreach (var layer in controller.layers) - { - if (layer.stateMachine != null) - { - queue.Enqueue(layer.stateMachine); - } - } - - Profiler.BeginSample("Walk animator graph"); - while (queue.Count > 0) - { - 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) - { - queue.Enqueue(sub.stateMachine); - - - foreach (var t in sm.GetStateMachineTransitions(sub.stateMachine)) - { - ProcessTransition(t, remaps); - } - } - - foreach (var st in sm.states) - { - ProcessState(st.state, remaps); - } - } - Profiler.EndSample(); + controller.Parameters = newParameters; } - private void ProcessState(AnimatorState state, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) + private void ProcessStateMachine(VirtualStateMachine vsm, + 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); - - foreach (var t in state.transitions) - { - ProcessTransition(t, remaps); - } - - foreach (var behavior in state.behaviours) + foreach (var behavior in vsm.Behaviours) { if (behavior is VRCAvatarParameterDriver driver) { ProcessDriver(driver, remaps); } } - - ProcessMotion(state.motion, remaps); } - private void ProcessMotion(Motion motion, - ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) + private void ProcessState(VirtualState state, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) { - if (motion is BlendTree blendTree) ProcessBlendtree(blendTree, 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); - if (motion is AnimationClip clip) ProcessClip(clip, remaps); + foreach (var behavior in state.Behaviours) + { + if (behavior is VRCAvatarParameterDriver driver) + { + ProcessDriver(driver, remaps); + } + } } - private void ProcessClip(AnimationClip clip, + private void ProcessClip(VirtualClip clip, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) { - var curveBindings = AnimationUtility.GetCurveBindings(clip); + var curveBindings = clip.GetFloatCurveBindings(); var bindingsToUpdate = new List(); var newCurves = new List(); @@ -653,48 +628,30 @@ namespace nadena.dev.modular_avatar.core.editor 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) @@ -710,19 +667,17 @@ namespace nadena.dev.modular_avatar.core.editor } } - 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/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/Editor/VisibleHeadAccessoryProcessor.cs b/Editor/VisibleHeadAccessoryProcessor.cs index 54bbcb1a..fb68d9a0 100644 --- a/Editor/VisibleHeadAccessoryProcessor.cs +++ b/Editor/VisibleHeadAccessoryProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using nadena.dev.modular_avatar.editor.ErrorReporting; +using nadena.dev.ndmf.animator; using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -164,33 +165,34 @@ namespace nadena.dev.modular_avatar.core.editor private void ProcessAnimations() { - var animdb = _context.AnimationDatabase; - var paths = _context.PathMappings; + var animdb = _context.PluginBuildContext.Extension(); + var paths = animdb.ObjectPathRemapper; Dictionary pathMappings = new Dictionary(); + HashSet clips = new(); foreach (var kvp in _boneShims) { - var orig = paths.GetObjectIdentifier(kvp.Key.gameObject); - var shim = paths.GetObjectIdentifier(kvp.Value.gameObject); + var orig = paths.GetVirtualPathForObject(kvp.Key.gameObject); + var shim = paths.GetVirtualPathForObject(kvp.Value.gameObject); pathMappings[orig] = shim; + + clips.UnionWith(animdb.AnimationIndex.GetClipsForObjectPath(orig)); } - animdb.ForeachClip(motion => + foreach (var clip in clips) { - if (!(motion.CurrentClip is AnimationClip clip)) return; - - var bindings = AnimationUtility.GetCurveBindings(clip); - foreach (var binding in bindings) + foreach (var binding in clip.GetFloatCurveBindings()) { - if (binding.type != typeof(Transform)) continue; - if (!pathMappings.TryGetValue(binding.path, out var newPath)) continue; - - var newBinding = binding; - newBinding.path = newPath; - AnimationUtility.SetEditorCurve(clip, newBinding, AnimationUtility.GetEditorCurve(clip, binding)); + if (binding.type == typeof(Transform) && pathMappings.TryGetValue(binding.path, out var newPath)) + { + clip.SetFloatCurve( + EditorCurveBinding.FloatCurve(newPath, typeof(Transform), binding.propertyName), + clip.GetFloatCurve(binding) + ); + } } - }); + } } private Transform CreateShim(Transform target) diff --git a/UnitTests~/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 class ActiveAnimationRetargeterTests : TestBase // 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 class ActiveAnimationRetargeterTests : TestBase 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~/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/AvatarMask/AvatarMaskTest.cs b/UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs index 09bf1825..1d4bc8ad 100644 --- a/UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs +++ b/UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs @@ -108,9 +108,10 @@ namespace modular_avatar_tests Assert.Greater(animRootIndex, parentIndex); Assert.Greater(bodyIndex, animRootIndex); - // Body is still enabled; the injected parent and parent/anim-root are not + // Body is still enabled; the injected parent is not. anim-root should be enabled, since the test mask has + // the root element enabled. Assert.IsTrue(state.transformMaskElements[parentIndex].Item2 < 0.5f); - Assert.IsTrue(state.transformMaskElements[animRootIndex].Item2 < 0.5f); + Assert.IsTrue(state.transformMaskElements[animRootIndex].Item2 > 0.5f); Assert.IsTrue(state.transformMaskElements[bodyIndex].Item2 > 0.5f); // Original paths are removed diff --git a/UnitTests~/Animation/LayerPruningTest.cs b/UnitTests~/Animation/LayerPruningTest.cs index c5af1638..40cd179c 100644 --- a/UnitTests~/Animation/LayerPruningTest.cs +++ b/UnitTests~/Animation/LayerPruningTest.cs @@ -32,8 +32,12 @@ namespace modular_avatar_tests Assert.AreEqual("L3", l3.name); Assert.AreEqual("L3.a", l3a.name); - Assert.AreEqual(2, l3.stateMachine.defaultState.behaviours.Length); - Assert.AreEqual(4, ((VRCAnimatorLayerControl)l3.stateMachine.defaultState.behaviours[0]).layer); + // The layer control behavior referencing the deleted layer should be removed + Assert.AreEqual(3, l3.stateMachine.defaultState.behaviours.Length); + Assert.AreEqual("2", ((VRCAnimatorLayerControl)l3.stateMachine.defaultState.behaviours[0]).debugString); + Assert.IsTrue(l3.stateMachine.defaultState.behaviours[1] is VRCAnimatorTrackingControl); + Assert.AreEqual("3", ((VRCAnimatorLayerControl)l3.stateMachine.defaultState.behaviours[2]).debugString); + Assert.AreEqual(3, ((VRCAnimatorLayerControl)l3.stateMachine.defaultState.behaviours[0]).layer); Assert.AreEqual(1, l3a.stateMachine.defaultState.behaviours.Length); Assert.AreEqual(3, ((VRCAnimatorLayerControl)l3a.stateMachine.defaultState.behaviours[0]).layer); diff --git a/UnitTests~/Animation/LayerPruningTest/AC3.controller b/UnitTests~/Animation/LayerPruningTest/AC3.controller index 4df78521..0f9264ba 100644 --- a/UnitTests~/Animation/LayerPruningTest/AC3.controller +++ b/UnitTests~/Animation/LayerPruningTest/AC3.controller @@ -1,5 +1,22 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!114 &-8943217745761122886 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1936262289, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + playable: 0 + layer: 0 + goalWeight: 0 + blendDuration: 0 + debugString: 3 --- !u!1107 &-6599268793956612610 AnimatorStateMachine: serializedVersion: 6 @@ -76,6 +93,7 @@ AnimatorState: - {fileID: 2645212092703721488} - {fileID: -4263003778027536188} - {fileID: 1637143755065747004} + - {fileID: -8943217745761122886} m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 m_WriteDefaultValues: 1 @@ -102,8 +120,8 @@ MonoBehaviour: m_Script: {fileID: -1936262289, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} m_Name: m_EditorClassIdentifier: - playable: 0 - layer: 0 + playable: 1 + layer: 1 goalWeight: 0 blendDuration: 0 debugString: 2 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 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.animation; 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 @@ namespace UnitTests.MergeAnimatorTests 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 @@ namespace UnitTests.MergeAnimatorTests 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 class PreexistingParamsTest : TestBase 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~/MergeAnimatorTests/SyncedLayerOverrideInSubStatemachine/SyncedLayerOverrideInSubStateMachine.cs b/UnitTests~/MergeAnimatorTests/SyncedLayerOverrideInSubStatemachine/SyncedLayerOverrideInSubStateMachine.cs index ecf3c88a..7c24c171 100644 --- a/UnitTests~/MergeAnimatorTests/SyncedLayerOverrideInSubStatemachine/SyncedLayerOverrideInSubStateMachine.cs +++ b/UnitTests~/MergeAnimatorTests/SyncedLayerOverrideInSubStatemachine/SyncedLayerOverrideInSubStateMachine.cs @@ -1,8 +1,10 @@ #if MA_VRCSDK3_AVATARS +using HarmonyLib; using modular_avatar_tests; using nadena.dev.ndmf; using NUnit.Framework; +using UnityEditor; using UnityEditor.Animations; using VRC.SDK3.Avatars.Components; @@ -16,7 +18,9 @@ namespace UnitTests.MergeAnimatorTests.SyncedLayerOverrideInSubStatemachine var controller = LoadAsset("syncedlayer.controller"); var root = CreateRoot("root"); var vrc_descriptor = root.GetComponent(); - + + vrc_descriptor.customizeAnimationLayers = true; + var layers = vrc_descriptor.baseAnimationLayers; for (int i = 0; i < layers.Length; i++) { diff --git a/UnitTests~/MergeAnimatorTests/TypeAdjustment/ConvertTransitionTypes.cs b/UnitTests~/MergeAnimatorTests/TypeAdjustment/ConvertTransitionTypes.cs index e0a024de..bd8c6549 100644 --- a/UnitTests~/MergeAnimatorTests/TypeAdjustment/ConvertTransitionTypes.cs +++ b/UnitTests~/MergeAnimatorTests/TypeAdjustment/ConvertTransitionTypes.cs @@ -237,7 +237,10 @@ public class ConvertTransitionTypes : TestBase var fx = (AnimatorController) FindFxController(prefab).animatorController; - Assert.AreEqual(fx.parameters[0].type, AnimatorControllerParameterType.Bool); + Assert.AreEqual( + AnimatorControllerParameterType.Bool, + fx.parameters.First(p => p.name == "bool").type + ); } void AssertTransitions(AnimatorControllerLayer layer, string src, string dest, int index, 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 System.Linq; 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 @@ namespace modular_avatar_tests.MergeArmatureTests 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 @@ namespace modular_avatar_tests.MergeArmatureTests 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 @@ namespace modular_avatar_tests.MergeArmatureTests 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~/ReactiveComponent/ObjectToggleTests.cs b/UnitTests~/ReactiveComponent/ObjectToggleTests.cs index 30bbfe65..9d4f816c 100644 --- a/UnitTests~/ReactiveComponent/ObjectToggleTests.cs +++ b/UnitTests~/ReactiveComponent/ObjectToggleTests.cs @@ -13,7 +13,7 @@ namespace UnitTests.ReactiveComponent internal class ObjectToggleTests : TestBase { [Test] - public void WhenObjectIsAlwaysOn_CorrectProxyParameterIsGenerated() + public void WhenObjectIsAlwaysOn_CorrectObjectStateIsSelected() { var root = CreateRoot("root"); var obj = CreateChild(root, "obj"); @@ -36,15 +36,6 @@ namespace UnitTests.ReactiveComponent }; AvatarProcessor.ProcessAvatar(root); - - // TODO: Ideally we should start using play mode testing for these things... - var fx = (AnimatorController)FindFxController(root).animatorController; - var readableProp = fx.parameters.FirstOrDefault( - p => p.name.StartsWith("__MA/ReadableProp/obj/UnityEngine.GameObject/m_IsActive") - ); - - Assert.IsNotNull(readableProp); - Assert.AreEqual(readableProp.defaultFloat, 0); Assert.IsFalse(obj.activeSelf); } 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 System.Linq; 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 @@ namespace modular_avatar_tests.RenameParametersTests var context = CreateContext(prefab); var maContext = context.ActivateExtensionContext().BuildContext; + context.ActivateExtensionContextRecursive(); var errors = ErrorReport.CaptureErrors( () => @@ -209,6 +211,7 @@ namespace modular_avatar_tests.RenameParametersTests var context = CreateContext(av); var maContext = context.ActivateExtensionContext().BuildContext; + context.ActivateExtensionContextRecursive(); var errors = ErrorReport.CaptureErrors(() => new RenameParametersHook().OnPreprocessAvatar(av, maContext)); @@ -243,6 +246,7 @@ namespace modular_avatar_tests.RenameParametersTests var context = CreateContext(av); var maContext = context.ActivateExtensionContext().BuildContext; + context.ActivateExtensionContextRecursive(); var errors = ErrorReport.CaptureErrors(() => new RenameParametersHook().OnPreprocessAvatar(av, maContext)); diff --git a/UnitTests~/ReplaceObject/ReplaceObjectTests.cs b/UnitTests~/ReplaceObject/ReplaceObjectTests.cs index 0f97e97d..5ea75b32 100644 --- a/UnitTests~/ReplaceObject/ReplaceObjectTests.cs +++ b/UnitTests~/ReplaceObject/ReplaceObjectTests.cs @@ -5,6 +5,7 @@ using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEngine; @@ -12,12 +13,12 @@ namespace modular_avatar_tests.ReplaceObject { public class ReplaceObjectTests : TestBase { - private PathMappings pathMappings; + private ObjectPathRemapper pathMappings; void Process(GameObject root) { var buildContext = new nadena.dev.ndmf.BuildContext(root, null); - pathMappings = buildContext.ActivateExtensionContext().PathMappings; + pathMappings = buildContext.ActivateExtensionContextRecursive().ObjectPathRemapper; new ReplaceObjectPass(buildContext).Process(); } @@ -162,7 +163,7 @@ namespace modular_avatar_tests.ReplaceObject Process(root); - Assert.AreEqual("replacement", pathMappings.MapPath("replacee")); + Assert.AreEqual("replacement", pathMappings.GetVirtualPathForObject(pathMappings.GetObjectForPath("replacee")!)); } } } \ No newline at end of file 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 @@ namespace modular_avatar_tests 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 @@ namespace modular_avatar_tests 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~/SyncedLayerHandling/AC2_SameLayerBehavior.controller b/UnitTests~/SyncedLayerHandling/AC2_SameLayerBehavior.controller new file mode 100644 index 00000000..a13634ee --- /dev/null +++ b/UnitTests~/SyncedLayerHandling/AC2_SameLayerBehavior.controller @@ -0,0 +1,218 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1107 &-1523926916476422360 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: sync + m_ChildStates: [] + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 0} +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AC2_SameLayerBehavior + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: Base Layer + m_StateMachine: {fileID: 487233783815393512} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: main + m_StateMachine: {fileID: 5038666263108125925} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: sync + m_StateMachine: {fileID: -1523926916476422360} + m_Mask: {fileID: 0} + m_Motions: + - serializedVersion: 2 + m_State: {fileID: 3457079820388324162} + m_Motion: {fileID: 7400000, guid: bf660ccc19e7d6e45849be7bdb273645, type: 2} + - serializedVersion: 2 + m_State: {fileID: 592244106348618204} + m_Motion: {fileID: 7400000, guid: b4473bdae48543347999d599bcef9efc, type: 2} + m_Behaviours: + - m_State: {fileID: 3457079820388324162} + m_StateMachineBehaviours: + - {fileID: 684586325619030559} + m_BlendingMode: 0 + m_SyncedLayerIndex: 1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} +--- !u!1107 &487233783815393512 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Base Layer + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 7429053045055472530} + m_Position: {x: 310.35324, y: 17.679626, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 7429053045055472530} +--- !u!1102 &592244106348618204 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: m2 + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 7104638764aea2b4682734684ce18d68, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!114 &684586325619030559 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1936262289, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + playable: 1 + layer: 1 + goalWeight: 0.5 + blendDuration: 0 + debugString: +--- !u!1102 &3457079820388324162 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: m1 + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 462fc0905374b744fb7a4ca1110e9c84, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &5038666263108125925 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: main + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 3457079820388324162} + m_Position: {x: 360, y: 10, z: 0} + - serializedVersion: 1 + m_State: {fileID: 592244106348618204} + m_Position: {x: 360, y: 120, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 3457079820388324162} +--- !u!1102 &7429053045055472530 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New State + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 0} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: diff --git a/UnitTests~/SyncedLayerHandling/AC2_SameLayerBehavior.controller.meta b/UnitTests~/SyncedLayerHandling/AC2_SameLayerBehavior.controller.meta new file mode 100644 index 00000000..0dc9ab5b --- /dev/null +++ b/UnitTests~/SyncedLayerHandling/AC2_SameLayerBehavior.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 26ef9d769605a1e46a5ad2768e28ad73 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab b/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab new file mode 100644 index 00000000..a276fb61 --- /dev/null +++ b/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab @@ -0,0 +1,373 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &521542568012153140 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2616869947308232229} + - component: {fileID: 8063907918941263456} + - component: {fileID: 4452914144664039463} + m_Layer: 0 + m_Name: MergedController_AC2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2616869947308232229 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 521542568012153140} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -1.6884441, y: 1.1147345, z: -4.914471} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 6479369535091914718} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8063907918941263456 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 521542568012153140} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + Name: + ViewPosition: {x: 0, y: 1.6, z: 0.2} + Animations: 0 + ScaleIPD: 1 + lipSync: 0 + lipSyncJawBone: {fileID: 0} + lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1} + lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1} + VisemeSkinnedMesh: {fileID: 0} + MouthOpenBlendShapeName: Facial_Blends.Jaw_Down + VisemeBlendShapes: [] + unityVersion: + portraitCameraPositionOffset: {x: 0, y: 0, z: 0} + portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139} + customExpressions: 0 + expressionsMenu: {fileID: 0} + expressionParameters: {fileID: 0} + enableEyeLook: 0 + customEyeLookSettings: + eyeMovement: + confidence: 0.5 + excitement: 0.5 + leftEye: {fileID: 0} + rightEye: {fileID: 0} + eyesLookingStraight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingUp: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingDown: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingLeft: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingRight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidType: 0 + upperLeftEyelid: {fileID: 0} + upperRightEyelid: {fileID: 0} + lowerLeftEyelid: {fileID: 0} + lowerRightEyelid: {fileID: 0} + eyelidsDefault: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsClosed: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingUp: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingDown: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsSkinnedMesh: {fileID: 0} + eyelidsBlendshapes: + customizeAnimationLayers: 1 + baseAnimationLayers: + - isEnabled: 0 + type: 0 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 2 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 3 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 4 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 5 + animatorController: {fileID: 9100000, guid: 91f184d4fc3ff1f49afdd24f69e085f0, + type: 2} + mask: {fileID: 0} + isDefault: 0 + specialAnimationLayers: + - isEnabled: 0 + type: 6 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 7 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 8 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + AnimationPreset: {fileID: 0} + animationHashSet: [] + autoFootsteps: 1 + autoLocomotion: 1 + collider_head: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_torso: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} +--- !u!114 &4452914144664039463 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 521542568012153140} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + m_Name: + m_EditorClassIdentifier: + launchedFromSDKPipeline: 0 + completedSDKPipeline: 0 + blueprintId: + contentType: 0 + assetBundleUnityVersion: + fallbackStatus: 0 +--- !u!1 &7379231148671166614 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6479369535091914718} + - component: {fileID: 5229589187063892731} + - component: {fileID: 1179508555950567543} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6479369535091914718 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7379231148671166614} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 2616869947308232229} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5229589187063892731 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7379231148671166614} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a5bf908a199a4648845ebe2fd3b5a4bd, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &1179508555950567543 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7379231148671166614} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: 22f4686ec8638e243854be6fccb38820, type: 2} + layerType: 5 + deleteAttachedAnimator: 0 + pathMode: 0 + matchAvatarWriteDefaults: 0 diff --git a/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab.meta b/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab.meta new file mode 100644 index 00000000..ed8fbbce --- /dev/null +++ b/UnitTests~/SyncedLayerHandling/MergedController_AC2.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3946772624f2d6a469547924e29e0f35 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs b/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs index fb0fd678..ccf893b4 100644 --- a/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs +++ b/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs @@ -76,7 +76,7 @@ namespace modular_avatar_tests.SyncedLayerHandling } [Test] - public void WhenSyncedLayerIsOnMergedController_LayerControlBehaviorsAreAdjusted() + public void WhenSyncedLayerIsOnMergedController_LayerControlBehaviorsAreNotAdjusted_CrossPlayable() { var prefab = CreatePrefab("MergedController.prefab"); AvatarProcessor.ProcessAvatar(prefab); @@ -93,7 +93,28 @@ namespace modular_avatar_tests.SyncedLayerHandling var layercontrol = overrides[0] as VRCAnimatorLayerControl; Assert.NotNull(layercontrol); - Assert.AreEqual(2, layercontrol.layer); + Assert.AreEqual(1, layercontrol.layer); + } + + [Test] + public void WhenSyncedLayerIsOnMergedController_LayerControlBehaviorsAreAdjusted_SamePlayable() + { + var prefab = CreatePrefab("MergedController_AC2.prefab"); + AvatarProcessor.ProcessAvatar(prefab); + + var mainLayer = findFxLayer(prefab, "main"); + var syncLayer = findFxLayer(prefab, "sync"); + + Assert.AreEqual(2, syncLayer.syncedLayerIndex); + + var m1State = FindStateInLayer(mainLayer, "m1"); + var overrides = syncLayer.GetOverrideBehaviours(m1State); + Assert.AreEqual(1, overrides.Length); + + var layercontrol = overrides[0] as VRCAnimatorLayerControl; + Assert.NotNull(layercontrol); + + Assert.AreEqual(1, layercontrol.layer); } } } 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; 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 @@ namespace modular_avatar_tests.VirtualMenuTests }; 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 @@ namespace modular_avatar_tests.VirtualMenuTests 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 @@ namespace modular_avatar_tests.VirtualMenuTests 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 modular_avatar_tests; 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 class WorldFixedObjectTest : TestBase // initialize context var buildContext = new BuildContext(avatar); - buildContext.PluginBuildContext.ActivateExtensionContext(); + buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new WorldFixedObjectProcessor().Process(buildContext); @@ -42,7 +43,7 @@ public class WorldFixedObjectTest : TestBase // initialize context var buildContext = new BuildContext(avatar); - buildContext.PluginBuildContext.ActivateExtensionContext(); + buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new WorldFixedObjectProcessor().Process(buildContext); @@ -75,7 +76,7 @@ public class WorldFixedObjectTest : TestBase // initialize context var buildContext = new BuildContext(avatar); - var animationServices = buildContext.PluginBuildContext.ActivateExtensionContext(); + var animationServices = buildContext.PluginBuildContext.ActivateExtensionContextRecursive(); new WorldFixedObjectProcessor().Process(buildContext); diff --git a/package.json b/package.json index f308e022..addd927b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "vpmDependencies": { "com.vrchat.avatars": ">=3.7.4", - "nadena.dev.ndmf": ">=1.6.0 <2.0.0-a" + "nadena.dev.ndmf": ">=1.7.0-alpha.0 <2.0.0-a" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" }