diff --git a/assets/xml/objects/gameplay_keep.xml b/assets/xml/objects/gameplay_keep.xml
index 79940f1cb4..831dff61e7 100644
--- a/assets/xml/objects/gameplay_keep.xml
+++ b/assets/xml/objects/gameplay_keep.xml
@@ -1451,7 +1451,7 @@
-
+
@@ -1483,7 +1483,7 @@
-
+
diff --git a/include/functions.h b/include/functions.h
index a208b48c09..715a385bcf 100644
--- a/include/functions.h
+++ b/include/functions.h
@@ -588,47 +588,6 @@ s32 Math3D_YZInSphere(Sphere16* sphere, f32 y, f32 z);
// void func_8017FD44(void);
void func_80183070(void);
-// void func_801830A0(void);
-// void func_801830C8(void);
-// void func_801830E8(UNK_TYPE1 param_1, UNK_TYPE1 param_2, UNK_TYPE1 param_3, UNK_TYPE1 param_4, UNK_TYPE4 param_5, UNK_TYPE4 param_6, UNK_TYPE4 param_7);
-// void func_80183148(void);
-// void func_80183224(void);
-// void func_801832B0(void);
-// void func_8018332C(void);
-// void func_8018340C(void);
-void func_80183430(SkeletonInfo* skeletonInfo, void* arg1, void* arg2, Vec3s* arg3, Vec3s* arg4, UnkKeyframeCallback* callbacks);
-void func_8018349C(UNK_PTR arg0);
-void func_801834A8(SkeletonInfo* skeletonInfo, void* arg1);
-// void func_80183510(void);
-// void func_80183580(void);
-void func_801835EC(UNK_PTR arg0, UNK_PTR arg1);
-// void func_80183658(void);
-// void func_801836CC(void);
-// void func_8018373C(UNK_TYPE1 param_1, UNK_TYPE1 param_2, UNK_TYPE1 param_3, UNK_TYPE1 param_4, UNK_TYPE4 param_5, UNK_TYPE4 param_6, UNK_TYPE4 param_7, UNK_TYPE4 param_8, UNK_TYPE4 param_9);
-// void func_801837CC(void);
-// void func_80183808(UNK_TYPE1 param_1, UNK_TYPE1 param_2, UNK_TYPE1 param_3, UNK_TYPE1 param_4, UNK_TYPE4 param_5, UNK_TYPE4 param_6);
-// void func_80183880(void);
-// void func_80183A3C(void);
-// void func_80183B08(void);
-// void func_80183B68(void);
-s32 func_80183DE0(SkeletonInfo* skeletonInfo);
-// void func_8018410C(UNK_TYPE1 param_1, UNK_TYPE1 param_2, UNK_TYPE1 param_3, UNK_TYPE1 param_4, UNK_TYPE4 param_5, UNK_TYPE4 param_6, UNK_TYPE4 param_7);
-void func_8018450C(PlayState* play, SkeletonInfo* skeleton, Mtx* mtx, OverrideKeyframeDrawScaled overrideKeyframeDraw, PostKeyframeDrawScaled postKeyframeDraw, Actor* actor);
-// void func_801845A4(void);
-// void func_801845C8(UNK_TYPE1 param_1, UNK_TYPE1 param_2, UNK_TYPE1 param_3, UNK_TYPE1 param_4, UNK_TYPE4 param_5);
-// void func_80184638(void);
-// void func_801846AC(void);
-// void func_80184728(void);
-// void func_801847A0(void);
-// void func_80184818(void);
-// void func_80184898(void);
-// void func_80184914(UNK_TYPE1 param_1, UNK_TYPE1 param_2, UNK_TYPE1 param_3, UNK_TYPE1 param_4, UNK_TYPE4 param_5, UNK_TYPE4 param_6, UNK_TYPE4 param_7, UNK_TYPE4 param_8, UNK_TYPE4 param_9, UNK_TYPE4 param_10);
-// void func_801849A0(void);
-// void func_801849DC(void);
-// void func_80184C48(void);
-// void func_801850A0(UNK_TYPE1 param_1, UNK_TYPE1 param_2, UNK_TYPE1 param_3, UNK_TYPE1 param_4, UNK_TYPE4 param_5, UNK_TYPE4 param_6, UNK_TYPE4 param_7);
-// void func_801853C8(UNK_TYPE1 param_1, UNK_TYPE1 param_2, UNK_TYPE1 param_3, UNK_TYPE1 param_4, UNK_TYPE4 param_5, UNK_TYPE4 param_6);
-// void func_80185460(void);
AudioTask* AudioThread_Update(void);
void AudioThread_QueueCmdF32(u32 opArgs, f32 data);
diff --git a/include/macros.h b/include/macros.h
index 6d74330374..1038838e49 100644
--- a/include/macros.h
+++ b/include/macros.h
@@ -65,6 +65,7 @@
0)
#define SQ(x) ((x) * (x))
+#define CB(x) ((x) * (x) * (x))
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define ABS_ALT(x) ((x) < 0 ? -(x) : (x))
#define DECR(x) ((x) == 0 ? 0 : --(x))
diff --git a/include/z64.h b/include/z64.h
index b4d805ea68..c18c206880 100644
--- a/include/z64.h
+++ b/include/z64.h
@@ -70,6 +70,7 @@
#include "z64rumble.h"
#include "z64transition.h"
#include "z64view.h"
+#include "z64keyframe.h"
#include "regs.h"
diff --git a/include/z64animation.h b/include/z64animation.h
index 4c255a7396..bf38b5da66 100644
--- a/include/z64animation.h
+++ b/include/z64animation.h
@@ -244,75 +244,6 @@ typedef struct AnimationSpeedInfo {
/* 0xC */ f32 morphFrames;
} AnimationSpeedInfo; // size = 0x10
-struct SkeletonInfo;
-
-typedef s32 (*UnkKeyframeCallback)(struct PlayState* play, struct SkeletonInfo* skeletonInfo, s32* arg2, Gfx** dList,
- u8* arg4, void* arg5);
-
-// Keyframe limb?
-typedef struct {
- /* 0x0 */ Gfx* dList;
- /* 0x4 */ u8 unk_4;
- /* 0x5 */ u8 flags;
- /* 0x6 */ Vec3s root;
-} Struct_801BFA14_Arg1_Field4; // size = 0xC
-
-// Other limb type?
-typedef struct {
- /* 0x0 */ Gfx* dList;
- /* 0x4 */ u8 unk_4;
- /* 0x5 */ u8 flags;
- /* 0x6 */ u8 unk_6; // transform limb draw index
-} Struct_801BFA14_Arg1_Field4_2; // size = 0x8
-
-typedef struct {
- /* 0x00 */ u8 limbCount;
- /* 0x01 */ u8 unk_1; // non-zero in object files, number of non-null-dlist limbs?
- /* 0x04 */ union {
- Struct_801BFA14_Arg1_Field4* unk_4; // arrays
- Struct_801BFA14_Arg1_Field4_2* unk_4_2;
- };
- /* 0x08 */ s16* unk_8;
- /* 0x0C */ s16* unk_C;
- /* 0x10 */ char unk_10[0x2];
- /* 0x12 */ s16 unk_12;
-} Struct_801BFA14_Arg1; // size = 0x14
-
-typedef struct {
- /* 0x00 */ u16* unk_0;
- /* 0x04 */ s16* unk_4;
- /* 0x08 */ s16* unk_8;
- /* 0x0C */ s16* unk_C;
- /* 0x10 */ char unk_10[0x2];
- /* 0x12 */ s16 unk_12;
-} SkeletonInfo_1C; // size = 0x14
-
-typedef struct {
- /* 0x00 */ f32 unk_0;
- /* 0x04 */ f32 unk_4;
- /* 0x08 */ f32 unk_8;
- /* 0x0C */ f32 unk_C;
- /* 0x10 */ f32 unk_10;
- /* 0x14 */ s32 unk_14;
-} FrameControl; // size = 0x18
-
-// FlexKeyframeSkeleton ?
-typedef struct SkeletonInfo {
- /* 0x00 */ FrameControl frameCtrl;
- /* 0x18 */ Struct_801BFA14_Arg1* unk_18; // array
- /* 0x1C */ SkeletonInfo_1C* unk_1C;
- /* 0x20 */ UnkKeyframeCallback* unk_20; // pointer to array of functions
- /* 0x24 */ f32 unk_24; // duration? current time?
- /* 0x28 */ Vec3s* frameData; // array of 3 Vec3s
- /* 0x2C */ s16* unk_2C;
-} SkeletonInfo; // size = 0x30
-
-typedef s32 (*OverrideKeyframeDrawScaled)(struct PlayState* play, SkeletonInfo* skeletonInfo, s32 limbIndex, Gfx** dList,
- u8* flags, struct Actor* actor, Vec3f* scale, Vec3s* rot, Vec3f* pos);
-
-typedef s32 (*PostKeyframeDrawScaled)(struct PlayState* play, SkeletonInfo* skeleton, s32 limbIndex, Gfx** dList,
- u8* flags, struct Actor* actor, Vec3f* scale, Vec3s* rot, Vec3f* pos);
-
void SkelAnime_DrawLod(struct PlayState* play, void** skeleton, Vec3s* jointTable, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, struct Actor* actor, s32 lod);
void SkelAnime_DrawFlexLod(struct PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, OverrideLimbDrawFlex overrideLimbDraw, PostLimbDrawFlex postLimbDraw, struct Actor* actor, s32 lod);
void SkelAnime_DrawOpa(struct PlayState* play, void** skeleton, Vec3s* jointTable, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, struct Actor* actor);
diff --git a/include/z64keyframe.h b/include/z64keyframe.h
new file mode 100644
index 0000000000..10d7f85096
--- /dev/null
+++ b/include/z64keyframe.h
@@ -0,0 +1,199 @@
+#ifndef Z64_KEYFRAME_H
+#define Z64_KEYFRAME_H
+
+#include "ultra64.h"
+#include "z64math.h"
+
+struct PlayState;
+
+struct KFSkelAnime;
+struct KFSkelAnimeFlex;
+
+typedef s32 (*OverrideKeyframeDraw)(struct PlayState* play, struct KFSkelAnime* kfSkelAnime, s32 limbIndex,
+ Gfx** dList, u8* flags, void* arg, Vec3s* rot, Vec3f* pos);
+typedef s32 (*PostKeyframeDraw)(struct PlayState* play, struct KFSkelAnime* kfSkelAnime, s32 limbIndex,
+ Gfx** dList, u8* flags, void* arg, Vec3s* rot, Vec3f* pos);
+
+typedef s32 (*OverrideKeyframeDrawScaled)(struct PlayState* play, struct KFSkelAnimeFlex* kfSkelAnime, s32 limbIndex,
+ Gfx** dList, u8* flags, void* arg, Vec3f* scale, Vec3s* rot, Vec3f* pos);
+typedef s32 (*PostKeyframeDrawScaled)(struct PlayState* play, struct KFSkelAnimeFlex* kfSkelAnime, s32 limbIndex,
+ Gfx** dList, u8* flags, void* arg, Vec3f* scale, Vec3s* rot, Vec3f* pos);
+
+typedef s32 (*KeyframeTransformCallback)(struct PlayState* play, struct KFSkelAnimeFlex* kfSkelAnime, s32 limbIndex,
+ Gfx** dList, u8* flags, void* arg);
+
+// These flags are mutually exclusive, if XLU is set OPA is not set and vice-versa.
+#define KEYFRAME_DRAW_OPA (0 << 0)
+#define KEYFRAME_DRAW_XLU (1 << 0)
+
+typedef enum {
+ /* 0 */ KEYFRAME_NOT_DONE,
+ /* 1 */ KEYFRAME_DONE_ONCE,
+ /* 2 */ KEYFRAME_DONE_LOOP
+} KeyFrameDoneType;
+
+#define KF_CALLBACK_INDEX_NONE 0xFF
+
+typedef struct {
+ /* 0x00 */ Gfx* dList; // Display list for this limb
+ /* 0x04 */ u8 numChildren; // Number of children for this limb
+ /* 0x05 */ u8 drawFlags; // KEYFRAME_DRAW_*, determines which render layer the matrix + display list will be appended to
+ /* 0x06 */ Vec3s jointPos; // For the root limb this is the position in model space; for child limbs it is the relative position to the parent
+} KeyFrameStandardLimb; // size = 0xC
+
+typedef struct {
+ /* 0x00 */ u8 limbCount; // Number of limbs in this skeleton
+ /* 0x01 */ u8 dListCount; // Number of limbs with a non-NULL display list, used to know how many matrices to allocate for drawing
+ /* 0x04 */ KeyFrameStandardLimb* limbs; // Pointer to standard limb array
+} KeyFrameSkeleton; // Size = 0x8
+
+typedef struct {
+ /* 0x00 */ Gfx* dList; // Display list for this limb
+ /* 0x04 */ u8 numChildren; // Number of children for this limb
+ /* 0x05 */ u8 drawFlags; // KEYFRAME_DRAW_*, determines which render layer the matrix + display list will be appended to
+ /* 0x06 */ u8 callbackIndex; // Transform callback function index, KF_CALLBACK_INDEX_NONE indicates no callback
+} KeyFrameFlexLimb; // size = 0x8
+
+typedef struct {
+ /* 0x00 */ u8 limbCount; // Number of limbs in this skeleton
+ /* 0x01 */ u8 dListCount; // Number of limbs with a non-NULL display list, used to know how many matrices to allocate for drawing
+ /* 0x04 */ KeyFrameFlexLimb* limbs; // Pointer to flex limb array
+} KeyFrameFlexSkeleton; // Size = 0x8
+
+typedef struct {
+ /* 0x00 */ s16 frame; // Frame number for this keyframe
+ /* 0x02 */ s16 value; // Value (any of translation, rotation, scale)
+ /* 0x04 */ s16 velocity; // The instantaneous rate of change of the value
+} KeyFrame; // Size = 0x6
+
+typedef struct {
+ // Array of bitflags for each limb indicating whether to do keyframe interpolation
+ // or pull from fixed values that do not change throughout the animation.
+ union {
+ // For standard the bit layout in each array element is:
+ // [5] X Translation (root limb only)
+ // [4] Y Translation (root limb only)
+ // [3] Z Translation (root limb only)
+ // [2] X Rotation (all limbs)
+ // [1] Y Rotation (all limbs)
+ // [0] Z Rotation (all limbs)
+ /* 0x00 */ u8* standard;
+ // For flex the bit layout in each array element is:
+ // [8] X Scale
+ // [7] Y Scale
+ // [6] Z Scale
+ // [5] X Rotation
+ // [4] Y Rotation
+ // [3] Z Rotation
+ // [2] X Translation
+ // [1] Y Translation
+ // [0] Z Translation
+ /* 0x00 */ u16* flex;
+ } bitFlags;
+ /* 0x04 */ KeyFrame* keyFrames; // Array of keyframes determining the motion, grouped by limb
+ /* 0x08 */ s16* kfNums; // Array containing how many keyframes belong to each limb
+ /* 0x0C */ s16* fixedValues; // Array of fixed rotation (standard skeleton) or scale/rotation/translation (flex skeleton) values
+ /* 0x10 */ UNK_TYPE2 unk_10;
+ /* 0x12 */ s16 frameCount; // Length of the animation in 30fps frames
+} KeyFrameAnimation; // Size = 0x14
+
+typedef enum {
+ /* 0 */ KEYFRAME_ANIM_ONCE, // Play once and stop
+ /* 1 */ KEYFRAME_ANIM_LOOP // Play in a loop
+} KeyFrameAnimMode;
+
+typedef struct {
+ /* 0x00 */ f32 start; // Current animation start frame number
+ /* 0x04 */ f32 end; // Current animation end frame number
+ /* 0x08 */ f32 frameCount; // Current animation total frame count
+ /* 0x0C */ f32 speed; // Current play speed
+ /* 0x10 */ f32 curTime; // Current play frame number
+ /* 0x14 */ s32 animMode; // Current play mode (see FrameAnimMode)
+} FrameControl; // Size = 0x18
+
+typedef struct KFSkelAnime {
+ /* 0x00 */ FrameControl frameCtrl; // Current play state
+ /* 0x18 */ KeyFrameSkeleton* skeleton; // Skeleton to animate
+ /* 0x1C */ KeyFrameAnimation* animation; // Currently active animation
+ /* 0x20 */ f32 morphFrames; // Number of frames in which to morph between the previous pose and the current animation
+ /* 0x24 */ Vec3s* jointTable; // Array of data describing the current pose
+ // size = 1 + limbCount, one root translation followed by rotations for each limb
+ /* 0x28 */ Vec3s* morphTable; // Array of data describing the current morph pose to interpolate with
+ // size = 1 + limbCount, one root translation followed by rotations for each limb
+ /* 0x2C */ Vec3s* rotOffsetsTable; // Table of rotations to add to the current pose, may be NULL so that no additional rotations are added
+ // size = limbCount
+} KFSkelAnime; // Size = 0x30
+
+typedef struct KFSkelAnimeFlex {
+ /* 0x00 */ FrameControl frameCtrl; // Current play state
+ /* 0x18 */ KeyFrameFlexSkeleton* skeleton; // Skeleton to animate
+ /* 0x1C */ KeyFrameAnimation* animation; // Currently active animation
+ /* 0x20 */ KeyframeTransformCallback* transformCallbacks; // Pointer to array of limb transform callbacks, indexed by callbackIndex in KeyFrameFlexLimb
+ /* 0x24 */ f32 morphFrames; // Number of frames in which to morph between the previous pose and the current animation
+ /* 0x28 */ Vec3s* jointTable; // Array of data describing the current pose
+ // size = 3 * limbCount in order of (scale, rotation, translation) for each limb
+ /* 0x2C */ Vec3s* morphTable; // Array of data describing the current morph pose to interpolate with
+ // size = 3 * limbCount in order of (scale, rotation, translation) for each limb
+} KFSkelAnimeFlex; // Size = 0x30
+
+void FrameCtrl_Reset(FrameControl* frameCtrl);
+void FrameCtrl_Init(FrameControl* frameCtrl);
+void FrameCtrl_SetProperties(FrameControl* frameCtrl, f32 startTime, f32 endTime, f32 frameCount, f32 t, f32 speed,
+ s32 animMode);
+s32 FrameCtrl_PassCheck(FrameControl* frameCtrl, f32 t, f32* remainingTime);
+s32 FrameCtrl_UpdateOnce(FrameControl* frameCtrl);
+s32 FrameCtrl_UpdateLoop(FrameControl* frameCtrl);
+s32 FrameCtrl_Update(FrameControl* frameCtrl);
+
+void Keyframe_ResetFlex(KFSkelAnimeFlex* kfSkelAnime);
+void Keyframe_InitFlex(KFSkelAnimeFlex* kfSkelAnime, KeyFrameFlexSkeleton* skeleton, KeyFrameAnimation* animation,
+ Vec3s* jointTable, Vec3s* morphTable, KeyframeTransformCallback* transformCallbacks);
+void Keyframe_DestroyFlex(KFSkelAnimeFlex* kfSkelAnime);
+void Keyframe_FlexPlayOnce(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation);
+void Keyframe_FlexPlayOnceSetSpeed(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation, f32 speed);
+void Keyframe_FlexMorphToPlayOnce(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation, f32 morphFrames);
+void Keyframe_FlexPlayLoop(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation);
+void Keyframe_FlexPlayLoopSetSpeed(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation, f32 speed);
+void Keyframe_FlexMorphToPlayLoop(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation, f32 morphFrames);
+void Keyframe_FlexChangeAnim(KFSkelAnimeFlex* kfSkelAnime, KeyFrameFlexSkeleton* skeleton, KeyFrameAnimation* animation,
+ f32 startTime, f32 endTime, f32 t, f32 speed, f32 morphFrames, s32 animMode);
+void Keyframe_FlexChangeAnimQuick(KFSkelAnimeFlex* kfSkelAnime, KeyFrameAnimation* animation);
+f32 Keyframe_Interpolate(f32 t, f32 delta, f32 x0, f32 x1, f32 v0, f32 v1);
+s16 Keyframe_KeyCalc(s16 kfStart, s16 kfNum, KeyFrame* keyFrames, f32 t);
+void Keyframe_MorphInterpolateRotation(f32 t, s16* out, s16 rot1, s16 rot2);
+void Keyframe_MorphInterpolateLinear(s16* jointData, s16* morphData, f32 t);
+void Keyframe_FlexMorphInterpolation(KFSkelAnimeFlex* kfSkelAnime);
+s32 Keyframe_UpdateFlex(KFSkelAnimeFlex* kfSkelAnime);
+void Keyframe_DrawFlex(struct PlayState* play, KFSkelAnimeFlex* kfSkelAnime, Mtx* mtxStack,
+ OverrideKeyframeDrawScaled overrideKeyframeDraw, PostKeyframeDrawScaled postKeyframeDraw,
+ void* arg);
+
+void Keyframe_ResetStandard(KFSkelAnime* kfSkelAnime);
+void Keyframe_InitStandard(KFSkelAnime* kfSkelAnime, KeyFrameSkeleton* skeleton, KeyFrameAnimation* animation,
+ Vec3s* jointTable, Vec3s* morphTable);
+void Keyframe_DestroyStandard(KFSkelAnime* kfSkelAnime);
+void Keyframe_StandardPlayOnce(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable);
+void Keyframe_StandardPlayOnceSetSpeed(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable,
+ f32 speed);
+void Keyframe_StandardMorphToPlayOnce(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable,
+ f32 morphFrames);
+void Keyframe_StandardPlayLoop(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable);
+void Keyframe_StandardPlayLoopSetSpeed(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable,
+ f32 speed);
+void Keyframe_StandardMorphToPlayLoop(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation, Vec3s* rotOffsetsTable,
+ f32 morphFrames);
+void Keyframe_StandardChangeAnim(KFSkelAnime* kfSkelAnime, KeyFrameSkeleton* skeleton, KeyFrameAnimation* animation,
+ f32 startTime, f32 endTime, f32 t, f32 speed, f32 morphFrames, s32 animMode,
+ Vec3s* rotOffsetsTable);
+void Keyframe_StandardChangeAnimQuick(KFSkelAnime* kfSkelAnime, KeyFrameAnimation* animation);
+void Keyframe_StandardMorphInterpolation(KFSkelAnime* kfSkelAnime);
+s32 Keyframe_UpdateStandard(KFSkelAnime* kfSkelAnime);
+void Keyframe_DrawStandardLimb(struct PlayState* play, KFSkelAnime* kfSkelAnime, s32* limbIndex,
+ OverrideKeyframeDraw overrideKeyframeDraw, PostKeyframeDraw postKeyframeDraw, void* arg,
+ Mtx** mtxStack);
+void Keyframe_DrawStandard(struct PlayState* play, KFSkelAnime* kfSkelAnime, Mtx* mtxStack,
+ OverrideKeyframeDraw overrideKeyframeDraw, PostKeyframeDraw postKeyframeDraw, void* arg);
+
+void Keyframe_FlexGetScale(KFSkelAnimeFlex* kfSkelAnime, s32 targetLimbIndex, Vec3s* scale);
+
+#endif
diff --git a/src/code/c_keyframe.c b/src/code/c_keyframe.c
index 5914eec777..1f148b75c8 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;
+ }
+ }
+ }
+ }
+}
diff --git a/src/code/z_collision_check.c b/src/code/z_collision_check.c
index 11c3865e1e..5b25e29b33 100644
--- a/src/code/z_collision_check.c
+++ b/src/code/z_collision_check.c
@@ -1,3 +1,4 @@
+#include "prevent_bss_reordering.h"
#include "z64collision_check.h"
#include "z64actor.h"
diff --git a/src/code/z_demo.c b/src/code/z_demo.c
index 7527cc3a10..d7d0d1c338 100644
--- a/src/code/z_demo.c
+++ b/src/code/z_demo.c
@@ -14,6 +14,28 @@ static s16 sBssPad;
u8 gDisablePlayerCsActionStartPos;
s16 gDungeonBossWarpSceneId;
+// clang-format off
+// Partial structs taken from "prevent_bss_reordering.h"
+struct Dummy200 { int x; };
+struct Dummy201 { int x; };
+struct Dummy202 { int x; };
+struct Dummy203 { int x; };
+struct Dummy204 { int x; };
+struct Dummy205 { int x; };
+struct Dummy206 { int x; };
+struct Dummy207 { int x; };
+struct Dummy208 { int x; };
+struct Dummy209 { int x; };
+struct Dummy210 { int x; };
+struct Dummy211 { int x; };
+struct Dummy212 { int x; };
+struct Dummy213 { int x; };
+struct Dummy214 { int x; };
+struct Dummy215 { int x; };
+struct Dummy216 { int x; };
+struct Dummy217 { int x; };
+// clang-format on
+
#include "z64quake.h"
#include "z64rumble.h"
#include "z64shrink_window.h"
diff --git a/src/overlays/actors/ovl_Boss_03/z_boss_03.c b/src/overlays/actors/ovl_Boss_03/z_boss_03.c
index fde9797bf4..9dc32de4e3 100644
--- a/src/overlays/actors/ovl_Boss_03/z_boss_03.c
+++ b/src/overlays/actors/ovl_Boss_03/z_boss_03.c
@@ -49,7 +49,6 @@
* - Seaweed
*/
-#include "prevent_bss_reordering.h"
#include "z_boss_03.h"
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "overlays/actors/ovl_En_Water_Effect/z_en_water_effect.h"
diff --git a/src/overlays/actors/ovl_Demo_Moonend/z_demo_moonend.c b/src/overlays/actors/ovl_Demo_Moonend/z_demo_moonend.c
index b40707f645..91d6c2377a 100644
--- a/src/overlays/actors/ovl_Demo_Moonend/z_demo_moonend.c
+++ b/src/overlays/actors/ovl_Demo_Moonend/z_demo_moonend.c
@@ -46,14 +46,14 @@ void DemoMoonend_Init(Actor* thisx, PlayState* play) {
this->actionFunc = func_80C17B60;
} else {
Actor_SetScale(&this->actor, 0.095f);
- func_80183430(&this->skeletonInfo, &object_moonend_Blob_00B5A0, &object_moonend_Blob_001214, this->jointTable,
- this->morphTable, NULL);
- func_801834A8(&this->skeletonInfo, &object_moonend_Blob_001214);
+ Keyframe_InitFlex(&this->kfSkelAnime, (KeyFrameFlexSkeleton*)&object_moonend_Blob_00B5A0,
+ (KeyFrameAnimation*)&object_moonend_Blob_001214, this->jointTable, this->morphTable, NULL);
+ Keyframe_FlexPlayOnce(&this->kfSkelAnime, (KeyFrameAnimation*)&object_moonend_Blob_001214);
this->cueType = CS_CMD_ACTOR_CUE_560;
this->actionFunc = func_80C17C48;
this->actor.home.rot.z = 0;
this->actor.draw = NULL;
- this->skeletonInfo.frameCtrl.unk_C = 2.0f / 3.0f;
+ this->kfSkelAnime.frameCtrl.speed = 2.0f / 3.0f;
}
}
@@ -61,7 +61,7 @@ void DemoMoonend_Destroy(Actor* thisx, PlayState* play) {
DemoMoonend* this = THIS;
if (DEMOMOONEND_GET_PARAM_F(thisx) != 1) {
- func_8018349C(&this->skeletonInfo);
+ Keyframe_DestroyFlex(&this->kfSkelAnime);
}
}
@@ -97,7 +97,7 @@ void func_80C17B60(DemoMoonend* this, PlayState* play) {
}
void func_80C17C48(DemoMoonend* this, PlayState* play) {
- if (func_80183DE0(&this->skeletonInfo)) {
+ if (Keyframe_UpdateFlex(&this->kfSkelAnime)) {
this->actor.home.rot.z = 0;
}
if (Cutscene_IsCueInChannel(play, this->cueType)) {
@@ -110,14 +110,14 @@ void func_80C17C48(DemoMoonend* this, PlayState* play) {
switch (this->cueId) {
case 1:
this->actor.draw = DemoMoonend_Draw;
- func_801834A8(&this->skeletonInfo, &object_moonend_Blob_001214);
- this->skeletonInfo.frameCtrl.unk_C = 0.0f;
+ Keyframe_FlexPlayOnce(&this->kfSkelAnime, (KeyFrameAnimation*)&object_moonend_Blob_001214);
+ this->kfSkelAnime.frameCtrl.speed = 0.0f;
break;
case 2:
this->actor.draw = DemoMoonend_Draw;
- func_801834A8(&this->skeletonInfo, &object_moonend_Blob_001214);
- this->skeletonInfo.frameCtrl.unk_C = 2.0f / 3.0f;
+ Keyframe_FlexPlayOnce(&this->kfSkelAnime, (KeyFrameAnimation*)&object_moonend_Blob_001214);
+ this->kfSkelAnime.frameCtrl.speed = 2.0f / 3.0f;
Actor_PlaySfx(&this->actor, NA_SE_EV_MOON_EXPLOSION);
this->actor.home.rot.z = 1;
break;
@@ -166,8 +166,8 @@ void DemoMoonend_Update(Actor* thisx, PlayState* play) {
this->actionFunc(this, play);
}
-s32 func_80C17E70(PlayState* play, SkeletonInfo* skeletonInfo, s32 limbIndex, Gfx** dList, u8* flags, Actor* thisx,
- Vec3f* scale, Vec3s* rot, Vec3f* pos) {
+s32 DemoMoonend_OverrideLimbDraw(PlayState* play, KFSkelAnimeFlex* kfSkelAnime, s32 limbIndex, Gfx** dList, u8* flags,
+ void* thisx, Vec3f* scale, Vec3s* rot, Vec3f* pos) {
DemoMoonend* this = THIS;
if (limbIndex == 2) {
@@ -178,8 +178,8 @@ s32 func_80C17E70(PlayState* play, SkeletonInfo* skeletonInfo, s32 limbIndex, Gf
return true;
}
-s32 func_80C17EE0(PlayState* play, SkeletonInfo* skeleton, s32 limbIndex, Gfx** dList, u8* flags, Actor* thisx,
- Vec3f* scale, Vec3s* rot, Vec3f* pos) {
+s32 DemoMoonend_PostLimbDraw(PlayState* play, KFSkelAnimeFlex* kfSkelAnime, s32 limbIndex, Gfx** dList, u8* flags,
+ void* thisx, Vec3f* scale, Vec3s* rot, Vec3f* pos) {
DemoMoonend* this = THIS;
if (limbIndex == 8) {
@@ -191,16 +191,17 @@ s32 func_80C17EE0(PlayState* play, SkeletonInfo* skeleton, s32 limbIndex, Gfx**
void DemoMoonend_Draw(Actor* thisx, PlayState* play) {
DemoMoonend* this = THIS;
- Mtx* mtx;
+ Mtx* mtxStack;
AnimatedMat_Draw(play, (AnimatedMaterial*)Lib_SegmentedToVirtual(object_moonend_Matanimheader_00B540));
- mtx = GRAPH_ALLOC(play->state.gfxCtx, this->skeletonInfo.unk_18->unk_1 * sizeof(Mtx));
+ mtxStack = GRAPH_ALLOC(play->state.gfxCtx, this->kfSkelAnime.skeleton->dListCount * sizeof(Mtx));
- if (mtx != NULL) {
+ if (mtxStack != NULL) {
Gfx_SetupDL25_Xlu(play->state.gfxCtx);
Gfx_SetupDL25_Opa(play->state.gfxCtx);
- func_8018450C(play, &this->skeletonInfo, mtx, func_80C17E70, func_80C17EE0, &this->actor);
+ Keyframe_DrawFlex(play, &this->kfSkelAnime, mtxStack, DemoMoonend_OverrideLimbDraw, DemoMoonend_PostLimbDraw,
+ &this->actor);
}
}
diff --git a/src/overlays/actors/ovl_Demo_Moonend/z_demo_moonend.h b/src/overlays/actors/ovl_Demo_Moonend/z_demo_moonend.h
index b56c60a9bc..692b0b84a5 100644
--- a/src/overlays/actors/ovl_Demo_Moonend/z_demo_moonend.h
+++ b/src/overlays/actors/ovl_Demo_Moonend/z_demo_moonend.h
@@ -12,7 +12,7 @@ typedef void (*DemoMoonendActionFunc)(struct DemoMoonend*, PlayState*);
typedef struct DemoMoonend {
/* 0x000 */ Actor actor;
- /* 0x144 */ SkeletonInfo skeletonInfo;
+ /* 0x144 */ KFSkelAnimeFlex kfSkelAnime;
/* 0x174 */ Vec3s jointTable[30];
/* 0x228 */ Vec3s morphTable[30];
/* 0x2DC */ u16 cueType;
diff --git a/src/overlays/actors/ovl_Demo_Syoten/z_demo_syoten.c b/src/overlays/actors/ovl_Demo_Syoten/z_demo_syoten.c
index 6ffd93ace3..3780c075c5 100644
--- a/src/overlays/actors/ovl_Demo_Syoten/z_demo_syoten.c
+++ b/src/overlays/actors/ovl_Demo_Syoten/z_demo_syoten.c
@@ -73,9 +73,9 @@ void DemoSyoten_Init(Actor* thisx, PlayState* play) {
switch (DEMOSYOTEN_GET_F(&this->actor)) {
case DEMOSYOTEN_F_0:
- func_80183430(&this->unk_144, &object_syoten_Blob_001328, &object_syoten_Blob_00023C, this->unk_174,
- this->unk_2A6, NULL);
- func_801835EC(&this->unk_144, &object_syoten_Blob_00023C);
+ Keyframe_InitFlex(&this->kfSkelAnime, (KeyFrameFlexSkeleton*)&object_syoten_Blob_001328,
+ (KeyFrameAnimation*)&object_syoten_Blob_00023C, this->jointTable, this->morphTable, NULL);
+ Keyframe_FlexPlayLoop(&this->kfSkelAnime, (KeyFrameAnimation*)&object_syoten_Blob_00023C);
this->actor.draw = NULL;
this->actionFunc = func_80C16A74;
this->actor.child =
@@ -131,7 +131,7 @@ void DemoSyoten_Destroy(Actor* thisx, PlayState* play) {
DemoSyoten* this = THIS;
if (DEMOSYOTEN_GET_F(&this->actor) == DEMOSYOTEN_F_0) {
- func_8018349C(&this->unk_144);
+ Keyframe_DestroyFlex(&this->kfSkelAnime);
}
}
@@ -221,7 +221,7 @@ void func_80C16A64(DemoSyoten* this, PlayState* play) {
void func_80C16A74(DemoSyoten* this, PlayState* play) {
u16 cueId;
- func_80183DE0(&this->unk_144);
+ Keyframe_UpdateFlex(&this->kfSkelAnime);
if (Cutscene_IsCueInChannel(play, this->cueType)) {
if ((play->csCtx.curFrame >= 160) && (play->csCtx.curFrame < 322)) {
Actor_PlaySfx_Flagged(&this->actor, NA_SE_EV_IKANA_SOUL_LV - SFX_FLAG);
@@ -436,13 +436,14 @@ void DemoSyoten_Update(Actor* thisx, PlayState* play) {
this->actionFunc(this, play);
}
-s32 func_80C170F8(PlayState* play, UNK_TYPE arg1, s32 arg2, UNK_TYPE arg3, UNK_TYPE arg4, Actor* thisx) {
+s32 DemoSyoten_OverrideLimbDraw(PlayState* play, KFSkelAnimeFlex* kfSkelAnime, s32 limbIndex, Gfx** dList, u8* flags,
+ void* thisx, Vec3f* scale, Vec3s* rot, Vec3f* pos) {
GraphicsContext* gfxCtx = play->state.gfxCtx;
DemoSyoten* this = THIS;
OPEN_DISPS(gfxCtx);
- switch (arg2) {
+ switch (limbIndex) {
case 2:
gDPPipeSync(POLY_XLU_DISP++);
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, 255, 240, 140, (s32)(this->unk_3D8 * 150.0f));
@@ -483,16 +484,16 @@ s32 func_80C170F8(PlayState* play, UNK_TYPE arg1, s32 arg2, UNK_TYPE arg3, UNK_T
void func_80C173B4(Actor* thisx, PlayState* play) {
s32 pad;
DemoSyoten* this = THIS;
- Mtx* mtx;
+ Mtx* mtxStack;
AnimatedMat_DrawXlu(play, Lib_SegmentedToVirtual(&object_syoten_Matanimheader_001298));
- mtx = GRAPH_ALLOC(play->state.gfxCtx, this->unk_144.unk_18->unk_1 * sizeof(Mtx));
+ mtxStack = GRAPH_ALLOC(play->state.gfxCtx, this->kfSkelAnime.skeleton->dListCount * sizeof(Mtx));
- if (mtx != NULL) {
+ if (mtxStack != NULL) {
Gfx_SetupDL25_Xlu(play->state.gfxCtx);
Matrix_Mult(&play->billboardMtxF, MTXMODE_APPLY);
- func_8018450C(play, &this->unk_144, mtx, (void*)func_80C170F8, 0, &this->actor);
+ Keyframe_DrawFlex(play, &this->kfSkelAnime, mtxStack, DemoSyoten_OverrideLimbDraw, NULL, &this->actor);
}
}
diff --git a/src/overlays/actors/ovl_Demo_Syoten/z_demo_syoten.h b/src/overlays/actors/ovl_Demo_Syoten/z_demo_syoten.h
index d5caa19209..4c0e3d3035 100644
--- a/src/overlays/actors/ovl_Demo_Syoten/z_demo_syoten.h
+++ b/src/overlays/actors/ovl_Demo_Syoten/z_demo_syoten.h
@@ -22,9 +22,9 @@ typedef enum {
typedef struct DemoSyoten {
/* 0x000 */ Actor actor;
- /* 0x144 */ SkeletonInfo unk_144;
- /* 0x174 */ Vec3s unk_174[51];
- /* 0x2A6 */ Vec3s unk_2A6[51];
+ /* 0x144 */ KFSkelAnimeFlex kfSkelAnime;
+ /* 0x174 */ Vec3s jointTable[51];
+ /* 0x2A6 */ Vec3s morphTable[51];
/* 0x3D8 */ f32 unk_3D8;
/* 0x3DC */ Gfx* unk_3DC;
/* 0x3E0 */ AnimatedMaterial* unk_3E0;
diff --git a/src/overlays/actors/ovl_Eff_Change/z_eff_change.c b/src/overlays/actors/ovl_Eff_Change/z_eff_change.c
index 9839d29d5a..24a079632f 100644
--- a/src/overlays/actors/ovl_Eff_Change/z_eff_change.c
+++ b/src/overlays/actors/ovl_Eff_Change/z_eff_change.c
@@ -50,19 +50,19 @@ void EffChange_Init(Actor* thisx, PlayState* play) {
EffChange_SetColors(this, EFFCHANGE_GET_COLORS(thisx));
Actor_SetScale(&this->actor, 0.075f);
this->primColors[3] = 0;
- func_80183430(&this->skeletonInfo, gameplay_keep_Blob_02900C, gameplay_keep_Blob_0281DC, this->jointTable,
- this->morphTable, NULL);
- func_801834A8(&this->skeletonInfo, gameplay_keep_Blob_0281DC);
+ Keyframe_InitFlex(&this->kfSkelAnime, (KeyFrameFlexSkeleton*)&gameplay_keep_Blob_02900C,
+ (KeyFrameAnimation*)&gameplay_keep_Blob_0281DC, this->jointTable, this->morphTable, NULL);
+ Keyframe_FlexPlayOnce(&this->kfSkelAnime, (KeyFrameAnimation*)&gameplay_keep_Blob_0281DC);
this->step = 0;
this->actor.shape.rot.y = 0;
- this->skeletonInfo.frameCtrl.unk_C = (2.0f / 3.0f);
+ this->kfSkelAnime.frameCtrl.speed = 2.0f / 3.0f;
CutsceneManager_Queue(CS_ID_GLOBAL_ELEGY);
}
void EffChange_Destroy(Actor* thisx, PlayState* play) {
EffChange* this = THIS;
- func_8018349C(&this->skeletonInfo);
+ Keyframe_DestroyFlex(&this->kfSkelAnime);
}
void EffChange_SetColors(EffChange* this, s32 arg1) {
@@ -78,7 +78,7 @@ void EffChange_SetColors(EffChange* this, s32 arg1) {
void func_80A4C5CC(EffChange* this, PlayState* play) {
f32 phi_fv0;
- if (func_80183DE0(&this->skeletonInfo)) {
+ if (Keyframe_UpdateFlex(&this->kfSkelAnime)) {
Actor_Kill(&this->actor);
CutsceneManager_Stop(CS_ID_GLOBAL_ELEGY);
Environment_AdjustLights(play, 0.0f, 850.0f, 0.2f, 0.0f);
@@ -86,13 +86,13 @@ void func_80A4C5CC(EffChange* this, PlayState* play) {
}
this->step++;
- if (this->skeletonInfo.frameCtrl.unk_10 < 20.0f) {
+ if (this->kfSkelAnime.frameCtrl.curTime < 20.0f) {
if ((this->primColors[3]) < 242) {
this->primColors[3] += 13;
} else {
this->primColors[3] = 255;
}
- } else if (this->skeletonInfo.frameCtrl.unk_10 > 70.0f) {
+ } else if (this->kfSkelAnime.frameCtrl.curTime > 70.0f) {
if ((this->primColors[3]) >= 14) {
this->primColors[3] -= 13;
} else {
@@ -126,13 +126,13 @@ void EffChange_Update(Actor* thisx, PlayState* play) {
void EffChange_Draw(Actor* thisx, PlayState* play) {
s32 pad;
- Mtx* mtx;
+ Mtx* mtxStack;
EffChange* this = THIS;
AnimatedMat_DrawStepXlu(play, Lib_SegmentedToVirtual(&gameplay_keep_Matanimheader_028FEC), this->step);
- mtx = GRAPH_ALLOC(play->state.gfxCtx, this->skeletonInfo.unk_18->unk_1 * sizeof(Mtx));
+ mtxStack = GRAPH_ALLOC(play->state.gfxCtx, this->kfSkelAnime.skeleton->dListCount * sizeof(Mtx));
- if (mtx != NULL) {
+ if (mtxStack != NULL) {
Gfx_SetupDL25_Xlu(play->state.gfxCtx);
Matrix_RotateYS((Camera_GetCamDirYaw(GET_ACTIVE_CAM(play)) + 0x8000), MTXMODE_APPLY);
@@ -141,7 +141,7 @@ void EffChange_Draw(Actor* thisx, PlayState* play) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, this->primColors[0], this->primColors[1], this->primColors[2],
this->primColors[3]);
gDPSetEnvColor(POLY_XLU_DISP++, this->envColors[0], this->envColors[1], this->envColors[2], 255);
- func_8018450C(play, &this->skeletonInfo, mtx, NULL, NULL, &this->actor);
+ Keyframe_DrawFlex(play, &this->kfSkelAnime, mtxStack, NULL, NULL, &this->actor);
CLOSE_DISPS(play->state.gfxCtx);
}
diff --git a/src/overlays/actors/ovl_Eff_Change/z_eff_change.h b/src/overlays/actors/ovl_Eff_Change/z_eff_change.h
index 194ae3ee23..c109519ae1 100644
--- a/src/overlays/actors/ovl_Eff_Change/z_eff_change.h
+++ b/src/overlays/actors/ovl_Eff_Change/z_eff_change.h
@@ -12,9 +12,9 @@ typedef void (*EffChangeActionFunc)(struct EffChange*, PlayState*);
typedef struct EffChange {
/* 0x000 */ Actor actor;
- /* 0x144 */ SkeletonInfo skeletonInfo;
- /* 0x174 */ Vec3s jointTable[PINK_DEKU_FLOWER_LIMB_BACK_RIGHT_PETAL];
- /* 0x198 */ Vec3s morphTable[PINK_DEKU_FLOWER_LIMB_BACK_RIGHT_PETAL];
+ /* 0x144 */ KFSkelAnimeFlex kfSkelAnime;
+ /* 0x174 */ Vec3s jointTable[6];
+ /* 0x198 */ Vec3s morphTable[6];
/* 0x1BC */ s16 step;
/* 0x1BE */ u8 primColors[4];
/* 0x1C2 */ u8 envColors[4];
diff --git a/src/overlays/actors/ovl_En_Fall2/z_en_fall2.c b/src/overlays/actors/ovl_En_Fall2/z_en_fall2.c
index 68f9d4dab6..b1a67d9e0d 100644
--- a/src/overlays/actors/ovl_En_Fall2/z_en_fall2.c
+++ b/src/overlays/actors/ovl_En_Fall2/z_en_fall2.c
@@ -35,9 +35,9 @@ void EnFall2_Init(Actor* thisx, PlayState* play) {
Actor_SetScale(&this->actor, 1.0f);
this->actionFunc = EnFall2_DoNothing;
- func_80183430(&this->skeletonInfo, object_fall2_Blob_008898, object_fall2_Blob_005EF4, this->unk174, this->unk228,
- NULL);
- func_801835EC(&this->skeletonInfo, object_fall2_Blob_005EF4);
+ Keyframe_InitFlex(&this->kfSkelAnime, (KeyFrameFlexSkeleton*)&object_fall2_Blob_008898,
+ (KeyFrameAnimation*)&object_fall2_Blob_005EF4, this->jointTable, this->morphTable, NULL);
+ Keyframe_FlexPlayLoop(&this->kfSkelAnime, (KeyFrameAnimation*)&object_fall2_Blob_005EF4);
this->unk2DC = Lib_SegmentedToVirtual(object_fall2_Matanimheader_008840);
Actor_SetScale(&this->actor, 0.02f);
this->actionFunc = EnFall2_HandleCutscene;
@@ -48,7 +48,7 @@ void EnFall2_Init(Actor* thisx, PlayState* play) {
void EnFall2_Destroy(Actor* thisx, PlayState* play) {
EnFall2* this = THIS;
- func_8018349C(&this->skeletonInfo);
+ Keyframe_DestroyFlex(&this->kfSkelAnime);
}
static u8 sAlphaTableIndices[] = {
@@ -125,7 +125,7 @@ void func_80C1B8F0(EnFall2* this) {
}
void EnFall2_HandleCutscene(EnFall2* this, PlayState* play) {
- func_80183DE0(&this->skeletonInfo);
+ Keyframe_UpdateFlex(&this->kfSkelAnime);
if (Cutscene_IsCueInChannel(play, this->cueType)) {
Cutscene_ActorTranslateAndYaw(&this->actor, play, Cutscene_GetCueChannel(play, this->cueType));
if (this->cueId != play->csCtx.actorCues[Cutscene_GetCueChannel(play, this->cueType)]->id) {
@@ -151,18 +151,18 @@ void EnFall2_Update(Actor* thisx, PlayState* play) {
void EnFall2_Draw(Actor* thisx, PlayState* play) {
s32 pad;
EnFall2* this = THIS;
- Mtx* mtx;
+ Mtx* mtxStack;
if (!(this->alphaLevel <= 0.0f)) {
Gfx_SetupDL25_Xlu(play->state.gfxCtx);
AnimatedMat_DrawXlu(play, Lib_SegmentedToVirtual(object_fall2_Matanimheader_008840));
- mtx = GRAPH_ALLOC(play->state.gfxCtx, this->skeletonInfo.unk_18->unk_1 * sizeof(Mtx));
+ mtxStack = GRAPH_ALLOC(play->state.gfxCtx, this->kfSkelAnime.skeleton->dListCount * sizeof(Mtx));
- if (mtx != NULL) {
+ if (mtxStack != NULL) {
Gfx_SetupDL25_Xlu(play->state.gfxCtx);
Matrix_RotateYS((s16)(Camera_GetCamDirYaw(GET_ACTIVE_CAM(play)) + 0x8000), MTXMODE_APPLY);
- func_8018450C(play, &this->skeletonInfo, mtx, NULL, NULL, &this->actor);
+ Keyframe_DrawFlex(play, &this->kfSkelAnime, mtxStack, NULL, NULL, &this->actor);
}
}
}
diff --git a/src/overlays/actors/ovl_En_Fall2/z_en_fall2.h b/src/overlays/actors/ovl_En_Fall2/z_en_fall2.h
index 3555595f33..1906f81d32 100644
--- a/src/overlays/actors/ovl_En_Fall2/z_en_fall2.h
+++ b/src/overlays/actors/ovl_En_Fall2/z_en_fall2.h
@@ -10,9 +10,9 @@ typedef void (*EnFall2ActionFunc)(struct EnFall2*, PlayState*);
typedef struct EnFall2 {
/* 0x000 */ Actor actor;
- /* 0x144 */ SkeletonInfo skeletonInfo;
- /* 0x174 */ Vec3s unk174[30];
- /* 0x228 */ Vec3s unk228[30];
+ /* 0x144 */ KFSkelAnimeFlex kfSkelAnime;
+ /* 0x174 */ Vec3s jointTable[30];
+ /* 0x228 */ Vec3s morphTable[30];
/* 0x2DC */ AnimatedMaterial* unk2DC;
/* 0x2E0 */ f32 alphaLevel;
/* 0x2E4 */ s16 cueId;
diff --git a/src/overlays/actors/ovl_En_Test/z_en_test.c b/src/overlays/actors/ovl_En_Test/z_en_test.c
index 12aba1abb3..6cff068b32 100644
--- a/src/overlays/actors/ovl_En_Test/z_en_test.c
+++ b/src/overlays/actors/ovl_En_Test/z_en_test.c
@@ -186,26 +186,26 @@ void EnTest_Init(Actor* thisx, PlayState* play2) {
this->surfaceMaterial = SurfaceType_GetMaterial(&play->colCtx, thisx->floorPoly, bgId);
}
- func_80183430(&this->skeletonInfo, &gameplay_keep_Blob_06EB70, &gameplay_keep_Blob_06BB0C, this->unk_178,
- this->unk_1C0, NULL);
- func_801834A8(&this->skeletonInfo, &gameplay_keep_Blob_06BB0C);
- this->skeletonInfo.frameCtrl.unk_10 = 9.0f;
+ Keyframe_InitFlex(&this->kfSkelAnime, (KeyFrameFlexSkeleton*)&gameplay_keep_Blob_06EB70,
+ (KeyFrameAnimation*)&gameplay_keep_Blob_06BB0C, this->jointTable, this->morphTable, NULL);
+ Keyframe_FlexPlayOnce(&this->kfSkelAnime, (KeyFrameAnimation*)&gameplay_keep_Blob_06BB0C);
+ this->kfSkelAnime.frameCtrl.curTime = 9.0f;
func_80862B70(this->unk_20C);
}
void EnTest_Destroy(Actor* thisx, PlayState* play) {
EnTest* this = THIS;
- func_8018349C(&this->skeletonInfo);
+ Keyframe_DestroyFlex(&this->kfSkelAnime);
}
void EnTest_Update(Actor* thisx, PlayState* play) {
EnTest* this = THIS;
s32 i;
- this->unk_208 = this->skeletonInfo.frameCtrl.unk_10;
+ this->unk_208 = this->kfSkelAnime.frameCtrl.curTime;
- if (func_80183DE0(&this->skeletonInfo) && (this->actor.parent == NULL) && (this->actor.params != -1)) {
+ if (Keyframe_UpdateFlex(&this->kfSkelAnime) && (this->actor.parent == NULL) && (this->actor.params != -1)) {
this->unk_209++;
if (this->unk_209 > 20) {
Actor_Kill(&this->actor);
@@ -224,8 +224,8 @@ void EnTest_Update(Actor* thisx, PlayState* play) {
func_80862EDC(this->unk_20C);
}
-s32 EnTest_OverrideKeyframeDraw(PlayState* play, SkeletonInfo* skeletonInfo, s32 limbIndex, Gfx** dList, u8* flags,
- Actor* thisx, Vec3f* scale, Vec3s* rot, Vec3f* pos) {
+s32 EnTest_OverrideLimbDraw(PlayState* play, KFSkelAnimeFlex* kfSkelAnime, s32 limbIndex, Gfx** dList, u8* flags,
+ void* thisx, Vec3f* scale, Vec3s* rot, Vec3f* pos) {
EnTest* this = THIS;
OPEN_DISPS(play->state.gfxCtx);
@@ -250,7 +250,7 @@ s32 EnTest_OverrideKeyframeDraw(PlayState* play, SkeletonInfo* skeletonInfo, s32
void EnTest_Draw(Actor* thisx, PlayState* play) {
EnTest* this = THIS;
- Mtx* mtx;
+ Mtx* mtxStack;
s32 sp2C = this->unk_208 - 1;
if (sp2C >= 29) {
@@ -264,11 +264,11 @@ void EnTest_Draw(Actor* thisx, PlayState* play) {
AnimatedMat_DrawStep(play, Lib_SegmentedToVirtual(gameplay_keep_Matanimheader_06B6A0), sp2C);
}
- mtx = GRAPH_ALLOC(play->state.gfxCtx, this->skeletonInfo.unk_18->unk_1 * sizeof(Mtx));
+ mtxStack = GRAPH_ALLOC(play->state.gfxCtx, this->kfSkelAnime.skeleton->dListCount * sizeof(Mtx));
- if (mtx != NULL) {
+ if (mtxStack != NULL) {
Gfx_SetupDL25_Xlu(play->state.gfxCtx);
- func_8018450C(play, &this->skeletonInfo, mtx, EnTest_OverrideKeyframeDraw, NULL, thisx);
+ Keyframe_DrawFlex(play, &this->kfSkelAnime, mtxStack, EnTest_OverrideLimbDraw, NULL, thisx);
func_80863048(play, this->unk_20C);
}
}
diff --git a/src/overlays/actors/ovl_En_Test/z_en_test.h b/src/overlays/actors/ovl_En_Test/z_en_test.h
index 68395a96a7..de622d4467 100644
--- a/src/overlays/actors/ovl_En_Test/z_en_test.h
+++ b/src/overlays/actors/ovl_En_Test/z_en_test.h
@@ -18,10 +18,10 @@ typedef struct {
typedef struct EnTest {
/* 0x000 */ Actor actor;
- /* 0x144 */ SkeletonInfo skeletonInfo;
+ /* 0x144 */ KFSkelAnimeFlex kfSkelAnime;
/* 0x174 */ s32 unk_174;
- /* 0x178 */ Vec3s unk_178[12];
- /* 0x1C0 */ Vec3s unk_1C0[12];
+ /* 0x178 */ Vec3s jointTable[12];
+ /* 0x1C0 */ Vec3s morphTable[12];
/* 0x208 */ u8 unk_208;
/* 0x209 */ u8 unk_209;
/* 0x20A */ u8 surfaceMaterial;
diff --git a/src/overlays/actors/ovl_En_Test7/z_en_test7.c b/src/overlays/actors/ovl_En_Test7/z_en_test7.c
index 690e1a5760..0853fe1177 100644
--- a/src/overlays/actors/ovl_En_Test7/z_en_test7.c
+++ b/src/overlays/actors/ovl_En_Test7/z_en_test7.c
@@ -383,9 +383,9 @@ void EnTest7_Init(Actor* thisx, PlayState* play2) {
this->playerScaleZ = player->actor.scale.z;
// Keyframe animations
- func_80183430(&this->skeletonInfo, &gameplay_keep_Blob_085640, &gameplay_keep_Blob_083534, this->unk_18FC,
- this->unk_1BA8, NULL);
- func_801834A8(&this->skeletonInfo, &gameplay_keep_Blob_083534);
+ Keyframe_InitFlex(&this->kfSkelAnime, (KeyFrameFlexSkeleton*)&gameplay_keep_Blob_085640,
+ (KeyFrameAnimation*)&gameplay_keep_Blob_083534, this->jointTable, this->morphTable, NULL);
+ Keyframe_FlexPlayOnce(&this->kfSkelAnime, (KeyFrameAnimation*)&gameplay_keep_Blob_083534);
EnTest7_InitFeathers(this->feathers);
EnTest7_InitWindCapsule(&this->windCapsule);
@@ -482,20 +482,20 @@ void EnTest7_UpdateGrowingWindCapsule(EnTest7* this, PlayState* play) {
void EnTest7_WarpCsPart2(EnTest7* this, PlayState* play) {
Vec3f featherPos;
- if (func_80183DE0(&this->skeletonInfo)) {
+ if (Keyframe_UpdateFlex(&this->kfSkelAnime)) {
EnTest7_SetupAction(this, EnTest7_WarpCsPart3);
}
- if (this->skeletonInfo.frameCtrl.unk_10 > 60.0f) {
+ if (this->kfSkelAnime.frameCtrl.curTime > 60.0f) {
EnTest7_UpdateGrowingWindCapsule(this, play);
}
- if ((this->skeletonInfo.frameCtrl.unk_10 > 20.0f) && !(this->flags & OWL_WARP_FLAGS_40)) {
+ if ((this->kfSkelAnime.frameCtrl.curTime > 20.0f) && !(this->flags & OWL_WARP_FLAGS_40)) {
this->flags |= OWL_WARP_FLAGS_40;
Audio_PlaySfx_AtPos(&this->actor.projectedPos, NA_SE_PL_WARP_WING_CLOSE);
}
- if (this->skeletonInfo.frameCtrl.unk_10 > 42.0f) {
+ if (this->kfSkelAnime.frameCtrl.curTime > 42.0f) {
if (!(this->flags & OWL_WARP_FLAGS_80)) {
this->flags |= OWL_WARP_FLAGS_80;
Audio_PlaySfx_AtPos(&this->actor.projectedPos, NA_SE_PL_WARP_WING_ROLL);
@@ -538,7 +538,7 @@ void EnTest7_WarpCsPart3(EnTest7* this, PlayState* play) {
Math_Vec3f_Copy(&featherPos, &this->actor.world.pos);
EnTest7_AddAndChooseFeather(this->feathers, &featherPos, true);
- this->unk_18FC[1].y += 0x2EE0;
+ this->jointTable[1].y += 0x2EE0;
}
void EnTest7_WarpCsPart4(EnTest7* this, PlayState* play) {
@@ -953,8 +953,8 @@ void EnTest7_Update(Actor* thisx, PlayState* play) {
(this->flags & OWL_WARP_FLAGS_10) != 0);
}
-s32 func_80AF31D0(PlayState* play, SkeletonInfo* skeletonInfo, s32 limbIndex, Gfx** dList, u8* flags, Actor* thisx,
- Vec3f* scale, Vec3s* rot, Vec3f* pos) {
+s32 EnTest7_OverrideLimbDraw(PlayState* play, KFSkelAnimeFlex* kfSkelAnime, s32 limbIndex, Gfx** dList, u8* flags,
+ void* thisx, Vec3f* scale, Vec3s* rot, Vec3f* pos) {
EnTest7* this = THIS;
Vec3f featherPos;
@@ -972,12 +972,12 @@ void EnTest7_Draw(Actor* thisx, PlayState* play) {
// Draw wings
if (this->flags & OWL_WARP_FLAGS_DRAW_WINGS) {
- Mtx* mtx = GRAPH_ALLOC(play->state.gfxCtx, this->skeletonInfo.unk_18->unk_1 * sizeof(Mtx));
+ Mtx* mtxStack = GRAPH_ALLOC(play->state.gfxCtx, this->kfSkelAnime.skeleton->dListCount * sizeof(Mtx));
- if (mtx == NULL) {
+ if (mtxStack == NULL) {
return;
}
- func_8018450C(play, &this->skeletonInfo, mtx, func_80AF31D0, NULL, &this->actor);
+ Keyframe_DrawFlex(play, &this->kfSkelAnime, mtxStack, EnTest7_OverrideLimbDraw, NULL, &this->actor);
}
// Draw windCapsule encasing that surrounds player after wings
diff --git a/src/overlays/actors/ovl_En_Test7/z_en_test7.h b/src/overlays/actors/ovl_En_Test7/z_en_test7.h
index 87c26a3564..6f7dd7436d 100644
--- a/src/overlays/actors/ovl_En_Test7/z_en_test7.h
+++ b/src/overlays/actors/ovl_En_Test7/z_en_test7.h
@@ -53,9 +53,9 @@ typedef struct EnTest7 {
/* 0x0144 */ s32 flags;
/* 0x0148 */ OwlWarpWindCapsule windCapsule;
/* 0x015C */ OwlWarpFeather feathers[OWL_WARP_NUM_FEATHERS];
- /* 0x18CC */ SkeletonInfo skeletonInfo; // wingsSkeletonInfo
- /* 0x18FC */ Vec3s unk_18FC[114]; // wingsFrameData
- /* 0x1BA8 */ Vec3s unk_1BA8[114];
+ /* 0x18CC */ KFSkelAnimeFlex kfSkelAnime;
+ /* 0x18FC */ Vec3s jointTable[114];
+ /* 0x1BA8 */ Vec3s morphTable[114];
/* 0x1E54 */ s32 timer;
/* 0x1E58 */ EnTest7ActionFunc actionFunc;
/* 0x1E5C */ EnTest7PlayerCamFunc playerCamFunc;
diff --git a/src/overlays/actors/ovl_Obj_Takaraya_Wall/z_obj_takaraya_wall.c b/src/overlays/actors/ovl_Obj_Takaraya_Wall/z_obj_takaraya_wall.c
index 3218c3050d..4e593f30e8 100644
--- a/src/overlays/actors/ovl_Obj_Takaraya_Wall/z_obj_takaraya_wall.c
+++ b/src/overlays/actors/ovl_Obj_Takaraya_Wall/z_obj_takaraya_wall.c
@@ -23,7 +23,7 @@
* Front
*
*/
-
+#include "prevent_bss_reordering.h"
#include "z_obj_takaraya_wall.h"
#include "objects/object_takaraya_objects/object_takaraya_objects.h"
diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt
index 4c9e98e94b..23f8c986e9 100644
--- a/tools/disasm/functions.txt
+++ b/tools/disasm/functions.txt
@@ -3401,48 +3401,48 @@
0x80183020:("RumbleManager_Init",),
0x80183058:("RumbleManager_Destroy",),
0x80183070:("func_80183070",),
- 0x801830A0:("func_801830A0",),
- 0x801830C8:("func_801830C8",),
- 0x801830E8:("func_801830E8",),
- 0x80183148:("func_80183148",),
- 0x80183224:("func_80183224",),
- 0x801832B0:("func_801832B0",),
- 0x8018332C:("func_8018332C",),
- 0x8018340C:("func_8018340C",),
- 0x80183430:("func_80183430",),
- 0x8018349C:("func_8018349C",),
- 0x801834A8:("func_801834A8",),
- 0x80183510:("func_80183510",),
- 0x80183580:("func_80183580",),
- 0x801835EC:("func_801835EC",),
- 0x80183658:("func_80183658",),
- 0x801836CC:("func_801836CC",),
- 0x8018373C:("func_8018373C",),
- 0x801837CC:("func_801837CC",),
- 0x80183808:("func_80183808",),
- 0x80183880:("func_80183880",),
- 0x80183A3C:("func_80183A3C",),
- 0x80183B08:("func_80183B08",),
- 0x80183B68:("func_80183B68",),
- 0x80183DE0:("func_80183DE0",),
- 0x8018410C:("func_8018410C",),
- 0x8018450C:("func_8018450C",),
- 0x801845A4:("func_801845A4",),
- 0x801845C8:("func_801845C8",),
- 0x8018462C:("func_8018462C",),
- 0x80184638:("func_80184638",),
- 0x801846AC:("func_801846AC",),
- 0x80184728:("func_80184728",),
- 0x801847A0:("func_801847A0",),
- 0x80184818:("func_80184818",),
- 0x80184898:("func_80184898",),
- 0x80184914:("func_80184914",),
- 0x801849A0:("func_801849A0",),
- 0x801849DC:("func_801849DC",),
- 0x80184C48:("func_80184C48",),
- 0x801850A0:("func_801850A0",),
- 0x801853C8:("func_801853C8",),
- 0x80185460:("func_80185460",),
+ 0x801830A0:("FrameCtrl_Reset",),
+ 0x801830C8:("FrameCtrl_Init",),
+ 0x801830E8:("FrameCtrl_SetProperties",),
+ 0x80183148:("FrameCtrl_PassCheck",),
+ 0x80183224:("FrameCtrl_UpdateOnce",),
+ 0x801832B0:("FrameCtrl_UpdateLoop",),
+ 0x8018332C:("FrameCtrl_Update",),
+ 0x8018340C:("Keyframe_ResetFlex",),
+ 0x80183430:("Keyframe_InitFlex",),
+ 0x8018349C:("Keyframe_DestroyFlex",),
+ 0x801834A8:("Keyframe_FlexPlayOnce",),
+ 0x80183510:("Keyframe_FlexPlayOnceSetSpeed",),
+ 0x80183580:("Keyframe_FlexMorphToPlayOnce",),
+ 0x801835EC:("Keyframe_FlexPlayLoop",),
+ 0x80183658:("Keyframe_FlexPlayLoopSetSpeed",),
+ 0x801836CC:("Keyframe_FlexMorphToPlayLoop",),
+ 0x8018373C:("Keyframe_FlexChangeAnim",),
+ 0x801837CC:("Keyframe_FlexChangeAnimQuick",),
+ 0x80183808:("Keyframe_Interpolate",),
+ 0x80183880:("Keyframe_KeyCalc",),
+ 0x80183A3C:("Keyframe_MorphInterpolateRotation",),
+ 0x80183B08:("Keyframe_MorphInterpolateLinear",),
+ 0x80183B68:("Keyframe_FlexMorphInterpolation",),
+ 0x80183DE0:("Keyframe_UpdateFlex",),
+ 0x8018410C:("Keyframe_DrawFlexLimb",),
+ 0x8018450C:("Keyframe_DrawFlex",),
+ 0x801845A4:("Keyframe_ResetStandard",),
+ 0x801845C8:("Keyframe_InitStandard",),
+ 0x8018462C:("Keyframe_DestroyStandard",),
+ 0x80184638:("Keyframe_StandardPlayOnce",),
+ 0x801846AC:("Keyframe_StandardPlayOnceSetSpeed",),
+ 0x80184728:("Keyframe_StandardMorphToPlayOnce",),
+ 0x801847A0:("Keyframe_StandardPlayLoop",),
+ 0x80184818:("Keyframe_StandardPlayLoopSetSpeed",),
+ 0x80184898:("Keyframe_StandardMorphToPlayLoop",),
+ 0x80184914:("Keyframe_StandardChangeAnim",),
+ 0x801849A0:("Keyframe_StandardChangeAnimQuick",),
+ 0x801849DC:("Keyframe_StandardMorphInterpolation",),
+ 0x80184C48:("Keyframe_UpdateStandard",),
+ 0x801850A0:("Keyframe_DrawStandardLimb",),
+ 0x801853C8:("Keyframe_DrawStandard",),
+ 0x80185460:("Keyframe_FlexGetScale",),
0x80185660:("Slowly_Main",),
0x801856FC:("Slowly_ThreadEntry",),
0x8018571C:("Slowly_Init",),
diff --git a/tools/sizes/code_functions.csv b/tools/sizes/code_functions.csv
index d0fca6cf73..8b21b5f147 100644
--- a/tools/sizes/code_functions.csv
+++ b/tools/sizes/code_functions.csv
@@ -2915,48 +2915,48 @@ asm/non_matchings/code/sys_ucode/RumbleManager_Update.s,RumbleManager_Update,0x8
asm/non_matchings/code/sys_ucode/RumbleManager_Init.s,RumbleManager_Init,0x80183020,0xE
asm/non_matchings/code/sys_ucode/RumbleManager_Destroy.s,RumbleManager_Destroy,0x80183058,0x6
asm/non_matchings/code/code_80183070/func_80183070.s,func_80183070,0x80183070,0xC
-asm/non_matchings/code/c_keyframe/func_801830A0.s,func_801830A0,0x801830A0,0xA
-asm/non_matchings/code/c_keyframe/func_801830C8.s,func_801830C8,0x801830C8,0x8
-asm/non_matchings/code/c_keyframe/func_801830E8.s,func_801830E8,0x801830E8,0x18
-asm/non_matchings/code/c_keyframe/func_80183148.s,func_80183148,0x80183148,0x37
-asm/non_matchings/code/c_keyframe/func_80183224.s,func_80183224,0x80183224,0x23
-asm/non_matchings/code/c_keyframe/func_801832B0.s,func_801832B0,0x801832B0,0x1F
-asm/non_matchings/code/c_keyframe/func_8018332C.s,func_8018332C,0x8018332C,0x38
-asm/non_matchings/code/c_keyframe/func_8018340C.s,func_8018340C,0x8018340C,0x9
-asm/non_matchings/code/c_keyframe/func_80183430.s,func_80183430,0x80183430,0x1B
-asm/non_matchings/code/c_keyframe/func_8018349C.s,func_8018349C,0x8018349C,0x3
-asm/non_matchings/code/c_keyframe/func_801834A8.s,func_801834A8,0x801834A8,0x1A
-asm/non_matchings/code/c_keyframe/func_80183510.s,func_80183510,0x80183510,0x1C
-asm/non_matchings/code/c_keyframe/func_80183580.s,func_80183580,0x80183580,0x1B
-asm/non_matchings/code/c_keyframe/func_801835EC.s,func_801835EC,0x801835EC,0x1B
-asm/non_matchings/code/c_keyframe/func_80183658.s,func_80183658,0x80183658,0x1D
-asm/non_matchings/code/c_keyframe/func_801836CC.s,func_801836CC,0x801836CC,0x1C
-asm/non_matchings/code/c_keyframe/func_8018373C.s,func_8018373C,0x8018373C,0x24
-asm/non_matchings/code/c_keyframe/func_801837CC.s,func_801837CC,0x801837CC,0xF
-asm/non_matchings/code/c_keyframe/func_80183808.s,func_80183808,0x80183808,0x1E
-asm/non_matchings/code/c_keyframe/func_80183880.s,func_80183880,0x80183880,0x6F
-asm/non_matchings/code/c_keyframe/func_80183A3C.s,func_80183A3C,0x80183A3C,0x33
-asm/non_matchings/code/c_keyframe/func_80183B08.s,func_80183B08,0x80183B08,0x18
-asm/non_matchings/code/c_keyframe/func_80183B68.s,func_80183B68,0x80183B68,0x9E
-asm/non_matchings/code/c_keyframe/func_80183DE0.s,func_80183DE0,0x80183DE0,0xCB
-asm/non_matchings/code/c_keyframe/func_8018410C.s,func_8018410C,0x8018410C,0x100
-asm/non_matchings/code/c_keyframe/func_8018450C.s,func_8018450C,0x8018450C,0x26
-asm/non_matchings/code/c_keyframe/func_801845A4.s,func_801845A4,0x801845A4,0x9
-asm/non_matchings/code/c_keyframe/func_801845C8.s,func_801845C8,0x801845C8,0x19
-asm/non_matchings/code/c_keyframe/func_8018462C.s,func_8018462C,0x8018462C,0x3
-asm/non_matchings/code/c_keyframe/func_80184638.s,func_80184638,0x80184638,0x1D
-asm/non_matchings/code/c_keyframe/func_801846AC.s,func_801846AC,0x801846AC,0x1F
-asm/non_matchings/code/c_keyframe/func_80184728.s,func_80184728,0x80184728,0x1E
-asm/non_matchings/code/c_keyframe/func_801847A0.s,func_801847A0,0x801847A0,0x1E
-asm/non_matchings/code/c_keyframe/func_80184818.s,func_80184818,0x80184818,0x20
-asm/non_matchings/code/c_keyframe/func_80184898.s,func_80184898,0x80184898,0x1F
-asm/non_matchings/code/c_keyframe/func_80184914.s,func_80184914,0x80184914,0x23
-asm/non_matchings/code/c_keyframe/func_801849A0.s,func_801849A0,0x801849A0,0xF
-asm/non_matchings/code/c_keyframe/func_801849DC.s,func_801849DC,0x801849DC,0x9B
-asm/non_matchings/code/c_keyframe/func_80184C48.s,func_80184C48,0x80184C48,0x116
-asm/non_matchings/code/c_keyframe/func_801850A0.s,func_801850A0,0x801850A0,0xCA
-asm/non_matchings/code/c_keyframe/func_801853C8.s,func_801853C8,0x801853C8,0x26
-asm/non_matchings/code/c_keyframe/func_80185460.s,func_80185460,0x80185460,0x80
+asm/non_matchings/code/c_keyframe/FrameCtrl_Reset.s,FrameCtrl_Reset,0x801830A0,0xA
+asm/non_matchings/code/c_keyframe/FrameCtrl_Init.s,FrameCtrl_Init,0x801830C8,0x8
+asm/non_matchings/code/c_keyframe/FrameCtrl_SetProperties.s,FrameCtrl_SetProperties,0x801830E8,0x18
+asm/non_matchings/code/c_keyframe/FrameCtrl_PassCheck.s,FrameCtrl_PassCheck,0x80183148,0x37
+asm/non_matchings/code/c_keyframe/FrameCtrl_UpdateOnce.s,FrameCtrl_UpdateOnce,0x80183224,0x23
+asm/non_matchings/code/c_keyframe/FrameCtrl_UpdateLoop.s,FrameCtrl_UpdateLoop,0x801832B0,0x1F
+asm/non_matchings/code/c_keyframe/FrameCtrl_Update.s,FrameCtrl_Update,0x8018332C,0x38
+asm/non_matchings/code/c_keyframe/Keyframe_ResetFlex.s,Keyframe_ResetFlex,0x8018340C,0x9
+asm/non_matchings/code/c_keyframe/Keyframe_InitFlex.s,Keyframe_InitFlex,0x80183430,0x1B
+asm/non_matchings/code/c_keyframe/Keyframe_DestroyFlex.s,Keyframe_DestroyFlex,0x8018349C,0x3
+asm/non_matchings/code/c_keyframe/Keyframe_FlexPlayOnce.s,Keyframe_FlexPlayOnce,0x801834A8,0x1A
+asm/non_matchings/code/c_keyframe/Keyframe_FlexPlayOnceSetSpeed.s,Keyframe_FlexPlayOnceSetSpeed,0x80183510,0x1C
+asm/non_matchings/code/c_keyframe/Keyframe_FlexMorphToPlayOnce.s,Keyframe_FlexMorphToPlayOnce,0x80183580,0x1B
+asm/non_matchings/code/c_keyframe/Keyframe_FlexPlayLoop.s,Keyframe_FlexPlayLoop,0x801835EC,0x1B
+asm/non_matchings/code/c_keyframe/Keyframe_FlexPlayLoopSetSpeed.s,Keyframe_FlexPlayLoopSetSpeed,0x80183658,0x1D
+asm/non_matchings/code/c_keyframe/Keyframe_FlexMorphToPlayLoop.s,Keyframe_FlexMorphToPlayLoop,0x801836CC,0x1C
+asm/non_matchings/code/c_keyframe/Keyframe_FlexChangeAnim.s,Keyframe_FlexChangeAnim,0x8018373C,0x24
+asm/non_matchings/code/c_keyframe/Keyframe_FlexChangeAnimQuick.s,Keyframe_FlexChangeAnimQuick,0x801837CC,0xF
+asm/non_matchings/code/c_keyframe/Keyframe_Interpolate.s,Keyframe_Interpolate,0x80183808,0x1E
+asm/non_matchings/code/c_keyframe/Keyframe_KeyCalc.s,Keyframe_KeyCalc,0x80183880,0x6F
+asm/non_matchings/code/c_keyframe/Keyframe_MorphInterpolateRotation.s,Keyframe_MorphInterpolateRotation,0x80183A3C,0x33
+asm/non_matchings/code/c_keyframe/Keyframe_MorphInterpolateLinear.s,Keyframe_MorphInterpolateLinear,0x80183B08,0x18
+asm/non_matchings/code/c_keyframe/Keyframe_FlexMorphInterpolation.s,Keyframe_FlexMorphInterpolation,0x80183B68,0x9E
+asm/non_matchings/code/c_keyframe/Keyframe_UpdateFlex.s,Keyframe_UpdateFlex,0x80183DE0,0xCB
+asm/non_matchings/code/c_keyframe/Keyframe_DrawFlexLimb.s,Keyframe_DrawFlexLimb,0x8018410C,0x100
+asm/non_matchings/code/c_keyframe/Keyframe_DrawFlex.s,Keyframe_DrawFlex,0x8018450C,0x26
+asm/non_matchings/code/c_keyframe/Keyframe_ResetStandard.s,Keyframe_ResetStandard,0x801845A4,0x9
+asm/non_matchings/code/c_keyframe/Keyframe_InitStandard.s,Keyframe_InitStandard,0x801845C8,0x19
+asm/non_matchings/code/c_keyframe/Keyframe_DestroyStandard.s,Keyframe_DestroyStandard,0x8018462C,0x3
+asm/non_matchings/code/c_keyframe/Keyframe_StandardPlayOnce.s,Keyframe_StandardPlayOnce,0x80184638,0x1D
+asm/non_matchings/code/c_keyframe/Keyframe_StandardPlayOnceSetSpeed.s,Keyframe_StandardPlayOnceSetSpeed,0x801846AC,0x1F
+asm/non_matchings/code/c_keyframe/Keyframe_StandardMorphToPlayOnce.s,Keyframe_StandardMorphToPlayOnce,0x80184728,0x1E
+asm/non_matchings/code/c_keyframe/Keyframe_StandardPlayLoop.s,Keyframe_StandardPlayLoop,0x801847A0,0x1E
+asm/non_matchings/code/c_keyframe/Keyframe_StandardPlayLoopSetSpeed.s,Keyframe_StandardPlayLoopSetSpeed,0x80184818,0x20
+asm/non_matchings/code/c_keyframe/Keyframe_StandardMorphToPlayLoop.s,Keyframe_StandardMorphToPlayLoop,0x80184898,0x1F
+asm/non_matchings/code/c_keyframe/Keyframe_StandardChangeAnim.s,Keyframe_StandardChangeAnim,0x80184914,0x23
+asm/non_matchings/code/c_keyframe/Keyframe_StandardChangeAnimQuick.s,Keyframe_StandardChangeAnimQuick,0x801849A0,0xF
+asm/non_matchings/code/c_keyframe/Keyframe_StandardMorphInterpolation.s,Keyframe_StandardMorphInterpolation,0x801849DC,0x9B
+asm/non_matchings/code/c_keyframe/Keyframe_UpdateStandard.s,Keyframe_UpdateStandard,0x80184C48,0x116
+asm/non_matchings/code/c_keyframe/Keyframe_DrawStandardLimb.s,Keyframe_DrawStandardLimb,0x801850A0,0xCA
+asm/non_matchings/code/c_keyframe/Keyframe_DrawStandard.s,Keyframe_DrawStandard,0x801853C8,0x26
+asm/non_matchings/code/c_keyframe/Keyframe_FlexGetScale.s,Keyframe_FlexGetScale,0x80185460,0x80
asm/non_matchings/code/sys_slowly/Slowly_Main.s,Slowly_Main,0x80185660,0x27
asm/non_matchings/code/sys_slowly/Slowly_ThreadEntry.s,Slowly_ThreadEntry,0x801856FC,0x8
asm/non_matchings/code/sys_slowly/Slowly_Init.s,Slowly_Init,0x8018571C,0x21