Sign in to follow this  
gorogoro

LookAt function

Recommended Posts

Hi! I'm having a lot of problems doing a LookAt function. The lookat function should rotate the agent so it faces some position, or direction. Here is the function:
bool Agent::LookAtDirection(Vector3f& dir, float angle, float speed /*=0*/)
{
	if(!speed)
		speed = GetMaxSpeed();

	dir.Normalize();

	Vector3f direction = GetWorldDirection();

	Vector3f upVector;
	GetWorldRotation().TransformVector(&upVector, Vector3f::k);

	float actualAngle = direction.Dot(dir);


	Vector3f c;
	float amount = 1;
	c.Cross(direction, dir);

	if (upVector.Dot(c) < 0)
		amount = -amount;
			
	if ( !((actualAngle >= angle)))
	{
            SetDesirableRotateZSpeed(-amount * speed);
		return false;
	}
}


As you notest I have a variable named amount this is used to determine to wich direction should the agent turn, to the left or to the rigth. But sometimes the agent passes the angle, and enters in the if, then the amount change the signal and agent turns to the other side. So the agent does'nt move and keeps shaking. The angle is the aperture angle so it is safe to say, ok you are aligned. The angle should vary accordingly with the distance. So as longer the distance the angle should be closer to 1, else it should be closer to 0.85 or something like that. For the angle I have this function:

float Agent::CalculateAngle(const Vector3f& pos)
{
	float sqrdist = GetWorldPosition().SquareDistance(pos);
	float alpha = sqrdist / (1600 + sqrdist);

	//alpha = cos(pi / 2 - atan(sqrdist));
	// alpha = sqrdist / sqrt( 500 + sqrdist * sqrdist );
	return ClampMinMax(alpha, 0.85f, 0.97f);
}


The comment code is some experience based on a post in this forum to. Basically I've tried to change the aperture of the angle, but or it is to much and the agent thinks that is in front of something and it isn't or else it starts shaking :p. I've tried to make some signal test, I mean when the variable amount changes its signal i return true. But it makes the same shake problem sometimes. I've also tried to slow down the rotation speed so it should not "jump" through the angle, without any positive result also. Now I'm running out of ideias. :p Have anyone passed through the same problem? Any more ideas?

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
a PD Controller
or more generically a PID Controller

generates a control output (for example how fast to turn) based on P proportional, I integral, and D derivative inputs.


in your case 'P' would be the difference in degrees between current direction and target direction

'D' would be the deriviative of 'P' -how fast that angle difference changes over time

and 'I' we shall ignore - this is more useful for stuff like Air Conditioners


psuedocode PID Controller:

[code]
class PID{
float P,I,D;
void init(float p,float i,float d){P=p;D=d,I=i;}

float controldecision(float error){
static float deriv=0;
static float lasterror=0;
static float integral=0;

deriv=error-lasterror;
lasterror=error;
integral+=error;

return (P*error)+(I*integral)+(D*deriv);

}

}
[/code]



That code uses Static variables, so you will need a new instance for each object being controlled.
the idea is that each gameloop (better have a fixed timestep) you will call controldecision(), and pass the signed difference in degrees between your direciton and target as an argument. it will return a float, which you will use as your rotation force.
The trick of course, is initializing the Controller with Good values for P,I,and D.
it needs to be 'tuned' to work with whatever you are trying to control.
In your case, i'd leave 'I' as 0.
Start out with P and D as something small, like .5 for now
experiment with them, which one is larger or smaller, etc, till it starts acting properly.
if it occilates too much (back and fourth motion) try increasing D
if it doesnt move fast enough, try increasing P

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Special note:
the angle input you pass to controldecision
Must be the Relative angle between your current direction, and your goal direction; from the character's reference frame. Do not use absolute coord system angles, since that will mess up....
and it should be signed so that for example left is - and right is + (or vice versa)

Share this post


