Quaternion.Angle wired bug

I have a methd to log the angle diff:

        protected override void LogDiff(Quaternion v1, Quaternion v2)
            var direction = new Vector3(0, 0, 1f);
            var d1 = v1 * direction;
            var d2 = v2 * direction;
            var delta = Vector3.Angle(d1, d2);
            var dist = Quaternion.Angle(v1, v2);
            Debug.Log(Time.frameCount + " -()-()-Calibrate diff Quaternion.Angle=" + dist + ", Vector3.Angle=" + delta + " | " + v1 + " - " + v2 + ", " + v1.eulerAngles + " - " + v2.eulerAngles);

Then I got his result that I can not understand, Quaternion.Angle returns wired value:

2143 -()-()-Calibrate diff Quaternion.Angle=2.451461, Vector3.Angle=0 | (0.00000, 0.10456, 0.00000, 0.99440) - (0.00000, 0.10456, 0.00000, 0.99440), (0.00, 12.00, 0.00) - (0.00, 12.00, 0.00)
5740 -()-()-Calibrate diff Quaternion.Angle=2.868992, Vector3.Angle=0 | (0.00000, 0.10456, 0.00000, 0.99436) - (0.00000, 0.10457, 0.00000, 0.99436), (0.00, 12.01, 0.00) - (0.00, 12.01, 0.00)
9338 -()-()-Calibrate diff Quaternion.Angle=3.252124, Vector3.Angle=0 | (0.00000, 0.10457, 0.00000, 0.99431) - (0.00000, 0.10457, 0.00000, 0.99432), (0.00, 12.01, 0.00) - (0.00, 12.01, 0.00)
12924 -()-()-Calibrate diff Quaternion.Angle=3.571501, Vector3.Angle=0 | (0.00000, 0.10458, 0.00000, 0.99427) - (0.00000, 0.10458, 0.00000, 0.99427), (0.00, 12.01, 0.00) - (0.00, 12.01, 0.00)
16512 -()-()-Calibrate diff Quaternion.Angle=3.867012, Vector3.Angle=0 | (0.00000, 0.10459, 0.00000, 0.99423) - (0.00000, 0.10459, 0.00000, 0.99423), (0.00, 12.01, 0.00) - (0.00, 12.01, 0.00)
20092 -()-()-Calibrate diff Quaternion.Angle=4.153012, Vector3.Angle=0 | (0.00000, 0.10460, 0.00000, 0.99418) - (0.00000, 0.10460, 0.00000, 0.99418), (0.00, 12.01, 0.00) - (0.00, 12.01, 0.00)
23674 -()-()-Calibrate diff Quaternion.Angle=4.420554, Vector3.Angle=0 | (0.00000, 0.10461, 0.00000, 0.99414) - (0.00000, 0.10461, 0.00000, 0.99414), (0.00, 12.01, 0.00) - (0.00, 12.01, 0.00)
27224 -()-()-Calibrate diff Quaternion.Angle=4.667782, Vector3.Angle=0 | (0.00000, 0.10462, 0.00000, 0.99410) - (0.00000, 0.10462, 0.00000, 0.99409), (0.00, 12.02, 0.00) - (0.00, 12.02, 0.00)

I know you may say there's float error, but why so such big, and getting bigger and bigger?

What is the error you refer to? What values do you expect?

Quaternion.Angle isn‘t the angle around forward but the angle between two rotations which is something different from Vector3.Angle along forward direction.

btw your direction can be replaced by Vector3.forward.

If v1 is equal to v2, so Quaternion.Angle should returns zero, right? But it doesn't.

Don’t be too sure that v1 is equal to v2. The values printed could be truncated, with differing trailing bits. Try printing out each component of each quaternion, you should see there are some small differences if Angle is nonzero.

Also, if you’re expecting your little direction-vector test to help, keep in mind that you could end up with a 0 result in some cases, like if both quaternions represent different rotations about the same axis, with that axis being parallel to the direction vector you use, which means both Quaternion * Vector3 operations result in the same rotated vector.

