Download

Yaw Rotation from 0 to 90

Hello and thank you for taking the time to look at my question.

I am working on a class that rotates an actor around the Yaw axis. My goal is to rotate the actor in certain degree increments over a variable amount of time. For now I would like the increments to be 90 degrees, for example: 0, 90, -180, -90 or 0, 90, 180, 270.

First I have a question about why in the Transform section of Details of an object, the Rotation value scales from 0 to 360 but once I am in Play mode and start Rotating things the scale seems to be -180 to 180?

As of right now I am able to continuously rotate my actor, but I’ve run in to issues stopping the rotation at the right float value. I am always off but some value < or > 1. What I am asking for is guidance on how to ensure my rotations stop at whole numbers and how to clamp my rotations between 0 and 360. If I am going about this all wrong, please let me know. I am new to Unreal so I have yet to even scratch the surface of all of the various functions available. If anyone could point me in the right direction that would be greatly appreciated. My alternate approach would be to create some animations for each rotation in the editor, and play each animation based on the current angle of the actor. I am thinking I will run into similar problems though.

I am aware I can accomplish my goal in BP quite easily but I am specifically trying to improve my unreal & c++ abilities.

Thank you once again for your time an consideration.


// Called every frame
void ACameraRotator::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    // if a player hits the input to rotate the camera right
    if (bRotateRightToggle == true && bRotateLeftToggle == false) 
    {
        RotateRight(DeltaTime);
    }

    if (bRotateLeftToggle == true && bRotateRightToggle == false) 
    {
        RotateLeft(DeltaTime);
    }
}

void ACameraRotator::RotateRight(float DeltaTime)
{
    // conditionals for 0, 90, -180, -90 would go here
    if (GetActorRotation().Yaw == 90.f) {
        bRotateRightToggle = false;
        return;
    }
    else 
    {
        FQuat QuatRotation = FQuat(FRotator(0.f, RotationSpeed * DeltaTime, 0.f));
        AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
    }
}

void ACameraRotator::RotateLeft(float DeltaTime)
{
    FQuat QuatRotation = FQuat(FRotator(0.f, -RotationSpeed * DeltaTime, 0.f));
    AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
}

As you are now aware, floats are, mathematically speaking, rough approximations. Comparing floats with operator == rarely works. Simply put, a value of 0.99999999 is technically not the same as 1.0f, but it’s often close enough for practical purposes. Thus, you should use a comparison based on some tolerance. You could do it yourself, but Unreal offers FMath::IsNearlyEqual() and FMath::IsNearlyZero(). There are also versions for vectors.

If you really must stop at whole numbers, you can use some tolerance as I noted above, then set the value to exactly the number you want, or you can round it downwards using one of the functions Unreal provides.

To clamp rotations between 0 and 360 you can wrap them every full turn. This process of ensuring values are within a defined interval is called “canonicalization”. To canonize angles between 0 <= x < 360, divide the degree value by 360, round it downwards, then multiply by 360. That gives you how many extra rotations you have. Remove that from the original value.

As an example, we know 725 is twice 360 plus 5, so we know it’s the same as 5 degrees. That is: 5 = 725.0f - (FMath::floor(725.0f / 360.0f) * 360.0f). This will wrap the values every full turn. Use x instead of “725.0f” and you have the formula.

The function floor() rounds downwards. You also have ceiling(), which rounds upwards.

Thank you DanteWingates!

I will give this a go. I really appreciate you taking the time to answer. Have a great day!

If you want to rotate between two angles in a set amount of time seems like FMath::Interp variants would be much easier? Right now you are doing sort of an “approach” algorithm (move, check if near target, repeat). With interpolation you will always get to the exact point in the correct amount of time.

Thank you @Shmoopy1701! I did end up getting this “working” using conditionals, but the interpolation approach sounds like it is a much better idea. I am so unfamiliar with what c++ programming with Unreal has to offer that I am having a hard time using it’s proper tools and libraries for the right problems.

I am going to refactor using FMath::RInterpConstantTo() - But here is the code I ended up with:


