Small Rotation Changes Being Ignored (Precision Error?)

I am working with a camera with very small FOV values to simulate scoped shots.

I have implemented a system to simulate aim sway that comes from the player’s breathing, where the aim of the player will move around a bit over time when scoped, based on some curves that add to the controller’s pitch and yaw every frame.

This is an example of what I use for the yaw part of this process (pitch is identical):

float elapsed = GetWorld()->GetTimeSeconds() - ScopedInStartTime;
      float minTime = 0.0f;
      float maxTime = 0.0f;

      YawCurve->GetTimeRange(minTime, maxTime);

      float sampleTime = FMath::Fmod(elapsed, maxTime - minTime);
      float sampledYaw = YawCurve->GetFloatValue(sampleTime);

      float yawToAdd = sampledYaw - LastYawVal;

      LastYawVal= sampledYaw;

This works well when we are adding relatively large* angles each frame (in the range of 0.01f). However, as soon as the curve’s gradient approaches zero, there is a range of very small values for yawToAdd that just seem to stop adding to the rotation, so slow interpolation of small values breaks and the movement is abruptly stopped.

At first I thought it might be floating point imprecision, as at those points we are adding a number that is usually in the hundreds (-180.0f to 180.0f), with one that is sometimes as small as 0.0005f. However, when inspecting and debugging around the function where the RotationInput rotator is added (APlayerCameraManager::ProcessViewRotation), I can verify that even those small values get added correctly, so I should still see the camera movement at those moments, but no change is apparent in the game.

Am I missing something here? is there a better way to add small rotations? Does the camera do some sort of check before updating its matrices and small deltas are ignored? Any help would be greatly appreciated

* I am using a 14x zoom factor to test, so even those deltas for aim rotation matter, as the breaks are very apparent, 0.01f each frame means around 3cm of movement every frame if we are looking at something that is 170m away.

I’ll try and get back to you, but that seems promising. (it’s Jaime, (formerly) from PT btw, such an amazing coincidence! :smiley: )

That worked amazingly well, thank you very much!

If you follow AddYawControllerViewInput you’ll come to Controller.cpp which contains:

void AController::SetControlRotation(const FRotator& NewRotation)
	if (NewRotation.ContainsNaN())
		logOrEnsureNanError(TEXT("AController::SetControlRotation about to apply NaN-containing rotation! (%s)"), *NewRotation.ToString());
	if (!ControlRotation.Equals(NewRotation, 1e-3f))
		ControlRotation = NewRotation;

		if (RootComponent && RootComponent->bAbsoluteRotation)
		//UE_LOG(LogPlayerController, Log, TEXT("Skipping SetControlRotation for %s (Pawn %s)"), *GetNameSafe(this), *GetNameSafe(GetPawn()));

Here you can see that a check is performed agains the current rotation (ControlRotation) and the new desired rotation (NewRotation). The check is to see if the rotations are equal within a tollerance of 1e-3f and if they are, then the new rotation is discarded. Are your rotation changes small enough to get discarded at this point?

If the above is the problem then the fix is pretty straight forward. AController::SetControlRotation is virtual, so you’ll need a custom controller class (if you don’t already have one) and simply override this equality tollerance check. You could of course modify the engine source directly, but I woudn’t in this case.

I’d also track your game state so your new custom code is only called when you’re in sniping mode, otherwise it falls back to the existing SetControlRotation which has this tollerance check to avoid twitchy movement.

Ha, small world! I thought the username looked familiar but didn’t make the connection. Hope that works out for you and you’re enjoying whatever you’re working on now :slight_smile: