Skip to content

Instantly share code, notes, and snippets.

@shaunlebron
Created February 5, 2014 20:41
Show Gist options
  • Save shaunlebron/8832585 to your computer and use it in GitHub Desktop.
Save shaunlebron/8832585 to your computer and use it in GitHub Desktop.
The best way to interpolate 2D angles
/*
2D Angle Interpolation (shortest distance)
Parameters:
a0 = start angle
a1 = end angle
t = interpolation factor (0.0=start, 1.0=end)
Benefits:
1. Angles do NOT need to be normalized.
2. Implementation is portable, regardless of how the modulo "%" operator outputs sign (i.e. Python, Ruby, Javascript)
3. Very easy to remember.
Thanks to Trey Wilson for the closed-form solution for shortAngleDist!
*/
function shortAngleDist(a0,a1) {
var max = Math.PI*2;
var da = (a1 - a0) % max;
return 2*da % max - da;
}
function angleLerp(a0,a1,t) {
return a0 + shortAngleDist(a0,a1)*t;
}
@fgirbal
Copy link

fgirbal commented Nov 14, 2019

Thank you for this, it's really useful!
It does appear to have a problem when a1 - a0 is a very small negative value. For example, if a1 - a0 = -1e-16, the result will be incorrect. A solution I found was to remove the sign from the modulo operation:
da = sign(a1 - a0)*(abs(a1 - a0) % max)
and then again on the return:
return sign(a1 - a0)*(2*abs(da) % max) - da

@earlin
Copy link

earlin commented Mar 26, 2020

translated from Unity's source code (but with DEGREES), Mathf.LerpAngle

function repeat(t, m) {
  return clamp(t - floor(t / m) * m, 0, m);
}

function lerpTheta(a, b, t) {
  const dt = repeat(b - a, 360);
  return lerp(a, a + (dt > 180 ? dt - 360 : dt), t);
}

@bytemaya
Copy link

Very useful !

@dwulive
Copy link

dwulive commented Jul 30, 2022

How is that better? I see a number of branches, less numerical stability/continuity (due to the clamp which presumably handles rounding errors from the divide)

@1j01
Copy link

1j01 commented Oct 19, 2022

It's important to note that % has the same operator precedence as * and / in JavaScript, when porting this to other languages. Maybe that's conventional, but in languages where you have to use a function for modulo (for floats), such as Go or GLSL, you have to be mindful of it.

Here's a Golang version:

func shortAngleDist(from float64, to float64) float64 {
	var turn = math.Pi * 2
	var deltaAngle = math.Mod(to-from, turn)
	return math.Mod(2*deltaAngle, turn) - deltaAngle
}

func angleLerp(from float64, to float64, fraction float64) float64 {
	return from + shortAngleDist(from, to)*fraction
}

@Lecrapouille
Copy link

I prefer the @earlin code
https://gist.github.com/shaunlebron/8832585?permalink_comment_id=3227412#gistcomment-3227412
because when testing godotengine/godot#30564 (the C++ version)

  • lerp_angle(-90.0_deg, 90.0_deg, 0.0) returns 90.0_deg
  • lerp_angle(-90.0_deg, 90.0_deg, 0.5) returns -180.0_deg instead of 0.0_deg
  • lerp_angle(-90.0_deg, 90.0_deg, 1.0) returns -270.0_deg instead of -90.0_deg

Which is not the case with earlin's code.

@Lecrapouille
Copy link

My quick implementation in C++ using https://github.com/nholthaus/units:

#include "units.h"

using namespace units::literals;
using Radian = units::angle::radian_t;
using Degree = units::angle::degree_t;

template<typename T, typename U>
inline T lerp(T const from, T const to, U const weight)
{
   return from + (to - from) * weight;
}

template<typename T>
inline T constrain(T const value, T const lower, T const upper)
{
    if (value < lower) { return lower; }
    if (value > upper) { return upper; }
    return value;
}

inline Degree lerp_angle(Degree const from, Degree const to, double weight)
{
   auto repeat = [](Degree const t, Degree const m) -> Degree
   {
      return constrain(t - units::math::floor(t / m) * m, 0.0_deg, m);
   };
   const Degree dt = repeat(to - from, 360.0_deg);
   return lerp(from, from + (dt > 180.0_deg ? dt - 360.0_deg : dt), weight);
}

@Lecrapouille
Copy link

@codergautam nice !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment