Skip to content(if available)orjump to list(if available)

I'm Unsatisfied with Easing Functions

EsportToys

Closed-form (non-iterative) PID solution:

https://www.desmos.com/calculator/mu80ttc9aa

   function sprung_response(t,pos,vel,k,c,m)
      local decay = c/2/m
      local omega = math.sqrt(k/m)
      local resid = decay*decay-omega*omega
      local scale = math.sqrt(math.abs(resid))
      local T1,T0 = t , 1
      if resid<0 then
         T1,T0 = math.sin( scale*t)/scale , math.cos( scale*t)
      elseif resid>0 then
         T1,T0 = math.sinh(scale*t)/scale , math.cosh(scale*t)
      end
      local dissipation = math.exp(-decay*t)
      local evolved_pos = dissipation*( pos*(T0+T1*decay) + vel*(   T1      ) )
      local evolved_vel = dissipation*( pos*(-T1*omega^2) + vel*(T0-T1*decay) )
     return evolved_pos , evolved_vel
   end
For anticipation, just add an extra initial velocity in the opposite direction and let the closed-form solution handle the time evolution. The main trick here is to keep both position and velocity as state. There is no need to “step through the simulation”.

creata

Material Design 3's "motion physics system" uses damped harmonic oscillators, too. The parameters (undamped angular frequency omega0 and damping ratio zeta) they use are on this page:

https://m3.material.io/styles/motion/overview/how-it-works

pahgawk

