/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "dusktest.h" #include "util/memory.h" #include "time/time.h" #include "entity/entitymanager.h" #include "entity/component/animation/entityanimation.h" static void test_entityAnimationSetKeyframesDefaults(void **state) { entitymanager_t mgr; entityManagerInit(&mgr); entityid_t entity = entityManagerAdd(&mgr); componentid_t anim = entityAddComponent( &mgr, entity, COMPONENT_TYPE_ANIMATION ); static keyframe_t keyframes[] = { { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR } }; keyframe_t *channels[] = { keyframes }; uint16_t channelCounts[] = { 2 }; entityAnimationSetKeyframes(&mgr, entity, anim, channels, channelCounts, 1); assert_false(entityAnimationIsPlaying(&mgr, entity, anim)); assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 0.0f, 0.0001f ); entityManagerDispose(&mgr); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_entityAnimationPlayAdvancesAndStopsNonLooping(void **state) { entitymanager_t mgr; entityManagerInit(&mgr); entityid_t entity = entityManagerAdd(&mgr); componentid_t anim = entityAddComponent( &mgr, entity, COMPONENT_TYPE_ANIMATION ); static keyframe_t keyframes[] = { { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR } }; keyframe_t *channels[] = { keyframes }; uint16_t channelCounts[] = { 2 }; entityAnimationSetKeyframes(&mgr, entity, anim, channels, channelCounts, 1); entityAnimationPlay(&mgr, entity, anim); assert_true(entityAnimationIsPlaying(&mgr, entity, anim)); TIME.delta = 0.5f; entityUpdate(&mgr, entity); assert_true(entityAnimationIsPlaying(&mgr, entity, anim)); assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 5.0f, 0.0001f ); // Reaches the final keyframe exactly: stops, holds the final value // (rather than dividing by zero evaluating exactly at the last // keyframe -- see keyframeGetValue()). entityUpdate(&mgr, entity); assert_false(entityAnimationIsPlaying(&mgr, entity, anim)); assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 10.0f, 0.0001f ); entityManagerDispose(&mgr); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_entityAnimationLoopsAndWrapsTime(void **state) { entitymanager_t mgr; entityManagerInit(&mgr); entityid_t entity = entityManagerAdd(&mgr); componentid_t anim = entityAddComponent( &mgr, entity, COMPONENT_TYPE_ANIMATION ); static keyframe_t keyframes[] = { { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR } }; keyframe_t *channels[] = { keyframes }; uint16_t channelCounts[] = { 2 }; entityAnimationSetKeyframes(&mgr, entity, anim, channels, channelCounts, 1); entityAnimationSetLoop(&mgr, entity, anim, true); entityAnimationPlay(&mgr, entity, anim); TIME.delta = 0.7f; entityUpdate(&mgr, entity); assert_true(entityAnimationIsPlaying(&mgr, entity, anim)); assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 7.0f, 0.0001f ); // Wraps back around instead of stopping: 0.7 + 0.7 = 1.4 -> 0.4. entityUpdate(&mgr, entity); assert_true(entityAnimationIsPlaying(&mgr, entity, anim)); assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 4.0f, 0.0001f ); entityManagerDispose(&mgr); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_entityAnimationStopAndSpeed(void **state) { entitymanager_t mgr; entityManagerInit(&mgr); entityid_t entity = entityManagerAdd(&mgr); componentid_t anim = entityAddComponent( &mgr, entity, COMPONENT_TYPE_ANIMATION ); static keyframe_t keyframes[] = { { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR } }; keyframe_t *channels[] = { keyframes }; uint16_t channelCounts[] = { 2 }; entityAnimationSetKeyframes(&mgr, entity, anim, channels, channelCounts, 1); entityAnimationSetSpeed(&mgr, entity, anim, 2.0f); entityAnimationPlay(&mgr, entity, anim); TIME.delta = 0.25f; entityUpdate(&mgr, entity); // 0.25s of real time at 2x speed = 0.5 track-seconds -> halfway. assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 5.0f, 0.0001f ); entityAnimationStop(&mgr, entity, anim); assert_false(entityAnimationIsPlaying(&mgr, entity, anim)); // Stopping doesn't reset time -- value is held where it was. assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 5.0f, 0.0001f ); entityUpdate(&mgr, entity); assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 5.0f, 0.0001f ); entityManagerDispose(&mgr); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_entityAnimationMultipleChannels(void **state) { entitymanager_t mgr; entityManagerInit(&mgr); entityid_t entity = entityManagerAdd(&mgr); componentid_t anim = entityAddComponent( &mgr, entity, COMPONENT_TYPE_ANIMATION ); // X goes 0 -> 10 over 1s; Y goes 0 -> 100 over 2s -- different lengths, // sharing one timeline. static keyframe_t xKeyframes[] = { { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR } }; static keyframe_t yKeyframes[] = { { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, { .time = 2.0f, .value = 100.0f, .easing = EASING_LINEAR } }; keyframe_t *channels[] = { xKeyframes, yKeyframes }; uint16_t channelCounts[] = { 2, 2 }; entityAnimationSetKeyframes(&mgr, entity, anim, channels, channelCounts, 2); entityAnimationPlay(&mgr, entity, anim); TIME.delta = 1.0f; entityUpdate(&mgr, entity); // Still playing: the set's duration is the longer (Y's) 2s. assert_true(entityAnimationIsPlaying(&mgr, entity, anim)); // X reached/passed its own end -- held at its final value. assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 0), 10.0f, 0.0001f ); // Y is halfway through its own 2s span. assert_float_equal( entityAnimationGetValue(&mgr, entity, anim, 1), 50.0f, 0.0001f ); entityManagerDispose(&mgr); assert_int_equal(memoryGetAllocatedCount(), 0); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_entityAnimationSetKeyframesDefaults), cmocka_unit_test(test_entityAnimationPlayAdvancesAndStopsNonLooping), cmocka_unit_test(test_entityAnimationLoopsAndWrapsTime), cmocka_unit_test(test_entityAnimationStopAndSpeed), cmocka_unit_test(test_entityAnimationMultipleChannels), }; return cmocka_run_group_tests(tests, NULL, NULL); }