Controlling an object's Roll/Pitch/Yaw

I am attempting to control an object’s Roll/Pitch/Yaw via GUI controls. The GUI controls just out put an angle between -180 and 180 degrees. I have a GUI_Controller script that collects the rotations from each of the 3 GUI components and then rotates the selected GameObject.

I have come up with two ways to do this, but each has a drawback that I have been unable to think of a solution for.

First, some variable definitions:
rollControl.RotationAngle is the Roll amount in degrees of type float
pitchControl.RotationAngle is the Pitch amount in degrees of type float
yawControl.RotationAngle is the Yaw amount in degrees of type float
SelectedShip is the GameObject to be rotated of type GameObject
rollAngle, pitchAngle, yawAngle all hold their respective angles between Update cycles so I only call the rotation logic as needed.

The following two code segments are in my void Update() function (only one uncommented at a time)

//This code's issue: Cannot adjust Roll if Pitch or Yaw have an angel. Cannot adjust Pitch if Yaw has an angle.
if (rollControl.RotationAngle != rollAngle || pitchControl.RotationAngle != pitchAngle || yawControl.RotationAngle != yawAngle)
        {
           SelectedShip.transform.rotation = Quaternion.identity;

            SelectedShip.transform.RotateAround(Vector3.zero, SelectedShip.transform.forward, -rollControl.RotationAngle);
            rollAngle = rollControl.RotationAngle;

            SelectedShip.transform.RotateAround(Vector3.zero, SelectedShip.transform.right, -pitchControl.RotationAngle);
            pitchAngle = pitchControl.RotationAngle;

            SelectedShip.transform.RotateAround(Vector3.zero, SelectedShip.transform.up, yawControl.RotationAngle);
            yawAngle = yawControl.RotationAngle;
        }
//This code's issue: Cannot return to zero rotation properply. Must return to zero by undoing the rotations in the order they were done
        if (rollControl.RotationAngle != rollAngle)
        {
            SelectedShip.transform.RotateAround(Vector3.zero, SelectedShip.transform.forward, -rollControl.RotationAngle + rollAngle);
            rollAngle = rollControl.RotationAngle;
        }
        if(pitchControl.RotationAngle != pitchAngle)
        {
            SelectedShip.transform.RotateAround(Vector3.zero, SelectedShip.transform.right, -pitchControl.RotationAngle + pitchAngle);
            pitchAngle = pitchControl.RotationAngle;
        }
        if(yawControl.RotationAngle != yawAngle)
        {
            SelectedShip.transform.RotateAround(Vector3.zero, SelectedShip.transform.up, yawControl.RotationAngle - yawAngle);
            yawAngle = yawControl.RotationAngle;
        }

I would have thought that the first segment would solve my issue. I’m reapplying the rotation values in Unity’s Z-X-Y order every cycle. So even if I am only changing the roll amount the roll/pitch/yaw should still be getting reapplied to the temporary GameObject, which starts with no rotation. Yet, after I adjust the object’s Yaw the roll is being applied to the World Z axis instead of the Object Z axis.

I can supply the project file if needed.

Edit: Removed the temp object from the first code segment. Functionality did not change but was a performance improvement recommended by Polymorphik.

I did not test your code, but from what I know of transform.RotateAround continuously rotates…if I am not mistaken. I suggest you use Quaternion.Euler. All you need to do is pass it a Vector3 of the angles you want, and it will return a Quaternion reflecting those angles. Also, creating a GameObject and then destroying just to rotate something is really bad…it can cost you in performance.

So you could do something like:

    protected virtual void RotateSelected(Vector3 rotationInput, GameObject selectedObject) {
        selectedObject.transform.rotation = Quaternion.Euler(rotationInput);
    }

Hope this helps.

Yes, it does. This is why I have the IF statements. This why I reset the rotation to Identity then apply the RotateAround’s only when the angle has been updated.

The issue with Quaternion.Euler is that it’s rotations are around the world axis and not the object’s axis. If you rotate an object around the world Z axis (as in a roll) then the direction of the object’s X and Y axis change relative to the world X and Y axis. The RotateAround function appears to rotate the object around it’s own internal axis, thus letting me get proper Roll/Pitch/Yaw.

Good point. I just checked it and modified the code and dropped the temp object. It works exactly the same (problems and all). I’ll update my OP with the new code.

1 Like

Ah I see, well when you talk about world vs local this really depends on the rotation you want to do. As of right now, what is going on in my head is that what you want to do is set a fixed rotation value for all 3 axis via some GUI then have the object reflect those values. If this is the case, unless you care about rotation relative to something else world rotation is fine. But, if you have the object parented to something that is also rotation, IE a platform then swap the transform.rotation for transform.localrotation this will be relative to what its parented to. Now option 3 is you want the object to rate based on its rotation IE if the ship is pointing down 45 degree pitch will result in +45 on the X since it was facing down. If the 3 option is what you want, then you need to change the way the GUI works, if the slider snaps back after you let go then it should be fine, if its a series of knobs then it will always rotate since the object coordinate system relative to itself is based on its rotation. So if you update its rotation, its new forward rotation will also change there for pitching 45 degrees will result in a spin and when set to 0 degrees the ship will be pitched at whatever the last value was this is the continous effect I was pointing out before. So clear up what exactly your GUI is supposed to do as far as rotating the ship and I can give you a way of doing this.

Ya, there might be a confusion on “local rotation” on my end. When I say “local rotation” I talk about the object’s internal rotation given that an object has a front, top, and right static directions (as in the nose of the airplane is always forward, the landing gear is always down, etc). So, if I were to Roll the airplane by 90 degrees to the left, the plane’s “up down” are now equal to the world’s “left right” and the plane’s “left right” is now equal to the world’s “down up”.

I think this is what I am doing. In the screenshot below you can see the UI in the game view and the “Ship” that has been rotated. The goal is to be able to change the Roll/Pitch/Yaw independently and have the model update it’s rotation. So having a Roll/Pitch/Yaw of -90/90/90 would cause the ship to point strait up on the Y axis which is the same as just changing the Pitch to 90.

I have exported everything to a UnityPackage and uploaded it to my DropBox. Maybe it will make more sense if you take a look at it.
https://dl.dropboxusercontent.com/u/30067887/Unity_ShipRotation.unitypackage

Maybe I will have some more ideas after a night’s sleep as well. Thanks for the help.

2319980–156409–Unity_ShipRotation.unitypackage (61.5 KB)

So, doing some more testing today (and trying stupid things I did not think would work but tried them anyways just to make sure they dont work) and there is one central thing that I do not understand at all.

With the first code segment I am resting the object’s rotation back to Identity before I apply any rotations. And because with the UI I can only adjust one rotation at a time, so only one rotation is being changed at a time. In the script, all rotations are being applied in order (Roll, then Pitch, then Yaw) every time there is a change in one of them.

So, I start with R/P/Y of 0/0/0. Then I change say Pitch to 30 degrees (0/30/0), the code resets rotation to Identity, applies a Roll of 0, then a Pitch of 30, then a Yaw of 0 to the object. Next cycle I change the Roll to 10 degrees, so that gives a total of (10/30/0). The code then resets things to 0/0/0 (Identity), applies the Roll of 10, then the Pitch of 30, then the Yaw of 0. For what ever reason this results in the object rolling around the world axis instead of the local axis.

However, if instead I change the Roll to 10 first then change the Pitch to 30, I get a correct rotation…but the logic still applied things in the correct order. Unless I am missing something and the transform.forward, transform.right, and transform.up do not get updated between transform.RotateAround calls. But I inserted some Debug.Log logic between calls and it looked like things were getting updated after RotateAround calls.

I’m really struggling with figuring out what I am missing here.

Why RotateAround and not just Rotate?

Because rotate uses the world/absolute axis to rotate.

When the object is at Roll/Pitch/Yaw of 0/0/0 that is fine. And it is fine if you only want to move on one axis. However, when you say roll 10 degrees to the left all the sudden the Pitch and Yaw axis are no longer parallel with the X and Y axis. This is why I have to rotate around a axis through the object.

You can choose to rotate in world or local. Its a parameter to the function.

rotate around is deprecated anyway.

You can specify world or local space as the last parameter with Rotate.

I made this:

Is that what you’re looking for? Where you specify the angle, “commit” the rotation, and then everything resets and you specify new angles? Your end use-case is a little confusing in that regard.

Here’s the code (I just edited the values via the inspector to make the gif):

public class RollPitchYaw : MonoBehaviour
{
   public float roll = 0.0f;
   public float pitch = 0.0f;
   public float yaw = 0.0f;

   public bool commit = false;

   protected void Update()
   {
     if(commit == true)
     {
       // roll
       transform.Rotate(0f, 0f, roll, Space.Self);

       // pitch
       transform.Rotate(pitch, 0f, 0f, Space.Self);

       // yaw
       transform.Rotate(0f, yaw, 0f, Space.Self);

       commit = false;
       roll = 0f;
       pitch = 0f;
       yaw = 0f;
     }  
   }

   protected void OnGUI()
   {
     GUI.Label(new Rect(5, 5, 100, 25),  "Roll  : " + roll);
     GUI.Label(new Rect(5, 25, 100, 25), "Pitch : " + pitch);
     GUI.Label(new Rect(5, 50, 100, 25), "Yaw  : " + yaw);
   }
}

If you’re looking for the use case where you adjust the rotation value, and the object maintains its current rotation but adjusts slightly, then @Polymorphik 's answer is correct.

1 Like

I didn’t realize RotateAround was deprecated. The documentation does not mention that. (Unity - Scripting API: Transform.RotateAround)

GroZZleR…I think your on to something. I could have sworn I tried transform.Rotate but it did not work at all like I thought it would. According to the documentation Space defaults to Space.Self so I would not have bothered with that parameter. Maybe I was trying to apply all 3 rotations to the object at once instead of the Z/X/Y order that Unity operates on.

I threw your code into a new scene and it does appear to work how I expect. It is late here but tomorrow I’ll try your code with my UI to see if I can change the UI angles in real time.

Actually, I just had an idea (mostly going to write it here so I remember to look at it tomorrow). Maybe things were not working how I expect them to work because when I am turning the “knobs” on my UI I am expecting the axis to change relative to the object’s current orientation. I might have to put some code in to detect which knob I am turning and change which order the axis rotations are applied or if the additional rotation is applied before other rotations or after.

The end goal for all of this is a Star Trek like ship navigation (“bearing 313 mark 25” style) and to take the ships starting rotation and use the UI (and this rotation logic) to give a desired final rotation.

Its a compiler warning. Doesn’t surprise me the docs are out of date.

It worked for me