Files
fn_registry/dev/issues/completed/0031-cpp-animation-curves.md
T
egutierrez cf3936314b chore(issues): close 0031 — animation curves shipped
Three primitives delivered + gallery demos + builds cleanly:
- tween_curves_cpp_core (16 Penner curves, header-mostly)
- bezier_editor_cpp_core (visual cubic Bezier editor)
- timeline_cpp_core (DAW-style keyframe widget)
2026-04-25 21:50:50 +02:00

5.9 KiB

0031 — C++ animation curves (timeline + bezier_editor + tween_curves)

APP Metadata

Campo Valor
ID 0031
Estado pendiente
Prioridad media
Tipo feature — C++ core (cpp/functions/core)

Dependencias

tokens_cpp_core. Independiente de los demas issues.

Desbloquea: animar uniforms en shaders_lab con keyframes; easing reusable en transiciones de UI; control fino sobre animaciones procedurales.


Objetivo

Tres primitivos:

  1. tween_curves_cpp_core — set de funciones de easing puras (Penner): linear, ease_in/out_quad/cubic/expo, elastic, bounce. Header-only.
  2. bezier_editor_cpp_core — editor visual de una curva cubica Bezier (4 puntos de control), evaluacion f(t) para t∈[0,1]. Estado puro + render en ImGui canvas.
  3. timeline_cpp_core — widget tipo DAW: tracks horizontales con keyframes draggable, scrub, play/pause, evaluacion track_value_at(time) con interp lineal o curve por keyframe.

Demo en primitives_gallery con un slider animado por timeline + curva bezier para ease.

Contexto

shaders_lab tiene sliders manuales para uniforms. No hay forma de:

  • Animar un uniform con curva temporal.
  • Disenar transiciones suaves reusables.

Las funciones de easing son utiles tambien fuera de animacion (interpolacion de colores, rampas).

Arquitectura

cpp/functions/core/
├── tween_curves.h               # NEW (header-only ok)
├── tween_curves.cpp             # NEW (si hace falta .cpp)
├── tween_curves.md              # NEW
├── bezier_editor.h              # NEW
├── bezier_editor.cpp            # NEW
├── bezier_editor.md             # NEW
├── timeline.h                   # NEW
├── timeline.cpp                 # NEW
└── timeline.md                  # NEW
cpp/apps/primitives_gallery/
├── demos_animation.cpp          # NEW
├── demos.h                      # MOD
├── main.cpp                     # MOD
└── CMakeLists.txt               # MOD

API propuesta

namespace fn {

// --- tween_curves (puro, header-only ok) ---
namespace tween {
    inline float linear(float t)       { return t; }
    inline float in_quad(float t)      { return t*t; }
    inline float out_quad(float t)     { return 1 - (1-t)*(1-t); }
    inline float in_out_cubic(float t) { /* Penner */ }
    inline float in_expo(float t);
    inline float out_expo(float t);
    inline float in_elastic(float t);
    inline float out_elastic(float t);
    inline float out_bounce(float t);
    // ... ~15 easing funcs

    enum class Ease { Linear, InQuad, OutQuad, InOutQuad, InCubic, OutCubic, /*...*/ };
    float apply(Ease e, float t);
}

// --- bezier_editor (puro estado) ---
struct BezierCurve { ImVec2 p0{0,0}, p1{0.25f,0.0f}, p2{0.75f,1.0f}, p3{1,1}; };
float bezier_eval(const BezierCurve&, float t);  // y at x=t
bool  bezier_editor(const char* id, BezierCurve&, ImVec2 size = {200, 200});  // returns true if changed

// --- timeline ---
struct Keyframe { float time; float value; tween::Ease ease = tween::Ease::Linear; };
struct Track { std::string name; std::vector<Keyframe> keys; };
struct TimelineState {
    std::vector<Track> tracks;
    float current_time = 0.0f;
    float duration     = 5.0f;
    bool  playing      = false;
};
float track_value_at(const Track&, float t);  // interp puro
void  timeline_update(TimelineState&, float dt);  // avanza si playing
bool  timeline_widget(const char* id, TimelineState&, ImVec2 size = {-1, 200});  // returns true if changed
}

Tareas

Fase 1 — tween_curves

  • 1.1 Implementar las funciones Penner (referencia: easings.net). Header-only inline para que el compilador inline en hot paths.
  • 1.2 tween::apply(Ease, t) con switch.
  • 1.3 Tests: cada curva en t=0 y t=1 da 0 y 1 respectivamente (excepto elastic/bounce).
  • 1.4 .md.

Fase 2 — bezier_editor

  • 2.1 bezier_eval: De Casteljau o forma polinomial. Implementacion puramente algebraica.
  • 2.2 bezier_editor: canvas ImGui con 4 puntos draggable (p0/p3 fijos en {0,0}/{1,1} para easing). Dibuja la curva con AddBezierCubic.
  • 2.3 Tests para bezier_eval.
  • 2.4 .md.

Fase 3 — timeline

  • 3.1 track_value_at: encuentra el segmento (k_i, k_{i+1}), normaliza t, aplica tween::apply(ease).
  • 3.2 timeline_update: si playing, avanza current_time += dt; loop al llegar a duration.
  • 3.3 timeline_widget: barra con tracks horizontales, keyframes como diamantes draggable, scrub time con click en barra superior, botones play/pause. Estilo basado en tokens.
  • 3.4 Tests para track_value_at (linear).
  • 3.5 .md.
  • 4.1 demos_animation.cpp:
    • demo_tween(): dropdown de Ease + curva animandose contra el tiempo.
    • demo_bezier_editor(): editor + plot de la curva resultante.
    • demo_timeline(): timeline con 2 tracks ("hue", "amp") + slider que muestra track_value_at(now) para cada uno.
  • 4.2 Registrar en gallery (3 entradas).

Fase 5 — Tests + docs

  • 5.1 Tests de easing y track_value_at.
  • 5.2 ./fn index + ./fn show de los 3.

Ejemplo de uso

fn::TimelineState tl{};
tl.tracks.push_back({"hue", {{0, 0}, {2, 1}, {4, 0}}});
tl.duration = 4.0f;
tl.playing  = true;

fn::run_app("anim", [&](float dt){
    fn::timeline_update(tl, dt);
    float h = fn::track_value_at(tl.tracks[0], tl.current_time);
    ImGui::Text("hue = %.3f", h);
    fn::timeline_widget("##tl", tl);
});

Decisiones de diseño

  • Tween header-only: codigo pequeño, alto uso → inline.
  • Bezier solo para easing (p0=0, p3=1) en MVP. Generalizable si hace falta.
  • Timeline simple, no jerarquico: tracks planos. Si hace falta layering en el futuro, otro issue.

Riesgos

  • UX del bezier_editor: precision de drag con mouse en canvas pequeño. Documentar tamaño minimo recomendado (180+).
  • Timeline drag race: keyframe drag debe respetar orden temporal. Reordenar al soltar.