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.