This is good! Although I'd also say that initial velocity doesn't quite cover what I was talking about in the post -- even anticipation arguably can start from 0 velocity, accelerate backwards, decelerate, then accelerate in the opposite direction. Imo, any sudden change in velocity should by default be avoided (there are always valid uses where breaking that expectation is good, but I'd want it smooth by default.)

That could possibly be done by incrementally changing force to move it back first, then forward, or to model this as a PD controller following an input with some baked in reversal before moving forward. That can still be closed-form (state response to a known input will be; Laplace transforms can help there), but still would need a bit of effort to model and tune to look right.

LegionMammal978

You wouldn't really need an incremental force: a step-function force (first backward for some time steps, then instantly forward) will still produce a continuous velocity curve.

pahgawk

true! I suppose you'd risk getting some oscillations in the anticipation depending on the scale of the force, but that could be desirable, or might not happen if the scale is small enough, and certainly makes the math a little easier

joenot443

Wow, this is excellent!

Is there a name for this kind of motion? I'd love to use it sometime.

EsportToys

It’s the general solution to a damped harmonic oscillator/mass-spring-damper system:

    m * x’’ + c * x’ + k * x = f(t)
See https://web.archive.org/web/20230604215211/https://esporttoy...

ww520

Wow. This is so good. I would never imagine using PID control for animation motion. Saving this.

null

[deleted]

thomascountz

Wow, this is great!

esafak

How did you even find this site; did you make it?

thomascountz

The section on feedback control reminded me of procedural animation where you don't calculate the velocities and positions directly; instead the animation is a consequence of constraints and a target.

I took your PD controller concept and added anticipation using two targets: the original mouse target, and an "anticipation" target set proportionally based on the distance from the point to the main target[1].

This also made me think of Jelly Car and the amazing simulations they did using rigid frames for soft bodies, called Shape Matching[2]. Instead of simulating the soft body physics directly, they used a frame, springs, and "gas" to constrain points, which moved towards targets fixed to the frame.

[1]: https://editor.p5js.org/Thomascountz/sketches/YXWm_VV6s

[2]: https://www.gamedeveloper.com/programming/deep-dive-the-soft...

pahgawk

Author here! Nice, thanks for taking the time to create that demo! I also like the look of commenting out `vel.set(0, 0)` when the anticipation target is reached, as it has less "snap" between velocities. Although if you keep velocity the same, now it'll go a little farther than the anticipation target. So maybe if you wanted to have a specific distance of anticipation (I think something an animator would reasonably want to do), one could do some math to figure out where to place the target position so that by the time it decelerates to 0, the distance is the real anticipation distance.

unconed

Bouncy animations that overshoot just seem like a bad idea in general. The purpose of a UI animation is to guide the eye, but the bounce explicitly introduces a reversal of motion at the end before stopping.

Easing functions are just very cargo culty. We've had the same basic set that dates from the Flash era. Now there's an Apple variant that's just a parametric version of the same idea, but it lacks guaranteed continuity and it's even harder to control?

Personally I've had far better results using the repeated-lerping-towards-a-target trick, aka a true exponential ease. When stacked, you get a classic LTI (linear time invariant) system, and the math around how those behave is well established.

Classic hand-drawn animation does often use stretching and squeezing to emphasize and to create a sense of anticipation, but that's very different and always dependent on the specific motion. You can't automate that by making everything act like jello.

pahgawk

Hi, author here! When writing this, I was thinking more in the space of procedural character animation and motion graphics than UI animations. That's part of why I want a system with nice parameters, so that I do have the ability to fine tune and tweak the motion to fit the context. My background is in classical animation so it's something I might just keyframe by hand in a non-code context, or in Flash when it's easier to jump back and forth between code and non code. Although I think having it parameterized still can lead to interesting opportunities for variation in procedural animation!

ipv6ipv4

Overshoot is important for scroll views. Without the bounce, there is no feedback if you have scrolled to the edge or not. Early Android lacked the bounce, and it would invariably lead to users scrolling again to be sure they had indeed scrolled to the edge of the view.

Jensson

Desktop doesn't have bounce and I prefer it that way. For mobile maybe bounce is more important since its less precise than a mouse, but point is that bounce is not obviously superior since on desktop the bounce just feels buggy rather than what you want.

ipv6ipv4

Desktop on Mac does if you swipe to scroll. It doesn't if you drag the scroll bar to scroll. On Windows, a scroll wheel doesn't bounce either. It doesn't need to when the scroll wheel itself provides physical feedback that you actually did something.

Touch interfaces tend to need some visual (or artificial haptic) feedback because touch itself is not enough.

panic

You can add the bounce as a second layer of dynamics on top of the exponential deceleration (this is how iOS scroll views do it).

Jyaif

> Early Android lacked the bounce

Early android had some visual feedback (a gradient that faded out) instead of the bounce, possibly because Apple owns a patent ( https://patents.google.com/patent/US7469381B2/en ).

Current Android still does not use the bounce, and instead stretches the content, which works well enough on the high resolution screens that we have now.

null

[deleted]

ipv6ipv4

Initial Android had scroll bars with no overshoot specific indication.

socalgal2

Why does literally every single article on easing functions mention Penner? First off he didn’t invent them. Games had been using those functions since at least the 80s.

Second, why only him and these functions? I don’t write: In JavaScript by Brendan Eich using Node.js by Ryan Dahl I installed a package using npm by Isaac Z. Schlueter called React by Jordan Walke and for the backend I used TJ Holowaychuk’s express.js. Instead just write: In JavaScript using node.js I installed react and express.js

But literally, I’ve never read an article about easing functions that just says “easing functions”. they all feel obligated to mention Penner” Why? and no, it’s not because open source or multiple contributors. I used those examples because I can’t name the 1000s of people for functions which is precisely my point

xwolfi

Sir, this is a Wendy

neuroscihacker

I wrote about analytic easing functions a number of years ago and show how to precisely design a spring function:

https://medium.com/hackernoon/the-spring-factory-4c3d988e712...

And a bounce function:

https://medium.com/hackernoon/the-bounce-factory-3498de1e526...

spankalee

There are a lot of post and talks out there about why spring is easier to use and gives nicer results than most easing functions.

In CSS, there's a long standing feature request to add a spring() timing function: https://github.com/w3c/csswg-drafts/issues/280

This would not only be great for developer ergonomics, but would remove JS from the animation path for these cases.

franciscop

I also didn't like when a new easing happened _while_ another easing was happening, which often felt very jerky. Had to do a bunch of calculus (derivatives) by hand and wrote a small library for it in JS:

https://github.com/franciscop/ola

Note: ola means (sea) wave in Spanish

pahgawk

Thanks for sharing this library! Author of the post here, I'll definitely check out your implementation.

franciscop

Thanks! From your article, you might not want/like my library since it's based on a single easing function. I used a cubic function to find out the interpolation values, and the derivative to make sure it's always smooth. The equation looks like this, it's on the source code:

https://www.wolframalpha.com/input/?i=x+%3D+2+*+t+%5E+3+-+3+...

If you wanted some more details please feel free to ping me (email through my website's resume, or Twitter) and I'll dig the handwritten equations.

x187463

I have found myself leaning heavily on easing functions to smooth motion within my terminal visual effects engine. I do not have a very strong math background so I am limited in how much I can modify the commonly available functions. I have found it useful to create custom easing functions by mapping the easing function progress across a bezier curve. There's an example in the changeblog write-up from the last release:

https://chrisbuilds.github.io/terminaltexteffects/changeblog...

SimplyUnknown

I have the feeling that B-splines would be a good solution for this problem. Given that they have a continuous zeroth (i.e., the function is continuous), first, and second derivative, the motion will always be smooth and there will be no kinks. However, maybe it's moving the problem because now you must tune the coefficients of the B-spline instead of damping parameters (even though a direct mapping between these must exist but this mapping may not be trivial).

bhk

Oscullation/"anticipation" in transitions is so gimmicky and cartoonish. Living things don't move like that, and mechanical things don't unless they are broken, chintzy, or poorly designed (underdamped).

magicalhippo

In my view we most certainly do, though of course not all the time.

Oscillations typically happen when moving quickly and then stopping at a certain position.

A prime example is the ISSF 25m rapid fire pistol event, where you start with the pistol pointing down at 45 degrees, and within 8, 6 and 4 seconds have to raise the pistol and take 5 aimed shots.

Beginners will often find they oscillate vertically above and below the center of the target as they raise the pistol, and it usually takes a fair bit of practice not to oscillate. This is more pronounced at the rapid pistol events due to the tight time constraint forcing them to move quickly, especially at the final 4 second round.

Another example is gymnastic rings, especially when they go from a swinging motion to stopping vertically or horizontally.

Anticipation-like movement often occurs when we want to get a bit of extra momentum. For example, casually tossing a base ball or tennis ball often results in a bit of backwards movement first, before the main swing. Typically it's one smooth movement.

thomascountz

Think of a slingshot? Or a person jumping?[1].

We see this, not only in mammals and muscles, but in organic systems which evolved to trade speed for power by storing mechanical, electrical, or chemical potential energy over time.

Overshoot and damping is the same in reverse: dissipating kinetic energy when you, for example, bend your knees when you land from a jump (hopefully).

[1]: https://www.youtube.com/watch?v=qN3apht8zRs&t

nilamo

Sure they do? When people do a standing jump, they first crouch lower to the ground. An arrow is pulled back on the bow before fired. A ball pushed uphill will roll uphill before stopping and rolling downhill.

fidotron

Those are distinct actions though.

"Anticipation" in character animations is a source of persistent friction between game programmers and animators because if incorporated as part of user/player action it simply kills responsiveness. OTOH being able to see NPCs prepare to do something (like pulling the bow) is incredibly useful.

pc86

Eh, sort of. You do that if you're working out, because you're trying to maximize performance.

Most living beings aren't trying maximize performance, they're trying to maximize survival. That crouch gives away that you're about to jump, and it allows prey to avoid you more easily or predators to adjust their attack to counter for it. A rabbit trying to get away from a predator simply bounds away in its given direction without giving away what its doing.

nilamo

My experience seeing animals is apparently different from yours. The anticipation/tell is very clear for a wide range of animals... Birds squat down before taking off, cats hunker down to the ground before pouncing, etc.

Even the human jumping example again... Suggesting people only bend their knees for performance would mean the "normal" way of jumping involves keeping your legs straight at all times and using your ankles/toes for all your thrust?

panic

The correct way to do "anticipation" is to apply it as something is being pressed, then finish the animation on release. Putting both together in a single easing function is missing the point, IMO.

jcalx

Apple's easing function (like others) is parameterized by physical characteristics (e.g. spring force, damping) that are easier to model from first principles, but there are other parameters (overshoot distance, anticipation size, animation time) that are more useful for animation. A closed-form parametrization with the latter might be tricky to derive, but some kind of iterative solver (plug in desired animation parameters, get values for the physical parameters) shouldn't be too difficult?

null

[deleted]

null

[deleted]

aappleby

I've used the same easing function in my personal projects for decades now - move the variable 90% of the way to the goal every 80 milliseconds.

Koffiepoeder

Reminds me of Zeno's paradox. Do you have a terminating condition? Or just a fixed number of iterations?