How is the angle between two identical quaternions 51.68?

I’m trying to get the angle between two quaternions. The quaternions are equal to each other. Unity engine Quaternion.Angle is determining 51.683 degrees.

   Quaternion q1 = new Quaternion(0.0f, 0.3f, 0.0f, -0.9f);
   Quaternion q2 = q1;
    var _angle = Quaternion.Angle (q1, q2);
    Debug.Log("q1: " + q1 + ".q2: " + q2 +  " .angle = "+ _angle);

3057879--229656--q.jpg

That quaternion is not normalized. As I understand it, quaternions can do strange things if not normalized. Basically I never touch quaternion components directly because they’re very difficult to understand. What is it you’re really trying to do?

Try this instead. It has an angle of 0.

Quaternion a = Quaternion.Euler(10, 20, 30);
Quaternion b = a;
Debug.Log(Quaternion.Angle(a, b));
1 Like

I’m trying to estimate angular velocity of a transform. I got strange results using:

Quaternion lastRotation;
Update(){
Debug.Log(transform.rotation);
float angle = Quaternion.Angle (transform.rotation, lastRotation);
float angularVelocity = angle / Time.deltaTime;
lastRotation = transform.rotation;
}

Unity reports the quaternion as (0.0f, 0.3f, 0.0f, -0.9f) . So that’s the actual rotation, I’m not manually creating the rotation.

Very similar to this:

Do I need to resort to Euler angles instead of Quaternions for this?
Or do I need to check myself if the quaternions are close, then assume the angle is 0?

To be fair, I thought the same thing but then tried this case:

var q1 = Quaternion.Euler(0, 90, 0);
var q2 = q1;
var _angle = Quaternion.Angle(q1, q2);
Debug.Log("q1: " + q1 + ".q2: " + q2 + " .angle = " + _angle);

The result was 0.03956468. Surprised me, actually.

If I massaged that to give X a non-zero value (such as 0.1f, 90f, 0f), then the angle given is 0.

I suspect there’s some sort of a cross product using colinear vectors at play in the Angle() function, but it’s hard to tell what’s going on without the source code.

Here’s the Unity engine API code:

public static float Angle(Quaternion a, Quaternion b)
        {
            float f = Dot(a, b);
            return Mathf.Acos(Mathf.Min(Mathf.Abs(f), 1f)) * 2f * 57.29578f;
        }

    public static float Dot(Quaternion a, Quaternion b)
        {
            return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
        }

I see. I just tried the same code and I’m getting the results you’d expect, between 0 and 3 degrees for something moving in my game (unscaled for time, that’s per frame).

However, you’re dividing by Time.deltaTime? I think you mean to multiply by Time.deltaTime. Edit: never mind about that, I misunderstood.

And I was just using Euler angles to produce a known good quaternion in a frame of reference I understand better. I have no idea why constructing a quaternion like that doesn’t work.

That could easily be explained by floating point errors. If that’s in degrees then an error of 0.04 degrees is nothing to worry about here.

Velocity = distance / time;

0.04 is actually a pretty large value so it’d be pretty disconcerting to get that value when you’re expecting 0. Especially with 7 digits precision.

1 Like

It’s not quite that bad. You have to remember that all the math here is being done in radians and Unity is converting to degrees. The floating point error here is actually 0.0007 radians. That is a very, very tiny error, but since you’ve multiplied by 180/pi it’s gotten bigger.

It’s a limitation of floating point hardware of using using 32-bit floats, there’s nothing that can be done about it. The rule of thumb is to never expect any floating point math to be exactly the result you’d expect, only that it will be very close. And 0.0007 away from the expected result is very, very close.

1 Like

Thanks. Plugging your values into the Dot function: 00 + 0.30.3 + 00 + 0.90.9 = 0.9. So these values aren’t fully normalized.

Acos(1) = 0. This would’ve given you 0 degrees.
Acos(0.9) = 0.451. Multiply that by 2f * 57.29578f and you get 51.68.

FYI - it appears that Quaternion.ToString only prints the first decimal value, so you’re not getting the full x,y,z,w values. You’ll need to print out these values separately to get more accurate results.

I dug around for the source code for Quaternion.Euler, and ran across this code. It appears to be a mess of casting double to float, take the float result to do more calculations, re-cast to float, etc. You lose a LOT of precision that way.

1 Like

Thanks for the info here. It wouldn’t seem like a big deal. But in my case I’m setting an animator speed based on it’s estimated angularVelocity. A value of 0.04 every frame means if you’re staring at the gameobject which isn’t rotating, the animation would be playing very slowly, but it’s still detectable. The solution must be to discard values that are close to zero.

I just tried to prove to myself what was happening. True, the debug.log(quaternion) prints the floats to only one decimal. That explains why I was getting a rediculous high angle of 51.
Here is more detailed info on what happens a particular frame:

3057977--229658--qs.jpg

The current and last rotations are equal.
Using purely double math, the angle would be 0.
But using the floats, the angle could become 0.05.

Conclusions:

Mystery solved.:smile:

1 Like

If it was me, I’d discard based on the dot value.

The Unity engine API does a dot check in the “==” operator in the Quaternion.

public static bool operator ==(Quaternion lhs, Quaternion rhs)
        {
            return Quaternion.Dot(lhs, rhs) > 0.999999f;
        }

This appears to be sufficient.

Why not just normalize before creating the Quaternion? The values you are putting into new Quaternion should not even be allowed.

example extension methods, which I use to serialize Quaternions in my editor scripts (serialized as Vector4s)

        public static Quaternion ToQuaternion(this Vector4 inVector)
        {
            float magnitude = inVector.sqrMagnitude;

            if(Mathf.Approximately(magnitude,0))
                return Quaternion.identity;
        
            Vector4 vector = inVector.normalized;
        
            return new Quaternion(vector.x,vector.y,vector.z,vector.w);
        }

        public static Vector4 ToVector4(this Quaternion q)
        {
            return new Vector4(q.x,q.y,q.z,q.w);
        }

I’m sure your solution would work. (Note I was not really creating a Quaternion to begin with. I just did it for the sole purpose of troubleshooting the issues I was having with the floating point precisions).