Deriving angles from 0 to 360 from Dot Product.

Is there a clean way to extract an angle with the range of 0 to 360, or -180 to 180 from a dot product of 2 normalized vectors, using trig identities perhaps?


No, the best you can do is 0 to 180. (inverse cos of the dot product)

My imaginary friend above is right. But there is a way to get what you want, given some more information and/or assumptions.

The acos() of the dot product gives you the angle. But if you take the cross product of the two vectors, you get a third vector (perpendicular to the two) that, with one more step, can give you that winding directionality you seek.

[edit: fixed for asin range, and the dot is better than taking the asin of the cross length anyway.]

So in simplistic C++, given two vectors, A & B:

d = Dot(A,B);
C = Cross(A,B);
angle = acos(d);
dir = Dot(C,Vref);
if (dir is less than 0) angle = -angle;

This gives a range of -180 to 180, which is what you want. And what we’re doing is testing the perp (cross) vector against some known normal (Vref) and seeing if it points with it or away from it. The trick is to pick a good Vref, such as the plane normal for multi-segment curve.

+++The key thing is, you absolutely can’t use the plane normal derived from crossing A and B because that won’t tell you anything new.

If, for example, you’re testing the angles along some piecewise-linear curve, you’d want to use the overall plane normal (if there is one) from the whole curve. If your curve winds in 3D, it’s not so simple, but you could maybe assume the cross product of the previous two vectors as your reference and at least know how your current angle is turning +/- relative to that.

If you know the vectors are drawn in the plane of the screen, you can use the camera/eye vector as a reference normal.

Not every application of this method will have a good reference normal to test against, so think carefully about what you want to compare this result to – if you pick an arbitrary vector, the sign could be meaningless. But you get 90-180 degrees of “not-quite-right” before the result is wrong.

asin(x), input (-1,+1), output (-pi/2,+pi/2).

Asin outputs -90 to 90, not -180 to 180.

Fixed above. Thanks.

That’s another reason why the acos(dot) is better than the asin(cross.len) method, which needs some rotation to get the quardants right, plus requires a n extra sqrt.

I hope it’s now easier to see why the extra cross product gives you the full -180 to +180 range. It lets you flip the normal acos range around depending on which direction you wind.