Talk:Internal Animation Format

From Second Life Wiki
Jump to navigation Jump to search

Funny this page showed up, I was just thinking about this stuff the other night. This documentation is only good for version 2 and not version 1. -- Strife Onizuka 23:37, 10 December 2007 (PST)

Joint Scale Keys

I noticed that the format is missing Joint Scale Keys, which the client seems to fully support internally.

What are the thoughts of upgrading the animation asset format to support Scale keys? Should I prematurely add support for Xscale Yscale and Zscale to my upgraded BVH parser and add Scale keys ("num_scale_keys")? Although, even if I did that, I am not sure what the final quantizations are to be for serialization. I probably should best attempt to contact Nyx Linden directly (He was the one who worked on this format, right?) --Nexii Malthus 00:40, 30 January 2010 (UTC)

You are talking about scaling the joints during the animation. I'm pretty sure you can do this without modding the client. I just forget how to do it. -- Strife (talk|contribs) 03:28, 30 January 2010 (UTC)
You sure about that? I checked the sourcecode paths myself and the LLKeyFrameMotion class during deserialization does not take into account any scale keys. Neither does it do on serialization. I am using 1.23 as a base though, so this might be different in the latest snowglobe clients, but I think all the lindens had been too busy with work Viewer 2.0, bug fixing or other assorted projects.
Unless you mean way the avatar mesh is artificially stretched with position values, that isn't true scaling, that is only an artifact/side effect. --Nexii Malthus 12:35, 30 January 2010 (UTC)

Joint Rotation Keys

I've spotted an inaccuracy in this article.

The x, y, and z values of the joint rotation keys are the x, y, and z values of a normalized quaternion, not degrees or anything to do with Euler angles. The w value of the quaternion is reconstructed when the animation is read, and being normalized makes this possible. The x, y, and z values range from -1 to 1 instead of -180 to 180. I don't think the non-linear comment is helpful either.

Coaldust Numbers 20:17, 8 October 2011 (PDT)

There were two animation versions: 0.1 and 1.0.
  • 0.1 uses a quaternion with a w value that needs to be regenerated as you describe.
  • 1.0 uses 3 floats representing a Euler.
