How can I find the smallest difference between two angles around a point?
Asked Answered
M

12

199

Given a 2D circle with 2 angles in the range -PI -> PI around a coordinate, what is the value of the smallest angle between them?

Taking into account that the difference between PI and -PI is not 2 PI but zero.

An 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

Miserly answered 10/12, 2009 at 6:1 Comment(6)
I read 3 times before I understood what you meant. Please add an example, or explain better...Blaubok
Imagine a circle, with 2 lines comign 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 rolloverMiserly
Possible duplicate of How to calculate the angle between a line and the horizontal axis?Allysonalma
@JimG. this isn't the same question, in this question the angle P1 used in the other question would be the incorrect answer, it would be the other, smaller angle. Also, there is no guarantee that the angle is with the horizontal axisMiserly
if you use Unity c# script, you can use Mathf.DeltaAngle function.Induplicate
@bigant02, thank you so much. I have researching this exact question in Unity. :) You helped me so much :)Smaragdite
B
276

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
Booking answered 23/10, 2011 at 21:47 Comment(11)
Simpler and makes more sense read out loud, though effectively the same thing, first bti figures out the angle, second part makes sure its always the smaller of the 2 possible anglesMiserly
although one might want to do a % 360, e.g. if I had the angle 0 and the target angle 721, the correct answer would be 1, the answer given by the above would be 361Miserly
Yes that's true and deliberate but definitely worth pointing out. In my example I previously got targetA and sourceA from atan2, hence their absolute angles are never greater than 360.Booking
the latter approach also works for other clamped angle intervals such as [0, 360) as the two angles are subtracted.Ornamented
I made a js gist for this [here](gist.github.com/Aaronduino/4068b058f8dbc34b4d3a9eedc8b2cbe0.Milreis
A more concise, though potentially more expensive, equivalent of the latter approach's second statement, is a -= 360*sgn(a)*(abs(a) > 180). (Come to think of it, if you've branchless implementations of sgn and abs, then that characteristic might actually start to compensate for needing two multiplications.)Hairy
The "Signed angle for any angle" example seems to work in most scenarios, with one exception. In scenario double targetA = 2; double sourceA = 359; 'a' will be equal to -357.0 instead of 3.0Tomato
In C++ you can use std::fmod(a,360), or fmod(a,360) to use floating point modulo.Freeboot
Joeppie: That's true. But will it behave as mentioned for the sign of the dividend?Sartre
Mh. This a += (a>180) ? -360 : (a<-180) ? 360 : 0 seems not to work for me when written in C++.Sartre
If the % 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 - 180Tarra
I
185

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.

Indra answered 5/1, 2010 at 16:6 Comment(8)
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].Haunt
This passes the test suite gist.github.com/bradphelan/7fe21ad8ebfcb43696b8Polyzoic
a one line simple solution and solved for me(not the selected answer ;) ). but tan inverse is a costly process.Caeoma
For me, the most elegant solution. Shame it might be computationally expensive.Henrion
For me the most elegant solution as well! Solved my problem perfectly (wanted to have a formula that gives me the signed turn angle which is the smaller one from the two possible turn directions/angles).Regression
Unfortunately, this also isn't as precise as the other solutions.Bornholm
I still use this function every time because it's easy to remember, elegant, and avoids any constants. For me that's worth the loss in precision and speed.Doublestop
This solution works, but is f@%& slow!!! All three trigonometric operations are slow to compute.Fewness
D
60

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.

Decurion answered 10/12, 2009 at 6:9 Comment(6)
@Polyzoic That is/was true, but to be fair your tests checked for things that weren't specified in the original question, specifically non-normalized inputs and sign-preservation. The second version in the edited answer should pass your tests.Decurion
The second version also doesn't work for me. Try 350 and 0 for example. It should return -10 but returns -350Kenn
@Kenn I can't reproduce the behavior you describe. Can you post the exact code?Decurion
Ah, I'm sorry. I've tested exactly your version with rad and degrees in python again and it worked fine. So must have been a mistake in my translation to C# (don't have it anymore).Kenn
Note that, as of Python 3, you can actually use tau natively! Just write from math import tau.Tourniquet
Assuming (x,y) = (targetA, source A). Does it work for angles from [-180,180] ? Tried following (-2,-4), (172,-4), (-4, 172), (-172,-170) got answers with sign flipped. Or does it assumes the otherway arround? (x,y) = (sourceA, targetA) ?Isochroous
F
15

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

