Quaternion Shortest Route?

I am trying to make a little abstract arcadey-esque gamey-thing.

I have a cube, around the centre point of which my camera rotates.

The camera always faces one side of the cube.

Each rotation is 90°. look at one face, button press, look at the next face. The rotation can be in any direction, “up”, “down”, “left” or “right”.

My problem is that after a few rotations, rather than simply rotating the human-obvious 90° to the next face, the camera decides to rotate around the cube in an arc back the wrong way. It even ignores the 90° planes and whips round via corners and all sorts.

Chatting to an acquaintance, he suspects that my script doesn’t like rotating from (hypothetical example) 270° to 90° via the 0° point.
Rather than jumping the 0° point it will rotate alllllll the way back round the long way.

I am pretty much a scripting virgin, just doing tutorials and trying to apply them to my game when the light bulb goes on above me head.

PLEASE HELP ME

public float turnSpeed = 200f;
	private int rotation = 0;
	private Quaternion qTo = Quaternion.identity;
	
	
	void Update () 
	{
	
			if ((Input.GetKeyDown ("w")) & (Input.GetKey (KeyCode.RightShift))) 
			{
				rotation += 90;
				qTo = Quaternion.Euler(rotation, 0, 0);
				Debug.Log ("Camera Rotated Screen-North");
			}
			if ((Input.GetKeyDown ("s")) & (Input.GetKey (KeyCode.RightShift)))
			{
				rotation -= 90;
				qTo = Quaternion.Euler(rotation, 0, 0);
				Debug.Log ("Camera Rotated Screen-South");
			}
			if ((Input.GetKeyDown ("d")) & (Input.GetKey (KeyCode.RightShift)))
			{
				rotation -= 90;
				qTo = Quaternion.Euler(0, 0, rotation);
				Debug.Log ("Camera Rotated Screen-East");
			}
			if ((Input.GetKeyDown ("a")) & (Input.GetKey (KeyCode.RightShift))) 
			{
				rotation += 90;
				qTo = Quaternion.Euler(0, 0, rotation);
				Debug.Log ("Camera Rotated Screen-West");
			}

		
			transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, turnSpeed * Time.deltaTime);

	}

Your code doesn’t make much sense. You only use one rotation variable which you increment / decrement in each of your cases but one time you use it as x-rotation and one time as z-rotation.

Also your rotations are absolute worldspace rotation an not relative to the screen. You should just dump your rotation variable and only use quaternions. Something like that:

void Update () 
{
    if (Input.GetKey (KeyCode.RightShift))
    {
        if ( Input.GetKeyDown ("w") ) 
        {
            qTo = Quaternion.AngleAxis(90, transform.right) * qTo;
        }
        else if ( Input.GetKeyDown ("s"))
        {
                 qTo = Quaternion.AngleAxis(-90, transform.right) * qTo;
        }
        else if (Input.GetKeyDown ("d"))
        {
            qTo = Quaternion.AngleAxis(90, transform.up) * qTo;
        }
        else if (Input.GetKeyDown ("a")) 
        {
            qTo = Quaternion.AngleAxis(-90, transform.up) * qTo;
        }
        transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, turnSpeed * Time.deltaTime);
}

Note: this is untested but should work that way. You might need to flip the sign of the “90” if it rotates into a wrong direction.

Keep in mind that you can implicitly rotate the cube around the z axis as well. If you do an up, right down rotation you’re back on the starting side but it’s rotated by 90° counterclockwise. It’s 3d, you can’t prevent that unless you force a certain orientation on a certain side. However that will yield some strange transitions where you will experience a double rotation when going from one side to another.

Games which allow such a game mechanic might want to provide a way to rotate around z as well (usually with “q” and “e”)

Also note that using tranform.up /.right /.forward might cause problems if you quickly press two different keys in succession. You might want to replace them with aTo*Vector3.up and aTo*Vector3.right. That way the axis is calculated from the current target orientation and not the current intermediate rotation.

Hey

I thought Quaternion.RotateTowards handled this automatically, but perhaps it doesn’t.

Assuming it’s not handled internally, the answer lies in the fact that a quaternion represents 720 degrees of rotation! Your rotation ‘qTo’ does represent the euler angle required. However if you imagine it as an instruction ‘rotate to 90 degrees around the x axis’, you could also say ‘rotate to -270 degrees around the x axis’ - the end result is the same.

If you want to make sure you always go in the correct direction, you need to do a bit of extra work, You need to make sure your quaternion and the other quaternion represent rotations in the same direction. This is done with a dot product:

if (Quaternion.Dot(transform.rotation,qTo) < 0)
    qTo = -qTo;

Here we’ve said ‘if transform.rotation is in the opposite direction to qTo, flip the direction of qTo’.

That should solve the problem, though I’ve not tested it. Let me know if you’re still struggling and I’ll take a look at home once I have unity in front of me.

-Chris

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateQuaNoneShortWay : MonoBehaviour
{
    public float rotateAngle;
    public float speed;

    Quaternion targetRot;

    float targetAngle;
    float currentAngle;

    Quaternion firstRotation;

    public bool canRotate;

    Transform c_Transform;

    private void Start()
    {
        c_Transform = GetComponent<Transform>();

        firstRotation = c_Transform.rotation;
    }
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.R))
        {
            targetAngle += rotateAngle;

            if (!canRotate)
            {
                firstRotation = c_Transform.rotation;

                canRotate = true;
            }
        }
        if (canRotate)
        {
            currentAngle = Mathf.Lerp(currentAngle, targetAngle, Time.deltaTime * speed);
            targetRot = Quaternion.AngleAxis(currentAngle, c_Transform.up) * firstRotation;
            c_Transform.rotation = Quaternion.Slerp(c_Transform.rotation, targetRot, Time.deltaTime * speed);

            if (Quaternion.Angle(c_Transform.rotation,targetRot) < 0.2f)
            {
                c_Transform.rotation = targetRot;
                currentAngle = 0;
                targetAngle = 0;

                canRotate = false;
            }
        }
    }
}