This commit is contained in:
bd_ 2024-12-15 19:51:11 -08:00
parent 81ef5419f7
commit 322a13ca44
19 changed files with 423 additions and 586 deletions

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.animation;
using nadena.dev.ndmf.animator;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using EditorCurveBinding = UnityEditor.EditorCurveBinding; using EditorCurveBinding = UnityEditor.EditorCurveBinding;
@ -16,7 +17,7 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
private readonly BuildContext _context; private readonly BuildContext _context;
private readonly BoneDatabase _boneDatabase; private readonly BoneDatabase _boneDatabase;
private readonly PathMappings _pathMappings; private readonly AnimatorServicesContext _asc;
private readonly List<IntermediateObj> _intermediateObjs = new List<IntermediateObj>(); private readonly List<IntermediateObj> _intermediateObjs = new List<IntermediateObj>();
/// <summary> /// <summary>
@ -55,15 +56,15 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
_context = context; _context = context;
_boneDatabase = boneDatabase; _boneDatabase = boneDatabase;
_pathMappings = context.PluginBuildContext.Extension<AnimationServicesContext>().PathMappings; _asc = context.PluginBuildContext.Extension<AnimatorServicesContext>();
while (root != null && !RuntimeUtil.IsAvatarRoot(root)) while (root != null && !RuntimeUtil.IsAvatarRoot(root))
{ {
var originalPath = RuntimeUtil.AvatarRootPath(root.gameObject); var originalPath = RuntimeUtil.AvatarRootPath(root.gameObject);
System.Diagnostics.Debug.Assert(originalPath != null); System.Diagnostics.Debug.Assert(originalPath != null);
if (context.AnimationDatabase.ClipsForPath(originalPath).Any(clip => if (_asc.AnimationIndex.GetClipsForObjectPath(originalPath).Any(clip =>
GetActiveBinding(clip.CurrentClip as AnimationClip, originalPath) != null GetActiveBinding(clip, originalPath) != null
)) ))
{ {
_intermediateObjs.Add(new IntermediateObj _intermediateObjs.Add(new IntermediateObj
@ -118,7 +119,6 @@ namespace nadena.dev.modular_avatar.core.editor
// Ensure mesh retargeting looks through this // Ensure mesh retargeting looks through this
_boneDatabase.AddMergedBone(sourceBone.transform); _boneDatabase.AddMergedBone(sourceBone.transform);
_boneDatabase.RetainMergedBone(sourceBone.transform); _boneDatabase.RetainMergedBone(sourceBone.transform);
_pathMappings.MarkTransformLookthrough(sourceBone);
} }
return sourceBone; return sourceBone;
@ -130,22 +130,14 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
var path = intermediate.OriginalPath; 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); var curve = GetActiveBinding(clip, path);
if (curve != null) if (curve != null)
{ {
foreach (var mapping in intermediate.Created) 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); 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, return clip.GetFloatCurve(EditorCurveBinding.FloatCurve(path, typeof(GameObject), "m_IsActive"));
EditorCurveBinding.FloatCurve(path, typeof(GameObject), "m_IsActive"));
} }
} }
} }

View File

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

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
@ -21,40 +22,34 @@ namespace nadena.dev.modular_avatar.core.editor
var values = context.GetState<DefaultValues>()?.InitialValueOverrides var values = context.GetState<DefaultValues>()?.InitialValueOverrides
?? ImmutableDictionary<string, float>.Empty; ?? ImmutableDictionary<string, float>.Empty;
foreach (var layer in context.AvatarDescriptor.baseAnimationLayers var asc = context.Extension<AnimatorServicesContext>();
.Concat(context.AvatarDescriptor.specialAnimationLayers))
foreach (var controller in asc.ControllerContext.GetAllControllers())
{ {
if (layer.isDefault || layer.animatorController == null) continue; var parameters = controller.Parameters;
foreach (var (name, parameter) in parameters)
// We should have converted anything that's not an AnimationController by now
var controller = layer.animatorController as AnimatorController;
if (controller == null || !context.IsTemporaryAsset(controller))
{ {
throw new Exception("Leaked unexpected controller: " + layer.animatorController + " (type " + layer.animatorController?.GetType() + ")"); if (!values.TryGetValue(name, out var defaultValue)) continue;
}
var parameters = controller.parameters; switch (parameter.type)
for (int i = 0; i < parameters.Length; i++)
{
if (!values.TryGetValue(parameters[i].name, out var defaultValue)) continue;
switch (parameters[i].type)
{ {
case AnimatorControllerParameterType.Bool: case AnimatorControllerParameterType.Bool:
parameters[i].defaultBool = defaultValue != 0.0f; parameter.defaultBool = defaultValue != 0.0f;
break; break;
case AnimatorControllerParameterType.Int: case AnimatorControllerParameterType.Int:
parameters[i].defaultInt = Mathf.RoundToInt(defaultValue); parameter.defaultInt = Mathf.RoundToInt(defaultValue);
break; break;
case AnimatorControllerParameterType.Float: case AnimatorControllerParameterType.Float:
parameters[i].defaultFloat = defaultValue; parameter.defaultFloat = defaultValue;
break; break;
default: default:
continue; // unhandled type, e.g. trigger continue; // unhandled type, e.g. trigger
} }
parameters = parameters.SetItem(name, parameter);
} }
controller.parameters = parameters; controller.Parameters = parameters;
} }
} }
} }

View File

