1bd73d69fe
- keyframeGetValue now returns the last keyframe's value for times at or beyond it, fixes a missing util/math.h include, and asserts keyframes are sorted by time; adds test/animation/test_keyframe.c - Adds mainmenu scene/UI and a battle HUD UI frame - Adds save autosave-related fields and battle scene tweaks - Adds headless test coverage for cutscenes, entities, and map areas
181 lines
7.0 KiB
C
181 lines
7.0 KiB
C
/**
|
|
* 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);
|
|
}
|