How to avoid gimbal lock (c++)?

I created an orbit camera (sometimes called turntable camera; similar to the one with the “use UE3 orbit controls” setting in a static mesh view).

I attached the camera to a USpringArmComponent with a TargetArmLength set to 400.
In the tick function, I rotate the arm with this simple method:

        // Simple, clamped version
        FRotator Rotation = CameraSpringArm->GetComponentRotation();
        Rotation.Yaw += CameraInput.X * CameraRotationSpeed;
        Rotation.Pitch = FMath::Clamp(Rotation.Pitch + CameraInput.Y * CameraRotationSpeed, -85.0f, 85.0f);
        CameraSpringArm->SetRelativeRotation(Rotation);

I had to clamp the pitch to hide the gimbal lock problem. But this prevent users to rotate completely around objects.
I don’t understand why the Z rotation (the yaw) occurs on the world z axis ( FVector::UpVector which is (0, 0, 1)) and not on the local z axis. It turns out that this is exactly what I want.

I tried to solve this gimbal lock problem with this other method:

        // Taken from https://answers.unrealengine.com/questions/232923/how-can-i-avoid-gimbal-lock-in-code.html
        FRotator RotationDelta(CameraInput.Y * CameraRotationSpeed, CameraInput.X * CameraRotationSpeed, 0.f);
        FTransform NewTransform = CameraSpringArm->GetComponentTransform();
        NewTransform.ConcatenateRotation(RotationDelta.Quaternion());
        NewTransform.NormalizeRotation();
        CameraSpringArm->SetWorldTransform(NewTransform);

It works, but this time, the Z rotation (yaw) occurs on the local Z axis. How can I change it to rotate around the world Z axis, and the local Y axis, without gimbal lock?

I tried this hybrid solution, but the gimbal lock is still there:

        // Hybrid
        FRotator RotationDelta(CameraInput.Y * CameraRotationSpeed, 0.f, 0.f);
        FTransform Transform = CameraSpringArm->GetComponentTransform();
        FRotator Rotation = CameraSpringArm->GetComponentRotation();
        Rotation.Yaw += CameraInput.X * CameraRotationSpeed;
        Transform.SetRotation(Rotation.Quaternion());
        Transform.ConcatenateRotation(RotationDelta.Quaternion());
        Transform.NormalizeRotation();
        CameraSpringArm->SetWorldTransform(Transform);

I did solve this problem (a long time ago) in OpenGL using quaternions, so I tried this version:

        // Quaternion
        FRotator Rotator = CameraSpringArm->GetComponentRotation();
        FQuat Quaternion = Rotator.Quaternion();
        // Rotate around the world Z axis:
        Quaternion *= FQuat(FVector::UpVector, FMath::DegreesToRadians(CameraInput.X * CameraRotationSpeed));
        // Rotate around the local Y axis:
        Quaternion *= FQuat(Rotation.RotateVector(FVector::RightVector), FMath::DegreesToRadians(CameraInput.Y * CameraRotationSpeed));
        CameraSpringArm->SetRelativeTransform(Quaternion);

But this does not work. I also tried this:

        // Quaternion + transform
        FTransform Transform = CameraSpringArm->GetComponentTransform();
        Transform.ConcatenateRotation(FQuat(FVector::UpVector, FMath::DegreesToRadians(CameraInput.X * CameraRotationSpeed)));
        Transform.ConcatenateRotation(FQuat(Rotation.RotateVector(FVector::RightVector), FMath::DegreesToRadians(CameraInput.Y * CameraRotationSpeed)));
        Transform.NormalizeRotation();
        CameraSpringArm->SetWorldTransform(Transform);

without success.

Thx! Why does the pitch seem incorrect?

You can use Add for Rotator and also use FMath::ClampAngle(…) instead of FMath::Clamp(…). This will prevent Gimbal Lock.

FRotator Rotation = CameraSpringArm->GetComponentRotation();
Rotation.Pitch = FMath::ClampAngle(Rotation.Pitch + CameraInput.Y * CameraRotationSpeed, -85.0f, 85.0f);
Rotation.Add(CameraInput.X * CameraRotationSpeed * DeltaSeconds, 0, 0);
CameraSpringArm->SetRelativeRotation(Rotation);

Btw pitch setup doesn’t seem correct for me. You can also may want to use DeltaTime in these equations.

Rotation.Add(CameraInput.X * CameraRotationSpeed * DeltaSeconds, 0, 0); should be replace with Rotation.Add(0, CameraInput.X * CameraRotationSpeed * DeltaSeconds, 0);. Then it has the same behavior as my for simple method. Thank you for mentioning DeltaSeconds.

Arthur, do you have lag on the spring arm? The lag implementation does not support full 6DoF and you’ll have to update it to use quaternions instead of rotators, as well.

Your hybrid solution should work.

// Hybrid
     FRotator RotationDelta(CameraInput.Y * CameraRotationSpeed, 0.f, 0.f);
     FTransform Transform = CameraSpringArm->GetComponentTransform();
     FRotator Rotation = CameraSpringArm->GetComponentRotation();
     Rotation.Yaw += CameraInput.X * CameraRotationSpeed;
     Transform.SetRotation(Rotation.Quaternion());
     Transform.ConcatenateRotation(RotationDelta.Quaternion());
     Transform.NormalizeRotation();
     CameraSpringArm->SetWorldTransform(Transform);

Yes I have a lag on the spring arm. This solution prevents gimbal lock, but the Z rotation occurs on the local Z axis, which is not handy at all. How can I have a rotation around the global Z axis (0,0,1)?

Ok, I realized that my hybrid solution works when I remove the camera lag!!! Thanks!! Can you put the hybrid solution instead in your answer? Then I’ll accept it :slight_smile:

Done! Glad you got it sorted!

FRotator Rotation = CameraSpringArm->GetComponentRotation(); should also be replace with FRotator Rotation = CameraSpringArm-RelativeRotation; Yes the code is nearly same. But this also worked for me. Maybe in this situation Quaternions are the right choice.

It’s annoying that I can’t use Quats in Blueprints in order to do a simple non-gimbal lock when using a timeline to rotate the Y axis, without going into code.

Also, hey Darcey :wink: