mirror of https://github.com/zeldaret/mm.git
1331 lines
52 KiB
C
1331 lines
52 KiB
C
/**
|
|
* @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"
|
|
|
|
#define FMOD(x, mod) ((x) - ((s32)((x) * (1.0f / (mod))) * (f32)(mod)))
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* @note Original name: cKF_FrameControl_ct
|
|
*/
|
|
void FrameCtrl_Init(FrameControl* frameCtrl) {
|
|
FrameCtrl_Reset(frameCtrl);
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* @note Original name: cKF_FrameControl_passCheck
|
|
*/
|
|
s32 FrameCtrl_PassCheck(FrameControl* frameCtrl, f32 t, f32* remainingTime) {
|
|
f32 curTime;
|
|
f32 speed;
|
|
|
|
*remainingTime = 0.0f;
|
|
curTime = frameCtrl->curTime;
|
|
|
|
if (t == curTime) {
|
|
return false;
|
|
}
|
|
|
|
speed = ((frameCtrl->start < frameCtrl->end) ? frameCtrl->speed : -frameCtrl->speed) * (30.0f / 20.0f);
|
|
|
|
if (((speed >= 0.0f) && (curTime < t) && (t <= curTime + speed)) ||
|
|
((speed < 0.0f) && (t < curTime) && (curTime + speed <= t))) {
|
|
|
|
*remainingTime = curTime + speed - t;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Updates a FrameControl structure whose mode is KEYFRAME_ANIM_ONCE
|
|
*
|
|
* @note Original name: cKF_FrameControl_stop_proc
|
|
*/
|
|
s32 FrameCtrl_UpdateOnce(FrameControl* frameCtrl) {
|
|
f32 remainingTime;
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Updates a FrameControl structure whose mode is KEYFRAME_ANIM_LOOP
|
|
*
|
|
* @note Original name: cKF_FrameControl_repeat_proc
|
|
*/
|
|
s32 FrameCtrl_UpdateLoop(FrameControl* frameCtrl) {
|
|
f32 remainingTime;
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
// Check if the animation is done, possibly updating curTime
|
|
if (frameCtrl->animMode == KEYFRAME_ANIM_ONCE) {
|
|
result = FrameCtrl_UpdateOnce(frameCtrl);
|
|
} else {
|
|
result = FrameCtrl_UpdateLoop(frameCtrl);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Destroys a flex-type keyframe skeleton.
|
|
*
|
|
* @note Original name unknown
|
|
*/
|
|
void Keyframe_DestroyFlex(KFSkelAnimeFlex* kfSkelAnime) {
|
|
}
|
|
|
|
void Keyframe_FlexChangeAnim(KFSkelAnimeFlex* kfSkelAnime, KeyFrameFlexSkeleton* skeleton, KeyFrameAnimation* animation,
|
|
f32 startTime, f32 endTime, f32 t, f32 speed, f32 morphFrames, s32 animMode);
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
if (kfSkelAnime->skeleton != skeleton) {
|
|
kfSkelAnime->skeleton = Lib_SegmentedToVirtual(skeleton);
|
|
}
|
|
kfSkelAnime->animation = Lib_SegmentedToVirtual(animation);
|
|
|
|
FrameCtrl_SetProperties(&kfSkelAnime->frameCtrl, startTime, endTime, kfSkelAnime->animation->frameCount, t, speed,
|
|
animMode);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
return px0 * x0 + px1 * x1 + (pv0 * v0 + pv1 * v1) * delta;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
if (t <= keyFramesOffset->frame) {
|
|
return keyFramesOffset->value;
|
|
}
|
|
if (keyFramesOffset[kfNum - 1].frame <= t) {
|
|
return keyFramesOffset[kfNum - 1].value;
|
|
}
|
|
|
|
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;
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
if (fabsf(signedDiff) < fabsf(unsignedDiff)) {
|
|
*out = rot1f + signedDiff * t;
|
|
} else {
|
|
*out = urot1f + unsignedDiff * t;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|