Skip to content

Instantly share code, notes, and snippets.

@r-lyeh-archived
Last active August 27, 2025 02:30
Show Gist options
  • Save r-lyeh-archived/40d4fd0ea157ab3a58a4 to your computer and use it in GitHub Desktop.
Save r-lyeh-archived/40d4fd0ea157ab3a58a4 to your computer and use it in GitHub Desktop.

Revisions

  1. @r-lyeh r-lyeh revised this gist Feb 17, 2016. 1 changed file with 291 additions and 274 deletions.
    565 changes: 291 additions & 274 deletions curve.hpp
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,11 @@
    // [src] https://github.com/ocornut/imgui/issues/123
    // [src] https://github.com/ocornut/imgui/issues/55

    // v1.22 - flip button; cosmetic fixes
    // v1.21 - oops :)
    // v1.2 - add interpolation code from iq's []
    // v1.1 - easing and colors
    // v1.0 - jari komppa's original
    // v1.20 - add iq's interpolation code
    // v1.10 - easing and colors
    // v1.00 - jari komppa's original

    #pragma once

    @@ -13,29 +14,31 @@
    #define IMGUI_DEFINE_MATH_OPERATORS
    #include "imgui_internal.h"

    #include <cmath>

    /* To use, add this prototype somewhere..
    namespace ImGui
    {
    int Curve(const char *label, const ImVec2& size, int maxpoints, ImVec2 *points);
    float CurveValue(float p, int maxpoints, const ImVec2 *points);
    int Curve(const char *label, const ImVec2& size, int maxpoints, ImVec2 *points);
    float CurveValue(float p, int maxpoints, const ImVec2 *points);
    float CurveValueSmooth(float p, int maxpoints, const ImVec2 *points);
    };
    */
    /*
    Example of use:
    ImVec2 foo[10];
    ...
    foo[0].x = -1; // init data so editor knows to take it from here
    ...
    if (ImGui::Curve("Das editor", ImVec2(600, 200), 10, foo))
    {
    // curve changed
    }
    ...
    float value_you_care_about = ImGui::CurveValue(0.7f, 10, foo); // calculate value at position 0.7
    Example of use:
    ImVec2 foo[10];
    ...
    foo[0].x = -1; // init data so editor knows to take it from here
    ...
    if (ImGui::Curve("Das editor", ImVec2(600, 200), 10, foo))
    {
    // curve changed
    }
    ...
    float value_you_care_about = ImGui::CurveValue(0.7f, 10, foo); // calculate value at position 0.7
    */

    namespace tween {
    @@ -379,12 +382,14 @@ namespace tween {
    return sin(p * pi2);
    }
    }
    }
    }
    }

    namespace ImGui
    {
    // [src] http://iquilezles.org/www/articles/minispline/minispline.htm
    // key format (for dim == 1) is (t0,x0,t1,x1 ...)
    // key format (for dim == 2) is (t0,x0,y0,t1,x1,y1 ...)
    // key format (for dim == 3) is (t0,x0,y0,z0,t1,x1,y1,z1 ...)
    void spline( const float *key, int num, int dim, float t, float *v )
    {
    @@ -424,7 +429,7 @@ namespace ImGui
    return 0;
    if (p < 0) return points[0].y;

    float *input = new float [ maxpoints * 4 ];
    float *input = new float [ maxpoints * 2 ];
    float output[4];

    for( int i = 0; i < maxpoints; ++i ) {
    @@ -438,271 +443,283 @@ namespace ImGui
    return output[0];
    }

    float CurveValue(float p, int maxpoints, const ImVec2 *points)
    {
    if (maxpoints < 2 || points == 0)
    return 0;
    if (p < 0) return points[0].y;

    int left = 0;
    while (left < maxpoints && points[left].x < p && points[left].x != -1) left++;
    if (left) left--;

    if (left == maxpoints-1)
    return points[maxpoints - 1].y;

    float d = (p - points[left].x) / (points[left + 1].x - points[left].x);

    return points[left].y + (points[left + 1].y - points[left].y) * d;
    }

    int Curve(const char *label, const ImVec2& size, const int maxpoints, ImVec2 *points)
    {
    int modified = 0;
    int i;
    if (maxpoints < 2 || points == 0)
    return 0;

    if (points[0].x < 0)
    {
    points[0].x = 0;
    points[0].y = 0;
    points[1].x = 1;
    points[1].y = 1;
    points[2].x = -1;
    }

    ImGuiWindow* window = GetCurrentWindow();
    ImGuiState& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const ImGuiID id = window->GetID(label);
    if (window->SkipItems)
    return 0;

    ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    ItemSize(bb);
    if (!ItemAdd(bb, NULL))
    return 0;

    const bool hovered = IsHovered(bb, id);

    int max = 0;
    while (max < maxpoints && points[max].x >= 0) max++;

    int kill = 0;
    do
    {
    if (kill)
    {
    modified = 1;
    for (i = kill + 1; i < max; i++)
    {
    points[i - 1] = points[i];
    }
    max--;
    points[max].x = -1;
    kill = 0;
    }

    for (i = 1; i < max - 1; i++)
    {
    if (abs(points[i].x - points[i - 1].x) < 1 / 128.0)
    {
    kill = i;
    }
    }
    }
    while (kill);


    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg, 1), true, style.FrameRounding);

    float ht = bb.Max.y - bb.Min.y;
    float wd = bb.Max.x - bb.Min.x;

    if (hovered)
    {
    SetHoveredID(id);
    if (g.IO.MouseDown[0])
    {
    modified = 1;
    ImVec2 pos = (g.IO.MousePos - bb.Min) / (bb.Max - bb.Min);
    pos.y = 1 - pos.y;

    int left = 0;
    while (left < max && points[left].x < pos.x) left++;
    if (left) left--;

    ImVec2 p = points[left] - pos;
    float p1d = sqrt(p.x*p.x + p.y*p.y);
    p = points[left+1] - pos;
    float p2d = sqrt(p.x*p.x + p.y*p.y);
    int sel = -1;
    if (p1d < (1 / 16.0)) sel = left;
    if (p2d < (1 / 16.0)) sel = left + 1;

    if (sel != -1)
    {
    points[sel] = pos;
    }
    else
    {
    if (max < maxpoints)
    {
    max++;
    for (i = max; i > left; i--)
    {
    points[i] = points[i - 1];
    }
    points[left + 1] = pos;
    }
    if (max < maxpoints)
    points[max].x = -1;
    }


    // snap first/last to min/max
    points[0].x = 0;
    points[max - 1].x = 1;
    }
    }

    // bg grid
    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 2),
    ImVec2(bb.Max.x, bb.Min.y + ht / 2),
    GetColorU32(ImGuiCol_TextDisabled), 3);

    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 4),
    ImVec2(bb.Max.x, bb.Min.y + ht / 4),
    GetColorU32(ImGuiCol_TextDisabled));

    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 4 * 3),
    ImVec2(bb.Max.x, bb.Min.y + ht / 4 * 3),
    GetColorU32(ImGuiCol_TextDisabled));

    for (i = 0; i < 9; i++)
    {
    window->DrawList->AddLine(
    ImVec2(bb.Min.x + (wd / 10) * (i + 1), bb.Min.y),
    ImVec2(bb.Min.x + (wd / 10) * (i + 1), bb.Max.y),
    GetColorU32(ImGuiCol_TextDisabled));
    }

    // smooth curve
    enum { smoothness = 256 }; // the higher the smoother
    for( i = 0; i < (smoothness+1); ++i ) {
    float px = (i+0) / float(smoothness);
    float qx = (i+1) / float(smoothness);
    float py = 1 - CurveValueSmooth(px, maxpoints, points);
    float qy = 1 - CurveValueSmooth(qx, maxpoints, points);
    ImVec2 p( px * (bb.Max.x - bb.Min.x) + bb.Min.x, py * (bb.Max.y - bb.Min.y) + bb.Min.y);
    ImVec2 q( qx * (bb.Max.x - bb.Min.x) + bb.Min.x, qy * (bb.Max.y - bb.Min.y) + bb.Min.y);
    window->DrawList->AddLine(p, q, GetColorU32(ImGuiCol_PlotLines));
    }

    // lines
    for (i = 1; i < max; i++)
    {
    ImVec2 a = points[i - 1];
    ImVec2 b = points[i];
    a.y = 1 - a.y;
    b.y = 1 - b.y;
    a = a * (bb.Max - bb.Min) + bb.Min;
    b = b * (bb.Max - bb.Min) + bb.Min;
    window->DrawList->AddLine(a, b, GetColorU32(ImGuiCol_PlotLinesHovered));
    }

    if (hovered)
    {
    // control points
    for (i = 0; i < max; i++)
    {
    ImVec2 p = points[i];
    p.y = 1 - p.y;
    p = p * (bb.Max - bb.Min) + bb.Min;
    ImVec2 a = p - ImVec2(2, 2);
    ImVec2 b = p + ImVec2(2, 2);
    window->DrawList->AddRect(a, b, GetColorU32(ImGuiCol_PlotLinesHovered));
    }
    }

    // curve selector
    const char* items[] = {
    "Custom",

    "Linear",
    "Quad in",
    "Quad out",
    "Quad in out",
    "Cubic in",
    "Cubic out",
    "Cubic in out",
    "Quart in",
    "Quart out",
    "Quart in out",
    "Quint in",
    "Quint out",
    "Quint in out",
    "Sine in",
    "Sine out",
    "Sine in out",
    "Expo in",
    "Expo out",
    "Expo in out",
    "Circ in",
    "Circ out",
    "Circ in out",
    "Elastic in",
    "Elastic out",
    "Elastic in out",
    "Back in",
    "Back out",
    "Back in out",
    "Bounce in",
    "Bounce out",
    "Bounce in out",

    "Sine square",
    "Exponential",

    "Schubring1",
    "Schubring2",
    "Schubring3",

    "SinPi2",
    "Swing"
    };
    float CurveValue(float p, int maxpoints, const ImVec2 *points)
    {
    if (maxpoints < 2 || points == 0)
    return 0;
    if (p < 0) return points[0].y;

    int left = 0;
    while (left < maxpoints && points[left].x < p && points[left].x != -1) left++;
    if (left) left--;

    if (left == maxpoints-1)
    return points[maxpoints - 1].y;

    float d = (p - points[left].x) / (points[left + 1].x - points[left].x);

    return points[left].y + (points[left + 1].y - points[left].y) * d;
    }

    int Curve(const char *label, const ImVec2& size, const int maxpoints, ImVec2 *points)
    {
    int modified = 0;
    int i;
    if (maxpoints < 2 || points == 0)
    return 0;

    if (points[0].x < 0)
    {
    points[0].x = 0;
    points[0].y = 0;
    points[1].x = 1;
    points[1].y = 1;
    points[2].x = -1;
    }

    ImGuiWindow* window = GetCurrentWindow();
    ImGuiState& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const ImGuiID id = window->GetID(label);
    if (window->SkipItems)
    return 0;

    ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    ItemSize(bb);
    if (!ItemAdd(bb, NULL))
    return 0;

    const bool hovered = IsHovered(bb, id);

    int max = 0;
    while (max < maxpoints && points[max].x >= 0) max++;

    int kill = 0;
    do
    {
    if (kill)
    {
    modified = 1;
    for (i = kill + 1; i < max; i++)
    {
    points[i - 1] = points[i];
    }
    max--;
    points[max].x = -1;
    kill = 0;
    }

    for (i = 1; i < max - 1; i++)
    {
    if (abs(points[i].x - points[i - 1].x) < 1 / 128.0)
    {
    kill = i;
    }
    }
    }
    while (kill);


    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg, 1), true, style.FrameRounding);

    float ht = bb.Max.y - bb.Min.y;
    float wd = bb.Max.x - bb.Min.x;

    if (hovered)
    {
    SetHoveredID(id);
    if (g.IO.MouseDown[0])
    {
    modified = 1;
    ImVec2 pos = (g.IO.MousePos - bb.Min) / (bb.Max - bb.Min);
    pos.y = 1 - pos.y;

    int left = 0;
    while (left < max && points[left].x < pos.x) left++;
    if (left) left--;

    ImVec2 p = points[left] - pos;
    float p1d = sqrt(p.x*p.x + p.y*p.y);
    p = points[left+1] - pos;
    float p2d = sqrt(p.x*p.x + p.y*p.y);
    int sel = -1;
    if (p1d < (1 / 16.0)) sel = left;
    if (p2d < (1 / 16.0)) sel = left + 1;

    if (sel != -1)
    {
    points[sel] = pos;
    }
    else
    {
    if (max < maxpoints)
    {
    max++;
    for (i = max; i > left; i--)
    {
    points[i] = points[i - 1];
    }
    points[left + 1] = pos;
    }
    if (max < maxpoints)
    points[max].x = -1;
    }

    // snap first/last to min/max
    if( points[0].x < points[max - 1].x ) {
    points[0].x= 0;
    points[max - 1].x = 1;
    } else {
    points[0].x= 1;
    points[max - 1].x = 0;
    }
    }
    }

    // bg grid
    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 2),
    ImVec2(bb.Max.x, bb.Min.y + ht / 2),
    GetColorU32(ImGuiCol_TextDisabled), 3);

    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 4),
    ImVec2(bb.Max.x, bb.Min.y + ht / 4),
    GetColorU32(ImGuiCol_TextDisabled));

    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 4 * 3),
    ImVec2(bb.Max.x, bb.Min.y + ht / 4 * 3),
    GetColorU32(ImGuiCol_TextDisabled));

    for (i = 0; i < 9; i++)
    {
    window->DrawList->AddLine(
    ImVec2(bb.Min.x + (wd / 10) * (i + 1), bb.Min.y),
    ImVec2(bb.Min.x + (wd / 10) * (i + 1), bb.Max.y),
    GetColorU32(ImGuiCol_TextDisabled));
    }

    // smooth curve
    enum { smoothness = 256 }; // the higher the smoother
    for( i = 0; i <= (smoothness-1); ++i ) {
    float px = (i+0) / float(smoothness);
    float qx = (i+1) / float(smoothness);
    float py = 1 - CurveValueSmooth(px, maxpoints, points);
    float qy = 1 - CurveValueSmooth(qx, maxpoints, points);
    ImVec2 p( px * (bb.Max.x - bb.Min.x) + bb.Min.x, py * (bb.Max.y - bb.Min.y) + bb.Min.y);
    ImVec2 q( qx * (bb.Max.x - bb.Min.x) + bb.Min.x, qy * (bb.Max.y - bb.Min.y) + bb.Min.y);
    window->DrawList->AddLine(p, q, GetColorU32(ImGuiCol_PlotLines));
    }

    // lines
    for (i = 1; i < max; i++)
    {
    ImVec2 a = points[i - 1];
    ImVec2 b = points[i];
    a.y = 1 - a.y;
    b.y = 1 - b.y;
    a = a * (bb.Max - bb.Min) + bb.Min;
    b = b * (bb.Max - bb.Min) + bb.Min;
    window->DrawList->AddLine(a, b, GetColorU32(ImGuiCol_PlotLinesHovered));
    }

    if (hovered)
    {
    // control points
    for (i = 0; i < max; i++)
    {
    ImVec2 p = points[i];
    p.y = 1 - p.y;
    p = p * (bb.Max - bb.Min) + bb.Min;
    ImVec2 a = p - ImVec2(2, 2);
    ImVec2 b = p + ImVec2(2, 2);
    window->DrawList->AddRect(a, b, GetColorU32(ImGuiCol_PlotLinesHovered));
    }
    }

    // buttons; @todo: mirror, smooth, tessellate
    if( ImGui::Button("Flip") ) {
    for( i = 0; i < max; ++i) {
    points[i].y = 1 - points[i].y;
    }
    }
    ImGui::SameLine();

    // curve selector
    const char* items[] = {
    "Custom",

    "Linear",
    "Quad in",
    "Quad out",
    "Quad in out",
    "Cubic in",
    "Cubic out",
    "Cubic in out",
    "Quart in",
    "Quart out",
    "Quart in out",
    "Quint in",
    "Quint out",
    "Quint in out",
    "Sine in",
    "Sine out",
    "Sine in out",
    "Expo in",
    "Expo out",
    "Expo in out",
    "Circ in",
    "Circ out",
    "Circ in out",
    "Elastic in",
    "Elastic out",
    "Elastic in out",
    "Back in",
    "Back out",
    "Back in out",
    "Bounce in",
    "Bounce out",
    "Bounce in out",

    "Sine square",
    "Exponential",

    "Schubring1",
    "Schubring2",
    "Schubring3",

    "SinPi2",
    "Swing"
    };
    static int item = 0;
    if( modified ) {
    item = 0;
    item = 0;
    }
    if( ImGui::Combo("Ease type", &item, items, IM_ARRAYSIZE(items)) ) {
    max = maxpoints;
    if( item > 0 ) {
    for( i = 0; i < max; ++i) {
    points[i].x = i / float(max-1);
    points[i].y = float( tween::ease( item - 1, points[i].x ) );
    }
    }
    max = maxpoints;
    if( item > 0 ) {
    for( i = 0; i < max; ++i) {
    points[i].x = i / float(max-1);
    points[i].y = float( tween::ease( item - 1, points[i].x ) );
    }
    }
    }

    char buf[128];
    const char *str = label;

    if( hovered ) {
    ImVec2 pos = (g.IO.MousePos - bb.Min) / (bb.Max - bb.Min);
    pos.y = 1 - pos.y;
    if( hovered ) {
    ImVec2 pos = (g.IO.MousePos - bb.Min) / (bb.Max - bb.Min);
    pos.y = 1 - pos.y;

    sprintf(buf, "%s (%f,%f)", label, pos.x, pos.y );
    str = buf;
    }
    sprintf(buf, "%s (%f,%f)", label, pos.x, pos.y );
    str = buf;
    }

    RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + style.FramePadding.y), bb.Max, str, NULL, NULL, ImGuiAlign_Center);
    RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + style.FramePadding.y), bb.Max, str, NULL, NULL, ImGuiAlign_Center);

    return modified;
    }
    return modified;
    }

    };
    };
  2. @r-lyeh r-lyeh revised this gist Feb 16, 2016. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions curve.hpp
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,11 @@
    // [src] https://github.com/ocornut/imgui/issues/123
    // [src] https://github.com/ocornut/imgui/issues/55

    // v1.21 - oops :)
    // v1.2 - add interpolation code from iq's []
    // v1.1 - easing and colors
    // v1.0 - jari komppa's original

    #pragma once

    #include "imgui.h"
    @@ -14,6 +19,7 @@ namespace ImGui
    {
    int Curve(const char *label, const ImVec2& size, int maxpoints, ImVec2 *points);
    float CurveValue(float p, int maxpoints, const ImVec2 *points);
    float CurveValueSmooth(float p, int maxpoints, const ImVec2 *points);
    };
    */
  3. @r-lyeh r-lyeh revised this gist Feb 16, 2016. 1 changed file with 6 additions and 12 deletions.
    18 changes: 6 additions & 12 deletions curve.hpp
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,6 @@
    // [src] https://github.com/ocornut/imgui/issues/123
    // [src] https://github.com/ocornut/imgui/issues/55

    // v1.2 - add interpolation code from iq's []
    // v1.1 - easing and colors
    // v1.0 - jari komppa's original

    #pragma once

    #include "imgui.h"
    @@ -17,8 +13,7 @@
    namespace ImGui
    {
    int Curve(const char *label, const ImVec2& size, int maxpoints, ImVec2 *points);
    float CurveValue(float p, int maxpoints, const ImVec2 *points);
    float CurveValueSmooth(float p, int maxpoints, const ImVec2 *points);
    float CurveValue(float p, int maxpoints, const ImVec2 *points);
    };
    */
    @@ -427,15 +422,14 @@ namespace ImGui
    float output[4];

    for( int i = 0; i < maxpoints; ++i ) {
    input[ i * 3 + 0 ] = i / float(maxpoints-1);
    input[ i * 3 + 1 ] = points[i].x;
    input[ i * 3 + 2 ] = points[i].y;
    input[ i * 2 + 0 ] = points[i].x;
    input[ i * 2 + 1 ] = points[i].y;
    }

    spline( input, maxpoints, 2, p, output );
    spline( input, maxpoints, 1, p, output );

    delete [] input;
    return output[1];
    return output[0];
    }

    float CurveValue(float p, int maxpoints, const ImVec2 *points)
    @@ -705,4 +699,4 @@ namespace ImGui
    return modified;
    }

    };
    };
  4. @r-lyeh r-lyeh revised this gist Feb 16, 2016. 1 changed file with 66 additions and 6 deletions.
    72 changes: 66 additions & 6 deletions curve.hpp
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,10 @@
    // [src] https://github.com/ocornut/imgui/issues/123
    // [src] https://github.com/ocornut/imgui/issues/55

    // v1.2 - add interpolation code from iq's []
    // v1.1 - easing and colors
    // v1.0 - jari komppa's original

    #pragma once

    #include "imgui.h"
    @@ -13,7 +17,8 @@
    namespace ImGui
    {
    int Curve(const char *label, const ImVec2& size, int maxpoints, ImVec2 *points);
    float CurveValue(float p, int maxpoints, const ImVec2 *points);
    float CurveValue(float p, int maxpoints, const ImVec2 *points);
    float CurveValueSmooth(float p, int maxpoints, const ImVec2 *points);
    };
    */
    @@ -378,6 +383,61 @@ namespace tween {

    namespace ImGui
    {
    // [src] http://iquilezles.org/www/articles/minispline/minispline.htm
    // key format (for dim == 3) is (t0,x0,y0,z0,t1,x1,y1,z1 ...)
    void spline( const float *key, int num, int dim, float t, float *v )
    {
    static signed char coefs[16] = {
    -1, 2,-1, 0,
    3,-5, 0, 2,
    -3, 4, 1, 0,
    1,-1, 0, 0 };

    const int size = dim + 1;

    // find key
    int k = 0; while( key[k*size] < t ) k++;

    // interpolant
    const float h = (t-key[(k-1)*size])/(key[k*size]-key[(k-1)*size]);

    // init result
    for( int i=0; i < dim; i++ ) v[i] = 0.0f;

    // add basis functions
    for( int i=0; i<4; i++ )
    {
    int kn = k+i-2; if( kn<0 ) kn=0; else if( kn>(num-1) ) kn=num-1;

    const signed char *co = coefs + 4*i;

    const float b = 0.5f*(((co[0]*h + co[1])*h + co[2])*h + co[3]);

    for( int j=0; j < dim; j++ ) v[j] += b * key[kn*size+j+1];
    }
    }

    float CurveValueSmooth(float p, int maxpoints, const ImVec2 *points)
    {
    if (maxpoints < 2 || points == 0)
    return 0;
    if (p < 0) return points[0].y;

    float *input = new float [ maxpoints * 4 ];
    float output[4];

    for( int i = 0; i < maxpoints; ++i ) {
    input[ i * 3 + 0 ] = i / float(maxpoints-1);
    input[ i * 3 + 1 ] = points[i].x;
    input[ i * 3 + 2 ] = points[i].y;
    }

    spline( input, maxpoints, 2, p, output );

    delete [] input;
    return output[1];
    }

    float CurveValue(float p, int maxpoints, const ImVec2 *points)
    {
    if (maxpoints < 2 || points == 0)
    @@ -531,17 +591,17 @@ namespace ImGui
    GetColorU32(ImGuiCol_TextDisabled));
    }

    /* smooth curve
    // smooth curve
    enum { smoothness = 256 }; // the higher the smoother
    for( i = 0; i < (smoothness+1); ++i ) {
    float px = (i+0) / float(smoothness);
    float qx = (i+1) / float(smoothness);
    float py = 1 - CurveValue(px, maxpoints, points);
    float qy = 1 - CurveValue(qx, maxpoints, points);
    float py = 1 - CurveValueSmooth(px, maxpoints, points);
    float qy = 1 - CurveValueSmooth(qx, maxpoints, points);
    ImVec2 p( px * (bb.Max.x - bb.Min.x) + bb.Min.x, py * (bb.Max.y - bb.Min.y) + bb.Min.y);
    ImVec2 q( qx * (bb.Max.x - bb.Min.x) + bb.Min.x, qy * (bb.Max.y - bb.Min.y) + bb.Min.y);
    window->DrawList->AddLine(p, q, GetColorU32(ImGuiCol_PlotLines));
    } */
    }

    // lines
    for (i = 1; i < max; i++)
    @@ -645,4 +705,4 @@ namespace ImGui
    return modified;
    }

    };
    };
  5. @r-lyeh r-lyeh created this gist Feb 15, 2016.
    648 changes: 648 additions & 0 deletions curve.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,648 @@
    // [src] https://github.com/ocornut/imgui/issues/123
    // [src] https://github.com/ocornut/imgui/issues/55

    #pragma once

    #include "imgui.h"

    #define IMGUI_DEFINE_MATH_OPERATORS
    #include "imgui_internal.h"

    /* To use, add this prototype somewhere..
    namespace ImGui
    {
    int Curve(const char *label, const ImVec2& size, int maxpoints, ImVec2 *points);
    float CurveValue(float p, int maxpoints, const ImVec2 *points);
    };
    */
    /*
    Example of use:
    ImVec2 foo[10];
    ...
    foo[0].x = -1; // init data so editor knows to take it from here
    ...
    if (ImGui::Curve("Das editor", ImVec2(600, 200), 10, foo))
    {
    // curve changed
    }
    ...
    float value_you_care_about = ImGui::CurveValue(0.7f, 10, foo); // calculate value at position 0.7
    */

    namespace tween {
    enum TYPE
    {
    LINEAR,

    QUADIN, // t^2
    QUADOUT,
    QUADINOUT,
    CUBICIN, // t^3
    CUBICOUT,
    CUBICINOUT,
    QUARTIN, // t^4
    QUARTOUT,
    QUARTINOUT,
    QUINTIN, // t^5
    QUINTOUT,
    QUINTINOUT,
    SINEIN, // sin(t)
    SINEOUT,
    SINEINOUT,
    EXPOIN, // 2^t
    EXPOOUT,
    EXPOINOUT,
    CIRCIN, // sqrt(1-t^2)
    CIRCOUT,
    CIRCINOUT,
    ELASTICIN, // exponentially decaying sine wave
    ELASTICOUT,
    ELASTICINOUT,
    BACKIN, // overshooting cubic easing: (s+1)*t^3 - s*t^2
    BACKOUT,
    BACKINOUT,
    BOUNCEIN, // exponentially decaying parabolic bounce
    BOUNCEOUT,
    BOUNCEINOUT,

    SINESQUARE, // gapjumper's
    EXPONENTIAL, // gapjumper's
    SCHUBRING1, // terry schubring's formula 1
    SCHUBRING2, // terry schubring's formula 2
    SCHUBRING3, // terry schubring's formula 3

    SINPI2, // tomas cepeda's
    SWING, // tomas cepeda's & lquery's
    };

    // }

    // implementation

    static inline
    double ease( int easetype, double t )
    {
    using namespace std;

    const double d = 1.f;
    const double pi = 3.1415926535897932384626433832795;
    const double pi2 = 3.1415926535897932384626433832795 / 2;

    double p = t/d;

    switch( easetype )
    {
    // Modeled after the line y = x
    default:
    case TYPE::LINEAR: {
    return p;
    }

    // Modeled after the parabola y = x^2
    case TYPE::QUADIN: {
    return p * p;
    }

    // Modeled after the parabola y = -x^2 + 2x
    case TYPE::QUADOUT: {
    return -(p * (p - 2));
    }

    // Modeled after the piecewise quadratic
    // y = (1/2)((2x)^2) ; [0, 0.5)
    // y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
    case TYPE::QUADINOUT: {
    if(p < 0.5) {
    return 2 * p * p;
    }
    else {
    return (-2 * p * p) + (4 * p) - 1;
    }
    }

    // Modeled after the cubic y = x^3
    case TYPE::CUBICIN: {
    return p * p * p;
    }

    // Modeled after the cubic y = (x - 1)^3 + 1
    case TYPE::CUBICOUT: {
    double f = (p - 1);
    return f * f * f + 1;
    }

    // Modeled after the piecewise cubic
    // y = (1/2)((2x)^3) ; [0, 0.5)
    // y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
    case TYPE::CUBICINOUT: {
    if(p < 0.5) {
    return 4 * p * p * p;
    }
    else {
    double f = ((2 * p) - 2);
    return 0.5 * f * f * f + 1;
    }
    }

    // Modeled after the quartic x^4
    case TYPE::QUARTIN: {
    return p * p * p * p;
    }

    // Modeled after the quartic y = 1 - (x - 1)^4
    case TYPE::QUARTOUT: {
    double f = (p - 1);
    return f * f * f * (1 - p) + 1;
    }

    // Modeled after the piecewise quartic
    // y = (1/2)((2x)^4) ; [0, 0.5)
    // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
    case TYPE::QUARTINOUT: {
    if(p < 0.5) {
    return 8 * p * p * p * p;
    }
    else {
    double f = (p - 1);
    return -8 * f * f * f * f + 1;
    }
    }

    // Modeled after the quintic y = x^5
    case TYPE::QUINTIN: {
    return p * p * p * p * p;
    }

    // Modeled after the quintic y = (x - 1)^5 + 1
    case TYPE::QUINTOUT: {
    double f = (p - 1);
    return f * f * f * f * f + 1;
    }

    // Modeled after the piecewise quintic
    // y = (1/2)((2x)^5) ; [0, 0.5)
    // y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
    case TYPE::QUINTINOUT: {
    if(p < 0.5) {
    return 16 * p * p * p * p * p;
    }
    else {
    double f = ((2 * p) - 2);
    return 0.5 * f * f * f * f * f + 1;
    }
    }

    // Modeled after quarter-cycle of sine wave
    case TYPE::SINEIN: {
    return sin((p - 1) * pi2) + 1;
    }

    // Modeled after quarter-cycle of sine wave (different phase)
    case TYPE::SINEOUT: {
    return sin(p * pi2);
    }

    // Modeled after half sine wave
    case TYPE::SINEINOUT: {
    return 0.5 * (1 - cos(p * pi));
    }

    // Modeled after shifted quadrant IV of unit circle
    case TYPE::CIRCIN: {
    return 1 - sqrt(1 - (p * p));
    }

    // Modeled after shifted quadrant II of unit circle
    case TYPE::CIRCOUT: {
    return sqrt((2 - p) * p);
    }

    // Modeled after the piecewise circular function
    // y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
    // y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
    case TYPE::CIRCINOUT: {
    if(p < 0.5) {
    return 0.5 * (1 - sqrt(1 - 4 * (p * p)));
    }
    else {
    return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1);
    }
    }

    // Modeled after the exponential function y = 2^(10(x - 1))
    case TYPE::EXPOIN: {
    return (p == 0.0) ? p : pow(2, 10 * (p - 1));
    }

    // Modeled after the exponential function y = -2^(-10x) + 1
    case TYPE::EXPOOUT: {
    return (p == 1.0) ? p : 1 - pow(2, -10 * p);
    }

    // Modeled after the piecewise exponential
    // y = (1/2)2^(10(2x - 1)) ; [0,0.5)
    // y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
    case TYPE::EXPOINOUT: {
    if(p == 0.0 || p == 1.0) return p;

    if(p < 0.5) {
    return 0.5 * pow(2, (20 * p) - 10);
    }
    else {
    return -0.5 * pow(2, (-20 * p) + 10) + 1;
    }
    }

    // Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
    case TYPE::ELASTICIN: {
    return sin(13 * pi2 * p) * pow(2, 10 * (p - 1));
    }

    // Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1
    case TYPE::ELASTICOUT: {
    return sin(-13 * pi2 * (p + 1)) * pow(2, -10 * p) + 1;
    }

    // Modeled after the piecewise exponentially-damped sine wave:
    // y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
    // y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
    case TYPE::ELASTICINOUT: {
    if(p < 0.5) {
    return 0.5 * sin(13 * pi2 * (2 * p)) * pow(2, 10 * ((2 * p) - 1));
    }
    else {
    return 0.5 * (sin(-13 * pi2 * ((2 * p - 1) + 1)) * pow(2, -10 * (2 * p - 1)) + 2);
    }
    }

    // Modeled (originally) after the overshooting cubic y = x^3-x*sin(x*pi)
    case TYPE::BACKIN: { /*
    return p * p * p - p * sin(p * pi); */
    double s = 1.70158f;
    return p * p * ((s + 1) * p - s);
    }

    // Modeled (originally) after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
    case TYPE::BACKOUT: { /*
    double f = (1 - p);
    return 1 - (f * f * f - f * sin(f * pi)); */
    double s = 1.70158f;
    return --p, 1.f * (p*p*((s+1)*p + s) + 1);
    }

    // Modeled (originally) after the piecewise overshooting cubic function:
    // y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
    // y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
    case TYPE::BACKINOUT: { /*
    if(p < 0.5) {
    double f = 2 * p;
    return 0.5 * (f * f * f - f * sin(f * pi));
    }
    else {
    double f = (1 - (2*p - 1));
    return 0.5 * (1 - (f * f * f - f * sin(f * pi))) + 0.5;
    } */
    double s = 1.70158f * 1.525f;
    if (p < 0.5) {
    return p *= 2, 0.5 * p * p * (p*s+p-s);
    }
    else {
    return p = p * 2 - 2, 0.5 * (2 + p*p*(p*s+p+s));
    }
    }

    # define tween$bounceout(p) ( \
    (p) < 4/11.0 ? (121 * (p) * (p))/16.0 : \
    (p) < 8/11.0 ? (363/40.0 * (p) * (p)) - (99/10.0 * (p)) + 17/5.0 : \
    (p) < 9/10.0 ? (4356/361.0 * (p) * (p)) - (35442/1805.0 * (p)) + 16061/1805.0 \
    : (54/5.0 * (p) * (p)) - (513/25.0 * (p)) + 268/25.0 )

    case TYPE::BOUNCEIN: {
    return 1 - tween$bounceout(1 - p);
    }

    case TYPE::BOUNCEOUT: {
    return tween$bounceout(p);
    }

    case TYPE::BOUNCEINOUT: {
    if(p < 0.5) {
    return 0.5 * (1 - tween$bounceout(1 - p * 2));
    }
    else {
    return 0.5 * tween$bounceout((p * 2 - 1)) + 0.5;
    }
    }

    # undef tween$bounceout

    case TYPE::SINESQUARE: {
    double A = sin((p)*pi2);
    return A*A;
    }

    case TYPE::EXPONENTIAL: {
    return 1/(1+exp(6-12*(p)));
    }

    case TYPE::SCHUBRING1: {
    return 2*(p+(0.5f-p)*abs(0.5f-p))-0.5f;
    }

    case TYPE::SCHUBRING2: {
    double p1pass= 2*(p+(0.5f-p)*abs(0.5f-p))-0.5f;
    double p2pass= 2*(p1pass+(0.5f-p1pass)*abs(0.5f-p1pass))-0.5f;
    double pAvg=(p1pass+p2pass)/2;
    return pAvg;
    }

    case TYPE::SCHUBRING3: {
    double p1pass= 2*(p+(0.5f-p)*abs(0.5f-p))-0.5f;
    double p2pass= 2*(p1pass+(0.5f-p1pass)*abs(0.5f-p1pass))-0.5f;
    return p2pass;
    }

    case TYPE::SWING: {
    return ((-cos(pi * p) * 0.5) + 0.5);
    }

    case TYPE::SINPI2: {
    return sin(p * pi2);
    }
    }
    }
    }

    namespace ImGui
    {
    float CurveValue(float p, int maxpoints, const ImVec2 *points)
    {
    if (maxpoints < 2 || points == 0)
    return 0;
    if (p < 0) return points[0].y;

    int left = 0;
    while (left < maxpoints && points[left].x < p && points[left].x != -1) left++;
    if (left) left--;

    if (left == maxpoints-1)
    return points[maxpoints - 1].y;

    float d = (p - points[left].x) / (points[left + 1].x - points[left].x);

    return points[left].y + (points[left + 1].y - points[left].y) * d;
    }

    int Curve(const char *label, const ImVec2& size, const int maxpoints, ImVec2 *points)
    {
    int modified = 0;
    int i;
    if (maxpoints < 2 || points == 0)
    return 0;

    if (points[0].x < 0)
    {
    points[0].x = 0;
    points[0].y = 0;
    points[1].x = 1;
    points[1].y = 1;
    points[2].x = -1;
    }

    ImGuiWindow* window = GetCurrentWindow();
    ImGuiState& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const ImGuiID id = window->GetID(label);
    if (window->SkipItems)
    return 0;

    ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    ItemSize(bb);
    if (!ItemAdd(bb, NULL))
    return 0;

    const bool hovered = IsHovered(bb, id);

    int max = 0;
    while (max < maxpoints && points[max].x >= 0) max++;

    int kill = 0;
    do
    {
    if (kill)
    {
    modified = 1;
    for (i = kill + 1; i < max; i++)
    {
    points[i - 1] = points[i];
    }
    max--;
    points[max].x = -1;
    kill = 0;
    }

    for (i = 1; i < max - 1; i++)
    {
    if (abs(points[i].x - points[i - 1].x) < 1 / 128.0)
    {
    kill = i;
    }
    }
    }
    while (kill);


    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg, 1), true, style.FrameRounding);

    float ht = bb.Max.y - bb.Min.y;
    float wd = bb.Max.x - bb.Min.x;

    if (hovered)
    {
    SetHoveredID(id);
    if (g.IO.MouseDown[0])
    {
    modified = 1;
    ImVec2 pos = (g.IO.MousePos - bb.Min) / (bb.Max - bb.Min);
    pos.y = 1 - pos.y;

    int left = 0;
    while (left < max && points[left].x < pos.x) left++;
    if (left) left--;

    ImVec2 p = points[left] - pos;
    float p1d = sqrt(p.x*p.x + p.y*p.y);
    p = points[left+1] - pos;
    float p2d = sqrt(p.x*p.x + p.y*p.y);
    int sel = -1;
    if (p1d < (1 / 16.0)) sel = left;
    if (p2d < (1 / 16.0)) sel = left + 1;

    if (sel != -1)
    {
    points[sel] = pos;
    }
    else
    {
    if (max < maxpoints)
    {
    max++;
    for (i = max; i > left; i--)
    {
    points[i] = points[i - 1];
    }
    points[left + 1] = pos;
    }
    if (max < maxpoints)
    points[max].x = -1;
    }


    // snap first/last to min/max
    points[0].x = 0;
    points[max - 1].x = 1;
    }
    }

    // bg grid
    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 2),
    ImVec2(bb.Max.x, bb.Min.y + ht / 2),
    GetColorU32(ImGuiCol_TextDisabled), 3);

    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 4),
    ImVec2(bb.Max.x, bb.Min.y + ht / 4),
    GetColorU32(ImGuiCol_TextDisabled));

    window->DrawList->AddLine(
    ImVec2(bb.Min.x, bb.Min.y + ht / 4 * 3),
    ImVec2(bb.Max.x, bb.Min.y + ht / 4 * 3),
    GetColorU32(ImGuiCol_TextDisabled));

    for (i = 0; i < 9; i++)
    {
    window->DrawList->AddLine(
    ImVec2(bb.Min.x + (wd / 10) * (i + 1), bb.Min.y),
    ImVec2(bb.Min.x + (wd / 10) * (i + 1), bb.Max.y),
    GetColorU32(ImGuiCol_TextDisabled));
    }

    /* smooth curve
    enum { smoothness = 256 }; // the higher the smoother
    for( i = 0; i < (smoothness+1); ++i ) {
    float px = (i+0) / float(smoothness);
    float qx = (i+1) / float(smoothness);
    float py = 1 - CurveValue(px, maxpoints, points);
    float qy = 1 - CurveValue(qx, maxpoints, points);
    ImVec2 p( px * (bb.Max.x - bb.Min.x) + bb.Min.x, py * (bb.Max.y - bb.Min.y) + bb.Min.y);
    ImVec2 q( qx * (bb.Max.x - bb.Min.x) + bb.Min.x, qy * (bb.Max.y - bb.Min.y) + bb.Min.y);
    window->DrawList->AddLine(p, q, GetColorU32(ImGuiCol_PlotLines));
    } */

    // lines
    for (i = 1; i < max; i++)
    {
    ImVec2 a = points[i - 1];
    ImVec2 b = points[i];
    a.y = 1 - a.y;
    b.y = 1 - b.y;
    a = a * (bb.Max - bb.Min) + bb.Min;
    b = b * (bb.Max - bb.Min) + bb.Min;
    window->DrawList->AddLine(a, b, GetColorU32(ImGuiCol_PlotLinesHovered));
    }

    if (hovered)
    {
    // control points
    for (i = 0; i < max; i++)
    {
    ImVec2 p = points[i];
    p.y = 1 - p.y;
    p = p * (bb.Max - bb.Min) + bb.Min;
    ImVec2 a = p - ImVec2(2, 2);
    ImVec2 b = p + ImVec2(2, 2);
    window->DrawList->AddRect(a, b, GetColorU32(ImGuiCol_PlotLinesHovered));
    }
    }

    // curve selector
    const char* items[] = {
    "Custom",

    "Linear",
    "Quad in",
    "Quad out",
    "Quad in out",
    "Cubic in",
    "Cubic out",
    "Cubic in out",
    "Quart in",
    "Quart out",
    "Quart in out",
    "Quint in",
    "Quint out",
    "Quint in out",
    "Sine in",
    "Sine out",
    "Sine in out",
    "Expo in",
    "Expo out",
    "Expo in out",
    "Circ in",
    "Circ out",
    "Circ in out",
    "Elastic in",
    "Elastic out",
    "Elastic in out",
    "Back in",
    "Back out",
    "Back in out",
    "Bounce in",
    "Bounce out",
    "Bounce in out",

    "Sine square",
    "Exponential",

    "Schubring1",
    "Schubring2",
    "Schubring3",

    "SinPi2",
    "Swing"
    };
    static int item = 0;
    if( modified ) {
    item = 0;
    }
    if( ImGui::Combo("Ease type", &item, items, IM_ARRAYSIZE(items)) ) {
    max = maxpoints;
    if( item > 0 ) {
    for( i = 0; i < max; ++i) {
    points[i].x = i / float(max-1);
    points[i].y = float( tween::ease( item - 1, points[i].x ) );
    }
    }
    }

    char buf[128];
    const char *str = label;

    if( hovered ) {
    ImVec2 pos = (g.IO.MousePos - bb.Min) / (bb.Max - bb.Min);
    pos.y = 1 - pos.y;

    sprintf(buf, "%s (%f,%f)", label, pos.x, pos.y );
    str = buf;
    }

    RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + style.FramePadding.y), bb.Max, str, NULL, NULL, ImGuiAlign_Center);

    return modified;
    }

    };