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.
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.
Touch interfaces tend to need some visual (or artificial haptic) feedback because touch itself is not enough.
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”.Is there a name for this kind of motion? I'd love to use it sometime.
m * x’’ + c * x’ + k * x = f(t)
See https://web.archive.org/web/20230604215211/https://esporttoy...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.
And obviously it would only apply for the underdamped case.
Kudos.-
PS. And, if I may as a recovering Flash 5/Actionscript victim ...
... this (great) article brought back memories.-
Every time I have used animation my approach has been to treat it as frosting to be used sparingly. As such I would be much more attracted to simple/supported modes (that someone else will be interested in keeping functional) than I would be to obsessing over physics realism. But I am also a UI caveman with no taste, so there's that.
Often people just argue over the proper parameters for the same old functions, but that's all they have available. Their arguments use lots of ambiguous human terminology like snappiness, surprise, anticipation, sluggishness, etc. that don't cleanly map to specific parameters.
Adjusting easing can produce dramatically different results in how we feel about something. It triggers our hindbrain. We want to pounce on the mouse, or block the incoming rock, or whatever, I guess? Try going in for a handshake a little faster or slower than usual and see what happens...
[1] https://developer.mozilla.org/en-US/docs/Web/CSS/easing-func...
> An easing function takes in a linear progress value, and returns a new progress value, but converted to nonlinear motion.
They then describe the problem with them in the section "Easing is not so easy", after introducing easing functions that are already more expressive than `cubic-bezier`:
> In traditional animation, the principles are just guidelines; you still end up creating new unique motion each time based on what acting the scene calls for. Easing functions in code don't quite give you the flexibility to do that.
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.
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?
"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.
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).
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.
And even if you are to calculate the integration factors based on dynamic frame rates, they only need to be computed just once per frame for all objects that share the same damping configuration.
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...
I've posted this before for other reasons but https://www.luduxia.com/showdown/ - in this case everything is easing functions, except for, bizarrely, the giant text that announces win/draw and the text inside the counter, which is done with actual physics because that proves better, especially with constantly moving targets.
https://github.com/franciscop/ola
Note: ola means (sea) wave in Spanish
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.
This is one of the things I find fascinating about Bret Victor. In the early 10s, he was giving a lot of really influential talks on creative coding. Someone asked him what his motivation was. He essentially said that he gives his ideas away, because he wants to live in a world where they exist. It's easy enough to build enough of a demo to give a talk, but he wanted someone else to do the legwork to grow it into a full project.
And most builders are most passionate about their own ideas. An idea “gifted” from someone else may actually have negative value.
I agree that just taking an idea and implementing it as-is isn't very inspiring. Or at least, not until you actually do it -- since in the process, you'll invariably discover that the idea is incomplete or needs adaptation for your specific situation, and it turns out you have to come up with your own ideas for it to be a "true" manifestation of the original idea.
https://chrisbuilds.github.io/terminaltexteffects/changeblog...
I would imagine you're going to step through time anyways, to draw frame after frame. Wouldn't it in fact be cheaper to step through using some finite differences scheme, with one FD step for one draw step? It could be as cheap as a handful of additions per time step. Unless I'm overlooking something?
If needed for stability or accuracy, you can also step through several FD steps in one frame, but I'd imagine at a rate of 20fps, this should not be necessary for the types of equations considered.
- Part of my use case is that I build animation software. In there, you've got a timeline, and you can seek anywhere on the timeline. So in that scenario, you're not always moving consistently forward in time.
- In real-time contexts, sometimes you drop frames, even for simple motion, just due to the hardware it's being run on and what else the computer is doing. Simulations can be sensitive to time steps and produce slightly different results depending on them. The size of the issue depends on a lot of factors, but you don't have that issue at all with a closed form solution.
For your second point, you can decouple ODE time stepping and frame time stepping. I think it suffice to step until the lowest time ODE time step that is greater than the current frame time and interpolate between that and the previous ODE time step.
This technique is used in loosely coupled systems, for instance in mechanics where a rigid body needs a much lower time step (higher frequencies) than a soft body to compute the dynamics of, but you still need common time steps to compute interactions. Often times, the time steps are dictated by CFL conditions, and they may not even be integer multiples of each other.
However your first point is where I see the iterative approach really wouldn't work. Especially if the user might change the parameters before you've done anything with them, it wouldn't make sense to precompute values using the iterative scheme. Otherwise, that could be done, and then values interpolated between steps.
If you have few parameters, one solution that comes to mind if you cannot find a closed form solution is to grid the params space, and precompute the curve at each point of the grid. Then, when the user requests any value, simply localize that in the grid and interpolate from the nodes of the element. It becomes problematic if start and end points are parameters, though... In that case I suppose a linear transform of the curve to fit the end points precisely would be in order. You can consider the end points alone, it wouldn't be very complicated.
An improvement would be an adaptive grid; each time a point is inserted in a hypercube, compute also at the projections of that point on the hypercube's facets (and then the projection of that onto the facets' facets, and so on...), and split it. Consider whether to insert based on an indicator that takes into account the solutions at the nodes of the initial hypercube (if they are too distinct, a split is in order). Maybe this is too much complication for something this simple, but anyways there would be solutions for using more difficult ODEs that don't have closed-form solutions. Note if a single family of ODEs is considered, then this can be done offline, or online but cached.
There are more sophisticated methods like PINNs of course, but I don't know if you'd gain anything in performance versus a bespoke scheme. In particular if the inference step is more expensive than localizing in a kd-tree and interpolating from a few points.
In the case of the exponential, if you're willing to start from a known value (say 0, where exp(0) = 1) and need values until the last, then using the very definition of the exponential is even more straightforward... this is the only function s.t. f' = f and f(0) = 1. In other words, step through it! The most natural definition of the exponential is as an ODE to begin with.
Tangentially related, this is one of my favourite articles "Nineteen Dubious Ways to Compute the Exponential of a Matrix":
https://www.cs.jhu.edu/~misha/ReadingSeminar/Papers/Moler03....
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
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.
Er ... "un ... eased"? :)
https://medium.com/hackernoon/the-spring-factory-4c3d988e712...
And a bounce function:
https://medium.com/hackernoon/the-bounce-factory-3498de1e526...
In the limit, constant velocity still has the same discontinuity (infinite acceleration) at those points, and easing functions generally stick with the same constant-ish acceleration at those points. Easing makes it feel like the object has to get up to speed, and it takes about the same amount of time regardless of distance/overall time.
ndyg•1w ago