Difference between revisions of "Internal Animation Format"

From Second Life Wiki
Jump to navigation Jump to search
m (Added links to the source code references)
(removed blender-anim-exporter; the owner took it down)
Tag: Manual revert
 
(15 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{KBtip|You may find the contents of [[Anim File Format]] page helpful in addition to this page.}}
== 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.
{| class="wikitable" style="margin:auto; width: 85%; text-align: center;"
|+ .anim file format utilities
|-
! scope="col" style="background-color:#A7C1F2" | Name
! scope="col" style="background-color:#A7C1F2" | Notes
|-
| [https://gitlab.com/Quillia/anim-converter '''Anim-converter''']
| Converts .bvh files into Second Life .anim files.
|-
| [https://github.com/Gina43/anim-2-bvh '''Anim2BVH''']
| Inverse of the above, converts .anim files into .bvh files.
|-
| [https://community.secondlife.com/forums/topic/468179-anim-hacker/ '''Anim Hacker''']
| Tool for manipulating .anim files. Can be used to edit joint priorities, add constraints, and more!
|-
| [https://github.com/tapple/tanimbomb '''tanimbomb''']
| Python CLI tool for making simple bulk-edits to multiple anim files at once, especially joint removal and animation mirroring
|-
| [https://github.com/LGGGreg/par/releases/tag/v1 '''AnimMaker''']
| Older tool, equivalent to AnimHacker. ([http://web.archive.org/web/20100526180459/http://code.google.com/p/par/source/browse#svn/branches/AnimMaker Source Code available in archive])
|}
==Overview==
==Overview==


Line 14: Line 40:


Relevant source files:
Relevant source files:
* ''[https://bitbucket.org/lindenlab/viewer-release/src/8579cefad3049e139efaa1b40a94f0357fcd0274/indra/llcharacter/llkeyframemotion.cpp linden/indra/llcharacter/llbvhloader.cpp]''
* [https://github.com/secondlife/viewer/blob/main/indra/llcharacter/llbvhloader.cpp llbvhloader.cpp]
* ''[https://bitbucket.org/lindenlab/viewer-release/src/8579cefad3049e139efaa1b40a94f0357fcd0274/indra/llcharacter/llbvhloader.cpp linden/indra/llcharacter/llkeyframemotion.cpp]''.
* [https://github.com/secondlife/viewer/blob/main/indra/llcharacter/llkeyframemotion.cpp#L1229 llkeyframemotion.cpp:deserialize()]
 
Note: the binary file is little endian.


Note: binary file write is little Endian.
{{KBwarning|1=The asset uploader enforces a {{HoverText|250,000 byte|250 KB (decimal), 244 KB (binary)}} limit on .anim files. Files larger than this will fail to upload, and will return an error. <br> {{HoverText|Simplify your animation|Remove triplicate keyframes, remove unused joints, remove keyframes for small movements, etc.}}, then retry uploading.<br>'''Blender Users:''' You can use a built-in function called "''Decimate''" to simplify your animation. See [https://www.youtube.com/watch?v=lScwEYJZy1M this tutorial].}}


==Header==
==Header==
Line 26: Line 54:
{{LLSD Field Entry|name=version|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=version|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=sub_version|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=sub_version|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=base_priority|llsd=integer|cpp=S32}}
{{LLSD Field Entry|name=base_priority|llsd=integer|cpp=S32|note=Is informational only, and does not affect the animation. The animation system always follows individual joint priority; Except for animations making use of constraints, in which case the constraint will be applied at priority defined by this value.}}
{{LLSD Field Entry|name=duration|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=duration|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=emote_name|llsd=string|cpp=std::string}}
{{LLSD Field Entry|name=emote_name|llsd=string|cpp=char *|note=NULL-terminated character sequence}}
{{LLSD Field Entry|name=loop_in_point|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=loop_in_point|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=loop_out_point|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=loop_out_point|llsd=real|cpp=F32}}
Line 34: Line 62:
{{LLSD Field Entry|name=ease_in_duration|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=ease_in_duration|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=ease_out_duration|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=ease_out_duration|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=hand_pose|llsd=integer|cpp=U32}}
{{LLSD Field Entry|name=hand_pose|llsd=integer|cpp=U32|note=Enum defined in [https://github.com/secondlife/viewer/blob/main/indra/llcharacter/llhandmotion.h#L45]}}
{{LLSD Field Entry|name=num_joints|llsd=integer|cpp=U32}}
{{LLSD Field Entry|name=num_joints|llsd=integer|cpp=U32}}
|}
|}
Line 44: Line 72:


{|{{LLSD Field Table}}
{|{{LLSD Field Table}}
{{LLSD Field Entry|name=joint_name|llsd=string|cpp=std::string}}
{{LLSD Field Entry|name=joint_name|llsd=string|cpp=char *|note=NULL-terminated character sequence}}
{{LLSD Field Entry|name=joint_priority|llsd=integer|cpp=S32}}
{{LLSD Field Entry|name=joint_priority|llsd=integer|cpp=S32}}
|}
|}
Line 63: Line 91:
{|{{LLSD Field Table}}
{|{{LLSD Field Table}}
{{LLSD Field Entry|name=time|llsd=integer|cpp=U16|note=0: first frame, 65535: last frame}}
{{LLSD Field Entry|name=time|llsd=integer|cpp=U16|note=0: first frame, 65535: last frame}}
{{LLSD Field Entry|name=rot_angle_x|llsd=integer|cpp=U16|note= 0:-1, 32767:0, 65535:+1 }}
{{LLSD Field Entry|name=rot_x|llsd=integer|cpp=U16|note= 0:-1, 32767:0, 65535:+1 }}
{{LLSD Field Entry|name=rot_angle_y|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=rot_y|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=rot_angle_z|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=rot_z|llsd=integer|cpp=U16}}
|}
|}


Line 84: Line 112:
{|{{LLSD Field Table}}
{|{{LLSD Field Table}}
{{LLSD Field Entry|name=time|llsd=integer|cpp=U16|note=0: first frame, 65535: last frame}}
{{LLSD Field Entry|name=time|llsd=integer|cpp=U16|note=0: first frame, 65535: last frame}}
{{LLSD Field Entry|name=pos_x|llsd=integer|cpp=U16|note=0: -5M, 32767: 0M, 65535: +5M}}
{{LLSD Field Entry|name=pos_x|llsd=integer|cpp=U16|desc=position measured from avatar root, not joint offset|note=0:-5m, 32767:0m, 65535:+5m (m for metres)}}
{{LLSD Field Entry|name=pos_y|llsd=integer|cpp=U16|note=pos measured from AV COG not joint offset}}
{{LLSD Field Entry|name=pos_y|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=pos_z|llsd=integer|cpp=U16}}
{{LLSD Field Entry|name=pos_z|llsd=integer|cpp=U16}}
|}
|}
Line 91: Line 119:
==Constraints==
==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.
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).


{|{{LLSD Field Table}}
{|{{LLSD Field Table}}
Line 101: Line 129:
{|{{LLSD Field Table}}
{|{{LLSD Field Table}}
{{LLSD Field Entry|name=chain_length|llsd=integer|cpp=U8|note=number of attached joints to include}}
{{LLSD Field Entry|name=chain_length|llsd=integer|cpp=U8|note=number of attached joints to include}}
{{LLSD Field Entry|name=constraint_type|llsd=integer|cpp=U8|note=0: point, 1: plane}}
{{LLSD Field Entry|name=constraint_type|llsd=integer|cpp=U8|note=0: point'''*''', 1: plane}}
{{LLSD Field Entry|name=source_volume|llsd=string |cpp=U8[16] array|note=skeleton collision volume name}}
{{LLSD Field Entry|name=source_volume|llsd=string |cpp=char[16]|desc=skeleton collision volume name|note=Always 16 bytes, but if shorter, it's interpreted as NULL-terminated and the remaining bytes ignored.}}
{{LLSD Field Entry|name=source_offset|llsd=string (?)|cpp=LLVector3}}
{{LLSD Field Entry|name=source_offset|llsd=string (?)|cpp=LLVector3}}
{{LLSD Field Entry|name=target_volume|llsd=string|cpp= U8[16] array|note=skeleton collision volume name}}
{{LLSD Field Entry|name=target_volume|llsd=string|cpp=char[16]|desc=skeleton collision volume name|note=Always 16 bytes, but if shorter, it's interpreted as NULL-terminated and the remaining bytes ignored.}}
{{LLSD Field Entry|name=target_offset|llsd=string (?)|cpp=LLVector3}}
{{LLSD Field Entry|name=target_offset|llsd=string (?)|cpp=LLVector3}}
{{LLSD Field Entry|name=target_dir|llsd=string (?)|cpp=LLVector3|note=value is currently ignored}}
{{LLSD Field Entry|name=target_dir|llsd=string (?)|cpp=LLVector3|note=value is currently ignored}}
Line 112: Line 140:
{{LLSD Field Entry|name=ease_out_stop|llsd=real|cpp=F32}}
{{LLSD Field Entry|name=ease_out_stop|llsd=real|cpp=F32}}
|}
|}
'''*''' The implementation of constraint type '''Point''' is incomplete, and is considered non-functional. Until further notice, all constraints should use the '''Plane''' type.
:The above does not seem to be true, both types would seem to be working. ([[User:Jenna Huntsman |Jenna Huntsman]] tested {{#time: d F Y|8/12/2022}} )
=== [[User:Jenna Huntsman |Jenna's]] notes on constraints ===
* ''chain_length'' is '''not''' inclusive of the source bone itself, so for example, if the source bone is R_HAND, setting a ''chain_length'' of 2 will use R_LOWER_ARM (elbow joint) and R_UPPER_ARM (shoulder joint).
** Setting a ''chain_length'' of more than 4 will prevent the animation from playing - the animation will upload, but will not play.
** Setting a ''chain_length'' of 4 (both constraint types) will cause the viewer to crash.
* ''target_offset'' uses an axis layout of X+ forward, Y+ left, Z+ up.
** Constraints only seem to work reliably if ''target_offset'' has a value of 1 on the Z axis. The values on the X and Y axis can be anything however.
* ''source_offset'' uses an axis layout of X+ forward, Y+ left, Z+ up.
** <s>The distance units expected are of an unknown unit, however it does not seem to be meters or inches. (Also true of ''target_offset'')</s>
** '''llkeyframemotion.cpp''' would seem to suggest that constraints are inherited from a BVH upon conversion to .anim, which would make the units be inches (as is standard for BVH animations in SL); <s>however in-world testing casts doubt upon this.</s>
* ''target_dir'' is unused, however is likely a unit vector relative to the armature root.
** As such, bone rotations can be manipulated by rot keys as normal, however the rotation will not be relative to the target volume.
** It seems that while ''target_dir'' is present in the file format for constraints, it was never implemented (As per '''llkeyframemotion.cpp''').
* While ''GROUND'' is a recognized and implemented target volume, server-side checks prevent uploads of animations making use of this constraint target.
* There seems to be a hard limit of 10 constraints per animation (as per '''llkeyframemotion.cpp''').
* Constraints always follow the shortest path from their source to their destination, irrespective of 'legal' joint rotations. (Meaning to say, constraints can cause bones to move in an odd way (e.g. an arm clipping inside your body) if that is the shortest path.)
* Unlike regular animations, Constraints seem to require an intact armature from the skeleton root (mPelvis) to the source and target bones; Otherwise the animation will not play.
** The bones do not need to be animated, they only need to be present in the .anim file.
* Constraints '''do''' respect animation priority (The ''global'' animation priority, as defined by ''base_priority'', '''not''' the ''per joint'' priority), meaning it's possible to stack constraints, but not within the same animation.

Latest revision as of 06:55, 26 August 2023

KBtip2.png Tip: You may find the contents of Anim File Format page helpful in addition to this page.

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.

.anim file format utilities
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!
tanimbomb Python CLI tool for making simple bulk-edits to multiple anim files at once, especially joint removal and animation mirroring
AnimMaker Older tool, equivalent to AnimHacker. (Source Code available in archive)

Overview

From a programming perspective, there are several steps to uploading an animation from a BVH (BioVision Hierarchy) file:

  1. Read and parse the BVH file, creating an LLKeyframeMotion object containing the motion data.
  2. 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.
  3. Serialize the LLKeyframeMotion object as LLSD.
  4. Upload the serialized data to the asset server.

Before other viewers can play an animation, they must:

  1. Download the serialized data from the asset server.
  2. Deserialize it to an LLKeyframeMotion object.

Relevant source files:

Note: the binary file is little endian.

KBwarning.png Warning: The asset uploader enforces a 250,000 byte limit on .anim files. Files larger than this will fail to upload, and will return an error.
Simplify your animation, then retry uploading.
Blender Users: You can use a built-in function called "Decimate" to simplify your animation. See this tutorial.

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; Except for animations making use of constraints, in which case the constraint will be applied at priority defined by this value.
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.

The above does not seem to be true, both types would seem to be working. (Jenna Huntsman tested 12 August 2022 )

Jenna's notes on constraints

  • chain_length is not inclusive of the source bone itself, so for example, if the source bone is R_HAND, setting a chain_length of 2 will use R_LOWER_ARM (elbow joint) and R_UPPER_ARM (shoulder joint).
    • Setting a chain_length of more than 4 will prevent the animation from playing - the animation will upload, but will not play.
    • Setting a chain_length of 4 (both constraint types) will cause the viewer to crash.
  • target_offset uses an axis layout of X+ forward, Y+ left, Z+ up.
    • Constraints only seem to work reliably if target_offset has a value of 1 on the Z axis. The values on the X and Y axis can be anything however.
  • source_offset uses an axis layout of X+ forward, Y+ left, Z+ up.
    • The distance units expected are of an unknown unit, however it does not seem to be meters or inches. (Also true of target_offset)
    • llkeyframemotion.cpp would seem to suggest that constraints are inherited from a BVH upon conversion to .anim, which would make the units be inches (as is standard for BVH animations in SL); however in-world testing casts doubt upon this.
  • target_dir is unused, however is likely a unit vector relative to the armature root.
    • As such, bone rotations can be manipulated by rot keys as normal, however the rotation will not be relative to the target volume.
    • It seems that while target_dir is present in the file format for constraints, it was never implemented (As per llkeyframemotion.cpp).
  • While GROUND is a recognized and implemented target volume, server-side checks prevent uploads of animations making use of this constraint target.
  • There seems to be a hard limit of 10 constraints per animation (as per llkeyframemotion.cpp).
  • Constraints always follow the shortest path from their source to their destination, irrespective of 'legal' joint rotations. (Meaning to say, constraints can cause bones to move in an odd way (e.g. an arm clipping inside your body) if that is the shortest path.)
  • Unlike regular animations, Constraints seem to require an intact armature from the skeleton root (mPelvis) to the source and target bones; Otherwise the animation will not play.
    • The bones do not need to be animated, they only need to be present in the .anim file.
  • Constraints do respect animation priority (The global animation priority, as defined by base_priority, not the per joint priority), meaning it's possible to stack constraints, but not within the same animation.