summaryrefslogtreecommitdiff
path: root/src/code
diff options
context:
space:
mode:
authorEllipticEllipsis <elliptic.ellipsis@gmail.com>2022-06-19 03:38:10 +0100
committerGitHub <noreply@github.com>2022-06-19 03:38:10 +0100
commitaf0123de1eb774aebb88b26e5d719b3406f51eb9 (patch)
treeef80dcb96198082164637f104245c5a6d4e79bd1 /src/code
parent5ae4ef87cd8b1bd94cdc63174bfe6bd3f8183723 (diff)
`z_fcurve_data` OK, match last function in `z_fcurve_data_skelanime`, document SkelCurve system (#776)
* Match SkelCurve_Update Co-authored-by: Kelebek1 <34639600+Kelebek1@users.noreply.github.com> * Match and document z_fcurve_data * Begin documenting SkelCurve * More documentation * Deformat header * Pluralise knotCount * Sort out jointTable * Rename frameCount * Format * More documentation * Cleanup on DemoEffect * Remove space on typedef * Format, couple of fixes in the header * Review * Oops * Fix EnBox, DemoTreLgt, use macros in EnTorch Co-authored-by: Kelebek1 <34639600+Kelebek1@users.noreply.github.com>
Diffstat (limited to 'src/code')
-rw-r--r--src/code/z_fcurve_data.c85
-rw-r--r--src/code/z_fcurve_data_skelanime.c247
-rw-r--r--src/code/z_skelanime.c4
3 files changed, 236 insertions, 100 deletions
diff --git a/src/code/z_fcurve_data.c b/src/code/z_fcurve_data.c
index a0bf887e1..5c0aa066a 100644
--- a/src/code/z_fcurve_data.c
+++ b/src/code/z_fcurve_data.c
@@ -1,5 +1,86 @@
+/**
+ * @file z_fcurve_data.c
+ * @brief Interpolation functions for use with Curve SkelAnime
+ */
#include "global.h"
+#include "z64curve.h"
-#pragma GLOBAL_ASM("asm/non_matchings/code/z_fcurve_data/func_800F23E0.s")
+#define FCURVE_INTERP_CUBIC 0 // Interpolate using a Hermite cubic spline
+#define FCURVE_INTERP_NONE 1 // Return the value at the left endpoint instead of interpolating
+#define FCURVE_INTERP_LINEAR 2 // Interpolate linearly
-#pragma GLOBAL_ASM("asm/non_matchings/code/z_fcurve_data/func_800F2478.s")
+/**
+ * Hermite cubic spline interpolation between two endpoints, a,b. More information available at
+ * https://en.wikipedia.org/wiki/Cubic_Hermite_spline
+ *
+ * @param t interpolation parameter rescaled to lie in [0,1], (x-a)/(b-a)
+ * @param interval distance (b-a) between the endpoints
+ * @param y0 p(a)
+ * @param y1 p(b)
+ * @param m0 p'(a)
+ * @param m1 p'(b)
+ * @return f32 p(t), value of the cubic interpolating polynomial
+ */
+f32 Curve_CubicHermiteSpline(f32 t, f32 interval, f32 y0, f32 y1, f32 m0, f32 m1) {
+ f32 t2 = t * t;
+ f32 t3 = t2 * t;
+ f32 t3x2 = t3 * 2.0f;
+ f32 t2x3 = t2 * 3.0f;
+
+ // Hermite basis cubics h_{ij} satisfy h_{ij}^{(j)}(i) = 1, the other three values being 0
+ f32 h00 = t3x2 - t2x3 + 1.0f; // h_{00}(t) = 2t^3 - 3t^2 + 1
+ f32 h01 = t2x3 - t3x2; // h_{01}(t) = 3t^2 - 2t^3
+ f32 h10 = t3 - t2 * 2.0f + t; // h_{10}(t) = t^3 - 2t^2 + t
+ f32 h11 = t3 - t2; // h_{11}(t) = t^3 - t^2
+
+ f32 ret = h00 * y0;
+
+ ret += h01 * y1;
+ ret += h10 * m0 * interval;
+ ret += h11 * m1 * interval;
+
+ return ret;
+}
+
+/**
+ * Interpolates based on an array of CurveInterpKnot.
+ *
+ * @param x point at which to interpolate.
+ * @param knots Beginning of CurveInterpKnot array to use.
+ * @param knotCount number of knots to read from the array.
+ * @return f32 interpolated value
+ */
+f32 Curve_Interpolate(f32 x, CurveInterpKnot* knots, s32 knotCount) {
+ // If outside the entire interpolation interval, return the value at the near endpoint.
+ if (x <= knots[0].abscissa) {
+ return knots[0].ordinate;
+ } else if (x >= knots[knotCount - 1].abscissa) {
+ return knots[knotCount - 1].ordinate;
+ } else {
+ s32 cur;
+
+ for (cur = 0;; cur++) {
+ s32 next = cur + 1;
+
+ // Find the subinterval in which x lies
+ if (x < knots[next].abscissa) {
+ if (knots[cur].flags & FCURVE_INTERP_NONE) {
+ // No interpolation
+ return knots[cur].ordinate;
+ } else if (knots[cur].flags & FCURVE_INTERP_LINEAR) {
+ // Linear interpolation
+ return knots[cur].ordinate +
+ ((x - (f32)knots[cur].abscissa) / ((f32)knots[next].abscissa - (f32)knots[cur].abscissa)) *
+ (knots[next].ordinate - knots[cur].ordinate);
+ } else {
+ // Cubic interpolation
+ f32 diff = (f32)knots[next].abscissa - (f32)knots[cur].abscissa;
+ f32 t = (x - (f32)knots[cur].abscissa) / ((f32)knots[next].abscissa - (f32)knots[cur].abscissa);
+
+ return Curve_CubicHermiteSpline(t, diff * (1.0f / 30.0f), knots[cur].ordinate, knots[next].ordinate,
+ knots[cur].rightGradient, knots[next].leftGradient);
+ }
+ }
+ }
+ }
+}
diff --git a/src/code/z_fcurve_data_skelanime.c b/src/code/z_fcurve_data_skelanime.c
index c8eb927fa..b40a1c351 100644
--- a/src/code/z_fcurve_data_skelanime.c
+++ b/src/code/z_fcurve_data_skelanime.c
@@ -1,130 +1,187 @@
+/**
+ * @file z_fcurve_data_skelanime.c
+ * @brief Curve skeleton animation system
+ *
+ * A curve skeleton has a fixed number of limbs, each of which has 9 properties that may be changed by the animation:
+ * - 3 scales,
+ * - 3 rotations,
+ * - 3 positions
+ * (note the position is stored in the animations instead of being stored in the limbs like SkelAnime would). Otherwise
+ * the structure is similar to an ordinary SkelAnime-compatible skeleton.
+ *
+ * The animations are significantly more complex than SkelAnime. A curve animation consists of 4 parts:
+ * - a header (CurveAnimationHeader)
+ * - a list of counts, one for each of the 9 properties of each limb (u8)
+ * - a list of interpolation data (CurveInterpKnot). The length is the sum of the counts.
+ * - a list of constant data (s16[9]). The length is the number of 0s in counts.
+ *
+ * If the interpolation count for a property is 0, the value of the property is copied from the next number in the
+ * constant data; there are no gaps for nonzero interpolation count.
+ * If the interpolation count N for a property is larger than 0, the next N elements of the interpolation data array
+ * are used to interpolate the value of the property, using Curve_Interpolate.
+ *
+ * Curve limbs may use LOD:
+ * - lower detail draws only the first displaylist
+ * - higher detail draws both.
+ */
+
#include "global.h"
+#include "z64curve.h"
-void SkelCurve_Clear(SkelAnimeCurve* skelCurve) {
+void SkelCurve_Clear(SkelCurve* skelCurve) {
skelCurve->limbCount = 0;
- skelCurve->limbList = NULL;
- skelCurve->transUpdIdx = NULL;
- skelCurve->animCurFrame = 0.0f;
- skelCurve->animSpeed = 0.0f;
- skelCurve->animFinalFrame = 0.0f;
- skelCurve->unk0C = 0.0f;
- skelCurve->transforms = NULL;
+ skelCurve->skeleton = NULL;
+ skelCurve->animation = NULL;
+ skelCurve->curFrame = 0.0f;
+ skelCurve->playSpeed = 0.0f;
+ skelCurve->endFrame = 0.0f;
+ skelCurve->unk_0C = 0.0f;
+ skelCurve->jointTable = NULL;
}
-s32 SkelCurve_Init(GlobalContext* globalCtx, SkelAnimeCurve* skelCurve, SkelCurveLimbList* limbListSeg,
- TransformUpdateIndex* transUpdIdx) {
+/**
+ * Initialises the SkelCurve struct and mallocs the joint table.
+ *
+ * @return bool always true
+ */
+s32 SkelCurve_Init(GlobalContext* globalCtx, SkelCurve* skelCurve, CurveSkeletonHeader* skeletonHeaderSeg,
+ CurveAnimationHeader* animation) {
SkelCurveLimb** limbs;
- SkelCurveLimbList* limbList = Lib_SegmentedToVirtual(limbListSeg);
+ CurveSkeletonHeader* skeletonHeader = Lib_SegmentedToVirtual(skeletonHeaderSeg);
- skelCurve->limbCount = limbList->limbCount;
- skelCurve->limbList = Lib_SegmentedToVirtual(limbList->limbs);
+ skelCurve->limbCount = skeletonHeader->limbCount;
+ skelCurve->skeleton = Lib_SegmentedToVirtual(skeletonHeader->limbs);
- skelCurve->transforms = ZeldaArena_Malloc(sizeof(*skelCurve->transforms) * skelCurve->limbCount);
+ skelCurve->jointTable = ZeldaArena_Malloc(sizeof(*skelCurve->jointTable) * skelCurve->limbCount);
- do {
- skelCurve->animCurFrame = 0.0f;
- } while (0);
- return 1;
+ skelCurve->curFrame = 0.0f;
+ return true;
}
-void SkelCurve_Destroy(GlobalContext* globalCtx, SkelAnimeCurve* skelCurve) {
- if (skelCurve->transforms != NULL) {
- ZeldaArena_Free(skelCurve->transforms);
+/**
+ * Frees the joint table.
+ */
+void SkelCurve_Destroy(GlobalContext* globalCtx, SkelCurve* skelCurve) {
+ if (skelCurve->jointTable != NULL) {
+ ZeldaArena_Free(skelCurve->jointTable);
}
}
-void SkelCurve_SetAnim(SkelAnimeCurve* skelCurve, TransformUpdateIndex* transUpdIdx, f32 arg2, f32 animFinalFrame,
- f32 animCurFrame, f32 animSpeed) {
- skelCurve->unk0C = arg2 - skelCurve->animSpeed;
- skelCurve->animFinalFrame = animFinalFrame;
- skelCurve->animCurFrame = animCurFrame;
- skelCurve->animSpeed = animSpeed;
- skelCurve->transUpdIdx = transUpdIdx;
+void SkelCurve_SetAnim(SkelCurve* skelCurve, CurveAnimationHeader* animation, f32 arg2, f32 endFrame, f32 curFrame,
+ f32 playSpeed) {
+ skelCurve->unk_0C = arg2 - skelCurve->playSpeed;
+ skelCurve->endFrame = endFrame;
+ skelCurve->curFrame = curFrame;
+ skelCurve->playSpeed = playSpeed;
+ skelCurve->animation = animation;
}
-#ifdef NON_MATCHING
-/* Should be functionally equivalent, also migrating rodata makes it a lot cleaner */
-s32 SkelCurve_Update(GlobalContext* globalCtx, SkelAnimeCurve* skelCurve) {
- s16* transforms;
- u8* transformRefIdx;
- TransformUpdateIndex* transformIndex;
- u16* transformCopyValues;
- s32 i;
- s32 ret = 0;
- s32 k;
- TransformData* transData;
- f32 transformValue;
- s32 j;
-
- transformIndex = Lib_SegmentedToVirtual(skelCurve->transUpdIdx);
- transformRefIdx = Lib_SegmentedToVirtual(transformIndex->refIndex);
- transData = Lib_SegmentedToVirtual(transformIndex->transformData);
- transformCopyValues = Lib_SegmentedToVirtual(transformIndex->copyValues);
- transforms = (s16*)skelCurve->transforms;
-
- skelCurve->animCurFrame += skelCurve->animSpeed * (globalCtx->state.framerateDivisor * 0.5f);
-
- if ((skelCurve->animSpeed >= 0.0f && skelCurve->animCurFrame > skelCurve->animFinalFrame) ||
- (skelCurve->animSpeed < 0.0f && skelCurve->animCurFrame < skelCurve->animFinalFrame)) {
- skelCurve->animCurFrame = skelCurve->animFinalFrame;
- ret = 1;
+typedef enum {
+ /* 0 */ SKELCURVE_VEC_TYPE_SCALE,
+ /* 1 */ SKELCURVE_VEC_TYPE_ROTATION,
+ /* 2 */ SKELCURVE_VEC_TYPE_POSIITON,
+ /* 3 */ SKELCURVE_VEC_TYPE_MAX
+} SkelCurveVecType;
+
+#define SKELCURVE_SCALE_SCALE 1024.0f
+#define SKELCURVE_SCALE_POSITION 100
+
+/**
+ * The only animation updating function.
+ *
+ * @return bool true when the animation has finished.
+ */
+s32 SkelCurve_Update(GlobalContext* globalCtx, SkelCurve* skelCurve) {
+ s16* jointData;
+ u8* knotCounts;
+ CurveAnimationHeader* animation;
+ u16* constantData;
+ s32 curLimb;
+ s32 ret = false;
+ s32 coord;
+ CurveInterpKnot* startKnot;
+ s32 vecType;
+
+ animation = Lib_SegmentedToVirtual(skelCurve->animation);
+ knotCounts = Lib_SegmentedToVirtual(animation->knotCounts);
+ startKnot = Lib_SegmentedToVirtual(animation->interpolationData);
+ constantData = Lib_SegmentedToVirtual(animation->constantData);
+ jointData = *skelCurve->jointTable;
+
+ skelCurve->curFrame += skelCurve->playSpeed * ((s32)globalCtx->state.framerateDivisor * 0.5f);
+
+ if (((skelCurve->playSpeed >= 0.0f) && (skelCurve->curFrame > skelCurve->endFrame)) ||
+ ((skelCurve->playSpeed < 0.0f) && (skelCurve->curFrame < skelCurve->endFrame))) {
+ skelCurve->curFrame = skelCurve->endFrame;
+ ret = true;
}
- for (i = 0; i < skelCurve->limbCount; i++) {
- for (j = 0; j < 3; j++) {
- for (k = 0; k < 3; k++, transformRefIdx++, transforms++) {
- if (*transformRefIdx == 0) {
- transformValue = *transformCopyValues;
- *transforms = transformValue;
- transformCopyValues++;
+ for (curLimb = 0; curLimb < skelCurve->limbCount; curLimb++) {
+
+ // scale/rotation/position
+ for (vecType = SKELCURVE_VEC_TYPE_SCALE; vecType < SKELCURVE_VEC_TYPE_MAX; vecType++) {
+
+ // x/y/z
+ for (coord = 0; coord < 3; coord++) {
+ f32 transformValue;
+
+ if (*knotCounts == 0) {
+ transformValue = *constantData;
+ *jointData = transformValue;
+ constantData++;
} else {
- transformValue = func_800F2478(skelCurve->animCurFrame, transData, *transformRefIdx);
- transData += *transformRefIdx;
- if (j == 0) {
- *transforms = transformValue * 1024.0f;
- } else if (j == 1) {
- *transforms = transformValue * (32768.0f / 180.0f);
- } else {
- *transforms = transformValue * 100.0f;
+ transformValue = Curve_Interpolate(skelCurve->curFrame, startKnot, *knotCounts);
+ startKnot += *knotCounts;
+ if (vecType == SKELCURVE_VEC_TYPE_SCALE) {
+ // Rescaling allows for more refined scaling using an s16
+ *jointData = transformValue * SKELCURVE_SCALE_SCALE;
+ } else if (vecType == SKELCURVE_VEC_TYPE_ROTATION) {
+ // Convert value from degrees to a binary angle
+ *jointData = DEG_TO_BINANG(transformValue);
+ } else { // SKELCURVE_VEC_TYPE_POSIITON
+ // Model to world scale conversion
+ *jointData = transformValue * SKELCURVE_SCALE_POSITION;
}
}
+ knotCounts++;
+ jointData++;
}
}
}
return ret;
}
-#else
-#pragma GLOBAL_ASM("asm/non_matchings/code/z_fcurve_data_skelanime/SkelCurve_Update.s")
-#endif
-void SkelCurve_DrawLimb(GlobalContext* globalCtx, s32 limbIndex, SkelAnimeCurve* skelCurve,
+/**
+ * Recursively draws limbs with appropriate properties.
+ */
+void SkelCurve_DrawLimb(GlobalContext* globalCtx, s32 limbIndex, SkelCurve* skelCurve,
OverrideCurveLimbDraw overrideLimbDraw, PostCurveLimbDraw postLimbDraw, s32 lod, Actor* thisx) {
- SkelCurveLimb* limb = Lib_SegmentedToVirtual(skelCurve->limbList[limbIndex]);
+ SkelCurveLimb* limb = Lib_SegmentedToVirtual(skelCurve->skeleton[limbIndex]);
OPEN_DISPS(globalCtx->state.gfxCtx);
Matrix_Push();
- if (overrideLimbDraw == NULL ||
- (overrideLimbDraw != NULL && overrideLimbDraw(globalCtx, skelCurve, limbIndex, thisx))) {
+ if ((overrideLimbDraw == NULL) ||
+ ((overrideLimbDraw != NULL) && overrideLimbDraw(globalCtx, skelCurve, limbIndex, thisx))) {
Vec3f scale;
Vec3s rot;
Vec3f pos;
Gfx* dList;
- Vec3s* transform = (Vec3s*)&skelCurve->transforms[limbIndex];
-
- scale.x = transform->x / 1024.0f;
- scale.y = transform->y / 1024.0f;
- scale.z = transform->z / 1024.0f;
- transform++;
- rot.x = transform->x;
- rot.y = transform->y;
- rot.z = transform->z;
- transform++;
- pos.x = transform->x;
- pos.y = transform->y;
- pos.z = transform->z;
+ s16* jointData = skelCurve->jointTable[limbIndex];
+
+ scale.x = jointData[0] / SKELCURVE_SCALE_SCALE;
+ scale.y = jointData[1] / SKELCURVE_SCALE_SCALE;
+ scale.z = jointData[2] / SKELCURVE_SCALE_SCALE;
+ jointData += 3;
+ rot.x = jointData[0];
+ rot.y = jointData[1];
+ rot.z = jointData[2];
+ jointData += 3;
+ pos.x = jointData[0];
+ pos.y = jointData[1];
+ pos.z = jointData[2];
Matrix_TranslateRotateZYX(&pos, &rot);
Matrix_Scale(scale.x, scale.y, scale.z, MTXMODE_APPLY);
@@ -160,22 +217,22 @@ void SkelCurve_DrawLimb(GlobalContext* globalCtx, s32 limbIndex, SkelAnimeCurve*
postLimbDraw(globalCtx, skelCurve, limbIndex, thisx);
}
- if (limb->firstChildIdx != LIMB_DONE) {
- SkelCurve_DrawLimb(globalCtx, limb->firstChildIdx, skelCurve, overrideLimbDraw, postLimbDraw, lod, thisx);
+ if (limb->child != LIMB_DONE) {
+ SkelCurve_DrawLimb(globalCtx, limb->child, skelCurve, overrideLimbDraw, postLimbDraw, lod, thisx);
}
Matrix_Pop();
- if (limb->nextLimbIdx != LIMB_DONE) {
- SkelCurve_DrawLimb(globalCtx, limb->nextLimbIdx, skelCurve, overrideLimbDraw, postLimbDraw, lod, thisx);
+ if (limb->sibling != LIMB_DONE) {
+ SkelCurve_DrawLimb(globalCtx, limb->sibling, skelCurve, overrideLimbDraw, postLimbDraw, lod, thisx);
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
-void SkelCurve_Draw(Actor* actor, GlobalContext* globalCtx, SkelAnimeCurve* skelCurve,
+void SkelCurve_Draw(Actor* actor, GlobalContext* globalCtx, SkelCurve* skelCurve,
OverrideCurveLimbDraw overrideLimbDraw, PostCurveLimbDraw postLimbDraw, s32 lod, Actor* thisx) {
- if (skelCurve->transforms != NULL) {
+ if (skelCurve->jointTable != NULL) {
SkelCurve_DrawLimb(globalCtx, 0, skelCurve, overrideLimbDraw, postLimbDraw, lod, thisx);
}
}
diff --git a/src/code/z_skelanime.c b/src/code/z_skelanime.c
index 0a3334bc8..4c77b3669 100644
--- a/src/code/z_skelanime.c
+++ b/src/code/z_skelanime.c
@@ -627,9 +627,7 @@ void SkelAnime_GetFrameData(AnimationHeader* animation, s32 frame, s32 limbCount
for (i = 0; i < limbCount; i++) {
// Debug prints here, this is needed to prevent loop unrolling
- if (0) {
- if (0) {};
- }
+ if ((frameTable == NULL) || (jointIndices == NULL) || (dynamicData == NULL)) {}
frameTable->x = jointIndices->x >= staticIndexMax ? dynamicData[jointIndices->x] : frameData[jointIndices->x];
frameTable->y = jointIndices->y >= staticIndexMax ? dynamicData[jointIndices->y] : frameData[jointIndices->y];
frameTable->z = jointIndices->z >= staticIndexMax ? dynamicData[jointIndices->z] : frameData[jointIndices->z];