-
-
Save shaunlebron/8832585 to your computer and use it in GitHub Desktop.
/* | |
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; | |
} |
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
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);
}
Very useful !
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)
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
}
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.
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);
}
@codergautam nice !
In shortAngleDist, why do you need this statement
return 2*da % max - da;
?