From 8702bf84da4596edd240d86ba225202cc8f75773 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Wed, 5 Aug 2020 21:33:21 -0700 Subject: [PATCH] Add channels to animations --- src/animation/animation.hpp | 352 +++++++++++++++++++++++++++--------- src/application.cpp | 18 +- 2 files changed, 278 insertions(+), 92 deletions(-) diff --git a/src/animation/animation.hpp b/src/animation/animation.hpp index e5f7347..3f7f625 100644 --- a/src/animation/animation.hpp +++ b/src/animation/animation.hpp @@ -20,10 +20,12 @@ #ifndef ANTKEEPER_ANIMATION_HPP #define ANTKEEPER_ANIMATION_HPP +#include #include #include #include #include +#include /** * Abstract base class for animations. @@ -84,6 +86,9 @@ public: /// Returns the current loop count of the animation. int get_loop_count() const; + /// Returns the duration of the animation. + virtual double get_duration() const = 0; + /// Sets the callback that's executed when the animation is started from a stopped state. void set_start_callback(std::function callback); @@ -135,24 +140,25 @@ inline int animation_base::get_loop_count() const return loop_count; } -/** - * Templated keyframe animation class. - */ template -class animation: public animation_base +class animation_channel { public: - /// Scheduled function consisting of a time and function object. + /// Keyframe consisting of a time and a value. typedef std::tuple keyframe; - /// Interpolator function type. - typedef typename std::decay>::type interpolator_type; + /** + * Creates an animation channel. + * + * @param id ID of the channel. + */ + animation_channel(int id); - /// Creates an animation - animation(); + /// Creates an animation channel. + animation_channel(); - /// @copydoc animation_base::advance() - virtual void advance(double dt); + /// Creates an animation channel. + animation_channel(const animation_channel& other); /** * Adds a keyframe to the animation. @@ -169,35 +175,33 @@ public: */ void remove_keyframes(double start, double end); - /** - * Returns all the keyframes on `[start, end)`. - * - * @param start Starting position in time (inclusive). - * @param end Ending position in time (non-inclusive). - * @return All keyframes on `[start, end)`. - */ - std::list get_keyframes(double start, double end) const; - /// Removes all keyframes from the animation. - void clear(); + void remove_keyframes(); /** - * Sets the frame interpolator function object. + * Finds the keyframes to the left and right of @p position. * - * @param interpolator Frame interpolator function object. + * @param position Position in time. + * @return Array containing the the keyframes on the left and right of @p position. */ - void set_interpolator(interpolator_type interpolator); + std::array find_keyframes(double position) const; /** - * Sets the callback that's executed on each frame of animation. + * Finds all the keyframes on `[start, end)`. * - * @param callback Frame callback which receives the value of the interpolated frames. + * @param start Starting position in time (inclusive). + * @param end Ending position in time (non-inclusive). + * @return All keyframes on `[start, end)`. */ - void set_frame_callback(std::function callback); - -private: - //static bool keyframe_compare(const keyframe& a, const keyframe & b); + std::list find_keyframes(double start, double end) const; + + /// Returns the ID of the animation channel. + int get_id() const; + + /// Returns the duration of the animation channel. + double get_duration() const; +private: struct keyframe_compare { inline bool operator()(const keyframe& lhs, const keyframe& rhs) const @@ -206,30 +210,180 @@ private: } }; - interpolator_type interpolator; - std::function frame_callback; + int id; std::set keyframes; }; -/* template -bool animation::keyframe_compare(const keyframe& a, const keyframe & b) +animation_channel::animation_channel(int id): + id(id), + keyframes(keyframe_compare()) +{} + +template +animation_channel::animation_channel(): + animation_channel(-1) +{} + +template +animation_channel::animation_channel(const animation_channel& other): + id(other.id), + keyframes(other.keyframes) +{} + +template +void animation_channel::insert_keyframe(const keyframe& k) +{ + keyframes.emplace(k); +} + +template +void animation_channel::remove_keyframes(double start, double end) { - return std::get<0>(a) < std::get<0>(b); + auto lower_bound = keyframes.lower_bound({start, T()}); + auto upper_bound = keyframes.upper_bound({end, T()}); + keyframes.erase(lower_bound, upper_bound); } -*/ + +template +void animation_channel::remove_keyframes() +{ + keyframes.clear(); +} + +template +std::array::keyframe*, 2> animation_channel::find_keyframes(double position) const +{ + // Find the following keyframe + auto upper_bound = keyframes.upper_bound({position, T()}); + + // Find the preceding keyframe + auto lower_bound = upper_bound; + --lower_bound; + + std::array frames; + frames[0] = (lower_bound != keyframes.end()) ? &(*lower_bound) : nullptr; + frames[1] = (upper_bound != keyframes.end()) ? &(*upper_bound) : nullptr; + + return frames; +} + +template +std::list::keyframe> animation_channel::find_keyframes(double start, double end) const +{ + std::list keyframe_list; + + auto lower_bound = keyframes.lower_bound({start, T()}); + auto upper_bound = keyframes.upper_bound({end, T()}); + for (auto iterator = lower_bound; iterator != upper_bound; ++iterator) + { + keyframe_list.push_back(*iterator); + } + + return keyframe_list; +} + +template +inline int animation_channel::get_id() const +{ + return id; +} + +template +double animation_channel::get_duration() const +{ + if (keyframes.empty()) + { + return 0.0; + } + + return std::get<0>(*keyframes.rbegin()); +} + +/** + * Templated keyframe animation class. + */ +template +class animation: public animation_base +{ +public: + /// Channel for this animation type. + typedef animation_channel channel; + + // Keyframe type for this animation. + typedef typename channel::keyframe keyframe; + + /// Interpolator function type. + typedef typename std::decay>::type interpolator_type; + + /// Creates an animation. + animation(); + + /// @copydoc animation_base::advance() + virtual void advance(double dt); + + /** + * Adds a channel to the animation. + * + * @param id ID of the channel. + * @return Added or pre-existing channel. + */ + channel* add_channel(int id); + + /** + * Removes a channel from the animation. + * + * @param id ID of the channel to remove. + */ + void remove_channel(int id); + + /// Removes all channels from the animation. + void remove_channels(); + + /** + * Sets the frame interpolator function object. + * + * @param interpolator Frame interpolator function object. + */ + void set_interpolator(interpolator_type interpolator); + + /** + * Sets the callback that's executed on each frame of animation. + * + * @param callback Frame callback which receives the index of an animation channel and value of an interpolated frame. + */ + void set_frame_callback(std::function callback); + + /** + * Returns the channel with the specified ID. + * + * @param id ID of the channel to get. + */ + const channel* get_channel(int id) const; + + /// @copydoc animation::get_channel(int) const + channel* get_channel(int id); + + /// @copydoc animation_base::get_duration() const + virtual double get_duration() const; + + +private: + std::unordered_map channels; + interpolator_type interpolator; + std::function frame_callback; +}; template animation::animation(): interpolator(nullptr), - frame_callback(nullptr), - keyframes(keyframe_compare()) + frame_callback(nullptr) {} template void animation::advance(double dt) { - if (paused || stopped || keyframes.empty()) + if (paused || stopped) { return; } @@ -237,25 +391,39 @@ void animation::advance(double dt) // Advance position by dt position += dt * speed; - // Find the following keyframe - auto upper_bound = keyframes.upper_bound({position, T()}); + // Determine duration of the animation + double duration = get_duration(); - // Find the preceding keyframe - auto lower_bound = upper_bound; - --lower_bound; - - if (upper_bound != keyframes.end()) + if (position < duration) { if (frame_callback != nullptr && interpolator != nullptr) { - // Calculate interpolated frame - double t0 = std::get<0>(*lower_bound); - double t1 = std::get<0>(*upper_bound); - double alpha = (position - t0) / (t1 - t0); - T frame = interpolator(std::get<1>(*lower_bound), std::get<1>(*upper_bound), alpha); - - // Pass frame to frame callback - frame_callback(frame); + for (std::size_t i = 0; i < channels.size(); ++i) + { + auto frames = channels[i].find_keyframes(position); + + if (frames[0] != nullptr && frames[1] != nullptr) + { + // Calculate interpolated frame + double t0 = std::get<0>(*frames[0]); + double t1 = std::get<0>(*frames[1]); + double alpha = (position - t0) / (t1 - t0); + T frame = interpolator(std::get<1>(*frames[0]), std::get<1>(*frames[1]), alpha); + + // Pass frame to frame callback + frame_callback(static_cast(i), frame); + } + else if (frames[0] != nullptr) + { + // Pass frame to frame callback + frame_callback(static_cast(i), std::get<1>(*frames[0])); + } + else if (frames[1] != nullptr) + { + // Pass frame to frame callback + frame_callback(static_cast(i), std::get<1>(*frames[1])); + } + } } } else @@ -265,7 +433,7 @@ void animation::advance(double dt) ++loop_count; // Subtract duration of animation from position - position -= std::get<0>(*lower_bound); + position -= duration; // Execute loop callback if (loop_callback) @@ -280,13 +448,7 @@ void animation::advance(double dt) } } else - { - // Call frame callback on final keyframe - if (frame_callback) - { - frame_callback(std::get<1>(*lower_bound)); - } - + { stopped = true; // Call end callback @@ -299,50 +461,74 @@ void animation::advance(double dt) } template -void animation::insert_keyframe(const keyframe& k) +typename animation::channel* animation::add_channel(int id) { - keyframes.emplace(k); + return &(*channels.emplace(id, id).first).second; } template -void animation::remove_keyframes(double start, double end) +void animation::remove_channel(int id) { - auto lower_bound = keyframes.lower_bound({start, T()}); - auto upper_bound = keyframes.upper_bound({end, T()}); - keyframes.erase(lower_bound, upper_bound); + auto it = channels.find(id); + if (it != channels.end()) + { + channels.erase(it); + } } template -std::list::keyframe> animation::get_keyframes(double start, double end) const +void animation::remove_channels() { - std::list keyframe_list; + channels.clear(); +} - auto lower_bound = keyframes.lower_bound({start, T()}); - auto upper_bound = keyframes.upper_bound({end, T()}); - for (auto iterator = lower_bound; iterator != upper_bound; ++iterator) - { - keyframe_list.push_back(*iterator); - } +template +void animation::set_interpolator(interpolator_type interpolator) +{ + this->interpolator = interpolator; +} - return keyframe_list; +template +void animation::set_frame_callback(std::function callback) +{ + this->frame_callback = callback; } template -void animation::clear() +const typename animation::channel* animation::get_channel(int id) const { - keyframes.clear(); + auto it = channels.find(id); + if (it != channels.end()) + { + return &it->second; + } + + return nullptr; } template -void animation::set_interpolator(interpolator_type interpolator) +typename animation::channel* animation::get_channel(int id) { - this->interpolator = interpolator; + auto it = channels.find(id); + if (it != channels.end()) + { + return &it->second; + } + + return nullptr; } template -void animation::set_frame_callback(std::function callback) +double animation::get_duration() const { - this->frame_callback = callback; + double duration = 0.0; + + for (auto it = channels.begin(); it != channels.end(); ++it) + { + duration = std::max(duration, it->second.get_duration()); + } + + return duration; } #endif // ANTKEEPER_ANIMATION_HPP diff --git a/src/application.cpp b/src/application.cpp index 6ceac16..609b44a 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -503,19 +503,19 @@ application::application(int argc, char** argv): float radial_transition_time = 0.5f; radial_transition_in = new animation(); - radial_transition_in->insert_keyframe({0.0f, 0.0f}); - radial_transition_in->insert_keyframe({radial_transition_time, 1.0f}); - radial_transition_in->set_frame_callback(std::bind(&material_property::set_val, underground_transition_property, std::placeholders::_1)); + radial_transition_in->set_frame_callback([this](int channel, float value){this->underground_transition_property->set_value(value);}); radial_transition_in->set_interpolator(ease_in_quad); - radial_transition_in->set_start_callback([this](){this->logger.log("animation started\n");}); - radial_transition_in->set_end_callback([this](){this->logger.log("animation ended\n");}); + animation::channel* channel = radial_transition_in->add_channel(0); + channel->insert_keyframe({0.0f, 0.0f}); + channel->insert_keyframe({radial_transition_time, 1.0f}); animator->add_animation(radial_transition_in); - radial_transition_out = new animation(); - radial_transition_out->insert_keyframe({0.0f, 1.0f}); - radial_transition_out->insert_keyframe({radial_transition_time, 0.0f}); - radial_transition_out->set_frame_callback(std::bind(&material_property::set_val, underground_transition_property, std::placeholders::_1)); + radial_transition_out = new animation(); + radial_transition_out->set_frame_callback([this](int channel, float value){this->underground_transition_property->set_value(value);}); radial_transition_out->set_interpolator(ease_out_quad); + channel = radial_transition_out->add_channel(0); + channel->insert_keyframe({0.0f, 1.0f}); + channel->insert_keyframe({radial_transition_time, 0.0f}); animator->add_animation(radial_transition_out); // ECS