Internal Animation Format
Tools
If you're coming here to find out about how to take advantage of Second Life's internal animation format (.anim), you may find the below tools of use.
Name | Notes |
---|---|
Anim-converter | Converts .bvh files into Second Life .anim files. |
Anim2BVH | Inverse of the above, converts .anim files into .bvh files. |
Anim Hacker | Tool for manipulating .anim files. Can be used to edit joint priorities, add constraints, and more! |
AnimMaker | Older tool, equivalent to AnimHacker. |
Overview
From a programming perspective, there are several steps to uploading an animation from a BVH (BioVision Hierarchy) file:
- Read and parse the BVH file, creating an LLKeyframeMotion object containing the motion data.
- Gather input from the user (via the upload preview floater) for things like animation priority, facial expression, and looping; these settings are stored in the LLKeyframeMotion object.
- Serialize the LLKeyframeMotion object as LLSD.
- Upload the serialized data to the asset server.
Before other viewers can play an animation, they must:
- Download the serialized data from the asset server.
- Deserialize it to an LLKeyframeMotion object.
Relevant source files:
Note: the binary file is little endian.
Header
The first part of the animation data is a header describing various details about the animation as a whole. The elements, in order, are:
field name | description | LLSD type | C++ type | Note |
---|---|---|---|---|
version | integer | U16 | ||
sub_version | integer | U16 | ||
base_priority | integer | S32 | Is informational only, and does not affect the animation. The animation system always follows individual joint priority. | |
duration | real | F32 | ||
emote_name | string | char * | NULL-terminated character sequence | |
loop_in_point | real | F32 | ||
loop_out_point | real | F32 | ||
loop | integer | S32 | 0: not looped, 1: looped | |
ease_in_duration | real | F32 | ||
ease_out_duration | real | F32 | ||
hand_pose | integer | U32 | Enum defined in [1] | |
num_joints | integer | U32 |
Joint Data
After the header is data for each joint in the skeleton: Note: Unused bones need not be included in the file.
field name | description | LLSD type | C++ type | Note |
---|---|---|---|---|
joint_name | string | char * | NULL-terminated character sequence | |
joint_priority | integer | S32 |
Joint Rotation Keys
At the start of the rotation data for each bone is the total number of rotation keys: If the bone has no rotation based keyframes, this value must be 0.
field name | description | LLSD type | C++ type | Note |
---|---|---|---|---|
num_rot_keys | integer | S32 |
Then, for each rotation key:
Note: These three values X Y Z appear to be the first three values of a truncated quaternion with the W term being calculated afterwards. Since a quaternion is X2 + Y2 + Z2 + W2 = 1 as long as you assume the W term has a consistent sign the X Y Z terms will be accurate.
field name | description | LLSD type | C++ type | Note |
---|---|---|---|---|
time | integer | U16 | 0: first frame, 65535: last frame | |
rot_x | integer | U16 | 0:-1, 32767:0, 65535:+1 | |
rot_y | integer | U16 | ||
rot_z | integer | U16 |
Joint Position Keys
At the start of the position data is the total number of position keys: If the bone has no position based animations, this value must be 0.
field name | description | LLSD type | C++ type | Note |
---|---|---|---|---|
num_pos_keys | integer | S32 |
Then, for each position key, position data is measured from Avatar Center ( mPelvis ), not joint resting position
( IE [0,0,0] is not joint resting position, it'll be the bone's position in parent coordinate space)
with the exception of the mPelvis bone which is stored in world space coordinates.:
field name | description | LLSD type | C++ type | Note |
---|---|---|---|---|
time | integer | U16 | 0: first frame, 65535: last frame | |
pos_x | position measured from avatar root, not joint offset | integer | U16 | 0:-5m, 32767:0m, 65535:+5m (m for metres) |
pos_y | integer | U16 | ||
pos_z | integer | U16 |
Constraints
After the joint data are a number of entries for joint constraints. Constraints can target an avatar's parts in relation to each other or the ground (IK).
field name | description | LLSD type | C++ type | Note |
---|---|---|---|---|
num_constraints | integer | S32 |
Then, for each joint constraint:
field name | description | LLSD type | C++ type | Note |
---|---|---|---|---|
chain_length | integer | U8 | number of attached joints to include | |
constraint_type | integer | U8 | 0: point*, 1: plane | |
source_volume | skeleton collision volume name | string | char[16] | Always 16 bytes, but if shorter, it's interpreted as NULL-terminated and the remaining bytes ignored. |
source_offset | string (?) | LLVector3 | ||
target_volume | skeleton collision volume name | string | char[16] | Always 16 bytes, but if shorter, it's interpreted as NULL-terminated and the remaining bytes ignored. |
target_offset | string (?) | LLVector3 | ||
target_dir | string (?) | LLVector3 | value is currently ignored | |
ease_in_start | real | F32 | ||
ease_in_stop | real | F32 | ||
ease_out_start | real | F32 | ||
ease_out_stop | real | F32 |
* The implementation of constraint type Point is incomplete, and is considered non-functional. Until further notice, all constraints should use the Plane type.