Given 2 angles in the range -PI -> PI around a coordinate, what is the value of the smallest of the 2 angles between them?
Taking into account that the difference between PI and -PI is not 2 PI but zero.
Example:
Imagine a circle, with 2 lines coming out from the center, there are 2 angles between those lines, the angle they make on the inside aka the smaller angle, and the angle they make on the outside, aka the bigger angle. Both angles when added up make a full circle. Given that each angle can fit within a certain range, what is the smaller angles value, taking into account the rollover
This gives a signed angle for any angles:
a = targetA - sourceA
a = (a + 180) % 360 - 180
Beware in many languages the modulo
operation returns a value with the same sign as the dividend (like C, C++, C#, JavaScript, full list here). This requires a custom mod
function like so:
mod = (a, n) -> a - floor(a/n) * n
Or so:
mod = (a, n) -> (a % n + n) % n
If angles are within [-180, 180] this also works:
a = targetA - sourceA
a += (a>180) ? -360 : (a<-180) ? 360 : 0
In a more verbose way:
a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180
x is the target angle. y is the source or starting angle:
atan2(sin(x-y), cos(x-y))
It returns the signed delta angle. Note that depending on your API the order of the parameters for the atan2() function might be different.
x-y
gives you the difference in angle, but it may be out of the desired bounds. Think of this angle defining a point on the unit circle. The coordinates of that point are (cos(x-y), sin(x-y))
. atan2
returns the angle for that point (which is equivalent to x-y
) except its range is [-PI, PI].
If your two angles are x and y, then one of the angles between them is abs(x - y). The other angle is (2 * PI) - abs(x - y). So the value of the smallest of the 2 angles is:
min((2 * PI) - abs(x - y), abs(x - y))
This gives you the absolute value of the angle, and it assumes the inputs are normalized (ie: within the range [0, 2π)
).
If you want to preserve the sign (ie: direction) of the angle and also accept angles outside the range [0, 2π)
you can generalize the above. Here's Python code for the generalized version:
PI = math.pi
TAU = 2*PI
def smallestSignedAngleBetween(x, y):
a = (x - y) % TAU
b = (y - x) % TAU
return -a if a < b else b
Note that the %
operator does not behave the same in all languages, particularly when negative values are involved, so if porting some sign adjustments may be necessary.
from math import tau
.
I rise to the challenge of providing the signed answer:
def f(x,y):
import math
return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
An efficient code in C++ that works for any angle and in both: radians and degrees is:
inline double getAbsoluteDiff2Angles(const double x, const double y, const double c)
{
// c can be PI (for radians) or 180.0 (for degrees);
return c - fabs(fmod(fabs(x - y), 2*c) - c);
}
See it working here: https://www.desmos.com/calculator/sbgxyfchjr
Arithmetical (as opposed to algorithmic) solution:
angle = Pi - abs(abs(a1 - a2) - Pi);
A simple method, which I use in C++ is:
double deltaOrientation = angle1 - angle2;
double delta = remainder(deltaOrientation, 2*M_PI);
I absolutely love Peter B's answer above, but if you need a dead simple approach that produces the same results, here it is:
function absAngle(a) { // this yields correct counter-clock-wise numbers, like 350deg for -370 return (360 + (a % 360)) % 360; } function angleDelta(a, b) { // https://gamedev.stackexchange.com/a/4472 let delta = Math.abs(absAngle(a) - absAngle(b)); let sign = absAngle(a) > absAngle(b) || delta >= 180 ? -1 : 1; return (180 - Math.abs(delta - 180)) * sign; } // sample output for (let angle = -370; angle <= 370; angle+=20) { let testAngle = 10; console.log(testAngle, "->", angle, "=", angleDelta(testAngle, angle)); }
One thing to note is that I deliberately flipped the sign: counter-clockwise deltas are negative, and clockwise ones are positive
There is no need to compute trigonometric functions. The simple code in C language is:
#include <math.h>
#define PIV2 M_PI+M_PI
#define C360 360.0000000000000000000
double difangrad(double x, double y)
{
double arg;
arg = fmod(y-x, PIV2);
if (arg < 0 ) arg = arg + PIV2;
if (arg > M_PI) arg = arg - PIV2;
return (-arg);
}
double difangdeg(double x, double y)
{
double arg;
arg = fmod(y-x, C360);
if (arg < 0 ) arg = arg + C360;
if (arg > 180) arg = arg - C360;
return (-arg);
}
let dif = a - b , in radians
dif = difangrad(a,b);
let dif = a - b , in degrees
dif = difangdeg(a,b);
difangdeg(180.000000 , -180.000000) = 0.000000
difangdeg(-180.000000 , 180.000000) = -0.000000
difangdeg(359.000000 , 1.000000) = -2.000000
difangdeg(1.000000 , 359.000000) = 2.000000
No sin, no cos, no tan,.... only geometry!!!!
arg = arg - PIV2;
expands to arg = arg - M_PI + M_PI
, and so does nothing.
Success story sharing
a -= 360*sgn(a)*(abs(a) > 180)
. (Come to think of it, if you've branchless implementations ofsgn
andabs
, then that characteristic might actually start to compensate for needing two multiplications.)double targetA = 2; double sourceA = 359;
'a' will be equal to -357.0 instead of 3.0%
operator acts like remainder in your language (retains sign), you can simply add an extra 360 instead of defining a modulus function:a = (a + 540) % 360 - 180
As stated above, this only works for angles within 360 of each other, which may often be the case. Otherwise:a = ((a % 360) + 540) % 360 - 180