It’s highly unlikely that Angle is bugged in this manner, it’s surely been battle-tested enough. It’s probably correct handling of the input quaternions.

1 Like

I know it is not "equal", I mean it is almost "equal", see the eulerAngles, the diff should be minor(less than 0.001), but the output 4.6 even more, is it normal?

Here is another quick and simple test:

var q2 = new Quaternion(0.000000E+000f, -2.588381E-001f, 0.000000E+000f, -9.655697E-001f);
var q1 = q2;
var angle = Quaternion.Angle(q1, q2);
Debug.LogError($"Angle = {angle:E}, e1={q1.eulerAngles.ToString("E")}, e2={q2.eulerAngles.ToString("E")}");


Angle = 4.220142E+000, e1=(0.000000E+000, 3.001268E+001, 0.000000E+000), e2=(0.000000E+000, 3.001268E+001, 0.000000E+000)

Wired? How to understand the document of the API : Returns the angle in degrees between two rotations a and b. The resulting angle ranges from 0 to 180. Any thing I misunderstood?

Well, I finally found the reason, that is because the quaternion is not normalized, here's the fix:

var q2 = new Quaternion(0.000000E+000f, -2.588381E-001f, 0.000000E+000f, -9.655697E-001f);
var q1 = q2;
var angle = Quaternion.Angle(q1, q2);
Debug.LogError($"Angle = {angle:E}, e1={q1.eulerAngles.ToString("E")}, e2={q2.eulerAngles.ToString("E")}");


Angle = 0.000000E+000, e1=(0.000000E+000, 3.001267E+001, 0.000000E+000), e2=(0.000000E+000, 3.001267E+001, 0.000000E+000)

Since you are repeating it: wired means "making use of computers to transfer or receive information, especially by means of the internet."

You meant to say: weird. As in: odd, strange, wtf. ;)


Of course, you can't just type whatever you want, some functions depend on quaternion being unit (in fact everything is made around unit quaternions bc they're used to mathematically define rotations and are known as 'versors') and quat c-tor doesn't sanitize on input.


Right. Yesterday I already started typing an answer but got distracted and forgot about it ^^. One of my question was where those quaternions came from in the first place? Your recent example shows that the magnitude of the quaternion is less than 1.0. Not by much but the arccos is very sensitive in the low angle range. Just pull out a calculator and calculate the cosine of 2°. It's 0.99939. Since the "w" component of a quaternion essentially encodes half the angle the quaternion actually rotates about, very subtle inaccuracies can have a bit impact. Especially when you compare two quaternions which both are slightly shorter than 1, this actually multiplies (literally, since Angle does the dot product between them). Here I checked the length of your quaternion twice, first before normalization and again afterwards. As you can see it's just a small error, but it's there.

So, where did the quaternions actually came from?


I was debuging my Client/Server sync logic, client will predict, and every 60 seconds the server will send a value to client to verify if the client predicted the correct value. For example, there's a object rotating with a consist speed.
The v1 is client predicted value, v2 is the server sent value. If the client was not cheating, the difference should be zero (Or a very small value).
However, the normalizing is the key, if the quaternion is not nomalized, Angle method will not return right value.

Most of the API will work incorrectly (with regards to rotations) if you use non-unit quaternions.

It is perfectly doable to have each method sanitize on input, however normalization isn't cheap (4 multiplications and a square root, then 1 reciprocal and 4 more multiplications and a new struct assembly) and there is no easy way to avoid it when not necessary (a test whether to do it is only slightly less expensive than actually doing it). So the best approach when designing an API like this is to assume full programmer responsibility. There is nothing worse than overdone math system, where you can't do much to fix the performance, so they make everything as lightweight as possible. It's a balancing act.

In my libraries I have a couple of situations where I pass whether I'd like to normalize something or not as an argument (then in code I conditionally multiply by 1 or ad-hoc inverse magnitude). So you can perhaps do something like this if it bothers you. I do this very rarely however (i.e. finding a perpendicular vector, and it's slightly cheaper to normalize on the fly, then doing a separate normalization pass on the output).

1 Like