summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2022-11-05 12:11:15 -0700
committeryum <yum.food.vr@gmail.com>2022-11-05 12:14:12 -0700
commit7f930340e3aa94d6aca120d28436594427469373 (patch)
tree9c39374cb8be35d813fcf39db2620da027ac6d7d
parentca10de9a5cb2a9b360bc0cd2c357f8d0041f0fca (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.md8
-rw-r--r--generate_params.py11
-rw-r--r--generate_utils.py8
-rw-r--r--libtastt.py65
-rw-r--r--libunity.py82
-rw-r--r--osc_ctrl.py6
6 files changed, 129 insertions, 51 deletions
diff --git a/README.md b/README.md
index 37fdac5..9ee090e 100644
--- a/README.md
+++ b/README.md
@@ -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()