Here is a subset of my notes from when I reverse engineered the client cache (with the aid of client strings) before LL open sourced the client.
They are in a shorthand that is relatively straight forward. I don't think I ever checked and corrected them against the client source.
   {<type = 0x4100> INVENTORY_ANIMATION
        <data> = <version><priority><length><emote><loop_in_point><loop_out_point>
                    <subversion?><ease_in_time><ease_out_time><hand_pose><NoJ>(<Joint>*<NoJ>)<NoC>(<Constraint>*<NoC>)
        <version>               =   word
        <subversion>            =   word
        {<version> == 0x0001 && <subversion> == 0x0000
            <priority>          =   0x40000000
            <length>            =   float, length in seconds
            {<emote>            =   null terminated string, if empty just a null
                Aaaaah
                Afraid
                Angry
                Big Smile
                Bored
                Cry
                Disdain
                Embarrassed
                Frown
                Kiss
                Laugh
                Plllppt
                Repulsed
                Sad
                Shrug
                Smile
                Surprise
                Wink
                Worry
                Express_Anger_Emote
            }
            <loop_in_point>     =   float, loop start
            <loop_out_point>    =   float, loop end
            <loop>              =   0x10000000
            <ease_in_time>      =   float, lead in
            <ease_out_time>     =   float, lead out
            {<hand_pose>        =   unsigned long, hand pose
                <Hand = 0x00000000> Spread
                <Hand = 0x01000000> Relaxed
                <Hand = 0x02000000> Point Both
                <Hand = 0x03000000> Fist
                <Hand = 0x04000000> Relaxed Left
                <Hand = 0x05000000> Point Left
                <Hand = 0x06000000> Fist Left
                <Hand = 0x07000000> Relaxed Right
                <Hand = 0x08000000> Point Right
                <Hand = 0x09000000> Fist Right
                <Hand = 0x0A000000> Salute Right
                <Hand = 0x0B000000> Typing
                <Hand = 0x0C000000> Peace Right
            }
            <NoJ>               =   unsigned long, Number of Joints
            {<Joint>            =   <name><JPriority><NoP R>(<R>*<NoR>)<NoP>(<P>*<NoP>)
                <name>          =   null terminated string
                <JPriority>     =   unsigned long, Joint Priority (same as Priority unless you use voodoo)
                <NoR>           =   unsigned long, Number Of Paramaters in section A, Angles
                <NoP>           =   unsigned long, Number Of Paramaters in section B, Positions
                {<R>            =   <FN><Y><X><Z>
                    <FN>        =   short, Frame Number
                    <X>         =   unsigned short
                    <Y>         =   unsigned short
                    <Z>         =   unsigned short
                    {How to read:
                        rot.x = ((double)d.x - 32767.0)/32767.0;
                        rot.y = ((double)d.y - 32767.0)/32767.0;
                        rot.z = ((double)d.z - 32767.0)/32767.0;
                        rot.w = 0.0;
                        double tw = 1.0 - rot.norm();
                        if(tw > 0.0) rot.w = sqrt(tw);
                    }
                }
                {<P>            =   <FN><Y><X><Z>
                    <FN>        =   short, Frame Number
                    <X>         =   unsigned short
                    <Y>         =   unsigned short
                    <Z>         =   unsigned short
                    {How to read:
                        (((double)a - 32767.0) * 0.3125)
                    }
                }
            }
            <NoC>               =   signed long
            {<Constraint>       =   <chain_length><constraint_type><source_volume><source_offset><target_volume>
                                    <target_offset><target_dir><ease_in_start><ease_in_stop><ease_out_start><ease_out_stop>
                <chain_length>      =   unsigned byte
                <constraint_type>   =   unsigned byte
                <source_volume>     =   unsigned byte[16]
                {<source_offset>    =   LLVector3 = <x><y><z>
                    <x>             =   float
                    <y>             =   float
                    <z>             =   float
                }
                {<target_volume>    =   unsigned byte[16], null terminated string
                    "GROUND"
                    "PELVIS"
                    "BELLY"
                    "CHEST"
                    "NECK"
                    "HEAD"
                    "L_CLAVICLE"
                    "L_UPPER_ARM"
                    "L_LOWER_ARM"
                    "L_HAND"
                    "R_CLAVICLE"
                    "R_UPPER_ARM"
                    "R_LOWER_ARM"
                    "R_HAND"
                    "R_UPPER_LEG"
                    "R_LOWER_LEG"
                    "R_FOOT"
                    "L_UPPER_LEG"
                    "L_LOWER_LEG"
                    "L_FOOT"
                }
                {<target_offset>    =   LLVector3 = <x><y><z>
                    <x>             =   float
                    <y>             =   float
                    <z>             =   float
                }
                {<target_dir>       =   LLVector3 = <x><y><z>
                    <x>             =   float
                    <y>             =   float
                    <z>             =   float
                }
                <ease_in_start>     =   float
                <ease_in_stop>      =   float
                <ease_out_start>    =   float
                <ease_out_stop>     =   float
            }
        }
        {<version> == 0x0000 && <subversion> == 0x0001
            <priority>          =   0x40000000
            <length>            =   float, length in seconds
            {<emote>            =   null terminated string, if empty just a null
                Aaaaah
                Afraid
                Angry
                Big Smile
                Bored
                Cry
                Disdain
                Embarrassed
                Frown
                Kiss
                Laugh
                Plllppt
                Repulsed
                Sad
                Shrug
                Smile
                Surprise
                Wink
                Worry
                Express_Anger_Emote
            }
            <loop_in_point>     =   float, loop start
            <loop_out_point>    =   float, loop end
            <loop>              =   0x10000000
            <ease_in_time>      =   float, lead in
            <ease_out_time>     =   float, lead out
            {<hand_pose>        =   unsigned long, hand pose
                <Hand = 0x00000000> Spread
                <Hand = 0x01000000> Relaxed
                <Hand = 0x02000000> Point Both
                <Hand = 0x03000000> Fist
                <Hand = 0x04000000> Relaxed Left
                <Hand = 0x05000000> Point Left
                <Hand = 0x06000000> Fist Left
                <Hand = 0x07000000> Relaxed Right
                <Hand = 0x08000000> Point Right
                <Hand = 0x09000000> Fist Right
                <Hand = 0x0A000000> Salute Right
                <Hand = 0x0B000000> Typing
                <Hand = 0x0C000000> Peace Right
            }
            <NoJ>               =   unsigned long, Number of Joints
            {<Joint>            =   <name><JPriority><NoP R>(<R>*<NoR>)<NoP>(<P>*<NoP>)
                <name>          =   null terminated string
                <JPriority>     =   unsigned long, Joint Priority (same as Priority unless you use voodoo)
                <NoR>           =   unsigned long, Number Of Paramaters in section A, Angles
                <NoP>           =   unsigned long, Number Of Paramaters in section B, Positions
                {<R>            =   <FN><Y><X><Z>
                    <FN>        =   float, frame time
                    <X>         =   float
                    <Y>         =   float
                    <Z>         =   float
                }
                {<P>            =   <FN><Y><X><Z>
                    <FN>        =   float, frame time
                    <X>         =   float
                    <Y>         =   float
                    <Z>         =   float
                }
            }
            <NoC>               =   signed long, number of constraints, no more then 10
            {<Constraint>       =   <chain_length><constraint_type><source_volume><source_offset><target_volume>
                                    <target_offset><target_dir><ease_in_start><ease_in_stop><ease_out_start><ease_out_stop>
                <chain_length>      =   unsigned byte
                <constraint_type>   =   unsigned byte
                <source_volume>     =   unsigned byte[16]
                {<source_offset>    =   LLVector3 = <x><y><z>
                    <x>             =   float
                    <y>             =   float
                    <z>             =   float
                }
                {<target_volume>    =   unsigned byte[16], null terminated string
                    "GROUND"
                    "PELVIS"
                    "BELLY"
                    "CHEST"
                    "NECK"
                    "HEAD"
                    "L_CLAVICLE"
                    "L_UPPER_ARM"
                    "L_LOWER_ARM"
                    "L_HAND"
                    "R_CLAVICLE"
                    "R_UPPER_ARM"
                    "R_LOWER_ARM"
                    "R_HAND"
                    "R_UPPER_LEG"
                    "R_LOWER_LEG"
                    "R_FOOT"
                    "L_UPPER_LEG"
                    "L_LOWER_LEG"
                    "L_FOOT"
                }
                {<target_offset>    =   LLVector3 = <x><y><z>
                    <x>             =   float
                    <y>             =   float
                    <z>             =   float
                }
                {<target_dir>       =   LLVector3 = <x><y><z>
                    <x>             =   float
                    <y>             =   float
                    <z>             =   float
                }
                <ease_in_start>     =   float
                <ease_in_stop>      =   float
                <ease_out_start>    =   float
                <ease_out_stop>     =   float
            }
        }
    }
