using System.Linq; using nadena.dev.modular_avatar.core.editor; using nadena.dev.ndmf; using UnityEditor; using UnityEditor.Animations; using UnityEngine; using VRC.SDK3.Avatars.Components; using BuildContext = nadena.dev.ndmf.BuildContext; namespace nadena.dev.modular_avatar.animation { /// /// 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. /// internal class GameObjectDelayDisablePass : Pass { protected override void Execute(BuildContext context) { var asc = context.Extension(); if (!asc.BoundReadableProperties.Any()) return; var fx = (AnimatorController)context.AvatarDescriptor.baseAnimationLayers .FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX).animatorController; if (fx == null) return; var nullMotion = new AnimationClip(); nullMotion.name = "NullMotion"; var blendTree = new BlendTree(); blendTree.blendType = BlendTreeType.Direct; blendTree.useAutomaticThresholds = false; blendTree.children = asc.BoundReadableProperties .Select(prop => GenerateDelayChild(nullMotion, prop)) .ToArray(); var asm = new AnimatorStateMachine(); var state = new AnimatorState(); state.name = "DelayDisable"; state.motion = blendTree; state.writeDefaultValues = true; asm.defaultState = state; asm.states = new[] { new ChildAnimatorState { state = state, position = Vector3.zero } }; fx.layers = fx.layers.Append(new AnimatorControllerLayer { name = "DelayDisable", stateMachine = asm, defaultWeight = 1, blendingMode = AnimatorLayerBlendingMode.Override }).ToArray(); } private ChildMotion GenerateDelayChild(Motion nullMotion, (EditorCurveBinding, string) binding) { 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); // 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 } }; return new ChildMotion { motion = bufferBlendTree, directBlendParameter = MergeBlendTreePass.ALWAYS_ONE, timeScale = 1 }; } } }