11

Im trying to find out the angle (in degrees) between two 2D vectors. I know I need to use trig but I'm not too good with it. This is what I'm trying to work out (the Y axis increases downward): alt text

I'm trying to use this code at the moment, but it's not working at all (calculates random angles for some reason):

private float calcAngle(float x, float y, float x1, float y1)
{
    float _angle = (float)Math.toDegrees(Math.atan2(Math.abs(x1-x), Math.abs(y1-y)));
    Log.d("Angle","Angle: "+_angle+" x: "+x+" y: "+y+" x1: "+x1+" y1: "+y1);
    return _angle;
}

These are my results (There constant when providing a constant position, but when I change the position, the angle changes and I can't find any link between the two angles):

Position 1: x:100 y:100 x1:50 y1:50 Angle: 45

Position 2: x:92 y:85 x1:24 y1:16 Angle: 44.58

Position 3: x:44 y: 16 x1:106 y1:132 Angle: 28.12

Edit: Thanks everyone who answered and helped me figure out that was wrong! Sorry the title and the question was confusing.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Niall
  • 757
  • 3
  • 9
  • 17
  • 1
    I doubt it's random. Could you post the values of x1, x, y1, y? Does the output change even when the input is constant? – FrustratedWithFormsDesigner Aug 09 '10 at 15:53
  • 2
    Your diagram is incorrect. You have only defined 2 points, and there is no representation for the the vector that creates the angle Theta. Using, p1 and p2 as in this diagram will find a very different angle; the angle p1 and p2 make with the origin. – aepryus Aug 09 '10 at 16:01
  • 1
    You say you are trying to calculate the angle between two vectors but the diagram seems to imply that you are actually trying to get the angle between one vector and the y-axis. Is that correct? – Troubadour Aug 09 '10 at 16:02
  • @Troubadour Sorry about the confusing question. I'm doing this for a game, so when you reach a point, you have to face a certain degrees (on a compass) then walk a distance to reach the next point. – Niall Aug 09 '10 at 16:14
  • @FrustratedWithFormsDesigner I'll post my results and actual code in the question, 2 secs :) – Niall Aug 09 '10 at 16:15
  • @Niall: That sounds as if you have a current direction and a new (absolute) angle and want to know the new direction vector. So you are just trying to take the absolute compass angle and work out the new direction (with the old direction being irrelevant)? – Troubadour Aug 09 '10 at 16:20
  • Yeah. Again, sorry about the question! (& sorry for replying late, didn't see your comment until just now) – Niall Aug 09 '10 at 16:42
  • @Niall: No problem. I've added an answer that explains your problem. Note that your result for Position 3 is correct. – Troubadour Aug 09 '10 at 16:45

9 Answers9

15

You first have to understand how to compute angle between two vectors and there are several of them. I will give you what I think is the simplest.

  1. Given v1 and v2, their dot product is: v1x * v2x + v1y * v2y
  2. The norm of a vector v is given by: sqtr(vx^2+vy^2)

With this information, please take this definition:

dot(v1, v2) = norm(v1) * norm(v2) * cos(angle(v1, v2))

Now, you solve for angle(v1, v2):

angle(v1, v2) = acos( dot(v1, v2) / (norm(v1) * norm(v2)) )

Finally, taking the definitions given at the beginning, then you end up with:

angle(v1, v2) = acos( (v1x * v2x + v1y * v2y) / (sqrt(v1x^2+v1y^2) * sqrt(v2x^2+v2y^2)) )

Again, there are many ways to do this, but I like this one because it is helpful for dot product given angle and norm, or angle, given vectors.

The answer will be in radians, but you know that pi radians (that is 3.14 radians) are 180 degrees, so you simply multiply by the conversion factor 180/pi.

walkytalky
  • 9,453
  • 2
  • 36
  • 44
Escualo
  • 40,844
  • 23
  • 87
  • 135
  • Thanks for explaining it! I'm trying out your algorithm, but when the angle is obtuse I have to mirror it and when it's not, I have to add 45 degrees. Is there any reason for this? I mean, I'm fine doing a simple if else statement, but I'd like to know why im doing it :) – Niall Aug 09 '10 at 16:56
  • Don't worry, just figured out i had to use atan2 and mirror the angle. Thanks for your answer and for explaining it though! :) – Niall Aug 09 '10 at 17:10
  • Yeah. If you think about the dot product of two vectors, you will realize why the arctangent resolves the quadrant issue. In other words, it picks the correct sign for the angle. Good luck. – Escualo Aug 10 '10 at 15:26
  • sqrt(vx^2 + vy^2) is the vector magnitude, not the normal. One normal is (-vy / mag, vx / mag). Note the x and y values are switched up. – Gabe Johnson Nov 10 '15 at 18:42
  • Normalize both vectors. Then it's simply: `angle = acos (vectorA.dot(vectorB))` – Hal50000 Jan 23 '16 at 21:42
13

Aha! Turns out I just needed to flip my angle and use atan2. This is my final code:

private float calcAngle(float x, float y, float x1, float y1)
{
    float _angle = (float)Math.toDegrees(Math.atan2(x1-x, y-y1));
    return _angle;
}

Thanks everyone for helping me figure this out and also for helping me to understand what I'm actually doing! :)

Niall
  • 757
  • 3
  • 9
  • 17
  • So your y co-ord increases down the way then and you want North to be straight up. Instead of subtracting from 180 you can just flip the signs of y i.e. your second argument to `atan2` is therefore `y-y1`. – Troubadour Aug 09 '10 at 17:11
  • 1
    warning: a division by zero will result when (y1 - y) == 0. – Leftium Aug 09 '10 at 17:46
  • 1
    @wonsungi: No it won't. `atan2` takes care of that and returns either +pi/2 or -pi/2 depending on the signs of the two arguments (including the argument that is zero since that can still have a sign). At least it does in the C version. I assume other languages would do something similar. – Troubadour Aug 09 '10 at 18:12
  • @Troubadour: You're right! Thanks~ That guard must be from before I knew about `atan2`; I was using asin and doing the division manually... – Leftium Aug 09 '10 at 23:43
5

Do not take the absolute value of the arguments to atan2. The whole point of atan2 is that it uses the signs of its arguments to work out which qaudrant the angle is in. By taking the absolute values you are forcing atan2 to only return values between 0 and pi/2 instead of -pi to pi.

Troubadour
  • 13,334
  • 2
  • 38
  • 57
  • 1
    Thanks for explaining why I shouldn't take the absolute values when using atan2 :) Turns out in the end I just had to mirror the angle (E.g. 180+(-toDegrees(atan2(x1-x,y1-y))) ) – Niall Aug 09 '10 at 17:08
  • @Niall: Glad to help. :) See my comment to your answer. Also, since you are actually trying to work out the angle between the negative y-axis and a single vector perhaps you should edit the title of the question as it's very misleading at the moment. Something like "How to calculate the angle of a vector from the vertical?" and point out in the question that y increases downwards in your system. – Troubadour Aug 09 '10 at 17:17
  • This looks like it should be a comment to me. It doesn't address the actual question. – Llew Vallis Oct 05 '17 at 10:42
