// Copyright (c) 2022 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT #pragma once #include "Animation.hpp" #include "easing.hpp" #include "util/mathutils.hpp" namespace Dawn { template struct SimpleKeyframe { float_t time; T value; }; template struct SimpleAnimation : public Animation { protected: /** * Function for subclasses to be "notified" when the value has been * modified. */ virtual void onValueModified() { } /** * Sorts internal keyframes by their time to make them conform correctly. */ void sortKeyframes() { std::sort( this->keyframes.begin(), this->keyframes.end(), [](struct SimpleKeyframe &a, struct SimpleKeyframe &b) { return a.time < b.time; } ); } public: std::function easing = easeLinear; T *modifies; std::vector> keyframes; /** * Constructs a new Simple Animation instance. * * @param modifies Pointer to the value that will be modified. */ SimpleAnimation(T *modifies) { assertNotNull(modifies, "Modifies cannot be null"); this->modifies = modifies; } /** * Adds a keyframe that will be slerped to at a given time. * * @param time Time the keyframe occurs. * @param value Value at this given time. */ void addKeyframe(float_t time, T value) { assertTrue(time >= 0, "Time must be >= 0"); struct SimpleKeyframe keyframe; keyframe.time = time; keyframe.value = value; this->duration = mathMax(this->duration, time); this->finished = false; this->keyframes.push_back(keyframe); if(time < this->duration) this->sortKeyframes(); } /** * Quickly add a series of keyframes. For example, if you want to have a * keyframe series of; * [ 1, 3, 5, 7, 9 ] * * And occur at times * [ 0, 2, 4, 6, 8 ] * * You would pass frameTime as 2, and step as 2. * * @param startTime When the first keyframe occurs. * @param frameTime At what rate do keyframes occur. * @param start Initial value (the value at startTime). * @param end The end value (for the last keyframe). * @param step How to step the value. */ void addSequentialKeyframes( float_t startTime, float_t frameTime, T start, T end, T step ) { T v = start; float_t n = startTime; while(v != end) { this->addKeyframe(n, v); n += frameTime; v += step; } } /** * Shorthand addSequentialKeyframes, assumes a step of 1 and a startTime * of 0. * * @param frameTime Time between frames. * @param start Initial value. * @param end End value. */ void addSequentialKeyframes(float_t frameTime, T start, T end) { this->addSequentialKeyframes(0, frameTime, start, end, 1); } /** * Immediately sets the value, bypassing keyframes and ticks. Useful for * setting an initial value. * * @param value Value to set. */ void setValue(T value) { *modifies = value; this->onValueModified(); } void tick(float_t delta) override { if(this->finished) return; float_t newTime = this->time + delta; struct SimpleKeyframe *keyframeNext = nullptr; struct SimpleKeyframe *keyframeCurrent = nullptr; // Find current and next keyframe(s) auto itKey = this->keyframes.begin(); while(itKey != this->keyframes.end()) { if(itKey->time > newTime) { keyframeNext = &(*itKey); break; } keyframeCurrent = &(*itKey); ++itKey; } // Update values if(keyframeCurrent != nullptr && keyframeNext == nullptr) { // "End of animation" *this->modifies = keyframeCurrent->value; this->onValueModified(); } else if(keyframeNext != nullptr) { T oldValue; float_t oldTime; if(keyframeCurrent == nullptr) { // "Start of animation" oldValue = keyframeCurrent->value; oldTime = keyframeCurrent->time; } else { // "Mid animation" oldTime = this->time; oldValue = *this->modifies; } // Slerp between keyframes float_t keyframeDelta = this->easing( (newTime - oldTime) / (keyframeNext->time - oldTime) ); *this->modifies = oldValue + ( (keyframeNext->value - oldValue) * keyframeDelta ); this->onValueModified(); } // First possible frame? I think this can be done cleaner if(this->time == 0 && keyframeCurrent->time == 0) { *this->modifies = keyframeCurrent->value; this->onValueModified(); } // Update time. this->time = newTime; // Has the animation finished? if(newTime < this->duration) return; // Do we need to loop? if(this->loop) { this->time = 0; return; } // Animation end. this->finished = true; this->eventAnimationEnd.invoke(); } void clear() override { Animation::clear(); this->keyframes.clear(); } }; }