Bug in Quaternion.Angle()?

So I had the following simple function in my code:

bool CheckAttachment(Vector3 pos1, Quaternion rot1, Vector3 pos2, Quaternion rot2) {
	return Quaternion.Angle(rot1, rot2) > (180-MaxSnapAngle)  Vector3.Distance(pos1, pos2) < MaxSnapDistance;
}

For the purpose of checking if two objects were close enough together, and back-to-back, to see if they should be considered attached. However, it was falsely attaching objects which were close enough, but at 90 degree angles from each other. In order to debug this further, I changed the function, as follows, to get some debugging output:

bool CheckAttachment(Vector3 pos1, Quaternion rot1, Vector3 pos2, Quaternion rot2) {
	float angle = Quaternion.Angle(rot1, rot2);
	float dist = Vector3.Distance(pos1, pos2);
	if (angle > (180-MaxSnapAngle)  dist < MaxSnapDistance) {
		Debug.Log("Found Connection - Angle " + angle + ", 1st Angle " + Quaternion.Angle(Quaternion.identity, rot1) + ", 2nd Angle " + Quaternion.Angle(Quaternion.identity, rot2) + ", Dist " + dist);
		return true;
	}
	return false;
}

And I got this output:

Found Connection - Angle 180, 1st Angle 90, 2nd Angle 180, Dist 0

How is it possible for a quaternion to be 180 degrees from the identity quaternion, and ALSO 180 degrees for a quaternion that’s itself 90 degrees away from the identity quaternion? Does it somehow count different “roll” as part of the angle difference?

It’s always possible to go from one rotation to another by rotating a certain amount around a certain axis. The Quaternion.Angle function measures this angle, and yes, it’s a rotation in 3D, so it takes the complete 3D alignment of both rotations into account.

You may be better off measuring the angle between two actual vectors, like e.g. the respective forward vectors of the two objects.

Rune

The way I would recommend doing this is to get the dot product of the two objects’ transform.forward vectors. The resulting value will be equal to the cosine of the angle between the vectors. This value will be close to -1 when the objects are back to back - you can use a test something like:-

var cos = Vector3.Dot(obj1.transform.forward, obj2.transform.forward);

if (cos < -0.9) {
   ...
}

If you need a precise angle, you can calculate the cosine from it using Mathf.Cos.

Well, that is what Vector3.Angle already does for you. :wink:

One reason to use Dot instead of Angle is if you can use the Dot product directly without taking the the cosine (Cos), or inverse cosine (Acos), then you can get a small speed increase (at least if you call this code a LOT, like many times per frame). But if you call the cosine or inverse cosine anyway, you might as well use Vector3.Angle which does it for you and thus give you shorter and more readable code.

Rune

That’s actually exactly what I ended up doing. My final function looks like:

bool CheckAttachment(Vector3 pos1, Quaternion rot1, Vector3 pos2, Quaternion rot2) {
	float angle = Vector3.Angle(rot1*Vector3.forward, rot2*Vector3.forward);
	float dist = Vector3.Distance(pos1, pos2);
	return angle > (180 - MaxSnapAngle)  dist < MaxSnapDistance;
}

I knew that I COULD solve it that way all along, but it seemed like extra steps that should be unnecessary, if Quanternion.Angle() did what I expected it to do. When that didn’t work as expected, I tried Quaternion.Dot(), and that gave me much stranger results, seemingly completely unrelated to the dot product of the two vectors resulting from the Quaternion.

I considered using the dot product of the vectors instead, for the speed reasons you mention, but this code doesn’t get called all THAT frequently, and I wanted to be able to specify MaxSnapAngle in degrees if possible (since it’s a value that’s exposed in the editor).

Thanks for your help, though. I’ve got a lot of experience in most areas of 3D graphics programming, but Quaternions are still kind of mysterious to me.