Rotating to face a point

Started by
10 comments, last by haegarr 14 years, 3 months ago
I have a turret that I need to rotate to fire at a point defined by the user (mouse click). I've done some reading around and have come up with some code, but it doesn't work:
float radians_to_degrees(float radians)
{
    return radians * (180.0f / PI);
}

float angle_between(const PointF& p1, const PointF& p2)
{
    return radians_to_degrees(atan2(p2.y - p1.y, p2.x - p1.x) * 180.0f / PI);
}

// ...

float angle = Geometry::angle_between(turret.Get_Position(),
    to_point_type<int, float>(turret.Get_Attack_Position()));

if(turret.Get_Y_Angle() >= angle - 1 && turret.Get_Y_Angle() <= angle + 1)
{ // Within range of target.
    if(turret.Get_Fire_Delay_Timer().Finished())
    {
        Fire_Projectile(*vehicle, turret_index);
        turret.Get_Fire_Delay_Timer().Start();
    }
}
else
{ // Not within range of target; need to move turret more.
    float move_by = turret.Get_Y_Angle() > angle ? -delta : delta;
    turret.Set_Y_Angle(turret.Get_Y_Angle() + move_by * turret.Get_Rotate_Speed());
}



The angles returned by angle_between seem to always be in the range of roughly 60 - 80 degrees... The quote below sounds like my problem, but I don't know how to translate what they are saying into code.
Quote: There's also a third option to consider, if you're trying to solve the "what way do I turn?" problem. Say you have a gun turret in 2d that can rotate at ω rad/s (at maximum) and you want it to face a target. If the unit vector from the turret to the target is u and the unit vector describing the heading of the turret is v, then the sign of the 2d scalar-valued "cross product" u x v gives you the direction to rotate in.
(link) Cheers.

Advertisement
Just from a quick look at look your code, it seems like you're converting from radians to degrees twice in angle_between: once in the function itself, and again by passing it through radians_to_degrees. Does fixing that help?
Haha I wouldn't be surprised...

I previously had it without the call to radians_to_degrees and it had similar results.

Ok, I changed the angle_between function, and also swapped the order of the points that are passed to it in the calling code (turret position was previously passed in first, turret attack point passed in second, they've now been swapped):
float Geometry::radians_to_degrees(float radians){	return radians * (180.0f / PI);}float Geometry::angle_between(const PointF& p1, const PointF& p2){    return radians_to_degrees(atan2(p2.y - p1.y, p2.x - p1.x));}float angle = Geometry::angle_between(to_point_type<int, float>(turret.Get_Attack_Position()),                turret.Get_Position());


Which I think is what I had previously, but it's still not working.

I found some more reading material on the subject, but I'm already doing what they've wrote and it's not working.

Any help is appreciated.

Cheers.

The name "angle_between(p1,p2)" is somewhat misleading, because it suggests to compute the angle from p1 to p2 w.r.t. a common reference point. But in fact it computes the angle from the cardinal x direction up to the difference vector p2-p1. In other words, it puts a globally aligned, local co-ordinate system into the turret, and computes the angle of the attack position w.r.t. that local space.

Computing the local space angle is probably not wrong, because it seems principally to coincide with the "turret.Get_Y_Angle" stuff. But it is at least misleading (I, for example, have been trapped during my 1st reading of the OP).

Besides that, there seems me no error in radians_to_degrees or angle_between. However, later on, are you sure that you have to compare turret.Get_Y_Angle with the required attack angle which is measured from cardinal x direction? In other words, is Y_Angle of the turret also measured from the cardinal x direction (and not from y direction)? Or perhaps the conversion of the click's screen position to the world space is wrong?

Further, comparisons like
turret.Get_Y_Angle() >= angle - 1 && turret.Get_Y_Angle() <= angle + 1
will not work well because of the periodicity of sin/cos of angles. The result of atan2 is AFAIK in [-180,+180] after conversion to degrees. So you will get problems if the attack position lies close in the local negative x direction.

Later on there is a another hiccup with
float move_by = turret.Get_Y_Angle() > angle ? -delta : delta;
Assume that the turret is aiming at -179 degree, and the attack position is at +179 degree. Then the turret will rotate the long arc around (i.e. 358 degrees) instead of the minimal difference of 2 degrees.
Quote:Original post by haegarr
The name "angle_between(p1,p2)" is somewhat misleading, because it suggests to compute the angle from p1 to p2 w.r.t. a common reference point. But in fact it computes the angle from the cardinal x direction up to the difference vector p2-p1. In other words, it puts a globally aligned, local co-ordinate system into the turret, and computes the angle of the attack position w.r.t. that local space.

Computing the local space angle is probably not wrong, because it seems principally to coincide with the "turret.Get_Y_Angle" stuff. But it is at least misleading (I, for example, have been trapped during my 1st reading of the OP).

Sorry, but I have no idea what a globally aligned, local co-ordinate system is, nor the definition of cardinal. :(

Quote:Original post by haegarr
Besides that, there seems me no error in radians_to_degrees or angle_between. However, later on, are you sure that you have to compare turret.Get_Y_Angle with the required attack angle which is measured from cardinal x direction? In other words, is Y_Angle of the turret also measured from the cardinal x direction (and not from y direction)? Or perhaps the conversion of the click's screen position to the world space is wrong?

I compare the turret's y_angle with the angle between the turret and the target so that I know if the turret should rotate to accurately face the target.

Again, I'm not sure what cardinal means, but I am probably wrong comparing the y_angle of the turret against the angle between the turret and the target, as I am not sure if the angle is even correct as you mention (which is my whole problem - I don't know how to get the right angle).

Quote:Original post by haegarr
Further, comparisons like
turret.Get_Y_Angle() >= angle - 1 && turret.Get_Y_Angle() <= angle + 1
will not work well because of the periodicity of sin/cos of angles. The result of atan2 is AFAIK in [-180,+180] after conversion to degrees. So you will get problems if the attack position lies close in the local negative x direction.
What kind of problems? How can I fix them? Add 360 to the result if it's below -180 and subtract 360 if it's above +180?

Quote:Original post by haegarr
Later on there is a another hiccup with
float move_by = turret.Get_Y_Angle() > angle ? -delta : delta;
Assume that the turret is aiming at -179 degree, and the attack position is at +179 degree. Then the turret will rotate the long arc around (i.e. 358 degrees) instead of the minimal difference of 2 degrees.

True, I had thought about that but was more concerned about getting the angle correct first haha.

Cheers.

This is asked a lot. Short answer use a dot product. For the solution click here. That should be right. I seemed to have lost the link to my old pastebin write up so I had to write it again from scratch. It should also be noted that this problem comes up a lot when writing heat seeking 2D missiles.
Looks promising, Sirisian - thanks! Will try it out and get back to you.

Quote:Original post by Mybowlcut
Sorry, but I have no idea what a globally aligned, local co-ordinate system is, nor the definition of cardinal. :(

A co-ordinate system is a frame given by an origin (a point) and the necessary amount of, say for the sake of simplicity, mutually independent direction vectors. In the case of 3D, you need 3 direction vectors. You use a global co-ordinate system that is suitable to describe positions and orientations in the game world. The origin of this system has co-ordinates [0,0,0], and the direction vectors have co-ordinates [1,0,0], [0,1,0], and [0,0,1], usually called x, y, and z. That are the vectors I meant with "cardinal".

Note please that the origin and the 3 direction vectors are often rendered as an axis in 3D modeling apps. It is in fact a co-ordinate frame. Now you can define another co-ordinate frame, that has an origin at the global position pl, for example, and has direction vectors xl, yl, and zl also measured using the global frame. This new frame defines e.g. the position and orientation of the turret.

Because the origin and direction vectors can be formulated as a transformation, you can apply the inverse transformation to your world. What happens then is that pl becomes [0,0,0], and xl, yl, and zl become [1,0,0], [0,1,0], and [0,0,1], resp. Notice please that this doesn't mean that the turret is moved to the world origin or such, instead the entire world is transformed, each pair of objects has the same relative position and orientation as before. What we've done by applying the said transformation is in fact that we've changed the reference frame only. That is in short what makes a local co-ordinate system or space.

Quote:Original post by Mybowlcut
I compare the turret's y_angle with the angle between the turret and the target so that I know if the turret should rotate to accurately face the target.

You don't compute the angle between the turret and the target. Instead, you compute the angle between the cardinal x axis (i.e. [1,0,0], see above) and the direction vector to the target position. See that you substract the turret's position from the target position. That is in fact a step of the aforementioned inverse transformation, namely the inverse translation. Further you don't use the orientation of the turret to inversely rotate the world. So you still use the direction vectors of the global frame (but, as said, not its origin). Hence I spoke of a "globally aligned local frame".

Quote:Original post by Mybowlcut
... but I am probably wrong comparing the y_angle of the turret against the angle between the turret and the target, as I am not sure if the angle is even correct as you mention (which is my whole problem - I don't know how to get the right angle).

It is unclear how you've defined the y_angle of the turret. Is it the rotation angle around the cardinal y axis? Does the turret's cannon point into the cardinal x direction when the y_angle is 0? Is it rotating clockwise for increasing y_angle? If all is answered with "yes", then it is probably okay to use y_angle.

Quote:Original post by haegarr
Further, comparisons like
turret.Get_Y_Angle() >= angle - 1 && turret.Get_Y_Angle() <= angle + 1
will not work well because of the periodicity of sin/cos of angles. The result of atan2 is AFAIK in [-180,+180] after conversion to degrees. So you will get problems if the attack position lies close in the local negative x direction.
What kind of problems? How can I fix them? Add 360 to the result if it's below -180 and subtract 360 if it's above +180?
Let's assume that the y_angle is +179. Let's assume further that the target position is at 180 degree. Then the condition is
179 >= 180 - 1 && 179 <= 180 + 1 == true
but
179 >= -180 - 1 && 179 <= -180 + 1 == false
although +180 degree and -180 degree are different angles but the same orientation.

Besides that, remember that even for a constant angle, the distance of the point of impact from the targeted point increases with the distance the cannon ball has to travel. That means that if the turret's orientation is within +/- 1 degree of the direction to the target, it may be sufficient to hit the target if it is just 10 length units ahead, but it may totally miss it if it is 100 length units ahead.

A distance independent measure would be to check whether a ray (as a projected representation of the trajectory of the cannon ball) hits a circle around the target position. Chosing a constant radius for the circle, you can be sure that the cannon ball will impact ever close enough. However, that also means that you have to make sure that the turret can be rotated fine enough to reach the required value for each possible target position on the screen. I.e. it may not be sufficient to work with just one "delta" angle, because it means to rotate really slow if it is small enough or else to be not able to match the required orientation if it is too big.
Before I respond to that mammoth post by haegarr, Sirisian: I only use a y_angle variable in my code, whereas your snippet uses a "direction" Vector with x,y values. How can I translate the Vector to a single float?

Quote:Original post by haegarr
Quote:Original post by Mybowlcut
Sorry, but I have no idea what a globally aligned, local co-ordinate system is, nor the definition of cardinal. :(

A co-ordinate system is a frame given by an origin (a point) and the necessary amount of, say for the sake of simplicity, mutually independent direction vectors. In the case of 3D, you need 3 direction vectors. You use a global co-ordinate system that is suitable to describe positions and orientations in the game world. The origin of this system has co-ordinates [0,0,0], and the direction vectors have co-ordinates [1,0,0], [0,1,0], and [0,0,1], usually called x, y, and z. That are the vectors I meant with "cardinal".
Ok.

Quote:Original post by haegarr
Note please that the origin and the 3 direction vectors are often rendered as an axis in 3D modeling apps. It is in fact a co-ordinate frame. Now you can define another co-ordinate frame, that has an origin at the global position pl, for example, and has direction vectors xl, yl, and zl also measured using the global frame. This new frame defines e.g. the position and orientation of the turret.

Because the origin and direction vectors can be formulated as a transformation, you can apply the inverse transformation to your world. What happens then is that pl becomes [0,0,0], and xl, yl, and zl become [1,0,0], [0,1,0], and [0,0,1], resp. Notice please that this doesn't mean that the turret is moved to the world origin or such, instead the entire world is transformed, each pair of objects has the same relative position and orientation as before. What we've done by applying the said transformation is in fact that we've changed the reference frame only. That is in short what makes a local co-ordinate system or space.
I can understand that "the entire world is transformed", as a lecturer also explained it to me that way when we were programming games in OpenGL. But I don't understand how this applies to a 2D game?

Quote:Original post by haegarr
Quote:Original post by Mybowlcut
I compare the turret's y_angle with the angle between the turret and the target so that I know if the turret should rotate to accurately face the target.

You don't compute the angle between the turret and the target. Instead, you compute the angle between the cardinal x axis (i.e. [1,0,0], see above) and the direction vector to the target position.

This is my understanding of what you're saying (turret is blue, target is red, horizontal line is x-axis, direction vector is between the target and turret, angle between the cardinal x axis is the curved line):


Quote:Original post by haegarr
See that you substract the turret's position from the target position. That is in fact a step of the aforementioned inverse transformation, namely the inverse translation. Further you don't use the orientation of the turret to inversely rotate the world. So you still use the direction vectors of the global frame (but, as said, not its origin). Hence I spoke of a "globally aligned local frame".
Ok so I kinda see that I have to "inversely rotate the world", but I don't understand how.

Quote:Original post by haegarr
Quote:Original post by Mybowlcut
... but I am probably wrong comparing the y_angle of the turret against the angle between the turret and the target, as I am not sure if the angle is even correct as you mention (which is my whole problem - I don't know how to get the right angle).

It is unclear how you've defined the y_angle of the turret. Is it the rotation angle around the cardinal y axis?
Yes.

Quote:Original post by haegarr
Does the turret's cannon point into the cardinal x direction when the y_angle is 0?
No, it points to the top of the screen when the y_angle is 0, to the right of screen when the angle is -90, bottom when -180, etc.

Quote:Original post by haegarr
Is it rotating clockwise for increasing y_angle? If all is answered with "yes", then it is probably okay to use y_angle.
No, rotating clockwise decreases the y_angle I'm pretty sure.

Quote:Original post by haegarr
Quote:Original post by haegarr
Further, comparisons like
turret.Get_Y_Angle() >= angle - 1 && turret.Get_Y_Angle() <= angle + 1
will not work well because of the periodicity of sin/cos of angles. The result of atan2 is AFAIK in [-180,+180] after conversion to degrees. So you will get problems if the attack position lies close in the local negative x direction.
What kind of problems? How can I fix them? Add 360 to the result if it's below -180 and subtract 360 if it's above +180?
Let's assume that the y_angle is +179. Let's assume further that the target position is at 180 degree. Then the condition is
179 >= 180 - 1 && 179 <= 180 + 1 == true
but
179 >= -180 - 1 && 179 <= -180 + 1 == false
although +180 degree and -180 degree are different angles but the same orientation.
So then my adjustment would work, yes?

Quote:Original post by haegarr
Besides that, remember that even for a constant angle, the distance of the point of impact from the targeted point increases with the distance the cannon ball has to travel. That means that if the turret's orientation is within +/- 1 degree of the direction to the target, it may be sufficient to hit the target if it is just 10 length units ahead, but it may totally miss it if it is 100 length units ahead.
That makes sense.

Quote:Original post by haegarr
A distance independent measure would be to check whether a ray (as a projected representation of the trajectory of the cannon ball) hits a circle around the target position. Chosing a constant radius for the circle, you can be sure that the cannon ball will impact ever close enough. However, that also means that you have to make sure that the turret can be rotated fine enough to reach the required value for each possible target position on the screen. I.e. it may not be sufficient to work with just one "delta" angle, because it means to rotate really slow if it is small enough or else to be not able to match the required orientation if it is too big.
I understand this problem I'm pretty sure. What other angles could I use to ensure that the turret is accurate but can still rotate quickly?

Thanks a lot for putting all that time and effort into the post. :)

This topic is closed to new replies.

Advertisement