Bezier-curve keyframe timeline animation for openFrameworks ECS apps, integrated with the shared ofxEnTTInspector reflection system.
Based on hegworks/tanim.
| Addon | Why |
|---|---|
ofxEnTT |
entt::registry |
ofxImGui |
Curve editor and UI |
ofxEnTTInspector |
Shared reflection (registerProperties, PinDataType, ReflectedProperty) |
Removed dependencies:
visit_struct,nlohmann/json(bundled copy). JSON serialization is now viaofJSON(OF's built-in nlohmann). Reflection is viaofxEnTTInspector.
Old approach (removed):
// At global scope in a header:
TANIM_REFLECT(my_comp, x, y, z);
// At startup:
tanim::RegisterComponent<my_comp>();New approach — one specialization serves Inspector, Tanim, and StateCollector:
// In my_comp_inspector.h (include wherever these tools are used):
#pragma once
#include "ComponentInspector.h"
#include "my_comp.h"
template<>
inline void inspector::registerProperties(my_comp& c, inspector::ComponentInspector& ci) {
ci.addProperty("x", &c.x, -100.f, 100.f);
ci.addProperty("y", &c.y, -100.f, 100.f);
ci.addProperty("z", &c.z, -100.f, 100.f);
}
// At startup (explicit name recommended for JSON stability):
tanim::RegisterComponent<my_comp>("my_comp");The structName parameter is stored in serialized JSON. It should remain stable across refactors — if you rename the struct, also update the string, or existing saved timelines will not load correctly.
#include "ofxTanim.h"
#include "my_comp_inspector.h" // specializes inspector::registerProperties<my_comp>
void ofApp::setup() {
tanim::Init();
tanim::RegisterComponent<my_comp>("my_comp");
}
void ofApp::update() {
// Advance all playing timelines:
tanim::UpdateTimeline(registry, entityDatas, timelineData, componentData, ofGetLastFrameTime());
}
void ofApp::draw() {
// Inside ImGui frame:
tanim::Draw(); // draws the curve editor window
tanim::UpdateEditor(ofGetLastFrameTime());
}
// When you want to open the editor for a specific entity:
tanim::OpenForEditing(registry, entityDatas, timelineData, componentData);| PinDataType | Curves | Notes |
|---|---|---|
Float |
1 | Smooth Bezier |
Int |
1 | Linear, integer-snapped |
Bool |
1 | Constant (step) |
Vec2 |
2 | X, Y |
Vec3 |
3 | X, Y, Z |
Vec4 |
4 | X, Y, Z, W |
Quat |
5 | W, X, Y, Z + Spins curve |
Color |
4 | R, G, B, A normalized 0–1 in curves |
String, Trigger, and Any are not animatable and will be silently skipped.
// Save:
std::string json = tanim::Serialize(timelineData);
ofSaveFile("timeline.json", json);
// Load:
std::string json = ofLoadFileAsString("timeline.json");
tanim::Deserialize(timelineData, json);Serialized format version 2 (unchanged from original). JSON keys use the explicit struct name passed to RegisterComponent.
tanim::Init();
tanim::RegisterComponent<T>("struct_name");
tanim::Draw();
tanim::UpdateEditor(dt);
tanim::OpenForEditing(registry, entityDatas, tdata, cdata);
tanim::CloseEditor();
tanim::EnterPlayMode();
tanim::StartTimeline(tdata, cdata);
tanim::UpdateTimeline(registry, entityDatas, tdata, cdata, dt);
tanim::StopTimeline(cdata);
tanim::ExitPlayMode();
tanim::Play(cdata);
tanim::Pause(cdata);
tanim::Stop(cdata);
bool playing = tanim::IsPlaying(cdata);
std::string json = tanim::Serialize(tdata);
tanim::Deserialize(tdata, json);