Saturday, March 5, 2016

mathematics - How do I lerp between values that loop (such as hue or rotation)?


joint animation example


View Demo


I'm trying to make the joint rotate smoothly around the center of the canvas, toward the angle of mouse pointer. What I have works, but I want it to animate the shortest distance possible to get to the mouse angle. The problem occurs when the value loops around at the horizontal line (3.14 and -3.14). Mouseover that area to see how the direction switches and it takes the long way back around.



Relevant Code


// ease the current angle to the target angle
joint.angle += ( joint.targetAngle - joint.angle ) * 0.1;

// get angle from joint to mouse
var dx = e.clientX - joint.x,
dy = e.clientY - joint.y;
joint.targetAngle = Math.atan2( dy, dx );

How can I make it rotate the shortest distance, even "across the gap"?




Answer



It’s not always the best method, and it can be more computationally expensive (though this ultimately depends on how you store your data), but I will make the argument that lerping 2D values works reasonably well in the majority of cases. Instead of lerping a desired angle, you can lerp the desired normalised direction vector.


One advantage of this method over the “pick the shortest route to angle” method is that it works when you need to interpolate between more than two values.


When lerping hue values, you can replace hue with a [cos(hue), sin(hue)] vector.


In your case, lerping the normalised joint direction:


// get normalised direction from joint to mouse
var dx = e.clientX - joint.x,
dy = e.clientY - joint.y;
var len = Math.sqrt(dx * dx + dy * dy);
dx /= len ? len : 1.0; dy /= len ? len : 1.0;

// get current direction
var dirx = cos(joint.angle),
diry = sin(joint.angle);
// ease the current direction to the target direction
dirx += (dx - dirx) * 0.1;
diry += (dy - diry) * 0.1;

joint.angle = Math.atan2(diry, dirx);

The code can be shorter if you can use a 2D vector class. For instance:



// get normalised direction from joint to mouse
var desired_dir = normalize(vec2(e.clientX, e.clientY) - joint);
// get current direction
var current_dir = vec2(cos(joint.angle), sin(joint.angle));
// ease the current direction to the target direction
current_dir += (desired_dir - current_dir) * 0.1;

joint.angle = Math.atan2(current_dir.y, current_dir.x);

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...