From 4c39e621fe5bbf1915f87dc936f15601f8895e45 Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Fri, 29 Dec 2023 13:30:37 +0100 Subject: [PATCH] Add easing functions --- engine/CMakeLists.txt | 1 + engine/breeze.h | 1 + engine/breeze/math/easings.h | 227 +++++++++++++++++++++++++++++++++++ engine/tests/CMakeLists.txt | 3 + engine/tests/ease_test.c | 59 +++++++++ 5 files changed, 291 insertions(+) create mode 100644 engine/breeze/math/easings.h create mode 100644 engine/tests/ease_test.c diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index de1a0d7..02f16e9 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -57,6 +57,7 @@ set(BreezeHeaders breeze/core/logger.h breeze/math/vec2i.h + breeze/math/easings.h breeze/memory/memory.h breeze/memory/stack_alloc.h diff --git a/engine/breeze.h b/engine/breeze.h index ed49755..874d3dc 100644 --- a/engine/breeze.h +++ b/engine/breeze.h @@ -4,6 +4,7 @@ #include "breeze/core/logger.h" #include "breeze/math/vec2i.h" +#include "breeze/math/easings.h" #include "breeze/memory/memory.h" #include "breeze/memory/stack_alloc.h" diff --git a/engine/breeze/math/easings.h b/engine/breeze/math/easings.h new file mode 100644 index 0000000..59f4b43 --- /dev/null +++ b/engine/breeze/math/easings.h @@ -0,0 +1,227 @@ +#ifndef BREEZE_EASINGS_H +#define BREEZE_EASINGS_H + +#include + +// From: https://easings.net/ +typedef enum BzEaseType { + BZ_EASE_NONE, + BZ_EASE_IN_SINE, + BZ_EASE_OUT_SINE, + BZ_EASE_INOUT_SINE, + BZ_EASE_IN_QUAD, + BZ_EASE_OUT_QUAD, + BZ_EASE_INOUT_QUAD, + BZ_EASE_IN_CUBIC, + BZ_EASE_OUT_CUBIC, + BZ_EASE_INOUT_CUBIC, + BZ_EASE_IN_QUART, + BZ_EASE_OUT_QUART, + BZ_EASE_INOUT_QUART, + BZ_EASE_IN_QUINT, + BZ_EASE_OUT_QUINT, + BZ_EASE_INOUT_QUINT, + BZ_EASE_IN_EXPO, + BZ_EASE_OUT_EXPO, + BZ_EASE_INOUT_EXPO, + BZ_EASE_IN_CIRC, + BZ_EASE_OUT_CIRC, + BZ_EASE_INOUT_CIRC, + BZ_EASE_IN_BACK, + BZ_EASE_OUT_BACK, + BZ_EASE_INOUT_BACK, + BZ_EASE_IN_ELASTIC, + BZ_EASE_OUT_ELASTIC, + BZ_EASE_INOUT_ELASTIC, + BZ_EASE_IN_BOUNCE, + BZ_EASE_OUT_BOUNCE, + BZ_EASE_INOUT_BOUNCE, +} BzEaseType; + +static f32 bzEaseNone(f32 x) { + return x; +} + +static f32 bzEaseInSine(f32 x) { + return 1 - cosf((x * M_PI) / 2.0f); +} +static f32 bzEaseOutSine(f32 x) { + return sinf((x * M_PI) / 2.0f); +} +f32 bzEaseInOutSine(f32 x) { + return -(-cosf(M_PI * x) - 1) / 2.0f; +} + +static f32 bzEaseInQuad(f32 x) { + return x * x; +} +static f32 bzEaseOutQuad(f32 x) { + return 1 - (1 - x) * (1 - x); +} +static f32 bzEaseInOutQuad(f32 x) { + return x < 0.5f ? 2.0f * x * x : 1 - powf(-2.0f * x + 2, 2) / 2.0f; +} + +static f32 bzEaseInCubic(f32 x) { + return x * x * x; +} +static f32 bzEaseOutCubic(f32 x) { + return 1 - powf(1 - x, 3); +} +static f32 bzEaseInOutCubic(f32 x) { + return x < 0.5 ? 4 * x * x * x : 1 - powf(-2.0f * x + 2, 3) / 2.0f; +} + +static f32 bzEaseInQuart(f32 x) { + return x * x * x * x; +} +static f32 bzEaseOutQuart(f32 x) { + return 1 - powf(1 - x, 4); +} +static f32 bzEaseInOutQuart(f32 x) { + return x < 0.5 ? 8 * x * x * x * x : 1 - powf(-2 * x + 2, 4) / 2.0f; +} + +static f32 bzEaseInQuint(f32 x) { + return x * x * x * x * x; +} +static f32 bzEaseOutQuint(f32 x) { + return 1 - powf(1 - x, 5); +} +static f32 bzEaseInOutQuint(f32 x) { + return x < 0.5 ? 16 * x * x * x * x * x : 1 - powf(-2 * x + 2, 5) / 2.0f; +} + +static f32 bzEaseInExpo(f32 x) { + return x == 0 ? 0 : powf(2, 10 * x - 10); +} +static f32 bzEaseOutExpo(f32 x) { + return x == 1 ? 1 : 1 - powf(2, -10 * x); +} +static f32 bzEaseInOutExpo(f32 x) { + if (x == 0) return 0; + if (x == 1) return 1; + return x < 0.5 ? powf(2, 20 * x - 10) / 2.0 : (2 - powf(2, -20 * x + 10)) / 2.0; +} + +static f32 bzEaseInCirc(f32 x) { + return 1 - sqrtf(1 - powf(x, 2)); +} +static f32 bzEaseOutCirc(f32 x) { + return sqrtf(1 - powf(x - 1, 2)); +} +static f32 bzEaseInOutCirc(f32 x) { + return x < 0.5 + ? (1 - sqrtf(1 - powf(2 * x, 2))) / 2 + : (sqrtf(1 - powf(-2 * x + 2, 2)) + 1) / 2; +} + +static f32 bzEaseInBack(f32 x) { + const f32 c1 = 1.70158f; + const f32 c3 = c1 + 1; + + return (c3 * x * x * x) - (c1 * x * x); +} +static f32 bzEaseOutBack(f32 x) { + const f32 c1 = 1.70158f; + const f32 c3 = c1 + 1; + + return 1 + (c3 * powf(x - 1, 3)) + (c1 * powf(x - 1, 2)); +} +static f32 bzEaseInOutBack(f32 x) { + const f32 c1 = 1.70158f; + const f32 c2 = c1 * 1.525; + + return x < 0.5 + ? (powf(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2.0f + : (powf(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2.0f; +} + +static f32 bzEaseInElastic(f32 x) { + const f32 c4 = (2 * M_PI) / 3.0f; + + if (x == 0) return 0; + if (x == 1) return 1; + + return -powf(2, 10 * x - 10) * sinf((x * 10 - 10.75) * c4); +} +static f32 bzEaseOutElastic(f32 x) { + const f32 c4 = (2 * M_PI) / 3.0f; + + if (x == 0) return 0; + if (x == 1) return 1; + + return powf(2, -10 * x) * sinf((x * 10 - 0.75) * c4) + 1; +} +static f32 bzEaseInOutElastic(f32 x) { + const f32 c5 = (2 * M_PI) / 4.5f; + + if (x == 0) return 0; + if (x == 1) return 1; + + return x < 0.5 ? + -(powf(2, 20 * x - 10) * sinf((20 * x - 11.125) * c5)) / 2.0f : + (powf(2, -20 * x + 10) * sinf((20 * x - 11.125) * c5)) / 2.0f + 1; +} + +static f32 bzEaseOutBounce(f32 x); +static f32 bzEaseInBounce(f32 x) { + return 1 - bzEaseOutBounce(1 - x); +} +static f32 bzEaseOutBounce(f32 x) { + const f32 n1 = 7.5625; + const f32 d1 = 2.75; + + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + return (x -= 1.5f / d1, n1 * x * x + 0.75f); + } else if (x < 2.5 / d1) { + return (x -= 2.25 / d1, n1 * x * x + 0.9375); + } else { + return (x -= 2.625 / d1, n1 * x * x + 0.984375); + } +} +static f32 bzEaseInOutBounce(f32 x) { + return x < 0.5 + ? (1 - bzEaseOutBounce(1 - 2 * x)) / 2.0f + : (1 + bzEaseOutBounce(2 * x - 1)) / 2.0f; +} + +static f32 bzEase(BzEaseType easeType, f32 x) { + switch (easeType) { + case BZ_EASE_NONE: return bzEaseNone(x); + case BZ_EASE_IN_SINE: return bzEaseInSine(x); + case BZ_EASE_OUT_SINE: return bzEaseOutSine(x); + case BZ_EASE_INOUT_SINE: return bzEaseInOutSine(x); + case BZ_EASE_IN_QUAD: return bzEaseInQuad(x); + case BZ_EASE_OUT_QUAD: return bzEaseOutQuad(x); + case BZ_EASE_INOUT_QUAD: return bzEaseInOutQuad(x); + case BZ_EASE_IN_CUBIC: return bzEaseInCubic(x); + case BZ_EASE_OUT_CUBIC: return bzEaseOutCubic(x); + case BZ_EASE_INOUT_CUBIC: return bzEaseInOutCubic(x); + case BZ_EASE_IN_QUART: return bzEaseInQuart(x); + case BZ_EASE_OUT_QUART: return bzEaseOutQuart(x); + case BZ_EASE_INOUT_QUART: return bzEaseInOutQuart(x); + case BZ_EASE_IN_QUINT: return bzEaseInQuint(x); + case BZ_EASE_OUT_QUINT: return bzEaseOutQuint(x); + case BZ_EASE_INOUT_QUINT: return bzEaseInOutQuint(x); + case BZ_EASE_IN_EXPO: return bzEaseInExpo(x); + case BZ_EASE_OUT_EXPO: return bzEaseOutExpo(x); + case BZ_EASE_INOUT_EXPO: return bzEaseInOutExpo(x); + case BZ_EASE_IN_CIRC: return bzEaseInCirc(x); + case BZ_EASE_OUT_CIRC: return bzEaseOutCirc(x); + case BZ_EASE_INOUT_CIRC: return bzEaseInOutCirc(x); + case BZ_EASE_IN_BACK: return bzEaseInBack(x); + case BZ_EASE_OUT_BACK: return bzEaseOutBack(x); + case BZ_EASE_INOUT_BACK: return bzEaseInOutBack(x); + case BZ_EASE_IN_ELASTIC: return bzEaseInElastic(x); + case BZ_EASE_OUT_ELASTIC: return bzEaseOutElastic(x); + case BZ_EASE_INOUT_ELASTIC: return bzEaseInOutElastic(x); + case BZ_EASE_IN_BOUNCE: return bzEaseInBounce(x); + case BZ_EASE_OUT_BOUNCE: return bzEaseOutBounce(x); + case BZ_EASE_INOUT_BOUNCE: return bzEaseInOutBounce(x); + } +} + +#endif //BREEZE_EASINGS_H diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 52a39b1..9ca04e3 100644 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -22,3 +22,6 @@ target_link_libraries(pan_test LINK_PRIVATE Breeze) add_executable(ui_test ui_test.c) target_link_libraries(ui_test LINK_PRIVATE Breeze) + +add_executable(ease_test ease_test.c) +target_link_libraries(ease_test LINK_PRIVATE Breeze) \ No newline at end of file diff --git a/engine/tests/ease_test.c b/engine/tests/ease_test.c new file mode 100644 index 0000000..b902c65 --- /dev/null +++ b/engine/tests/ease_test.c @@ -0,0 +1,59 @@ +#define BZ_ENTRYPOINT +#include + +#include +#include + +bool init(int *game) { + rlImGuiSetup(true); + + return true; +} + +void render(float dt, int *game) { + + static f32 x = 0; + + x += dt * 2; + + //f32 eased = bzEase(BZ_EASE_INOUT_SINE, x); + f32 eased = bzEase(BZ_EASE_OUT_ELASTIC, x); + + Rectangle rect = { + 500, 500, 200, 400 + }; + Vector2 origin = {rect.width * 0.5f, rect.height}; + + BeginDrawing(); + ClearBackground(WHITE); + + f32 rotation = 45 * (1 - eased); + DrawRectanglePro(rect, origin, rotation, RED); + + + if (IsKeyReleased(KEY_SPACE)) { + x = 0.0f; + } + + + rlImGuiBegin(); + + igBegin("Easing", NULL, 0); + igText("x: %.2f", x); + igText("eased: %.2f", eased); + igText("rotation: %.2f", rotation); + if (igSmallButton("Reset")) { + x = 0.0f; + } + igEnd(); + + rlImGuiEnd(); + EndDrawing(); +} + +bool bzMain(BzAppDesc *appDesc, int argc, const char **argv) { + appDesc->init = (BzAppInitFunc) init; + appDesc->render = (BzAppRenderFunc) render; + + return true; +} \ No newline at end of file