Smooth animation/interpolation

When animating a property of an object, I’d like the property to change slowly at first, then speed up and slow down at the end again.

For linear change, there is the lerp() method in Processing PApplet for linear interpolation between two values. I wonder if there is a method that could help me implement non-linear interpolation. I don’t have great theoretical knowledge of motion animation but I guess I’m looking for some interpolation technique. I’m not quite sure which one, though, and whether Processing has a support for that.

I know I could use the “easing” method (moving the object by a fraction of the outstanding travel distance every frame) which makes the object arrive slowly but it still does a jump start to a high speed.

As mentioned earlier, I’d like the object to start slow, then speed up and finish slow, which I realized can be represented by a negative and shifted cosine curve:

-cos(x) + 1 // for x in 0..TWO_PI

Screenshot 2021-08-08 at 12.46.55 PM

I implemented a function where I can use the above formula to calculate a point between start and end according to how many frames of animation has passed.

The downside of my approach is that I need to know the animation length (in frames) in advance, which might be unavoidable (is it?). Another thing that feels wrong is that for each point I need to calculate the amount of movement increments of all previous points and add them up. There must be an easier way (is there?).

My code is in Kotlin and I converted it into sudo code, hopefully everyone will understand.

val animationLength = 100 // in frames
val maxAccumulatedProgress = animationLength // I found that the sum of all increments mysteriously adds up to the animation length

fun interpolate(from: Float, to: Float, animationProgress: Int): Float {
    // Add up increments for all previous animation frames
    var accumulatedProgress = 0
    repeat(animationProgress) {
        val normalizedProgress = map(it, 0, animationLength, 0, TWO_PI)
        val increment = formula(normalizedProgress)
        accumulatedProgress += increment
    }
    return map(accumulatedProgress, 0, maxAccumulatedProgress, from, to)
}

fun formula(normalizedProgress: Float) = -1 * cos(normalizedProgress) + 1 }

Ideally, I’d just give the function the start, end and amt between 0 and 1, same as to the lerp() function. Is that possible and is there a built-in function in Processing to do that?

1 Like

I recommend you check out autonomous behaviour videos by the coding train. The code is written in p5js but the logic is the same in processing. This is the final video you “have” to watch:

Here is the first video:

Try to implement this sort of thing. I hope you find this helpful.

1 Like

Thank you @CodeMasterX, although I could use Dan’s approach to implement arrival and potentially apply the same logic to speed up gradually on departure, it’s not quite what I’m looking for. My code doesn’t work with forces and I don’t have moving targets.

I’m looking for a function that I could apply to any animation (I used movement in my example but I’d also like to apply this technique to change size and rotation) to make it smooth.

I edited my original post to make it clear I’m also interested in other types of animation (not only movement) and implemented my method in p5js to demonstrate what it looks like:

https://editor.p5js.org/anoniim/sketches/h-1oa59Ps

I’m wondering whether/how it can be improved or whether something like this already exists in the Processing library.

1 Like

Sorry for misunderstanding. But you could still technically do it. Just imagine a line with two points on it.
One is the mover and the other is the destination.

Now all you have to do is loop through process until the mover arrives.
Instead of using forces, get the “acceleration” force (should be a vector, but because it is a 1D space and you stop at destination you can replace it with a value) and graph it.
Take the values from the graph and use it as “speed” of animation. This way you could solve the problem. I doubt it is the most efficient but should do the job.

Alternatively, you could graph a function and do something like:

float getSpeed(float origin, float destination, float progress) { //progress between 0-1
   return sin(progress*PI);
}

I have no idea how this would look but it should give a gradual speed.

Here is an example using the second thing

  PVector origin = new PVector(100,100);
  PVector destination = new PVector(500,500);
  float progress = 0;
  void setup() {
    size(600,600);
  }
  void draw() {
    background(0);
    fill(255,0,0);
    circle(origin.x,origin.y,15);
    fill(0,0,255);
    circle(destination.x,destination.y,15);
    
    fill(255-255*progress,0,255*progress);
    PVector m = new PVector(origin.x,origin.y);
    m.lerp(destination,progress);
    circle(m.x,m.y,5);
    
    
    progress+=move(progress);
  }
  float move(float pr) {
    float output = (sin(pr*PI)+0.01)*0.00625;
    return constrain(output,0,1);
  }

version with -cos()+1

float move(float pr) {
    float output = (-cos(pr*TWO_PI)+1.01)*0.00625;
    return constrain(output,0,1);
  }

I added the 0.001 since -cos(0)+1 = 0. This program requires progress > 0 && <= 1
the good thing about the thing I did is that you can “overshoot”, allowing you to continue the path.

1 Like

@anoniim,

If you are look for non-linear alternatives to the lerp() and map() Processing built-ins, these are sometimes discussed as “interpolation” but often – especially in animation – called “easing.”

Easing forms are often named after the function type of the curve that does the mapping, e.g. linear, quadratic, cubic, sinusoidal, et cetera. There is a good survey of implementing these in Java Processing (for your Kotlin project) in the recent writeup:

Easing Functions in Processing

The source code from that writeup is here – it implements map2() and map3():

Several past Processing libraries have implemented a collection of easing function that might be worth looking over. Two in particular:

  1. gicentre utils, which includes Ease
  2. the Ani animation library, which is actually an older Processing 2 library from 2015, but implements easing as a full set of classes rather than mapping functions:

Finally, piecewise interpolation (a.k.a. list-based interpolation) is the same thing based on a segmented path rather than a curve.

3 Likes