Visualizing Objects' Rotation

Alright, it’s time to stop banging my head against this wall and call out for help. Here’s what I’m trying to do…

The player object can move its lower body and upper body independently of one another. Both parts can rotate around the UP (the reason why I said up and not Y will be apparent soon) axis in full 360 degree motion. The upper body’s rotation is controlled at the “spine” bone in its rig. The lower body’s rotation is controlled by the main GameObject’s transform. When the player presses A or D the GameObject’s transform rotates. The spine’s rotation is controlled in a separate script’s LateUpdate(). It’s controlled by the mouse’s X axis input. When the input changes, the spine is rotated along its X axis. This is why I said UP earlier… Due to the orientation of the bone, the spine’s would be Y axis…up…is really it’s X axis.

Visualizing it is the problem. In the Canvas I have two images. One nested inside the other. At the start, the upper-body and lower-body are aligned, so the images in the Canvas are aligned as well. The parent image represents the body’s orientation and the child image represents the spine’s orientation (which makes sense since the spine is a child of the main GameObject). When the player rotates the spine, it updates its orientation, up until a certain point, and then it seems to be picking random rotations after a certain degree. The image that represents the lower-body (the main GameObject) is always accurate.

I hope I’ve explained enough for someone to help me see what I might be doing wrong here. The code that controls the UI elements is below.

private void Update()
{
    if (player == null)
        return;

    Vector3 upperBodyNorthDirection = new Vector3();
    Vector3 lowerBodyNorthDirection = new Vector3();

    upperBodyNorthDirection.z = player.spine.transform.localEulerAngles.x;

    upperBodyIndicatorRectTransform.localEulerAngles = upperBodyNorthDirection;

    lowerBodyNorthDirection.z = player.transform.eulerAngles.y;
    lowerBodyIndicatorRectTransform.localEulerAngles = -bodyNorthDirection;
}

And here’s a shot of the UI elements.

This is from gimbal lock. You can’t read and write into eulerAngles past a certain distance of rotation because they suffer from gimbal lock.

Keep your own float rotations and create fresh rotations.

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

https://discussions.unity.com/t/838550/2
https://discussions.unity.com/t/838550/4

Reading through your explanation to the other user… It’s been my understanding that Unity recommends never setting an object’s rotation manually in the fashion that you’re describing?

Also, I’m not having issues with setting the player object’s rotations. Just getting the one UI element to “display” it correctly.

So I solved the issue, but it didn’t have anything to do with gimbal locking. It had to do with adding and offset due to the spine bone’s awkward default orientation, and using it’s y eulerAngle axis. Also I noticed that my method of rotating it wasn’t exactly accurate. I was using Mathf.Clamp(x, 360, -360). This would lock the rotation at -360 and never let it get back around to 0. I added in my own custom clamping method for this case (which really isn’t a “clamp” technically) that lets it go back to zero once the max threshold is reached. Here’s the updated UI code.

private void Update()
{
    if (player == null)
        return;

    Vector3 upperBodyNorthDirection = new Vector3();
    Vector3 lowerBodyNorthDirection = new Vector3();

    upperBodyNorthDirection.z = player.spine.transform.localEulerAngles.y;

    upperBodyNorthDirection = upperBodyNorthDirection - offset; //offset is a Vector3 value assigned in the Inspector

    upperBodyIndicatorRectTransform.localEulerAngles = -upperBodyNorthDirection;

    lowerBodyNorthDirection.z = player.transform.eulerAngles.y;
    lowerBodyIndicatorRectTransform.localEulerAngles = -bodyNorthDirection;
}

Also I un-nested the UI elements from each other. Having the upper-body indicator image nested within the lower-body indicator image caused unwanted behavior. It’s all working properly now.

Any logic that involves splitting apart Euler angles into the X/Y/Z components is going to come back to bite you later with glitchy rotations and weird behavior - Euler angles can’t be reliably/meaningfully split up that way, because each of the three numbers depends on the other two. More information and workarounds here.

It actually is gimbal locking: generally you can’t rely on eulerAngles fields giving back anything outside of -180 to 180, except I think in certain cases it can go as high as 359. They are intended to be useful for human reading, not for computation.

As Star Manta pointed out, they are ambiguous beyond 45 degrees of rotation, as in there is more than one Euler angle way to represent a rotation, whereas there is only one Quaternion way.

So I’ve watched the video and read over your replies. However, I think you two aren’t seeing how I’m using the eulerAngles. I’m only reading from one objects single axis… And then setting another object’s eulerAngle based on what I got from the other object. Since both objects are ONLY rotating on a single axis, the euler is always the same. Which is why my use case is working properly. Unless there’s something I’m still not seeing here.

Now assuming that I’m still over-looking something that you two are trying to point out, I have adopted a method he mentioned in the video which looks something like…

float angle = Vector3.Angle(Vector3.forward, player.transform.forward);
bodyIndicatorRectTransform.rotation = Quaternion.Euler(0.0f, 0.0f, -angle);

However the problem with this is that angle is always between 0 and 180. I need to be able to access the full 360 of an axis.

Vector3.Angle is not signed, meaning it can only represent angles from 0 to 180. Use Vector3.SignedAngle for that.

1 Like