2
\$\begingroup\$

I need to find a way to set min and max angles for each Euler component and check whether transform.localEulerAngles falls within those limits.

My current code looks like this:

class JointLimit : MonoBehaviour
{
    [SerializeField]
    [Range(-180f, 180f)]
    private float minX = -180f;
    [SerializeField]
    [Range(-180f, 180f)]
    private float maxX = 180f;
    [SerializeField]
    [Range(-180f, 180f)]
    private float minY = -180f;
    [SerializeField]
    [Range(-180f, 180f)]
    private float maxY = 180f;
    [SerializeField]
    [Range(-180f, 180f)]
    private float minZ = -180f;
    [SerializeField]
    [Range(-180f, 180f)]
    private float maxZ = 180f;

    public float Error
    {
        get
        {
            Vector3 localEuler = transform.localEulerAngles;
            Vector3 signedLocalEuler = new Vector3(Mathf.DeltaAngle(localEuler.x, 0), Mathf.DeltaAngle(localEuler.y, 0), Mathf.DeltaAngle(localEuler.z, 0));

            float error = 0;
            error += Mathf.Max(0, minX - signedLocalEuler.x);
            error += Mathf.Max(0, signedLocalEuler.x - maxX);
            error += Mathf.Max(0, minY - signedLocalEuler.y);
            error += Mathf.Max(0, signedLocalEuler.y - maxY);
            error += Mathf.Max(0, minZ - signedLocalEuler.z);
            error += Mathf.Max(0, signedLocalEuler.z - maxZ);
            return error;
        }
    }
}

This basically checks whether the Euler components fall within the specified range and sums the exceeding angles up to an error.

The problem is that Unity internally stores rotations as Quaternions and that there can be several euler representations for a single Quaternion, like Euler 123f, 17f, 235f and Euler 57f, 197f, 55f are the same thing.

Is there any way I can bring an Euler representation into some kind of normalized form that allows me to reliably check it against my limits?

\$\endgroup\$
5
\$\begingroup\$

Euler angles aren't necessarily a good way of expressing limits over arbitrary ranges, as their behaviour close to zero is very different from their behaviour away from zero.

Below I've visualized the forward, right, and up vectors of an object rotated with Euler angles over various ranges.

Here yaw ∈ [-50, 50] and roll ∈ [-10, 10]. From left to right, I gradually alter the pitch range, from pitch ∈ [-10, 10] to pitch ∈ [70, 90]. You can see I haven't reduced the range of allowed angles anywhere, only shifted where it starts.

Example of Euler rotation ranges visualized

And yet, when we look at the range of orientations allowed in these ranges, we can see all three axis regions distort and change size & shape. The allowed range of right vectors flattens toward a line, the forward vectors collapse toward a triangle, and the up vectors spread out in a wider arc.

So limits specified in Euler angles are error prone. If the ranges of allowed rotation you actually want look like this, for example:

The first range, rotated in a more intuitive way

...then there might be no combination of Euler min/max angles that corresponds to this shape.

For narrow Euler angle ranges, we can ameliorate this a bit by choosing a neutral orientation that corresponds roughly to the middle of our range, and then measure our Euler angles as relative deviations from this. This will tend to keep our min & max close to and symmetric about zero, where Euler angles are most intuitive.

Vector3 GetEulerOffset(Quaternion rotation, Quaternion neutral) {
    var localDeviation = Quaternion.Inverse(neutral) * rotation;
    return localDeviation.eulerAngles
}

If you want to avoid the non-uniformity and cross-axis wraparound problems of Euler angles altogether, then it's usually better to try to express your limitation in terms of vectors. Let's say we have an elbow joint that's allowed to rotate -70 degrees inward and 2 degrees outward relative to its parent.

We can get its forward direction in the parent's space as:

Vector3 localForward = transform.localRotation * Vector3.forward;

And convert this to an angle in the xz plane with known range -180 to 180, regardless of what's happening on the remaining axis.

float angle = Mathf.Atan2(localForward.z, localForward.x) * Mathf.Rad2Deg;

bool inRange = angle > -70 && angle <= 2;
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you, you have no idea how many different iterations of clamping rotations I tried before finding this answer, which while not specifically resolving my issue, allowed me to get my code working. Keywords for others and google search -> (Clamp Rotation, Unity Clamp Rotation, Clamp Quaternion, Limit Quaternion) \$\endgroup\$ – Reahreic Feb 9 at 0:15
1
\$\begingroup\$

Maintain your own layer that operates on euler angles, and keeps current values of these stored every frame. Unity derives the quaternion form for the purposes of allowing the renderer and physics to do their thing, whenever you set transform.localEulerAngles.

Whenever you need the source values, you have them stored right there in your class and can perform your own necessary pre-checks upon them. You are then not relying on Unity's internal representations, as you have your own to hand.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Hi Arcane! Unfortunately the rotation is specified by systems I have no control over either, like Animation and IK. Once those systems have calculated the rotation, I need to analyze it to check how well it fits into the limitations. \$\endgroup\$ – Thomas Hilbert Jul 26 '17 at 20:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.