refactor: use IVirtualize*

This commit is contained in:
bd_ 2025-03-09 14:32:50 -07:00
parent e3a01ff58b
commit e91b8ab6c3
8 changed files with 126 additions and 120 deletions

View File

@ -27,6 +27,7 @@
using System.Collections.Generic;
using System.Linq;
using nadena.dev.ndmf.animator;
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using Object = UnityEngine.Object;
@ -37,6 +38,23 @@ namespace nadena.dev.modular_avatar.core.editor
{
private AnimatorServicesContext _asc;
[InitializeOnLoadMethod]
private static void Init()
{
ModularAvatarMergeAnimator.GetMotionBasePathCallback = (merge, objectBuildContext) =>
{
if (merge.pathMode == MergeAnimatorPathMode.Absolute) return "";
var context = (ndmf.BuildContext)objectBuildContext;
var targetObject = merge.relativePathRoot.Get(context.AvatarRootTransform);
if (targetObject == null) targetObject = merge.gameObject;
var relativePath = RuntimeUtil.RelativePath(context.AvatarRootObject, targetObject);
return relativePath != "" ? relativePath : "";
};
}
internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context)
{
_asc = context.PluginBuildContext.Extension<AnimatorServicesContext>();
@ -92,35 +110,18 @@ namespace nadena.dev.modular_avatar.core.editor
}
}
private void MergeSingle(BuildContext context, VirtualAnimatorController controller, ModularAvatarMergeAnimator merge, bool? initialWriteDefaults)
private void MergeSingle(BuildContext context, VirtualAnimatorController targetController,
ModularAvatarMergeAnimator merge, bool? initialWriteDefaults)
{
if (merge.animator == null)
{
return;
}
var stash = context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var clonedController = stash.Controllers.GetValueOrDefault(merge)
?? _asc.ControllerContext.CloneContext.CloneDistinct(merge.animator);
string basePath;
if (merge.pathMode == MergeAnimatorPathMode.Relative)
{
var targetObject = merge.relativePathRoot.Get(context.AvatarRootTransform);
if (targetObject == null) targetObject = merge.gameObject;
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 vac = context.PluginBuildContext.Extension<VirtualControllerContext>();
if (!vac.Controllers.TryGetValue(merge, out var clonedController)) return;
var firstLayer = clonedController.Layers.FirstOrDefault();
// the first layer in an animator controller always has weight 1.0f (regardless of what is serialized)
if (firstLayer != null) firstLayer.DefaultWeight = 1.0f;
@ -134,12 +135,13 @@ namespace nadena.dev.modular_avatar.core.editor
s.WriteDefaultValues = initialWriteDefaults.Value;
}
}
controller.AddLayer(new LayerPriority(merge.layerPriority), l);
targetController.AddLayer(new LayerPriority(merge.layerPriority), l);
}
foreach (var (name, parameter) in clonedController.Parameters)
{
if (controller.Parameters.TryGetValue(name, out var existingParam))
if (targetController.Parameters.TryGetValue(name, out var existingParam))
{
if (existingParam.type != parameter.type)
{
@ -156,12 +158,12 @@ namespace nadena.dev.modular_avatar.core.editor
existingParam.type = AnimatorControllerParameterType.Float;
controller.Parameters = controller.Parameters.SetItem(name, existingParam);
targetController.Parameters = targetController.Parameters.SetItem(name, existingParam);
}
continue;
}
controller.Parameters = controller.Parameters.Add(name, parameter);
targetController.Parameters = targetController.Parameters.Add(name, parameter);
}
if (merge.deleteAttachedAnimator)
@ -169,6 +171,8 @@ namespace nadena.dev.modular_avatar.core.editor
var animator = merge.GetComponent<Animator>();
if (animator != null) Object.DestroyImmediate(animator);
}
Object.DestroyImmediate(merge);
}
}
}

View File

