summaryrefslogtreecommitdiff
path: root/src/code/c_keyframe.c
diff options
context:
space:
mode:
authorTharo <17233964+Thar0@users.noreply.github.com>2024-05-17 13:29:19 +0100
committerGitHub <noreply@github.com>2024-05-17 08:29:19 -0400
commitc609d3dcae48ec2bd261c800f6cf75911a7bd1b2 (patch)
treee2af33178a712f1a008d9c61af4c44a5f5dfdab2 /src/code/c_keyframe.c
parent27aa3f748b838efd6e779655e35c4cd1c38b09e1 (diff)
c_keyframe.c decompiled and documented (#1630)
* 2 non-matching * 1 function left Co-authored-by: engineer124 <47598039+engineer124@users.noreply.github.com> * Fully matching Co-authored-by: engineer124 <47598039+engineer124@users.noreply.github.com> * Documented, has bss reordering issues * Fixes * Apply suggestions * Format * Re-add cast oops * Remove != 0 for override and transform limb draws, format --------- Co-authored-by: engineer124 <47598039+engineer124@users.noreply.github.com> Co-authored-by: Derek Hensley <hensley.derek58@gmail.com>
Diffstat (limited to 'src/code/c_keyframe.c')
-rw-r--r--src/code/c_keyframe.c1329
1 files changed, 1287 insertions, 42 deletions
diff --git a/src/code/c_keyframe.c b/src/code/c_keyframe.c
index 5914eec77..1f148b75c 100644
--- a/src/code/c_keyframe.c
+++ b/src/code/c_keyframe.c
@@ -1,85 +1,1330 @@
+/**
+ * @file c_keyframe.c
+ *
+ * This file implements a skeletal animation system supporting all of scale, rotation and translation on all joints. It
+ * uses keyframe data and interpolates intermediate values via cubic Hermite splines.
+ */
#include "global.h"
+#include "libc64/fixed_point.h"
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801830A0.s")
+#define FMOD(x, mod) ((x) - ((s32)((x) * (1.0f / (mod))) * (f32)(mod)))
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801830C8.s")
+/**
+ * @note Original name: cKF_FrameControl_zeroClera
+ */
+void FrameCtrl_Reset(FrameControl* frameCtrl) {
+ frameCtrl->frameCount = 0.0f;
+ frameCtrl->curTime = 0.0f;
+ frameCtrl->speed = 0.0f;
+ frameCtrl->end = 0.0f;
+ frameCtrl->start = 0.0f;
+ frameCtrl->animMode = KEYFRAME_ANIM_ONCE;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801830E8.s")
+/**
+ * @note Original name: cKF_FrameControl_ct
+ */
+void FrameCtrl_Init(FrameControl* frameCtrl) {
+ FrameCtrl_Reset(frameCtrl);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183148.s")
+/**
+ * @note Original name: cKF_FrameControl_setFrame
+ */
+void FrameCtrl_SetProperties(FrameControl* frameCtrl, f32 startTime, f32 endTime, f32 frameCount, f32 t, f32 speed,
+ s32 animMode) {
+ frameCtrl->start = startTime;
+ frameCtrl->end = (endTime < 1.0f) ? frameCount : endTime;
+ frameCtrl->frameCount = frameCount;
+ frameCtrl->speed = speed;
+ frameCtrl->curTime = t;
+ frameCtrl->animMode = animMode;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183224.s")
+/**
+ * @note Original name: cKF_FrameControl_passCheck
+ */
+s32 FrameCtrl_PassCheck(FrameControl* frameCtrl, f32 t, f32* remainingTime) {
+ f32 curTime;
+ f32 speed;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801832B0.s")
+ *remainingTime = 0.0f;
+ curTime = frameCtrl->curTime;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_8018332C.s")
+ if (t == curTime) {
+ return false;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_8018340C.s")
+ speed = ((frameCtrl->start < frameCtrl->end) ? frameCtrl->speed : -frameCtrl->speed) * (30.0f / 20.0f);
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183430.s")
+ if (((speed >= 0.0f) && (curTime < t) && (t <= curTime + speed)) ||
+ ((speed < 0.0f) && (t < curTime) && (curTime + speed <= t))) {
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_8018349C.s")
+ *remainingTime = curTime + speed - t;
+ return true;
+ }
+ return false;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801834A8.s")
+/**
+ * Updates a FrameControl structure whose mode is KEYFRAME_ANIM_ONCE
+ *
+ * @note Original name: cKF_FrameControl_stop_proc
+ */
+s32 FrameCtrl_UpdateOnce(FrameControl* frameCtrl) {
+ f32 remainingTime;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183510.s")
+ if (frameCtrl->curTime == frameCtrl->end) {
+ // If the current time is at the end time, the animation is done.
+ return KEYFRAME_DONE_ONCE;
+ }
+ if (FrameCtrl_PassCheck(frameCtrl, frameCtrl->end, &remainingTime)) {
+ frameCtrl->curTime = frameCtrl->end;
+ return KEYFRAME_DONE_ONCE;
+ }
+ if (FrameCtrl_PassCheck(frameCtrl, frameCtrl->start, &remainingTime)) {
+ frameCtrl->curTime = frameCtrl->end;
+ return KEYFRAME_DONE_ONCE;
+ }
+ return KEYFRAME_NOT_DONE;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183580.s")
+/**
+ * Updates a FrameControl structure whose mode is KEYFRAME_ANIM_LOOP
+ *
+ * @note Original name: cKF_FrameControl_repeat_proc
+ */
+s32 FrameCtrl_UpdateLoop(FrameControl* frameCtrl) {
+ f32 remainingTime;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801835EC.s")
+ if (FrameCtrl_PassCheck(frameCtrl, frameCtrl->end, &remainingTime)) {
+ frameCtrl->curTime = frameCtrl->start + remainingTime;
+ return KEYFRAME_DONE_LOOP;
+ }
+ if (FrameCtrl_PassCheck(frameCtrl, frameCtrl->start, &remainingTime)) {
+ frameCtrl->curTime = frameCtrl->end + remainingTime;
+ return KEYFRAME_DONE_LOOP;
+ }
+ return KEYFRAME_NOT_DONE;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183658.s")
+/**
+ * Check if the animation has finished playing and update the animation frame number.
+ *
+ * @note Original name: cKF_FrameControl_play
+ */
+s32 FrameCtrl_Update(FrameControl* frameCtrl) {
+ s32 result;
+ f32 speed;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801836CC.s")
+ // Check if the animation is done, possibly updating curTime
+ if (frameCtrl->animMode == KEYFRAME_ANIM_ONCE) {
+ result = FrameCtrl_UpdateOnce(frameCtrl);
+ } else {
+ result = FrameCtrl_UpdateLoop(frameCtrl);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_8018373C.s")
+ if (result == KEYFRAME_NOT_DONE) {
+ // Animation is not done, step curTime by (speed * (30.0f / 20.0f)), adjusting the sign if the animation is
+ // playing in reverse (end <= start)
+ speed = (frameCtrl->start < frameCtrl->end) ? frameCtrl->speed : -frameCtrl->speed;
+ frameCtrl->curTime = frameCtrl->curTime + speed * (30.0f / 20.0f);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801837CC.s")
+ // Adjust time for looping
+ if (frameCtrl->curTime < 1.0f) {
+ // Wrap from the start to the end of the animation
+ frameCtrl->curTime = (frameCtrl->curTime - 1.0f) + frameCtrl->frameCount;
+ } else if (frameCtrl->frameCount < frameCtrl->curTime) {
+ // Wrap from the end to the start of the animation
+ frameCtrl->curTime = (frameCtrl->curTime - frameCtrl->frameCount) + 1.0f;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183808.s")
+ return result;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183880.s")
+/**
+ * @note Original name unknown
+ */
+void Keyframe_ResetFlex(KFSkelAnimeFlex* kfSkelAnime) {
+ kfSkelAnime->skeleton = NULL;
+ kfSkelAnime->animation = NULL;
+ kfSkelAnime->jointTable = NULL;
+ kfSkelAnime->transformCallbacks = NULL;
+ kfSkelAnime->morphTable = NULL;
+ kfSkelAnime->morphFrames = 0.0f;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183A3C.s")
+/**
+ * Initializes a flex-type keyframe skeleton. The initial animation type is KEYFRAME_ANIM_ONCE.
+ *
+ * @param skeleton Skeleton to animate
+ * @param animation Initial animation to use
+ * @param jointTable Joint table to store limb transformations. Should have enough space to store a root translation
+ * plus a limb rotation for all limbs in the skeleton.
+ * @param morphTable Joint table to store morph interpolation values. Should have enough space to store a root
+ * translation plus a limb rotation for all limbs in the skeleton.
+ * @param transformCallbacks Array of limb transformation callbacks that will be called when drawing a particular limb.
+ * The limb data contains the index to select which callback to run.
+ *
+ * @note Original name unknown
+ */
+void Keyframe_InitFlex(KFSkelAnimeFlex* kfSkelAnime, KeyFrameFlexSkeleton* skeleton, KeyFrameAnimation* animation,
+ Vec3s* jointTable, Vec3s* morphTable, KeyframeTransformCallback* transformCallbacks) {
+ Keyframe_ResetFlex(kfSkelAnime);
+ FrameCtrl_Init(&kfSkelAnime->frameCtrl);
+ kfSkelAnime->skeleton = Lib_SegmentedToVirtual(skeleton);
+ kfSkelAnime->animation = Lib_SegmentedToVirtual(animation);
+ kfSkelAnime->jointTable = jointTable;
+ kfSkelAnime->morphTable = morphTable;
+ kfSkelAnime->transformCallbacks = transformCallbacks;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183B08.s")
+/**
+ * Destroys a flex-type keyframe skeleton.
+ *
+ * @note Original name unknown
+ */
+void Keyframe_DestroyFlex(KFSkelAnimeFlex* kfSkelAnime) {
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183B68.s")
+void Keyframe_FlexChangeAnim(KFSkelAnimeFlex* kfSkelAnime, KeyFrameFlexSkeleton* skeleton, KeyFrameAnimation* animation,
+ f32 startTime, f32 endTime, f32 t, f32 speed, f32 morphFrames, s32 animMode);
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80183DE0.s")
+/**
+ * Immediately changes to an animation that plays once from start to end at the default speed.
+ *
+ * @param animation Animation data to switch to
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexPlayOnce(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation) {
+ Keyframe_FlexChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, 1.0f, 0.0f,
+ KEYFRAME_ANIM_ONCE);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_8018410C.s")
+/**
+ * Immediately changes to an animation that plays once from start to end at the specified speed.
+ *
+ * @param animation Animation data to switch to
+ * @param speed Playback speed
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexPlayOnceSetSpeed(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation, f32 speed) {
+ Keyframe_FlexChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, speed, 0.0f,
+ KEYFRAME_ANIM_ONCE);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_8018450C.s")
+/**
+ * Smoothly transitions to an animation that plays once from start to end at the default speed, specifying the number of
+ * frames for the transition.
+ *
+ * @param animation Animation data to switch to
+ * @param morphFrames Number of frames to take to transition from the previous pose to the new animation. Positive morph
+ * frames morph from the current pose to the start pose of the new animation, then start the new
+ * animation. Negative morph frames start the new animation immediately, modified by the pose
+ * immediately before the animation change.
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexMorphToPlayOnce(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation, f32 morphFrames) {
+ Keyframe_FlexChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, 1.0f,
+ morphFrames, KEYFRAME_ANIM_ONCE);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801845A4.s")
+/**
+ * Immediately changes to an animation that loops over start to end at the default speed.
+ *
+ * @param animation Animation data to switch to
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexPlayLoop(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation) {
+ Keyframe_FlexChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, 1.0f, 0.0f,
+ KEYFRAME_ANIM_LOOP);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801845C8.s")
+/**
+ * Immediately changes to an animation that loops over start to end at the specified speed.
+ *
+ * @param animation Animation data to switch to
+ * @param speed Playback speed
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexPlayLoopSetSpeed(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation, f32 speed) {
+ Keyframe_FlexChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, speed, 0.0f,
+ KEYFRAME_ANIM_LOOP);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_8018462C.s")
+/**
+ * Smoothly transitions to an animation that loops over start to end at the default speed, specifying the number of
+ * frames for the transition.
+ *
+ * @param animation Animation data to switch to
+ * @param morphFrames Number of frames to take to transition from the previous pose to the new animation. Positive morph
+ * frames morph from the current pose to the start pose of the new animation, then start the new
+ * animation. Negative morph frames start the new animation immediately, modified by the pose
+ * immediately before the animation change.
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexMorphToPlayLoop(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation, f32 morphFrames) {
+ Keyframe_FlexChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, 1.0f,
+ morphFrames, KEYFRAME_ANIM_LOOP);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80184638.s")
+/**
+ * General way to set a new animation for flex-type skeletons, allowing choice of playback speed, start/end loop points,
+ * start time, play mode, and number of transition frames.
+ *
+ * Time parameters are valid from 0 to the last frame of the animation.
+ *
+ * @param skeleton Skeleton that will be animated
+ * @param animation Animation data to switch to
+ * @param startTime Loop start time
+ * @param endTime Loop end time, 0 indicates to use the animation length
+ * @param t Playback start time
+ * @param speed Playback speed
+ * @param morphFrames Number of frames to take to transition from the previous pose to the new animation. Positive morph
+ * frames morph from the current pose to the start pose of the new animation, then start the new
+ * animation. Negative morph frames start the new animation immediately, modified by the pose
+ * immediately before the animation change.
+ * @param animMode Animation play mode, see KeyFrameAnimMode enum
+ *
+ * @see KeyFrameAnimMode
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexChangeAnim(KFSkelAnimeFlex* kfSkelAnime, KeyFrameFlexSkeleton* skeleton, KeyFrameAnimation* animation,
+ f32 startTime, f32 endTime, f32 t, f32 speed, f32 morphFrames, s32 animMode) {
+ kfSkelAnime->morphFrames = morphFrames;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801846AC.s")
+ if (kfSkelAnime->skeleton != skeleton) {
+ kfSkelAnime->skeleton = Lib_SegmentedToVirtual(skeleton);
+ }
+ kfSkelAnime->animation = Lib_SegmentedToVirtual(animation);
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80184728.s")
+ FrameCtrl_SetProperties(&kfSkelAnime->frameCtrl, startTime, endTime, kfSkelAnime->animation->frameCount, t, speed,
+ animMode);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801847A0.s")
+/**
+ * Switches to a new animation without changing any of the playback parameters.
+ *
+ * @param animation The animation to switch to
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexChangeAnimQuick(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation) {
+ kfSkelAnime->animation = Lib_SegmentedToVirtual(animation);
+ kfSkelAnime->frameCtrl.frameCount = kfSkelAnime->animation->frameCount;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80184818.s")
+/**
+ * Compute a value on the cubic Hermite spline x(t) at a time `t` in the unit interval [0, 1]
+ *
+ * @param t Time parameter at which to sample the curve
+ * @param delta Scales the rates of change of the curve endpoints v0 and v1
+ * @param x0 Value on the curve at t=0
+ * @param x1 Value on the curve at t=1
+ * @param v0 Rate of change of the curve at t=0
+ * @param v1 Rate of change of the curve at t=1
+ *
+ * @note Original name: cKF_HermitCalc
+ */
+f32 Keyframe_Interpolate(f32 t, f32 delta, f32 x0, f32 x1, f32 v0, f32 v1) {
+ f32 px1 = 3.0f * SQ(t) - 2.0f * CB(t);
+ f32 px0 = 1.0f - px1;
+ f32 pv0 = CB(t) - 2.0f * SQ(t) + t;
+ f32 pv1 = CB(t) - SQ(t);
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80184898.s")
+ return px0 * x0 + px1 * x1 + (pv0 * v0 + pv1 * v1) * delta;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80184914.s")
+/**
+ * Computes an output value at time `t` based on interpolation of the provided keyframes.
+ * Interpolation between keyframes is performed via cubic Hermite splines.
+ *
+ * @param kfStart Index of the first keyframe to consider
+ * @param kfNum Number of keyframes following the first keyframe to consider
+ * @param keyFrames Array of all keyframes
+ * @param t Time at which to sample the interpolated curve
+ *
+ * @return The interpolated value
+ *
+ * @note Original name: cKF_KeyCalc
+ */
+s16 Keyframe_KeyCalc(s16 kfStart, s16 kfNum, KeyFrame* keyFrames, f32 t) {
+ KeyFrame* keyFramesOffset = &keyFrames[kfStart];
+ f32 delta;
+ s16 kf1;
+ s16 kf2;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801849A0.s")
+ if (t <= keyFramesOffset->frame) {
+ return keyFramesOffset->value;
+ }
+ if (keyFramesOffset[kfNum - 1].frame <= t) {
+ return keyFramesOffset[kfNum - 1].value;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801849DC.s")
+ kf1 = 0;
+ kf2 = 1;
+ while (true) {
+ // Search for the keyframes kf1 and kf2 such that kf1.frame <= t < kf2.frame
+ if (t < keyFramesOffset[kf2].frame) {
+ delta = keyFramesOffset[kf2].frame - keyFramesOffset[kf1].frame;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80184C48.s")
+ if (!IS_ZERO(delta)) {
+ // Between two keyframes, interpolate a value and round to nearest integer
+ return nearbyint(Keyframe_Interpolate((t - keyFramesOffset[kf1].frame) / delta, delta * (1.0f / 30),
+ keyFramesOffset[kf1].value, keyFramesOffset[kf2].value,
+ keyFramesOffset[kf1].velocity, keyFramesOffset[kf2].velocity));
+ } else {
+ // Close enough to a keyframe, take the specified value with no interpolation
+ return keyFramesOffset[kf1].value;
+ }
+ }
+ kf1++;
+ kf2++;
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801850A0.s")
+/**
+ * Morph interpolator for rotation.
+ *
+ * Linearly interpolates between `rot1` and `rot2` with weight `t`, choosing either signed angles or unsigned angles
+ * based on whichever choice has the smaller distance between the two.
+ *
+ * @note Original name: cKF_SkeletonInfo_subRotInterpolation
+ */
+void Keyframe_MorphInterpolateRotation(f32 t, s16* out, s16 rot1, s16 rot2) {
+ u16 urot1 = rot1;
+ s32 pad;
+ u16 urot2 = rot2;
+ f32 rot1f = rot1;
+ f32 signedDiff = rot2 - rot1f;
+ f32 urot1f = urot1;
+ f32 unsignedDiff = urot2 - urot1f;
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_801853C8.s")
+ if (fabsf(signedDiff) < fabsf(unsignedDiff)) {
+ *out = rot1f + signedDiff * t;
+ } else {
+ *out = urot1f + unsignedDiff * t;
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/code/c_keyframe/func_80185460.s")
+/**
+ * Morph interpolator for translation and scale.
+ *
+ * Linearly interpolates between `jointData` and `morphData` with weight `t`, storing the result back into `jointData`.
+ *
+ * @note Original name: cKF_SkeletonInfo_morphST
+ */
+void Keyframe_MorphInterpolateLinear(s16* jointData, s16* morphData, f32 t) {
+ s32 i;
+
+ for (i = 0; i < 3; i++) {
+ if (*jointData != *morphData) {
+ f32 f1 = *jointData;
+ f32 f2 = *morphData;
+ *jointData = f1 + (f2 - f1) * t;
+ }
+ jointData++;
+ morphData++;
+ }
+}
+
+/**
+ * Apply morph interpolation for the provided skeleton. Morph interpolation seeks to provide interpolation between
+ * a previous animation and a new animation over a fixed period of time (morphFrames)
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexMorphInterpolation(KFSkelAnimeFlex* kfSkelAnime) {
+ Vec3s* jointTable = kfSkelAnime->jointTable;
+ Vec3s* morphTable = kfSkelAnime->morphTable;
+ f32 t = 1.0f / fabsf(kfSkelAnime->morphFrames);
+ s32 limbIndex;
+
+ for (limbIndex = 0; limbIndex < kfSkelAnime->skeleton->limbCount; limbIndex++) {
+ Vec3s frameRot;
+ Vec3s morphRot;
+
+ // Interpolate scale
+ Keyframe_MorphInterpolateLinear((s16*)jointTable, (s16*)morphTable, t);
+ jointTable++;
+ morphTable++;
+
+ // Read rotation
+ frameRot.x = jointTable->x;
+ frameRot.y = jointTable->y;
+ frameRot.z = jointTable->z;
+
+ morphRot.x = morphTable->x;
+ morphRot.y = morphTable->y;
+ morphRot.z = morphTable->z;
+
+ // Interpolate rotation
+ if (frameRot.x != morphRot.x || frameRot.y != morphRot.y || frameRot.z != morphRot.z) {
+ Vec3s frameRotInv;
+ f32 norm1;
+ f32 norm2;
+
+ frameRotInv.x = 0x7FFF + frameRot.x;
+ frameRotInv.y = 0x7FFF - frameRot.y;
+ frameRotInv.z = 0x7FFF + frameRot.z;
+
+ // Compute L1 norms
+ norm1 = fabsf((f32)morphRot.x - frameRot.x) + fabsf((f32)morphRot.y - frameRot.y) +
+ fabsf((f32)morphRot.z - frameRot.z);
+ norm2 = fabsf((f32)morphRot.x - frameRotInv.x) + fabsf((f32)morphRot.y - frameRotInv.y) +
+ fabsf((f32)morphRot.z - frameRotInv.z);
+
+ if (norm1 < norm2) {
+ // frameRot is closer to morphRot than frameRotInv, interpolate between these two
+ Keyframe_MorphInterpolateRotation(t, &jointTable->x, frameRot.x, morphRot.x);
+ Keyframe_MorphInterpolateRotation(t, &jointTable->y, frameRot.y, morphRot.y);
+ Keyframe_MorphInterpolateRotation(t, &jointTable->z, frameRot.z, morphRot.z);
+ } else {
+ // frameRotInv is closer to morphRot than frameRot, interpolate between these two
+ Keyframe_MorphInterpolateRotation(t, &jointTable->x, frameRotInv.x, morphRot.x);
+ Keyframe_MorphInterpolateRotation(t, &jointTable->y, frameRotInv.y, morphRot.y);
+ Keyframe_MorphInterpolateRotation(t, &jointTable->z, frameRotInv.z, morphRot.z);
+ }
+ }
+ morphTable++;
+ jointTable++;
+
+ // Interpolate translation
+ Keyframe_MorphInterpolateLinear((s16*)jointTable, (s16*)morphTable, t);
+ jointTable++;
+ morphTable++;
+ }
+}
+
+/**
+ * Advances the current animation and updates all frame tables for flex-type keyframe skeletons.
+ *
+ * @return s32
+ * KEYFRAME_NOT_DONE : If the animation is still playing
+ * KEYFRAME_DONE_ONCE : If the animation was set to play once and has finished playing
+ * KEYFRAME_DONE_LOOP : If the animation was set to play in a loop and has finished a loop
+ *
+ * @note Original name unknown
+ */
+s32 Keyframe_UpdateFlex(KFSkelAnimeFlex* kfSkelAnime) {
+ s32 limbIndex;
+ s32 pad[2];
+ u16* bitFlags;
+ s16* outputValues;
+ s32 kfn = 0;
+ s32 fixedValueIndex = 0;
+ s32 kfStart = 0;
+ s16* fixedValues;
+ KeyFrame* keyFrames;
+ s16* kfNums;
+ u32 bit;
+ s32 i;
+ s32 j;
+
+ // If there are morph frames to process, use the morph table
+ if (kfSkelAnime->morphFrames != 0.0f) {
+ outputValues = (s16*)kfSkelAnime->morphTable;
+ } else {
+ outputValues = (s16*)kfSkelAnime->jointTable;
+ }
+
+ // Array of preset values to pull from
+ fixedValues = Lib_SegmentedToVirtual(kfSkelAnime->animation->fixedValues);
+
+ // Array of number of keyframes belonging to each limb
+ kfNums = Lib_SegmentedToVirtual(kfSkelAnime->animation->kfNums);
+
+ // Array of keyframes, ordered by frame number
+ keyFrames = Lib_SegmentedToVirtual(kfSkelAnime->animation->keyFrames);
+
+ // The bitFlags array indicates whether a transformation on an axis should interpolate a value (if the bit is set)
+ // or pull from an array of constant values (if the bit is unset) if the transformation on an axis does not change
+ // during the animtion. For the flex-type keyframe skeletons the flags for each limb are contained in 16 bits.
+ // The bitFlags layout for the flex-type keyframe skeletons is the same for all limbs:
+ // [8] : Scale x
+ // [7] : Scale y
+ // [6] : Scale z
+ // [5] : Rotate x
+ // [4] : Rotate y
+ // [3] : Rotate z
+ // [2] : Translate x
+ // [1] : Translate y
+ // [0] : Translate z
+ bitFlags = Lib_SegmentedToVirtual(kfSkelAnime->animation->bitFlags.flex);
+
+ // For each limb
+ for (limbIndex = 0; limbIndex < kfSkelAnime->skeleton->limbCount; limbIndex++) {
+ bit = 1 << (3 * 3 - 1);
+
+ // 3 iter (scale, rotate, translate)
+ for (i = 0; i < 3; i++) {
+ // 3 iter (x, y, z)
+ for (j = 0; j < 3; j++) {
+ if (bitFlags[limbIndex] & bit) {
+ // If the bit is set, interpolate with keyframes
+ *outputValues = Keyframe_KeyCalc(kfStart, kfNums[kfn], keyFrames, kfSkelAnime->frameCtrl.curTime);
+ kfStart += kfNums[kfn];
+ kfn++;
+ } else {
+ // If the bit is not set, pull from preset values
+ *outputValues = fixedValues[fixedValueIndex];
+ fixedValueIndex++;
+ }
+ bit >>= 1;
+
+ if (i == 1) {
+ // For rotations, translate angle value from tenths of a degree to binang
+ *outputValues = DEG_TO_BINANG(FMOD(*outputValues * 0.1f, 360));
+ }
+ outputValues++;
+ }
+ }
+ }
+
+ if (IS_ZERO(kfSkelAnime->morphFrames)) {
+ // No morph, just play the animation
+ return FrameCtrl_Update(&kfSkelAnime->frameCtrl);
+ } else if (kfSkelAnime->morphFrames > 0.0f) {
+ // Morph to first frame before playing the animation proper
+ Keyframe_FlexMorphInterpolation(kfSkelAnime);
+ kfSkelAnime->morphFrames -= 1.0f;
+ if (kfSkelAnime->morphFrames <= 0.0f) {
+ kfSkelAnime->morphFrames = 0.0f;
+ }
+ return KEYFRAME_NOT_DONE;
+ } else {
+ // Play the animation immediately, morphing as it plays
+ Keyframe_FlexMorphInterpolation(kfSkelAnime);
+ kfSkelAnime->morphFrames += 1.0f;
+ if (kfSkelAnime->morphFrames >= 0.0f) {
+ kfSkelAnime->morphFrames = 0.0f;
+ }
+ return FrameCtrl_Update(&kfSkelAnime->frameCtrl);
+ }
+}
+
+/**
+ * Draws the limb specified by `limbIndex` of type `KeyFrameFlexLimb` belonging to a flex-type keyframe skeleton to the
+ * display buffer specified by the limb's drawFlags.
+ *
+ * @param limbIndex Pointer to the index of the limb to draw
+ * @param overrideKeyframeDraw Callback for before submitting the limb to be drawn. The matrix state will not include
+ * the transformation for the current limb.
+ * @param postKeyframeDraw Callback for after submitting the limb to be drawn. The matrix state will include
+ * the transformation for the current limb.
+ * @param arg An arbitrary argument to pass to the callbacks.
+ * @param mtxStack Matrix stack for limb transformations. Should have enough room for one matrix per limb.
+ *
+ * @note Original name unknown
+ */
+void Keyframe_DrawFlexLimb(PlayState* play, KFSkelAnimeFlex* kfSkelAnime, s32* limbIndex,
+ OverrideKeyframeDrawScaled overrideKeyframeDraw, PostKeyframeDrawScaled postKeyframeDraw,
+ void* arg, Mtx** mtxStack) {
+ KeyFrameFlexLimb* limb = Lib_SegmentedToVirtual(kfSkelAnime->skeleton->limbs);
+ s32 i;
+ Gfx* newDList;
+ Gfx* limbDList;
+ u8 drawFlags;
+ Vec3f scale;
+ Vec3s rot;
+ Vec3f pos;
+ Vec3s* jointData;
+
+ OPEN_DISPS(play->state.gfxCtx);
+
+ limb += *limbIndex;
+ jointData = &kfSkelAnime->jointTable[*limbIndex * 3];
+
+ scale.x = jointData->x * 0.01f;
+ scale.y = jointData->y * 0.01f;
+ scale.z = jointData->z * 0.01f;
+
+ jointData++;
+
+ rot.x = jointData->x;
+ rot.y = jointData->y;
+ rot.z = jointData->z;
+
+ jointData++;
+
+ pos.x = jointData->x;
+ pos.y = jointData->y;
+ pos.z = jointData->z;
+
+ Matrix_Push();
+
+ newDList = limbDList = limb->dList;
+ drawFlags = limb->drawFlags;
+
+ if (overrideKeyframeDraw == NULL ||
+ (overrideKeyframeDraw != NULL &&
+ overrideKeyframeDraw(play, kfSkelAnime, *limbIndex, &newDList, &drawFlags, arg, &scale, &rot, &pos))) {
+ if ((kfSkelAnime->transformCallbacks == NULL) || (limb->callbackIndex == KF_CALLBACK_INDEX_NONE) ||
+ (kfSkelAnime->transformCallbacks[limb->callbackIndex] == NULL) ||
+ kfSkelAnime->transformCallbacks[limb->callbackIndex](play, kfSkelAnime, *limbIndex, &newDList, &drawFlags,
+ arg)) {
+
+ Matrix_TranslateRotateZYX(&pos, &rot);
+
+ if (scale.x != 1.0f || scale.y != 1.0f || scale.z != 1.0f) {
+ Matrix_Scale(scale.x, scale.y, scale.z, MTXMODE_APPLY);
+ }
+
+ if (newDList != NULL) {
+ Matrix_ToMtx(*mtxStack);
+
+ if (drawFlags & KEYFRAME_DRAW_XLU) {
+ gSPMatrix(POLY_XLU_DISP++, *mtxStack, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ gSPDisplayList(POLY_XLU_DISP++, newDList);
+ } else {
+ gSPMatrix(POLY_OPA_DISP++, *mtxStack, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ gSPDisplayList(POLY_OPA_DISP++, newDList);
+ }
+
+ (*mtxStack)++;
+ } else if (limbDList != NULL) {
+ gSPMatrix(POLY_OPA_DISP++, *mtxStack, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ Matrix_ToMtx(*mtxStack);
+
+ (*mtxStack)++;
+ }
+ }
+ }
+
+ if (postKeyframeDraw != NULL) {
+ postKeyframeDraw(play, kfSkelAnime, *limbIndex, &newDList, &drawFlags, arg, &scale, &rot, &pos);
+ }
+
+ (*limbIndex)++;
+
+ for (i = 0; i < limb->numChildren; i++) {
+ Keyframe_DrawFlexLimb(play, kfSkelAnime, limbIndex, overrideKeyframeDraw, postKeyframeDraw, arg, mtxStack);
+ }
+
+ Matrix_Pop();
+ CLOSE_DISPS(play->state.gfxCtx);
+}
+
+/**
+ * Draws a flex-type keyframe skeleton in its current pose.
+ *
+ * @param mtxStack Matrix stack for limb transformations. Should have enough room for one matrix per limb.
+ * @param overrideKeyframeDraw Callback for before submitting the limb to be drawn. The matrix state will not include
+ * the transformation for the current limb.
+ * @param postKeyframeDraw Callback for after submitting the limb to be drawn. The matrix state will include
+ * the transformation for the current limb.
+ * @param arg An arbitrary argument to pass to the callbacks.
+ *
+ * @note Original name unknown
+ */
+void Keyframe_DrawFlex(PlayState* play, KFSkelAnimeFlex* kfSkelAnime, Mtx* mtxStack,
+ OverrideKeyframeDrawScaled overrideKeyframeDraw, PostKeyframeDrawScaled postKeyframeDraw,
+ void* arg) {
+ s32 limbIndex;
+
+ if (mtxStack == NULL) {
+ return;
+ }
+
+ OPEN_DISPS(play->state.gfxCtx);
+
+ gSPSegment(POLY_OPA_DISP++, 0x0D, mtxStack);
+ gSPSegment(POLY_XLU_DISP++, 0x0D, mtxStack);
+
+ limbIndex = 0;
+ Keyframe_DrawFlexLimb(play, kfSkelAnime, &limbIndex, overrideKeyframeDraw, postKeyframeDraw, arg, &mtxStack);
+
+ CLOSE_DISPS(play->state.gfxCtx);
+}
+
+/**
+ * @note Original name: cKF_SkeletonInfo_R_zeroClear
+ */
+void Keyframe_ResetStandard(KFSkelAnime* kfSkelAnime) {
+ kfSkelAnime->skeleton = NULL;
+ kfSkelAnime->animation = NULL;
+ kfSkelAnime->jointTable = NULL;
+ kfSkelAnime->morphTable = NULL;
+ kfSkelAnime->rotOffsetsTable = NULL;
+ kfSkelAnime->morphFrames = 0.0f;
+}
+
+/**
+ * Initializes a standard-type keyframe skeleton. The initial animation type is KEYFRAME_ANIM_ONCE.
+ *
+ * @param skeleton Skeleton to animate
+ * @param animation Initial animation to use
+ * @param jointTable Joint table to store limb transformations. Should have enough space to store a root translation
+ * plus a limb rotation for all limbs in the skeleton.
+ * @param morphTable Joint table to store morph interpolation values. Should have enough space to store a root
+ * translation plus a limb rotation for all limbs in the skeleton.
+ *
+ * @note Original name: cKF_SkeletonInfo_R_ct
+ */
+void Keyframe_InitStandard(KFSkelAnime* kfSkelAnime, KeyFrameSkeleton* skeleton, KeyFrameAnimation* animation,
+ Vec3s* jointTable, Vec3s* morphTable) {
+ Keyframe_ResetStandard(kfSkelAnime);
+ FrameCtrl_Init(&kfSkelAnime->frameCtrl);
+ kfSkelAnime->skeleton = Lib_SegmentedToVirtual(skeleton);
+ kfSkelAnime->animation = Lib_SegmentedToVirtual(animation);
+ kfSkelAnime->jointTable = jointTable;
+ kfSkelAnime->morphTable = morphTable;
+}
+
+/**
+ * Destroys a standard-type keyframe skeleton.
+ *
+ * @note Original name: cKF_SkeletonInfo_R_dt
+ */
+void Keyframe_DestroyStandard(KFSkelAnime* kfSkelAnime) {
+}
+
+void Keyframe_StandardChangeAnim(KFSkelAnime* kfSkelAnime, KeyFrameSkeleton* skeleton, KeyFrameAnimation* animation,
+ f32 startTime, f32 endTime, f32 t, f32 speed, f32 morphFrames, s32 animMode,
+ Vec3s* rotOffsetsTable);
+
+/**
+ * Immediately changes to an animation that plays once from start to end at the default speed.
+ *
+ * @param animation Animation data to switch to
+ * @param rotOffsetsTable Table of length `skeleton->limbCount` containing rotations to add to every pose of the
+ * animation.
+ *
+ * @note Original name: cKF_SkeletonInfo_R_init_standard_stop
+ */
+void Keyframe_StandardPlayOnce(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable) {
+ Keyframe_StandardChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, 1.0f, 0.0f,
+ KEYFRAME_ANIM_ONCE, rotOffsetsTable);
+}
+
+/**
+ * Immediately changes to an animation that plays once from start to end at the specified speed.
+ *
+ * @param animation Animation data to switch to
+ * @param rotOffsetsTable Table of length `skeleton->limbCount` containing rotations to add to every pose of the
+ * animation.
+ * @param speed Playback speed
+ *
+ * @note Original name: cKF_SkeletonInfo_R_init_standard_stop_speedset
+ */
+void Keyframe_StandardPlayOnceSetSpeed(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable,
+ f32 speed) {
+ Keyframe_StandardChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, speed, 0.0f,
+ KEYFRAME_ANIM_ONCE, rotOffsetsTable);
+}
+
+/**
+ * Smoothly transitions to an animation that plays once from start to end at the default speed.
+ *
+ * @param animation Animation data to switch to
+ * @param rotOffsetsTable Table of length `skeleton->limbCount` containing rotations to add to every pose of the
+ * animation.
+ * @param morphFrames Number of frames to take to transition from the previous pose to the new animation. Positive morph
+ * frames morph from the current pose to the start pose of the new animation, then start the new
+ * animation. Negative morph frames start the new animation immediately, modified by the pose
+ * immediately before the animation change.
+ *
+ * @note Original name: cKF_SkeletonInfo_R_init_standard_stop_morph
+ */
+void Keyframe_StandardMorphToPlayOnce(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable,
+ f32 morphFrames) {
+ Keyframe_StandardChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, 1.0f,
+ morphFrames, KEYFRAME_ANIM_ONCE, rotOffsetsTable);
+}
+
+/**
+ * Immediately changes to an animation that loops at the default.
+ *
+ * @param animation Animation data to switch to
+ * @param rotOffsetsTable Table of length `skeleton->limbCount` containing rotations to add to every pose of the
+ * animation.
+ *
+ * @note Original name: cKF_SkeletonInfo_R_init_standard_repeat
+ */
+void Keyframe_StandardPlayLoop(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable) {
+ Keyframe_StandardChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, 1.0f, 0.0f,
+ KEYFRAME_ANIM_LOOP, rotOffsetsTable);
+}
+
+/**
+ * Immediately changes to an animation that loops over start to end at the specified speed.
+ *
+ * @param animation Animation data to switch to
+ * @param rotOffsetsTable Table of length `skeleton->limbCount` containing rotations to add to every pose of the
+ * animation.
+ * @param speed Playback speed
+ *
+ * @note Original name: cKF_SkeletonInfo_R_init_standard_repeat_speedset
+ */
+void Keyframe_StandardPlayLoopSetSpeed(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable,
+ f32 speed) {
+ Keyframe_StandardChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, speed, 0.0f,
+ KEYFRAME_ANIM_LOOP, rotOffsetsTable);
+}
+
+/**
+ * Smoothly transitions to an animation that loops over start to end at the default speed, specifying the number of
+ * frames for the transition.
+ *
+ * @param animation Animation data to switch to
+ * @param rotOffsetsTable Table of length `skeleton->limbCount` containing rotations to add to every pose of the
+ * animation.
+ * @param morphFrames Number of frames to take to transition from the previous pose to the new animation. Positive morph
+ * frames morph from the current pose to the start pose of the new animation, then start the new
+ * animation. Negative morph frames start the new animation immediately, modified by the pose
+ * immediately before the animation change.
+ *
+ * @note Original name: cKF_SkeletonInfo_R_init_standard_repeat_morph
+ */
+void Keyframe_StandardMorphToPlayLoop(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable,
+ f32 morphFrames) {
+ Keyframe_StandardChangeAnim(kfSkelAnime, kfSkelAnime->skeleton, animation, 1.0f,
+ ((KeyFrameAnimation*)Lib_SegmentedToVirtual(animation))->frameCount, 1.0f, 1.0f,
+ morphFrames, KEYFRAME_ANIM_LOOP, rotOffsetsTable);
+}
+
+/**
+ * General way to set a new animation for standard-type skeletons, allowing choice of playback speed, start/end loop
+ * points, start time, play mode, and number of transition frames.
+ *
+ * Time parameters are valid from 0 to the last frame of the animation.
+ *
+ * @param skeleton Skeleton that will be animated
+ * @param animation Animation data to switch to
+ * @param startTime Loop start time
+ * @param endTime Loop end time, 0 indicates to use the animation length
+ * @param t Playback start time
+ * @param speed Playback speed
+ * @param morphFrames Number of frames to take to transition from the previous pose to the new animation. Positive morph
+ * frames morph from the current pose to the start pose of the new animation, then start the new
+ * animation. Negative morph frames start the new animation immediately, modified by the pose
+ * immediately before the animation change.
+ * @param animMode Animation play mode, see KeyFrameAnimMode enum
+ * @param rotOffsetsTable Table of length `skeleton->limbCount` containing rotations to add to every pose of the
+ * animation.
+ *
+ * @see KeyFrameAnimMode
+ *
+ * @note Original name: cKF_SkeletonInfo_R_init
+ */
+void Keyframe_StandardChangeAnim(KFSkelAnime* kfSkelAnime, KeyFrameSkeleton* skeleton, KeyFrameAnimation* animation,
+ f32 startTime, f32 endTime, f32 t, f32 speed, f32 morphFrames, s32 animMode,
+ Vec3s* rotOffsetsTable) {
+ kfSkelAnime->morphFrames = morphFrames;
+ kfSkelAnime->skeleton = Lib_SegmentedToVirtual(skeleton);
+ kfSkelAnime->animation = Lib_SegmentedToVirtual(animation);
+
+ FrameCtrl_SetProperties(&kfSkelAnime->frameCtrl, startTime, endTime, kfSkelAnime->animation->frameCount, t, speed,
+ animMode);
+ kfSkelAnime->rotOffsetsTable = rotOffsetsTable;
+}
+
+/**
+ * Switches to a new animation without changing any of the playback parameters.
+ *
+ * @param animation The animation to switch to
+ *
+ * @note Original name: cKF_SkeletonInfo_R_setAnim
+ */
+void Keyframe_StandardChangeAnimQuick(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation) {
+ kfSkelAnime->animation = Lib_SegmentedToVirtual(animation);
+ kfSkelAnime->frameCtrl.frameCount = kfSkelAnime->animation->frameCount;
+}
+
+/**
+ * Apply morph interpolation for the provided skeleton. Morph interpolation seeks to provide interpolation between
+ * a previous animation and a new animation over a fixed period of time (morphFrames)
+ *
+ * @note Original name: cKF_SkeletonInfo_R_morphJoint
+ */
+void Keyframe_StandardMorphInterpolation(KFSkelAnime* kfSkelAnime) {
+ Vec3s* jointTable = kfSkelAnime->jointTable;
+ Vec3s* morphTable = kfSkelAnime->morphTable;
+ f32 t = 1.0f / fabsf(kfSkelAnime->morphFrames);
+ s32 limbIndex;
+
+ // Interpolate root translation
+ Keyframe_MorphInterpolateLinear((s16*)jointTable, (s16*)morphTable, t);
+ jointTable++;
+ morphTable++;
+
+ for (limbIndex = 0; limbIndex < kfSkelAnime->skeleton->limbCount; limbIndex++) {
+ Vec3s frameRot;
+ Vec3s morphRot;
+
+ frameRot.x = jointTable->x;
+ frameRot.y = jointTable->y;
+ frameRot.z = jointTable->z;
+
+ morphRot.x = morphTable->x;
+ morphRot.y = morphTable->y;
+ morphRot.z = morphTable->z;
+
+ // Interpolate rotation
+ if (frameRot.x != morphRot.x || frameRot.y != morphRot.y || frameRot.z != morphRot.z) {
+ Vec3s frameRotInv;
+ f32 norm1;
+ f32 norm2;
+
+ frameRotInv.x = 0x7FFF + frameRot.x;
+ frameRotInv.y = 0x7FFF - frameRot.y;
+ frameRotInv.z = 0x7FFF + frameRot.z;
+
+ // Compute L1 norms
+ norm1 = fabsf((f32)morphRot.x - frameRot.x) + fabsf((f32)morphRot.y - frameRot.y) +
+ fabsf((f32)morphRot.z - frameRot.z);
+ norm2 = fabsf((f32)morphRot.x - frameRotInv.x) + fabsf((f32)morphRot.y - frameRotInv.y) +
+ fabsf((f32)morphRot.z - frameRotInv.z);
+
+ if (norm1 < norm2) {
+ // frameRot is closer to morphRot than frameRotInv, interpolate between these two
+ Keyframe_MorphInterpolateRotation(t, &jointTable->x, frameRot.x, morphRot.x);
+ Keyframe_MorphInterpolateRotation(t, &jointTable->y, frameRot.y, morphRot.y);
+ Keyframe_MorphInterpolateRotation(t, &jointTable->z, frameRot.z, morphRot.z);
+ } else {
+ // frameRotInv is closer to morphRot than frameRot, interpolate between these two
+ Keyframe_MorphInterpolateRotation(t, &jointTable->x, frameRotInv.x, morphRot.x);
+ Keyframe_MorphInterpolateRotation(t, &jointTable->y, frameRotInv.y, morphRot.y);
+ Keyframe_MorphInterpolateRotation(t, &jointTable->z, frameRotInv.z, morphRot.z);
+ }
+ }
+ morphTable++;
+ jointTable++;
+ }
+}
+
+/**
+ * Advances the current animation and updates all frame tables for standard-type keyframe skeletons.
+ *
+ * @return s32
+ * KEYFRAME_NOT_DONE : If the animation is still playing
+ * KEYFRAME_DONE_ONCE : If the animation was set to play once and has finished playing
+ * KEYFRAME_DONE_LOOP : If the animation was set to play in a loop and has finished a loop
+ *
+ * @note Original name: cKF_SkeletonInfo_R_play
+ */
+s32 Keyframe_UpdateStandard(KFSkelAnime* kfSkelAnime) {
+ s32 limbIndex;
+ u32 bit;
+ u8* bitFlags;
+ s32 i;
+ s32 kfn = 0;
+ s32 fixedValueIndex = 0;
+ s32 kfStart = 0;
+ s16* fixedValues;
+ KeyFrame* keyFrames;
+ s16* kfNums;
+ s16* outputValues;
+
+ // Choose which array to update, if currently morphing update the morph table else update the joint table
+ if (kfSkelAnime->morphFrames != 0.0f) {
+ outputValues = (s16*)kfSkelAnime->morphTable;
+ } else {
+ outputValues = (s16*)kfSkelAnime->jointTable;
+ }
+
+ fixedValues = Lib_SegmentedToVirtual(kfSkelAnime->animation->fixedValues);
+ kfNums = Lib_SegmentedToVirtual(kfSkelAnime->animation->kfNums);
+ keyFrames = Lib_SegmentedToVirtual(kfSkelAnime->animation->keyFrames);
+
+ // The bitFlags array indicates whether a transformation on an axis should interpolate a value (if the bit is set)
+ // or pull from an array of constant values (if the bit is unset) if the transformation on an axis does not change
+ // during the animtion. For the standard-type keyframe skeletons the flags for each limb are contained in 8 bits.
+ // The bitFlags layout for the standard-type keyframe skeletons is different for the root limb, which may have a
+ // translation:
+ // [5] = tx
+ // [4] = ty
+ // [3] = tz
+ // [2] = rx
+ // [1] = ry
+ // [0] = rz
+ // Otherwise, the layout only contains rotations:
+ // [2] = rx
+ // [1] = ry
+ // [0] = rz
+ bitFlags = Lib_SegmentedToVirtual(kfSkelAnime->animation->bitFlags.standard);
+
+ // Interpolate translation for the root limb
+
+ bit = 1 << (3 * 2 - 1);
+
+ // 3 iter (x, y, z)
+ for (i = 0; i < 3; i++) {
+ if (bitFlags[0] & bit) {
+ *outputValues = Keyframe_KeyCalc(kfStart, kfNums[kfn], keyFrames, kfSkelAnime->frameCtrl.curTime);
+ kfStart += kfNums[kfn++];
+ } else {
+ *outputValues = fixedValues[fixedValueIndex++];
+ }
+ bit >>= 1;
+ outputValues++;
+ }
+
+ // Update rotation for all limbs
+
+ for (limbIndex = 0; limbIndex < kfSkelAnime->skeleton->limbCount; limbIndex++) {
+ bit = 1 << (3 - 1);
+
+ // 3 iter (x, y, z)
+ for (i = 0; i < 3; i++) {
+ s32 pad;
+
+ if (bitFlags[limbIndex] & bit) {
+ *outputValues = Keyframe_KeyCalc(kfStart, kfNums[kfn], keyFrames, kfSkelAnime->frameCtrl.curTime);
+ kfStart += kfNums[kfn++];
+ } else {
+ *outputValues = fixedValues[fixedValueIndex++];
+ }
+ bit >>= 1;
+
+ // Translate angle value from tenths of a degree to binang
+ *outputValues = DEG_TO_BINANG(FMOD(*outputValues * 0.1f, 360));
+ outputValues++;
+ }
+ }
+
+ if (kfSkelAnime->rotOffsetsTable != NULL) {
+ Vec3s* table;
+
+ if (kfSkelAnime->morphFrames != 0.0f) {
+ table = kfSkelAnime->morphTable;
+ } else {
+ table = kfSkelAnime->jointTable;
+ }
+ table++; // Skip root translation
+
+ // Add all offsets to rotations
+ for (limbIndex = 0; limbIndex < kfSkelAnime->skeleton->limbCount; limbIndex++, table++) {
+ table->x = table->x + kfSkelAnime->rotOffsetsTable[limbIndex].x;
+ table->y = table->y + kfSkelAnime->rotOffsetsTable[limbIndex].y;
+ table->z = table->z + kfSkelAnime->rotOffsetsTable[limbIndex].z;
+ }
+ }
+
+ if (IS_ZERO(kfSkelAnime->morphFrames)) {
+ // No morph, just play the animation
+ return FrameCtrl_Update(&kfSkelAnime->frameCtrl);
+ } else if (kfSkelAnime->morphFrames > 0.0f) {
+ // Morph to first frame before playing the animation proper
+ Keyframe_StandardMorphInterpolation(kfSkelAnime);
+ kfSkelAnime->morphFrames -= 1.0f;
+ if (kfSkelAnime->morphFrames <= 0.0f) {
+ kfSkelAnime->morphFrames = 0.0f;
+ }
+ return KEYFRAME_NOT_DONE;
+ } else {
+ // Play the animation immediately, morphing as it plays
+ Keyframe_StandardMorphInterpolation(kfSkelAnime);
+ kfSkelAnime->morphFrames += 1.0f;
+ if (kfSkelAnime->morphFrames >= 0.0f) {
+ kfSkelAnime->morphFrames = 0.0f;
+ }
+ return FrameCtrl_Update(&kfSkelAnime->frameCtrl);
+ }
+}
+
+/**
+ * Draws the limb specified by `limbIndex` of type `KeyFrameStandardLimb` belonging to a standard-type keyframe skeleton
+ * to the display buffer specified by the limb's drawFlags.
+ *
+ * @param limbIndex Pointer to the index of the limb to draw
+ * @param overrideKeyframeDraw Callback for before submitting the limb to be drawn. The matrix state will not include
+ * the transformation for the current limb.
+ * @param postKeyframeDraw Callback for after submitting the limb to be drawn. The matrix state will include
+ * the transformation for the current limb.
+ * @param arg An arbitrary argument to pass to the callbacks.
+ * @param mtxStack Matrix stack for limb transformations. Should have enough room for one matrix per limb.
+ *
+ * @note Original name: cKF_Si3_draw_SV_R_child
+ */
+void Keyframe_DrawStandardLimb(PlayState* play, KFSkelAnime* kfSkelAnime, s32* limbIndex,
+ OverrideKeyframeDraw overrideKeyframeDraw, PostKeyframeDraw postKeyframeDraw, void* arg,
+ Mtx** mtxStack) {
+ KeyFrameStandardLimb* limb =
+ *limbIndex + (KeyFrameStandardLimb*)Lib_SegmentedToVirtual(kfSkelAnime->skeleton->limbs);
+ s32 i;
+ Gfx* newDList;
+ Gfx* limbDList;
+ u8 drawFlags;
+ Vec3s rot;
+ Vec3s* jointData = &kfSkelAnime->jointTable[*limbIndex];
+ Vec3f pos;
+
+ if (*limbIndex != 0) {
+ pos.x = limb->jointPos.x;
+ pos.y = limb->jointPos.y;
+ pos.z = limb->jointPos.z;
+ } else {
+ pos.x = jointData->x;
+ pos.y = jointData->y;
+ pos.z = jointData->z;
+ }
+
+ jointData++;
+
+ rot.x = jointData->x;
+ rot.y = jointData->y;
+ rot.z = jointData->z;
+
+ OPEN_DISPS(play->state.gfxCtx);
+
+ Matrix_Push();
+
+ newDList = limbDList = limb->dList;
+ drawFlags = limb->drawFlags;
+
+ if (overrideKeyframeDraw == NULL ||
+ (overrideKeyframeDraw != NULL &&
+ overrideKeyframeDraw(play, kfSkelAnime, *limbIndex, &newDList, &drawFlags, arg, &rot, &pos))) {
+
+ Matrix_TranslateRotateZYX(&pos, &rot);
+
+ if (newDList != NULL) {
+ Matrix_ToMtx(*mtxStack);
+
+ if (drawFlags & KEYFRAME_DRAW_XLU) {
+ gSPMatrix(POLY_XLU_DISP++, *mtxStack, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ gSPDisplayList(POLY_XLU_DISP++, newDList);
+ } else {
+ gSPMatrix(POLY_OPA_DISP++, *mtxStack, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ gSPDisplayList(POLY_OPA_DISP++, newDList);
+ }
+ (*mtxStack)++;
+ } else if (limbDList != NULL) {
+ Matrix_ToMtx(*mtxStack);
+
+ gSPMatrix(POLY_OPA_DISP++, *mtxStack, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ (*mtxStack)++;
+ }
+ }
+
+ if (postKeyframeDraw != NULL) {
+ postKeyframeDraw(play, kfSkelAnime, *limbIndex, &newDList, &drawFlags, arg, &rot, &pos);
+ }
+
+ (*limbIndex)++;
+
+ for (i = 0; i < limb->numChildren; i++) {
+ Keyframe_DrawStandardLimb(play, kfSkelAnime, limbIndex, overrideKeyframeDraw, postKeyframeDraw, arg, mtxStack);
+ }
+
+ Matrix_Pop();
+
+ CLOSE_DISPS(play->state.gfxCtx);
+}
+
+/**
+ * Draws a standard-type keyframe skeleton in its current pose.
+ *
+ * @param mtxStack Matrix stack for limb transformations. Should have enough room for one matrix per limb.
+ * @param overrideKeyframeDraw Callback for before submitting the limb to be drawn. The matrix state will not include
+ * the transformation for the current limb.
+ * @param postKeyframeDraw Callback for after submitting the limb to be drawn. The matrix state will include
+ * the transformation for the current limb.
+ * @param arg An arbitrary argument to pass to the callbacks.
+ *
+ * @note Original name: cKF_Si3_draw_R_SV
+ */
+void Keyframe_DrawStandard(PlayState* play, KFSkelAnime* kfSkelAnime, Mtx* mtxStack,
+ OverrideKeyframeDraw overrideKeyframeDraw, PostKeyframeDraw postKeyframeDraw, void* arg) {
+ s32 limbIndex;
+
+ if (mtxStack == NULL) {
+ return;
+ }
+
+ OPEN_DISPS(play->state.gfxCtx);
+
+ gSPSegment(POLY_OPA_DISP++, 0x0D, mtxStack);
+ gSPSegment(POLY_XLU_DISP++, 0x0D, mtxStack);
+
+ limbIndex = 0;
+ Keyframe_DrawStandardLimb(play, kfSkelAnime, &limbIndex, overrideKeyframeDraw, postKeyframeDraw, arg, &mtxStack);
+
+ CLOSE_DISPS(play->state.gfxCtx);
+}
+
+/**
+ * Extracts the x,y,z scales for the limb `targetLimbIndex` for the current pose from a flex-type keyframe skeleton.
+ *
+ * The output scale values are quantized, that is they have been multiplied by 100 and rounded to an integer. To get
+ * the scale values in coordinate units, multiply the result by 0.01.
+ *
+ * @param targetLimbIndex The limb index for which to extract the scale for
+ * @param scale Vec3s of the x,y,z scale for the chosen limb
+ *
+ * @note Original name unknown
+ */
+void Keyframe_FlexGetScale(KFSkelAnimeFlex* kfSkelAnime, s32 targetLimbIndex, Vec3s* scale) {
+ s16* kfNums;
+ s32 i;
+ s32 kfn = 0;
+ s32 fixedValueIndex = 0;
+ s32 kfStart = 0;
+ s32 j;
+ u16* bitFlags;
+ s16* scaleArray = (s16*)scale;
+ s16* fixedValues;
+ KeyFrame* keyFrames;
+ s32 limbIndex;
+
+ fixedValues = Lib_SegmentedToVirtual(kfSkelAnime->animation->fixedValues);
+ kfNums = Lib_SegmentedToVirtual(kfSkelAnime->animation->kfNums);
+ keyFrames = Lib_SegmentedToVirtual(kfSkelAnime->animation->keyFrames);
+ bitFlags = Lib_SegmentedToVirtual(kfSkelAnime->animation->bitFlags.flex);
+
+ for (limbIndex = 0; limbIndex < kfSkelAnime->skeleton->limbCount; limbIndex++) {
+ u32 bit = 1 << (3 * 3 - 1);
+
+ // 3 iter (scale, rotation, translation)
+ for (i = 0; i < 3; i++) {
+ if ((limbIndex == targetLimbIndex) && (i == 0)) {
+ // Is the target limb and is scale data, compute and write out scale values for each axis
+ // 3 iter (x, y, z)
+ for (j = 0; j < 3; j++) {
+ if (bitFlags[limbIndex] & bit) {
+ *scaleArray = Keyframe_KeyCalc(kfStart, kfNums[kfn], keyFrames, kfSkelAnime->frameCtrl.curTime);
+ kfStart += kfNums[kfn];
+ kfn++;
+ } else {
+ *scaleArray = fixedValues[fixedValueIndex];
+ fixedValueIndex++;
+ }
+ bit >>= 1;
+ scaleArray++;
+ }
+ } else {
+ // Not the target limb or scale data, step over values
+ // 3 iter (x, y, z)
+ for (j = 0; j < 3; j++) {
+ if (bitFlags[limbIndex] & bit) {
+ kfStart += kfNums[kfn];
+ kfn++;
+ } else {
+ fixedValueIndex++;
+ }
+ bit >>= 1;
+ }
+ }
+ }
+ }
+}