Mircea Ispas
Mircea Ispas

Reputation: 20780

Direct way of computing the clockwise angle between two vectors

I want to find out the clockwise angle between two vectors (two-dimensional or three-dimensional).

The classic way with the dot product gives me the inner angle (0-180 degrees), and I need to use some if statements to determine if the result is the angle I need or its complement.

Is there a direct way of computing the clockwise angle?

Upvotes: 122

Views: 125053

Answers (10)

MvG
MvG

Reputation: 60858

2D case

Just like the dot product is proportional to the cosine of the angle, the determinant is proportional to its sine. So you can compute the angle like this:

dot = x1*x2 + y1*y2      # Dot product between [x1, y1] and [x2, y2]
det = x1*y2 - y1*x2      # Determinant
angle = atan2(det, dot)  # atan2(y, x) or atan2(sin, cos)

The orientation of this angle matches that of the coordinate system. In a left-handed coordinate system, i.e. x pointing right and y down as is common for computer graphics, this will mean you get a positive sign for clockwise angles. If the orientation of the coordinate system is mathematical with y up, you get counterclockwise angles as is the convention in mathematics. Changing the order of the inputs will change the sign, so if you are unhappy with the signs just swap the inputs.

3D case

In 3D, two arbitrarily placed vectors define their own axis of rotation, perpendicular to both. That axis of rotation does not come with a fixed orientation, which means that you cannot uniquely fix the direction of the angle of rotation either. One common convention is to let angles be always positive, and to orient the axis in such a way that it fits a positive angle. In this case, the dot product of the normalized vectors is enough to compute angles.

dot = x1*x2 + y1*y2 + z1*z2    # Between [x1, y1, z1] and [x2, y2, z2]
lenSq1 = x1*x1 + y1*y1 + z1*z1
lenSq2 = x2*x2 + y2*y2 + z2*z2
angle = acos(dot/sqrt(lenSq1 * lenSq2))

Note that some comments and alternate answers advise against the use of acos for numeric reasons, in particular if the angles to be measured are small.

Plane embedded in 3D

One special case is the case where your vectors are not placed arbitrarily, but lie within a plane with a known normal vector n. Then the axis of rotation will be in direction n as well, and the orientation of n will fix an orientation for that axis. In this case, you can adapt the 2D computation above, including n into the determinant to make its size 3×3.

dot = x1*x2 + y1*y2 + z1*z2
det = x1*y2*zn + x2*yn*z1 + xn*y1*z2 - z1*y2*xn - z2*yn*x1 - zn*y1*x2
angle = atan2(det, dot)

One condition for this to work is that the normal vector n has unit length. If not, you'll have to normalize it.

As triple product

This determinant could also be expressed as the triple product, as @Excrubulent pointed out in a suggested edit.

det = n · (v1 × v2)

This might be easier to implement in some APIs, and gives a different perspective on what's going on here: The cross product is proportional to the sine of the angle, and will lie perpendicular to the plane, hence be a multiple of n. The dot product will therefore basically measure the length of that vector, but with the correct sign attached to it.

Range 0 – 360°

Most atan2 implementations will return an angle from [-π, π] in radians, which is [-180°, 180°] in degrees. If you need positive angles [0, 2π] or [0°, 360°] instead, you can just add 2π to any negative result you get. Or you can avoid the case distinction and use atan2(-det, -dot) + π unconditionally. If you are in a rare setup where you need the opposite correction, i.e. atan2 returns non-negative [0, 2π] and you need signed angles from [-π, π] instead, use atan2(-det, -dot) - π. This trick is actually not specific to this question here, but can be applied in most cases where atan2 gets used. Remember to check whether your atan2 deals in degrees or radians, and convert between these as needed.

Upvotes: 291

Oko Lenmi
Oko Lenmi

Reputation: 33

For the two-dimensional case, atan2 can easily calculate the angle between a (1, 0) vector (the x-axis) and one of your vectors.

The formula is:

Atan2(y, x)

So you can easily calculate the difference of the two angles relative to the x-axis:

angle = -(atan2(y2, x2) - atan2(y1, x1))

Why is it not used as default solution? atan2 is not efficient enough. The solution from the top answer is better. Tests on C# showed that this method has 19.6% less performance (100 000 000 iterations).

It's not critical, but unpleasant.

So, other information that can be useful:

The smallest angle between outer and inner in degrees:

abs(angle * 180 / PI)

Full angle in degrees:

angle = angle * 180 / PI
angle = angle > 0 ? angle : 360 - angle

or

angle = angle * 180 / PI
if (angle < 0)
    angle = 360 - angle;

Upvotes: 1

Carlos Borau
Carlos Borau

Reputation: 1473

Since one of the simplest and most elegant solutions is hidden in one of the comments, I think it might be useful to post it as a separate answer.

acos can cause inaccuracies for very small angles, so atan2 is usually preferred. For the three-dimensional case:

dot = x1*x2 + y1*y2 + z1*z2
cross_x = (y1*z2 – z1*y2)
cross_y = (z1*x2 – x1*z2) 
cross_z = (x1*y2 – y1*x2)
det = sqrt(cross_x*cross_x + cross_y*cross_y + cross_z*cross_z)
angle = atan2(det, dot)

Upvotes: 5

Red
Red

Reputation: 1

Just copy and paste this:

angle = (acos((v1.x * v2.x + v1.y * v2.y)/((sqrt(v1.x*v1.x + v1.y*v1.y) * sqrt(v2.x*v2.x + v2.y*v2.y))))/pi*180);

Upvotes: -4

theodore panagos
theodore panagos

Reputation: 77

A formula for clockwise angle, two-dimensional case, between two vectors, (xa,ya) and (xb,yb).

Angle(vec.a-vec,b) =
  pi()/2*((1 + sign(ya))*
  (1 - sign(xa^2)) - (1 + sign(yb))*
  (1 - sign(xb^2))) + pi()/4*
  ((2 + sign(ya))*sign(xa) - (2 + sign(yb))*
  sign(xb)) + sign(xa*ya)*
  atan((abs(ya) - abs(xa))/(abs(ya) + abs(xa))) - sign(xb*yb)*
  atan((abs(yb) - abs(xb))/(abs(yb) + abs(xb)))

Upvotes: -1

sircolinton
sircolinton

Reputation: 6836

This answer is the same as MvG's, but explains it differently (it's the result of my efforts in trying to understand why MvG's solution works).

The anti-clockwise angle theta from x to y, with respect to the viewpoint of their given normal n (||n|| = 1), is given by

atan2( dot(n, cross(x,y)), dot(x,y) )

(1) = atan2( ||x|| ||y|| sin(theta),  ||x|| ||y|| cos(theta) )

(2) = atan2( sin(theta), cos(theta) )

(3) = anti-clockwise angle between x axis and the vector (cos(theta), sin(theta))

(4) = theta

where ||x|| denotes the magnitude of x.

Step (1) follows by noting that

cross(x,y) = ||x|| ||y|| sin(theta) n,

and so

dot(n, cross(x,y))

= dot(n, ||x|| ||y|| sin(theta) n)

= ||x|| ||y|| sin(theta) dot(n, n)

which equals

||x|| ||y|| sin(theta)

if ||n|| = 1.

Step (2) follows from the definition of atan2, noting that atan2(cy, cx) = atan2(y,x), where c is a scalar. Step (3) follows from the definition of atan2. Step (4) follows from the geometric definitions of cos and sin.

Upvotes: 7

nichole
nichole

Reputation: 131

For a two-dimensional method, you could use the law of cosines and the "direction" method.

To calculate the angle of segment P3:P1 sweeping clockwise to segment P3:P2.

    P1     P2

        P3
    double d = direction(x3, y3, x2, y2, x1, y1);

    // c
    int d1d3 = distanceSqEucl(x1, y1, x3, y3);

    // b
    int d2d3 = distanceSqEucl(x2, y2, x3, y3);

    // a
    int d1d2 = distanceSqEucl(x1, y1, x2, y2);

    //cosine A = (b^2 + c^2 - a^2)/2bc
    double cosA = (d1d3 + d2d3 - d1d2)
        / (2 * Math.sqrt(d1d3 * d2d3));

    double angleA = Math.acos(cosA);

    if (d > 0) {
        angleA = 2.*Math.PI - angleA;
    }

This has the same number of transcendental operations as suggestions above and only one more or so floating point operation.

The methods it uses are:

 public int distanceSqEucl(int x1, int y1,
    int x2, int y2) {

    int diffX = x1 - x2;
    int diffY = y1 - y2;
    return (diffX * diffX + diffY * diffY);
}

public int direction(int x1, int y1, int x2, int y2,
    int x3, int y3) {

    int d = ((x2 - x1)*(y3 - y1)) - ((y2 - y1)*(x3 - x1));

    return d;
}

Upvotes: 2

Vadim Khotilovich
Vadim Khotilovich

Reputation: 793

If by "direct way" you mean avoiding the if statement, then I don't think there is a really general solution.

However, if your specific problem would allow losing some precision in angle discretization and you are ok with losing some time in type conversions, you can map the [-pi,pi] allowed range of phi angle onto the allowed range of some signed integer type. Then you would get the complementarity for free. However, I didn't really use this trick in practice. Most likely, the expense of float-to-integer and integer-to-float conversions would outweigh any benefit of the directness. It's better to set your priorities on writing autovectorizable or parallelizable code when this angle computation is done a lot.

Also, if your problem details are such that there is a definite more likely outcome for the angle direction, then you can use compilers' builtin functions to supply this information to the compiler, so it can optimize the branching more efficiently. E.g., in case of GCC, that's __builtin_expect function. It's somewhat more handy to use when you wrap it into such likely and unlikely macros (like in the Linux kernel):

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)

Upvotes: 0

kassak
kassak

Reputation: 4184

To compute the angle you just need to call atan2(v1.s_cross(v2), v1.dot(v2)) for the two-dimensional case. Where s_cross is the scalar analogue of cross production (signed area of parallelogram).

For the two-dimensional case that would be wedge production.

For the three-dimensional case you need to define the clockwise rotation, because from one side of plane clockwise is one direction, from other side of plane is another direction =)

This is the counterclockwise angle, and the clockwise angle is just opposite.

Upvotes: 5

Nickolay Olshevsky
Nickolay Olshevsky

Reputation: 14160

The scalar (dot) product of two vectors lets you get the cosine of the angle between them.

To get the 'direction' of the angle, you should also calculate the cross product. It will let you check (via the z coordinate) of the angle is clockwise or not (i.e., should you extract it from 360 degrees or not).

Upvotes: 2

Related Questions