diff options
| author | yum <yum.food.vr@gmail.com> | 2022-11-05 12:11:15 -0700 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2022-11-05 12:14:12 -0700 |
| commit | 7f930340e3aa94d6aca120d28436594427469373 (patch) | |
| tree | 9c39374cb8be35d813fcf39db2620da027ac6d7d | |
| parent | ca10de9a5cb2a9b360bc0cd2c357f8d0041f0fca (diff) | |
Reduce dimensionality of animator by factor of 80
Instead of generating one animation for every single character in our
character set, we just generate 2: the lowest and the highest. We use
blend trees to interpolate between these two extremes.
This reduces the number of animations we have to generate by a factor
of 80. It also clears the way for multi-language support (coming soon).
It also means we don't have to reopen unity every time we generate a new
animator.
| -rw-r--r-- | README.md | 8 | ||||
| -rw-r--r-- | generate_params.py | 11 | ||||
| -rw-r--r-- | generate_utils.py | 8 | ||||
| -rw-r--r-- | libtastt.py | 65 | ||||
| -rw-r--r-- | libunity.py | 82 | ||||
| -rw-r--r-- | osc_ctrl.py | 6 |
6 files changed, 129 insertions, 51 deletions
@@ -158,11 +158,11 @@ To use the STT: 2. ~~Text-to-text interface. Type in terminal, show in game.~~ DONE 3. ~~Speech-to-text interface. Speak out loud, show in game.~~ DONE 4. Optimization - 1. Utilize the avatar 3.0 SDK's ability to drive parameters to reduce the + 1. ~~Utilize the avatar 3.0 SDK's ability to drive parameters to reduce the total # of parameters (and therefore OSC messages & sync events). Note - that the parameter memory usage may not decrease. - 2. Optimize FX layer. We have 14k animations and a 1.2 million line FX - layer. Something must be rethought to bring these numbers down. + that the parameter memory usage may not decrease.~~ DONE + 2. ~~Optimize FX layer. We have 14k animations and a 1.2 million line FX + layer. Something must be rethought to bring these numbers down.~~ DONE 3. ~~Implement multicore YAML parsing. This will make working with large animators much more practical.~~ DONE 4. ~~Transcription engine sleep interval increases exponentially up to 1-2 diff --git a/generate_params.py b/generate_params.py index f1be1dd..4cdab00 100644 --- a/generate_params.py +++ b/generate_params.py @@ -34,6 +34,13 @@ BOOL_PARAM = """ defaultValue: 0 """[1:][0:-1] +FLOAT_PARAM = """ + - name: %PARAM_NAME% + valueType: 1 + saved: 0 + defaultValue: 0 +"""[1:][0:-1] + # We're working with an 84-character board, and each FX layer is responsible # for 8 of those characters. params = {} @@ -76,5 +83,5 @@ params["PARAM_NAME"] = generate_utils.getSelectParam() print(generate_utils.replaceMacros(INT_PARAM, params)) for i in range(0, generate_utils.NUM_LAYERS): - params["PARAM_NAME"] = generate_utils.getLayerParam(i) - print(generate_utils.replaceMacros(INT_PARAM, params)) + params["PARAM_NAME"] = generate_utils.getBlendParam(i) + print(generate_utils.replaceMacros(FLOAT_PARAM, params)) diff --git a/generate_utils.py b/generate_utils.py index 2fa2077..171857b 100644 --- a/generate_utils.py +++ b/generate_utils.py @@ -12,7 +12,7 @@ def replaceMacros(lines, macro_defs): BOARD_ROWS=4 BOARD_COLS=44 INDEX_BITS=4 -CHARS_PER_CELL=80 +CHARS_PER_CELL=256 NUM_LAYERS=ceil((BOARD_ROWS * BOARD_COLS) / (2**INDEX_BITS)) @@ -69,6 +69,9 @@ def getLayerParam(which_layer: int) -> str: def getLayerName(which_layer: int) -> str: return getLayerParam(which_layer) +def getBlendParam(which_layer: int) -> str: + return "TaSTT_L%02d_Blend" % which_layer + def getDefaultStateName(which_layer): return "TaSTT_L%02d_Do_Nothing" % which_layer @@ -78,6 +81,9 @@ def getActiveStateName(which_layer): def getSelectStateName(which_layer, select): return "TaSTT_L%02d_S%02d" % (which_layer, select) +def getBlendStateName(which_layer, select): + return "TaSTT_L%02d_S%02d_Blend" % (which_layer, select) + def getLetterStateName(which_layer, select, letter): return "TaSTT_L%02d_S%02d_L%03d" % (which_layer, select, letter) diff --git a/libtastt.py b/libtastt.py index 1ef2837..7d9f11a 100644 --- a/libtastt.py +++ b/libtastt.py @@ -208,7 +208,10 @@ def generateAnimations(anim_dir, guid_map): print("Generating letter animations (row {}/{})".format(row, generate_utils.BOARD_ROWS), file=sys.stderr) for col in range(0, generate_utils.BOARD_COLS): - for letter in range(0, generate_utils.CHARS_PER_CELL): + for letter in range(0, 2): + if letter == 1: + letter = generate_utils.CHARS_PER_CELL - 1 + # Make a deep copy of the templates node = anim_node.copy() curve = curve_template.copy() @@ -255,7 +258,7 @@ def generateFXController(anim: libunity.UnityAnimator) -> typing.Dict[int, libun layers = {} for i in range(0, generate_utils.NUM_LAYERS): - anim.addParameter(generate_utils.getLayerParam(i), int) + anim.addParameter(generate_utils.getBlendParam(i), float) layer = anim.addLayer(generate_utils.getLayerName(i)) layers[i] = layer @@ -283,48 +286,36 @@ def generateFXLayer(which_layer: int, anim: libunity.UnityAnimator, layer: dx = i * 200 dy = 200 - select_states[i] = anim.addAnimatorState(layer, - generate_utils.getSelectStateName(which_layer, i), dx = dx, dy = dy) + # Create blend tree for this region. + anim_lo_path = gen_anim_dir + \ + generate_utils.getAnimationNameByLayerAndIndex( + which_layer, i, 0) + \ + ".anim" + guid_lo = guid_map[anim_lo_path] + anim_hi_path = gen_anim_dir + \ + generate_utils.getAnimationNameByLayerAndIndex( + which_layer, i, generate_utils.CHARS_PER_CELL - 1) + \ + ".anim" + guid_hi = guid_map[anim_hi_path] + + select_states[i] = anim.addAnimatorBlendTree(layer, + generate_utils.getBlendStateName(which_layer, i), + generate_utils.getBlendParam(which_layer), + guid_lo, guid_hi, dx = dx, dy = dy) state = select_states[i] + # Create transition to state. select_state_transition = anim.addTransition(state) select_param = generate_utils.getSelectParam() anim.addTransitionIntegerEqualityCondition(active_state, select_state_transition, select_param, i) - l_states = {} # shorthand for `letter_states` - for i in range(0, 2 ** generate_utils.INDEX_BITS): - l_states[i] = {} - for letter in range(0, generate_utils.CHARS_PER_CELL): - dy = 300 - l_states[i][letter] = anim.addAnimatorState(layer, - generate_utils.getLetterStateName(which_layer, - i, letter), - dy = dy) - state = l_states[i][letter] - - animation_path = gen_anim_dir + \ - generate_utils.getAnimationNameByLayerAndIndex( - which_layer, i, letter) + \ - ".anim" - guid = guid_map[animation_path] - anim.setAnimatorStateAnimation(state, guid) - - # TODO(yum) see if we can get away with reusing the - # same transition object, but just stitch it into every - # state. - l_state_transition = anim.addTransition(state) - l_param = generate_utils.getLayerParam(which_layer) - #print("add condition on letter {}".format(letter), - # file=sys.stderr) - anim.addTransitionIntegerEqualityCondition(select_states[i], - l_state_transition, l_param, letter) - - home_state_transition = anim.addTransition(default_state) - home_state_transition.mapping['AnimatorStateTransition'].mapping['m_InterruptionSource'] = '0' - dummy_param = generate_utils.getDummyParam() - anim.addTransitionBooleanCondition(state, - home_state_transition, dummy_param, False) + # Create return-home transition. + home_state_transition = anim.addTransition(default_state) + home_state_transition.mapping['AnimatorStateTransition'].mapping['m_InterruptionSource'] = '0' + dummy_param = generate_utils.getDummyParam() + anim.addTransitionBooleanCondition(state, + home_state_transition, dummy_param, False) pass # Generic toggle adding utility. diff --git a/libunity.py b/libunity.py index dcf3b69..5ff23cd 100644 --- a/libunity.py +++ b/libunity.py @@ -179,6 +179,40 @@ AnimatorStateTransition: m_CanTransitionToSelf: 1 """[1:][:-1] +BLEND_TREE_TEMPLATE = """ +--- !u!206 &1071664566462684110 +BlendTree: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: REPLACEME_BLEND_TREE_NAME + m_Childs: + - serializedVersion: 2 + m_Motion: {fileID: 7400000, guid: REPLACEME_GUID_LO, type: 2} + m_Threshold: -1 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: REPLACEME_BLEND_PARAMETER + m_Mirror: 0 + - serializedVersion: 2 + m_Motion: {fileID: 7400000, guid: REPLACEME_GUID_HI, type: 2} + m_Threshold: 1 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: REPLACEME_BLEND_PARAMETER + m_Mirror: 0 + m_BlendParameter: REPLACEME_BLEND_PARAMETER + m_BlendParameterY: REPLACEME_BLEND_PARAMETER + m_MinThreshold: -1 + m_MaxThreshold: 1 + m_UseAutomaticThresholds: 0 + m_NormalizedBlendValues: 0 + m_BlendType: 0 +"""[1:][:-1] + class Metadata: def __init__(self): self.guid = "%032x" % random.randrange(16 ** 32) @@ -630,6 +664,42 @@ class UnityAnimator(): anim_state.mapping['AnimatorState'].mapping['m_Motion'].mapping['fileID'] = '7400000' anim_state.mapping['AnimatorState'].mapping['m_Motion'].mapping['type'] = '2' + # Adds a blend tree which uses the parameter named `param_name` to blend + # between anim_lo and anim_hi. Also creates the corresponding animation + # state. + def addAnimatorBlendTree(self, layer, state_name, param_name, + anim_guid_lo, anim_guid_hi, dx = 0, dy = 0) -> UnityDocument: + # Create the blend tree. + parser = UnityParser() + parser.parse(BLEND_TREE_TEMPLATE) + new_anim = UnityAnimator() + new_anim.addNodes(parser.nodes) + node = new_anim.nodes[0] + + new_id = self.allocateId() + node.class_id = "206" + node.anchor = str(new_id) + tree = node.mapping['BlendTree'] + tree.mapping['m_Name'] = state_name + # Low animation + tree.mapping['m_Childs'].sequence[0].mapping['m_Motion'].mapping['guid'] = anim_guid_lo + tree.mapping['m_Childs'].sequence[0].mapping['m_DirectBlendParameter'] = param_name + # High animation + tree.mapping['m_Childs'].sequence[1].mapping['m_Motion'].mapping['guid'] = anim_guid_hi + tree.mapping['m_Childs'].sequence[1].mapping['m_DirectBlendParameter'] = param_name + + tree.mapping['m_BlendParameter'] = param_name + tree.mapping['m_BlendParameterY'] = param_name + + self.nodes.append(node) + + # Create the corresponding animation state. + anim_state = self.addAnimatorState(layer, state_name, False, dx = dx, dy = + dy) + anim_state.mapping['AnimatorState'].mapping['m_Motion'].mapping['fileID'] = node.anchor + + return anim_state + def addTransition(self, dst_state): # Create animation state parser = UnityParser() @@ -841,12 +911,14 @@ class UnityAnimator(): continue motion = node.mapping['AnimatorState'].mapping['m_Motion'] replace = False - if not "guid" in motion.mapping.keys(): - replace = True - elif not motion.mapping["guid"] in guid_map: - replace = True - if not replace: + + if "fileID" in motion.mapping.keys(): continue + + if "guid" in motion.mapping.keys() and \ + motion.mapping["guid"] in guid_map: + continue + motion.mapping["fileID"] = "7400000" motion.mapping["guid"] = noop_anim_meta.guid motion.mapping["type"] = "2" diff --git a/osc_ctrl.py b/osc_ctrl.py index aa93dfb..6b97b09 100644 --- a/osc_ctrl.py +++ b/osc_ctrl.py @@ -75,8 +75,10 @@ def encodeMessage(lines): return result def updateCell(client, cell_idx, letter_encoded): - addr="/avatar/parameters/" + getLayerParam(cell_idx) - client.send_message(addr, letter_encoded) + addr="/avatar/parameters/" + generate_utils.getBlendParam(cell_idx) + letter_remapped = (-127.5 + letter_encoded) / 127.5 + print("Send encoded letter {} / {}".format(letter_encoded, letter_remapped)) + client.send_message(addr, letter_remapped) def enable(client): addr="/avatar/parameters/" + getEnableParam() |