For signed angle: return fmod(fabs(x - y) + c, 2*c) - c;

In some other programming languages where mod of negative numbers are positive, the inner abs can be eliminated.

Fewness answered 20/9, 2018 at 20:33 Comment(1)
Great idea, but it doesn't yield signed angle.Elvyn
P
11

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)
Preiser answered 5/1, 2010 at 16:19 Comment(5)
Ah... the answer is a Python function by the way. Sorry, I was in Python mode for a moment. Hope that's okay.Preiser
I shall plug the new formula into my code upstairs and see what becomes of it! ( thankyou ^_^ )Miserly
I'm pretty sure PeterB's answer is correct too. And evilly hackish. :)Preiser
But this one contains no trig functions :)Plank
might be a good idea to import math at the start of the file insteadHunterhunting
A
6

For UnityEngine users, the easy way is just to use Mathf.DeltaAngle.

Analyse answered 7/2, 2016 at 20:3 Comment(1)
Has no signed output thoKenn
B
5

Arithmetical (as opposed to algorithmic) solution:

angle = Pi - abs(abs(a1 - a2) - Pi);
Broadbrim answered 28/3, 2012 at 12:7 Comment(2)
This fails the test suite gist.github.com/bradphelan/7fe21ad8ebfcb43696b8Polyzoic
Fails if abs(a1-a2) >>> 360 . Use this instead: https://mcmap.net/q/127476/-how-can-i-find-the-smallest-difference-between-two-angles-around-a-pointFewness
D
2

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

Daube answered 8/7, 2022 at 11:15 Comment(0)
C
0

Old ass thread but ... I had the same problem in Desmos. Here was my implementation if useful to anyone:

Screenshot of the LaTex: https://i.stack.imgur.com/6RzDA.png

Parameters v1, and v2 are vectors. Plug in like points. Such as: v1 = (0,0), v2 = (0,1).

I was thinking there might be some way to optimize it with complex number multiplication but I'm just gonna leave it there bc I'm just using Desmos to throw together a model.

Carditis answered 5/7, 2023 at 21:19 Comment(0)
P
0

The Lua code to compare two vectors, given with :

function angleComparing(x1, y1, x2, y2)
    local a1 = math.atan2(y1, x1)
    local a2 = math.atan2(y2, x2)
    local diff = (a2 - a1 + math.pi)%(2*math.pi) - math.pi
    return diff -- returns values in range [-pi, pi]
end

Usage:

for i = 360, -360, -180 do
    for j = -120, 120, 120 do
        local a1 = math.rad (i)
        local a2 = math.rad (j)
        local dy1, dx1 = math.sin (a1), math.cos (a1)
        local dy2, dx2 = math.sin (a2), math.cos (a2)
        local dif = angleComparing(dx1, dy1, dx2, dy2)
        print (i, j, string.format('%.1f', math.deg (dif)))
    end
end

Result:

(angle2 + difference == angle1)

angle1, angle2, difference
360     -120    120.0
360     0       0.0
360     120     -120.0
180     -120    -60.0
180     0       -180.0
180     120     60.0
0       -120    120.0
0       0       0.0
0       120     -120.0
-180    -120    -60.0
-180    0       -180.0
-180    120     60.0
-360    -120    120.0
-360    0       0.0
-360    120     -120.0
Pratfall answered 23/4 at 12:22 Comment(0)
G
-1

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!!!!

Gossipmonger answered 27/9, 2013 at 6:17 Comment(1)
Bug! Since you #define PIV2 as "M_PI+M_PI", not "(M_PI+M_PI)", the line arg = arg - PIV2; expands to arg = arg - M_PI + M_PI, and so does nothing.Gwenni
S
-1

A simple method, which I use in C++ is:

double deltaOrientation = angle1 - angle2;
double delta =  remainder(deltaOrientation, 2*M_PI);
Supernormal answered 10/9, 2020 at 10:17 Comment(1)
This is wrong, I'm afraid. Consider if angle1 = 0 and angle2 = pi+c, for some c>0. The correct answer should be -(pi-c), but your answer gives pi+c. Bear in mind that the OP explicitly asked for the smaller angle, and the smaller angle should always be less than or equal to pi.Sholom

© 2022 - 2024 — McMap. All rights reserved.