From CloudModding OoT Wiki

Animations in Ocarina of Time and Majora's Mask follow what I find to be a peculiar format. To have a working animation, the given resource file must have both a hierarchy and animation(s) to go with it.

Capable Viewers

ZSaten, OZMAV2 and z64anim are all viewers which can render normal skeletal structures (hierarchies) with their animations. ZSaten is the only viewer for Link and the beta format used in object_human. z64anim is currently the only editor for hierarchies and animations.

Normal Animations

Animations are composed of three chunks: rotation values, rotation index, and a header

Rotation values

Pretty simple. This is just a list of unsigned 16 bit values that are referenced by the rotation index.
Example:

00004000 8000C000

This would be four values, 0/360 degrees, 90 degrees, 180 degrees, and 270 degrees Note: the first value in most lists of rotation values is 0.

Rotation index

A little more complex. It's size is exactly 6 + (6 * Number of limbs). The first three 16 bit values are translation values for the whole model (in xyz order), and the rest of the six byte groups are rotation values for each part of the hierarchy, in the order they are listed in the hierarchy.
Example:

00000001 00000000 00020000

This would translate the model on the y axis as much as the second value in the rotation values, and would rotate the first part of the hierarchy (and all of it's children) as much as the third value of the rotation value list. All of the values left at 0 will do nothing since the first value in the rotation value list is 0.

Animation header

Format:

ffff0000 rrrrrrrr iiiiiiii llll0000

Where:

  • f is the number of frames in the animation
  • r is the segment offset of the rotation value list
  • i is the segment offset of the rotation index list
  • l is the limit

Most of these are self explanatory, but there is one that more confusing, the limit. If a entry in the rotation index list is greater than or equal to the limit, then for each frame the next value in the rotation list is read.

Link Animations

Documentation released by Zant Nov 2010

Animation headers

0x2310 to 0x34F8 in gameplay_keep
Format:
ffff0000bboooooo
Where: f is the frame count, b is the segment id, used to reference the file containing the animation (in this case, segment 7 = link_animetion), and o is the offset from the start of the file

Translation

At the offset specified by the header; within link_animetion
Format:
XXXX YYYY ZZZZ
X, Y and Z are all global translation values for this particular frame (signed)

Rotations

The next 0x7E bytes immediately after the translation values are rotation values for each limb, in the order they are listed in the hierarchy. They are rotated on the X, Y, and Z axis, in that order, in 6-byte groups of 2 byte values.
Format:
XXXX YYYY ZZZZ
X, Y and Z are unsigned rotation amounts for the particular limb.

Animated Texture Properties

The last two bytes of an animation frame change Link's facial expression for the current frame. A 16-bit unsigned value. This still needs more research.

Example

Here's the first 0x12 bytes of the first frame of the "Alpha Jump Strike" animation:

0054 00D6 0096 /* XYZ Translation for Link's model for this frame */

C000 FBAC C000 /* XYZ rotation for Link's first limb for this frame */
0000 0000 0000 /* XYZ rotation for Link's second limb for this frame */

C code

Documentation written by CrookedPoe Oct 2019

/*** Link's Face Textures; Left and Right are from our point-of-view. ***/
enum
{
LINKEYE_AUTO, /* Automatic Eyes */
LINKEYE_OPEN, /* Open Eyes */
LINKEYE_HALF, /* Half Open Eyes */
LINKEYE_CLOSED, /* Closed Eyes */
LINKEYE_LEFT, /* Look Left */
LINKEYE_RIGHT, /* Look Right */
LINKEYE_SHOCK, /* Shocked / Surprised */
LINKEYE_DOWN, /* Look Down */
LINKEYE_CLTIGHT /* Tightly Closed Eyes */
};

/*** Link's Mouth Textures ***/
enum
{
  LINKMOUTH_AUTO, /* Automatic Mouth */
  LINKMOUTH_CLOSED, /* Closed Mouth */
  LINKMOUTH_SLIGHT, /* Open Mouth (Slight, Playing Ocarina) */
  LINKMOUTH_WIDE, /* Open Mouth (Wide, Shouting) */
  LINKMOUTH_SMILE /* Open Mouth (Smile, Item Get) */
};

/*** Link's Limbs; Left and Right are from Link's point-of-view. ***/
enum
{
  LINKLIMB_ROOT, /* Limb 0 : Root */
  LINKLIMB_WAIST, /* Limb 1 : Waist */
  LINKLIMB_BODY_LO, /* Limb 2 : Lower-Body Affector */
  LINKLIMB_THIGH_R, /* Limb 3 : Right Thigh */
  LINKLIMB_SHIN_R, /* Limb 4 : Right Shin */
  LINKLIMB_FOOT_R, /* Limb 5 : Right Foot */
  LINKLIMB_THIGH_L, /* Limb 6 : Left Thigh */
  LINKLIMB_SHIN_L, /* Limb 7 : Left Shin */
  LINKLIMB_FOOT_L, /* Limb 8 : Left Foot */
  LINKLIMB_BODY_HI, /* Limb 9 : Upper-Body Affector */
  LINKLIMB_HEAD, /* Limb 10 : Head */
  LINKLIMB_HAT, /* Limb 11 : Hat */
  LINKLIMB_COLLAR, /* Limb 12 : Collar */
  LINKLIMB_SHOULDER_L, /* Limb 13 : Left Shoulder */
  LINKLIMB_FOREARM_L, /* Limb 14 : Left Forarm */
  LINKLIMB_HAND_L, /* Limb 15 : Left Hand */
  LINKLIMB_SHOULDER_R, /* Limb 16 : Right Shoulder */
  LINKLIMB_FOREARM_R, /* Limb 17 : Right Forearm */
  LINKLIMB_HAND_R, /* Limb 18 : Right Hand */
  LINKLIMB_SHEATH, /* Limb 19 : Sheath / Sheathed Sword */
  LINKLIMB_TORSO /* Limb 20 : Torso */
};

/*** 3-Vector Structure (Signed 16-bit Integers) ***/
typedef struct
{
  int16_t x;
  int16_t y;
  int16_t z;
} vec3s_t;

/*** Animation Header for Link's Animations (located inside gameplay_keep). ***/
typedef struct
{
  uint16_t frame_count;
  uint16_t pad; /* Padding / Unused */
  uint32_t addr; /* The address of the animation data (includes the RAM segment as its left-most byte). */
} link_animetion_header_t;

/*** Link's Facial Expression ***/
typedef struct
{
  uint8_t mouth: 4;
  uint8_t eyes : 4;
} link_animetion_face_t;

/*** Animation Frame Structure ***/
typedef struct
{
  vec3s_t loc; /* Root Translation */
  vec3s_t[21] rot; /* Rotation of Each Limb */
  uint8_t pad; /* Padding / Unused */
  link_animetion_face_t face; /* Facial Expression */
} link_animetion_frame_t;

Hierarchy

Hierarchies are composed of three chunks: limb entries, limb index, and a header.

Limb entries

Format:

xxxxyyyy zzzzaabb dddddddd [cccccccc*] (repeats for each entry)

* Rare, only used in link
Where:
x is the x translation, relative to the limb's parent
y is the y translation, relative to the limb's parent
z is the z translation, relative to the limb's parent
a is the first child's index in the limb list
b is the next limb's index in the limb list
d is the display list (as a segment offset)
c is the far model display list (as a segment offset)
Example:

00640032 000004FF 06002458

This entry and it's child (entry 4) would be offset by 100 on the x axis, 50 on the y axis, 0 on the x axis. It would render the display list at the offset of 0x2458 within object_link_child (0015, Child Link) or object_link_boy (0014, Adult Link) (the files assigned to segment 6), and it has one child (0x4), 0xFF indicates that there isn't a next limb.

Limb index

Format:

bboooooo (repeats for each entry)

Where:
b is the segment id
o is the offset of the limb entry

Header

Format:

bboooooo pp000000 xx000000

Where:
b is segment id
o is the offset of the beginning of the limb index
p is the number of parts
x is the number of display lists

Beta Format

There is one known file, object_human, which uses a depreciated hierarchy and animation format. ZSaten is capable of viewing these animations. Refer to zold.c for details on implementation of the format.

Hierarchy

Like the supported format, it is a "tree" setup, with child and sibling limbs. Unlike the used format, however, it uses pointers instead of indexes, and contains default rotation values. The translation values are 32-bit floats, as well. The structure for each limb is as follows:

typedef struct {
    Gfx *dl;
    f32 trans[3];
    s16 rot[3];
    u16 __align;
    limb *a;
    limb *b;
} limb;

For people who are not familiar with C structures:

dddddddd xxxxxxxx yyyyyyyy zzzzzzzz qqqqwwww rrrr0000 nnnnnnnn uuuuuuuu

Where d is the display list, x, y, and z are floating-points of the translation values, q, w and r are x/y/z rotation, n is a pointer to the child limb and u is a pointer to the 'sibling' limb.
Besides being pointed to by eachother, all the limbs are pointed to in an array of pointers following them (the limb list). The order they are in this array is the same order that rotations are applied to them during animation.

Animation

The header defines the number of parts in the model and the number of frames in the animation. Format:
ppppffff bbrrrrrr bbiiiiii
Where:
p is the number of parts,
f is the number of frames,
b is the segment id,
r is the rotation values, and
i is the "key".

The rotation values are values referenced by the keys. They are simply unsigned half words representing rotation.
The key is a "lookup" table for the animations. It goes in the order that the limbs are listed in the limb list (as defined above), taking into account that the first "entry" is for translation, not rotation, of limbs. The format is:
aaaaxxxx bbbbyyyy cccczzzz
Where:
a is how many frames there are for x; either 1 or the value of f as defined in the header.
x is which start index of the rotation values to use. If a is 1, x defines the constant rotation index. However, if a is equal to f, x defines the rotation index for the first frame.
b and y are the same as a and x except on the y-axis, as are the c and z pair.


Rendering tips

typedef struct{
    signed short X, Y, Z;
    short XR, YR, ZR;
    char Child;
    char Next;
    unsigned long DList;
} Bone;

void DrawBone(Bones[], int CurrentBone)
{
    glPushMatrix();

    glTranslated(Bones[CurrentBone].X, Bones[CurrentBone].Y, Bones[CurrentBone].Z);
    /* Apply rotation in the order z, y, x! */
    glRotated(Bones[CurrentBone].RZ / 182.0444444, 0, 0, 1);
    glRotated(Bones[CurrentBone].RY / 182.0444444, 0, 1, 0);
    glRotated(Bones[CurrentBone].RX / 182.0444444, 1, 0, 0);

    //Draw display list
    if(Bones[CurrentBone].DList)
        RenderDisplayList(Bones[CurrentBone].DList, true);
    
    //Draw child
    if(Bones[CurrentBone].Child > -1)
        DrawBone(Bones, Bones[CurrentBone].Child);
    
    glPopMatrix(); // pop matrix here!
    
    //Draw next
    if(Bones[CurrentBone].Next > -1)
        DrawBone(Bones, Bones[CurrentBone].Next);
}

Credits

spinout - figuring out most of this stuff initially
euler - filling in the holes in spinout's knowledge
Zant - Link's animation format