Skip to content

Instantly share code, notes, and snippets.

@scurest
Last active December 23, 2019 22:30
Show Gist options
  • Save scurest/ec8ff29328044803da3c5f1f2337b844 to your computer and use it in GitHub Desktop.
Save scurest/ec8ff29328044803da3c5f1f2337b844 to your computer and use it in GitHub Desktop.
Notes on how IQM 2 animations work
Notes on how IQM 2 animations work:
(see http://sauerbraten.org/iqm/iqm.txt)
There is just one global animation strip for the whole file. Each iqmanim is
defined by picking out one subrange of frames from this global strip.
Every joint has one iqmpose. Every iqmpose has ten channels for the ten TRS
properties (Tx Ty Tz Qx Qy Qz Qw Sx Sy Sz). Every channel maps a frame
number to the value of that TRS property at that frame. A channel can be
either constant or variable.
The i-th channel is constant if the i-th bit of channelmask is zero
is channel i constant?
channelmask & (1 << i) == 0
The value of a constant channel at every frame is channeloffset[i].
Otherwise the channel is variable and there are num_frames samples stored in
the frames[] array that encode the value of the channel at every frame. Each
channel has it's own minimum and maximum value, and each sample is an
unsigned 16-bit integer that interpolates between this minimum and maximum.
The value is computed from the sample with
channeloffset[i] + (16-bit sample) * channelscale[i]
Giving the following relation to the minimum and maximum for that channel
channeloffset[i] = min
channelscale[i] = (max - min) / (2^16 - 1)
The frames[] array is packed in the order
for each frame
for each joint
for each channel
if this channel is variable
16-bit sample
num_framechannels is the total number of variable channels in the whole
file. The frames[] array therefore has total length num_framechannels *
num_frames.
Pseudo-code to evaluate the n-th animation on its f-th frame:
// output[j] will be the j-th joint's local-to-parent
// Compute frame number in the global strip
frame = iqmanims[n].first_frame + f
samples = &frames[num_framechannels * frame]
for j in 0..num_poses
pose = iqmposes[j]
float trs[10]
for i in 0..10
trs[i] = pose.channeloffset[i]
if pose.channelmask & (1 << i) != 0
trs[i] += (*samples++) * channelscale[i]
output[j] = trs_to_matrix(
translation= trs[0..3],
rotation= normalize(trs[3..7]),
scale= trs[7..10],
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment