Last active
June 28, 2019 05:48
-
-
Save ArnCarveris/494eaba3bf396ecedaf3a1674fb7a2bf to your computer and use it in GitHub Desktop.
EnTT Animation - https://www.youtube.com/watch?v=yy8jQgmhbAU
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <vector> | |
#include <entt/entity/registry.hpp> | |
namespace entt | |
{ | |
template<typename Entity> | |
class Animation; | |
struct AnimationCurve final { | |
using fn_type = void(*)(const float&, const std::vector<float>&, float&); | |
fn_type function; | |
std::vector<float> data; | |
}; | |
template<typename Component> | |
struct AnimationFunctor final { | |
using trail_fn_type = void(*)(Registry<Entity> &, const Entity&, const int&, const Component*, Component&); | |
using interpolator_fn_type = void(*)(const float&, const Component&, const Component&, Component&); | |
trail_fn_type trail; | |
interpolator_fn_type interpolator; | |
}; | |
template<typename Component> | |
struct AnimationData final { | |
AnimationFunctor<Component> functor; | |
Component default; | |
std::vector<Component> frame; | |
std::vector<float> time; | |
std::vector<AnimationCurve> curve; | |
}; | |
struct AnimationState final { | |
float elapsed{ 0.0f }; | |
bool loop{ false }; | |
}; | |
struct AnimationFrame final { | |
int trail{ 0 }; | |
size_t from{ 0 }; | |
size_t to{ 0 }; | |
float progress{ 0.0f }; | |
}; | |
template<typename Component> | |
struct AnimationFrameState final { | |
AnimationFrame frame; | |
std::shared_ptr<void> data; | |
}; | |
/*! @brief TODO */ | |
template<typename Entity> | |
class AnimationRegistry final { | |
friend class Animation<Entity>; | |
using component_type = typename Registry<Entity>::component_type; | |
using search_fn_type = bool(*)(const float&, const float*, const float*, size_t&); | |
using recalc_fn_type = void(*)(search_fn_type, Registry<Entity> &); | |
using apply_fn_type = void(*)(Registry<Entity> &); | |
template<typename Component> | |
struct Data { | |
AnimationFunctor<Component> functor; | |
}; | |
struct Handler { | |
recalc_fn_type recalc; | |
apply_fn_type apply; | |
std::shared_ptr<void> data; | |
}; | |
template<typename Component> | |
static void frame_recalc(search_fn_type search, const AnimationState& state, AnimationFrameState<Component>& target) { | |
auto* d = static_cast<AnimationData<Component>*>(target.data.get()); | |
auto& time = d->time; | |
float progress; | |
float elapsed{ std::min(time.back(), state.elapsed) }; | |
{ | |
const float* first = &time.front(); | |
const float* last = &time.back() - 1; | |
auto from = target.frame.from; | |
if (!search(elapsed, first, last, from)) { | |
from = time.size() - 2; | |
} | |
target.frame.trail = int(from) - int(target.frame.from); | |
target.frame.from = from; | |
target.frame.to = from + 1; | |
} { | |
auto& from = time[target.frame.from]; | |
auto& to = time[target.frame.to]; | |
progress = (elapsed - from) / (to - from); | |
} { | |
auto& curve = d->curve[target.frame.from]; | |
curve.function(progress, curve.data, target.frame.progress); | |
} | |
} | |
template<typename Component, typename ...Args> | |
static void frame_recalc_view(search_fn_type search, Registry<Entity> ®istry, Args&&... args) { | |
registry.view<AnimationState, AnimationFrameState<Component>>(std::forward<Args>(args)...).each([&](auto& entity, auto& state, auto& target) { | |
frame_recalc<Component>(search, state, target); | |
}); | |
} | |
template<typename Component> | |
static void frame_apply(Registry<Entity> ®istry, const Entity& entity, const AnimationFrameState<Component>& state, Component& target) { | |
auto* d = static_cast<AnimationData<Component>*>(state.data.get()); | |
auto& frame = d->frame; | |
assert(frame.size() > 1); | |
assert(frame.size() > state.frame.from); | |
assert(frame.size() > state.frame.to); | |
auto& from = frame[state.frame.from]; | |
auto& to = frame[state.frame.to]; | |
d->functor.interpolator(state.frame.progress, from, to, target); | |
d->functor.trail(registry, entity, state.frame.trail, &from, target); | |
} | |
template<typename Component, typename ...Args> | |
static void frame_apply_view(Registry<Entity> ®istry, Args&&... args) { | |
registry.view<AnimationFrameState<Component>, Component>(std::forward<Args>(args)...).each([®istry](auto& entity, auto& state, auto& target) { | |
frame_apply<Component>(registry, entity, state, target); | |
}); | |
} | |
template<typename Component> | |
static void default_trail_fn(Registry<Entity> &, const Entity&, const int&, const Component*, Component&) { | |
} | |
template<typename Component> | |
static void stepped_interpolator_fn(const float&, const Component& from, const Component&, Component& target) { | |
target = from; | |
} | |
template<typename Component> | |
static void frame_recalc_view_fn(search_fn_type search, Registry<Entity> ®istry) { | |
frame_recalc_view<Component>(search, registry); | |
} | |
template<typename Component> | |
static void frame_apply_view_fn(Registry<Entity> ®istry) { | |
frame_apply_view<Component>(registry); | |
} | |
template<typename Component> | |
Handler& handler() { | |
auto cid = registry.type<Component>(); | |
auto it = handlers.find(cid); | |
if (it == handlers.end()) | |
{ | |
auto data = std::make_shared<Data<Component>>(); | |
data->functor.trail = &default_trail_fn<Component>; | |
data->functor.interpolator = &stepped_interpolator_fn<Component>; | |
it = handlers.emplace | |
( | |
cid, | |
Handler | |
{ | |
&frame_recalc_view_fn<Component>, | |
&frame_apply_view_fn<Component>, | |
data | |
} | |
).first; | |
} | |
return it->second; | |
} | |
template<typename Component> | |
Data<Component>* data(const Handler& ref) { | |
return static_cast<Data<Component>*>(ref.data.get()); | |
} | |
template<typename Component> | |
Data<Component>* data() { | |
return data<Component>(handler<Component>()); | |
} | |
public: | |
/*! @brief TODO */ | |
AnimationRegistry() = delete; | |
/*! @brief TODO */ | |
AnimationRegistry(Registry<Entity> ®istry) | |
: registry{ registry } | |
{} | |
/*! @brief TODO */ | |
~AnimationRegistry() = default; | |
/*! @brief TODO */ | |
AnimationRegistry(const AnimationRegistry &) = delete; | |
/*! @brief TODO */ | |
AnimationRegistry(AnimationRegistry &&) = default; | |
/*! @brief TODO */ | |
AnimationRegistry & operator=(const AnimationRegistry &) = delete; | |
/*! @brief TODO */ | |
AnimationRegistry & operator=(AnimationRegistry &&) = default; | |
/*! @brief TODO */ | |
template<typename Fn> | |
void searcher(Fn&& fn) { | |
search = fn; | |
} | |
/*! @brief TODO */ | |
template<typename Component, typename Fn> | |
void trail(Fn&& fn) { | |
data<Component>()->functor.trail = fn; | |
} | |
/*! @brief TODO */ | |
template<typename Component, typename Fn> | |
void interpolator(Fn&& fn) { | |
data<Component>()->functor.interpolator = fn; | |
} | |
/*! @brief TODO */ | |
void select(const float& time) { | |
assert(time >= 0); | |
for (auto& state : registry.view<AnimationState>(raw_t{})) { | |
state.elapsed = time; | |
} | |
} | |
/*! @brief TODO */ | |
void advance(const float& delta) { | |
assert(delta > 0); | |
for (auto& state : registry.view<AnimationState>(raw_t{})) { | |
state.elapsed += delta; | |
} | |
} | |
/*! @brief TODO */ | |
void recalc() { | |
for (auto &handler : handlers) { | |
handler.second.recalc(search, registry); | |
} | |
} | |
/*! @brief TODO */ | |
void apply() const { | |
for (auto &handler : handlers) { | |
handler.second.apply(registry); | |
} | |
} | |
std::unique_ptr<Animation<Entity>> create() { | |
return std::make_unique<Animation<Entity>>(*this); | |
} | |
private: | |
std::unordered_map<component_type, Handler> handlers; | |
Registry<Entity> ®istry; | |
search_fn_type search{ nullptr }; | |
}; | |
/*! @brief TODO */ | |
template<typename Entity> | |
class Animation final { | |
public: | |
/*! @brief TODO */ | |
using registry_type = AnimationRegistry<Entity>; | |
private: | |
struct Handler; | |
using Frame = AnimationFrame; | |
using Curve = AnimationCurve; | |
template<typename Component> | |
using Data = AnimationData<Component>; | |
template<typename Component> | |
using FrameState = AnimationFrameState<Component>; | |
using State = AnimationState; | |
using component_type = typename Registry<Entity>::component_type; | |
using fn_type = void(*)(const Handler&, registry_type&, const Entity&); | |
using recalc_fn_type = void(*)(const Handler&, registry_type&, const Entity&, const State&); | |
struct Handler { | |
fn_type set; | |
fn_type reset; | |
fn_type unset; | |
fn_type assign; | |
fn_type accommodate; | |
fn_type remove; | |
recalc_fn_type recalc; | |
fn_type apply; | |
std::shared_ptr<void> data; | |
}; | |
static void stepped_curve_fn(const float& in, const std::vector<float>&, float& out) { | |
out = in; | |
} | |
template<typename Component> | |
static void default_noset_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
} | |
template<typename Component> | |
static void frame_set_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
registry.registry.assign<FrameState<Component>>(entity).data = handler.data; | |
} | |
template<typename Component> | |
static void frame_reset_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
registry.registry.accommodate<FrameState<Component>>(entity).data = handler.data; | |
} | |
template<typename Component> | |
static void frame_unset_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
registry.registry.remove<FrameState<Component>>(entity); | |
} | |
template<typename Component> | |
static void assign_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
auto* data = static_cast<Data<Component>*>(handler.data.get()); | |
registry.registry.assign<Component>(entity, data->default); | |
} | |
template<typename Component> | |
static void accommodate_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
auto* data = static_cast<Data<Component>*>(handler.data.get()); | |
registry.registry.accommodate<Component>(entity, data->default); | |
} | |
template<typename Component> | |
static void remove_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
registry.registry.remove<Component>(entity); | |
} | |
template<typename Component> | |
static void default_recalc_fn(const Handler&, registry_type&, const Entity&, const State&) { | |
} | |
template<typename Component> | |
static void frame_recalc_fn(const Handler& handler, registry_type®istry, const Entity& entity, const State& state) { | |
registry_type::frame_recalc<Component>(registry.search, state, registry.registry.get<FrameState<Component>>(entity)); | |
} | |
template<typename Component> | |
static void default_apply_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
} | |
template<typename Component> | |
static void frame_apply_fn(const Handler& handler, registry_type®istry, const Entity& entity) { | |
registry_type::frame_apply<Component>(registry.registry, entity, registry.registry.get<FrameState<Component>>(entity), registry.registry.get<Component>(entity)); | |
} | |
template<typename Component> | |
Handler& handler() { | |
auto cid = registry.registry.template type<Component>(); | |
auto it = handlers.find(cid); | |
if (it == handlers.end()) | |
{ | |
auto data = std::make_shared<Data<Component>>(); | |
data->functor = registry.data<Component>()->functor; | |
it = handlers.emplace | |
( | |
cid, | |
Handler | |
{ | |
&default_noset_fn<Component>, | |
&default_noset_fn<Component>, | |
&default_noset_fn<Component>, | |
&assign_fn<Component>, | |
&accommodate_fn<Component>, | |
&remove_fn<Component>, | |
nullptr, | |
nullptr, | |
data | |
} | |
).first; | |
} | |
return it->second; | |
} | |
template<typename Component> | |
Data<Component>* data(const Handler& ref) { | |
return static_cast<Data<Component>*>(ref.data.get()); | |
} | |
template<typename Component> | |
Data<Component>* data() { | |
return data<Component>(handler<Component>()); | |
} | |
public: | |
/*! @brief TODO */ | |
using entity_type = Entity; | |
/*! @brief TODO */ | |
using size_type = std::size_t; | |
/*! @brief TODO */ | |
using curve_type = Curve; | |
/*! @brief TODO */ | |
using state_type = State; | |
/*! @brief TODO */ | |
Animation() = delete; | |
/*! @brief TODO */ | |
Animation(registry_type ®istry) | |
: registry{ registry } | |
{} | |
/*! @brief TODO */ | |
~Animation() = default; | |
/*! @brief TODO */ | |
Animation(const Animation &) = delete; | |
/*! @brief TODO */ | |
Animation(Animation &®istry) = default; | |
/*! @brief TODO */ | |
Animation & operator=(const Animation &) = delete; | |
/*! @brief TODO */ | |
Animation & operator=(Animation &&) = default; | |
/*! @brief TODO */ | |
template<typename Component, typename Fn> | |
void trail(Fn&& fn) { | |
data<Component>()->functor.trail = fn; | |
} | |
/*! @brief TODO */ | |
template<typename Component, typename Fn> | |
void interpolator(Fn&& fn) { | |
data<Component>()->functor.interpolator = fn; | |
} | |
/*! @brief TODO */ | |
template<typename Component, typename... Args> | |
Component & default(Args &&... args) { | |
auto& h = handler<Component>(); | |
auto* d = data<Component>(h); | |
h.recalc = &default_recalc_fn<Component>; | |
h.apply = &default_apply_fn<Component>; | |
d->default = Component{ std::forward<Args>(args)... }; | |
return d->default; | |
} | |
/*! @brief TODO */ | |
template<typename Component> | |
Component & frame(const float& time) { | |
return frame<Component>(time, curve_type{ &stepped_curve_fn,{} }); | |
} | |
/*! @brief TODO */ | |
template<typename Component, typename... Args> | |
inline std::enable_if_t<(sizeof...(Args) > 0), Component &> | |
frame(const float& time, Args &&... args) { | |
return frame<Component>(time, curve_type{ &stepped_curve_fn,{} }, std::forward<Args>(args)...); | |
} | |
/*! @brief TODO */ | |
template<typename Component> | |
Component & frame(const float& time, curve_type&& curve) { | |
return frame<Component>(time, std::move(curve), data<Component>()->default); | |
} | |
/*! @brief TODO */ | |
template<typename Component, typename... Args> | |
inline std::enable_if_t<(sizeof...(Args) > 0), Component &> | |
frame(const float& time, curve_type&& curve, Args &&... args) { | |
auto& h = handler<Component>(); | |
auto* d = data<Component>(h); | |
assert(curve.function); | |
assert(d->time.empty() || d->time.back() < time); | |
d->time.emplace_back(time); | |
d->curve.emplace_back(std::move(curve)); | |
d->frame.emplace_back(std::forward<Args>(args)...); | |
if (d->frame.size() > 1) { | |
h.set = &frame_set_fn<Component>; | |
h.reset = &frame_reset_fn<Component>; | |
h.unset = &frame_unset_fn<Component>; | |
h.recalc = &frame_recalc_fn<Component>; | |
h.apply = &frame_apply_fn<Component>; | |
} | |
else { | |
h.set = &default_noset_fn<Component>; | |
h.reset = &default_noset_fn<Component>; | |
h.unset = &default_noset_fn<Component>; | |
h.recalc = &default_recalc_fn<Component>; | |
h.apply = &default_apply_fn<Component>; | |
} | |
length = std::max(length, time); | |
return d->frame.back(); | |
} | |
/*! @brief TODO */ | |
template<typename Component> | |
void remove() ENTT_NOEXCEPT { | |
handlers.erase(registry.template type<Component>()) | |
} | |
/*! @brief TODO */ | |
void set(const entity_type& entity) { | |
registry.registry.assign<State>(entity); | |
for (auto &handler : handlers) { | |
handler.second.set(handler.second, registry, entity); | |
} | |
} | |
/*! @brief TODO */ | |
void reset(const entity_type& entity) { | |
registry.registry.accommodate<State>(entity); | |
for (auto &handler : handlers) { | |
handler.second.reset(handler.second, registry, entity); | |
} | |
} | |
/*! @brief TODO */ | |
void unset(const entity_type& entity) { | |
registry.registry.remove<State>(entity); | |
for (auto &handler : handlers) { | |
handler.second.unset(handler.second, registry, entity); | |
} | |
} | |
/*! @brief TODO */ | |
void assign(const entity_type& entity) { | |
for (auto &handler : handlers) { | |
handler.second.assign(handler.second, registry, entity); | |
} | |
} | |
/*! @brief TODO */ | |
void accommodate(const entity_type& entity) { | |
for (auto &handler : handlers) { | |
handler.second.accommodate(handler.second, registry, entity); | |
} | |
} | |
/*! @brief TODO */ | |
void remove(const entity_type& entity) ENTT_NOEXCEPT { | |
for (auto &handler : handlers) { | |
handler.second.remove(handler.second, registry, entity); | |
} | |
} | |
/*! @brief TODO */ | |
void update(const float& progress, const entity_type& entity) { | |
assert(progress >= 0.0); | |
assert(progress <= 1.0f); | |
auto& state = registry.registry.get<State>(entity); | |
state.elapsed = progress * length; | |
} | |
/*! @brief TODO */ | |
void select(const float& time, const entity_type& entity) { | |
assert(time >= 0); | |
auto& state = registry.registry.get<State>(entity); | |
state.elapsed = time; | |
} | |
/*! @brief TODO */ | |
void advance(const float& delta, const entity_type& entity) { | |
assert(delta > 0); | |
auto& state = registry.registry.get<State>(entity); | |
state.elapsed += delta; | |
} | |
void recalc(const entity_type& entity) { | |
auto& state = registry.registry.get<State>(entity); | |
for (auto &handler : handlers) { | |
handler.second.recalc(handler.second, registry, entity, state); | |
} | |
} | |
/*! @brief TODO */ | |
void apply(const entity_type& entity) { | |
for (auto &handler : handlers) { | |
handler.second.apply(handler.second, registry, entity); | |
} | |
} | |
/*! @brief TODO */ | |
const float& duration() const { | |
return length; | |
} | |
private: | |
std::unordered_map<component_type, Handler> handlers; | |
AnimationRegistry<Entity> ®istry; | |
float length{ 0 }; | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "animation.hpp" | |
namespace entt | |
{ | |
template< | |
typename ResourceID, | |
template<typename> class ResourceHandle, | |
typename Texture, | |
typename Rect, | |
typename ResourceManager, | |
typename AnimationRegistry, | |
typename EntityRegistry | |
> | |
void animation_test(ResourceManager& res_man, AnimationRegistry& anim_reg, EntityRegistry& entity_reg) | |
{ | |
using texture_t = ResourceHandle<typename Texture::Resource>; | |
using rect_t = Rect; | |
auto obtain_texture = [&res_man](const char* id) { | |
return res_man.obtain<typename Texture::Resource>(ResourceID{ id }); | |
}; | |
auto linear_search = [](const float& time, const float* begin, const float* end, size_t& out) { | |
for (size_t i = 0; begin != end; ++i, ++begin) { | |
if (begin[0] >= time) { | |
out = i; | |
return true; | |
} | |
} | |
return false; | |
}; | |
//sets algorithm for seraching frame index with given time | |
anim_reg.searcher(linear_search); | |
auto animation = anim_reg.create(); | |
animation->default<rect_t>(0.0f, 0.0f, 102.0f, 180.0f); | |
animation->default<texture_t>(obtain_texture("castle_0.png")); | |
animation->frame<texture_t>(0.0f); | |
animation->frame<texture_t>(0.25f, obtain_texture("castle_1.png")); | |
animation->frame<texture_t>(0.50f, obtain_texture("castle_2.png")); | |
animation->frame<texture_t>(0.75f, obtain_texture("castle_3.png")); | |
animation->frame<texture_t>(1.0f); | |
auto e = entity_reg.create(); | |
//prepares animation state for given entity | |
animation->set(e); | |
animation->assign(e); | |
//compute animation time for given entity | |
animation->update(0.0f, e); | |
animation->select(0.3f, e); | |
animation->advance(0.16f, e); | |
animation->advance(1.16f, e); | |
//recalculate animation state for each component | |
animation->recalc(e); | |
//apply each component with calculated animation state for given entity | |
animation->apply(e); | |
//removes animation from given entity | |
animation->unset(e); | |
animation->remove(e); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment