I'm making aerial vehicle controller, which can rotate 3 axis: X(Pitch), Y(Yaw), Z(Roll).

Rotating x(pitch) and y(yaw) axis works fine, however after I rotated z(Roll) axis, x/y rotation gave me weird result, it seems like it still rotating just like z axis didn't rotated(0).

First I thought that it was some kind of gimbal lock problem, but it wasn't, it still happens using quaternion.

Here's the video I demonstrate: https://www.youtube.com/watch?v=8cdBSxcmiWE&feature=youtu.be

Note that I tried all these and had same results:

# Using Euler
rotation.x += pitch

# Using Euler II

# Using Quaternion
var qRot = Quat(transform.basis)
var r_euler = qRot.get_euler()
var new_rot = Vector3(r_euler.x + pitch, r_euler.y, r_euler.z)
var new_basis = Basis(qRot)
var new_transform = Transform(new_basis.x.normalized(), new_basis.y.normalized(), new_basis.z.normalized(), transform.origin)

Is there a something that I missed? Any advice will very appreciate it.

  • 1
    \$\begingroup\$ Your "using quaternion" code does nothing with a quaternion except convert it to Euler angles and do the math on the Euler angle form. That's just Euler angles with extra steps. You obtain absolutely zero benefit from converting to & from a quaternion if you're still doing all your work on Euler angles. \$\endgroup\$
    – DMGregory
    Nov 3, 2019 at 17:30
  • \$\begingroup\$ @DMGregory Okay... Then how should I rotate quaternion with euler angle? \$\endgroup\$
    – modernator
    Nov 4, 2019 at 4:43

1 Answer 1


When rotating your object, make sure you apply rotations around the local axes, not the global ones. Even in your third case, it does look like you are encountering gimbal lock. Here's a demo:

extends Spatial

func _ready():
    $Ship1.angular_velocity = Vector3(1, 0, 0)
    $Ship2.angular_velocity = Vector3(1, 0, 0)
    yield(get_tree().create_timer(1.0), "timeout")

    $Ship1.angular_velocity = Vector3(0, 1, 0)
    $Ship2.angular_velocity = $Ship2.global_transform.basis.y.normalized()
    yield(get_tree().create_timer(1.0), "timeout")

    $Ship1.angular_velocity = Vector3(0, 0, 1)
    $Ship2.angular_velocity = $Ship2.global_transform.basis.z.normalized()
    yield(get_tree().create_timer(1.0), "timeout")

Ship1 is the one further from the camera. It is ralways rotated about the global axes. Note that when we get to roll (z rotation), it is not rotating about the local Z axis (a vector extending out of the nose of the ship).

Ship2 is rotated first around global X, then about local y (basis.y), then about local z (basis.z). When it gets to roll, it is rolling around the nose of the ship, which is likely the behavior the player expects.


  • \$\begingroup\$ Thanks! After change to rotating local axis now it works! \$\endgroup\$
    – modernator
    Nov 4, 2019 at 4:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .