Problem with acos

Started by
8 comments, last by Black Knight 14 years, 2 months ago
OK here is my function to calculate the angle between two vectors.


float AngleBetweenVectors(const Vector3& vector1,const Vector3& vector2)
	{							
		//A.B
		float dotProduct = Dot(vector1,vector2);				
		
		//|A|*|B|
		float vectorsMagnitude = vector1.magnitude() * vector2.magnitude();

		//avoid division by zero
		if(vectorsMagnitude == 0)
			return 0;
		
		//cos(theta) = A.B / |A|*|B|
		float angle = acos( dotProduct / vectorsMagnitude );

		//check the number
		if(_isnan(angle))
		{
		   return 0;
		}
			
		return angle;
	}	

I use this to check if the player is facing a NPC in my game.I calculate the vector from the player to the NPC and normalize it and then I calculate the normalized facing vector of the player and use the above method to calculate the angle between these vectors.If the angle between them is less than 60 degress the player is facing the NPC.Everything works perfect except sometimes when I am facing the NPC exactly 180 degrees away the method fails and tells me that I am facing the NPC.I stepped through the code and check the values in this method.THe dot product returns -1 which is correct as the two vectors are facing away from each other the vector magnitude returns 0.99999 something which is almost correct,but then the acos(dot/magnitude) returns a indefinite number which is caught by isnan and the method returns 0 where infact the angle is 180.So whats up?Is this a precision problem? I made a dirty fix like this in the isnan part : //check the number if(_isnan(angle)) { if(dotProduct < 0) return 180; else return 0; } But it is ugly so is there any way to solve this problem maybe by using doubles or something else?
Advertisement
acos requires an argument greater or equal -1 and less or equal +1. In your case you give it fabs(1.0/0.999) > 1, and that cause the trouble, I think.
Quote:from acos man page
acos(x) returns a NAN and raises the "invalid" floating-point exception for |x| > 1.

You should restrict the argument to avoid the problem:
float arg = dot/magnitude;if(arg>+1) arg = +1;if(arg<-1) arg = -1;float angle = acos(arg);bool iSeeYou = angle < viewConeAngle;


Even better, you avoid to use acos at all. You can do somethink like
float arg = dot/magnitude;bool iSeeYou = arg > cosOfViewConeAngle;

haegarr's suggestion is probably the way to go, but if for some reason you do want to compute the angle itself, a more robust way to compute the (unsigned) angle between two vectors in 3-d is as follows:
angle = atan2(length(cross(a, b)), dot(a, b));
Quote:I calculate the vector from the player to the NPC and normalize it and then I calculate the normalized facing vector of the player and use the above method to calculate the angle between these vectors.If the angle between them is less than 60 degress the player is facing the NPC.
Why not skip the acos and the isnan stuff and just check whether the dot product of the two vectors is greater than cos(60°), that is, 0.5?
Quote:Why not skip the acos and the isnan stuff and just check whether the dot product of the two vectors is greater than cos(60°), that is, 0.5?
I think that's what haegarr was getting at in his code sample (if I'm reading it right, that is).
Quote:Original post by jyk
haegarr's suggestion is probably the way to go, but if for some reason you do want to compute the angle itself, a more robust way to compute the (unsigned) angle between two vectors in 3-d is as follows:
angle = atan2(length(cross(a, b)), dot(a, b));


If it's not too much trouble, mind explaining how that works? I'm having a little trouble visualizing it.
Quote:Original post by nullsquared
If it's not too much trouble, mind explaining how that works?
For sure:

|a x b| = |a| * |b| * sin( <a,b> )
a . b = |a| * |b| * cos( <a, b> )

|a x b| / (a . b) = sin( <a,b> ) / cos( <a,b> ) = tan( <a,b> )

Hence
<a,b> = atan( |a x b| / (a . b) ) = atan2( |a x b|, (a . b) )


EDIT: tan( y/x ) is in general not really the same as atan2( y, x ) because atan2 considers all quadarants.

[Edited by - haegarr on February 18, 2010 8:26:33 AM]
Quote:Original post by jyk
Quote:Why not skip the acos and the isnan stuff and just check whether the dot product of the two vectors is greater than cos(60°), that is, 0.5?
I think that's what haegarr was getting at in his code sample (if I'm reading it right, that is).
Yep, you're right, that was my intention.
Quote:Original post by haegarr
Quote:Original post by nullsquared
If it's not too much trouble, mind explaining how that works?
For sure:

|a x b| = |a| * |b| * sin( <a,b> )
a . b = |a| * |b| * cos( <a, b> )

|a x b| / (a . b) = sin( <a,b> ) / cos( <a,b> ) = tan( <a,b> )

Hence
<a,b> = atan( |a x b| / (a . b) ) = atan2( |a x b|, (a . b) )


Oh cool, thanks for the explanation [smile]
Great help guys thank you.

This topic is closed to new replies.

Advertisement