2

It looks like Niall figured it out, but I'll finish my explanation, anyways. In addition to explaining why the solution works, my solution has two advantages:

  • Potential division by zero within atan2() is avoided
  • Return value is always positive in the range 0 to 360 degrees

atan2() returns the counter-clockwise angle relative to the positive X axis. Niall was looking for the clockwise angle relative to the positive Y axis (between the vector formed by the two points and the positve Y axis).

The following function is adapted from my asteroids game where I wanted to calculate the direction a ship/velocity vector was "pointing:"

// Calculate angle between vector from (x1,y1) to (x2,y2) & +Y axis in degrees.
// Essentially gives a compass reading, where N is 0 degrees and E is 90 degrees.

double bearing(double x1, double y1, double x2, double y2)
{
    // x and y args to atan2() swapped to rotate resulting angle 90 degrees
    // (Thus angle in respect to +Y axis instead of +X axis)
    double angle = Math.toDegrees(atan2(x1 - x2, y2 - y1));

    // Ensure result is in interval [0, 360)
    // Subtract because positive degree angles go clockwise
    return (360 - angle) %  360;
}
Leftium
  • 16,497
  • 6
  • 64
  • 99
  • Thanks for helping me understand why it works! :) Unfortunately I'm currently working in Java (I'm a C & Obj-C developer mainly, but it's always good to learn new stuff! I started off with Java years ago though, so it's not too hard :) ) so I can't use #define. Also, do you think I should use double instead of using float? As all of the Math functions in Java use double, but I once had a problem with double vs float (probably just something else in my code) and since then have always used float. – Niall Aug 09 '10 at 18:37
  • 1
    I changed the code to be more Java-like: `Math.toDegrees()` is effectively the same as multiplying by the previous DEG_PER_RAD constant. (Otherwise you can create a Java constant with `static final double`). Float has less precision than double, and will likely incur a performance penalty since conversions between float and double will be required. Unless saving memory is a big concern, I would always use double. Also Troubadour pointed out atan2 does not need to be guarded against division by zero. – Leftium Aug 09 '10 at 23:59
1

It should be :

atan( abs(x1 - x)/abs(y1 - y) ) 

abs stands for absolute (to avoid negative values)

3rgo
  • 3,115
  • 7
  • 31
  • 44
1

I believe the equation for the angle between two vectors should look more like:

toDegrees(acos((x*x1+y*y1)/(sqrt(x*x+y*y)*sqrt(x1*x1+y1*y1))))

Your above equation will calculate the angle made between the vector p1-p2 and the line made by extending an orthogonal from the point p2 to the vector p1.

The dot product of two vectors V1 and V2 is equal to |V1|*|V2|cos(theta). Therefore, theta is equal to acos((V1 dot V2)/(|V1||V2|)). V1 dot V2 is V1.xV2.x+V1.yV2.y. The magnitude of V (i.e., |V|) is the pathogorean theorem... sqrt(V.x^2 + V.y^2)

aepryus
  • 4,715
  • 5
  • 28
  • 41
0

Are you using integers? Cast the arguments as doubles, and I would use fabs on the result, not the arguments. The result will be in radians; to get degrees, use:

res *= (360.0/(2.0*Math.PI));

Jess
  • 2,991
  • 3
  • 27
  • 40
  • Whoever downvoted this should realise that it was posted _before_ the OP posted code that confirms the use of floating point precision. – Troubadour Aug 09 '10 at 16:41
0

My first guess would be to calculate the angle of each vector with the axes using atan(y/x) and then subtract those angels and take the absolute value, that is:

abs(atan(y/x) - atan(y1/x1))

smichak
  • 4,716
  • 3
  • 35
  • 47
0

The angle of the second vector relative to the first = atan2(y2,x2) - atan2(y1,x1).

http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm

Mark Cidade
  • 98,437
  • 31
  • 224
  • 236