I also at one point wrote an animation decoder that would (in two steps) give you an bvh again but heck if I know what I did with it.
-- Strife (talk|contribs) 10:57, 11 October 2011 (PDT)
I am currently in the process of writing a tool to work with .anim files (dumps of the internal format). I ran into strange behavior when trying to edit it as if it was a Euler angle. I found this in the source code of indra\llcharacter\llkeyframemotion.cpp in the latest viewer:
			RotationKey rot_key;
			rot_key.mTime = time;
			LLVector3 rot_angles;
			U16 x, y, z;

			BOOL success = TRUE;

			if (old_version)
			{
				success = dp.unpackVector3(rot_angles, "rot_angles") && rot_angles.isFinite();

				LLQuaternion::Order ro = StringToOrder("ZYX");
				rot_key.mRotation = mayaQ(rot_angles.mV[VX], rot_angles.mV[VY], rot_angles.mV[VZ], ro);
			}
			else
			{
				success &= dp.unpackU16(x, "rot_angle_x");
				success &= dp.unpackU16(y, "rot_angle_y");
				success &= dp.unpackU16(z, "rot_angle_z");

				LLVector3 rot_vec;
				rot_vec.mV[VX] = U16_to_F32(x, -1.f, 1.f);
				rot_vec.mV[VY] = U16_to_F32(y, -1.f, 1.f);
				rot_vec.mV[VZ] = U16_to_F32(z, -1.f, 1.f);
				rot_key.mRotation.unpackFromVector3(rot_vec);
			}
