Sign in to follow this  
Mybowlcut

Rotating to face a point

Recommended Posts

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.

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


Link to post
Share on other sites
Quote:
Original post by Mybowlcut
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?
That's covered in the first line of code?
Vector objectDirection(Cos(objectAngle), -Sin(objectAngle));

At the bottom I mentioned that you can go from a vector back into an angle if you want using Atan2 which does Atan and does the quadrant offsets essentially.

Share this post


Link to post
Share on other sites
Quote:
Original post by Mybowlcut
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?

The said principle is totally independent of the dimensionality. It works for 2D, 3D, 4D, ... worlds. The difference is just that the amount of components of vectors and matrices are adapted appropriately.

Quote:
Original post by Mybowlcut
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):

Exactly.

Quote:
Original post by Mybowlcut
Ok so I kinda see that I have to "inversely rotate the world", but I don't understand how.

Whether you actually have to do so depends on what comes next. You just have to ensure to not compare apples and oranges. Make sure to always use the same reference.

However, _if_ you want to chose the turret's local orientation as reference, then you have to compute the rotation matrix of the turret w.r.t. the world, multiply it with the turret's translation matrix to get the entire transformation, and invert that matrix.

Quote:
Original post by Mybowlcut
Quote:
Original post by haegarr
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.

Here we get a bit out of sync. If you have a 2D world, is it a top-down game? Then I assume the cardinal y axis to point out of the screen. Else I would not understand how the turret rotates around y. Please describe in more detail how your world is defined w.r.t. co-ordinates.

However, it seems me that there's a problem. I assume the global cardinal x axis from left to right, and the z axis from bottom to top (or perhaps from top to bottom). You said y_angle is 0 when the turret points upwards, but the angle (to the target) is 0 when pointing to the right (atan2(0,1) is 0). If so, you cannot compare the both without transforming at least one of them.

Quote:
Original post by Mybowlcut
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?

Make the rotation speed dependent on the difference angle. For example, use a constant (maximum) delta angle as long as the absolute difference is greater than 45 degree, and use a linearly decreased delta if is is less. The minimum delta should be greater than 0 and less than the aforementioned minimum angle needed to reach all required target positions. Something like that.

If you choose the above way, then probably
if( diff > 45 ) {
delta = max_delta;
}
else {
delta = min_delta + ( diff / 45 ) * ( max_delta - min_delta );
}

should do the job.

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