Link to post
Share on other sites
Quote:
Original post by Anonymous Poster
Special note:
the angle input you pass to controldecision
Must be the Relative angle between your current direction, and your goal direction; from the character's reference frame. Do not use absolute coord system angles, since that will mess up....
and it should be signed so that for example left is - and right is + (or vice versa)


Thanks a lot for the help!
Never heard about that PID controller! Do you have any reference of interest? I've google it, but didn't find any really interesting!

By the way! If you my lookAt function returns a boolean telling if the agent is already at position or not. How can I now that with a PID controller? When the returning value its close to 0??

[Edited by - gorogoro on September 6, 2006 9:57:05 AM]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Yep,
I got into these myself when I was doing a similar problem to your's (spaceship rotation controls) and I heard vaguely that PID was a control method used in robotics...

Its kinda hard to find good tutorials or explanations that aren't math/engineering intensive, since thats apparently the field they come from, but this link helped me a lot
http://www.embedded.com/2000/0010/0010feat3.htm
has a lot of nice graphs of the resulting motions of a simple motor and stuff, so you can get an intuitive feel for how the adjustments work

the wikipedia entry actually looks pretty good too...

Share this post


Link to post
Share on other sites
Get any introductory text book on Control Theory and it will cover, in depth, PID-based control. My personal recommendation is 'Automatic Control Systems' by Benjamin Kuo. Books such as this, aimed at 2nd and 3rd year engineering students, typically cover the mathematical preliminaries you need for understanding and analysis in their early chapters, so if you're coming from a non-engineering background, you shouldn't have too much trouble learning the material (particularly if you keep a basic calculus book beside you as your read it).

As to where PIDs are used, well, it's not just robotics. Indeed, around 95% of controllers used in commercial control applications (and here I mean everything from spacecraft and aircraft flight systems through to thermostats in your house) are PI or PD controllers (there are very few full PID controllers, since they are generally not needed).

If you want more specific information or need help understanding PIDs, just holler.

Cheers,

Timkin

Share this post


Link to post
Share on other sites
Freaking out once again! :p

I've read the wiki tutorial and the paper you post, but didn't understand to much about the process. Have to read more times I guess :P

I've implemented the pidController just like yours.

But the behaviour is exactly the same :(. It never stops from oscilate, even if I increase the D value or the P value. I'm seek of trying to tunne this values without good results. Humpf.

I've levead the CalculateAngle function.

Here are the alterations I've done to the LookAt function:


bool Agent::LookAtDirection(Vector3f& dir, float angle, float speed /*=0*/)
{
if(!speed)
speed = GetMaxSpeed();

dir.Normalize();

Vector3f direction = GetLocalDirection();

Vector3f upVector;
GetWorldRotation().TransformVector(&upVector, Vector3f::k);

float actualAngle = direction.Dot(dir);
//float error = angle - actualAngle;

if(actualAngle >= angle)
actualAngle = 0;
else
actualAngle = 1 - actualAngle;


//TODO: Think of a way to do this in 3D (plain behavior!!)
Vector3f c;
float amount = 1;
c.Cross(direction, dir);
if (upVector.Dot(c) < 0)
amount = -amount;

float amountAux = m_pidController.ControlDecision( -amount * (actualAngle));


std::string text("PIDController returned: ");
SharedTools::RealToString(amountAux, text);
FE_LOG(text);
if ( amountAux < 0.05 && amountAux > -0.05)
return true;

SetDesirableRotateZSpeed(amountAux);
return false;
}






any more suggestions? I'm doing anything wrong?
[depressed]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
first thing I notice:

what does


if ( amountAux < 0.05 && amountAux > -0.05)
return true;


do?

Looks to me like if the output gets small, you cancel out of the function and don't do any rotation force
is that what it's doing?

because... thats bad, having a 'deadband' in the middle will certainly cause problems with the controller losing control at the critical moment...

try commenting that part out


There might also be issues with the way you input your angle stuff to the controller, but i havent looked at that part in detail yet, fix this obvious one first...

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
alternatly, since I think you probably wanted to keep the return value for some other purposes... change this:


if ( amountAux < 0.05 && amountAux > -0.05)
return true;

SetDesirableRotateZSpeed(amountAux);
return false;




to this:


SetDesirableRotateZSpeed(amountAux);

if ( amountAux < 0.05 && amountAux > -0.05)
return true;
return false;




Bascially, even though you are 'close enough' to target and return 'true' that youre pointed at it, still do apply the rotation force
that PD Controller needs to be operative even when you are 'close enough' so it can make a finely controlled stop, and keep you from occilating...

Share this post


Link to post
Share on other sites
Tunning tunning! Can't stable this thing.

Ok! Some changes done. Even with low values I make the rotation now. And I've changed the way I calculate the error (PID input).

Now it is like this:



bool Agent::LookAtDirection(Vector3f& dir, float angle, float speed /*=0*/)
{
if(!speed)
speed = GetMaxSpeed();

dir.Normalize();

Vector3f direction = GetLocalDirection();

Vector3f upVector;
GetWorldRotation().TransformVector(&upVector, Vector3f::k);

float actualAngle = direction.Dot(dir);
float error;

if(actualAngle >= angle)
error = 0;
else if(actualAngle > 0)
error = angle - actualAngle;
else
error = -actualAngle + angle;

Vector3f c;
float amount = 1;
c.Cross(direction, dir);
if (upVector.Dot(c) < 0)
amount = -amount;

float amountAux = m_pidController.ControlDecision( -amount * (error));


std::string text("PIDController returned: ");
SharedTools::RealToString(amountAux, text);
FE_LOG(text);

SetDesirableRotateZSpeed(amountAux);

if ( amountAux < 0.05 && amountAux > -0.05)
return true;

return false;
}



Since the angle is the cos(alpha).
The angle is calculated the same way, using the CalculateAngle function posted in the begging of the thread.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by Eldritch
I would have set a target angle and simply rotated the agent according to an interpolated value between the start and target angles.


Depends if your agent motion is based on physics or if you just want graphical smoothness...

I use the method you describe for my camera, but PD Controllers with Newtonian physics for all in-game entities

Share this post


Link to post
Share on other sites
Quote:
Original post by Eldritch
I would have set a target angle and simply rotated the agent according to an interpolated value between the start and target angles.


I've tried that with no good results

Quote:
Original post by Anonymous Poster
Depends if your agent motion is based on physics or if you just want graphical smoothness...

I use the method you describe for my camera, but PD Controllers with Newtonian physics for all in-game entities


The motion is based on physics. But I'm not getting there with the PD Controller either....

Any other suggestion?? This is suposed to be a simple problem, but I'm not getting there :'(

Damn!

[Edited by - gorogoro on September 11, 2006 9:15:03 AM]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Okay... lets look at your code line by line...

I'm going to repost your code, with Comments and Questions added
I suggest, that the best way for you to answer my questions, is to repost this, with even more comments of your own added


//tell me about dir and angle, what do they represent?
//is one the target and one the current orientation? which is which?
bool Agent::LookAtDirection(Vector3f& dir, float angle, float speed /*=0*/)
{
if(!speed)
speed = GetMaxSpeed();

dir.Normalize();

//is this returning the current direction your character faces?
Vector3f direction = GetLocalDirection();

Vector3f upVector;
GetWorldRotation().TransformVector(&upVector, Vector3f::k);

//here you seem to be finding the dot produce between direction and dir
//but I dont know exactly what each one means...

//if the angle between them is small... this should return near1
//if they are near perpendicualr, it shoudl return near 0
//obtuse angles are negative...
//it tells nothing about rotating left or right, just magnitude
//are you sure that dot product is what you wanted?
//it's not Exactly the same as the actual angle between them...
//BUG?
float actualAngle = direction.Dot(dir);
float error;

//as usual, what is 'angle'?
if(actualAngle >= angle)
error = 0;
else if(actualAngle > 0)//both of these branches are the same!!??
error = angle - actualAngle;
else
//-actualangle+angle is the same as angle-actualangle above
//this IF doesnt actually do anything
//BUG?
error = -actualAngle + angle;

//not sure where you're going with this part...
//complicated, can you explain?
Vector3f c;
float amount = 1;
c.Cross(direction, dir);
if (upVector.Dot(c) < 0)
amount = -amount;

float amountAux = m_pidController.ControlDecision( -amount * (error));


std::string text("PIDController returned: ");
SharedTools::RealToString(amountAux, text);
FE_LOG(text);

SetDesirableRotateZSpeed(amountAux);

if ( amountAux < 0.05 && amountAux > -0.05)
return true;

return false;
}








Here's how I might do it (if debugging all my comments above doesnt work)
(its good to help think about it anyway)
a lot simpler anyhow

given:
vector goaldirection //aim here
vector currendirection //currently we face here
vector up //the world UP direction

...
vector rightside=cross(currendirection,up) //this might be backwards
float turnapprox= dot(goaldirection,rightside)
//turnapprox will return +1 when the direction to turn is near rightside
//-1 when direction to turn is opposite to rightside
//0 when nearly centered
//potential bug, it doesnt check if goal is in front or back of himself
//only left/right side comparision, this is OK for most cases, since we just turn...
float amountturn = m_pidController.ControlDecision( turnapprox);

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
P.S.
I notice you are in Portugal
I am in the USA
my morning is your afternoon
and my afternoon is your night

communication may be faster if you check this messageboard around 5-10 evenings your time, since then I will also be online and might respond faster




Also, to fix the 'check front/back' bug in my example code, all you have to do is do another dot product against the currentdirection vector(forward)
and increase the magnitude of error value if target is behind (just to make it respond faster)
..but save that for later.. keep simple first

Share this post


Link to post
Share on other sites
Ok!

Let's do it.


//The angle is the acceptable angle so we can say: "Ok your are facing the
//rigth direction!
//It varys, so if the agent is far from the thing it wants to face the angle is
//near 0.97 (near 1). Else if the agent is close then the acceptable angle is
//near 0.70
bool Agent::LookAtDirection(Vector3f& dir, float angle, float speed /*=0*/)
{
if(!speed)
speed = GetMaxSpeed();

dir.Normalize();

//is this returning the current direction your character faces?
//YES
Vector3f direction = GetLocalDirection();

Vector3f upVector;
GetWorldRotation().TransformVector(&upVector, Vector3f::k);

//here you seem to be finding the dot produce between direction and dir
//but I dont know exactly what each one means...

//Here I'm just finding the up vector of the agent.
//this is used just to determine if the agent should turn to the right or to the left


//if the angle between them is small... this should return near1
//if they are near perpendicualr, it shoudl return near 0
//obtuse angles are negative...

//Exacly, thats the point

//it tells nothing about rotating left or right, just magnitude
//are you sure that dot product is what you wanted?
//it's not Exactly the same as the actual angle between them...
//BUG?

//the point here is to determine de cos(angle) of the agent and the
//goal diretion it wants to face.
float actualAngle = direction.Dot(dir);
//Dot product from the agent orientation and the goaldirection

float error;

//if the Dot product from the agent orientation and the dir is longer than the angle then it is facing the goaldirection
if(actualAngle >= angle)
error = 0;
else if(actualAngle > 0)//both of these branches are the same!!??
error = angle - actualAngle;
else
//-actualangle+angle is the same as angle-actualangle above
//this IF doesnt actually do anything
//BUG?
//you are rigth. this is not a bug, but an useless code.
error = -actualAngle + angle;

//not sure where you're going with this part...
//complicated, can you explain?
//this is used to determine to wich side the agent turns (left or rigth)
Vector3f c;
float amount = 1;
c.Cross(direction, dir);
if (upVector.Dot(c) < 0)
amount = -amount;
//and is working fine :)


float amountAux = m_pidController.ControlDecision( -amount * (error));


//debug stuff
std::string text("PIDController returned: ");
SharedTools::RealToString(amountAux, text);
FE_LOG(text);

SetDesirableRotateZSpeed(amountAux);

if ( amountAux < 0.05 && amountAux > -0.05)
return true;

return false;
}




I'm going to put a cleaner code now.


bool Agent::LookAtDirection(Vector3f& goalDirection, float acceptableAngle, float speed /*=0*/)
{
//Speed is deprecated
if(!speed)
speed = GetMaxSpeed();

dir.Normalize();

//returning the current direction the character face
Vector3f agentDirection = GetLocalDirection();

//the point here is to determine de cos(angle) of the agent and the
//goal diretion it wants to face.
float actualAngle = agentDirection.Dot(goalDirection);
//Dot product from the agent orientation and the goaldirection
//if the angle between them is small... this should return near 1
//if they are near perpendicualr, it shoudl return near 0
//obtuse angles are negative...

float error;

//if the Dot product from the agent orientation and the dir is longer than the angle then it is facing the goaldirection
if(actualAngle >= acceptableAngle)
error = 0;
else
error = acceptableAngle - actualAngle;

//this is used to determine to wich side the agent turns (left or rigth)
//GetWorldRoation gives the world rotation of the agent
Vector3f upVector;
GetWorldRotation().TransformVector(&upVector, Vector3f::k);

Vector3f c;
float amount = 1;
c.Cross(agentDirection, goalDirection);
if (upVector.Dot(c) < 0)
amount = -amount;

//and is working fine :)

//the pid controller
float amountAux = m_pidController.ControlDecision( -amount * (error));

//debug stuff
std::string text("PIDController returned: ");
SharedTools::RealToString(amountAux, text);
FE_LOG(text);

SetDesirableRotateZSpeed(amountAux);

if ( amountAux < 0.05 && amountAux > -0.05)
return true;

return false;
}





Now I'm going to try something simpler as you suggested :)

[Edited by - gorogoro on September 12, 2006 6:54:07 AM]

Share this post


Link to post
Share on other sites
Thanks a Lot for your help Anonymous Poster.

It is working fine now with no occilations (at least it seams like that). More testing to come, but I have to move on..

Here is my final code:



bool Agent::LookAtDirection(Vector3f& goalDirection, float angle, float speed /*=0*/)
{
//speed deprecated
if(!speed)
speed = GetMaxSpeed();

goalDirection.Normalize();

Vector3f up;
GetWorldRotation().TransformVector(&up, Vector3f::k);

Vector3f rightside;
rightside.Cross(GetLocalDirection(), up); //this might be backwards
float turnapprox = goalDirection.Dot(rightside);

float wtf = GetLocalDirection().Dot(goalDirection);

float amountturn = m_pidController.ControlDecision( turnapprox );

std::string text("PIDController returned: ");
SharedTools::RealToString(amountturn, text);
FE_LOG(text);

SetDesirableRotateZSpeed(amountturn);
if ( amountturn < 0.05 && amountturn > -0.05)
return true;
return false;
}



[Edited by - gorogoro on September 12, 2006 9:02:51 AM]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Cool, glad it worked out. and now you have a new PID Controller tool for other stuff.

actually, in the approach I suggested, dotting rightside with goaldirection isn't measureing the actual angle at all (in fact, i dont think we even normalized anything)
but it works anyway because that dot product converges to 0 when the angle is also near 0
So it's kinda a hack, but as long as they converge to the same critical point the overall behaviour is usable -cool tip for future ideas

Share this post


Link to post
Share on other sites
Quote:
Original post by Anonymous Poster
Cool, glad it worked out. and now you have a new PID Controller tool for other stuff.

actually, in the approach I suggested, dotting rightside with goaldirection isn't measureing the actual angle at all (in fact, i dont think we even normalized anything)
but it works anyway because that dot product converges to 0 when the angle is also near 0
So it's kinda a hack, but as long as they converge to the same critical point the overall behaviour is usable -cool tip for future ideas


Indeed!

I have to normalize the direction vector! Otherwise the pid controller returns a huge values!

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