Remove rpg, ui, editor and scene subfolders; split scripts by platform
Delete src/dusk/rpg, src/dusk/ui, the animation/keyframe modules, the scene subfolders, the chunk asset loader and ./editor, and strip the dependent save slot, scene type, engine and test code so the main binary and tests build again. Add scene/node.c/.h skeleton and move build scripts into scripts/<platform>/. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
3948607b1e
commit
31ee7989de
464 files changed
+70
-33009
No files matched your search
@@ -7,5 +7,3 @@ include(dusktest)
|
||||
|
||||
# Tests
|
||||
dusktest(test_easing.c)
|
||||
dusktest(test_keyframe.c)
|
||||
dusktest(test_animation.c)
|
||||
@@ -1,443 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "animation/animation.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
typedef struct {
|
||||
uint16_t updateCount;
|
||||
uint16_t loopCount;
|
||||
uint16_t completeCount;
|
||||
float_t lastValues[8];
|
||||
} animationtestcallbacks_t;
|
||||
|
||||
static void testOnUpdate(
|
||||
const uint16_t layer, const float_t value, void *user
|
||||
) {
|
||||
animationtestcallbacks_t *cb = (animationtestcallbacks_t *)user;
|
||||
cb->updateCount++;
|
||||
cb->lastValues[layer] = value;
|
||||
}
|
||||
|
||||
static void testOnLoop(void *user) {
|
||||
((animationtestcallbacks_t *)user)->loopCount++;
|
||||
}
|
||||
|
||||
static void testOnComplete(void *user) {
|
||||
((animationtestcallbacks_t *)user)->completeCount++;
|
||||
}
|
||||
|
||||
static void animationTestInit(
|
||||
animation_t *anim,
|
||||
animationtestcallbacks_t *cb,
|
||||
keyframe_t *keyframes,
|
||||
uint16_t *keyframeCounts,
|
||||
const uint16_t layerCount
|
||||
) {
|
||||
memoryZero(cb, sizeof(animationtestcallbacks_t));
|
||||
animationInit(anim, keyframes, keyframeCounts, layerCount);
|
||||
anim->user = cb;
|
||||
anim->onUpdate = testOnUpdate;
|
||||
anim->onLoop = testOnLoop;
|
||||
anim->onComplete = testOnComplete;
|
||||
}
|
||||
|
||||
static void test_animationInitSingleLayerDuration(void **state) {
|
||||
keyframe_t keyframes[3] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 3 };
|
||||
animation_t anim;
|
||||
|
||||
animationInit(&anim, keyframes, keyframeCounts, 1);
|
||||
|
||||
assert_float_equal(anim.duration, 2.0f, 0.0001f);
|
||||
assert_int_equal(anim.layerCount, 1);
|
||||
assert_float_equal(anim.time, 0.0f, 0.0001f);
|
||||
assert_int_equal(anim.flags, 0);
|
||||
}
|
||||
|
||||
static void test_animationInitMultiLayerDurationIsMax(void **state) {
|
||||
// Both layers must share the same keyframe count - animationInit/
|
||||
// animationGetLayerValue flatten the array as layer * keyframeCounts[layer],
|
||||
// which only produces the correct offset when every layer's count matches.
|
||||
keyframe_t keyframes[4] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 0.0f, .value = 100.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 3.0f, .value = 300.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[2] = { 2, 2 };
|
||||
animation_t anim;
|
||||
|
||||
animationInit(&anim, keyframes, keyframeCounts, 2);
|
||||
|
||||
// Layer 1's last keyframe (time=3) is later than layer 0's (time=1).
|
||||
assert_float_equal(anim.duration, 3.0f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_animationInitNullAsserts(void **state) {
|
||||
keyframe_t keyframes[1] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 1 };
|
||||
animation_t anim;
|
||||
|
||||
expect_assert_failure(animationInit(NULL, keyframes, keyframeCounts, 1));
|
||||
expect_assert_failure(animationInit(&anim, NULL, keyframeCounts, 1));
|
||||
expect_assert_failure(animationInit(&anim, keyframes, NULL, 1));
|
||||
}
|
||||
|
||||
static void test_animationInitZeroLayerCountAsserts(void **state) {
|
||||
keyframe_t keyframes[1] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 1 };
|
||||
animation_t anim;
|
||||
|
||||
expect_assert_failure(animationInit(&anim, keyframes, keyframeCounts, 0));
|
||||
}
|
||||
|
||||
static void test_animationInitUnsortedKeyframesAsserts(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
|
||||
expect_assert_failure(animationInit(&anim, keyframes, keyframeCounts, 1));
|
||||
}
|
||||
|
||||
static void test_animationGetLayerValueSamplesEachLayerIndependently(
|
||||
void **state
|
||||
) {
|
||||
keyframe_t keyframes[4] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 0.0f, .value = 100.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[2] = { 2, 2 };
|
||||
animation_t anim;
|
||||
|
||||
animationInit(&anim, keyframes, keyframeCounts, 2);
|
||||
anim.time = 1.0f;
|
||||
|
||||
assert_float_equal(animationGetLayerValue(&anim, 0), 10.0f, 0.0001f);
|
||||
assert_float_equal(animationGetLayerValue(&anim, 1), 50.0f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_animationGetLayerValueOutOfBoundsAsserts(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
|
||||
animationInit(&anim, keyframes, keyframeCounts, 1);
|
||||
expect_assert_failure(animationGetLayerValue(&anim, 1));
|
||||
}
|
||||
|
||||
static void test_animationUpdateAdvancesTimeAndCallsOnUpdate(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
animationUpdate(&anim, 0.5f);
|
||||
|
||||
assert_float_equal(anim.time, 0.5f, 0.0001f);
|
||||
assert_int_equal(cb.updateCount, 1);
|
||||
assert_float_equal(cb.lastValues[0], 5.0f, 0.0001f);
|
||||
assert_int_equal(cb.completeCount, 0);
|
||||
}
|
||||
|
||||
static void test_animationUpdatePlainPlayStopsAtEndAndFiresOnceOnly(
|
||||
void **state
|
||||
) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
|
||||
// Overshoot the duration in a single update.
|
||||
animationUpdate(&anim, 5.0f);
|
||||
assert_float_equal(anim.time, 2.0f, 0.0001f);
|
||||
assert_float_equal(cb.lastValues[0], 20.0f, 0.0001f);
|
||||
assert_int_equal(cb.completeCount, 1);
|
||||
|
||||
// Further updates must not clamp past the end or refire onComplete.
|
||||
animationUpdate(&anim, 1.0f);
|
||||
assert_float_equal(anim.time, 2.0f, 0.0001f);
|
||||
assert_int_equal(cb.completeCount, 1);
|
||||
}
|
||||
|
||||
static void test_animationUpdateExactLandingOnEndFiresOnComplete(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
|
||||
// deltaTime exactly matches the remaining distance to the boundary.
|
||||
animationUpdate(&anim, 2.0f);
|
||||
assert_float_equal(anim.time, 2.0f, 0.0001f);
|
||||
assert_int_equal(cb.completeCount, 1);
|
||||
}
|
||||
|
||||
static void test_animationUpdateLoopWrapsAndFiresOnLoop(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
anim.flags = ANIMATION_FLAG_LOOP;
|
||||
|
||||
// 2.5 durations worth of time: wraps twice, lands at 1.0 into the third.
|
||||
animationUpdate(&anim, 5.0f);
|
||||
|
||||
assert_float_equal(anim.time, 1.0f, 0.0001f);
|
||||
assert_int_equal(cb.loopCount, 2);
|
||||
assert_int_equal(cb.completeCount, 0);
|
||||
}
|
||||
|
||||
static void test_animationUpdateLoopStopEndCompletesInsteadOfWrapping(
|
||||
void **state
|
||||
) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
anim.flags = ANIMATION_FLAG_LOOP | ANIMATION_FLAG_STOP_END;
|
||||
|
||||
animationUpdate(&anim, 5.0f);
|
||||
|
||||
assert_float_equal(anim.time, 2.0f, 0.0001f);
|
||||
assert_int_equal(cb.loopCount, 0);
|
||||
assert_int_equal(cb.completeCount, 1);
|
||||
}
|
||||
|
||||
static void test_animationUpdateReversePlaysBackward(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
anim.flags = ANIMATION_FLAG_REVERSE;
|
||||
anim.time = anim.duration;
|
||||
|
||||
animationUpdate(&anim, 0.5f);
|
||||
|
||||
assert_float_equal(anim.time, 1.5f, 0.0001f);
|
||||
assert_float_equal(cb.lastValues[0], 15.0f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_animationUpdateReverseStopsAtBeginning(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
anim.flags = ANIMATION_FLAG_REVERSE;
|
||||
anim.time = anim.duration;
|
||||
|
||||
animationUpdate(&anim, 10.0f);
|
||||
|
||||
assert_float_equal(anim.time, 0.0f, 0.0001f);
|
||||
assert_float_equal(cb.lastValues[0], 0.0f, 0.0001f);
|
||||
assert_int_equal(cb.completeCount, 1);
|
||||
}
|
||||
|
||||
static void test_animationUpdatePingpongBouncesForeverWithoutStopFlags(
|
||||
void **state
|
||||
) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
anim.flags = ANIMATION_FLAG_PINGPONG;
|
||||
|
||||
// 0 -> 2 (2s consumed), reflect, backward 1s more -> time=1, going backward.
|
||||
animationUpdate(&anim, 3.0f);
|
||||
assert_float_equal(anim.time, 1.0f, 0.0001f);
|
||||
assert_true((anim.flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) != 0);
|
||||
assert_int_equal(cb.completeCount, 0);
|
||||
|
||||
// Continue backward to 0 (1s), reflect, forward 1s more -> time=1, forward.
|
||||
animationUpdate(&anim, 2.0f);
|
||||
assert_float_equal(anim.time, 1.0f, 0.0001f);
|
||||
assert_true((anim.flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) == 0);
|
||||
assert_int_equal(cb.completeCount, 0);
|
||||
}
|
||||
|
||||
static void test_animationUpdatePingpongStopEndCompletesAtEnd(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
anim.flags = ANIMATION_FLAG_PINGPONG | ANIMATION_FLAG_STOP_END;
|
||||
|
||||
animationUpdate(&anim, 10.0f);
|
||||
|
||||
assert_float_equal(anim.time, 2.0f, 0.0001f);
|
||||
assert_int_equal(cb.completeCount, 1);
|
||||
}
|
||||
|
||||
static void test_animationUpdatePingpongStopBeginningCompletesAtStart(
|
||||
void **state
|
||||
) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1);
|
||||
anim.flags = ANIMATION_FLAG_PINGPONG | ANIMATION_FLAG_STOP_BEGINNING;
|
||||
anim.time = anim.duration;
|
||||
anim.flags |= ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD;
|
||||
|
||||
animationUpdate(&anim, 10.0f);
|
||||
|
||||
assert_float_equal(anim.time, 0.0f, 0.0001f);
|
||||
assert_int_equal(cb.completeCount, 1);
|
||||
}
|
||||
|
||||
static void test_animationUpdateMultiLayerCallsOnUpdatePerLayer(void **state) {
|
||||
keyframe_t keyframes[4] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 0.0f, .value = 100.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[2] = { 2, 2 };
|
||||
animation_t anim;
|
||||
animationtestcallbacks_t cb;
|
||||
|
||||
animationTestInit(&anim, &cb, keyframes, keyframeCounts, 2);
|
||||
animationUpdate(&anim, 1.0f);
|
||||
|
||||
assert_int_equal(cb.updateCount, 2);
|
||||
assert_float_equal(cb.lastValues[0], 10.0f, 0.0001f);
|
||||
assert_float_equal(cb.lastValues[1], 50.0f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_animationUpdateNullAsserts(void **state) {
|
||||
expect_assert_failure(animationUpdate(NULL, 1.0f));
|
||||
}
|
||||
|
||||
static void test_animationUpdateNegativeDeltaTimeAsserts(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
|
||||
animationInit(&anim, keyframes, keyframeCounts, 1);
|
||||
expect_assert_failure(animationUpdate(&anim, -1.0f));
|
||||
}
|
||||
|
||||
static void test_animationInitZeroDurationAsserts(void **state) {
|
||||
keyframe_t keyframes[1] = {
|
||||
{ .time = 0.0f, .value = 5.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 1 };
|
||||
animation_t anim;
|
||||
|
||||
expect_assert_failure(animationInit(&anim, keyframes, keyframeCounts, 1));
|
||||
}
|
||||
|
||||
static void test_animationUpdateLoopAndPingpongTogetherAsserts(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
uint16_t keyframeCounts[1] = { 2 };
|
||||
animation_t anim;
|
||||
|
||||
animationInit(&anim, keyframes, keyframeCounts, 1);
|
||||
anim.flags = ANIMATION_FLAG_LOOP | ANIMATION_FLAG_PINGPONG;
|
||||
expect_assert_failure(animationUpdate(&anim, 0.1f));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_animationInitSingleLayerDuration),
|
||||
cmocka_unit_test(test_animationInitMultiLayerDurationIsMax),
|
||||
cmocka_unit_test(test_animationInitNullAsserts),
|
||||
cmocka_unit_test(test_animationInitZeroLayerCountAsserts),
|
||||
cmocka_unit_test(test_animationInitUnsortedKeyframesAsserts),
|
||||
cmocka_unit_test(test_animationGetLayerValueSamplesEachLayerIndependently),
|
||||
cmocka_unit_test(test_animationGetLayerValueOutOfBoundsAsserts),
|
||||
cmocka_unit_test(test_animationUpdateAdvancesTimeAndCallsOnUpdate),
|
||||
cmocka_unit_test(test_animationUpdatePlainPlayStopsAtEndAndFiresOnceOnly),
|
||||
cmocka_unit_test(test_animationUpdateExactLandingOnEndFiresOnComplete),
|
||||
cmocka_unit_test(test_animationUpdateLoopWrapsAndFiresOnLoop),
|
||||
cmocka_unit_test(test_animationUpdateLoopStopEndCompletesInsteadOfWrapping),
|
||||
cmocka_unit_test(test_animationUpdateReversePlaysBackward),
|
||||
cmocka_unit_test(test_animationUpdateReverseStopsAtBeginning),
|
||||
cmocka_unit_test(
|
||||
test_animationUpdatePingpongBouncesForeverWithoutStopFlags
|
||||
),
|
||||
cmocka_unit_test(test_animationUpdatePingpongStopEndCompletesAtEnd),
|
||||
cmocka_unit_test(test_animationUpdatePingpongStopBeginningCompletesAtStart),
|
||||
cmocka_unit_test(test_animationUpdateMultiLayerCallsOnUpdatePerLayer),
|
||||
cmocka_unit_test(test_animationUpdateNullAsserts),
|
||||
cmocka_unit_test(test_animationUpdateNegativeDeltaTimeAsserts),
|
||||
cmocka_unit_test(test_animationInitZeroDurationAsserts),
|
||||
cmocka_unit_test(test_animationUpdateLoopAndPingpongTogetherAsserts),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "animation/keyframe.h"
|
||||
|
||||
static void test_keyframeGetValueSingleSegmentLinear(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 0.0f), 0.0f, 0.0001f);
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 0.25f), 2.5f, 0.0001f);
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 0.5f), 5.0f, 0.0001f);
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 0.75f), 7.5f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueMultiSegment(void **state) {
|
||||
keyframe_t keyframes[3] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
// Within the first segment.
|
||||
assert_float_equal(keyframeGetValue(keyframes, 3, 0.5f), 5.0f, 0.0001f);
|
||||
|
||||
// Exactly on the interior keyframe, resolved as the end of segment one.
|
||||
assert_float_equal(keyframeGetValue(keyframes, 3, 1.0f), 10.0f, 0.0001f);
|
||||
|
||||
// Within the second segment.
|
||||
assert_float_equal(keyframeGetValue(keyframes, 3, 1.5f), 15.0f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueDescendingValues(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 100.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 0.0f), 100.0f, 0.0001f);
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 0.5f), 50.0f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueAppliesEasing(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_IN_QUAD },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
// EASING_IN_QUAD is t * t, so halfway through time should be a quarter of
|
||||
// the way through the value range, not half.
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 0.5f), 2.5f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueNonZeroStartTime(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 5.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 10.0f, .value = 100.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 5.0f), 0.0f, 0.0001f);
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 7.5f), 50.0f, 0.0001f);
|
||||
}
|
||||
|
||||
// NOTE: keyframeGetValue does not clamp to the first keyframe's value when
|
||||
// queried before the first keyframe's time - the start and end pointers
|
||||
// collapse onto the same keyframe, so the interpolation divides by a zero
|
||||
// time delta. This test documents that current behavior rather than
|
||||
// asserting it is desirable; flag to the maintainer if this should instead
|
||||
// clamp like the last-keyframe case does.
|
||||
static void test_keyframeGetValueBeforeFirstKeyframeIsNaN(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 3.0f, .value = 30.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
assert_true(isnan(keyframeGetValue(keyframes, 2, 0.0f)));
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueAtOrAfterLastKeyframeClampsToLastValue(
|
||||
void **state
|
||||
) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 1.0f), 10.0f, 0.0001f);
|
||||
assert_float_equal(keyframeGetValue(keyframes, 2, 2.0f), 10.0f, 0.0001f);
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueSingleKeyframeAtOrAfterClampsToValue(
|
||||
void **state
|
||||
) {
|
||||
keyframe_t keyframes[1] = {
|
||||
{ .time = 5.0f, .value = 42.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
assert_float_equal(keyframeGetValue(keyframes, 1, 5.0f), 42.0f, 0.0001f);
|
||||
assert_float_equal(keyframeGetValue(keyframes, 1, 10.0f), 42.0f, 0.0001f);
|
||||
|
||||
// Before the single keyframe's time is still the documented NaN case.
|
||||
assert_true(isnan(keyframeGetValue(keyframes, 1, 0.0f)));
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueNullKeyframesAsserts(void **state) {
|
||||
expect_assert_failure(keyframeGetValue(NULL, 1, 0.0f));
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueZeroCountAsserts(void **state) {
|
||||
keyframe_t keyframes[1] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
expect_assert_failure(keyframeGetValue(keyframes, 0, 0.0f));
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueNegativeTimeAsserts(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
expect_assert_failure(keyframeGetValue(keyframes, 2, -1.0f));
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueUnsortedKeyframesAsserts(void **state) {
|
||||
keyframe_t keyframes[2] = {
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
expect_assert_failure(keyframeGetValue(keyframes, 2, 0.0f));
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueLaterKeyframeOutOfOrderAsserts(void **state) {
|
||||
// The first pair is sorted, but the third keyframe is out of order - the
|
||||
// check must walk the whole array, not just the first pair.
|
||||
keyframe_t keyframes[3] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 0.5f, .value = 5.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
expect_assert_failure(keyframeGetValue(keyframes, 3, 0.0f));
|
||||
}
|
||||
|
||||
static void test_keyframeGetValueEqualConsecutiveTimesDoesNotAssert(
|
||||
void **state
|
||||
) {
|
||||
// Equal (non-decreasing) times are allowed - only strictly decreasing
|
||||
// times should trigger the sorted check.
|
||||
keyframe_t keyframes[3] = {
|
||||
{ .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 0.0f, .value = 5.0f, .easing = EASING_LINEAR },
|
||||
{ .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR },
|
||||
};
|
||||
|
||||
assert_float_equal(keyframeGetValue(keyframes, 3, 0.0f), 5.0f, 0.0001f);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_keyframeGetValueSingleSegmentLinear),
|
||||
cmocka_unit_test(test_keyframeGetValueMultiSegment),
|
||||
cmocka_unit_test(test_keyframeGetValueDescendingValues),
|
||||
cmocka_unit_test(test_keyframeGetValueAppliesEasing),
|
||||
cmocka_unit_test(test_keyframeGetValueNonZeroStartTime),
|
||||
cmocka_unit_test(test_keyframeGetValueBeforeFirstKeyframeIsNaN),
|
||||
cmocka_unit_test(
|
||||
test_keyframeGetValueAtOrAfterLastKeyframeClampsToLastValue
|
||||
),
|
||||
cmocka_unit_test(test_keyframeGetValueSingleKeyframeAtOrAfterClampsToValue),
|
||||
cmocka_unit_test(test_keyframeGetValueNullKeyframesAsserts),
|
||||
cmocka_unit_test(test_keyframeGetValueZeroCountAsserts),
|
||||
cmocka_unit_test(test_keyframeGetValueNegativeTimeAsserts),
|
||||
cmocka_unit_test(test_keyframeGetValueUnsortedKeyframesAsserts),
|
||||
cmocka_unit_test(test_keyframeGetValueLaterKeyframeOutOfOrderAsserts),
|
||||
cmocka_unit_test(test_keyframeGetValueEqualConsecutiveTimesDoesNotAssert),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
Reference in new issue
Block a user