- Home /
Quaternion.Angle is inaccurate
I have a strange issue with comparing angles. I do want to detect if an object rotated during the frame, using
if(Round(lastRotation, transform.rotation)) { // rotations are equal }
bool Round(Quaternion a, Quaternion b) {
return Quaternion.Angle(a, b) < 0.001f;
}
However, that returns false for some quaternions that are absolutely equal. Here's a debug print of those:
// (first value: Quaternion.Angle(a,b); -- this should be zero! Then the Quaternions xyzw; then the eulerAngles xyz)
diff_rot: 0.03956468; (0.02575354000000000000, 0.96469100000000000000, -0.24099940000000000000, 0.10308820000000000000) != (0.02575354000000000000, 0.96469100000000000000, -0.24099940000000000000, 0.10308820000000000000); (28.05311000000000000000, 167.80090000000000000000, 0.00000024185890000000) != (28.05311000000000000000, 167.80090000000000000000, 0.00000024185890000000)
Why on earth do two exactly equal rotations (even equal in floating point!) return a non-zero angle? And not even a small one, but 0.03 degrees, which would be a visible change in rotation?
Here's a script to test yourself this strange behaviour: link text
I'm a bit confused here... You say:
returns true for some quaternions that are absolutely equal
I can't see why it should return false... returning true seems correct: since the rotations are equal, the angle between them will be zero (or a very-very small float), which will be smaller than 0.001f (your condition), hence true.
You also say:
Why on earth do two exactly equal angles (even equal in floating point!) return a non-zero angle?
Which exactly is the non-zero angle you are referring to? Your printout only shows rotations in world space expressed as quaternions and Euler angles.
Corrected the mixup. Of course I mean that it returns false... Also, I added a script to test it yourself.
Seems like it might be an unfortunate outcome of floating point errors, you can use the following to avoid it, however its a bit messy:
float Round(Quaternion a, Quaternion b) {
float aL = $$anonymous$$athf.Sqrt(a.x*a.x + a.y*a.y + a.z*a.z + a.w*a.w);
float bL = $$anonymous$$athf.Sqrt(b.x*b.x + b.y*b.y + b.z*b.z + b.w*b.w);
a.x /= aL;
a.y /= aL;
a.z /= aL;
a.w /= aL;
b.x /= bL;
b.y /= bL;
b.z /= bL;
b.w /= bL;
return Quaternion.Angle(a, b) < 0.001f;
}
@scribe Did this Quaternion.Angle issue ever get resolved and is there a bug to vote on? I couldn't find one.
Your code above normalizes each Quat if I read it correctly (I'm not very Quat knowledgable). Why would that fix the problem, and have you confirmed that the problem comes from assigning transform.rotation rather than Angle?
I don't think there'a a bug report on it yet but feel free to create one ^^. The problem is actually simple floating point limitation. The quaternion value in question is normalized but the floating point error is large enough to produce an error of about 0.03 degree.
Unity's Angle method simply uses the dot product and Acos like that:
public static float Angle(Quaternion a, Quaternion b)
{
float f = Quaternion.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;
}
The values in question simply have reached their limit of accuracy:
(0.0257535400, 0.9646910000, -0.2409994000, 0.1030882000)
The dot product of the quaternion itself will yield a value of:
0.9999998580
Acos of that value converted to degree will yield the angle 0.0305338
That's a generally known problem due to the floating point limitation. Renormalizing the quaternions might help a little bit, but some values might still give you some problems.
See this SO question which is about the same algorithm. The answers to the question mention an alternative way to calculate the angle between two quaternions. It's a bit more complex to calculate but should be more accurate.
O$$anonymous$$, I run the script and found the source of the error by single stepping through code in the debugger. However, I'm not sure how to correct it.
It's not the Quaternion.Angle function being inaccurate, i.e. if you run the following it will return an angle of 0:
void Start()
{
Debug.Log(Quaternion.Angle(transform.rotation, transform.rotation));
}
The error arises when q is assigned to transform.rotation:
Quaternion q = new Quaternion(0.02575354000000000000f, 0.96469100000000000000f, -0.24099940000000000000f, 0.10308820000000000000f);
transform.rotation = q;
i.e. after the assignment the following values are in stored in transform.rotation:
x: 0.0257535446f -- 46 added at the end
y: 0.9646911f -- 1 added at the end
z: -0.24099943f -- 3 added at the end
w: 0.103088215f -- 15 added at the end
I believe this shouldn't happen, so it seems to be a bug.
Anyway, whenever you compare the assigned value to the original value, they are not equal because of this.
No idea how to correct it or work around it...
@Scribe, quaternions returned by Unity should always be normalized already, so normalizing them does not change anything.
@pako, I already reported a bug to Unity. Also, more investigation showed that the specific returned value seems to be the lowest non-zero value ever reported by Quaternion.Angle, so it indeed seems to be floating point-related. However, since that is the lowest value reported, I'll just go with changing the allowed angular difference to 0.04f.
So the curious point remains that this returned angle should be, compared to floating operations, very big (0° 2"), but at least I have a fix.
Answer by Amir1360 · Jan 14 at 02:35 PM
It seems that Quaternion.Angle
function is not accurate. Try the following function instead:
float AcurateAngle(Quaternion a, Quaternion b)
{
GameObject go = new GameObject();
go.transform.rotation = a;
Quaternion.Inverse(b).ToAngleAxis(out float angle, out Vector3 axis);
go.transform.Rotate(axis, angle);
go.transform.rotation.ToAngleAxis(out angle, out _);
Destroy(go);
return angle;
}
Wow, creating and destroying a gameObject just to calculate an angle seems brutally inefficient
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
How can i rotate a object more than once (90 degrees)? 1 Answer
better way to rotate instead Coroutine 1 Answer
Why is this rotation acting odd? 0 Answers