Created
January 7, 2022 00:25
-
-
Save ClickerMonkey/5b7632a04a5b4ac19c0e4202409e96dc to your computer and use it in GitHub Desktop.
C++ Animation system
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
// | |
// Created by Philip Diffenderfer on 10/24/21. | |
// | |
#include <string> | |
#include <map> | |
#include <vector> | |
#include <cmath> | |
#include <functional> | |
using Key = std::string; | |
using KeyList = std::string; | |
struct TypeBase { | |
Key id; | |
TypeBase(Key i): id(i) {} | |
virtual ~TypeBase() {} | |
}; | |
template <typename T> | |
struct Type : TypeBase { | |
TypeBase* GetProperty(Key prop) = 0; | |
Type(Key i): TypeBase(i) {} | |
}; | |
struct CalculatorBase { | |
virtual ~CalculatorBase() {} | |
}; | |
template <typename V> | |
struct Calculator : CalculatorBase { | |
V Lerp(const V& start, const V& end, float delta) { | |
return start * (1 - delta) + end * delta; | |
} | |
void Add(V* out, const V& a, const V& b, float scale) { | |
*out += a + b * scale; | |
} | |
void Scale(V* out, const V& a, float scale) { | |
*out += a * scale; | |
} | |
void Clear(V* out) { | |
*out = V(); | |
} | |
}; | |
struct Calculators { | |
static std::map<Key, CalculatorBase*>& GetCalculators() { | |
static std::map<Key, CalculatorBase*> calcs {}; | |
return calcs; | |
} | |
template <typename V> | |
static void Add(Type<V>* type, Calculator<V>* calculator) { | |
GetCalculators()[type->id] = calculator; | |
} | |
template <typename V> | |
static Calculator<V>* Get(Type<V>* type) { | |
return GetCalculators()[type->id]; | |
} | |
}; | |
struct Easing { | |
float Compute(float delta) { return delta; } | |
}; | |
struct Ease { | |
inline static Easing Linear{}; | |
}; | |
template <typename V> | |
struct PathPoint { | |
V value; | |
float time; | |
Easing* easing = nullptr; | |
Easing* GetEasing() { | |
return easing != nullptr ? easing : &Ease::Linear; | |
} | |
}; | |
template <typename V> | |
struct PathData { | |
std::vector<PathPoint<V>> points; | |
Easing* easing; | |
PathData(std::vector<PathPoint<V>> _points): points(_points) {} | |
float Duration() { | |
return points.empty() ? 0 : points.back().time; | |
} | |
int IndexBefore(float time) { | |
int n = points.size() - 1; | |
int i = 0; | |
while (i < n && points[i + 1].time > time) i++; | |
return i; | |
} | |
Easing* GetEasing() { | |
return easing != nullptr ? easing : &Ease::Linear; | |
} | |
void ValueDelta(V* out, Calculator<V>* calculator, float delta) { | |
ValueAt(out, calculator, delta * Duration()); | |
} | |
void ValueAt(V* out, Calculator<V>* calculator, float time) { | |
float easedTime = GetEasing()->Compute(time / Duration()); | |
int i = IndexBefore(time); | |
PathPoint<V>& start = points[i]; | |
PathPoint<V>& end = points[i + 1]; | |
float frameDelta = (easedTime - start.time) / (end.time - start.time); | |
float easedPoint = start.GetEasing()->Compute(frameDelta); | |
return calculator->Lerp(start.value, end.value, easedPoint); | |
} | |
}; | |
template <typename V> | |
struct Path { | |
virtual bool Get(float delta, V* out, Calculator<V>* calculator, PathData<V>* data) = 0; | |
}; | |
template <typename V> | |
struct PathKeyframe : Path<V> { | |
bool Get(float delta, V* out, Calculator<V>* calculator, PathData<V>* data) override { | |
data->ValueDelta(out, calculator, delta); | |
return true; | |
} | |
}; | |
struct AttrimatorDefinitionBase { | |
}; | |
enum AttrimatorStop { | |
Now, CompleteCurrent, CompleteAll, Repeats | |
}; | |
struct Animation; | |
struct AnimationState { | |
Animation* animation; | |
bool enabled; | |
float blend; | |
AnimationState(Animation* anim) | |
: animation(anim) | |
, enabled(true) | |
, blend(1.0f) | |
{} | |
}; | |
template <typename V> | |
struct Attrimator { | |
float delay; | |
float elapsed; | |
float stopTime; | |
AnimationState* state; | |
Attrimator(float d, AnimationState* s): delay(d), state(s) { Start(); } | |
float GetDelay() { return delay; } | |
void SetDelay(float delay) { this->delay = delay; } | |
void AddDelay(float delay) { this->delay += delay; } | |
float GetElapsed() { return elapsed; } | |
virtual float GetStopTime() { return stopTime; } | |
void StopAt(float time) { stopTime = time; } | |
void StopIn(float relativeTime) { stopTime = elapsed + relativeTime; } | |
float GetRemaining() { return GetStopTime() - elapsed; } | |
virtual void Start() { | |
elapsed = 0.0f; | |
stopTime = std::numeric_limits<float>::infinity(); | |
}; | |
bool Update(float dt) { | |
elapsed += dt; | |
return OnUpdate(dt); | |
}; | |
virtual bool OnUpdate(float dt) { return true; }; | |
virtual bool Get(V* out, Calculator<V>* calculator) = 0; | |
virtual void Stop(AttrimatorStop stop) { | |
stopTime = elapsed; | |
} | |
}; | |
template <typename V> | |
struct AttrimatorDefinition : AttrimatorDefinitionBase { | |
virtual std::unique_ptr<Attrimator<V>> CreateAttrimator(AnimationState* state) = 0; | |
virtual float Duration() = 0; | |
}; | |
template <typename V> | |
struct AttrimatorPathDefinition; | |
template <typename V> | |
struct AttrimatorPath : Attrimator<V> { | |
AttrimatorPathDefinition<V>* def; | |
int repeats; | |
AttrimatorPath(AttrimatorPathDefinition<V>* d, AnimationState* state): Attrimator<V>(d->delay, state), def(d), repeats(d->repeats) {} | |
float GetStopTime() override { | |
return fmin(this->stopTime, this->delay + (fmin(repeats, def->repeats) * def->Cycle()) - def->sleep); | |
} | |
float GetDeltaAt(float time) { | |
return fmod(time - this->delay, this->def->Cycle()) / this->def->duration; | |
} | |
bool InCycle(float time) { | |
float delta = GetDeltaAt(time); | |
return delta >= 0.0f && delta <= 1.0f; | |
} | |
bool OnUpdate(float dt) override { | |
return InCycle(this->elapsed) || InCycle(this->elapsed - dt); | |
} | |
bool Get(V* out, Calculator<V>* calculator) override { | |
float delta = fmin(1.0f, GetDeltaAt(this->elapsed)); | |
float easedDelta = def->easing == nullptr ? delta : def->easing->Compute(delta); | |
def->path->Get(easedDelta, out, calculator, def->data); | |
return true; | |
} | |
void Start() override { | |
Attrimator<V>::Start(); | |
repeats = def->repeats; | |
} | |
void Stop(AttrimatorStop stop) override { | |
if (stop != AttrimatorStop::Repeats) { | |
this->stopTime = this->elapsed; | |
} else { | |
repeats = static_cast<int>(fmax(0, ((this->elapsed - this->delay) / this->def->Cycle()) + 1)); | |
} | |
} | |
}; | |
template <typename V> | |
struct AttrimatorPathDefinition : AttrimatorDefinition<V> { | |
PathData<V> data; | |
std::unique_ptr<Path<V>> path; | |
float delay = 0.0f; | |
int repeats = 1; | |
float sleep = 0.0f; | |
float duration = 1.0f; | |
Easing* easing = nullptr; | |
float Cycle() { return duration + sleep; } | |
float Duration() override { return delay + (repeats * Cycle()) - sleep; } | |
std::unique_ptr<Attrimator<V>> CreateAttrimator(AnimationState* state) override { | |
return std::make_unique<AttrimatorPath<V>>(this, state); | |
} | |
}; | |
struct AnimationSubset { | |
std::map<KeyList, KeyList> translate = {}; | |
std::vector<KeyList> ignore = {}; | |
std::vector<KeyList> only = {}; | |
}; | |
struct AnimationInfo { | |
AnimationSubset subset = {}; | |
float delay = 0.0f; | |
}; | |
struct Animation { | |
std::string name; | |
std::map<KeyList, std::vector<std::unique_ptr<AttrimatorDefinitionBase>>> definitions; | |
std::map<KeyList, std::vector<AttrimatorDefinitionBase*>> GetDefinitions(AnimationSubset subset) { | |
std::map<KeyList, std::vector<AttrimatorDefinitionBase*>> out; | |
const auto& [translate, ignore, only] = subset; | |
for (auto& pair : definitions) { | |
if (!only.empty() && std::find(only.begin(), only.end(), pair.first) == ignore.end()) { | |
continue; | |
} | |
if (std::find(ignore.begin(), ignore.end(), pair.first) != ignore.end()) { | |
continue; | |
} | |
auto translated = std::find(translate.begin(), translate.end(), pair.first); | |
auto key = translated != translate.end() ? translated->second : pair.first; | |
std::vector<AttrimatorDefinitionBase*> defs {}; | |
for (auto& def : pair.second) { | |
defs.push_back(def.get()); | |
} | |
out[key] = defs; | |
} | |
return out; | |
} | |
}; | |
template <typename V> | |
using Access = std::function<void(V*)>; | |
struct AccessInstance { | |
template <typename V> | |
Access<V> GetAccess(KeyList keys) { | |
return {}; | |
} | |
}; | |
struct AnimatorPropertyBase { | |
virtual ~AnimatorPropertyBase() = default; | |
virtual float GetRemaining() = 0; | |
virtual void Stop(AttrimatorStop stop) = 0; | |
virtual void Add(float relativeTime, std::vector<std::unique_ptr<AttrimatorDefinitionBase>>& definitions, AnimationState* state) = 0; | |
virtual void Update(float dt, AccessInstance& accessInstance) = 0; | |
}; | |
template <typename V> | |
struct AnimatorProperty : AnimatorPropertyBase { | |
KeyList keys; | |
Calculator<V>* calculator; | |
Type<V>* type; | |
std::vector<std::unique_ptr<Attrimator<V>>> attrimators; | |
float maxBlend = 1.0f; | |
AnimatorProperty(Type<V>* t): type(t) { | |
calculator = Calculators::Get(t); | |
} | |
void Update(float dt, AccessInstance& accessInstance) override { | |
float accumulatedBlend = 0.0f; | |
V accumulated; | |
calculator->Clear(&accumulated); | |
for (auto& attr : attrimators) { | |
float blendAmount = attr.state->blend; | |
if (!attr.state->enabled || blendAmount == 0.0f) { | |
continue; | |
} | |
if (attr.Update(dt)) { | |
V value; | |
if (attr.Get(&value, calculator)) { | |
calculator->Add(&accumulated, accumulated, value, blendAmount); | |
accumulatedBlend += blendAmount; | |
} | |
} | |
} | |
if (accumulatedBlend != 0.0f) { | |
if (accumulatedBlend > maxBlend) { | |
calculator->Scale(&accumulated, accumulated, maxBlend / accumulatedBlend); | |
} | |
auto& access = accessInstance.template GetAccess<V>(keys); | |
access(&accumulated); | |
} | |
} | |
void Stop(AttrimatorStop stop) override { | |
for (auto& attr : attrimators) { | |
attr.stop(stop); | |
} | |
} | |
void Add(float relativeTime, std::vector<AttrimatorDefinitionBase*>& definitions, AnimationState* state) override { | |
for (auto& definitionBase : definitions) { | |
auto definition = dynamic_cast<AttrimatorDefinition<V>>(*definitionBase); | |
auto attrimator = definition.CreateAttrimator(state); | |
attrimator->AddDelay(relativeTime); | |
attrimators.push_back(std::move(attrimator)); | |
} | |
} | |
}; | |
struct AnimationTypeBase { | |
Key type; | |
AnimationTypeBase(Key t): type(t) {} | |
virtual ~AnimationTypeBase() {} | |
}; | |
template <typename T> | |
struct AnimationType : AnimationTypeBase { | |
using get1 = std::function<std::unique_ptr<AttrimatorPathDefinition<T>>()>; | |
using get2 = std::function<std::unique_ptr<AnimatorProperty<T>>(Type<T>* type)>; | |
get1 getAttrimatorPathDefinition; | |
get2 getAnimatorProperty; | |
AnimationType(Key k, get1 a, get2 b) | |
: AnimationTypeBase(k) | |
, getAttrimatorPathDefinition(a) | |
, getAnimatorProperty(b) | |
{} | |
}; | |
struct Animate { | |
static std::map<Key, std::unique_ptr<AnimationTypeBase>> types; | |
template <typename V> | |
static void AddType(Type<V>* type) { | |
types[type->id] = std::make_unique<AnimationType<V>>( | |
type->id, | |
[](){ return std::make_unique<AttrimatorPathDefinition<V>>(); }, | |
[](Type<V>* type){ return std::make_unique<AnimatorProperty<V>>(type); } | |
); | |
} | |
template <typename V> | |
static AnimationType<V>* GetType(Type<V>* type) { | |
return dynamic_cast<AnimationType<V>*>(types[type->id].get()); | |
} | |
}; | |
template <typename T> | |
struct Animator { | |
Type<T>* type; | |
AccessInstance instance; | |
std::map<Key, std::unique_ptr<AnimatorPropertyBase>> properties; | |
std::vector<std::unique_ptr<AnimationState>> states; | |
void Update(float dt) { | |
for (auto& prop : properties) { | |
prop.second->Update(dt, instance); | |
} | |
} | |
AnimationState* InternalPlay(Animation* animation, std::map<KeyList, std::vector<AttrimatorDefinitionBase*>>& defs, float delay) { | |
auto state = GetState(animation, true); | |
for (auto& keys : defs) { | |
auto property = GetProperty(keys.first); | |
if (property == nullptr) { | |
continue; | |
} | |
property->Add(delay, keys.second, state); | |
} | |
return state; | |
} | |
AnimationState* Play(Animation* animation, AnimationInfo info = {}) { | |
return InternalPlay(animation, animation->GetDefinitions(info.subset), info.delay); | |
} | |
AnimationState* Queue(Animation* animation, AnimationInfo info = {}) { | |
float delay = 0.0f; | |
auto defs = animation->GetDefinitions(info.subset); | |
for (auto& keys : defs) { | |
auto property = GetProperty(keys.first); | |
if (property == nullptr) { | |
continue; | |
} | |
float remaining = property->GetRemaining(); | |
if (!isinf(remaining) && remaining > delay) { | |
delay = remaining; | |
} | |
} | |
delay += info.delay; | |
return InternalPlay(animation, defs, delay); | |
} | |
AnimationState* GetState(Animation* animation, bool create = false) { | |
for (auto& state : states) { | |
if (state->animation == animation) { | |
return state.get(); | |
} | |
} | |
if (create) { | |
states.push_back(std::make_unique<AnimationState>(animation)); | |
return states.back().get(); | |
} | |
return nullptr; | |
} | |
AnimatorPropertyBase* GetProperty(Key key) { | |
auto found = properties.find(key); | |
if (found != properties.end()) { | |
return found->second.get(); | |
} | |
auto propertyType = type->GetProperty(key); | |
if (propertyType == nullptr) { | |
return nullptr; | |
} | |
properties[key] = Animate::GetType(propertyType)->getAnimatorProperty(); | |
return properties[key].get(); | |
} | |
}; | |
template <typename T, typename V> | |
struct Access { | |
std::function<V(T*)> Get; | |
std::function<bool(T*, V)> Set; | |
}; | |
struct Animation { | |
Key name; | |
std::map<KeyList, std::unique_ptr<AttrimatorDefinition>> attrimators; | |
float duration; | |
float Duration() { | |
if (this->duration != 0) { | |
return this->duration; | |
} | |
float duration = 0; | |
for (auto& x : attrimators) { | |
float pathDuration = x.second->Duration(); | |
if (pathDuration > duration) { | |
duration = pathDuration; | |
} | |
} | |
return duration; | |
} | |
std::map<KeyList, AttrimatorDefinition*> GetDefinitions(AnimationSubset subset) { | |
std::map<KeyList, AttrimatorDefinition*> out; | |
const auto& [translate, ignore, only] = subset; | |
for (auto& pair : attrimators) { | |
if (!only.empty() && std::find(only.begin(), only.end(), pair.first) == ignore.end()) { | |
continue; | |
} | |
if (std::find(ignore.begin(), ignore.end(), pair.first) != ignore.end()) { | |
continue; | |
} | |
auto translated = std::find(translate.begin(), translate.end(), pair.first); | |
auto key = translated != translate.end() ? translated->second : pair.first; | |
out[key] = pair.second; | |
} | |
return out; | |
} | |
std::map<KeyList, std::unique_ptr<Attrimator>> GetAttrimators(AnimationSubset subset) { | |
auto defs = GetDefinitions(subset); | |
std::map<KeyList, std::unique_ptr<Attrimator>> out{}; | |
for (auto& pair : defs) { | |
out[pair.first] = pair.second->CreateAttrimator(); | |
} | |
return out; | |
} | |
}; | |
struct AnimationStateInfo { | |
AnimationSubset subset = {}; | |
float start = 0; | |
float end = 1; | |
float duration = 0; | |
int repeats = 1; | |
float weight = 1; | |
bool enabled = true; | |
}; | |
struct AnimatorPropertyBase { | |
KeyList key; | |
std::vector<std::string> property; | |
std::vector<Attrimator*> attrimators; | |
virtual ~AnimatorPropertyBase() = default; | |
virtual void Update(float dt) = 0; | |
virtual float TimeRemaining() = 0; | |
}; | |
template <typename T, typename V> | |
struct AnimatorProperty : AnimatorPropertyBase { | |
Calculator<V>* calculator; | |
Access<void, V> access; | |
std::vector<std::shared_ptr<AttrimatorValued<V>>> attrimators; | |
V accumulated; | |
float accumulatedWeight; | |
AnimatorProperty(Calculator<V>* c, Access<T, V> a): calculator(c), access(a) {} | |
void PreUpdate(float dt ) override { | |
if (attrimators.empty()) { | |
return; | |
} | |
accumulatedWeight = 0; | |
calculator->Clear(&accumulated); | |
auto n = attrimators.size(); | |
auto i = 0; | |
while (i < n) { | |
auto& current = attrimators[i]; | |
if (i > 0) { | |
current->Start(); | |
} | |
if (current->Update(dt)) { | |
} | |
auto remaining = current->TimeRemaining(); | |
if (remaining > 0) { | |
break; | |
} | |
dt = max(0, dt + remaining); | |
i++; | |
} | |
if (i > 0) { | |
attrimators.erase(attrimators.begin(), attrimators.begin() + i); | |
} | |
} | |
void Accumulate(void* value, float weight) override { | |
if (weight > 0) { | |
accumulatedWeight += weight; | |
calculator->Add(&accumulated, accumulated, *((V*)value), weight / accumulatedWeight); | |
} | |
} | |
void PostUpdate(float dt, void* subject) override { | |
if (accumulatedWeight != 0) { | |
access.Set(subject, accumulated); | |
} | |
} | |
}; | |
struct AnimationState : AnimationStateInfo { | |
std::map<KeyList, std::unique_ptr<Attrimator>> attrimators; | |
float start = 0; // the offset into the animation paths the animation begins | |
float end = 1; // the place in the animation paths to stop at the end of the animation | |
float duration = 0; // the cur | |
int repeats = 1; // how many times to repeat the animation before its marked not playing and is removed | |
float weight = 1; // the relative weight of this animation to the others with the same attrimators | |
bool enabled = true; // if this animation is enabled | |
bool playing; | |
float currentTime; | |
}; | |
template <typename T> | |
struct Animator { | |
std::map<KeyList, AnimatorPropertyBase*> properties = {}; | |
std::vector<AnimationState> states = {}; | |
T* character; | |
Animator(T* c): character(c) {}; | |
AnimationState& Add(Animation* animation, AnimationStateInfo info) { | |
AnimationState state = { | |
.attrimators = animation->GetAttrimators(info.subset), | |
.start = info.start, | |
.end = info.end, | |
.duration = info.duration == 0 ? animation->Duration() : info.duration, | |
.repeats = info.repeats, | |
.weight = info.weight, | |
.enabled = info.enabled, | |
.playing = info.enabled, | |
}; | |
for (auto& attrimator : state.attrimators) { | |
auto& prop = properties[attrimator.first]; | |
if (prop == nullptr) { | |
// TODO add property | |
} | |
prop->attrimators.push_back(attrimator.second.get()); | |
} | |
states.push_back(std::move(state)); | |
return states.back(); | |
} | |
void Update(float dt) { | |
for (auto& prop : properties) { | |
prop.second->PreUpdate(dt); | |
} | |
for (auto& state : states) { | |
if (!state.enabled || !state.playing) { | |
continue; | |
} | |
state.currentTime += dt; | |
float timeDelta = state.currentTime / state.duration; | |
if (timeDelta >= state.end) { | |
state.currentTime -= state.duration; | |
if (state.repeats != -1) { | |
state.repeats--; | |
} | |
if (state.repeats == 0) { | |
timeDelta = 1.0f; | |
state.playing = false; | |
} else { | |
timeDelta = fmod(timeDelta, 1.0f); | |
} | |
} | |
float frameDelta = (state.end - state.start) * timeDelta + state.start; | |
for (auto& path : state.paths) { | |
AttrimatorBase* attrimator = attrimators[path.first]; | |
if (attrimator != nullptr) { | |
// path.second-> | |
// attrimator->Accumulate(value, state.weight); | |
} | |
} | |
} | |
for (auto& prop : properties) { | |
prop.second->PostUpdate(dt, character); | |
} | |
} | |
}; | |
template <typename V> | |
struct AnimatableType { | |
int key; | |
std::function<std::unique_ptr<AnimatorProperty<void, V>>()> getAnimatorProperty; | |
std::function<std::unique_ptr<AttrimatorPathDefinition<V>>()> getAttrimatorPathDefinition; | |
std::function<std::unique_ptr<AttrimatorPath<V>>()> getAttrimatorPath; | |
std::function<Calculator<V>*()> getCalculator; | |
}; | |
class Animate { | |
public: | |
static std::map<int, std::unique_ptr<AnimatableType<void>>> types; | |
template <typename V> | |
static void AddType(AnimatableType<V> type) { | |
types[type.key] = std::unique_ptr<AnimatableType<V>>(type); | |
} | |
template <typename V> | |
}; | |
std::map<int, std::unique_ptr<AnimatableType<void>>> Animate::types | |
int main5() | |
{ | |
struct Character { float x, y; }; | |
auto CALCULATOR = new Calculator<float>(); | |
Animation RUNNING = { | |
.name = "Running", | |
.duration = 2.0f, | |
.paths = { | |
{"x", new AnimationPath<float>({ | |
new AnimationPathFrame<float>{.value = 0.0f, .time = 0.0f}, | |
new AnimationPathFrame<float>{.value = 10.0f, .time = 1.0f}, | |
})}, | |
}, | |
}; | |
Animation STRAFING = { | |
.name = "Strafing", | |
.duration = 2.0f, | |
.paths = { | |
{"y", new AnimationPath<float>({ | |
new AnimationPathFrame<float>{.value = 0.0f, .time = 0.0f}, | |
new AnimationPathFrame<float>{.value = 10.0f, .time = 1.0f}, | |
})}, | |
}, | |
}; | |
Character character; | |
Animator<Character> animator(&character); | |
// Will be handled by Types | |
animator.attrimators["x"] = new Attrimator<Character, float>(CALCULATOR, { | |
.Get = [](Character* c) { return c->x; }, | |
.Set = [](Character* c, float value) { c->x = value; return true; }, | |
}); | |
animator.attrimators["y"] = new Attrimator<Character, float>(CALCULATOR, { | |
.Get = [](Character* c) { return c->y; }, | |
.Set = [](Character* c, float value) { c->y = value; return true; }, | |
}); | |
AnimationState run = animator.Add(&RUNNING, { .end = 0.5f, .weight = 1, .repeats = -1 }); // half speed, only running | |
AnimationState strafe = animator.Add(&STRAFING, { .end = 0.0f, .weight = 0, .repeats = -1 }); // no speed, no strafing | |
animator.Update(0); | |
animator.Update(0.5); | |
animator.Update(0.5); | |
return 0; | |
} | |
/** | |
* Calculator<V> {} | |
* AnimationValue | |
* PathPoint<V> { value, time, easing } | |
* PathData<V> { vector<PathPoint<V>> } | |
* Path<V> { bool Get(delta, V* out, Calculator<V>*, PathData<V>*) } | |
* KeyframePath<V> {} | |
* Animation { name, duration, map<KeyList, vector<unique_ptr<AttrimatorDefinition>>>, params } | |
* AttrimatorDefinitionBase { } | |
* AttrimatorDefinition<V> { createAttrimator<V>(), duration() } | |
* - SpringDefinition<V> { rest, position, velocity, stiffness, damping, delay } | |
* - PathDefinition<V> { pathData, delay, repeats, sleep, duration, easing } | |
* - PhysicsDefinition<V> { position, velocity, acceleration, maxVelocity, delay } | |
* Attrimator<V> { get/set delay, duration, bool update(dt, V* out, Calculator<V>*) } | |
* - SpringAttrimator { ... } | |
* - PathAttrimator { ... } | |
* - PhysicsAttrimator { ... } | |
* AnimatorPropertyBase { remaining(), stop(nopeat|finish|end|now), add(at,vector<AttrimatorDefinitionBase*>) } | |
* AnimatorProperty<V> { keys, property, calculator, attrimators, update(dt,AccessObject), resting } | |
* Animator { AccessObject, map<keys, AnimatorPropertyBase>, | |
* | |
* TODO params, relative values, blending | |
* | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment