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))
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.
The direction of the cross product may be of interest, this flips at 180 degrees, and may be the equivalent of what happens in the 2d version with the sign. But agree with Akien, I think you need some kind of frame of reference for the chirality.
Isn't (a - b).angle() providing that signed angle?
Oh my bad I confused with 2D. Yeah I think there isn't a way to get a signed angle then, you need a 2D reference frame to decide what's negative and positive.
Isn't
(a - b).angle()providing that signed angle?
That method doesn't seem to exist for Vector3.
Also, is there a reason why the function angle_to(), for Vector3 is defined in vector3.h and not vector3.cpp but for Vector2 it is defined in vector2.cpp and not vector2.h. Shouldn't it be consistent?
Also, just a reminder: slerp() has a normalization check for Vector2 but not for Vector3.
Another note but not that significant: Sometimes the function parameters are named like p_vector2, p_other but other times it's p_b in Vector2 randomly.
It is more consistent in Vector3, where it is consistently p_b or p_v excluding cases that have very clear parameter names e.g, p_scalar, p_delta, p_phi, etc.
Just check the difference in parameter names for angle_to() in Vector2 and Vector3.
Also, is there a reason why the function angle_to(), for Vector3 is defined in vector3.h and not vector3.cpp but for Vector2 it is defined in vector2.cpp and not vector2.h.
For this kind of things it's usually decided by if the method should be inlined or not, it doesnt have a relation to "in Vector2 it's that way so Vector3 should". Different dimension, different problem. (or could as well just be random).
Also, is there a reason why the function angle_to(), for Vector3 is defined in vector3.h and not vector3.cpp but for Vector2 it is defined in vector2.cpp and not vector2.h. Shouldn't it be consistent?
Agree with Zylann, inlining is a key factor for me (although with whole program optimization modern compilers can inline from cpp, it makes it nice and easy for them if the function definition is in the header). Also whether the function itself calls functions which would require including more headers to the .h file, which can slow down compilation.
Large functions also tend to be more candidates for going in the cpp, because the relative cost of the function call versus the code within will be relatively small. And the compiler would probably choose not to inline them anyway.
Of course also every time you edit a function in a header file all the dependencies need to recompile, so iteration time can be slower.
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.
It is possible to make a
signed_angle_to(other, up = Vector3.UP)function, which callsangle_to, and then flips the sign ifthis.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).
If a game is working with 3D vectors within a known 2D plane, I would imagine that it wouldn't be too complicated to convert the vectors to Vector2 before performing the angle calculation.
@Zylann On the topic of inlining, it seems strange that the smaller functions (for Vector2) are in the cpp, while the larger functions (for Vector3) are in the header.
@lawnjelly Or simply have a function that returns axis angle between the two vectors
Returning an axis-angle would be tricky without Vector4. It could be done in C# via out parameters.
Returning an axis-angle would be tricky without Vector4. It could be done in C# via out parameters.
@aaronfranke it can be returned as quaternion.
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 !
Most helpful comment
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 !