godot: Vector3::angle_to() does not return a signed angle

Godot version: 3.1.1

OS/device including version: Fedora 29

Issue description: Unlike Vector2.angle_to(), the current implementation of Vector3.angle_to() always returns a positive value regardless of which direction the resulting angle is meant to indicate.

Steps to reproduce: Execute the following gdscript and note that both statements return the same value (positive pi/2):

    print(Vector3(-1, 0, 0).angle_to(Vector3.FORWARD))
    print(Vector3(1, 0, 0).angle_to(Vector3.FORWARD))

For comparison, the following two statements return different values (positive pi/2 and -pi/2):

    print(Vector2(-1, 0).angle_to(Vector2.UP))
    print(Vector2(1, 0).angle_to(Vector2.UP))

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (14 by maintainers)

Most upvoted comments

It is possible to make a signed_angle_to(other, up = Vector3.UP) function, which calls angle_to, and then flips the sign if this.cross(other).dot(up) < 0.

As a reference, unity3D has static Vector3.SignedAngle(Vector3 from, Vector3 to, Vector3 axis). Indeed quite useful (e.g. to sort vertices clockwise). Worth adding to Godot IMO !

@Oyusi Sorry I missed your comment. I had notifications turned off. I made a PR for 3.x version. Hopefully it will be cherry-picked into 3.4 🚀

In version 3.3.2.stable the added method does not exist, as far as I can tell. It seems to only have been added to the 4.x master branch. Can it be added to 3.x? Otherwise this issue is still present there.

This is not a bug. Vector3::angle_to will “Returns the minimum angle to the given vector.” (from the docs) This can be used to know if an object is facing another object and etc. It is not for getting an angle to rotate an object. In 3d we have 3 axis of rotation (pitch, yaw, roll). So even if this function was returning a signed value it will had turned useless as we do not know the axis. You may say that your objects are using only one of the rotation axis and by this you know witch one you want to rotate but if this is true, you can just use 2d math to find the angle.

It is possible to make a signed_angle_to(other, up = Vector3.UP) function, which calls angle_to, and then flips the sign if this.cross(other).dot(up) < 0.

Or simply have a function that returns axis angle between the two vectors (if there isn’t one already), as this is generally a useful function anyway. That way the calling code can do a cheaper check according to which ‘up’ vector is being used (just test the sign of e.g. y in the axis rather than dot product).

It is possible to make a signed_angle_to(other, up = Vector3.UP) function, which calls angle_to, and then flips the sign if this.cross(other).dot(up) < 0.

I’m not sure this is a bug, nor that it can be fixed. I did some research on the topic and the formula we use is:

real_t Vector3::angle_to(const Vector3 &p_b) const {
        return Math::atan2(cross(p_b).length(), dot(p_b));
}

See https://stackoverflow.com/a/10145056 What we’d need to get the signed angle would be the “signed norm” |a x b|, i.e. |a|*|b|*sin(theta), but that’s not what Vector3::length() gives us, since it’s the absolute norm.

One could probably try to find out the sign after getting the absolute angle by doing some extra computation, but we’d still have to define what is the reference place for the signedness (TIL this is called “chirality”, see below).

From https://stackoverflow.com/a/10145056:

If it were in 2 dimensions, it would be anti-commutative. However, in 3 dimensions, it is (and should be) commutative – because, in 3 dimensions, you need a third vector to determine chirality. Without a third vector to discriminate between positive and negative angles for the first two, you have no geometric reason to prefer one direction over the other.