Rotations sticks at 90deg

I’m not sure what is going on. I’ve searched around and found others with similar issues and their solution was to abandon c++ for blueprints. Hoping someone can help me solve this. I have a door that should rotate out of the way, in the code I am applying a rotator and a transform in a way that the distance traveled by the door is matched by a chord angle of 180deg. The code can also adjust the speed at which the door rotates out of the way. My first attempt was to use direct rotation and transforms applied to the door’s AActor object, Then I tried turning the rotation into a quaternion and then applying it. Both methods have the same result. The log output shows the Pitch angle correctly going from 0 to 90deg but then locks and jitters between 90 and 89.699326. The Roll and Yaw also seems to have a .000100 - .000010 jitter but is un-noticeable. Interestingly, once the pitch hits 90deg the roll will bounce between 90 and -89 while the yaw will bounce back and forth between 0.000100 and -179.99054. This almost looks like a gimble lock issue but that should not happen in quat space.

Video of issue: https://vimeo.com/168490381

Code using Quat:


void UDoorRotator::RotateOpenByQuat() {
	LogDoorLocation();
	if (!GetIsDoorOpen()) {

		AActor* Door = GetOwner();

		FVector DoorLocation = Door->GetActorLocation();
		FQuat DoorQuatRotation = Door->GetActorQuat();
		FRotator DoorRotation = Door->GetActorRotation();

		if (GetDoorTravel() < DoorMaxRange) {
			
			float DoorMaxRotation = 180.0f; // door will rotate 180 deg, used to get a delta rotation
			float TimeToOpen = 5.0f;

			float CurrentTime = GetWorld()->GetTimeSeconds();
			float CurrentTick = GetWorld()->GetDeltaSeconds();

			float CurrentPitch = Door->GetTransform().Rotator().Pitch;
			float CurrentX = Door->GetTransform().GetLocation().X;

			float DeltaRotation = (CurrentTick * (DoorMaxRotation / TimeToOpen)); //ammount to rotate pitch
			float DeltaX = (CurrentTick * (DoorMaxRange / TimeToOpen)); //ammount to move door

			SetDoorTravel(GetDoorTravel() + DeltaX);

			DoorLocation = FVector(DoorLocation.X - DeltaX, DoorLocation.Y, DoorLocation.Z); //create new location vector
			DoorQuatRotation = FRotator(DoorRotation.Pitch + DeltaRotation, DoorRotation.Yaw, DoorRotation.Roll).Quaternion(); // create quaternion from rotation

			Door->SetActorLocationAndRotation(DoorLocation, DoorQuatRotation); //sets new location
		}
		else { SetIsDoorOpen(true); }
	}
	else { return; }
	
}


Code not using Quat


void UDoorRotator::RotateOpen() {
	AActor* Door = GetOwner();
	
	
	bool CurrentDoorState = GetIsDoorOpen();
	if (!CurrentDoorState) {
		
		LogDoorLocation();

		float CurrentPitch = Door->GetTransform().Rotator().Pitch;
		float CurrentRoll = Door->GetTransform().Rotator().Roll;
		float CurrentYaw = Door->GetTransform().Rotator().Yaw;
		float CurrentX = Door->GetTransform().GetLocation().X;
		float CurrentY = Door->GetTransform().GetLocation().Y;
		float CurrentZ = Door->GetTransform().GetLocation().Z;

		float MaxRotation =180.0f;
		
		float TimeToOpen = 5.0f;
		float DoorRange = 0.0f;
		
		if (GetDoorClosedPosition() < GetDoorOpenPosition()) {
			float DoorRange = GetDoorClosedPosition() + GetDoorOpenPosition();
		}
		else {
			float DoorRange = GetDoorClosedPosition() - GetDoorOpenPosition();
		}
		
		if  (CurrentX > 3048.0f){
			float CurrentTime = GetWorld()->GetTimeSeconds();
			float CurrentTPS = GetWorld()->GetDeltaSeconds();
			float CurrentPitch = Door->GetTransform().Rotator().Pitch;
			float CurrentX = Door->GetTransform().GetLocation().X;
			float DeltaRotation = (CurrentTPS * (MaxRotation / TimeToOpen));
			float DeltaX = (CurrentTPS * (300.0 / TimeToOpen));
			
			FRotator NewRotation = FRotator(CurrentPitch + DeltaRotation, CurrentYaw, CurrentRoll);
			FVector NewTranslation = FVector(CurrentX - DeltaX, CurrentY, CurrentZ);
			
			Door->SetActorRotation(NewRotation);
			Door->SetActorLocation(NewTranslation);
			LogDoorLocation();
		} 
		else {
			SetIsDoorOpen(true);
		}
	}
}


The code that says “Using Quat” technically isn’t using a Quaternion to determine the rotation, you’re just creating a Quat after you’ve applied the rotation change to a rotator. There’s a big difference between using the Quat as a function argument and actually using it to do the math operation.

I would do it the following way using Matrices. Get the up-vector of your hinge (the axis you want the door to rotate on), rotate the front vector of the door around it, then get a rotation back from the vector. This will avoid gimbal lock completely. The following is peusdo-code but you kind of get it. I haven’t tested this btw, and there’s probably a quicker way - I just can’t think in Quats atm…



// Amount we want to rotate the door.
const float RotationAngleDegrees = DeltaRot * GetWorld()->GetDeltaSeconds();

// Axis we want to rotate the door on.
const FMatrix HingeUpMatrix = FRotationMatrix::MakeFromZ(GetHingeUpVector());

// Front vector of the door, rotated around the axis
const FVector NewDoorFrontVector = FVector::RotateAngleAxis(RotationAngleDegrees, HingeUpMatrix);

// Transform the vector to a rotator (or a quaternion, whatever you want). Use XZ to enforce orthoganility
const FRotator DoorRotation = FRotationMatrix::MakeFromXZ(NewDoorFrontVector, GetHingeUpVector()).Rotator();

// Apply Rotation


Your correct that it wasn’t true quaternion math… really looking back all I did was exactly what the engine does automatically by converting the Euler angles to a Quant. Still, it’s interesting how even once converted the quants end up in a gimble lock situation.

Your solution looks like it should work. I thought about rotating about an axis but wasn’t sure how to accomplish it. Your road map should get me there. Thx. I’ll post my code here for others if it works.