void ACameraRotator::RotateTo90(float DeltaTime, bool bRotateLeft)
{
    if (FMath::IsNearlyEqual(GetActorRotation().Yaw, 90.f, 0.5f)) {
        bRotateTo90 = false;
        return;
    }
    else
    {
        if (bRotateLeft) // left
        {
            FQuat QuatRotation = FQuat(FRotator(0.f, RotationSpeed * DeltaTime, 0.f));
            AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
        }
        else if (!bRotateLeft) // right
        {
            FQuat QuatRotation = FQuat(FRotator(0.f, -RotationSpeed * DeltaTime, 0.f));
            AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
        }
    }
}
void ACameraRotator::RotateToNegative90(float DeltaTime, bool bRotateLeft)
{
    if (FMath::IsNearlyEqual(GetActorRotation().Yaw, -90.f, 0.5f)) {
        bRotateToNegative90 = false;
        return;
    }
    else
    {
        if (bRotateLeft) // left
        {
            FQuat QuatRotation = FQuat(FRotator(0.f, RotationSpeed * DeltaTime, 0.f));
            AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
        }
        if (!bRotateLeft) // right
        {
            FQuat QuatRotation = FQuat(FRotator(0.f, -RotationSpeed * DeltaTime, 0.f));
            AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
        }
    }
}
void ACameraRotator::RotateToNegative180(float DeltaTime, bool bRotateLeft)
{
    if (FMath::IsNearlyEqual(GetActorRotation().Yaw, -180.f, 0.5f) || FMath::IsNearlyEqual(GetActorRotation().Yaw, 180.f, 0.5f)) {
        bRotateToNegative180 = false;
        return;
    }
    else
    {
        if (bRotateLeft) // left
        {
            FQuat QuatRotation = FQuat(FRotator(0.f, RotationSpeed * DeltaTime, 0.f));
            AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
        }
        if (!bRotateLeft) // right
        {
            FQuat QuatRotation = FQuat(FRotator(0.f, -RotationSpeed * DeltaTime, 0.f));
            AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
        }
    }
}
void ACameraRotator::RotateTo0(float DeltaTime, bool bRotateLeft)
{
    if (FMath::IsNearlyEqual(GetActorRotation().Yaw, 0.f, 0.5f)) {
        bRotateTo0 = false;
        return;
    }
    else
    {
        if (bRotateLeft) // left
        {
            FQuat QuatRotation = FQuat(FRotator(0.f, RotationSpeed * DeltaTime, 0.f));
            AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
        }
        if (!bRotateLeft) // right
        {
            FQuat QuatRotation = FQuat(FRotator(0.f, -RotationSpeed * DeltaTime, 0.f));
            AddActorLocalRotation(QuatRotation, false, 0, ETeleportType::None);
        }
    }
}
void ACameraRotator::OnToggleRotate(float DeltaTime)
{
    if (bRotateTo0)
    {
        if (GetActorRotation().Yaw <= 0.f)
        {
            RotateTo0(DeltaTime, true); // rotate left
        }
        if (GetActorRotation().Yaw >= 0.f)
        {
            RotateTo0(DeltaTime, false); // rotate right
        }

    }
    if (bRotateTo90)
    {
        if (GetActorRotation().Yaw >= -90.f && GetActorRotation().Yaw <= 90.f)
        {
            RotateTo90(DeltaTime, true); // rotate left
        }
        if (GetActorRotation().Yaw <= -90.f || GetActorRotation().Yaw >= 90.f)
        {
            RotateTo90(DeltaTime, false); // rotate right
        }
    }
    if (bRotateToNegative90)
    {
        if (GetActorRotation().Yaw >= 89.f || GetActorRotation().Yaw <= -90.f)
        {
            RotateToNegative90(DeltaTime, true); // rotate left
        }
        if (GetActorRotation().Yaw <= 0.f && GetActorRotation().Yaw >= -90.f)
        {
            RotateToNegative90(DeltaTime, false); // rotate right
        }
    }
    if (bRotateToNegative180)
    {
        if (GetActorRotation().Yaw >= 0.f)
        {
            RotateToNegative180(DeltaTime, true); // rotate left
        }
        if (GetActorRotation().Yaw <= 0.f)
        {
            RotateToNegative180(DeltaTime, false); // rotate right
        }
    }
}

Then I just call OnToggleRotate(DeltaTime) in Tick(). The goal is to expose each bool control in OnToggleRotate() to a UI Widget which can take mouse inputs to control how to rotate. Any and all feedback is greatly appreciated. Will report back once I get FMath::RInterpConstantTo() working. Thank you for your time, I really appreciate you taking the time to help me learn something.

Alright I got this all working again using FMath::RinterpConstantTo()! Thank you again for the guidance @Shmoopy1701, This is much more clean and resulted in far less code! For anyone interested, I’ve included my refactor below. I put everything in OnToggleRotate() which I run in Tick(). Rotations fire based on my bool toggles perfectly and always rotate in shortest direction to the target angle. Success!


void ACameraRotator::OnToggleRotate(float DeltaTime)
{
    if (bRotateTo0)
    {
        FRotator NewRotation = FMath::RInterpConstantTo(GetActorRotation(), FRotator(0.f, 0.f, 0.f), DeltaTime, RotationSpeed);
        SetActorRotation(NewRotation);

        if (GetActorRotation().Equals(FRotator(0.f, 0.f, 0.f), 0.1f))
        {
            bRotateTo0 = false;
        }
    }
    if (bRotateTo90)
    {
        FRotator NewRotation = FMath::RInterpConstantTo(GetActorRotation(), FRotator(0.f, 90.f, 0.f), DeltaTime, RotationSpeed);
        SetActorRotation(NewRotation);

        if (GetActorRotation().Equals(FRotator(0.f, 90.f, 0.f), 0.1f))
        {
            bRotateTo90 = false;
        }
    }
    if (bRotateToNegative90)
    {
        FRotator NewRotation = FMath::RInterpConstantTo(GetActorRotation(), FRotator(0.f, -90.f, 0.f), DeltaTime, RotationSpeed);
        SetActorRotation(NewRotation);

        if (GetActorRotation().Equals(FRotator(0.f, -90.f, 0.f), 0.1f))
        {
            bRotateToNegative90 = false;
        }
    }
    if (bRotateToNegative180)
    {
        FRotator NewRotation = FMath::RInterpConstantTo(GetActorRotation(), FRotator(0.f, -180.f, 0.f), DeltaTime, RotationSpeed);
        SetActorRotation(NewRotation);

        if (GetActorRotation().Equals(FRotator(0.f, -180.f, 0.f), 0.1f))
        {
            bRotateToNegative180 = false;
        }
    }
}

@QuaternionSlerp good work. That is nice and clean now. :cool:

Just going to do one last little update here on the final (hopefully? lol) code I ended up using. With the previous solution using bool triggers, I needed to take special care of managing those bools based on rotation state. After fiddling around a bit, I realized an enum would be much better. So here is the final code:

in CameraRotator.h


UENUM(BlueprintType)
enum ERotationEnum
{
    DontRotate UMETA(DisplayName = "Don't Rotate"),
    RotateTo0 UMETA(DisplayName = "Rotate To 0"),
    RotateTo90 UMETA(DisplayName = "Rotate To 90"),
    RotateToNegative180 UMETA(DisplayName = "Rotate To -180"),
    RotateToNegative90 UMETA(DisplayName = "Rotate To -90"),
};

// Inside my UCLASS()  
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CameraRotation)
TEnumAsByte<ERotationEnum> RotationEnum;

in CameraRotator.cpp - I threw this in a function I call in Tick(), but you can throw this whole block in Tick() if you’d prefer.


if (RotationEnum == ERotationEnum::RotateTo0)
    {
        FRotator NewRotation = FMath::RInterpConstantTo(GetActorRotation(), FRotator(0.f, 0.f, 0.f), DeltaTime, RotationSpeed);
        SetActorRotation(NewRotation);

        if (GetActorRotation().Equals(FRotator(0.f, 0.f, 0.f), 0.1f))
        {
            RotationEnum = ERotationEnum::DontRotate;
        }
    }
    if (RotationEnum == ERotationEnum::RotateTo90)
    {
        FRotator NewRotation = FMath::RInterpConstantTo(GetActorRotation(), FRotator(0.f, 90.f, 0.f), DeltaTime, RotationSpeed);
        SetActorRotation(NewRotation);

        if (GetActorRotation().Equals(FRotator(0.f, 90.f, 0.f), 0.1f))
        {
            RotationEnum = ERotationEnum::DontRotate;
        }
    }
    if (RotationEnum == ERotationEnum::RotateToNegative90)
    {
        FRotator NewRotation = FMath::RInterpConstantTo(GetActorRotation(), FRotator(0.f, -90.f, 0.f), DeltaTime, RotationSpeed);
        SetActorRotation(NewRotation);

        if (GetActorRotation().Equals(FRotator(0.f, -90.f, 0.f), 0.1f))
        {
            RotationEnum = ERotationEnum::DontRotate;
        }
    }
    if (RotationEnum == ERotationEnum::RotateToNegative180)
    {
        FRotator NewRotation = FMath::RInterpConstantTo(GetActorRotation(), FRotator(0.f, -180.f, 0.f), DeltaTime, RotationSpeed);
        SetActorRotation(NewRotation);

        if (GetActorRotation().Equals(FRotator(0.f, -180.f, 0.f), 0.1f))
        {
            RotationEnum = ERotationEnum::DontRotate;
        }
    }

Now I can fiddle around with my rotation state however I want without having to worry about messing up my bools and locking things up. I hope this helps someone! Thank you once again to all contributors!