godot: slerp between two normalized colinear vectors return not normalized error
Godot version
3.3.4
System information
Pop!_OS, Xbox Series Gamepad
Issue description
I was trying to gamepad input for my game and I’m stuck on this weird error.
Slerp method complain that vectors are not normalized, but when I use .is_normalized()
method I get True
for both vectors.
Also error only show up when I use vertical axis at the beginning and disappear after after moving joystick to the side.
Here is the code:
extends KinematicBody
var l_joystick_input: Vector3 = Vector3.FORWARD
var dir: Vector3 = Vector3.FORWARD
func _physics_process(delta):
l_joystick_input.x = Input.get_action_strength("P1_L_joystick_right") - Input.get_action_strength("P1_L_joystick_left")
l_joystick_input.z = Input.get_action_strength("P1_L_joystick_down") - Input.get_action_strength("P1_L_joystick_up")
l_joystick_input = l_joystick_input.normalized()
if l_joystick_input.length() > 0.8:
print("is dir vector normalized?: " + str(dir.is_normalized()))
print("is l_joystick_input vector normalized?: " + str(l_joystick_input.is_normalized()))
dir = dir.normalized().slerp(l_joystick_input.normalized(), 0.1)
$TankTurret.look_at(global_transform.origin + dir * 10, Vector3.UP)
And here is an error.
Steps to reproduce
- Download the project
- move left joystick on your xbox gamepad
Minimal reproduction project
About this issue
- Original URL
- State: open
- Created 3 years ago
- Comments: 18 (16 by maintainers)
I confirm
Vector3::slerp()
isn’t implemented properly: https://github.com/godotengine/godot/blob/468b987aa38b21b55c1cd8a8d4c03b8e1b2a1373/core/math/vector3.h#L217-L220This issue specifically seems to be caused by not handling collinear vectors properly (in such cases their cross product is a zero vector (which can’t be normalized)):
I would just add something like this in all relevant methods:
@JestemStefan It won’t work for 180 degrees.
@lawnjelly Only because of the simpler calculations or is there some other reason? And in case of lerping I think normalizing would be needed no matter what threshold is choosen for deciding whether we lerp or no. Imagine that two input unit vectors are almost equal and their lengths equal the smallest possible length satysfying
is_normalized()
check. Then lerping between two such vectors (using0.0 < weight < 1.0
) would likely output a vector with length not satysfyingis_normalized()
check.We’d need to pick any valid axis of rotation, but not a random one. Results of slerp for the same arguments should be deterministic.
angle_to
is fine, it’s calculating the same cross product but it uses it differently. Calculating cross product of collinear vectors is fine on its own, the problem is trying to use its result (a zero vector, even ifnormalized()
) as an axis of rotation inslerp
.Gave it a brief look and it seems fine. It’s much simpler since there’s only one plane to deal with and thus the same axis of rotation can be used no matter what.
About picking axis of rotation in 180 degrees case: here are some nice comments on the subject. According to this comment it’s impossible to pick such axis without considering different cases (branching is unavoidable). According to this comment we could for example use something like this:
I’m also not sure about using
PI
inreturn from.rotated(cross, PI * weight)
. When callingslerp(a, b, 1.0)
you’d expect it to returnb
. So I wonder if usingPI
instead of the result ofa.angle_to(b)
in there could result in less expectable results (more different fromb
) in some cases. However, if we’d use the result ofa.angle_to(b)
then it would probably matter whether we picksome_axis_of_rotation
or-some_axis_of_rotation
as we would want to rotate in the direction where the angle is sligthly less than 180 degrees, not slightly bigger. So if we’d pick wrong axis an error would be even bigger. So yeah, pickingPI
is probably just fine (just thinking out loud).Yeah, I’ve searched a little too and most implementations I found seems similar to that one (for example libGDX).
Yeah, not handling such case is also a viable solution. Anyway, it will need to be documented (whether it won’t be handled or the axis of rotation will be chosen in an arbitrarily choosen way).
For vectors pointing in the same direction, I tried adding these test cases and it passes:
For vectors pointing in opposite directions, I don’t know what you expect to happen. If we interpolate over the surface of a sphere, there is no preferred direction if the vectors are pointing in exactly opposite directions. I guess the best we can do is add a meaningful error message for debug builds?
Currently errors are shown for collinear vectors but slerping vectors with the same direction is fine, simply no rotation needs to be done.
Meaning the 0 degrees case (collinear vectors with the same direction) currently errors out but according to the behavior introduced in #55877 it should just lerp the vectors.
And yeah, not supporting the 180 degrees case (collinear vectors with the opposite direction) is fine. But besides documenting it maybe it should also result in a meaningful error, not like the current one? 🤔