Skip to content

Instantly share code, notes, and snippets.

@ClickerMonkey
Created January 7, 2022 00:25
Show Gist options
  • Save ClickerMonkey/5b7632a04a5b4ac19c0e4202409e96dc to your computer and use it in GitHub Desktop.
Save ClickerMonkey/5b7632a04a5b4ac19c0e4202409e96dc to your computer and use it in GitHub Desktop.
C++ Animation system
//
// 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