Loading an Animation
And remapping the animation to the character
[1]:
import numpy as np
from ipywidgets import widgets, interact, interactive
import ipyanimlab as lab
viewer = lab.Viewer(shadow_quality=lab.ShadowQuality.HIGH, move_speed=5, width=800, height=600)
Load the USD Asset
the default skinned character is accessible directly from internal resources
[2]:
character = viewer.import_usd_asset('AnimLabSimpleMale.usd')
Load a BVH animation
By default the skeleton from the animation does not match the loaded asset.
We can see that the character change shapes when playing the animation back.
[3]:
anim = lab.import_bvh('push1_subject2.bvh')
[4]:
def render(frame):
p = (anim.pos[frame,...])
q = (anim.quats[frame,...])
a = lab.utils.quat_to_mat(q, p)
viewer.set_shadow_poi(p[0])
viewer.begin_shadow()
# render giving the computed pose this frame, and the bones names from the animation
viewer.draw(character, a, anim.bones)
viewer.end_shadow()
viewer.begin_display()
viewer.draw_ground()
# render giving the computed pose this frame, and the bones names from the animation
viewer.draw(character, a, anim.bones)
viewer.end_display()
viewer.disable(depth_test=True)
# render giving the computed pose this frame, and the bones names from the animation
viewer.draw_axis(character.world_skeleton_xforms(a, anim.bones), 5)
# render giving the computed pose this frame, and the bones names from the animation
viewer.draw_lines(character.world_skeleton_lines(a, anim.bones))
viewer.execute_commands()
interact(
render,
frame=lab.Timeline(max=anim.quats.shape[0]-1)
)
viewer
[4]:
Anim Mapper
The animation mapper takes as paramter the character to match.
It will allows you to create a new animation with the proper positions and quats list so you can directly render the character without the need to passe the bone names.
It also by default replace the translations with the one coming from the character
[5]:
animmap = lab.AnimMapper(character)
animb = animmap(anim)
[6]:
def render(frame):
p = (anim.pos[frame,...])
q = (anim.quats[frame,...])
a = lab.utils.quat_to_mat(q, p)
p = (animb.pos[frame,...])
q = (animb.quats[frame,...])
b = lab.utils.quat_to_mat(q, p)
viewer.begin_shadow()
viewer.draw(character, b)
viewer.end_shadow()
viewer.begin_display()
viewer.draw_ground()
viewer.draw(character, b)
viewer.end_display()
viewer.disable(depth_test=True)
viewer.draw_lines(character.world_skeleton_lines(a, anim.bones))
viewer.draw_lines(character.world_skeleton_lines(b), color=np.array([1,0,1], dtype=np.float32))
viewer.execute_commands()
interact(
render,
frame=lab.Timeline(max=anim.quats.shape[0]-1)
)
viewer
[6]:
Compute the root motion
[7]:
animmap = lab.AnimMapper(character, root_motion=True)
animb = animmap(anim)
Mirror the animation
[8]:
animmap = lab.AnimMapper(character, root_motion=True, mirror=True)
animb = animmap(anim)
Match the feet positions
And we can also offset any bone locally
[9]:
animmap = lab.AnimMapper(character, root_motion=True, match_effectors=True, local_offsets={'Hips':[0, -25, 0]})
animb = animmap(anim)
Match feet and hands positions
[10]:
animmap = lab.AnimMapper(character, root_motion=True, match_effectors=True, local_offsets={'Hips':[0, -10, 0]}, effector_names=['LeftFoot', 'RightFoot', 'LeftHand', 'RightHand'])
animb = animmap(anim)
Use an AnimMapper at loading time
we can pass the animation mapper directly when loading the animation.
This works for both ‘import_bvh’ and ‘import_usd_animation’
[11]:
animmap = lab.AnimMapper(character, root_motion=True, match_effectors=True)
anim = lab.import_bvh('push1_subject2.bvh', anim_mapper=animmap)
Render spheres to represent the skeleton
As the draw command can take multiple xform for rigid instancing rendering, we can pass a rigid asset and all the matrices from the skeleton
[12]:
sphere = viewer.create_sphere(radius=8)
[15]:
def render(frame):
p = (anim.pos[frame,...])
q = (anim.quats[frame,...])
a = lab.utils.quat_to_mat(q, p)
viewer.begin_shadow()
# to pass the skeleton bone we have to convert the matrices to worldspace (otherwise we see the local space)
viewer.draw(sphere, character.world_skeleton_xforms(a))
viewer.end_shadow()
viewer.begin_display()
viewer.draw_ground()
# to pass the skeleton bone we have to convert the matrices to worldspace (otherwise we see the local space)
viewer.draw(sphere, character.world_skeleton_xforms(a))
viewer.end_display()
viewer.disable(depth_test=True)
viewer.draw_axis(character.world_skeleton_xforms(a), 5)
viewer.draw_lines(character.world_skeleton_lines(a))
viewer.execute_commands()
interact(
render,
frame=lab.Timeline(max=anim.quats.shape[0]-1)
)
viewer
[15]: