diff --git a/cpp/functions/core/tween_curves.cpp b/cpp/functions/core/tween_curves.cpp new file mode 100644 index 00000000..d4cb26a9 --- /dev/null +++ b/cpp/functions/core/tween_curves.cpp @@ -0,0 +1,49 @@ +#include "core/tween_curves.h" + +namespace fn::tween { + +float apply(Ease e, float t) { + switch (e) { + case Ease::Linear: return linear(t); + case Ease::InQuad: return in_quad(t); + case Ease::OutQuad: return out_quad(t); + case Ease::InOutQuad: return in_out_quad(t); + case Ease::InCubic: return in_cubic(t); + case Ease::OutCubic: return out_cubic(t); + case Ease::InOutCubic: return in_out_cubic(t); + case Ease::InExpo: return in_expo(t); + case Ease::OutExpo: return out_expo(t); + case Ease::InOutExpo: return in_out_expo(t); + case Ease::InElastic: return in_elastic(t); + case Ease::OutElastic: return out_elastic(t); + case Ease::InOutElastic: return in_out_elastic(t); + case Ease::InBounce: return in_bounce(t); + case Ease::OutBounce: return out_bounce(t); + case Ease::InOutBounce: return in_out_bounce(t); + } + return t; +} + +const char* name(Ease e) { + switch (e) { + case Ease::Linear: return "Linear"; + case Ease::InQuad: return "InQuad"; + case Ease::OutQuad: return "OutQuad"; + case Ease::InOutQuad: return "InOutQuad"; + case Ease::InCubic: return "InCubic"; + case Ease::OutCubic: return "OutCubic"; + case Ease::InOutCubic: return "InOutCubic"; + case Ease::InExpo: return "InExpo"; + case Ease::OutExpo: return "OutExpo"; + case Ease::InOutExpo: return "InOutExpo"; + case Ease::InElastic: return "InElastic"; + case Ease::OutElastic: return "OutElastic"; + case Ease::InOutElastic: return "InOutElastic"; + case Ease::InBounce: return "InBounce"; + case Ease::OutBounce: return "OutBounce"; + case Ease::InOutBounce: return "InOutBounce"; + } + return "?"; +} + +} // namespace fn::tween diff --git a/cpp/functions/core/tween_curves.h b/cpp/functions/core/tween_curves.h new file mode 100644 index 00000000..d7403e4a --- /dev/null +++ b/cpp/functions/core/tween_curves.h @@ -0,0 +1,128 @@ +#pragma once + +// tween_curves — easing functions (Penner) for animations and interpolations. +// Header-mostly so the compiler inlines on hot paths. Pure: no I/O, no state. +// +// Reference: easings.net (Penner formulas). +// Conventions: +// - All functions take t in [0,1] and return f(t). +// - linear, *_quad, *_cubic, *_expo: f(0)=0, f(1)=1 exactly. +// - elastic / bounce: f(0)=0, f(1)=1 but f overshoots/undershoots in between. +// +// Usage: +// #include "core/tween_curves.h" +// float y = fn::tween::out_cubic(t); +// float y2 = fn::tween::apply(fn::tween::Ease::OutElastic, t); + +#include + +namespace fn::tween { + +enum class Ease { + Linear, + InQuad, OutQuad, InOutQuad, + InCubic, OutCubic, InOutCubic, + InExpo, OutExpo, InOutExpo, + InElastic, OutElastic, InOutElastic, + InBounce, OutBounce, InOutBounce, +}; + +// --- Linear ----------------------------------------------------------------- + +inline float linear(float t) { return t; } + +// --- Quadratic (t^2) -------------------------------------------------------- + +inline float in_quad(float t) { return t * t; } +inline float out_quad(float t) { return 1.0f - (1.0f - t) * (1.0f - t); } +inline float in_out_quad(float t) { + return (t < 0.5f) ? (2.0f * t * t) + : (1.0f - 0.5f * (2.0f - 2.0f * t) * (2.0f - 2.0f * t)); +} + +// --- Cubic (t^3) ------------------------------------------------------------ + +inline float in_cubic(float t) { return t * t * t; } +inline float out_cubic(float t) { float u = 1.0f - t; return 1.0f - u * u * u; } +inline float in_out_cubic(float t) { + if (t < 0.5f) return 4.0f * t * t * t; + float u = -2.0f * t + 2.0f; + return 1.0f - 0.5f * u * u * u; +} + +// --- Exponential ------------------------------------------------------------ + +inline float in_expo(float t) { + return (t <= 0.0f) ? 0.0f : std::pow(2.0f, 10.0f * t - 10.0f); +} +inline float out_expo(float t) { + return (t >= 1.0f) ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t); +} +inline float in_out_expo(float t) { + if (t <= 0.0f) return 0.0f; + if (t >= 1.0f) return 1.0f; + return (t < 0.5f) ? 0.5f * std::pow(2.0f, 20.0f * t - 10.0f) + : 1.0f - 0.5f * std::pow(2.0f, -20.0f * t + 10.0f); +} + +// --- Elastic (oscillation) -------------------------------------------------- + +inline float in_elastic(float t) { + if (t <= 0.0f) return 0.0f; + if (t >= 1.0f) return 1.0f; + constexpr float c4 = 6.283185307179586f / 3.0f; // 2*pi/3 + return -std::pow(2.0f, 10.0f * t - 10.0f) * std::sin((t * 10.0f - 10.75f) * c4); +} +inline float out_elastic(float t) { + if (t <= 0.0f) return 0.0f; + if (t >= 1.0f) return 1.0f; + constexpr float c4 = 6.283185307179586f / 3.0f; + return std::pow(2.0f, -10.0f * t) * std::sin((t * 10.0f - 0.75f) * c4) + 1.0f; +} +inline float in_out_elastic(float t) { + if (t <= 0.0f) return 0.0f; + if (t >= 1.0f) return 1.0f; + constexpr float c5 = 6.283185307179586f / 4.5f; // 2*pi/4.5 + if (t < 0.5f) { + return -0.5f * std::pow(2.0f, 20.0f * t - 10.0f) * std::sin((20.0f * t - 11.125f) * c5); + } + return 0.5f * std::pow(2.0f, -20.0f * t + 10.0f) * std::sin((20.0f * t - 11.125f) * c5) + 1.0f; +} + +// --- Bounce ----------------------------------------------------------------- + +inline float out_bounce(float t) { + constexpr float n1 = 7.5625f; + constexpr float d1 = 2.75f; + if (t < 1.0f / d1) { + return n1 * t * t; + } else if (t < 2.0f / d1) { + float u = t - 1.5f / d1; + return n1 * u * u + 0.75f; + } else if (t < 2.5f / d1) { + float u = t - 2.25f / d1; + return n1 * u * u + 0.9375f; + } else { + float u = t - 2.625f / d1; + return n1 * u * u + 0.984375f; + } +} +inline float in_bounce(float t) { return 1.0f - out_bounce(1.0f - t); } +inline float in_out_bounce(float t) { + return (t < 0.5f) ? 0.5f * (1.0f - out_bounce(1.0f - 2.0f * t)) + : 0.5f * (1.0f + out_bounce(2.0f * t - 1.0f)); +} + +// --- Dispatch --------------------------------------------------------------- + +// Apply easing by enum. Useful when the curve is data-driven (Keyframe.ease, +// dropdown selection in UI). +float apply(Ease e, float t); + +// Human-readable name for UI dropdowns. +const char* name(Ease e); + +// Total number of easing modes (for iterating dropdowns). +constexpr int ease_count = 16; + +} // namespace fn::tween diff --git a/cpp/functions/core/tween_curves.md b/cpp/functions/core/tween_curves.md new file mode 100644 index 00000000..7425261a --- /dev/null +++ b/cpp/functions/core/tween_curves.md @@ -0,0 +1,115 @@ +--- +name: tween_curves +kind: function +lang: cpp +domain: core +version: "1.0.0" +purity: pure +signature: "float fn::tween::apply(fn::tween::Ease e, float t) + ~16 named easing functions (linear, in_quad, out_quad, ...)" +description: "Set de funciones de easing puras (Penner) para animaciones e interpolaciones: linear, quad, cubic, expo, elastic, bounce, en variantes in/out/inOut. Header-mostly: el compilador inlinea cada curva en el sitio de llamada. Sin estado, sin I/O." +tags: [animation, easing, tween, penner, math, header-only] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/core/tween_curves.cpp" +framework: "" +params: + - name: e + desc: "Curva a aplicar (enum Ease): Linear, InQuad, OutQuad, InOutQuad, InCubic, OutCubic, InOutCubic, InExpo, OutExpo, InOutExpo, InElastic, OutElastic, InOutElastic, InBounce, OutBounce, InOutBounce" + - name: t + desc: "Progreso normalizado en [0,1]. Para t<0 o t>1 algunas curvas extrapolan razonablemente, otras saturan." +output: "f(t) — valor de la curva en t. Para curvas no oscilantes f(0)=0 y f(1)=1. Para elastic/bounce f(0)=0 y f(1)=1 pero f puede salir del rango [0,1] en valores intermedios." +--- + +# tween_curves + +Funciones de easing al estilo Penner. Permiten convertir un progreso lineal `t in [0,1]` en una curva temporal con feel concreto (acelerar, frenar, rebotar, oscilar). Son el ladrillo basico de toda animacion: timeline interpolation, transiciones UI, smooth lerp de uniforms, ramps de color. + +## API + +```cpp +namespace fn::tween { + +enum class Ease { + Linear, + InQuad, OutQuad, InOutQuad, + InCubic, OutCubic, InOutCubic, + InExpo, OutExpo, InOutExpo, + InElastic, OutElastic, InOutElastic, + InBounce, OutBounce, InOutBounce, +}; + +// Funciones inline named (todas (float)->float, t en [0,1]): +inline float linear(float t); +inline float in_quad(float t); inline float out_quad(float t); inline float in_out_quad(float t); +inline float in_cubic(float t); inline float out_cubic(float t); inline float in_out_cubic(float t); +inline float in_expo(float t); inline float out_expo(float t); inline float in_out_expo(float t); +inline float in_elastic(float t); inline float out_elastic(float t); inline float in_out_elastic(float t); +inline float in_bounce(float t); inline float out_bounce(float t); inline float in_out_bounce(float t); + +// Dispatch dinamico por enum (util para data-driven: Keyframe.ease, dropdown UI) +float apply(Ease e, float t); +const char* name(Ease e); // "Linear", "InQuad", ... + +constexpr int ease_count = 16; + +} // namespace fn::tween +``` + +## Ejemplo + +```cpp +#include "core/tween_curves.h" + +// Lerp con easing entre dos colores +float t = elapsed_ms / duration_ms; +float k = fn::tween::out_cubic(t); +ImVec4 c = lerp(c0, c1, k); + +// Data-driven (timeline keyframe) +fn::tween::Ease ease = key.ease; +float k = fn::tween::apply(ease, t_normalized); + +// Dropdown ImGui con todas las curvas +static int idx = 0; +const char* labels[fn::tween::ease_count]; +for (int i = 0; i < fn::tween::ease_count; i++) + labels[i] = fn::tween::name((fn::tween::Ease)i); +ImGui::Combo("Ease", &idx, labels, fn::tween::ease_count); +``` + +## Valores conocidos (smoke tests) + +| Curva | t=0 | t=0.5 | t=1 | +|-------------|-------|--------|-------| +| Linear | 0.000 | 0.500 | 1.000 | +| InQuad | 0.000 | 0.250 | 1.000 | +| OutQuad | 0.000 | 0.750 | 1.000 | +| InOutQuad | 0.000 | 0.500 | 1.000 | +| InCubic | 0.000 | 0.125 | 1.000 | +| OutCubic | 0.000 | 0.875 | 1.000 | +| InOutCubic | 0.000 | 0.500 | 1.000 | +| InExpo | 0.000 | ~0.031 | 1.000 | +| OutExpo | 0.000 | ~0.969 | 1.000 | +| InOutExpo | 0.000 | 0.500 | 1.000 | +| OutBounce | 0.000 | ~0.766 | 1.000 | + +Para todas las curvas: `f(0) == 0` y `f(1) == 1`. Las curvas elastic/bounce pueden salir de [0,1] en valores intermedios — no es un bug, es el overshoot deseado. + +## Decisiones + +- **Header-mostly**: las 16 funciones son `inline` en `.h` para que el compilador inline llamadas en hot paths (animaciones por frame, miles de muestras por curva en plots). +- **`apply(enum, t)` en .cpp**: para no forzar `` enorme en headers; util para casos data-driven. +- **Sin clamp interior**: el caller decide si saturar t. Esto permite efectos de overshoot deliberado. +- **Sin templates**: los easings se usan masivamente con `float`; un `T` genérico complicaría el header sin beneficio. + +## Referencias + +- easings.net (visualizaciones interactivas y formulas) +- Robert Penner — *Motion, Tweening, and Easing* (libro original)