yum/FastTextPager

Compressed text paging over OSC.

git clone https://git.yummers.dev/yum/FastTextPager

yumcode bomb0c54e1f

master
8.7 KiB200 linesraw
1#if UNITY_EDITOR
2using AnimatorAsCode.V1;
3using AnimatorAsCode.V1.ModularAvatar;
4using YumTools;
5using nadena.dev.ndmf;
6using System.Collections.Generic;
7using UnityEditor;
8using UnityEditor.Animations;
9using UnityEngine;
10using VRC.SDK3.Avatars.Components;
11using VRC.SDKBase;
12
13// This example uses NDMF. See https://github.com/bdunderscore/ndmf?tab=readme-ov-file#getting-started
14[assembly: ExportsPlugin(typeof(GenerateTextAnimatorPlugin))]
15namespace YumTools
16{
17  public class GenerateTextAnimator : MonoBehaviour, IEditorOnly
18  {
19    // The number of blocks addressable.
20    public int numBlocks = 10;
21    // The number of datums sent per block.
22    public int blockWidth = 5;
23    // The number of bytes per datum.
24    public int bytesPerDatum = 2;
25
26    public string oscPointerParam = "_Unigram_Letter_Grid_OSC_Pointer";
27
28    // Data sent through OSC uses this pattern.
29    public string[] oscParamPerBlockDatumByte = {"_Unigram_Letter_Grid_OSC_Datum{0:00}_Byte{1:00}"};
30    // The pattern of the parameters which this script will animate.
31    public string[] matPropPerBlockDatumByte = {"_Unigram_Letter_Grid_Data_Block{0:00}_Datum{1:00}_Byte{2:00}_Animated"};
32
33    public string[] oscParamPerBlockDatum = {};
34    public string[] matPropPerBlockDatum = {};
35
36    public string[] oscParamPerBlock = {"_Unigram_Letter_Grid_OSC_Visual_Pointer"};
37    public string[] matPropPerBlock = {"_Unigram_Letter_Grid_Block_{0:00}_Visual_Pointer_Animated"};
38  }
39
40  public class GenerateTextAnimatorPlugin : Plugin<GenerateTextAnimatorPlugin>
41  {
42    public override string QualifiedName => "yum.generate_chat_animator";
43    public override string DisplayName => "Chat Animator";
44
45    private const string SystemName = "GenerateTextAnimator";
46    // Direct blendtrees have special semantics with write defaults. We want
47    // them on. They will not fuck up the rest of the animator, whether it uses
48    // write defaults or not.
49    private const bool UseWriteDefaults = true;
50
51    protected override void Configure()
52    {
53      InPhase(BuildPhase.Generating).Run($"Generate {DisplayName}", Generate);
54    }
55
56    private AacFlBlendTreeDirect Add8BitBlendTree(
57        AacFlBase aac,
58        AacFlLayer layer,
59        GenerateTextAnimator cfg,
60        AacFlBlendTreeDirect tree,
61        string matProp, string oscParam, string oneParam) {
62      var offAnim = aac.NewClip().Animating(clip =>
63          {
64          clip.Animates(cfg.GetComponent<Renderer>(), "material." + matProp).WithFrameCountUnit(keyframes =>
65              keyframes.Constant(/*when=*/0, /*value=*/0));
66          });
67      var onAnim = aac.NewClip().Animating(clip =>
68          {
69          clip.Animates(cfg.GetComponent<Renderer>(), "material." + matProp).WithFrameCountUnit(keyframes =>
70              keyframes.Constant(/*when=*/0, /*value=*/255));
71          });
72      var subtree = aac.NewBlendTree().Simple1D(layer.FloatParameter(oscParam))
73        .WithAnimation(offAnim, /*threshold=*/-1)
74        .WithAnimation(onAnim,  /*threshold=*/ 1);
75      return tree.WithAnimation(subtree, layer.FloatParameter("AlwaysOne"));
76    }
77
78    private void Generate(BuildContext ctx)
79    {
80      var components = ctx.AvatarRootTransform.GetComponentsInChildren<GenerateTextAnimator>(true);
81      if (components.Length == 0) return;
82
83      var aac = AacV1.Create(new AacConfiguration
84          {
85          SystemName = SystemName,
86          AnimatorRoot = ctx.AvatarRootTransform,
87          DefaultValueRoot = ctx.AvatarRootTransform,
88          AssetKey = GUID.Generate().ToString(),
89          AssetContainer = ctx.AssetContainer,
90          ContainerMode = AacConfiguration.Container.OnlyWhenPersistenceRequired,
91          AssetContainerProvider = new NDMFContainerProvider(ctx),
92          DefaultsProvider = new AacDefaultsProvider(UseWriteDefaults)
93          });
94
95      // Create a new object in the scene. We will add Modular Avatar components inside it.
96      var modularAvatar = MaAc.Create(new GameObject(SystemName)
97          {
98          transform = { parent = ctx.AvatarRootTransform }
99          });
100
101      var ctrl = aac.NewAnimatorController();
102      for (int component_i = 0; component_i < components.Length; component_i++) {
103        GenerateTextAnimator cfg = components[component_i];
104        var layer = ctrl.NewLayer($"Chatbox Plumbing (component #{component_i})");
105
106        var baseState = layer.NewState("Entry (noop)").WithWriteDefaultsSetTo(false);
107
108        // Create "always one" value to use in DBT.
109        // See https://vrc.school/docs/Other/DBT-Combining/ for details.
110        layer.OverrideValue(layer.FloatParameter("AlwaysOne"), 1.0f);
111
112        // Create blendtrees. One for each block of data.
113        var block_trees = new List<AacFlState>();
114        AacFlState last_block_state = null;
115        // For each block.
116        for (int i = 0; i < cfg.numBlocks; i++) {
117          var block_tree = aac.NewBlendTree().Direct();
118
119          // Create block-level animations.
120          for (int ii = 0; ii < cfg.oscParamPerBlock.Length; ii++) {
121            string matPropB = string.Format(cfg.matPropPerBlock[ii], i);
122            string oscParamB = cfg.oscParamPerBlock[ii];
123            block_tree = Add8BitBlendTree(aac, layer, cfg, block_tree, matPropB, oscParamB, "AlwaysOne");
124          }
125
126          // For each datum per block.
127          for (int j = 0; j < cfg.blockWidth; j++) {
128            // Create (block, datum)-level animations.
129            for (int ii = 0; ii < cfg.oscParamPerBlockDatum.Length; ii++) {
130              string matPropBD = string.Format(cfg.matPropPerBlockDatum[ii], i, j);
131              string oscParamBD = string.Format(cfg.oscParamPerBlockDatum[ii], j);
132              block_tree = Add8BitBlendTree(aac, layer, cfg, block_tree, matPropBD, oscParamBD, "AlwaysOne");
133            }
134
135            // For each byte per datum.
136            for (int k = 0; k < cfg.bytesPerDatum; k++) {
137              // Create (block, datum, byte)-level animations.
138              for (int ii = 0; ii < cfg.oscParamPerBlockDatumByte.Length; ii++) {
139                string matPropBDB = string.Format(cfg.matPropPerBlockDatumByte[ii], i, j, k);
140                //Debug.Log($"animating property: {matPropBDB}");
141                string oscParamBDB = string.Format(cfg.oscParamPerBlockDatumByte[ii], j, k);
142                block_tree = Add8BitBlendTree(aac, layer, cfg, block_tree, matPropBDB, oscParamBDB, "AlwaysOne");
143              }
144            }
145          }
146
147          var cur_block_state = layer.NewState($"Block {i}").WithAnimation(block_tree);
148          if (last_block_state != null) {
149            cur_block_state = cur_block_state.Under(last_block_state);
150          }
151          last_block_state = cur_block_state;
152          block_trees.Add(cur_block_state);
153        }
154
155        // Create transitions to each block's blendtree.
156        for (int i = 0; i < cfg.numBlocks; i++) {
157          block_trees[i].TransitionsFromAny()
158            //.WithInterruption(TransitionInterruptionSource.Source)
159            .When(layer.IntParameter(cfg.oscPointerParam).IsEqualTo(i));
160        }
161
162        // Create sync params (VRCSDK)
163        for (int ii = 0; ii < cfg.oscParamPerBlock.Length; ii++) {
164          string bParam = cfg.oscParamPerBlock[ii];
165          modularAvatar.NewParameter(layer.FloatParameter(bParam));
166        }
167        for (int i = 0; i < cfg.blockWidth; i++) {
168          for (int ii = 0; ii < cfg.oscParamPerBlockDatum.Length; ii++) {
169            string bdParam = string.Format(cfg.oscParamPerBlockDatum[ii], i);
170            modularAvatar.NewParameter(layer.FloatParameter(bdParam));
171          }
172          for (int j = 0; j < cfg.bytesPerDatum; j++) {
173            for (int ii = 0; ii < cfg.oscParamPerBlockDatumByte.Length; ii++) {
174              string bdbParam = string.Format(cfg.oscParamPerBlockDatumByte[ii], i, j);
175              modularAvatar.NewParameter(layer.FloatParameter(bdbParam)).WithDefaultValue(1.0f);
176            }
177          }
178        }
179
180        modularAvatar.NewParameter(layer.IntParameter(cfg.oscPointerParam));
181      }
182
183      // By creating a Modular Avatar Merge Animator component,
184      // our animator controller will be added to the avatar's FX layer.
185      modularAvatar.NewMergeAnimator(ctrl.AnimatorController, VRCAvatarDescriptor.AnimLayerType.FX);
186    }
187  }
188
189  // (For AAC 1.2.0 and above) This is recommended starting from NDMF 1.6.0. You only need to define this class once.
190  internal class NDMFContainerProvider : IAacAssetContainerProvider
191  {
192    private readonly BuildContext _ctx;
193    public NDMFContainerProvider(BuildContext ctx) => _ctx = ctx;
194    public void SaveAsPersistenceRequired(Object objectToAdd) => _ctx.AssetSaver.SaveAsset(objectToAdd);
195    public void SaveAsRegular(Object objectToAdd) { } // Let NDMF crawl our assets when it finishes
196    public void ClearPreviousAssets() { } // ClearPreviousAssets is never used in non-destructive contexts
197  }
198}
199#endif
200