@ -24,51 +24,22 @@
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.animation; using nadena.dev.ndmf.animator;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
using VRC.SDKBase;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
internal class MergeAnimatorProcessor internal class MergeAnimatorProcessor
{ {
private const string SAMPLE_PATH_PACKAGE = private AnimatorServicesContext _asc;
"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>();
internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context) internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context)
{ {
_context = context; _asc = context.PluginBuildContext.Extension<AnimatorServicesContext>();
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);
var toMerge = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeAnimator>(true); var toMerge = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeAnimator>(true);
Dictionary<VRCAvatarDescriptor.AnimLayerType, List<ModularAvatarMergeAnimator>> byLayerType Dictionary<VRCAvatarDescriptor.AnimLayerType, List<ModularAvatarMergeAnimator>> byLayerType
@ -89,10 +60,6 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
ProcessLayerType(context, entry.Key, entry.Value); ProcessLayerType(context, entry.Key, entry.Value);
} }
descriptor.baseAnimationLayers = FinishSessions(descriptor.baseAnimationLayers);
descriptor.specialAnimationLayers = FinishSessions(descriptor.specialAnimationLayers);
descriptor.customizeAnimationLayers = true;
} }
private void ProcessLayerType( private void ProcessLayerType(
@ -109,34 +76,34 @@ namespace nadena.dev.modular_avatar.core.editor
var afterOriginal = sorted.Where(x => x.layerPriority >= 0) var afterOriginal = sorted.Where(x => x.layerPriority >= 0)
.ToList(); .ToList();
var session = new AnimatorCombiner(context.PluginBuildContext, layerType.ToString() + " (merged)"); var controller = _asc.ControllerContext[layerType];
mergeSessions[layerType] = session;
mergeSessions[layerType].BlendableLayer = BlendableLayerFor(layerType);
foreach (var component in beforeOriginal) var wdStateCounter = controller.Layers.SelectMany(l => l.StateMachine.AllStates())
{ .Select(s => s.WriteDefaultValues)
MergeSingle(context, session, component); .GroupBy(b => b)
} .ToDictionary(g => g.Key, g => g.Count());
if (defaultControllers_.TryGetValue(layerType, out var defaultController) && bool? writeDefaults = null;
defaultController.layers.Length > 0) if (wdStateCounter.Count == 1) writeDefaults = wdStateCounter.First().Key;
{
session.AddController("", defaultController, null, forceFirstLayerWeight: true);
}
foreach (var component in afterOriginal) foreach (var component in sorted)
{ {
MergeSingle(context, session, component); MergeSingle(context, controller, component, writeDefaults);
} }
} }
private void MergeSingle(BuildContext context, AnimatorCombiner session, ModularAvatarMergeAnimator merge) private void MergeSingle(BuildContext context, VirtualAnimatorController controller, ModularAvatarMergeAnimator merge, bool? initialWriteDefaults)
{ {
if (merge.animator == null) if (merge.animator == null)
{ {
return; return;
} }
var stash = context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var clonedController = stash.Controllers.GetValueOrDefault(merge)
?? _asc.ControllerContext.CloneContext.Clone(merge.animator);
string basePath; string basePath;
if (merge.pathMode == MergeAnimatorPathMode.Relative) if (merge.pathMode == MergeAnimatorPathMode.Relative)
{ {
@ -145,17 +112,53 @@ namespace nadena.dev.modular_avatar.core.editor
var relativePath = RuntimeUtil.RelativePath(context.AvatarRootObject, targetObject); var relativePath = RuntimeUtil.RelativePath(context.AvatarRootObject, targetObject);
basePath = relativePath != "" ? relativePath + "/" : ""; basePath = relativePath != "" ? relativePath + "/" : "";
var animationIndex = new AnimationIndex(new[] { clonedController });
animationIndex.RewritePaths(p => p == "" ? relativePath : basePath + p);
} }
else else
{ {
basePath = ""; basePath = "";
} }
var writeDefaults = merge.matchAvatarWriteDefaults foreach (var l in clonedController.Layers)
? writeDefaults_.GetValueOrDefault(merge.layerType) {
: null; if (initialWriteDefaults != null)
var controller = _context.ConvertAnimatorController(merge.animator); {
session.AddController(basePath, controller, writeDefaults); 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) if (merge.deleteAttachedAnimator)
{ {
@ -163,182 +166,6 @@ namespace nadena.dev.modular_avatar.core.editor
if (animator != null) Object.DestroyImmediate(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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.animation;
using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.modular_avatar.editor.ErrorReporting;
using nadena.dev.ndmf.animator;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.Animations; using UnityEngine.Animations;
@ -54,13 +54,14 @@ namespace nadena.dev.modular_avatar.core.editor
#endif #endif
private BoneDatabase BoneDatabase = new BoneDatabase(); private BoneDatabase BoneDatabase = new BoneDatabase();
private PathMappings PathMappings => frameworkContext.Extension<AnimationServicesContext>() private AnimatorServicesContext AnimatorServices => frameworkContext.Extension<AnimatorServicesContext>();
.PathMappings;
private HashSet<Transform> humanoidBones = new HashSet<Transform>(); private HashSet<Transform> humanoidBones = new HashSet<Transform>();
private HashSet<Transform> mergedObjects = new HashSet<Transform>(); private HashSet<Transform> mergedObjects = new HashSet<Transform>();
private HashSet<Transform> thisPassAdded = 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) internal void OnPreprocessAvatar(ndmf.BuildContext context, GameObject avatarGameObject)
{ {
this.frameworkContext = context; this.frameworkContext = context;
@ -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<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) private void TopoProcessMergeArmatures(ModularAvatarMergeArmature[] mergeArmatures)
@ -294,6 +356,7 @@ namespace nadena.dev.modular_avatar.core.editor
_activeRetargeter.FixupAnimations(); _activeRetargeter.FixupAnimations();
thisPassAdded.UnionWith(_activeRetargeter.AddedGameObjects.Select(x => x.transform)); 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.AddMergedBone(mergedSrcBone.transform);
BoneDatabase.RetainMergedBone(mergedSrcBone.transform); BoneDatabase.RetainMergedBone(mergedSrcBone.transform);
PathMappings.MarkTransformLookthrough(mergedSrcBone); transformLookthrough.Add(mergedSrcBone.transform);
thisPassAdded.Add(mergedSrcBone.transform); thisPassAdded.Add(mergedSrcBone.transform);
} }
@ -372,7 +435,7 @@ namespace nadena.dev.modular_avatar.core.editor
if (zipMerge) if (zipMerge)
{ {
PathMappings.MarkTransformLookthrough(src); transformLookthrough.Add(src.transform);
BoneDatabase.AddMergedBone(src.transform); BoneDatabase.AddMergedBone(src.transform);
} }

View File

@ -4,10 +4,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.animation;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using nadena.dev.ndmf.util; using nadena.dev.ndmf.util;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEditor.Search;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
@ -20,16 +23,17 @@ namespace nadena.dev.modular_avatar.core.editor
internal const string ALWAYS_ONE = "__ModularAvatarInternal/One"; internal const string ALWAYS_ONE = "__ModularAvatarInternal/One";
internal const string BlendTreeLayerName = "ModularAvatar: Merge Blend Tree"; internal const string BlendTreeLayerName = "ModularAvatar: Merge Blend Tree";
private AnimatorController _controller; private AnimatorServicesContext _asc;
private BlendTree _rootBlendTree; private VirtualBlendTree _rootBlendTree;
private GameObject _mergeHost;
private HashSet<string> _parameterNames; private HashSet<string> _parameterNames;
protected override void Execute(ndmf.BuildContext context) protected override void Execute(ndmf.BuildContext context)
{ {
_asc = context.Extension<AnimatorServicesContext>();
_rootBlendTree = null; _rootBlendTree = null;
_parameterNames = new HashSet<string>(); _parameterNames = new HashSet<string>();
_controller = new AnimatorController();
var fx = _asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX];
foreach (var component in foreach (var component in
context.AvatarRootObject.GetComponentsInChildren<ModularAvatarMergeBlendTree>(true)) context.AvatarRootObject.GetComponentsInChildren<ModularAvatarMergeBlendTree>(true))
@ -37,39 +41,31 @@ namespace nadena.dev.modular_avatar.core.editor
ErrorReport.WithContextObject(component, () => ProcessComponent(context, component)); ErrorReport.WithContextObject(component, () => ProcessComponent(context, component));
} }
List<AnimatorControllerParameter> parameters = new List<AnimatorControllerParameter>(_parameterNames.Count + 1); // always add the ALWAYS_ONE parameter
if (_mergeHost != null) fx.Parameters = fx.Parameters.SetItem(ALWAYS_ONE, new AnimatorControllerParameter()
{ {
_parameterNames.Remove(ALWAYS_ONE); name = ALWAYS_ONE,
type = AnimatorControllerParameterType.Float,
defaultFloat = 1
});
parameters.Add(new AnimatorControllerParameter() foreach (var name in _parameterNames)
{
if (fx.Parameters.ContainsKey(name)) continue;
fx.Parameters = fx.Parameters.SetItem(name, new AnimatorControllerParameter()
{ {
name = ALWAYS_ONE, name = name,
type = AnimatorControllerParameterType.Float, type = AnimatorControllerParameterType.Float,
defaultFloat = 1 defaultFloat = 1.0f
}); });
foreach (var name in _parameterNames)
{
parameters.Add(new AnimatorControllerParameter()
{
name = name,
type = AnimatorControllerParameterType.Float,
defaultFloat = 0
});
}
var paramsAnimator = new AnimatorController();
paramsAnimator.parameters = parameters.ToArray();
var paramsComponent = _mergeHost.AddComponent<ModularAvatarMergeAnimator>();
paramsComponent.animator = paramsAnimator;
paramsComponent.layerPriority = Int32.MaxValue;
} }
} }
private void ProcessComponent(ndmf.BuildContext context, ModularAvatarMergeBlendTree component) private void ProcessComponent(BuildContext context, ModularAvatarMergeBlendTree component)
{ {
var stash = context.PluginBuildContext.GetState<RenamedMergeAnimators>();
BlendTree componentBlendTree = component.BlendTree as BlendTree; BlendTree componentBlendTree = component.BlendTree as BlendTree;
if (componentBlendTree == null) if (componentBlendTree == null)
@ -79,46 +75,60 @@ namespace nadena.dev.modular_avatar.core.editor
} }
string basePath = null; string basePath = null;
string rootPath = null;
if (component.PathMode == MergeAnimatorPathMode.Relative) if (component.PathMode == MergeAnimatorPathMode.Relative)
{ {
var root = component.RelativePathRoot.Get(context.AvatarRootTransform); var root = component.RelativePathRoot.Get(context.AvatarRootTransform);
if (root == null) root = component.gameObject; if (root == null) root = component.gameObject;
basePath = RuntimeUtil.AvatarRootPath(root) + "/"; rootPath = RuntimeUtil.AvatarRootPath(root);
basePath = rootPath + "/";
} }
var bt = new DeepClone(context).DoClone(componentBlendTree, basePath); var bt = stash.BlendTrees.GetValueOrDefault(component)
var rootBlend = GetRootBlendTree(context); ?? _asc.ControllerContext.CloneContext.Clone(componentBlendTree);
rootBlend.AddChild(bt); if (basePath != null)
var children = rootBlend.children;
children[children.Length - 1].directBlendParameter = ALWAYS_ONE;
rootBlend.children = children;
foreach (var asset in bt.ReferencedAssets(includeScene: false))
{ {
if (asset is BlendTree bt2) var animationIndex = new AnimationIndex(new[] { bt });
animationIndex.RewritePaths(p => p == "" ? rootPath : basePath + p);
}
var rootBlend = GetRootBlendTree();
rootBlend.Children = rootBlend.Children.Add(new()
{
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,58 +136,21 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
private BlendTree GetRootBlendTree(ndmf.BuildContext context) private VirtualBlendTree GetRootBlendTree()
{ {
if (_rootBlendTree != null) return _rootBlendTree; if (_rootBlendTree != null) return _rootBlendTree;
var newController = new AnimatorController(); var fx = _asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX];
var newStateMachine = new AnimatorStateMachine(); var controller = fx.AddLayer(new LayerPriority(int.MinValue), BlendTreeLayerName);
var newState = new AnimatorState(); var stateMachine = controller.StateMachine;
_rootBlendTree = new BlendTree(); _rootBlendTree = VirtualBlendTree.Create("Root");
_controller = newController; var state = stateMachine.AddState("State", _rootBlendTree);
stateMachine.DefaultState = state;
state.WriteDefaultValues = true;
newController.layers = new[] _rootBlendTree.BlendType = BlendTreeType.Direct;
{ _rootBlendTree.BlendParameter = ALWAYS_ONE;
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;
newState.writeDefaultValues = true;
newState.motion = _rootBlendTree;
_rootBlendTree.blendType = BlendTreeType.Direct;
_rootBlendTree.blendParameter = ALWAYS_ONE;
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;
mergeObject.transform.SetParent(context.AvatarRootTransform, false);
mergeObject.transform.SetSiblingIndex(0);
_mergeHost = mergeObject;
return _rootBlendTree; return _rootBlendTree;
} }

View File

@ -27,6 +27,7 @@ using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.animation;
using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.modular_avatar.editor.ErrorReporting;
using nadena.dev.ndmf.animator;
using UnityEngine; using UnityEngine;
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
@ -84,13 +85,15 @@ namespace nadena.dev.modular_avatar.core.editor
internal class RetargetMeshes internal class RetargetMeshes
{ {
private BoneDatabase _boneDatabase; private BoneDatabase _boneDatabase;
private PathMappings _pathTracker; private AnimationIndex _animationIndex;
private ObjectPathRemapper _pathRemapper;
internal void OnPreprocessAvatar(GameObject avatarGameObject, BoneDatabase boneDatabase, internal void OnPreprocessAvatar(GameObject avatarGameObject, BoneDatabase boneDatabase,
PathMappings pathMappings) AnimatorServicesContext pathMappings)
{ {
this._boneDatabase = boneDatabase; this._boneDatabase = boneDatabase;
this._pathTracker = pathMappings; this._animationIndex = pathMappings.AnimationIndex;
this._pathRemapper = pathMappings.ObjectPathRemapper;
foreach (var renderer in avatarGameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true)) foreach (var renderer in avatarGameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{ {
@ -153,7 +156,8 @@ namespace nadena.dev.modular_avatar.core.editor
child.SetParent(destBone, true); 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); UnityEngine.Object.DestroyImmediate(sourceBone.gameObject);
} }
} }

View File

@ -47,17 +47,20 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
seq.Run(ClearEditorOnlyTags.Instance); seq.Run(ClearEditorOnlyTags.Instance);
seq.Run(MeshSettingsPluginPass.Instance); seq.Run(MeshSettingsPluginPass.Instance);
seq.Run(ScaleAdjusterPass.Instance).PreviewingWith(new ScaleAdjusterPreview()); seq.Run(ScaleAdjusterPass.Instance).PreviewingWith(new ScaleAdjusterPreview());
// All these need to move to the new ASC
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
seq.Run(ReactiveObjectPrepass.Instance); 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 #endif
seq.WithRequiredExtension(typeof(AnimatorServicesContext), _s2 => seq.WithRequiredExtension(typeof(AnimatorServicesContext), _s2 =>
{ {
#if MA_VRCSDK3_AVATARS #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.WithRequiredExtension(typeof(ReadablePropertyExtension), _s3 =>
{ {
seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute()) seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute())
@ -65,19 +68,18 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
new MaterialSetterPreview()); new MaterialSetterPreview());
}); });
seq.Run(GameObjectDelayDisablePass.Instance); 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 // 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 // game objects which contain Menu Installers. It'd probably be better however to teach Merge Armature
// to retain those objects? maybe? // to retain those objects? maybe?
seq.Run(MenuInstallPluginPass.Instance); seq.Run(MenuInstallPluginPass.Instance);
#endif #endif
seq.Run(MergeArmaturePluginPass.Instance); seq.Run(MergeArmaturePluginPass.Instance);
});
seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 =>
{
seq.Run(BoneProxyPluginPass.Instance); seq.Run(BoneProxyPluginPass.Instance);
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
seq.Run(VisibleHeadAccessoryPluginPass.Instance); 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, // 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. // 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); var analysis = new ReactiveObjectAnalyzer(context).Analyze(context.AvatarRootObject);

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
@ -201,18 +202,20 @@ namespace nadena.dev.modular_avatar.core.editor
if (mamiWithRC.Count > 0) if (mamiWithRC.Count > 0)
{ {
// This make sures the parameters are correctly merged into the FX layer. var asc = context.Extension<AnimatorServicesContext>();
var mergeAnimator = context.AvatarRootObject.AddComponent<ModularAvatarMergeAnimator>(); var fx = asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX];
mergeAnimator.layerType = VRCAvatarDescriptor.AnimLayerType.FX;
mergeAnimator.deleteAttachedAnimator = false; foreach (var (name, _) in mamiWithRC)
mergeAnimator.animator = new AnimatorController
{ {
parameters = mamiWithRC.Select(kvp => new AnimatorControllerParameter if (!fx.Parameters.ContainsKey(name))
{ {
name = kvp.Key, fx.Parameters = fx.Parameters.SetItem(name, new()
type = AnimatorControllerParameterType.Float, {
}).ToArray(), 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.animation;
using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.modular_avatar.editor.ErrorReporting;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
@ -56,6 +57,43 @@ namespace nadena.dev.modular_avatar.core.editor
public ImmutableDictionary<string, float> InitialValueOverrides; 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 internal class RenameParametersHook
{ {
private const string DEFAULT_EXP_PARAMS_ASSET_GUID = "03a6d797deb62f0429471c4e17ea99a7"; private const string DEFAULT_EXP_PARAMS_ASSET_GUID = "03a6d797deb62f0429471c4e17ea99a7";
@ -164,6 +202,10 @@ namespace nadena.dev.modular_avatar.core.editor
_context = context; _context = context;
var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var asc = _context.PluginBuildContext.Extension<AnimatorServicesContext>();
stash.AnimatorServices = asc;
var syncParams = WalkTree(avatar); var syncParams = WalkTree(avatar);
SetExpressionParameters(avatar, syncParams); SetExpressionParameters(avatar, syncParams);
@ -389,11 +431,13 @@ namespace nadena.dev.modular_avatar.core.editor
if (merger.animator != null) if (merger.animator != null)
{ {
Profiler.BeginSample("DeepCloneAnimator"); var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
merger.animator = new DeepClone(_context.PluginBuildContext).DoClone(merger.animator);
Profiler.EndSample();
ProcessRuntimeAnimatorController(merger.animator, remap); var controller = stash.Clone(merger);
ProcessVirtualAnimatorController(controller, remap);
stash.Controllers[merger] = controller;
} }
break; break;
@ -404,8 +448,12 @@ namespace nadena.dev.modular_avatar.core.editor
var bt = merger.BlendTree as BlendTree; var bt = merger.BlendTree as BlendTree;
if (bt != null) if (bt != null)
{ {
merger.BlendTree = bt = new DeepClone(_context.PluginBuildContext).DoClone(bt); var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
ProcessBlendtree(bt, paramInfo.GetParameterRemappingsAt(obj));
var virtualbt = stash.Clone(merger);
ProcessBlendtree(virtualbt, paramInfo.GetParameterRemappingsAt(obj));
stash.BlendTrees[merger] = virtualbt;
} }
break; break;
@ -497,28 +545,6 @@ namespace nadena.dev.modular_avatar.core.editor
return rv; 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, private void ProcessMenuInstaller(ModularAvatarMenuInstaller installer,
ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps)
{ {
@ -537,113 +563,57 @@ namespace nadena.dev.modular_avatar.core.editor
}); });
} }
private void ProcessAnimator(AnimatorController controller, private void ProcessVirtualAnimatorController(VirtualAnimatorController controller,
ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remap)
{ {
if (remaps.IsEmpty) return; foreach (var node in controller.AllReachableNodes())
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)) switch (node)
{ {
parameters[i].name = newName.ParameterName; case VirtualState vs: ProcessState(vs, remap); break;
case VirtualTransition vt: ProcessTransition(vt, remap); break;
case VirtualClip vc: ProcessClip(vc, remap); break;
case VirtualBlendTree bt: ProcessBlendtree(bt, remap); break;
} }
} }
controller.parameters = parameters; var newParameters = controller.Parameters.Clear();
foreach (var layer in controller.layers) foreach (var (name, parameter) in controller.Parameters)
{ {
if (layer.stateMachine != null) if (remap.TryGetValue((ParameterNamespace.Animator, name), out var newParam))
{ {
queue.Enqueue(layer.stateMachine); newParameters = newParameters.Add(newParam.ParameterName, parameter);
}
else
{
newParameters = newParameters.Add(name, parameter);
} }
} }
Profiler.BeginSample("Walk animator graph"); controller.Parameters = newParameters;
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();
} }
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.MirrorParameter = remap(remaps, state.MirrorParameter);
state.timeParameter = remap(remaps, state.timeParameter); state.TimeParameter = remap(remaps, state.TimeParameter);
state.speedParameter = remap(remaps, state.speedParameter); state.SpeedParameter = remap(remaps, state.SpeedParameter);
state.cycleOffsetParameter = remap(remaps, state.cycleOffsetParameter); state.CycleOffsetParameter = remap(remaps, state.CycleOffsetParameter);
foreach (var t in state.transitions) foreach (var behavior in state.Behaviours)
{
ProcessTransition(t, remaps);
}
foreach (var behavior in state.behaviours)
{ {
if (behavior is VRCAvatarParameterDriver driver) if (behavior is VRCAvatarParameterDriver driver)
{ {
ProcessDriver(driver, remaps); ProcessDriver(driver, remaps);
} }
} }
ProcessMotion(state.motion, remaps);
} }
private void ProcessMotion(Motion motion, private void ProcessClip(VirtualClip clip,
ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps)
{ {
if (motion is BlendTree blendTree) ProcessBlendtree(blendTree, remaps); var curveBindings = clip.GetFloatCurveBindings();
if (motion is AnimationClip clip) ProcessClip(clip, remaps);
}
private void ProcessClip(AnimationClip clip,
ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps)
{
var curveBindings = AnimationUtility.GetCurveBindings(clip);
var bindingsToUpdate = new List<EditorCurveBinding>(); var bindingsToUpdate = new List<EditorCurveBinding>();
var newCurves = new List<AnimationCurve>(); var newCurves = new List<AnimationCurve>();
@ -653,48 +623,30 @@ namespace nadena.dev.modular_avatar.core.editor
if (binding.path != "" || binding.type != typeof(Animator)) continue; if (binding.path != "" || binding.type != typeof(Animator)) continue;
if (remaps.TryGetValue((ParameterNamespace.Animator, binding.propertyName), out var newBinding)) if (remaps.TryGetValue((ParameterNamespace.Animator, binding.propertyName), out var newBinding))
{ {
var curCurve = AnimationUtility.GetEditorCurve(clip, binding); var curCurve = clip.GetFloatCurve(binding);
var newECB = new EditorCurveBinding
bindingsToUpdate.Add(binding);
newCurves.Add(null);
bindingsToUpdate.Add(new EditorCurveBinding
{ {
path = "", path = "",
type = typeof(Animator), type = typeof(Animator),
propertyName = newBinding.ParameterName 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.BlendParameter = remap(remaps, blendTree.BlendParameter);
blendTree.blendParameterY = remap(remaps, blendTree.blendParameterY); blendTree.BlendParameterY = remap(remaps, blendTree.BlendParameterY);
var children = blendTree.children; var children = blendTree.Children;
for (int i = 0; i < children.Length; i++) foreach (var child in children)
{ {
var childMotion = children[i]; child.DirectBlendParameter = remap(remaps, child.DirectBlendParameter);
ProcessMotion(childMotion.motion, remaps);
childMotion.directBlendParameter = remap(remaps, childMotion.directBlendParameter);
children[i] = childMotion;
} }
blendTree.children = children;
} }
private void ProcessDriver(VRCAvatarParameterDriver driver, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps) private void ProcessDriver(VRCAvatarParameterDriver driver, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> remaps)
@ -710,19 +662,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; bool dirty = false;
var conditions = t.conditions; var conditions = t.Conditions
.Select(cond =>
for (int i = 0; i < conditions.Length; i++) {
{ cond.parameter = remap(remaps, cond.parameter, ref dirty);
var cond = conditions[i]; return cond;
cond.parameter = remap(remaps, cond.parameter, ref dirty); })
conditions[i] = cond; .ToImmutableList();
} t.Conditions = conditions;
if (dirty) t.conditions = conditions;
} }
private ImmutableDictionary<string, ParameterInfo> CollectParameters(ModularAvatarParameters p, private ImmutableDictionary<string, ParameterInfo> CollectParameters(ModularAvatarParameters p,

View File

@ -1,11 +1,14 @@
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
using System.Linq;
using modular_avatar_tests; using modular_avatar_tests;
using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.animation;
using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor;
using nadena.dev.ndmf.animator;
using NUnit.Framework; using NUnit.Framework;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components;
using EditorCurveBinding = UnityEditor.EditorCurveBinding; using EditorCurveBinding = UnityEditor.EditorCurveBinding;
public class ActiveAnimationRetargeterTests : TestBase public class ActiveAnimationRetargeterTests : TestBase
@ -17,8 +20,7 @@ public class ActiveAnimationRetargeterTests : TestBase
// initialize context // initialize context
var buildContext = new BuildContext(avatar); var buildContext = new BuildContext(avatar);
var pathMappings = buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>() var asc = buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
.PathMappings;
// get game objects // get game objects
var changedChild = avatar.transform.Find("Toggled/Child"); var changedChild = avatar.transform.Find("Toggled/Child");
@ -29,18 +31,16 @@ public class ActiveAnimationRetargeterTests : TestBase
var created = retargeter.CreateIntermediateObjects(newParent.gameObject); var created = retargeter.CreateIntermediateObjects(newParent.gameObject);
retargeter.FixupAnimations(); retargeter.FixupAnimations();
// commit var fx = asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX]!;
buildContext.AnimationDatabase.Commit(); var clip = (VirtualClip) fx.Layers.First(l => l.Name == "retarget").StateMachine.DefaultState!.Motion;
var curveBindings = clip!.GetFloatCurveBindings();
var clip = findFxClip(avatar, layerName: "retarget");
var curveBindings = AnimationUtility.GetCurveBindings(clip);
// Intermediate object must be created // Intermediate object must be created
Assert.That(created, Is.Not.EqualTo(newParent.gameObject)); Assert.That(created, Is.Not.EqualTo(newParent.gameObject));
// The created animation must have m_IsActive of intermediate object // The created animation must have m_IsActive of intermediate object
Assert.That(curveBindings, Does.Contain(EditorCurveBinding.FloatCurve( 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;
using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using NUnit.Framework; using NUnit.Framework;
using UnityEngine; using UnityEngine;
using BuildContext = nadena.dev.ndmf.BuildContext; using BuildContext = nadena.dev.ndmf.BuildContext;
@ -22,9 +23,13 @@ namespace UnitTests.MergeAnimatorTests
var ctx = new BuildContext(av, null); var ctx = new BuildContext(av, null);
ctx.ActivateExtensionContext<ModularAvatarContext>(); 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); Assert.IsEmpty(errors);
} }
@ -39,9 +44,15 @@ namespace UnitTests.MergeAnimatorTests
var ctx = new BuildContext(av, null); var ctx = new BuildContext(av, null);
ctx.ActivateExtensionContext<ModularAvatarContext>(); 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); Assert.IsEmpty(errors);

View File

@ -25,6 +25,8 @@ public class PreexistingParamsTest : TestBase
foreach (var kvp in paramDict) foreach (var kvp in paramDict)
{ {
if (kvp.Key.StartsWith("__ModularAvatarInternal/")) continue;
if (kvp.Key == "default_override" || kvp.Key == "animator_only") if (kvp.Key == "default_override" || kvp.Key == "animator_only")
{ {
Assert.AreEqual(1, kvp.Value); 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.animation;
using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core;
using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor;
using nadena.dev.ndmf.animator;
using NUnit.Framework; using NUnit.Framework;
using UnityEngine; using UnityEngine;
@ -54,7 +55,7 @@ namespace modular_avatar_tests.MergeArmatureTests
nadena.dev.ndmf.BuildContext context = nadena.dev.ndmf.BuildContext context =
new nadena.dev.ndmf.BuildContext(root, null); new nadena.dev.ndmf.BuildContext(root, null);
context.ActivateExtensionContext<ModularAvatarContext>(); context.ActivateExtensionContext<ModularAvatarContext>();
context.ActivateExtensionContext<AnimationServicesContext>(); context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new MergeArmatureHook().OnPreprocessAvatar(context, root); new MergeArmatureHook().OnPreprocessAvatar(context, root);
Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null); Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null);
@ -82,7 +83,7 @@ namespace modular_avatar_tests.MergeArmatureTests
nadena.dev.ndmf.BuildContext context = nadena.dev.ndmf.BuildContext context =
new nadena.dev.ndmf.BuildContext(root, null); new nadena.dev.ndmf.BuildContext(root, null);
context.ActivateExtensionContext<ModularAvatarContext>(); context.ActivateExtensionContext<ModularAvatarContext>();
context.ActivateExtensionContext<AnimationServicesContext>(); context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new MergeArmatureHook().OnPreprocessAvatar(context, root); new MergeArmatureHook().OnPreprocessAvatar(context, root);
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
@ -106,7 +107,7 @@ namespace modular_avatar_tests.MergeArmatureTests
nadena.dev.ndmf.BuildContext context = nadena.dev.ndmf.BuildContext context =
new nadena.dev.ndmf.BuildContext(root, null); new nadena.dev.ndmf.BuildContext(root, null);
context.ActivateExtensionContext<ModularAvatarContext>(); context.ActivateExtensionContext<ModularAvatarContext>();
context.ActivateExtensionContext<AnimationServicesContext>(); context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new MergeArmatureHook().OnPreprocessAvatar(context, root); new MergeArmatureHook().OnPreprocessAvatar(context, root);
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass 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;
using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using NUnit.Framework; using NUnit.Framework;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
@ -79,6 +80,7 @@ namespace modular_avatar_tests.RenameParametersTests
var context = CreateContext(prefab); var context = CreateContext(prefab);
var maContext = context.ActivateExtensionContext<ModularAvatarContext>().BuildContext; var maContext = context.ActivateExtensionContext<ModularAvatarContext>().BuildContext;
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
var errors = ErrorReport.CaptureErrors( var errors = ErrorReport.CaptureErrors(
() => () =>
@ -209,6 +211,7 @@ namespace modular_avatar_tests.RenameParametersTests
var context = CreateContext(av); var context = CreateContext(av);
var maContext = context.ActivateExtensionContext<ModularAvatarContext>().BuildContext; var maContext = context.ActivateExtensionContext<ModularAvatarContext>().BuildContext;
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
var errors = ErrorReport.CaptureErrors(() => new RenameParametersHook().OnPreprocessAvatar(av, maContext)); var errors = ErrorReport.CaptureErrors(() => new RenameParametersHook().OnPreprocessAvatar(av, maContext));
@ -243,6 +246,7 @@ namespace modular_avatar_tests.RenameParametersTests
var context = CreateContext(av); var context = CreateContext(av);
var maContext = context.ActivateExtensionContext<ModularAvatarContext>().BuildContext; var maContext = context.ActivateExtensionContext<ModularAvatarContext>().BuildContext;
context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
var errors = ErrorReport.CaptureErrors(() => new RenameParametersHook().OnPreprocessAvatar(av, maContext)); 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.animation;
using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor;
using nadena.dev.ndmf.animator;
using NUnit.Framework; using NUnit.Framework;
using UnityEngine; using UnityEngine;
@ -21,13 +22,12 @@ namespace modular_avatar_tests
Debug.Assert(skinnedMeshRenderer.bones.Length == 0); Debug.Assert(skinnedMeshRenderer.bones.Length == 0);
var build_context = new nadena.dev.ndmf.BuildContext(root, null); var build_context = new nadena.dev.ndmf.BuildContext(root, null);
var torc = new AnimationServicesContext(); var asc = build_context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
torc.OnActivate(build_context);
var bonedb = new BoneDatabase(); var bonedb = new BoneDatabase();
bonedb.AddMergedBone(b.transform); 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(a.transform, skinnedMeshRenderer.rootBone);
} }
@ -47,13 +47,12 @@ namespace modular_avatar_tests
Debug.Assert(skinnedMeshRenderer.bones.Length == 0); Debug.Assert(skinnedMeshRenderer.bones.Length == 0);
var build_context = new nadena.dev.ndmf.BuildContext(root, null); var build_context = new nadena.dev.ndmf.BuildContext(root, null);
var torc = new AnimationServicesContext(); var asc = build_context.ActivateExtensionContextRecursive<AnimatorServicesContext>();
torc.OnActivate(build_context);
var bonedb = new BoneDatabase(); var bonedb = new BoneDatabase();
bonedb.AddMergedBone(b.transform); 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(a.transform, skinnedMeshRenderer.rootBone);
Assert.AreEqual(new Bounds(new Vector3(0, 0, 0), new Vector3(2, 2, 2)), 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;
using nadena.dev.modular_avatar.core.editor.menu; using nadena.dev.modular_avatar.core.editor.menu;
using nadena.dev.modular_avatar.core.menu; using nadena.dev.modular_avatar.core.menu;
using nadena.dev.ndmf.animator;
using NUnit.Framework; using NUnit.Framework;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
@ -644,6 +645,7 @@ namespace modular_avatar_tests.VirtualMenuTests
}; };
var buildContext = new BuildContext(av_root.GetComponent<VRCAvatarDescriptor>()); var buildContext = new BuildContext(av_root.GetComponent<VRCAvatarDescriptor>());
buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new RenameParametersHook().OnPreprocessAvatar(av_root, buildContext); new RenameParametersHook().OnPreprocessAvatar(av_root, buildContext);
var virtualMenu = VirtualMenu.ForAvatar(av_root.GetComponent<VRCAvatarDescriptor>(), buildContext); var virtualMenu = VirtualMenu.ForAvatar(av_root.GetComponent<VRCAvatarDescriptor>(), buildContext);
@ -663,6 +665,7 @@ namespace modular_avatar_tests.VirtualMenuTests
var root = CreatePrefab("InternalParameterTest.prefab"); var root = CreatePrefab("InternalParameterTest.prefab");
BuildContext buildContext = new BuildContext(root.GetComponent<VRCAvatarDescriptor>()); BuildContext buildContext = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new RenameParametersHook().OnPreprocessAvatar(root, buildContext); new RenameParametersHook().OnPreprocessAvatar(root, buildContext);
var virtualMenu = VirtualMenu.ForAvatar(root.GetComponent<VRCAvatarDescriptor>(), buildContext); var virtualMenu = VirtualMenu.ForAvatar(root.GetComponent<VRCAvatarDescriptor>(), buildContext);
@ -676,6 +679,7 @@ namespace modular_avatar_tests.VirtualMenuTests
var root = CreatePrefab("UnusedSubParametersAreStripped.prefab"); var root = CreatePrefab("UnusedSubParametersAreStripped.prefab");
BuildContext buildContext = new BuildContext(root.GetComponent<VRCAvatarDescriptor>()); BuildContext buildContext = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new RenameParametersHook().OnPreprocessAvatar(root, buildContext); new RenameParametersHook().OnPreprocessAvatar(root, buildContext);
var virtualMenu = VirtualMenu.ForAvatar(root.GetComponent<VRCAvatarDescriptor>(), 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.animation;
using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core;
using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor;
using nadena.dev.ndmf.animator;
using NUnit.Framework; using NUnit.Framework;
using UnityEngine.Animations; using UnityEngine.Animations;
@ -16,7 +17,7 @@ public class WorldFixedObjectTest : TestBase
// initialize context // initialize context
var buildContext = new BuildContext(avatar); var buildContext = new BuildContext(avatar);
buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>(); buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new WorldFixedObjectProcessor().Process(buildContext); new WorldFixedObjectProcessor().Process(buildContext);
@ -42,7 +43,7 @@ public class WorldFixedObjectTest : TestBase
// initialize context // initialize context
var buildContext = new BuildContext(avatar); var buildContext = new BuildContext(avatar);
buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>(); buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new WorldFixedObjectProcessor().Process(buildContext); new WorldFixedObjectProcessor().Process(buildContext);
@ -75,7 +76,7 @@ public class WorldFixedObjectTest : TestBase
// initialize context // initialize context
var buildContext = new BuildContext(avatar); var buildContext = new BuildContext(avatar);
var animationServices = buildContext.PluginBuildContext.ActivateExtensionContext<AnimationServicesContext>(); var animationServices = buildContext.PluginBuildContext.ActivateExtensionContextRecursive<AnimatorServicesContext>();
new WorldFixedObjectProcessor().Process(buildContext); new WorldFixedObjectProcessor().Process(buildContext);