2024-10-20 09:58:41 +08:00
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
|
|
|
|
using System.Linq;
|
2024-10-20 08:15:14 +08:00
|
|
|
|
using nadena.dev.modular_avatar.core.editor;
|
2024-07-29 11:22:32 +08:00
|
|
|
|
using nadena.dev.ndmf;
|
2024-11-25 10:01:21 +08:00
|
|
|
|
using nadena.dev.ndmf.animator;
|
2024-07-29 11:22:32 +08:00
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEditor.Animations;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using VRC.SDK3.Avatars.Components;
|
2024-10-20 08:15:14 +08:00
|
|
|
|
using BuildContext = nadena.dev.ndmf.BuildContext;
|
2024-07-29 11:22:32 +08:00
|
|
|
|
|
|
|
|
|
namespace nadena.dev.modular_avatar.animation
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// This pass delays turning GameObjects OFF by one frame when those objects control a ReadableProperty. This
|
|
|
|
|
/// ensures that we don't expose hidden meshes when removing articles of clothing, for example.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class GameObjectDelayDisablePass : Pass<GameObjectDelayDisablePass>
|
|
|
|
|
{
|
|
|
|
|
protected override void Execute(BuildContext context)
|
|
|
|
|
{
|
2024-11-25 10:01:21 +08:00
|
|
|
|
var asc = context.Extension<AnimatorServicesContext>();
|
|
|
|
|
var activeProxies = context.GetState<ReadablePropertyExtension.Retained>().proxyProps;
|
|
|
|
|
if (activeProxies.Count == 0) return;
|
2024-07-29 11:22:32 +08:00
|
|
|
|
|
2024-11-25 10:01:21 +08:00
|
|
|
|
var fx = asc.ControllerContext[VRCAvatarDescriptor.AnimLayerType.FX];
|
2024-07-29 11:22:32 +08:00
|
|
|
|
if (fx == null) return;
|
2024-11-25 10:01:21 +08:00
|
|
|
|
|
2024-10-20 08:15:14 +08:00
|
|
|
|
var nullMotion = new AnimationClip();
|
|
|
|
|
nullMotion.name = "NullMotion";
|
|
|
|
|
|
2024-07-29 11:22:32 +08:00
|
|
|
|
var blendTree = new BlendTree();
|
|
|
|
|
blendTree.blendType = BlendTreeType.Direct;
|
|
|
|
|
blendTree.useAutomaticThresholds = false;
|
|
|
|
|
|
2024-11-25 10:01:21 +08:00
|
|
|
|
blendTree.children = activeProxies
|
|
|
|
|
.Select(prop => GenerateDelayChild(nullMotion, (prop.Key, prop.Value)))
|
2024-10-20 08:15:14 +08:00
|
|
|
|
.ToArray();
|
2024-07-29 11:22:32 +08:00
|
|
|
|
|
2024-11-25 10:01:21 +08:00
|
|
|
|
var layer = fx.AddLayer(LayerPriority.Default, "DelayDisable");
|
|
|
|
|
var state = layer.StateMachine.AddState("DelayDisable");
|
|
|
|
|
layer.StateMachine.DefaultState = state;
|
2024-07-29 11:22:32 +08:00
|
|
|
|
|
2024-11-25 10:01:21 +08:00
|
|
|
|
state.WriteDefaultValues = true;
|
|
|
|
|
state.Motion = asc.ControllerContext.Clone(blendTree);
|
2024-10-20 08:15:23 +08:00
|
|
|
|
|
|
|
|
|
// Ensure the initial state of readable props matches the actual state of the gameobject
|
2024-11-25 10:01:21 +08:00
|
|
|
|
foreach (var controller in asc.ControllerContext.GetAllControllers())
|
2024-10-20 08:15:23 +08:00
|
|
|
|
{
|
2024-11-25 10:01:21 +08:00
|
|
|
|
foreach (var (binding, prop) in activeProxies)
|
2024-10-20 08:15:23 +08:00
|
|
|
|
{
|
2024-11-25 10:01:21 +08:00
|
|
|
|
var obj = asc.ObjectPathRemapper.GetObjectForPath(binding.path);
|
|
|
|
|
|
|
|
|
|
if (obj != null && controller.Parameters.TryGetValue(prop, out var p))
|
|
|
|
|
{
|
|
|
|
|
p.defaultFloat = obj.activeSelf ? 1 : 0;
|
|
|
|
|
controller.Parameters = controller.Parameters.SetItem(prop, p);
|
|
|
|
|
}
|
2024-10-20 08:15:23 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-29 11:22:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-20 08:15:14 +08:00
|
|
|
|
private ChildMotion GenerateDelayChild(Motion nullMotion, (EditorCurveBinding, string) binding)
|
2024-07-29 11:22:32 +08:00
|
|
|
|
{
|
|
|
|
|
var ecb = binding.Item1;
|
|
|
|
|
var prop = binding.Item2;
|
|
|
|
|
|
|
|
|
|
var motion = new AnimationClip();
|
|
|
|
|
var curve = new AnimationCurve();
|
|
|
|
|
curve.AddKey(0, 1);
|
|
|
|
|
AnimationUtility.SetEditorCurve(motion, ecb, curve);
|
|
|
|
|
|
2024-10-20 08:15:14 +08:00
|
|
|
|
// Occasionally, we'll have a very small value pop up, probably due to FP errors.
|
|
|
|
|
// To correct for this, instead of directly using the property in the direct blend tree,
|
|
|
|
|
// we'll use a 1D blend tree to give ourselves a buffer.
|
|
|
|
|
|
|
|
|
|
var bufferBlendTree = new BlendTree();
|
|
|
|
|
bufferBlendTree.blendType = BlendTreeType.Simple1D;
|
|
|
|
|
bufferBlendTree.useAutomaticThresholds = false;
|
|
|
|
|
bufferBlendTree.blendParameter = prop;
|
|
|
|
|
bufferBlendTree.children = new[]
|
|
|
|
|
{
|
|
|
|
|
new ChildMotion
|
|
|
|
|
{
|
|
|
|
|
motion = nullMotion,
|
|
|
|
|
timeScale = 1,
|
|
|
|
|
threshold = 0
|
|
|
|
|
},
|
|
|
|
|
new ChildMotion
|
|
|
|
|
{
|
|
|
|
|
motion = nullMotion,
|
|
|
|
|
timeScale = 1,
|
|
|
|
|
threshold = 0.01f
|
|
|
|
|
},
|
|
|
|
|
new ChildMotion
|
|
|
|
|
{
|
|
|
|
|
motion = motion,
|
|
|
|
|
timeScale = 1,
|
|
|
|
|
threshold = 1
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-29 11:22:32 +08:00
|
|
|
|
return new ChildMotion
|
|
|
|
|
{
|
2024-10-20 08:15:14 +08:00
|
|
|
|
motion = bufferBlendTree,
|
|
|
|
|
directBlendParameter = MergeBlendTreePass.ALWAYS_ONE,
|
2024-07-29 11:22:32 +08:00
|
|
|
|
timeScale = 1
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-20 09:58:41 +08:00
|
|
|
|
}
|
|
|
|
|
#endif
|