Help with localEulerAngles going from 0 to 359

I have a switch that I want the user to click and drag the mouse to rotate. I’m checking to see if the localEulerAngles is at the start (0) or end (45) degree rotation. The problem I’m having is that when the user rotates the switch below the 0-degree mark, it starts the rotation over at 359. Is there any way to get it to show a negative degree so I can say “oh yea, that’s less than 0 and should be 0”?

Don’t have your code read from eulerAngles (or localEulerAngles). Instead, control the angle yourself in a float, then drive the rotation with a fresh rotation each time, created with Quaternion.Euler( 0, 0, angle), for example.

Here’s why:

Notes on clamping camera rotation and NOT using .eulerAngles because of gimbal lock:

How to instantly see gimbal lock for yourself:

All about Euler angles and rotations, by StarManta:

https://starmanta.gitbooks.io/unitytipsredux/content/second-question.html

Everybody has a hard time understanding Euler angles in Unity, and maybe the simplest way to understand this is to consider what drives what.

First of all, the underlying system of rotations runs on quaternions (that’s not the whole truth, but it’s the closest explanation to it). But nobody likes quaternions because they’re as intuitive as they sound. People like Euler angles, but angles don’t work well, hence the quaternions.

All in all, once set, angles do drive quaternions. But then rotations happen, and angles get refreshed back for the user, FROM the quaternions, angles are not part of this process, and the angles you get back have no relationship to the original ones. It’s all just a convenience because people like Euler angles. It is also a very unreliable convenience.

Instead do one (or both) of these two solutions:

  1. keep track of important angles on your own, and produce a legit quaternion by calling Quaternion.Euler.
  2. learn to use quaternions instead.

And read everything else Kurt-Dekker already said / linked.

I looked at the forum posts, looked at the help file, and looked at some videos but I just can’t understand this.

The idea is that when a user clicks on a switch, they can drag the mouse to rotate it along a single axis. When the rotation gets within a threshold, it snaps to the on or off angle. 0 is “off” and 45 is “on”. The snap threshold is 5°. So when the angle is less than 5 it should snap to off, and higher than 40 it should snap to on.

When they click on the object, it creates a temporary game object to apply the rotation to and help with the limits. The problem is when they move the mouse down, and the angle goes below 0, it starts at 359 and then snaps to the “on” or 45° angle because of the code that checks to see if the angle is lower or higher than the snap points:

// Check for the min and max rotation angles
if(currentRotationAngle < minRotSnap)
{
     transform.localEulerAngles = minRot;
}
else if (currentRotationAngle > maxRotSnap)
{
     transform.localEulerAngles = maxRot;
}
else
{
     transform.Rotate(rotation);
}

This is the entire code I have so far:

[Header("Rotation Attributes")]
    [Tooltip("Axis that the rotation for the switch is handled on.")]
    public RotationAxis rotationAxis = RotationAxis.X;
    [Tooltip("Minimum rotation value for the switch, or OFF position")]
    public float minRotation = 0;
    [Tooltip("Maximum rotation value for the switch, or ON position.")]
    public float maxRotation = 45;
    [Tooltip("Angle in degrees for the switch to snap when it's within that degree angle.")]
    public float snapAngleTollerance = 5;
    [Tooltip("Sensitivity of the mouse movement for the rotation.")]
    public float sensitivity = 0.4f;

    private Vector3 mouseReference;
    private Vector3 mouseOffset;
    private Vector3 rotation = Vector3.zero;
    private bool isRotating = false;

    private Vector3 minRot;
    private Vector3 maxRot;

    // Ranges for the switch
    private float minRotSnap = 0.0f;
    private float maxRotSnap = 0.0f;

    // Temp rotation to help calculation rotation
    private GameObject tempGO;

    // Start is called before the first frame update
    void Start()
    {
        Vector3 curRot = this.transform.localEulerAngles;

        minRotSnap = minRotation + snapAngleTollerance;
        maxRotSnap = maxRotation - snapAngleTollerance;

        switch (rotationAxis)
        {
            case RotationAxis.X:
                minRot = new Vector3(minRotation, curRot.y, curRot.z);
                maxRot = new Vector3(maxRotation, curRot.y, curRot.z);
                break;

            case RotationAxis.Y:
                minRot = new Vector3(curRot.x, minRotation, curRot.z);
                maxRot = new Vector3(curRot.x, maxRotation, curRot.z);
                break;

            case RotationAxis.Z:
                minRot = new Vector3(curRot.x, curRot.y, minRotation);
                maxRot = new Vector3(curRot.x, curRot.y, maxRotation);
                break;
        }
    }

    // Update is called once per frame
    private void FixedUpdate()
    {
        if (isRotating)
        {
            float currentRotationAngle = 0;

            // offset
            mouseOffset = (Input.mousePosition - mouseReference);

            // apply rotation based on axis selected
            switch (rotationAxis)
            {
                case RotationAxis.X:
                    rotation.x = (mouseOffset.x + mouseOffset.y) * sensitivity;
                    break;

                case RotationAxis.Y:
                    rotation.y = -(mouseOffset.x + mouseOffset.y) * sensitivity;
                    break;

                case RotationAxis.Z:
                    rotation.z = -(mouseOffset.x + mouseOffset.y) * sensitivity;
                    break;
            }

            // rotate
            tempGO.transform.Rotate(rotation);

            Quaternion myRot = tempGO.transform.rotation;

            // Check to see if the values are within range
            switch (rotationAxis)
            {
                case RotationAxis.X:
                    currentRotationAngle = myRot.eulerAngles.x;
                    break;

                case RotationAxis.Y:
                    currentRotationAngle = myRot.eulerAngles.y;
                    break;

                case RotationAxis.Z:
                    currentRotationAngle = myRot.eulerAngles.z;
                    break;
            }

            Debug.Log("currentRotationAngle = " + currentRotationAngle);

            // Check for the min and max rotation angles
            if(currentRotationAngle < minRotSnap) {
                transform.localEulerAngles = minRot;
            } else if (currentRotationAngle > maxRotSnap) {
                transform.localEulerAngles = maxRot;
            } else {
                transform.Rotate(rotation);
            }

            // store mouse
            mouseReference = Input.mousePosition;
        }
    }

    private void OnMouseDown()
    {
        // Setup a temp game object to help calculate the rotation
        tempGO = new GameObject();
        tempGO.transform.localEulerAngles = this.transform.localEulerAngles;

        // rotating flag
        isRotating = true;

        // store mouse
        mouseReference = Input.mousePosition;
    }

    void OnMouseUp()
    {
        Destroy(tempGO);

        // rotating flag
        isRotating = false;
    }

Well, what part of “Euler angles are unreliable” is difficult to understand?

The reason why they’re unreliable lies in the fact they’re created from thin air, based off of the underlying math that does not employ angles at all. And because a) angles are modular (270 = -90 = 630 = -450), as well as the fact that b) many Euler angle combinations are not defined uniquely, they can turn out however they want! They will always make sense, but they are not guaranteed to actually follow a continuous interval.

Now I’d like to try and help you more concretely than this, but I can’t really understand your description. It has too many words and some stuff is just silly (359 degrees means nothing to Unity, you’re fixated too much onto angles), and I’ve been programming the whole night…

If you can explain your thing one thing at a time, maybe we can do a single thing, something, that just works. You can then extrapolate what else is required and apply what you’ve learned to your game.

1 Like