This commit is contained in:
bd_ 2024-12-15 19:51:11 -08:00
parent 2ed1402dd4
commit 567cad4edd
19 changed files with 423 additions and 586 deletions

View File

@ -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<IntermediateObj> _intermediateObjs = new List<IntermediateObj>();
/// <summary>
@ -55,15 +56,15 @@ namespace nadena.dev.modular_avatar.core.editor
{
_context = context;
_boneDatabase = boneDatabase;
_pathMappings = context.PluginBuildContext.Extension<AnimationServicesContext>().PathMappings;
_asc = context.PluginBuildContext.Extension<AnimatorServicesContext>();
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"));
}
}
}

View File

@ -20,7 +20,7 @@ namespace nadena.dev.modular_avatar.animation
}
private AnimatorServicesContext? _asc;
private Retained _retained;
private Retained _retained = null!;
private AnimatorServicesContext asc =>
_asc ?? throw new InvalidOperationException("ActiveSelfProxyExtension is not active");

View File

@ -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<DefaultValues>()?.InitialValueOverrides
?? ImmutableDictionary<string, float>.Empty;
foreach (var layer in context.AvatarDescriptor.baseAnimationLayers
.Concat(context.AvatarDescriptor.specialAnimationLayers))
var asc = context.Extension<AnimatorServicesContext>();
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;
}
}
}

View File

@ -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<VRCAvatarDescriptor.AnimLayerType, AnimatorController> defaultControllers_ =
new Dictionary<VRCAvatarDescriptor.AnimLayerType, AnimatorController>();
private Dictionary<VRCAvatarDescriptor.AnimLayerType, bool?> writeDefaults_ =
new Dictionary<VRCAvatarDescriptor.AnimLayerType, bool?>();
Dictionary<VRCAvatarDescriptor.AnimLayerType, AnimatorCombiner> mergeSessions =
new Dictionary<VRCAvatarDescriptor.AnimLayerType, AnimatorCombiner>();
private AnimatorServicesContext _asc;
internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context)
{
_context = context;
defaultControllers_.Clear();
mergeSessions.Clear();
var descriptor = avatarGameObject.GetComponent<VRCAvatarDescriptor>();
if (!descriptor) return;
if (descriptor.baseAnimationLayers != null) InitSessions(descriptor.baseAnimationLayers);
if (descriptor.specialAnimationLayers != null) InitSessions(descriptor.specialAnimationLayers);
_asc = context.PluginBuildContext.Extension<AnimatorServicesContext>();
var toMerge = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeAnimator>(true);
Dictionary<VRCAvatarDescriptor.AnimLayerType, List<ModularAvatarMergeAnimator>> byLayerType
= new Dictionary<VRCAvatarDescriptor.AnimLayerType, List<ModularAvatarMergeAnimator>>();
@ -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<RenamedMergeAnimators>();
var clonedController = stash.Controllers.GetValueOrDefault(merge)
?? _asc.ControllerContext.CloneContext.Clone(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<Animator>();
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<string, AnimatorControllerParameterType> types =
new Dictionary<string, AnimatorControllerParameterType>();
// 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<AvatarMask>(
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<AnimatorStateMachine>();
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<AnimatorController>(SAMPLE_PATH_PACKAGE + name);
if (controller == null)
{
controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(SAMPLE_PATH_LEGACY + name);
}
}
}
return controller;
}
}
}

View File

