27
\$\begingroup\$

I am working on some movement AI where there are no obstacles and movement is restricted to the XY plane. I am calculating two vectors, v, the facing direction of ship 1, and w, the vector pointing from the position of ship 1 to ship 2.

I am then calculating the angle between these two vectors using the formula

arccos((v · w) / (|v| · |w|))

The problem I'm having is that arccos only returns values between 0° and 180°. This makes it impossible to determine whether I should turn left or right to face the other ship.

Is there a better way to do this?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ If you're using Unity, check out Mathf.DeltaAngle(). \$\endgroup\$ Commented Jul 14, 2014 at 17:21

5 Answers 5

23
\$\begingroup\$

It's much faster to use a 2d cross-product. No costly trig function involved.

b2Vec2 target( ... );
b2Vec2 heading( ... );

float cross = b2Cross( target, heading );

if( cross == -0.0f )
   // turn around

if( cross == 0.0f )
  // already traveling the right direction

if( cross < 0.0f)
  // turn left

if( cross > 0.0f)
  // turn right

If you still need the actual angles I recommend using atan2. atan2 will give you the absolute angle of any vector. To get the relative angle between any to vectors, calcuate their absolute angles and use simple subtraction.

b2Vec2 A(...);
b2Vec2 B(...);

float angle_A = std::atan2(A.y,A.x);
float angle_B = B.GetAngle(); // Box2D already figured this out for you.

float angle_from_A_to_B = angle_B-angle_A;
float angle_from_B_to_A = angle_A-angle_B;
\$\endgroup\$
8
  • 1
    \$\begingroup\$ After reading @Tetrad's answer I suppose you could combine a cross product and an arccos. This way you'll only use one trig function, but still have the actual angle. However I recommend against this optimization until you're sure you AI angle tracking is making a noticeable impact on your game's performance. \$\endgroup\$
    – deft_code
    Commented Jan 7, 2011 at 16:09
  • 2
    \$\begingroup\$ Yes, when converting between vectors and angles, atan2() is most definitely your friend. \$\endgroup\$
    – bluescrn
    Commented Jan 7, 2011 at 23:30
  • 1
    \$\begingroup\$ Thanks! I've found that I actually don't really need the angle, grabbing the 2D cross product is simple enough for my needs. \$\endgroup\$
    – Error 454
    Commented Jan 8, 2011 at 2:59
  • 2
    \$\begingroup\$ Also your if( cross == -0.0f ) vs if( cross == 0.0f ) check looks extremely fragile. \$\endgroup\$
    – bobobobo
    Commented Nov 25, 2012 at 21:42
  • 1
    \$\begingroup\$ @bobobobo, with a physics engine it may not be an option to just pick a direction and move. Magically turning can cause physic engines to freak out. Further magically rotating looks terrible for animation as well. So yes, you can solve this without a notion of left or right, but a polished solution often needs these. \$\endgroup\$
    – deft_code
    Commented Nov 26, 2012 at 20:36
11
\$\begingroup\$

Use arcsin of the 2D cross product (i.e the z component of the cross product vector). That'll give you -90 to 90 which will let you know whether to go left or right.

Be careful because A cross B is not the same as B cross A.

Another strategy (but probably not as straight forward) is to calculate the "heading" of the two vectors using atan2 and then figuring out whether A pointing at X degrees needs to go left or right to go to B pointing at y degrees.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for the response. To be clear for future browsers, taking the inverse sine of the magnitude of the 2d cross product would yield values between 0 and 90. Taking the sine of the z-component of the 2d cross product yields the desired results. \$\endgroup\$
    – Error 454
    Commented Jan 8, 2011 at 2:58
  • \$\begingroup\$ @Error 454, you're absolutely right, fixed my post. \$\endgroup\$
    – Tetrad
    Commented Jan 8, 2011 at 4:11
2
\$\begingroup\$

Use vectors to redirect the ship. This is how "steering behaviors" work -- you never need to calculate the angle, just use the vectors you have. This is computationally much cheaper.

The vector w (vector from Ship 1 to Ship 2) is all the information you need. Modify either ship 1's velocity vector or ship 1's acceleration vector (or even the heading vector directly) using a weighted version of w.

enter image description here

The magnitude of how far off ship 1 is off course (how badly v does not match up with w) can be found by using ( 1 - dot(v,w) )

  • (dot(v,w) is maximized when v and w line up exactly)
  • (1 - dot(v,w)) gives 0 when v and w are completely lined up, provided v and w are normalized)
\$\endgroup\$
0
\$\begingroup\$

There is a simple way to find the absolute angle of a vector through normal geometry.

for example vector V = 2i - 3j;

absolute value of x coefficient = 2;

absolute value of y coefficient = 3;

angle = atan( 2 / 3 ); [ angle will be in between 0 to 90 ]

Based on quadrant angle will be changed.

if ( x coefficient < 0 and y coefficient > 0 ) then angle = 180-angle;

if ( x coefficient < 0 and y coefficient < 0 ) then angle = 180+angle;

if ( x coefficient > 0 and y coefficient < 0 ) then angle = 360-angle;

if ( x coefficient > 0 and y coefficient > 0 ) then angle = angle;

after finding angle of first and second vectors, just subtract first vector angle from second vector. Then you will get absolute angle between two vectors.

\$\endgroup\$
2
  • 5
    \$\begingroup\$ This is exactly what the atan2() function implements for you. :) \$\endgroup\$ Commented Sep 30, 2011 at 18:59
  • \$\begingroup\$ @NathanReed, yeah, but couldn't you use this method with a dot product to avoid the trig overhead? \$\endgroup\$ Commented May 14, 2018 at 20:02
0
\$\begingroup\$

Maybe I should post a slightly different question and answer it myself; this is the closest question I could find to the one I had.

I'm doing 2D work in the html canvas, where I have rotation angles in radians rather than vectors. I needed the "turn angle" (ta) to get from "current heading" (h) to the "target heading" (t).

Here's my solution:

var ta = t - h,
    ata = Math.abs(ta)
;
if (ta > Math.PI) ta = (ta / ata) * (Math.PI - ata)
return ta;
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .