Files
perfect-dark/src/lib/anim.c
T
2024-08-18 11:00:56 +10:00

784 lines
20 KiB
C

#include <ultra64.h>
#include "constants.h"
#include "game/prop.h"
#include "game/game_1531a0.h"
#include "game/bg.h"
#include "bss.h"
#include "lib/dma.h"
#include "lib/memp.h"
#include "lib/mtx.h"
#include "lib/anim.h"
#include "lib/lib_2f490.h"
#include "lib/libc/ll.h"
#include "data.h"
#include "types.h"
#define ANIM_HEADER_CACHE_SIZE 40
#define ANIM_FRAME_CACHE_SIZE 32
u8 *g_AnimFrameByteSlots;
u8 **g_AnimFrameBytes;
s16 *g_AnimFrameAnimNums;
s16 *g_AnimFrameFrameNums;
u8 *g_AnimFrameBirths;
u8 *g_AnimHeaderByteSlots;
u8 **g_AnimHeaderBytes;
s16 *g_AnimHeaderAnimNums;
s32 *g_AnimHeaderBirths;
s16 g_NumRomAnimations;
struct animtableentry *g_RomAnims;
u32 g_NextAnimFrameIndex = 0;
s32 g_NextAnimHeaderIndex = 0;
s16 g_NumAnimations = 0;
struct animtableentry *g_Anims = NULL;
u8 *g_AnimToHeaderSlot = NULL;
s16 *g_AnimAverageMoveDist = NULL;
s32 g_AnimMaxBytesPerFrame = 176;
s32 g_AnimMaxHeaderLength = 608;
bool g_AnimHostEnabled = false;
u8 *g_AnimHostSegment = NULL;
extern u8 _animationsTableRomStart;
extern u8 _animationsTableRomEnd;
void anims_init(void)
{
s32 i;
u32 *ptr;
u32 tablelen = ALIGN64(&_animationsTableRomEnd - &_animationsTableRomStart);
ptr = memp_alloc(tablelen, MEMPOOL_PERMANENT);
dma_exec(ptr, (romptr_t) &_animationsTableRomStart, tablelen);
g_NumAnimations = g_NumRomAnimations = ptr[0];
g_Anims = g_RomAnims = (struct animtableentry *)&ptr[1];
g_AnimMaxHeaderLength = 1;
g_AnimMaxBytesPerFrame = 1;
for (i = 0; i < g_NumAnimations; i++) {
if (g_Anims[i].headerlen > g_AnimMaxHeaderLength) {
g_AnimMaxHeaderLength = g_Anims[i].headerlen;
}
if (g_Anims[i].bytesperframe > g_AnimMaxBytesPerFrame) {
g_AnimMaxBytesPerFrame = g_Anims[i].bytesperframe;
}
}
g_AnimMaxHeaderLength = ALIGN16(g_AnimMaxHeaderLength + 34);
g_AnimMaxBytesPerFrame = ALIGN16(g_AnimMaxBytesPerFrame + 34);
g_AnimToHeaderSlot = memp_alloc(ALIGN64(g_NumAnimations), MEMPOOL_PERMANENT);
g_AnimAverageMoveDist = memp_alloc(ALIGN64(g_NumAnimations * sizeof(*g_AnimAverageMoveDist)), MEMPOOL_PERMANENT);
g_AnimFrameByteSlots = memp_alloc(ALIGN64(ANIM_FRAME_CACHE_SIZE * g_AnimMaxBytesPerFrame), MEMPOOL_PERMANENT);
g_AnimFrameBytes = memp_alloc(ALIGN64(ANIM_FRAME_CACHE_SIZE * sizeof(*g_AnimFrameBytes)), MEMPOOL_PERMANENT);
g_AnimFrameAnimNums = memp_alloc(ALIGN64(ANIM_FRAME_CACHE_SIZE * sizeof(*g_AnimFrameAnimNums)), MEMPOOL_PERMANENT);
g_AnimFrameFrameNums = memp_alloc(ALIGN64(ANIM_FRAME_CACHE_SIZE * sizeof(*g_AnimFrameFrameNums)), MEMPOOL_PERMANENT);
g_AnimFrameBirths = memp_alloc(ALIGN64(ANIM_FRAME_CACHE_SIZE * sizeof(*g_AnimFrameBirths)), MEMPOOL_PERMANENT);
g_AnimHeaderByteSlots = memp_alloc(ALIGN64(ANIM_HEADER_CACHE_SIZE * g_AnimMaxHeaderLength), MEMPOOL_PERMANENT);
g_AnimHeaderBytes = memp_alloc(ALIGN64(ANIM_HEADER_CACHE_SIZE * sizeof(*g_AnimHeaderBytes)), MEMPOOL_PERMANENT);
g_AnimHeaderAnimNums = memp_alloc(ALIGN64(ANIM_HEADER_CACHE_SIZE * sizeof(*g_AnimHeaderAnimNums)), MEMPOOL_PERMANENT);
g_AnimHeaderBirths = memp_alloc(ALIGN64(ANIM_HEADER_CACHE_SIZE * sizeof(*g_AnimHeaderBirths)), MEMPOOL_PERMANENT);
anims_init_tables();
g_AnimHostSegment = NULL;
g_AnimHostEnabled = false;
}
void anims_init_tables(void)
{
s32 i;
for (i = 0; i < g_NumAnimations; i++) {
g_AnimToHeaderSlot[i] = 0xff;
g_AnimAverageMoveDist[i] = 0;
}
for (i = 0; i < ANIM_FRAME_CACHE_SIZE; i++) {
g_AnimFrameAnimNums[i] = 0;
g_AnimFrameFrameNums[i] = 0;
g_AnimFrameBirths[i] = 0;
}
for (i = 0; i < ANIM_HEADER_CACHE_SIZE; i++) {
g_AnimHeaderAnimNums[i] = 0;
g_AnimHeaderBirths[i] = -2;
}
}
void anims_reset(void)
{
g_NumAnimations = g_NumRomAnimations;
g_Anims = g_RomAnims;
g_AnimHostEnabled = false;
}
s32 anim_get_num_frames(s16 animnum)
{
return g_Anims[animnum].numframes;
}
bool anim_has_frames(s16 animnum)
{
return animnum < g_NumAnimations && g_Anims[animnum].numframes > 0;
}
s32 anim_get_num_animations(void)
{
return g_NumAnimations;
}
extern u8 _animationsSegmentRomStart;
u8 *anim_dma(u8 *dst, u32 segoffset, u32 len)
{
if (g_AnimHostEnabled) {
bcopy(&g_AnimHostSegment[segoffset], dst, len);
return dst;
}
return dma_exec_with_auto_align(dst, (romptr_t) &_animationsSegmentRomStart + segoffset, len);
}
/**
* Return -1 if the given apparent frame is a repeat frame, or if not a repeat
* frame then remap the apparent frame to a real one and return it.
*
* The end of the header can contain a sequence of shorts such as:
* -1, 55, 30
*
* The values are iterated backwards in pairs of 2 and are terminated by -1.
*
* In each pair, the right value is the repeatfromframe and the left value is
* the repeattoframe. In the above example, apparent frames 30 to 55 are
* repeated, so the remapping looks like:
* 29 -> 29
* 30 -> -1
* ...
* 55 -> -1
* 56 -> 30
* 57 -> 31
*/
s32 anim_get_remapped_frame(s16 animnum, s32 apparentframe)
{
u8 *ptr = (u8 *)(g_AnimHeaderBytes[g_AnimToHeaderSlot[animnum]] + g_Anims[animnum].headerlen - 2);
s32 realframe = apparentframe;
while (true) {
s16 repeatfromframe = ptr[0] << 8 | ptr[1];
s16 repeattoframe;
if (repeatfromframe < 0) {
break;
}
repeattoframe = ptr[-2] << 8 | ptr[-1];
ptr -= 4;
if (repeatfromframe <= apparentframe) {
if (repeattoframe < apparentframe) {
realframe = realframe - repeattoframe + repeatfromframe - 1;
} else {
realframe = -1;
break;
}
}
}
return realframe;
}
/**
* Similar to the above, but with the following differences:
* - Write the remapped frame to the frameptr pointer instead of returning it.
* - If the apparent frame is a repeat, write the original frame rather than -1.
* - Return true if the frame is original or false if it's a repeat.
*/
bool anim_remap_frame_for_load(s16 animnum, s32 apparentframe, s32 *frameptr)
{
u8 *ptr = (u8 *)(g_AnimHeaderBytes[g_AnimToHeaderSlot[animnum]] + g_Anims[animnum].headerlen - 2);
s32 result = apparentframe;
bool ret = true;
while (true) {
s16 repeatfromframe = ptr[0] << 8 | ptr[1];
s16 repeattoframe;
if (repeatfromframe < 0) {
break;
}
repeattoframe = ptr[-2] << 8 | ptr[-1];
ptr -= 4;
if (repeatfromframe <= apparentframe) {
if (repeattoframe < apparentframe) {
result = result - repeattoframe + repeatfromframe - 1;
} else {
result = result - apparentframe + repeatfromframe;
ret = false;
break;
}
}
}
*frameptr = result;
return ret;
}
/**
* Return true if the given animation and frame should be skipped.
*
* Used by cutscenes.
*
* The skip frame numbers are stored at the tail end of the header, prior to the
* frame repeat data. The frame numbers are stored as a list of shorts.
* The list is terminated on the left side with a negative value.
*/
bool anim_is_frame_cut_skipped(s16 animnum, s32 frame)
{
u8 *ptr = (u8 *)(g_AnimHeaderBytes[g_AnimToHeaderSlot[animnum]] + g_Anims[animnum].headerlen - 2);
// Iterate past the repeat list
if (g_Anims[animnum].flags & ANIMFLAG_HASREPEATFRAMES) {
while (true) {
s16 repeatfromframe = ptr[0] << 8 | ptr[1];
if (repeatfromframe < 0) {
break;
}
ptr -= 4;
}
ptr -= 2;
}
while (true) {
s16 skipframe = ptr[0] << 8 | ptr[1];
if (skipframe < 0) {
break;
}
if (skipframe == frame) {
return true;
}
ptr -= 2;
}
return false;
}
u8 anim_load_frame(s16 animnum, s32 framenum)
{
s32 slot = -1;
s32 i;
s32 offset;
s32 stack;
s32 loadframenum = framenum;
for (i = 0; i < ANIM_FRAME_CACHE_SIZE; i++) {
if (g_AnimFrameAnimNums[i] == animnum && g_AnimFrameFrameNums[i] == loadframenum) {
slot = i;
break;
}
}
if (slot >= 0) {
g_AnimFrameBirths[slot] = 1;
} else {
slot = g_NextAnimFrameIndex;
while (g_AnimFrameBirths[slot]) {
slot = (slot + 1) % ANIM_FRAME_CACHE_SIZE;
}
if (g_Anims[animnum].flags & ANIMFLAG_HASREPEATFRAMES) {
anim_remap_frame_for_load(animnum, framenum, &loadframenum);
}
if (g_Anims[animnum].bytesperframe) {
offset = g_Anims[animnum].bytesperframe * loadframenum + (g_Anims[animnum].data + g_Anims[animnum].headerlen);
g_AnimFrameBytes[slot] = anim_dma(&g_AnimFrameByteSlots[slot * g_AnimMaxBytesPerFrame], offset, g_Anims[animnum].bytesperframe);
} else {
g_AnimFrameBytes[slot] = &g_AnimFrameByteSlots[slot * g_AnimMaxBytesPerFrame];
}
g_AnimFrameAnimNums[slot] = animnum;
g_AnimFrameFrameNums[slot] = framenum;
g_AnimFrameBirths[slot] = 1;
g_NextAnimFrameIndex = (slot + 1) % ANIM_FRAME_CACHE_SIZE;
}
return slot;
}
void anim_forget_frame_births(void)
{
s32 i;
for (i = 0; i < ANIM_FRAME_CACHE_SIZE; i++) {
g_AnimFrameBirths[i] = 0;
}
}
void anim_load_header(s16 animnum)
{
s32 i;
if (g_AnimToHeaderSlot[animnum] != 0xff) {
g_AnimHeaderBirths[g_AnimToHeaderSlot[animnum]] = g_Vars.thisframestart240;
g_NextAnimHeaderIndex = (g_AnimToHeaderSlot[animnum] + 1) % ANIM_HEADER_CACHE_SIZE;
} else {
s32 tmp;
s32 slot = g_NextAnimHeaderIndex;
s32 stack;
for (i = 0; i < ANIM_HEADER_CACHE_SIZE; i++) {
if (g_AnimHeaderBirths[i] < g_AnimHeaderBirths[slot]) {
slot = i;
}
}
if (g_AnimHeaderBirths[slot]);
if (&g_Vars && &g_Vars);
if (g_AnimHeaderAnimNums[slot]) {
g_AnimToHeaderSlot[g_AnimHeaderAnimNums[slot]] = 0xff;
}
tmp = g_Anims[animnum].headerlen;
g_AnimHeaderBytes[slot] = anim_dma(&g_AnimHeaderByteSlots[slot * g_AnimMaxHeaderLength], g_Anims[animnum].data, tmp);
g_AnimToHeaderSlot[animnum] = slot;
g_AnimHeaderAnimNums[slot] = animnum;
g_AnimHeaderBirths[slot] = g_Vars.thisframestart240;
g_NextAnimHeaderIndex = (slot + 1) % ANIM_HEADER_CACHE_SIZE;
}
}
/**
* Read a number of bits from the given ptr and return it as an integer.
*
* remainingbits in the number of bits to read.
* bitoffset is the starting bit offset relative to ptr.
*/
s32 anim_read_bits(u8 *ptr, u8 remainingbits, u32 bitoffset)
{
u32 result = 0;
u32 mask;
u8 numbitsthisbyte;
result *= bitoffset / 8;
// Move ptr forward past all the bytes that should be fully skipped
ptr += bitoffset / 8;
// Calculate the number of bits to read in the first byte
bitoffset %= 8;
numbitsthisbyte = 8 - bitoffset;
// Iterate bytes, except for the last if it's a partial read
while (remainingbits >= numbitsthisbyte) {
remainingbits -= numbitsthisbyte;
mask = (1 << numbitsthisbyte) - 1;
result |= (*ptr & mask) << remainingbits;
ptr++;
numbitsthisbyte = 8;
}
// Read bits from the final byte if it's partial read
if (remainingbits > 0) {
mask = (1 << remainingbits) - 1;
result |= (*ptr >> (numbitsthisbyte - remainingbits)) & mask;
}
return result;
}
s32 anim_read_signed_short(u8 *ptr, u8 readbitlen, s32 bitoffset)
{
u16 result = anim_read_bits(ptr, readbitlen, bitoffset);
if (readbitlen < 16 && (result & (1 << (readbitlen - 1)))) {
result |= ((1 << (16 - readbitlen)) - 1) << readbitlen;
}
return result;
}
/**
* Read the rotation, position and scale values for the given part for the frame
* at the given frameslot.
*
* Both the anim header and frame data must be loaded already.
*/
void anim_get_rot_translate_scale(s32 part, bool flip, struct skeleton *skel, s16 animnum, u8 frameslot, struct coord *rot, struct coord *translate, struct coord *scale)
{
s32 i;
u16 introt[3];
u8 readbitlen;
u8 *framebytes = g_AnimFrameBytes[frameslot];
u8 framelen;
u8 *ptr;
u8 *end;
s32 bitoffset;
u32 stack;
if (flip) {
part = skel->things[part][1];
}
framelen = g_Anims[animnum].framelen;
ptr = g_AnimHeaderBytes[g_AnimToHeaderSlot[animnum]];
bitoffset = 0;
end = ptr + g_Anims[animnum].headerlen;
for (i = 0; i < part && ptr < end; i++) {
u8 flags = *ptr;
ptr++;
if (flags & ANIMFIELD_08) {
bitoffset += ptr[2] + ptr[5] + ptr[8] + ptr[11];
ptr += 12;
} else if (flags & ANIMFIELD_S16_TRANSLATE) {
bitoffset += ptr[2] + ptr[5] + ptr[8];
ptr += 9;
} else if (flags & ANIMFIELD_S32_TRANSLATE) {
bitoffset += ptr[0] + ptr[5] + ptr[10];
ptr += 15;
}
if (flags & ANIMFIELD_S16_ROTATE) {
bitoffset += ptr[2] + ptr[5] + ptr[8];
ptr += 9;
} else if (flags & ANIMFIELD_F32_ROTATE) {
bitoffset += 96;
}
if (flags & ANIMFIELD_CAMERA) {
bitoffset += ptr[0];
ptr += 5;
}
if (flags & ANIMFIELD_F32_SCALE) {
bitoffset += 0x60;
}
}
if (ptr < end) {
u8 flags = *ptr;
ptr++;
if (flags & ANIMFIELD_S16_TRANSLATE) {
readbitlen = ptr[2];
translate->x = (s16) (anim_read_signed_short(framebytes, readbitlen, bitoffset) + (ptr[0] << 8) + ptr[1]);
bitoffset += readbitlen;
readbitlen = ptr[5];
translate->y = (s16) (anim_read_signed_short(framebytes, readbitlen, bitoffset) + (ptr[3] << 8) + ptr[4]);
bitoffset += readbitlen;
readbitlen = ptr[8];
translate->z = (s16) (anim_read_signed_short(framebytes, readbitlen, bitoffset) + (ptr[6] << 8) + ptr[7]);
bitoffset += readbitlen;
ptr += 9;
} else if (flags & ANIMFIELD_S32_TRANSLATE) {
readbitlen = ptr[0];
translate->x = (anim_read_bits(framebytes, readbitlen, bitoffset) + ((ptr[1] << 24) + (ptr[2] << 16) + (ptr[3] << 8) + ptr[4])) * 0.001f;
bitoffset += readbitlen;
readbitlen = ptr[5];
translate->y = (anim_read_bits(framebytes, readbitlen, bitoffset) + ((ptr[6] << 24) + (ptr[7] << 16) + (ptr[8] << 8) + ptr[9])) * 0.001f;
bitoffset += readbitlen;
readbitlen = ptr[10];
translate->z = (anim_read_bits(framebytes, readbitlen, bitoffset) + ((ptr[11] << 24) + (ptr[12] << 16) + (ptr[13] << 8) + ptr[14])) * 0.001f;
bitoffset += readbitlen;
ptr += 15;
} else {
if (flags & ANIMFIELD_08) {
bitoffset += ptr[2] + ptr[5] + ptr[8] + ptr[11];
ptr += 12;
}
translate->x = translate->y = translate->z = 0.0f;
}
if (flags & ANIMFIELD_S16_ROTATE) {
readbitlen = ptr[2];
introt[0] = anim_read_bits(framebytes, readbitlen, bitoffset);
introt[0] += (ptr[0] << 8) + ptr[1];
introt[0] <<= 16 - framelen;
bitoffset += readbitlen;
readbitlen = ptr[5];
introt[1] = anim_read_bits(framebytes, readbitlen, bitoffset);
introt[1] += (ptr[3] << 8) + ptr[4];
introt[1] <<= 16 - framelen;
bitoffset += readbitlen;
readbitlen = ptr[8];
introt[2] = anim_read_bits(framebytes, readbitlen, bitoffset);
introt[2] += (ptr[6] << 8) + ptr[7];
introt[2] <<= 16 - framelen;
bitoffset += readbitlen;
rot->x = introt[0] * M_BADTAU / 65536.0f;
if (flip) {
if (introt[1] != 0) {
rot->y = (0x10000 - introt[1]) * M_BADTAU / 65536.0f;
} else {
rot->y = 0.0f;
}
if (introt[2] != 0) {
rot->z = (0x10000 - introt[2]) * M_BADTAU / 65536.0f;
} else {
rot->z = 0.0f;
}
} else {
rot->y = introt[1] * M_BADTAU / 65536.0f;
rot->z = introt[2] * M_BADTAU / 65536.0f;
}
} else if (flags & ANIMFIELD_F32_ROTATE) {
s32 sp38;
sp38 = anim_read_bits(framebytes, 32, bitoffset);
rot->x = *(f32 *)&sp38;
bitoffset += 32;
sp38 = anim_read_bits(framebytes, 32, bitoffset);
rot->y = *(f32 *)&sp38;
bitoffset += 32;
sp38 = anim_read_bits(framebytes, 32, bitoffset);
rot->z = *(f32 *)&sp38;
bitoffset += 32;
if (flip) {
if (rot->y != 0.0f) {
rot->y = M_BADTAU - rot->y;
}
if (rot->z != 0.0f) {
rot->z = M_BADTAU - rot->z;
}
}
} else {
rot->x = rot->y = rot->z = 0.0f;
}
if (flags & ANIMFIELD_F32_SCALE) {
s32 word;
word = anim_read_bits(framebytes, 32, bitoffset);
scale->x = *(f32 *)&word;
bitoffset += 32;
word = anim_read_bits(framebytes, 32, bitoffset);
scale->y = *(f32 *)&word;
bitoffset += 32;
word = anim_read_bits(framebytes, 32, bitoffset);
scale->z = *(f32 *)&word;
} else {
scale->x = scale->y = scale->z = 1.0f;
}
return;
}
rot->x = rot->y = rot->z = 0.0f;
translate->x = translate->y = translate->z = 0.0f;
scale->x = scale->y = scale->z = 1.0f;
}
/**
* Read the position and Y rotation (?) values for the given part at the given
* frame number.
*
* No data needs to be loaded by the caller - the function will ensure the
* header and frame are loaded.
*/
u16 anim_get_pos_angle_as_int(s32 part, bool flip, struct skeleton *skel, s16 animnum, s32 framenum, s16 inttranslate[3], bool use_cache)
{
u16 result = 0;
s32 bitoffset;
u8 readbitlen;
u8 slot;
u8 *framebytes;
u8 *ptr;
s32 i;
if (use_cache) {
inttranslate[0] = 0;
inttranslate[1] = 0;
inttranslate[2] = g_AnimAverageMoveDist[animnum];
} else {
anim_load_header(animnum);
slot = anim_load_frame(animnum, framenum);
anim_forget_frame_births();
framebytes = g_AnimFrameBytes[slot];
if (flip) {
part = skel->things[part][1];
}
bitoffset = 0;
ptr = g_AnimHeaderBytes[g_AnimToHeaderSlot[animnum]];
for (i = 0; i < part; i++) {
u8 flags = *ptr;
ptr++;
if (flags & ANIMFIELD_08) {
bitoffset += ptr[2] + ptr[5] + ptr[8] + ptr[11];
ptr += 12;
} else if (flags & ANIMFIELD_S16_TRANSLATE) {
bitoffset += ptr[2] + ptr[5] + ptr[8];
ptr += 9;
} else if (flags & ANIMFIELD_S32_TRANSLATE) {
bitoffset += ptr[0] + ptr[5] + ptr[10];
ptr += 15;
}
if (flags & ANIMFIELD_S16_ROTATE) {
bitoffset += ptr[2] + ptr[5] + ptr[8];
ptr += 9;
} else if (flags & ANIMFIELD_F32_ROTATE) {
bitoffset += 96;
}
if (flags & ANIMFIELD_CAMERA) {
bitoffset += *ptr;
ptr += 5;
}
if (flags & ANIMFIELD_F32_SCALE) {
bitoffset += 96;
}
}
readbitlen = ptr[3];
inttranslate[0] = anim_read_signed_short(framebytes, readbitlen, bitoffset) + ptr[1] * 256 + ptr[2];
bitoffset += readbitlen;
readbitlen = ptr[6];
inttranslate[1] = anim_read_signed_short(framebytes, readbitlen, bitoffset) + ptr[4] * 256 + ptr[5];
bitoffset += readbitlen;
readbitlen = ptr[9];
inttranslate[2] = anim_read_signed_short(framebytes, readbitlen, bitoffset) + ptr[7] * 256 + ptr[8];
bitoffset += readbitlen;
readbitlen = ptr[12];
result = anim_read_signed_short(framebytes, readbitlen, bitoffset) + ptr[10] * 256 + ptr[11];
if (flip) {
inttranslate[0] = -inttranslate[0];
if (result != 0) {
result = 0x10000 - result;
}
}
}
return result;
}
f32 anim_get_translate_angle(s32 part, bool flip, struct skeleton *skel, s16 animnum, s32 framenum, struct coord *translate, bool arg6)
{
s16 inttranslate[3];
f32 angle = anim_get_pos_angle_as_int(part, flip, skel, animnum, framenum, inttranslate, arg6);
translate->x = inttranslate[0];
translate->y = inttranslate[1];
translate->z = inttranslate[2];
return angle * M_BADTAU / 65536.0f;
}
/**
* Return a camera value (FOV Y or blur frac) for the current frame.
*
* The function assumes the current frame's data has been loaded.
* Its slot is provided by the frameslot argument.
*
* When part = 1, the returned value is the FOV Y.
* When part = 2, the returned value is the blur frac.
*/
f32 anim_get_camera_value(s32 part, s16 animnum, u8 frameslot)
{
u32 stack[2];
u8 *framebytes = g_AnimFrameBytes[frameslot];
u8 *ptr = g_AnimHeaderBytes[g_AnimToHeaderSlot[animnum]];
f32 result = 0;
s32 bitoffset = 0;
s32 i;
u8 *end = ptr + g_Anims[animnum].headerlen;
for (i = 0; i < part && ptr < end; i++) {
u8 flags = ptr[0];
ptr++;
if (flags & ANIMFIELD_08) {
bitoffset += ptr[2] + ptr[5] + ptr[8] + ptr[11];
ptr += 12;
} else if (flags & ANIMFIELD_S16_TRANSLATE) {
bitoffset += ptr[2] + ptr[5] + ptr[8];
ptr += 9;
} else if (flags & ANIMFIELD_S32_TRANSLATE) {
bitoffset += ptr[0] + ptr[5] + ptr[10];
ptr += 15;
}
if (flags & ANIMFIELD_S16_ROTATE) {
bitoffset += ptr[2] + ptr[5] + ptr[8];
ptr += 9;
} else if (flags & ANIMFIELD_F32_ROTATE) {
bitoffset += 0x60;
}
if (flags & ANIMFIELD_CAMERA) {
bitoffset += ptr[0];
ptr += 5;
}
if (flags & ANIMFIELD_F32_SCALE) {
bitoffset += 0x60;
}
}
if (ptr < end) {
u8 flags = ptr[0];
ptr++;
if (flags & ANIMFIELD_CAMERA) {
/**
* In the header:
* ptr[0] = number of bits to read in the frame data
* ptr[1,2,3,4] = base value
*
* The value in the frame data is an adjustment value that is added
* to the base value.
*/
s32 framevalue = anim_read_bits(framebytes, ptr[0], bitoffset);
result = (framevalue + ptr[1] * 0x1000000 + ptr[2] * 0x10000 + ptr[3] * 0x100 + ptr[4]) * 0.001f;
}
}
return result;
}