1
\$\begingroup\$

I'd like to be able to direct a game object to move to a new target position over a certain amount of time. There are lots of different easing functions available, but for now let's say I'm just planning to use a standard quadratic ease-in ease-out function and also a quadratic ease-out function.

For example

Math.easeInOutQuad = function (t, b, c, d) {
    t /= d/2;
    if (t < 1) return c/2*t*t + b;
    t--;
    return -c/2 * (t*(t-2) - 1) + b;
};

Now suppose while it is still in the process of moving to that target and I want to give it a new target. Rather than have it instantly change directions with a zero starting velocity, I'd like a smooth transition.

How is this done mathematically and programmatically?

I want to avoid keeping a dynamic array of tween commands for every parameter of each game object. (I want to tween all sorts of parameters besides position.) that could get really heavy and bloated. But that's the only thing that comes to mind that might work... Keeping an array of active tweens and averaging all of them.

Maybe some easing functions lend themselves better toward being interrupted? Or is there a way to recalculate the four inputs to the ease function when a new target is set, such that the same function can continue to be used once per frame without a jarring speed/direction change, and ending at the second target instead?

\$\endgroup\$
1
\$\begingroup\$

A standard "tween" algorithm usually works by simplifying a more general hermite spline function, to eliminate the necessity of manually providing initial and terminating velocity values.

But if you wish to be able to switch from one tween to another, you can no longer do that, and you'll need to use a full, non-simplified spline calculation.

Here's some pseudocode which will work for floats or mathematical vectors (substitute 'type' for whatever value type you're interpolating between):

type SplineValueAtTime( float t, type start, type startVelocity, type end, type endVelocity )
{
    float tSquared = t * t;
    float tCubed = tSquared * t;

    float a = 2.f * tCubed - 3.f * tSquared + 1.f;  // 2t^3 - 3t^2 + 1
    float b = tCubed - 2.f * tSquared + t;          // t^3 - 2t^2 + t
    float c = -2.f * tCubed + 3.f * tSquared;       // -2t^3 + 3t^2
    float d = tCubed - tSquared;                    // t^3 - t^2

    type result = (a * start) +
                  (b * startVelocity) +
                  (c * end) +
                  (d * endVelocity);

    return result;
}

The code above calculates a spline with the specified start, end, and velocities at each end. 't' is assumed to vary in the range [0..1]. (If 'startVelocity' and 'endVelocity' are both zero, then this math simplifies down into the standard hermite interpolator, start + ((3*t*t-2*t*t*t) * end-start))

Now all you need is to be able to figure out the object's current velocity at the time when it changes its interpolation target, so that you can create a new interpolation spline starting from its current position and velocity, and ending up at the desired new location.

Here's code to calculate the velocity at any particular point of the spline: (This is just the derivative of the above calculation)

type SplineVelocityAtTime( float t, type start, type startVelocity, type end, type endVelocity )
{
    float tSquared = t * t;

    float a = 6.f * tSquared - 6.f * t;             // 6t^2 - 6t
    float b = 3.f * tSquared - 4.f * t + 1;         // 3t^2 - 4t + 1
    float c = -6.f * tSquared + 6.f * t;            // -6t^2 + 6t
    float d = 3.f * tSquared - 2.f * t;             // 3t^2 - 2t

    type result = (a * start) +
                  (b * startVelocity) +
                  (c * end) +
                  (d * endVelocity);

    return result;
}

So with the information from these two functions, you can seamlessly change from one interpolation to another. Whenever you interrupt a transition with a new one, begin the new one using the item's current position and velocity (as determined by the two functions above), set 'end' to the new desired endpoint, and set endVelocity to 0 if you want an "ease-out", or to whatever other velocity you want the item to have at the end of the motion. (end-start is often a reasonable choice, if you don't want ease-out on the motion)

Hope that helps!

\$\endgroup\$
  • \$\begingroup\$ @TenFour04 Do note that in the above, "velocity" is expressed in units which assume that the duration of the interpolation is one second (the range of the t value). If you're actually interpolating over (for example) five seconds, and just dividing your 'current time' value by 5 before passing it into the function, then the real-world velocity will be 1/5 of the value returned. This is important to remember if you're interrupting an interpolation of one duration with a new one of a different duration -- you'll have to convert the velocity value to account for the changing scale of t. \$\endgroup\$ – Trevor Powell Dec 15 '13 at 23:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.