@ -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<AnimationServicesContext>()
.PathMappings;
private AnimatorServicesContext AnimatorServices => frameworkContext.Extension<AnimatorServicesContext>();
private HashSet<Transform> humanoidBones = new HashSet<Transform>();
private HashSet<Transform> mergedObjects = new HashSet<Transform>();
private HashSet<Transform> thisPassAdded = new HashSet<Transform>();
private HashSet<Transform> transformLookthrough = new HashSet<Transform>();
internal void OnPreprocessAvatar(ndmf.BuildContext context, GameObject avatarGameObject)
{
@ -117,7 +118,68 @@ namespace nadena.dev.modular_avatar.core.editor
RetainBoneReferences(c as Component);
}
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, BoneDatabase, PathMappings);
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, BoneDatabase, AnimatorServices);
ProcessTransformLookthrough();
}
private void ProcessTransformLookthrough()
{
var asc = frameworkContext.Extension<AnimatorServicesContext>();
transformLookthrough.RemoveWhere(t => !t);
var clipsToEdit = transformLookthrough.SelectMany(
xform =>
{
var path = asc.ObjectPathRemapper.GetVirtualPathForObject(xform);
return asc.AnimationIndex.GetClipsForObjectPath(path);
});
Dictionary<string, string> parentCache = new();
foreach (var clip in clipsToEdit)
{
foreach (var binding in clip.GetFloatCurveBindings())
{
if (binding.type == typeof(Transform))
{
var newPath = GetReplacementPath(binding.path);
var newBinding = EditorCurveBinding.FloatCurve(newPath, binding.type, binding.propertyName);
clip.SetFloatCurve(newBinding, clip.GetFloatCurve(binding));
clip.SetFloatCurve(binding, null);
}
}
}
string GetReplacementPath(string bindingPath)
{
if (parentCache.TryGetValue(bindingPath, out var cached))
{
return cached;
}
var obj = asc.ObjectPathRemapper.GetObjectForPath(bindingPath)!.transform;
while (obj != null && transformLookthrough.Contains(obj))
{
obj = obj.parent;
}
string path;
if (obj == null)
{
path = bindingPath;
}
else
{
path = asc.ObjectPathRemapper.GetVirtualPathForObject(obj);
}
parentCache[bindingPath] = path;
return path;
}
}
private void TopoProcessMergeArmatures(ModularAvatarMergeArmature[] mergeArmatures)
@ -276,6 +338,7 @@ 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));
}
/**
@ -339,7 +402,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);
}
@ -354,7 +417,7 @@ namespace nadena.dev.modular_avatar.core.editor
if (zipMerge)
{
PathMappings.MarkTransformLookthrough(src);
transformLookthrough.Add(src.transform);
BoneDatabase.AddMergedBone(src.transform);
}

View File

@ -4,10 +4,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using nadena.dev.modular_avatar.animation;
using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using nadena.dev.ndmf.util;
using UnityEditor.Animations;
using UnityEditor.Search;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
@ -20,56 +23,49 @@ 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<string> _parameterNames;
protected override void Execute(ndmf.BuildContext context)
{
_asc = context.Extension<AnimatorServicesContext>();
_rootBlendTree = null;
_parameterNames = new HashSet<string>();
_controller = new AnimatorController();
var fx = _asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX];
foreach (var component in
context.AvatarRootObject.GetComponentsInChildren<ModularAvatarMergeBlendTree>(true))
{
ErrorReport.WithContextObject(component, () => ProcessComponent(context, component));
}
List<AnimatorControllerParameter> parameters = new List<AnimatorControllerParameter>(_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<ModularAvatarMergeAnimator>();
paramsComponent.animator = paramsAnimator;
paramsComponent.layerPriority = Int32.MaxValue;
name = name,
type = AnimatorControllerParameterType.Float,
defaultFloat = 1.0f
});
}
}
private void ProcessComponent(ndmf.BuildContext context, ModularAvatarMergeBlendTree component)
private void ProcessComponent(BuildContext context, ModularAvatarMergeBlendTree component)
{
var stash = context.PluginBuildContext.GetState<RenamedMergeAnimators>();
BlendTree componentBlendTree = component.BlendTree as BlendTree;
if (componentBlendTree == null)
@ -79,46 +75,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 +136,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<ModularAvatarMergeAnimator>();
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;
}
}

View File

@ -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<SkinnedMeshRenderer>(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);
}
}

View File

@ -48,17 +48,20 @@ 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(AnimatorServicesContext), _s2 =>
{
#if MA_VRCSDK3_AVATARS
seq.Run(RenameParametersPluginPass.Instance);
seq.Run(ParameterAssignerPass.Instance);
seq.Run(MergeBlendTreePass.Instance);
seq.Run(MergeAnimatorPluginPass.Instance);
seq.Run(ApplyAnimatorDefaultValuesPass.Instance);
seq.WithRequiredExtension(typeof(ReadablePropertyExtension), _s3 =>
{
seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute())
@ -66,19 +69,18 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
new MaterialSetterPreview());
});
seq.Run(GameObjectDelayDisablePass.Instance);
#endif
});
seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 =>
{
#if MA_VRCSDK3_AVATARS
// TODO: We currently run this above MergeArmaturePlugin, because Merge Armature might destroy
// game objects which contain Menu Installers. It'd probably be better however to teach Merge Armature
// to retain those objects? maybe?
seq.Run(MenuInstallPluginPass.Instance);
#endif
seq.Run(MergeArmaturePluginPass.Instance);
});
seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 =>
{
seq.Run(BoneProxyPluginPass.Instance);
#if MA_VRCSDK3_AVATARS
seq.Run(VisibleHeadAccessoryPluginPass.Instance);

View File

@ -39,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<AnimatorServicesContext>();
_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);

View File

@ -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<ModularAvatarMergeAnimator>();
mergeAnimator.layerType = VRCAvatarDescriptor.AnimLayerType.FX;
mergeAnimator.deleteAttachedAnimator = false;
mergeAnimator.animator = new AnimatorController
var asc = context.Extension<AnimatorServicesContext>();
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,
});
}
}
}
}

View File

@ -9,6 +9,7 @@ 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;
@ -55,6 +56,43 @@ namespace nadena.dev.modular_avatar.core.editor
{
public ImmutableDictionary<string, float> InitialValueOverrides;
}
internal class RenamedMergeAnimators
{
public AnimatorServicesContext AnimatorServices;
public Dictionary<ModularAvatarMergeAnimator, VirtualAnimatorController> Controllers = new();
public Dictionary<ModularAvatarMergeBlendTree, VirtualBlendTree> BlendTrees = new();
public VirtualAnimatorController Clone(ModularAvatarMergeAnimator mama)
{
if (Controllers.TryGetValue(mama, out var controller))
{
return controller;
}
if (mama.animator == null) return null;
var cloned = AnimatorServices.ControllerContext.CloneContext.Clone(mama.animator);
Controllers[mama] = cloned;
return cloned;
}
public VirtualBlendTree Clone(ModularAvatarMergeBlendTree mbt)
{
if (BlendTrees.TryGetValue(mbt, out var blendTree))
{
return blendTree;
}
if (mbt.BlendTree is not BlendTree bt) return null;
var cloned = (VirtualBlendTree)AnimatorServices.ControllerContext.CloneContext.Clone(bt);
BlendTrees[mbt] = cloned;
return cloned;
}
}
internal class RenameParametersHook
{
@ -163,6 +201,10 @@ namespace nadena.dev.modular_avatar.core.editor
if (!context.AvatarDescriptor) return;
_context = context;
var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var asc = _context.PluginBuildContext.Extension<AnimatorServicesContext>();
stash.AnimatorServices = asc;
var syncParams = WalkTree(avatar);
@ -389,11 +431,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<RenamedMergeAnimators>();
ProcessRuntimeAnimatorController(merger.animator, remap);
var controller = stash.Clone(merger);
ProcessVirtualAnimatorController(controller, remap);
stash.Controllers[merger] = controller;
}
break;
@ -404,8 +448,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<RenamedMergeAnimators>();
var virtualbt = stash.Clone(merger);
ProcessBlendtree(virtualbt, paramInfo.GetParameterRemappingsAt(obj));
stash.BlendTrees[merger] = virtualbt;
}
break;
@ -485,28 +533,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<KeyValuePair<AnimationClip, AnimationClip>>();
aoc.GetOverrides(list);
for (var i = 0; i < list.Count; i++)
{
var kvp = list[i];
if (kvp.Value != null) ProcessClip(kvp.Value, remap);
}
ProcessRuntimeAnimatorController(aoc.runtimeAnimatorController, remap);
}
}
private void ProcessMenuInstaller(ModularAvatarMenuInstaller installer,
ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps)
{
@ -525,113 +551,57 @@ 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 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<AnimatorStateMachine>();
var queue = new Queue<AnimatorStateMachine>();
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 ProcessState(VirtualState state, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps)
{
state.mirrorParameter = remap(remaps, state.mirrorParameter);
state.timeParameter = remap(remaps, state.timeParameter);
state.speedParameter = remap(remaps, state.speedParameter);
state.cycleOffsetParameter = remap(remaps, state.cycleOffsetParameter);
state.MirrorParameter = remap(remaps, state.MirrorParameter);
state.TimeParameter = remap(remaps, state.TimeParameter);
state.SpeedParameter = remap(remaps, state.SpeedParameter);
state.CycleOffsetParameter = remap(remaps, state.CycleOffsetParameter);
foreach (var t in state.transitions)
{
ProcessTransition(t, remaps);
}
foreach (var behavior in state.behaviours)
foreach (var behavior in state.Behaviours)
{
if (behavior is VRCAvatarParameterDriver driver)
{
ProcessDriver(driver, remaps);
}
}
ProcessMotion(state.motion, remaps);
}
private void ProcessMotion(Motion motion,
private void ProcessClip(VirtualClip clip,
ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps)
{
if (motion is BlendTree blendTree) ProcessBlendtree(blendTree, remaps);
if (motion is AnimationClip clip) ProcessClip(clip, remaps);
}
private void ProcessClip(AnimationClip clip,
ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps)
{
var curveBindings = AnimationUtility.GetCurveBindings(clip);
var curveBindings = clip.GetFloatCurveBindings();
var bindingsToUpdate = new List<EditorCurveBinding>();
var newCurves = new List<AnimationCurve>();
@ -641,48 +611,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)
@ -698,19 +650,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<string, ParameterInfo> CollectParameters(ModularAvatarParameters p,

View File

@ -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<AnimationServicesContext>()
.PathMappings;
var asc = buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
// 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")));
}
}

View File

@ -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<ModularAvatarContext>();
ctx.ActivateExtensionContext<AnimationServicesContext>();
ctx.ActivateExtensionContextRecursive<AnimatorServicesContext>();
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<ModularAvatarContext>();
ctx.ActivateExtensionContext<AnimationServicesContext>();
ctx.ActivateExtensionContextRecursive<AnimatorServicesContext>();
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);

View File

@ -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);

View File

@ -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<ModularAvatarContext>();
context.ActivateExtensionContext<AnimationServicesContext>();
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new MergeArmatureHook().OnPreprocessAvatar(context, root);
Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null);
@ -82,7 +83,7 @@ namespace modular_avatar_tests.MergeArmatureTests
nadena.dev.ndmf.BuildContext context =
new nadena.dev.ndmf.BuildContext(root, null);
context.ActivateExtensionContext<ModularAvatarContext>();
context.ActivateExtensionContext<AnimationServicesContext>();
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
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<ModularAvatarContext>();
context.ActivateExtensionContext<AnimationServicesContext>();
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new MergeArmatureHook().OnPreprocessAvatar(context, root);
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass

View File

@ -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<ModularAvatarContext>().BuildContext;
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
var errors = ErrorReport.CaptureErrors(
() =>
@ -209,6 +211,7 @@ namespace modular_avatar_tests.RenameParametersTests
var context = CreateContext(av);
var maContext = context.ActivateExtensionContext<ModularAvatarContext>().BuildContext;
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
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<ModularAvatarContext>().BuildContext;
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
var errors = ErrorReport.CaptureErrors(() => new RenameParametersHook().OnPreprocessAvatar(av, maContext));

View File

@ -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<AnimatorServicesContext>();
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<AnimatorServicesContext>();
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)),

View File

@ -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<VRCAvatarDescriptor>());
buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new RenameParametersHook().OnPreprocessAvatar(av_root, buildContext);
var virtualMenu = VirtualMenu.ForAvatar(av_root.GetComponent<VRCAvatarDescriptor>(), buildContext);
@ -663,6 +665,7 @@ namespace modular_avatar_tests.VirtualMenuTests
var root = CreatePrefab("InternalParameterTest.prefab");
BuildContext buildContext = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new RenameParametersHook().OnPreprocessAvatar(root, buildContext);
var virtualMenu = VirtualMenu.ForAvatar(root.GetComponent<VRCAvatarDescriptor>(), buildContext);
@ -676,6 +679,7 @@ namespace modular_avatar_tests.VirtualMenuTests
var root = CreatePrefab("UnusedSubParametersAreStripped.prefab");
BuildContext buildContext = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new RenameParametersHook().OnPreprocessAvatar(root, buildContext);
var virtualMenu = VirtualMenu.ForAvatar(root.GetComponent<VRCAvatarDescriptor>(), buildContext);

View File

@ -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<AnimationServicesContext>();
buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new WorldFixedObjectProcessor().Process(buildContext);
@ -42,7 +43,7 @@ public class WorldFixedObjectTest : TestBase
// initialize context
var buildContext = new BuildContext(avatar);
buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>();
buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new WorldFixedObjectProcessor().Process(buildContext);
@ -75,7 +76,7 @@ public class WorldFixedObjectTest : TestBase
// initialize context
var buildContext = new BuildContext(avatar);
var animationServices = buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>();
var animationServices = buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new WorldFixedObjectProcessor().Process(buildContext);