Easing Library - v1 - posted into pattern library!

Continuing the discussion from Smooth Speed Slider?:

This isn’t quite right, the last few don’t look correct, so I’m sure I need to play with them, but most of these seem right, based on the demo results.
Likely, I’ll add some additional demos to this as well.

what these should look like:

My demo doesn’t yet match those curves (TBD), but I do have consistent curves, so…

Posting so that this gets some fresh eyeballs, and if someone spots the goof(s), let me know, and save me some debugging time. I’m thinking the last ones need to handle lower/higher than 0…1 values (in my demo that is, I suspect it’s correct for the library code to do that).

/**
 * Easing library by ScruffyNerf v0.9 May 2021
 * based on info from https://easings.net (see the images there for an example curve for each function)
 * To use as a library, add the easing functions you wish to use to your own patterns
 * All take a 0..1 value, and return a 0..1 value
*/

// Library

// Constants used
var c1 = 1.70158;
var c2 = c1 * 1.525;
var c3 = c1 + 1;
var c4 = (PI2) / 3;
var c5 = (PI2) / 4.5;

function easeInSine(x){
  return 1 - cos((x * PI) / 2);
}

function easeOutSine(x){
  return sin((x * PI) / 2);
}

function easeInOutSine(x){
  return -(cos(PI * x) - 1) / 2;
}

function easeInQuad(x) {
  return x * x;
}

function easeOutQuad(x) {
  return 1 - (1 - x) * (1 - x);
}

function easeInOutQuad(x) {
  return x < 0.5 ? 2 * x * x : 1 - pow(-2 * x + 2, 2) / 2;
}

function easeInCubic(x) {
  return x * x * x;
}

function easeOutCubic(x) {
  return 1 - pow(1 - x, 3);
}

function easeInOutCubic(x) {
  return x < 0.5 ? 4 * x * x * x : 1 - pow(-2 * x + 2, 3) / 2;
}

function easeInQuart(x) {
  return x * x * x * x;
}

function easeOutQuart(x) {
  return 1 - pow(1 - x, 4);
}

function easeInOutQuart(x) {
  return x < 0.5 ? 8 * x * x * x * x : 1 - pow(-2 * x + 2, 4) / 2;
}

function easeInQuint(x) {
  return x * x * x * x * x;
}

function easeOutQuint(x) {
  return 1 - pow(1 - x, 5);
}

function easeInOutQuint(x) {
  return x < 0.5 ? 16 * x * x * x * x * x : 1 - pow(-2 * x + 2, 5) / 2;
}

function easeInExpo(x) {
  return x == 0 ? 0 : pow(2, 10 * x - 10);
}

function easeOutExpo(x) {
  return x == 1 ? 1 : 1 - pow(2, -10 * x);
}

function easeInOutExpo(x) {
  return x == 0 ? 0  : x == 1  ? 1  : x < 0.5 ? pow(2, 20 * x - 10) / 2  : (2 - pow(2, -20 * x + 10)) / 2;
}

function easeInCirc(x) {
  return 1 - sqrt(1 - pow(x, 2));
}

function easeOutCirc(x) {
  return sqrt(1 - pow(x - 1, 2));
}

