Skip to content

ANI Animation Format

An .ani file holds one animation: keyframes for the bones of a skeleton, plus a list of timed events (sounds, hit checks, effects). It names bones but does not contain the skeleton. The model config says which skeleton an animation is played on.

This page follows the client loader (ioAnimationManager::LoadFileInternal in io3DEngine) and the web tools’ reader and writer. Anything marked not confirmed yet was not verified.

The general conventions (little-endian, int32 length strings, 4-byte token) are in MSH Mesh Format.

Part Type Field
Header char[4] ANI\0
int32 version: 4000, 4001 or 4002
Events int32 event count
per event string type, string sub info, float time
Total int32 total time of the animation
Tracks int32 track count
per track see below

Any other version makes the client log “Unknown Version” and refuse the file.

Type Field
string bone name (must match a bone of the skeleton)
float weight
int32 key count
key × count rotation, position, time (below)
Part Size Notes
Rotation 16, 4 or 8 bytes depends on the version
Position 12 bytes float3
Time 4 bytes int32

Times are whole numbers. The web tools sample them as milliseconds, and the client passes them on as float to the key list; the unit is not confirmed yet from the client side.

Version Rotation Size per key
4000 quaternion as four floats (x, y, z, w) 16 + 12 + 4 = 32 bytes
4001 “small three”, one uint32 4 + 12 + 4 = 20 bytes
4002 “eight bytes”, two uint32 (high word, then low word) 8 + 12 + 4 = 24 bytes

The uint32 holds:

  • bits 31 to 30: index (0 to 3) of the largest component, which is not stored
  • bits 29 to 20, 19 to 10, 9 to 0: the other three components, each a 10-bit number n mapped to 2 × n / 1023 - 1

The three stored values go to the remaining components in index order. The missing one is rebuilt as sqrt(1 - a² - b² - c²). The client function is QComp::DecompSmallThree.

Two uint32 words hold four 16-bit values:

Value Where Maps to
x high 16 bits of the first word 2 × n / 65535 - 1
y low 16 bits of the first word 2 × n / 65535 - 1
z high 16 bits of the second word 2 × n / 65535 - 1
w low 16 bits of the second word n / 65535 (0 to 1)

This is QComp::Decomp8Bytes. The tools read the stored words as they are and write them back untouched, so a save does not lose precision on keys you did not change.

  • Events are sorted by time after loading (SortEventTimeList). What each event type does is handled by the event code, not by this format.
  • Tracks are matched to skeleton bones by name. The root bone (Bip01) usually carries the movement.
  • Keep bone names identical to the skeleton. Retargeting tools exist because two skeletons rarely share names.

ioAnimationManager.cpp, ioFileTokenDefine.h and the quaternion routines are identical in the 2014, 2019 and 2021 sources. All three accept 4000, 4001 and 4002.

Tool Use
Models Plays .ani files on a skeleton
Anim Studio Reads and writes .ani: keys, events, join, retarget. The version and the stored rotation words are kept as they were for unchanged keys
Heroes Reads hero animations and their events
  • The unit of key times and event times on the client side (the web tools use milliseconds).
  • The list of event types and what each sub info string means.
  • What the client does with a track whose bone is missing from the skeleton, and with a bone that has no track.
  • Whether position keys are relative to the parent bone in every shipped file (it is how the web tools draw them).