Browse Source

Add channels to animations

master
C. J. Howard 4 years ago
parent
commit
8702bf84da
2 changed files with 278 additions and 92 deletions
  1. +269
    -83
      src/animation/animation.hpp
  2. +9
    -9
      src/application.cpp

+ 269
- 83
src/animation/animation.hpp View File

@ -20,10 +20,12 @@
#ifndef ANTKEEPER_ANIMATION_HPP #ifndef ANTKEEPER_ANIMATION_HPP
#define ANTKEEPER_ANIMATION_HPP #define ANTKEEPER_ANIMATION_HPP
#include <algorithm>
#include <functional> #include <functional>
#include <set> #include <set>
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <unordered_map>
/** /**
* Abstract base class for animations. * Abstract base class for animations.
@ -84,6 +86,9 @@ public:
/// Returns the current loop count of the animation. /// Returns the current loop count of the animation.
int get_loop_count() const; 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. /// Sets the callback that's executed when the animation is started from a stopped state.
void set_start_callback(std::function<void()> callback); void set_start_callback(std::function<void()> callback);
@ -135,24 +140,25 @@ inline int animation_base::get_loop_count() const
return loop_count; return loop_count;
} }
/**
* Templated keyframe animation class.
*/
template <typename T> template <typename T>
class animation: public animation_base
class animation_channel
{ {
public: public:
/// Scheduled function consisting of a time and function object.
/// Keyframe consisting of a time and a value.
typedef std::tuple<double, T> keyframe; typedef std::tuple<double, T> keyframe;
/// Interpolator function type.
typedef typename std::decay<std::function<T(const T&, const T&, double)>>::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. * Adds a keyframe to the animation.
@ -169,35 +175,33 @@ public:
*/ */
void remove_keyframes(double start, double end); 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<keyframe> get_keyframes(double start, double end) const;
/// Removes all keyframes from the animation. /// 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<const keyframe*, 2> 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<void(const T&)> callback);
private:
//static bool keyframe_compare(const keyframe& a, const keyframe & b);
std::list<keyframe> 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 struct keyframe_compare
{ {
inline bool operator()(const keyframe& lhs, const keyframe& rhs) const inline bool operator()(const keyframe& lhs, const keyframe& rhs) const
@ -206,30 +210,180 @@ private:
} }
}; };
interpolator_type interpolator;
std::function<void(const T&)> frame_callback;
int id;
std::set<keyframe, keyframe_compare> keyframes; std::set<keyframe, keyframe_compare> keyframes;
}; };
/*
template <typename T> template <typename T>
bool animation<T>::keyframe_compare(const keyframe& a, const keyframe & b)
animation_channel<T>::animation_channel(int id):
id(id),
keyframes(keyframe_compare())
{}
template <typename T>
animation_channel<T>::animation_channel():
animation_channel(-1)
{}
template <typename T>
animation_channel<T>::animation_channel(const animation_channel& other):
id(other.id),
keyframes(other.keyframes)
{}
template <typename T>
void animation_channel<T>::insert_keyframe(const keyframe& k)
{
keyframes.emplace(k);
}
template <typename T>
void animation_channel<T>::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 <typename T>
void animation_channel<T>::remove_keyframes()
{
keyframes.clear();
}
template <typename T>
std::array<const typename animation_channel<T>::keyframe*, 2> animation_channel<T>::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<const keyframe*, 2> frames;
frames[0] = (lower_bound != keyframes.end()) ? &(*lower_bound) : nullptr;
frames[1] = (upper_bound != keyframes.end()) ? &(*upper_bound) : nullptr;
return frames;
}
template <typename T>
std::list<typename animation_channel<T>::keyframe> animation_channel<T>::find_keyframes(double start, double end) const
{
std::list<keyframe> 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 <typename T>
inline int animation_channel<T>::get_id() const
{
return id;
}
template <typename T>
double animation_channel<T>::get_duration() const
{
if (keyframes.empty())
{
return 0.0;
}
return std::get<0>(*keyframes.rbegin());
}
/**
* Templated keyframe animation class.
*/
template <typename T>
class animation: public animation_base
{
public:
/// Channel for this animation type.
typedef animation_channel<T> channel;
// Keyframe type for this animation.
typedef typename channel::keyframe keyframe;
/// Interpolator function type.
typedef typename std::decay<std::function<T(const T&, const T&, double)>>::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<void(int, const T&)> 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<int, channel> channels;
interpolator_type interpolator;
std::function<void(int, const T&)> frame_callback;
};
template <typename T> template <typename T>
animation<T>::animation(): animation<T>::animation():
interpolator(nullptr), interpolator(nullptr),
frame_callback(nullptr),
keyframes(keyframe_compare())
frame_callback(nullptr)
{} {}
template <typename T> template <typename T>
void animation<T>::advance(double dt) void animation<T>::advance(double dt)
{ {
if (paused || stopped || keyframes.empty())
if (paused || stopped)
{ {
return; return;
} }
@ -237,25 +391,39 @@ void animation::advance(double dt)
// Advance position by dt // Advance position by dt
position += dt * speed; 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) 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<int>(i), frame);
}
else if (frames[0] != nullptr)
{
// Pass frame to frame callback
frame_callback(static_cast<int>(i), std::get<1>(*frames[0]));
}
else if (frames[1] != nullptr)
{
// Pass frame to frame callback
frame_callback(static_cast<int>(i), std::get<1>(*frames[1]));
}
}
} }
} }
else else
@ -265,7 +433,7 @@ void animation::advance(double dt)
++loop_count; ++loop_count;
// Subtract duration of animation from position // Subtract duration of animation from position
position -= std::get<0>(*lower_bound);
position -= duration;
// Execute loop callback // Execute loop callback
if (loop_callback) if (loop_callback)
@ -280,13 +448,7 @@ void animation::advance(double dt)
} }
} }
else else
{
// Call frame callback on final keyframe
if (frame_callback)
{
frame_callback(std::get<1>(*lower_bound));
}
{
stopped = true; stopped = true;
// Call end callback // Call end callback
@ -299,50 +461,74 @@ void animation::advance(double dt)
} }
template <typename T> template <typename T>
void animation<T>::insert_keyframe(const keyframe& k)
typename animation<T>::channel* animation<T>::add_channel(int id)
{ {
keyframes.emplace(k);
return &(*channels.emplace(id, id).first).second;
} }
template <typename T> template <typename T>
void animation<T>::remove_keyframes(double start, double end)
void animation<T>::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 <typename T> template <typename T>
std::list<typename animation<T>::keyframe> animation<T>::get_keyframes(double start, double end) const
void animation<T>::remove_channels()
{ {
std::list<keyframe> 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 <typename T>
void animation<T>::set_interpolator(interpolator_type interpolator)
{
this->interpolator = interpolator;
}
return keyframe_list;
template <typename T>
void animation<T>::set_frame_callback(std::function<void(int, const T&)> callback)
{
this->frame_callback = callback;
} }
template <typename T> template <typename T>
void animation<T>::clear()
const typename animation<T>::channel* animation<T>::get_channel(int id) const
{ {
keyframes.clear();
auto it = channels.find(id);
if (it != channels.end())
{
return &it->second;
}
return nullptr;
} }
template <typename T> template <typename T>
void animation<T>::set_interpolator(interpolator_type interpolator)
typename animation<T>::channel* animation<T>::get_channel(int id)
{ {
this->interpolator = interpolator;
auto it = channels.find(id);
if (it != channels.end())
{
return &it->second;
}
return nullptr;
} }
template <typename T> template <typename T>
void animation<T>::set_frame_callback(std::function<void(const T&)> callback)
double animation<T>::get_duration() const
{ {
this->frame_callback = callback;
double duration = 0.0;
for (auto it = channels.begin(); it != channels.end(); ++it)
{
duration = std::max<double>(duration, it->second.get_duration());
}
return duration;
} }
#endif // ANTKEEPER_ANIMATION_HPP #endif // ANTKEEPER_ANIMATION_HPP

+ 9
- 9
src/application.cpp View File

@ -503,19 +503,19 @@ application::application(int argc, char** argv):
float radial_transition_time = 0.5f; float radial_transition_time = 0.5f;
radial_transition_in = new animation<float>(); radial_transition_in = new animation<float>();
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<float>::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<float>); radial_transition_in->set_interpolator(ease_in_quad<float>);
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<float>::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); animator->add_animation(radial_transition_in);
radial_transition_out = new animation<float>();
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<float>::set_val, underground_transition_property, std::placeholders::_1));
radial_transition_out = new animation<float>();
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<float>); radial_transition_out->set_interpolator(ease_out_quad<float>);
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); animator->add_animation(radial_transition_out);
// ECS // ECS

Loading…
Cancel
Save