0
\$\begingroup\$

I need to write a function performing linear interpolating between two Euler angles in 2D, perfectly without the use of quaternions as conversion seems to be an overkill for such a simple task.

Here's what I came up with:

// Calculate difference on a wrapped number line
function difference(a, b, width) {
  const d = a - b;
  const ad0 = Math.abs(d);
  const ad1 = width - ad0;

  if (ad1 < ad0)
    return -Math.sign(d) * ad1;

  return d;
}

Number.prototype.mod = function(n) {
  return ((this % n) + n) % n;
};

const fullAngle = 2 * Math.PI;

// Linear interpolation
function slerp(a, b, t) {
  a = a.mod(fullAngle);
  b = b.mod(fullAngle);

  return (a + difference(b, a, fullAngle) * t).mod(fullAngle);
}

function toRadians(a) {
  return a * Math.PI / 180.0;
}

function toDegrees(a) {
  return a * 180.0 / Math.PI;
}

document.write(toDegrees(slerp(toRadians(20), toRadians(350), .25)));

The expected output is an angle linearly interpolated from one angle to another using specified t value.

In the example above the output is correct: The smallest angle difference between 350° and 20° is 30° (-30° with a sign).

20° + (-30°) * .25 = 20° - 7.5° = 12.5°

Is the code above correct? Could it be simplified?

\$\endgroup\$
  • \$\begingroup\$ Do you have supplied/expected output? Does that match the actual output (i.e. 12.499999999999984)? \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Nov 30 '17 at 17:45
  • \$\begingroup\$ @SamOnela I've edited the question. \$\endgroup\$ – mrpyo Nov 30 '17 at 18:02
1
\$\begingroup\$

Finding shortest angular distance.

I have always found the method of normalising angles with

const PI2 = Math.PI * 2;
var angle = ((angle % PI2) + PI2) % PI2;

and then finding the shortest angular distance a little bit hacky. This is even more so when the angles are originally vectors.

Cross and dot products.

Math.asin of the cross product of two vectors gives the angle left or right of first vector in the range -90 to 90 deg and the sign of the dot product will indicate if the vectors are in the same direction or not letting you get the quadrant the second vector is in and thus the shortest angular direction from one vector to the next.

The results are always relative to the direction of the first vector so you dont need to normalise the angle to 0 -Math.PI * 2

Polar to cartesian

As you want to work in angles (polar) you just need the extra step of calculating the cartesian vectors.

The function to find the shortest angular distance between any two bearings (angles).

// a and b are angles in radians
// returns direction in radians
function shortestAngle(a,b){
    const ax = Math.cos(a); // create vector a
    const ay = Math.sin(a);
    const bx = Math.cos(b); // create vector b
    const by = Math.sin(b);

    // asin cross product to find angle
    const angle = Math.asin(ax * by - ay * bx);

    // and dot to help find the quadrant
    const dot = (ax * bx + ay * by);

    // if facing in different direction dot < 0 angle will be
    // over 90 or under -90 else between  90 and -90
    return dot < 0 ? Math.sign(angle) * Math.PI - angle : angle;
}

Then to interpolate you just scale the result of the above function

// a is start angle 
// angDist is angular distance
// time is time in range 0 to 1
function slerp(a, angDist, time) {
    return angDist * time + a;
}

Gives in my view, a cleaner less hacky solution.

 // using
 var angle1 = ?
 var angle2 = ?
 var angDist = shortestAngle(angle1, angle2);
 var angle = slerp(angle1, angDist, time);

Which is even better if you are already working with vectors rather than angles.

\$\endgroup\$
  • \$\begingroup\$ Do vectors need to be normalized for the result to be correct (for version with vector inputs)? \$\endgroup\$ – mrpyo Nov 30 '17 at 20:05
  • 1
    \$\begingroup\$ @mrpyo For Math.asin( cross product ) yes vectors must be unit vectors \$\endgroup\$ – Blindman67 Nov 30 '17 at 20:14

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.