From 0c54e1fc74fe7677a0d4fef1c147c6e886d182db Mon Sep 17 00:00:00 2001 From: yum Date: Sun, 11 May 2025 22:22:48 -0700 Subject: code bomb --- GenerateTextAnimator.cs | 200 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 GenerateTextAnimator.cs (limited to 'GenerateTextAnimator.cs') diff --git a/GenerateTextAnimator.cs b/GenerateTextAnimator.cs new file mode 100644 index 0000000..fb4a1ee --- /dev/null +++ b/GenerateTextAnimator.cs @@ -0,0 +1,200 @@ +#if UNITY_EDITOR +using AnimatorAsCode.V1; +using AnimatorAsCode.V1.ModularAvatar; +using YumTools; +using nadena.dev.ndmf; +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using VRC.SDKBase; + +// This example uses NDMF. See https://github.com/bdunderscore/ndmf?tab=readme-ov-file#getting-started +[assembly: ExportsPlugin(typeof(GenerateTextAnimatorPlugin))] +namespace YumTools +{ + public class GenerateTextAnimator : MonoBehaviour, IEditorOnly + { + // The number of blocks addressable. + public int numBlocks = 10; + // The number of datums sent per block. + public int blockWidth = 5; + // The number of bytes per datum. + public int bytesPerDatum = 2; + + public string oscPointerParam = "_Unigram_Letter_Grid_OSC_Pointer"; + + // Data sent through OSC uses this pattern. + public string[] oscParamPerBlockDatumByte = {"_Unigram_Letter_Grid_OSC_Datum{0:00}_Byte{1:00}"}; + // The pattern of the parameters which this script will animate. + public string[] matPropPerBlockDatumByte = {"_Unigram_Letter_Grid_Data_Block{0:00}_Datum{1:00}_Byte{2:00}_Animated"}; + + public string[] oscParamPerBlockDatum = {}; + public string[] matPropPerBlockDatum = {}; + + public string[] oscParamPerBlock = {"_Unigram_Letter_Grid_OSC_Visual_Pointer"}; + public string[] matPropPerBlock = {"_Unigram_Letter_Grid_Block_{0:00}_Visual_Pointer_Animated"}; + } + + public class GenerateTextAnimatorPlugin : Plugin + { + public override string QualifiedName => "yum.generate_chat_animator"; + public override string DisplayName => "Chat Animator"; + + private const string SystemName = "GenerateTextAnimator"; + // Direct blendtrees have special semantics with write defaults. We want + // them on. They will not fuck up the rest of the animator, whether it uses + // write defaults or not. + private const bool UseWriteDefaults = true; + + protected override void Configure() + { + InPhase(BuildPhase.Generating).Run($"Generate {DisplayName}", Generate); + } + + private AacFlBlendTreeDirect Add8BitBlendTree( + AacFlBase aac, + AacFlLayer layer, + GenerateTextAnimator cfg, + AacFlBlendTreeDirect tree, + string matProp, string oscParam, string oneParam) { + var offAnim = aac.NewClip().Animating(clip => + { + clip.Animates(cfg.GetComponent(), "material." + matProp).WithFrameCountUnit(keyframes => + keyframes.Constant(/*when=*/0, /*value=*/0)); + }); + var onAnim = aac.NewClip().Animating(clip => + { + clip.Animates(cfg.GetComponent(), "material." + matProp).WithFrameCountUnit(keyframes => + keyframes.Constant(/*when=*/0, /*value=*/255)); + }); + var subtree = aac.NewBlendTree().Simple1D(layer.FloatParameter(oscParam)) + .WithAnimation(offAnim, /*threshold=*/-1) + .WithAnimation(onAnim, /*threshold=*/ 1); + return tree.WithAnimation(subtree, layer.FloatParameter("AlwaysOne")); + } + + private void Generate(BuildContext ctx) + { + var components = ctx.AvatarRootTransform.GetComponentsInChildren(true); + if (components.Length == 0) return; + + var aac = AacV1.Create(new AacConfiguration + { + SystemName = SystemName, + AnimatorRoot = ctx.AvatarRootTransform, + DefaultValueRoot = ctx.AvatarRootTransform, + AssetKey = GUID.Generate().ToString(), + AssetContainer = ctx.AssetContainer, + ContainerMode = AacConfiguration.Container.OnlyWhenPersistenceRequired, + AssetContainerProvider = new NDMFContainerProvider(ctx), + DefaultsProvider = new AacDefaultsProvider(UseWriteDefaults) + }); + + // Create a new object in the scene. We will add Modular Avatar components inside it. + var modularAvatar = MaAc.Create(new GameObject(SystemName) + { + transform = { parent = ctx.AvatarRootTransform } + }); + + var ctrl = aac.NewAnimatorController(); + for (int component_i = 0; component_i < components.Length; component_i++) { + GenerateTextAnimator cfg = components[component_i]; + var layer = ctrl.NewLayer($"Chatbox Plumbing (component #{component_i})"); + + var baseState = layer.NewState("Entry (noop)").WithWriteDefaultsSetTo(false); + + // Create "always one" value to use in DBT. + // See https://vrc.school/docs/Other/DBT-Combining/ for details. + layer.OverrideValue(layer.FloatParameter("AlwaysOne"), 1.0f); + + // Create blendtrees. One for each block of data. + var block_trees = new List(); + AacFlState last_block_state = null; + // For each block. + for (int i = 0; i < cfg.numBlocks; i++) { + var block_tree = aac.NewBlendTree().Direct(); + + // Create block-level animations. + for (int ii = 0; ii < cfg.oscParamPerBlock.Length; ii++) { + string matPropB = string.Format(cfg.matPropPerBlock[ii], i); + string oscParamB = cfg.oscParamPerBlock[ii]; + block_tree = Add8BitBlendTree(aac, layer, cfg, block_tree, matPropB, oscParamB, "AlwaysOne"); + } + + // For each datum per block. + for (int j = 0; j < cfg.blockWidth; j++) { + // Create (block, datum)-level animations. + for (int ii = 0; ii < cfg.oscParamPerBlockDatum.Length; ii++) { + string matPropBD = string.Format(cfg.matPropPerBlockDatum[ii], i, j); + string oscParamBD = string.Format(cfg.oscParamPerBlockDatum[ii], j); + block_tree = Add8BitBlendTree(aac, layer, cfg, block_tree, matPropBD, oscParamBD, "AlwaysOne"); + } + + // For each byte per datum. + for (int k = 0; k < cfg.bytesPerDatum; k++) { + // Create (block, datum, byte)-level animations. + for (int ii = 0; ii < cfg.oscParamPerBlockDatumByte.Length; ii++) { + string matPropBDB = string.Format(cfg.matPropPerBlockDatumByte[ii], i, j, k); + //Debug.Log($"animating property: {matPropBDB}"); + string oscParamBDB = string.Format(cfg.oscParamPerBlockDatumByte[ii], j, k); + block_tree = Add8BitBlendTree(aac, layer, cfg, block_tree, matPropBDB, oscParamBDB, "AlwaysOne"); + } + } + } + + var cur_block_state = layer.NewState($"Block {i}").WithAnimation(block_tree); + if (last_block_state != null) { + cur_block_state = cur_block_state.Under(last_block_state); + } + last_block_state = cur_block_state; + block_trees.Add(cur_block_state); + } + + // Create transitions to each block's blendtree. + for (int i = 0; i < cfg.numBlocks; i++) { + block_trees[i].TransitionsFromAny() + //.WithInterruption(TransitionInterruptionSource.Source) + .When(layer.IntParameter(cfg.oscPointerParam).IsEqualTo(i)); + } + + // Create sync params (VRCSDK) + for (int ii = 0; ii < cfg.oscParamPerBlock.Length; ii++) { + string bParam = cfg.oscParamPerBlock[ii]; + modularAvatar.NewParameter(layer.FloatParameter(bParam)); + } + for (int i = 0; i < cfg.blockWidth; i++) { + for (int ii = 0; ii < cfg.oscParamPerBlockDatum.Length; ii++) { + string bdParam = string.Format(cfg.oscParamPerBlockDatum[ii], i); + modularAvatar.NewParameter(layer.FloatParameter(bdParam)); + } + for (int j = 0; j < cfg.bytesPerDatum; j++) { + for (int ii = 0; ii < cfg.oscParamPerBlockDatumByte.Length; ii++) { + string bdbParam = string.Format(cfg.oscParamPerBlockDatumByte[ii], i, j); + modularAvatar.NewParameter(layer.FloatParameter(bdbParam)).WithDefaultValue(1.0f); + } + } + } + + modularAvatar.NewParameter(layer.IntParameter(cfg.oscPointerParam)); + } + + // By creating a Modular Avatar Merge Animator component, + // our animator controller will be added to the avatar's FX layer. + modularAvatar.NewMergeAnimator(ctrl.AnimatorController, VRCAvatarDescriptor.AnimLayerType.FX); + } + } + + // (For AAC 1.2.0 and above) This is recommended starting from NDMF 1.6.0. You only need to define this class once. + internal class NDMFContainerProvider : IAacAssetContainerProvider + { + private readonly BuildContext _ctx; + public NDMFContainerProvider(BuildContext ctx) => _ctx = ctx; + public void SaveAsPersistenceRequired(Object objectToAdd) => _ctx.AssetSaver.SaveAsset(objectToAdd); + public void SaveAsRegular(Object objectToAdd) { } // Let NDMF crawl our assets when it finishes + public void ClearPreviousAssets() { } // ClearPreviousAssets is never used in non-destructive contexts + } +} +#endif + -- cgit v1.2.3