13
votes

(In three dimensions) I'm looking for a way to compute the signed angle between two vectors, given no information other than those vectors. As answered in this question, it is simple enough to compute the signed angle given the normal of a plane to which the vectors are perpendicular. But I can find no way to do this without that value. It's obvious that the cross product of two vectors produces such a normal, but I've run into the following contradiction using the answer above:

signed_angle(x_dir, y_dir) == 90
signed_angle(y_dir, x_dir) == 90

where I would expect the second result to be negative. This is due to the fact that the cross product cross(x_dir, y_dir) is in the opposite direction of cross(y_dir, x_dir), given the following psuedocode with normalized input:

signed_angle(Va, Vb)
    magnitude = acos(dot(Va, Vb))
    axis = cross(Va, Vb)
    dir = dot(Vb, cross(axis, Va))
    if dir < 0 then
        magnitude = -magnitude
    endif
    return magnitude

I don't believe dir will ever be negative above.

I've seen the same problem with the suggested atan2 solution.

I'm looking for a way to make:

signed_angle(a, b) == -signed_angle(b, a)
4
The link in the above comment doesn't workephere

4 Answers

19
votes

The relevant mathematical formulas:

  dot_product(a,b) == length(a) * length(b) * cos(angle)
  length(cross_product(a,b)) == length(a) * length(b) * sin(angle)

For a robust angle between 3-D vectors, your actual computation should be:

  s = length(cross_product(a,b))
  c = dot_product(a,b)
  angle = atan2(s, c)

If you use acos(c) alone, you will get severe precision problems for cases when the angle is small. Computing s and using atan2() gives you a robust result for all possible cases.

Since s is always nonnegative, the resulting angle will range from 0 to pi. There will always be an equivalent negative angle (angle - 2*pi), but there is no geometric reason to prefer it.

3
votes

Signed angle between two vectors without a reference plane

angle = acos(dotproduct(normalized(a), normalized(b)));

signed_angle(a, b) == -signed_angle(b, a)

I think that's impossible without some kind of reference vector.

2
votes

Thanks all. After reviewing the comments here and looking back at what I was trying to do, I realized that I can accomplish what I need to do with the given, standard formula for a signed angle. I just got hung up in the unit test for my signed angle function.

For reference, I'm feeding the resulting angle back into a rotate function. I had failed to account for the fact that this will naturally use the same axis as in signed_angle (the cross product of input vectors), and the correct direction of rotation will follow from which ever direction that axis is facing.

More simply put, both of these should just "do the right thing" and rotate in different directions:

rotate(cross(Va, Vb), signed_angle(Va, Vb), point)
rotate(cross(Vb, Va), signed_angle(Vb, Va), point)

Where the first argument is the axis of rotation and second is the amount to rotate.

-2
votes

If all you want is a consistent result, then any arbitrary way of choosing between a × b and b × a for your normal will do. Perhaps pick the one that's lexicographically smaller?

(But you might want to explain what problem you are actually trying to solve: maybe there's a solution that doesn't involve computing a consistent signed angle between arbitrary 3-vectors.)