@ -5,6 +5,7 @@
using System.Collections.Generic;
using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
@ -22,6 +23,21 @@ namespace nadena.dev.modular_avatar.core.editor
private VirtualBlendTree _rootBlendTree;
private HashSet<string> _parameterNames;
[InitializeOnLoadMethod]
private static void Init()
{
ModularAvatarMergeBlendTree.GetMotionBasePathCallback = (mbt, objectBuildContext) =>
{
if (mbt.PathMode == MergeAnimatorPathMode.Absolute) return "";
var buildContext = (ndmf.BuildContext)objectBuildContext;
var root = mbt.RelativePathRoot.Get(buildContext.AvatarRootTransform);
if (root == null) root = mbt.gameObject;
return RuntimeUtil.AvatarRootPath(root);
};
}
protected override void Execute(ndmf.BuildContext context)
{
_asc = context.Extension<AnimatorServicesContext>();
@ -59,48 +75,28 @@ namespace nadena.dev.modular_avatar.core.editor
private void ProcessComponent(BuildContext context, ModularAvatarMergeBlendTree component)
{
var stash = context.PluginBuildContext.GetState<RenamedMergeAnimators>();
BlendTree componentBlendTree = component.BlendTree as BlendTree;
if (componentBlendTree == null)
var virtualBlendTree = _asc.ControllerContext.GetVirtualizedMotion(component);
if (virtualBlendTree == null)
{
ErrorReport.ReportError(Localization.L, ErrorSeverity.NonFatal, "error.merge_blend_tree.missing_tree");
return;
}
string basePath = null;
string rootPath = null;
if (component.PathMode == MergeAnimatorPathMode.Relative)
{
var root = component.RelativePathRoot.Get(context.AvatarRootTransform);
if (root == null) root = component.gameObject;
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 rootBlend = GetRootBlendTree();
rootBlend.Children = rootBlend.Children.Add(new()
{
Motion = bt,
Motion = virtualBlendTree,
DirectBlendParameter = ALWAYS_ONE,
Threshold = 1,
CycleOffset = 1,
TimeScale = 1,
});
foreach (var asset in bt.AllReachableNodes())
foreach (var asset in virtualBlendTree.AllReachableNodes())
{
if (asset is VirtualBlendTree bt2)
{
@ -129,6 +125,8 @@ namespace nadena.dev.modular_avatar.core.editor
}
}
}
Object.DestroyImmediate(component);
}
private VirtualBlendTree GetRootBlendTree()

View File

@ -10,7 +10,6 @@ using nadena.dev.modular_avatar.editor.ErrorReporting;
using nadena.dev.ndmf;
using nadena.dev.ndmf.animator;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
@ -55,43 +54,6 @@ 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.CloneDistinct(mama.animator, mama.layerType);
Controllers[mama] = cloned;
return cloned;
}
public VirtualBlendTree Clone(ModularAvatarMergeBlendTree mbt)
{
if (BlendTrees.TryGetValue(mbt, out var blendTree))
{
return blendTree;
}
if (mbt.BlendTree is not BlendTree bt) return null;
var cloned = (VirtualBlendTree)AnimatorServices.ControllerContext.CloneContext.Clone(bt);
BlendTrees[mbt] = cloned;
return cloned;
}
}
internal class RenameParametersHook
{
private const string DEFAULT_EXP_PARAMS_ASSET_GUID = "03a6d797deb62f0429471c4e17ea99a7";
@ -200,10 +162,6 @@ namespace nadena.dev.modular_avatar.core.editor
_context = context;
var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var asc = _context.PluginBuildContext.Extension<AnimatorServicesContext>();
stash.AnimatorServices = asc;
var syncParams = WalkTree(avatar);
SetExpressionParameters(avatar, syncParams);
@ -359,6 +317,8 @@ namespace nadena.dev.modular_avatar.core.editor
GameObject obj
)
{
var animServices = _context.PluginBuildContext.Extension<AnimatorServicesContext>();
var paramInfo = ndmf.ParameterInfo.ForContext(_context.PluginBuildContext);
ImmutableDictionary<string, ParameterInfo> rv = ImmutableDictionary<string, ParameterInfo>.Empty;
@ -406,7 +366,7 @@ namespace nadena.dev.modular_avatar.core.editor
break;
}
case ModularAvatarMergeAnimator merger:
case IVirtualizeAnimatorController virtualized:
{
var mappings = paramInfo.GetParameterRemappingsAt(obj);
var remap = mappings.SelectMany(item =>
@ -421,15 +381,10 @@ namespace nadena.dev.modular_avatar.core.editor
);
}).ToImmutableDictionary();
if (merger.animator != null)
var controller = animServices.ControllerContext[virtualized];
if (controller != null)
{
var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var controller = stash.Clone(merger);
ProcessVirtualAnimatorController(controller, remap);
stash.Controllers[merger] = controller;
}
break;
@ -437,15 +392,10 @@ namespace nadena.dev.modular_avatar.core.editor
case ModularAvatarMergeBlendTree merger:
{
var bt = merger.BlendTree as BlendTree;
if (bt != null)
var motion = animServices.ControllerContext.GetVirtualizedMotion(merger);
if (motion is VirtualBlendTree bt)
{
var stash = _context.PluginBuildContext.GetState<RenamedMergeAnimators>();
var virtualbt = stash.Clone(merger);
ProcessBlendtree(virtualbt, paramInfo.GetParameterRemappingsAt(obj));
stash.BlendTrees[merger] = virtualbt;
ProcessBlendtree(bt, paramInfo.GetParameterRemappingsAt(obj));
}
break;

View File

@ -23,9 +23,9 @@
*/
using System;
using nadena.dev.ndmf;
using UnityEngine;
#if MA_VRCSDK3_AVATARS
using VRC.SDKBase;
#endif
namespace nadena.dev.modular_avatar.core
@ -35,7 +35,7 @@ namespace nadena.dev.modular_avatar.core
/// inherited by user classes, and will be removed in Modular Avatar 2.0.
/// </summary>
[DefaultExecutionOrder(-9999)] // run before av3emu
public abstract class AvatarTagComponent : MonoBehaviour, IEditorOnly
public abstract class AvatarTagComponent : MonoBehaviour, INDMFEditorOnly
{
internal static event Action OnChangeAction;

View File

@ -25,6 +25,7 @@
#if MA_VRCSDK3_AVATARS
using System;
using nadena.dev.ndmf.animator;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
@ -38,8 +39,11 @@ namespace nadena.dev.modular_avatar.core
[AddComponentMenu("Modular Avatar/MA Merge Animator")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-animator?lang=auto")]
public class ModularAvatarMergeAnimator : AvatarTagComponent
public class ModularAvatarMergeAnimator : AvatarTagComponent, IVirtualizeAnimatorController
{
internal static Func<ModularAvatarMergeAnimator, object, string> GetMotionBasePathCallback =
(_, _) => "";
public RuntimeAnimatorController animator;
public VRCAvatarDescriptor.AnimLayerType layerType = VRCAvatarDescriptor.AnimLayerType.FX;
public bool deleteAttachedAnimator;
@ -47,7 +51,7 @@ namespace nadena.dev.modular_avatar.core
public bool matchAvatarWriteDefaults;
public AvatarObjectReference relativePathRoot = new AvatarObjectReference();
public int layerPriority = 0;
public override void ResolveReferences()
{
// no-op
@ -67,6 +71,22 @@ namespace nadena.dev.modular_avatar.core
{
deleteAttachedAnimator = true;
}
RuntimeAnimatorController IVirtualizeAnimatorController.AnimatorController
{
get => animator;
set => animator = value;
}
string IVirtualizeAnimatorController.GetMotionBasePath(object ndmfBuildContext, bool clearPath = true)
{
var path = GetMotionBasePathCallback(this, ndmfBuildContext);
if (clearPath) pathMode = MergeAnimatorPathMode.Absolute;
return path;
}
object IVirtualizeAnimatorController.TargetControllerKey => layerType;
}
}

View File

@ -1,17 +1,41 @@
#if MA_VRCSDK3_AVATARS
using System;
using API;
using UnityEngine;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core
{
[AddComponentMenu("Modular Avatar/MA Merge Blend Tree")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-blend-tree?lang=auto")]
public sealed class ModularAvatarMergeBlendTree : AvatarTagComponent
public sealed class ModularAvatarMergeBlendTree : AvatarTagComponent, IVirtualizeMotion
{
internal static Func<ModularAvatarMergeBlendTree, object, string> GetMotionBasePathCallback
= (_, _) => "";
// We can't actually reference a BlendTree here because it's not available when building a player build
public UnityEngine.Object BlendTree;
public Object BlendTree;
public MergeAnimatorPathMode PathMode = MergeAnimatorPathMode.Relative;
public AvatarObjectReference RelativePathRoot = new AvatarObjectReference();
Motion IVirtualizeMotion.Motion
{
get => (Motion)BlendTree;
set => BlendTree = value;
}
string IVirtualizeMotion.GetMotionBasePath(object ndmfBuildContext, bool clearPath = true)
{
var path = GetMotionBasePathCallback(this, ndmfBuildContext);
if (clearPath)
{
PathMode = MergeAnimatorPathMode.Absolute;
}
return path;
}
}
}

View File

@ -25,13 +25,14 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 521542568012153140}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -1.6884441, y: 1.1147345, z: -4.914471}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6479369535091914718}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &8063907918941263456
MonoBehaviour:
@ -59,6 +60,7 @@ MonoBehaviour:
unityVersion:
portraitCameraPositionOffset: {x: 0, y: 0, z: 0}
portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139}
networkIDs: []
customExpressions: 0
expressionsMenu: {fileID: 0}
expressionParameters: {fileID: 0}
@ -335,12 +337,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7379231148671166614}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 2616869947308232229}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5229589187063892731
MonoBehaviour:
@ -366,8 +369,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3}
m_Name:
m_EditorClassIdentifier:
animator: {fileID: 9100000, guid: 22f4686ec8638e243854be6fccb38820, type: 2}
animator: {fileID: 9100000, guid: 26ef9d769605a1e46a5ad2768e28ad73, type: 2}
layerType: 5
deleteAttachedAnimator: 0
pathMode: 0
matchAvatarWriteDefaults: 0
relativePathRoot:
referencePath:
targetObject: {fileID: 0}
layerPriority: 0

View File

@ -1,7 +1,9 @@
#if MA_VRCSDK3_AVATARS
using System.Linq;
using nadena.dev.modular_avatar.core.editor;
using NUnit.Framework;
using UnityEditor.Animations;
using VRC.SDK3.Avatars.Components;
namespace modular_avatar_tests.SyncedLayerHandling
@ -102,6 +104,7 @@ namespace modular_avatar_tests.SyncedLayerHandling
var prefab = CreatePrefab("MergedController_AC2.prefab");
AvatarProcessor.ProcessAvatar(prefab);
var fx = (AnimatorController) FindFxController(prefab).animatorController;
var mainLayer = findFxLayer(prefab, "main");
var syncLayer = findFxLayer(prefab, "sync");
@ -114,7 +117,7 @@ namespace modular_avatar_tests.SyncedLayerHandling
var layercontrol = overrides[0] as VRCAnimatorLayerControl;
Assert.NotNull(layercontrol);
Assert.AreEqual(1, layercontrol.layer);
Assert.AreEqual(layercontrol.layer, fx.layers.TakeWhile(l => l.name != "main").Count());
}
}
}