Sign in to follow this  
Black Knight

Problem with acos

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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;

Share this post


Link to post
Share on other sites
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));

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this