function easeInOutCirc(x) {
  return x < 0.5 ? (1 - sqrt(1 - pow(2 * x, 2))) / 2  : (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2;
}

function easeInBack(x) {
  return c3 * x * x * x - c1 * x * x;
}

function easeOutBack(x) {
  return 1 + c3 * pow(x - 1, 3) + c1 * pow(x - 1, 2);
}

function easeInOutBack(x) {
  return x < 0.5 ? (pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2  
  : (pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
}

function easeInElastic(x) {
  return x == 0 ? 0 : x == 1 ? 1  : -pow(2, 10 * x - 10) * sin((x * 10 - 10.75) * c4);
}

function easeOutElastic(x) {
  return x == 0 ? 0 : x == 1 ? 1 : pow(2, -10 * x) * sin((x * 10 - 0.75) * c4) + 1;
}

function easeInOutElastic(x) {
  return x == 0 ? 0 : x == 1 ? 1 : x < 0.5
  ? -(pow(2, 20 * x - 10) * sin((20 * x - 11.125) * c5)) / 2
  : (pow(2, -20 * x + 10) * sin((20 * x - 11.125) * c5)) / 2 + 1;
}

function easeInBounce(x) {
  return 1 - easeOutBounce(1 - x);
}

function easeOutBounce(x) {
  n1 = 7.5625;
  d1 = 2.75;
  if (x < 1 / d1) {
    return n1 * x * x;
  } else if (x < 2 / d1) {
    return n1 * (x -= 1.5 / d1) * x + 0.75;
  } else if (x < 2.5 / d1) {
    return n1 * (x -= 2.25 / d1) * x + 0.9375;
  } else {
    return n1 * (x -= 2.625 / d1) * x + 0.984375;
  }
}

function easeInOutBounce(x) {
  return x < 0.5 ? (1 - easeOutBounce(1 - 2 * x)) / 2 : (1 + easeOutBounce(2 * x - 1)) / 2;
}

// end library

// begin demo
demofunctions = 30
var demo = array(demofunctions)
demo[0] = (x) => easeInSine(x)
demo[1] = (x) => easeOutSine(x)
demo[2] = (x) => easeInOutSine(x)
demo[3] = (x) => easeInQuad(x)
demo[4] = (x) => easeOutQuad(x)
demo[5] = (x) => easeInOutQuad(x)
demo[6] = (x) => easeInCubic(x)
demo[7] = (x) => easeOutCubic(x)
demo[8] = (x) => easeInOutCubic(x)
demo[9] = (x) => easeInQuart(x)
demo[10] = (x) => easeOutQuart(x)
demo[11] = (x) => easeInOutQuart(x)
demo[12] = (x) => easeInQuint(x)
demo[13] = (x) => easeOutQuint(x)
demo[14] = (x) => easeInOutQuint(x)
demo[15] = (x) => easeInExpo(x)
demo[16] = (x) => easeOutExpo(x)
demo[17] = (x) => easeInOutExpo(x)
demo[18] = (x) => easeInCirc(x)
demo[19] = (x) => easeOutCirc(x)
demo[20] = (x) => easeInOutCirc(x)
demo[21] = (x) => easeInBack(x)
demo[22] = (x) => easeOutBack(x)
demo[23] = (x) => easeInOutBack(x)
demo[24] = (x) => easeInElastic(x)
demo[25] = (x) => easeOutElastic(x)
demo[26] = (x) => easeInOutElastic(x)
demo[27] = (x) => easeInBounce(x)
demo[28] = (x) => easeOutBounce(x)
demo[29] = (x) => easeInOutBounce(x)

export var timeelapsed
export var currentdemo = 0

export function beforeRender(delta) {
  timeelapsed += delta
  if (timeelapsed >= 3000){
    timeelapsed = 0
    currentdemo += 1
    if (currentdemo == 30){
      currentdemo = 0
    }
  }
}

export function render(index) {
  h = demo[currentdemo](index/pixelCount)
  s = 1
  v = 1
  hsv(h, s, v)
}

export function render2D(index,x,y){
  h = demo[currentdemo](x)
  s = 1
  v = 1
  if (demo[currentdemo](y) <= x){
    hsv(h, s, v)
  } else {
    hsv(0, 0, 0)
  }
}
5 Likes

so, yes, certain values go above and below 0…1 on certain curves. The worst is Elastic, next worst is Back… but strangely Sine also just touches above 1 as well (math weirdness maybe?)

Next version will have a reference line (diagonal across the x/y).
And the correct orientation of your panel will need to be 0,0 on the lower left corner, mostly to avoid lots of corrections.
But with those in place, with the exception of Elastic, the rest look good (Back has some corrections I’m making, at least for the demo)

I’ll add some movement easing to the demo, as well as the curves (which are sometimes nice, sometimes meh, on a 16x16 matrix).

I need to figure out the best way to use the ones that don’t remain within 0…1 too,

But not bad for a few hours work overall.

v1 coming soon(ish)

Oooh… another good set of demos/code:

https://vkbansal.github.io/easing-funcs/

https://semibran.github.io/easing/

https://mikescher.github.io/EasingCookbook/

I’ve added v1 into the pattern library.

now has a reference (linear) line in gray, which you can comment out…
and more interestingly, a KITT style movement bar, which illustrates the easing, doing it back and forth once a second, with each pattern being displayed as a graph for 5 seconds (configurable). This runs forward and then backwards, not forwards and forwards, so it’s literally running backwards as it goes right to left. For symetrical easings [usually InOut], this is identical, but most easings have a (time) directional orientation.

I’m really enjoying doing libraries now, hopefully others will find them helpful.

At some point, I need to take arrayless matrix KITT, add some random easing changes, and see what happens.

1 Like

I should have made a chart like that for my wall years ago. It just needs the math expression below it for it to be useful in every graphics context, from dozens of pixels of LED to megapixels of video!

1 Like

Also relevant, and something I will be playing with in the complex plane real soon… a nifty idea from inigo quilez on Twitter regarding how to use our t:

Linear interpolation:

c(t) = a·(1-t) + b·t

Exponential interpolation:

c(t) = a¹⁻ᵗ · bᵗ

It behaves “linearly” in exponential domains (rotations with complexes/quaternions, or scaling objects).

If for scalars and you need performance:

c(t) = 2^[ (1-t)·log₂a + t·log₂b ]

1 Like

Yeah, I need to work on the complex library again and make a really good domain visualizer based on some of the ones I’ve played with now.

The nice thing about this easing approach is it’s pretty clean to understand, use just what you need, and extend/add to. There will absolutely be more examples soon. And I’ll likely use this in some other projects.

Ahhhh… And now that I look at Inigo’s Twitter feed, I see Things like this graphtoy…

And all I can think now is Multifunction Tixy for the PB

decided to compare wave(), triangle(), and square() with the easings, and realized I need to make versions of the easings be complete loops (so mod %1, to handle values beyond 1, or maybe mod %2, and then subtract from 2 if greater than 1 [which reverses it], which is the trick I used to make the demo KITT in the example). So there may be a v1.1 soonish, if only to make them complete replacements for wave(), etc.

And yes, this is all about the KITT.

Additional: Well, it turns out, if you combine wave() with easings, you get waves that look like heartbeats, a quick up, a fast down, and then a slow return to middle… I think I have to do the Heartbeat task with these now.

I feel like there is so much here, down the rabbit hole…

Super cool. I had looked into bezier curves as well, might add that as an API.
The wave() function starts mid way, heading up (uses sin). If you want to start at zero (the peak of the negative curve of a sine wave), you can offset it by -0.25.

2 Likes

yeah, I’m going to likely add code to handle both easing uses and full cycles (going from 0 to 1 to 0), since the easings are generally just 1 way: 0 to 1, and then if I’m doing that, then the question is, behave like wave() or behave like triangle(), which also does 0 to 1 to 0…

wave() is actually the outlier starting midway.

I did notice weirdness with

function easeInSine(x){
  return 1 - cos((x * PI) / 2);
}

giving me a maximum of just over 1, instead of exactly 1… is that due to PI or cos() in PB?

It has to do with the speedy implementation of sin using a Taylor Series in fixed point math. It’s only an approximation.

1 Like

Sigh, deeper down the rabbit hole

More things (signal functions) to add

Between these and the various stuff in graphtoy, and the functions I didn’t yet port in complex numbers, (and making some of these functions work with complex numbers). I have a plateful and I just want to add more from the buffet.

When you get to the bottom of the rabbit hole, Inigo Quilez will be there digging it deeper for you.

1 Like

Here’s a poster without the clever names, and maybe with a few new ones:

In the end, it would be neat if the web frontend had a curve editing widget which fed a list of points into a variable, as seen in http://www.complang.tuwien.ac.at/schani/mathmap/screenshots.html

1 Like

a ‘generic slider’ is on my todo list. Likely with 3-4 sliders, so you can find the sweet spot, and then convert that into to a single ‘fixed’ function for your particular need.

Aaand it gets deeper - how do we ease things in/out of motion?

1 Like

Excellent summary in js

I could make new threads and likely will once I port these… But for posterity and future me:

https://xem.github.io/articles/projection.html

3D projection on a 2D canvas

Heck, all of the articles

Between the Tixy creator aemkai, this genius xem, and the rest of the folks at Jsgolf
@wizard , you ought to just reach out to all of them, offer them a discounted PB3, and let them run wild… I suspect these folks would make dozens of new patterns, and have a ton of ideas as to what is most sorely missing from PB-js, to do more.

A reason to add js canvas type functionality

1 Like

I downloaded this months ago and forgot about it because I didn’t see anything interesting happening on a 1D strand. Today I loaded it onto a PB with a 2D matrix and saw that it draws static velocity curves, but what I wanted to get a feel for was how the various easing functions move, so I added a couple of lines to the 1D and 2D render functions.

It’s in the pattern repository as Easing Library v1.01, but all credit stays with @scruffynerf for doing the heavy lifting.

1 Like

I’ll take a look. I did a version with moving 1D dots, which showed the effect, but haven’t touched it in a while. Too many other fun ideas to play with. But thanks for contributing!