It does at least seem to have a range of -1 to 1 as I said, and I know editing the angles in AnimMaker as if they were Euler angles doesn't get you the results you would expect. I'd like to hear the explanation. It appears that RotationKey (from indra\llcharacter\llkeyframemotion.h) consists of a time variable and a quaternion. Also, in indra\llmath\llquaternion.cpp the definition of unpackFromVector3 seems to do exactly what I described before.
I've also determined, and I'm quite positive of this, as I ran into a bug in my program due to believing the documentation on this page, that the vectors for the constraint source_offset, target_offset, and target_dir are stored as sets of 3 F32. I'm not certain of the order yet. To be specific, when I believed these to be strings it caused the alignment for everything after the first source_offset to be wrong. A vector definitely takes 12 bytes. If you're wondering how I'm so sure about the proper formatting of a constraint, it's because I'm looking at the official "ground sit constrained" animation.

Coaldust Numbers 01:39, 12 October 2011 (PDT)

I'm sorry for not actually saying this before, I do agree with you that the documentation in this article is wrong (seeing as it fails to handle the versioning issue). I just wanted to add what information I had on hand about this issue. The types of the fields in my notes are C++ types, so a "float" is 4 bytes, aka the single precision floating point type (F32). I had totally forgotten about the ZYX ordering for the old version btw. The reason I'm going to put forth as to how the documentation became corrupt is that the people editing the article didn't realize that there were two versions and were doing partial updates.
Digging through my code, I've found that I only handled the new version and not the old version, but then my app was for decoding animations ripped from the client cache (which all use the new format), the purpose (*cough*) was to allow the user to recover lost source material in the event of a disk crash. -- Strife (talk|contribs) 12:10, 12 October 2011 (PDT)


You never addressed the quaternion issue. I still believe I'm right. I'm watching the instruction pointer move through the source and looking at the contents of memory as a animation deserializes in a debugger. I don't think it gets much more official than this.
I'm aware what size a float is. I'm also aware that it's not the same format as a NUL terminated string. I think what I wrote about how I figured out the format of a constraint vector shows that.
I wasn't planning to respond to the XML formatted notes, but since you bring it up, some of the other information is inaccurate. For instance, base priority can be 0 through 4 normally (not 0x40000000, which is 1073741824 decimal), though supposedly higher values work, loop is 0 or 1 (not 0x10000000, which is 268435456 decimal), key frames are stored as unsigned shorts (U16), not floats, the 'emote names' (internal values) are not correct, and the hand pose values are not correct. You've listed what the upload dialog shows for all but the last emote names, which is actually one of the values used internally. I have a table that shows the mapping of the upload dialog names to internal values for the expressions and hand poses, should that be of interest to anyone.
As for people being very concerned with the old format, I'm not aware of any tools that worked with a version before 1.0, or any interest in the internal format before that. The article is obviously accurate (I have written a Python script that reads .anim files based on it) except for what it says about the format of the rotation keys and the vectors in constraints.
Coaldust Numbers 12:58, 12 October 2011 (PDT)
I am aware there are mistakes in them, I did preface them by saying they have not been verified. The notes are 5 years old. At the time the client was not open source and several high profile hacking incidents were taking place. The notes were produced for personal consumption, they were never intended to be shared. I was scared at the time of what LL would do if I did. I think in some places in the notes I used big-endian and others places I used little-endian. The examples you highlight are big-endian examples being parsed as little-endian. (I think 0x40000000 is a typo and should have been 0x04000000 but I digress)
To respond to your original comment.
In the new version it is as you describe exactly, but in the old version it's a Euler angle. I agree with you entirely about the comments not being helpful. As to the Euler angle being 1.0 or 180 degrees, I can't say without digging into the client source code (which I do not have time to do).
-- Strife (talk|contribs) 18:55, 12 October 2011 (PDT)