• Create Account

## Homing missile problem

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

24 replies to this topic

### #1Kain5056  Members

502
Like
2Likes
Like

Posted 05 June 2014 - 07:14 AM

I'm trying to follow this Actionscript tutorial for homing missiles, translating it to C++ with SFML.

But I seem to have a problem with this piece of code:

In Actionscript:

var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI;
if (Math.abs(rotation - missile.rotation) > 180)
{
if (rotation > 0 && missile.rotation < 0)
missile.rotation -= (360 - rotation + missile.rotation) / ease;
else if (missile.rotation > 0 && rotation < 0)
missile.rotation += (360 - rotation + missile.rotation) / ease;
}
else if (rotation < missile.rotation)
missile.rotation -= Math.abs(missile.rotation - rotation) / ease;
else
missile.rotation += Math.abs(rotation - missile.rotation) / ease;

Translated in C++ with SFML:

int current = rocket_sprite.getRotation();
if( abs( rotation - current ) > 180 )
{
if( rotation > 0 && current < 0 ) rotation -= ( 360 - rotation + current ) / ease;
else if( current > 0 && rotation < 0 ) rotation += ( 360 - rotation + current ) / ease;
}
else if( rotation < current ) rotation -= abs( current - rotation ) / ease;
else rotation += abs( rotation - current ) / ease;

With this code, the missile acts crazy, getting stuck in places, continually rotating in place for no reason and other stuff like that.

I suspect that the problem lies in the fact that .getRotation() returns absolute values, but I don't know how to even test it.

Here is my complete code:

#include <math.h>
#define PI 3.14159265

class rocket
{
private:
sf::Texture rocket_texture;
sf::Sprite rocket_sprite;
sf::Vector2f position;
int rotation;

public:
rocket();
void rocket_show( sf::RenderWindow * window );
void set_position( sf::Vector2f new_position );
void rocket_rotate( sf::RenderWindow * window );
void rocket_move();
};

rocket::rocket()
{
rotation = 0.f;
}

{
rocket_sprite.setTexture( rocket_texture );

return true;
}

void rocket::rocket_show( sf::RenderWindow * window )
{
rocket_sprite.setOrigin( rocket_texture.getSize().x / 2 , rocket_texture.getSize().y / 2 );
rocket_sprite.setPosition( position );
window->draw( rocket_sprite );
}

void rocket::set_position( sf::Vector2f new_position )
{
position = new_position;
}

void rocket::rocket_rotate( sf::RenderWindow * window )
{
sf::Vector2i mouse_position = sf::Mouse::getPosition( *window );
int targetx = mouse_position.x - position.x;
int targety = mouse_position.y - position.y;

rotation = atan2( targety , targetx ) * 180 / PI;

int ease = 5;
int current = rocket_sprite.getRotation();
if( abs( rotation - current ) > 180 )
{
if( rotation > 0 && current < 0 ) rotation -= ( 360 - rotation + current ) / ease;
else if( current > 0 && rotation < 0 ) rotation += ( 360 - rotation + current ) / ease;
}
else if( rotation < current ) rotation -= abs( current - rotation ) / ease;
else rotation += abs( rotation - current ) / ease;

rocket_sprite.setRotation( rotation );
}

void rocket::rocket_move()
{
int speed = 5;
float speedx = 0;
float speedy = 0;
speedx = speed * ( 90 - abs( rotation ) ) / 90;
if( rotation < 0 ) speedy = -speed + abs( speedx );
else speedy = speed - abs( speedx );
position.x += speedx;
position.y += speedy;
}

If anyone can point me to the right direction, I would really appreciate it. :-)

Edited by Kain5056, 05 June 2014 - 07:30 AM.

### #2Orymus3  Members

18543
Like
0Likes
Like

Posted 05 June 2014 - 07:28 AM

Oh! I went through the exact same issue, let me grab how I fixed it! (will edit)

-=- My Articles -=-
Getting Games Done - Method and tools on how to start a hobby project and get it Done!

The Art of Enemy Design in Zelda: A Link to the Past - Reverse-engineering functional enemy design from applied example.

Retro Mortis - "RTS" - Article Series (4 Parts) on the history of RTS development (4th part finally released!!!)

### #3Orymus3  Members

18543
Like
3Likes
Like

Posted 05 June 2014 - 07:34 AM

In Dartlang, using the StageXL Lib (you can probably work out the details)

rotationDistance += (rotationDistance > math.PI) ? - 2 * math.PI : (rotationDistance < -math.PI) ? 2 * math.PI : 0;

This is the core logic of the function that set the actual angle of rotation (I had another handle the actual target rotation towards which I was headed).

I realize it's a bit of a complex line, but it was the only way I found out how to fix the angle issue (using radians).
Ended up much simpler than actual degrees.

I know that it ended up working, but there may be better ways to do this, one of the gurus here will probably give an even better solution.

(Edit: That's some dusty old code... my personal next step was leading the target, e.g. making an assumption as to the future target's position and target that as opposed to its current position)

Edited by Orymus3, 05 June 2014 - 07:38 AM.

-=- My Articles -=-
Getting Games Done - Method and tools on how to start a hobby project and get it Done!

The Art of Enemy Design in Zelda: A Link to the Past - Reverse-engineering functional enemy design from applied example.

Retro Mortis - "RTS" - Article Series (4 Parts) on the history of RTS development (4th part finally released!!!)

### #4haegarr  Members

7189
Like
6Likes
Like

Posted 05 June 2014 - 07:45 AM

POPULAR

AFAIS: ActionScript's atan2() returns a value in [+pi,-pi], so that the value of variable "rotation" is in [-180,+180]. Your equivalent seems to be sf::Transformable::getRotation(), where the result is stored in "current". The documentation says that getRotation() returns a value in [0,360]. However, your code implementation dealing with the delta rotation doesn't take this difference into account.

For example, if this condition

if( abs( rotation - current ) > 180 )

becomes true, inside its body there are 2 branches, one requiring "current" to be negative and the other requiring "rotation" to be negative, but they are never negative by definition! As a result, the "rotation" is not altered, and you suffer from "stuck in places".

Edited by haegarr, 05 June 2014 - 07:52 AM.

### #5Kain5056  Members

502
Like
0Likes
Like

Posted 05 June 2014 - 08:09 AM

@Orymus3: Thanks, but I'm not sure how to implement your code, or how it will solve my problem. I will look into it more later, though. :-) Thanks.

@haegarr: Thanks for the info, I also think that the problem lies there, but I'm not sure how to fix it.

I tried

int current = rocket_sprite.getRotation() - 180;

but it does not quite work, even though it does make things a bit better. :-)

I'm still open for ideas...

### #6haegarr  Members

7189
Like
3Likes
Like

Posted 05 June 2014 - 09:18 AM

I tried
int current = rocket_sprite.getRotation() - 180;
but it does not quite work,

It doesn't work because although it matches the pure range of numbers, it does not consider the correct value space. For example, +90° in AS means +90° in SFML, but -90° in AS means +270° in SFML. I have assumed that in both cases 0° points into the same direction and in both cases positive angles are going counter-clockwise; if this isn't true, then things need more thinking...

I also think that the problem lies there, but I'm not sure how to fix it.

There are 2 ways:

1.) You can transform "current" and "rotation" into the same value space as is used by AS (i.e. the if-then-else stuff), and later on transform it back to the value space of SFML. I assume that this solution looks like

int current = rocket_sprite.getRotation();
current = current <= 180 ? current : current - 360;
rotation = rotation <= 180 ? rotation : rotation - 360;

followed by the if-then-else stuff, followed by the reverse transform

rotation = rotation >= 0 ? rotation : rotation + 360;

(I have not tested it.)

2.) Adapt the if-the-else stuff to the value range of SFML (which would be the better alternative, but requires although more thinking).

EDIT: There was an error in the reverse transform.

Edited by haegarr, 05 June 2014 - 09:22 AM.

### #7Álvaro  Members

20266
Like
5Likes
Like

Posted 05 June 2014 - 09:21 AM

POPULAR

This seems like the perfect opportunity to stop using angles. Instead, use unit-length complex numbers, which correspond to (cos(angle) + i * sin(angle)). Now applying a rotation around the origin to a point is simply a multiplication. If you are trying to compute the rotation that will make something face something else, that's just division. If you want to limit your rotation so it is at most some fixed rotation (which is kind of what your code here seems to be doing), it's a bit tricker, but not that bad either.

I'll be happy to write some sample code for you, probably tonight.

### #8Eck  Members

7401
Like
0Likes
Like

Posted 05 June 2014 - 09:30 AM

What units are the angles in rocket_sprite's rotation? Is it in degrees or radians? If it's radians, you're never converting it back to radians before handing it off to rocket_sprite.

If that doesn't fix your problem, I'd test the code to be sure you're getting the correct values you're expecting. There's lots of fiddily bits in there that could go wrong. Pull the code out to a function that takes two positions, and returns the angle. At first just get the math correct without the "ease" logic. Set a breakpoint and step through the debugger with your test cases checking the value at each step.

Most engines/math libraries do everything in radians, so I'd leave everything in radians. If you'd like to display degrees to the user, just convert it before displaying.

- Eck

EckTech Games - Games and Unity Assets I'm working on
Still Flying - My GameDev journal
The Shilwulf Dynasty - Campaign notes for my Rogue Trader RPG

### #9Álvaro  Members

20266
Like
4Likes
Like

Posted 05 June 2014 - 10:00 AM

Here, I found a bit of time:

#include <iostream>
#include <complex>

typedef std::complex<float> C;

// This functions expects all arguments to have length 1
C rotation_towards_with_limit(C current, C target, C max_rotation) {
C desired_rotation = target / current;

if (std::real(desired_rotation) < std::real(max_rotation))
desired_rotation = std::imag(desired_rotation) >= 0.0f ? max_rotation : std::conj(max_rotation);

return desired_rotation;
}

float const Degrees_per_radian = std::atan(1.0f) / 45.0f;

float convert_to_angle_in_degrees(C z) {
}

C convert_from_angle_in_degrees(float angle) {
}

int main() {
C current(1.0f, 0.0f);
C target(-1.0f, 0.0f);
C max_rotation = convert_from_angle_in_degrees(10.0f);

for (int i=0; i<25; ++i) {
std::cout << convert_to_angle_in_degrees(current) << '\n';
current *= rotation_towards_with_limit(current, target, max_rotation);

// You want to normalize periodically to make sure the length doesn't deviate from 1
current /= std::abs(current);
}
}

It is kind of sad that you have to convert the nice unit-length complex number to an angle in degrees to pass it to SFML, because I am sure the first thing SFML does with the angle is convert it to radians and then compute its cosine and sine, which will yield exactly what we started with, after some unnecessary trigonometric operations.

### #10Orymus3  Members

18543
Like
0Likes
Like

Posted 05 June 2014 - 11:31 AM

*mindblown by Alvaro's code*

unit-length complex numbers, which correspond to (cos(angle) + i * sin(angle)).

-=- My Articles -=-
Getting Games Done - Method and tools on how to start a hobby project and get it Done!

The Art of Enemy Design in Zelda: A Link to the Past - Reverse-engineering functional enemy design from applied example.

Retro Mortis - "RTS" - Article Series (4 Parts) on the history of RTS development (4th part finally released!!!)

### #11Álvaro  Members

20266
Like
0Likes
Like

Posted 05 June 2014 - 12:12 PM

unit-length complex numbers, which correspond to (cos(angle) + i * sin(angle)).

I don't know. I learned about complex numbers in high school and college, so I don't have a website to link to. Perhaps you can search the web for `multiplication of complex numbers rotation'. Or perhaps you can try Khan Academy...

### #12Orymus3  Members

18543
Like
0Likes
Like

Posted 05 June 2014 - 12:25 PM

My highschool taught me matrices but not that.

Thanks, I'll look it up (sorry 'bout the aside here)

-=- My Articles -=-
Getting Games Done - Method and tools on how to start a hobby project and get it Done!

The Art of Enemy Design in Zelda: A Link to the Past - Reverse-engineering functional enemy design from applied example.

Retro Mortis - "RTS" - Article Series (4 Parts) on the history of RTS development (4th part finally released!!!)

1786
Like
1Likes
Like

Posted 05 June 2014 - 01:00 PM

This is probably a good starting point for complex notation:

http://betterexplained.com/articles/a-visual-intuitive-guide-to-imaginary-numbers/

That being said, stick with matrices.  Complex notation is meant for 1D problem spaces (where the imaginary part becomes the second dimension).  You're working in a 2D space already, thus no imaginary dimension is needed, so represent your rotations/headings as a 2D unit vector (which is equivalent to a unit length complex number), and utilize matrix algebra to apply the necessary rotations and translations to your game objects.

### #14Kain5056  Members

502
Like
0Likes
Like

Posted 05 June 2014 - 06:24 PM

@Álvaro: Thank you so much for the code example. It's complexity is a bit out of my league for the the time being, though. ^^'  I will study it, but it's a bit more complex than I would like.

haegarr's code is more like what I'm looking for, but, as mentioned, it does not really work. Still, thank you both so much. :-)

To tell you the truth, I don't really know what parts of the code are in radiants and what are in degrees. I assumed it was all in degrees up until now.

I also know pretty much nothing about ActionScript, so the ranges are also a bit of an issue here.

Without the easing, the code works perfectly! The whole problem seems to be just with getRotation() when compared to rotation.

EDIT:

Playing with haegarr's code I tried this and it *almost* works, but still not quite there.

void rocket::rocket_rotate( sf::RenderWindow * window )
{
sf::Vector2i mouse_position = sf::Mouse::getPosition( *window );
float targetx = mouse_position.x - position.x;
float targety = mouse_position.y - position.y;
rotation = atan2( targety , targetx ) * 180 / PI;

int ease = 5;
float current = rocket_sprite.getRotation();

current = current <= 180 ? current : current - 360;
rotation = rotation <= 180 ? rotation : rotation - 360;

if( abs( rotation - current ) > 180 )
{
if( rotation > 0 && current < 0 ) rotation -= ( 360 - rotation + current ) / ease;
else if( current > 0 && rotation < 0 ) rotation += ( 360 - rotation + current ) / ease;
}
else if( rotation < current ) rotation -= abs( current - rotation ) / ease;
else rotation += abs( rotation - current ) / ease;

rotation += 360 / PI;

rocket_sprite.setRotation( rotation );
}

Edited by Kain5056, 05 June 2014 - 06:54 PM.

### #15Álvaro  Members

20266
Like
2Likes
Like

Posted 05 June 2014 - 08:33 PM

@Álvaro: Thank you so much for the code example. It's complexity is a bit out of my league for the the time being, though. ^^'  I will study it, but it's a bit more complex than I would like.

I don't think my code is complex: It doesn't have any special cases, and I got it right the first time I wrote it. However, if you are not used to thinking about 2D geometry in terms of complex numbers, it can be pretty hard to understand.

Notice that your code has all the common problems of using angles:
* multiple representations for the same thing (e.g., -90 == 270),
* if you try to pick a single representation, do you use [-180,180) or [0,360)?,
* having to use expensive trigonometric functions,
* difficulty in getting the code right because there are lots of cases to consider.

I could try to fix your angle-based code, but knowing there are much better alternatives, I feel lazy about it.

I really think you should put the effort to learn to think about 2D geometry in terms of either complex numbers or vectors and matrices. Notice that 3D rotations can be represented by unit-length quaternions, which is sort of analogous to representing 2D rotations by unit-length complex numbers. Vectors and matrices also work well in 3D (although interpolating rotations is much harder than with quaternions, and normalizing a rotation matrix is also harder than normalizing a quaternion). However, if you think about 2D geometry in terms of angles, you'll end up pretty confused in 3D, because the natural analog is Euler angles, which are very hard to work with.

### #16Álvaro  Members

20266
Like
3Likes
Like

Posted 05 June 2014 - 08:47 PM

Alright, I am a softie and I did write some angle-based code for you. This is pretty much a line-by-line translation of my previous code (which perhaps will let you understand the other code better):

#include <iostream>
#include <cmath>

struct Vector2 {
float x, y;

Vector2(float x, float y) : x(x), y(y) {
}
};

float const Pi = std::atan(1.0f) * 4.0f;

float sane_fmod(float x, float y) {
float scaled_x = x / y;
return (scaled_x - std::floor(scaled_x)) * y;
}

float rotation_towards_with_limit(float current, Vector2 target, float max_rotation) {
float desired_rotation = std::atan2(target.y, target.x) - current;

desired_rotation = sane_fmod(desired_rotation + Pi, 2.0f*Pi) - Pi;

if (std::abs(desired_rotation) > max_rotation)
desired_rotation = desired_rotation >= 0.0f ? max_rotation : -max_rotation;

return desired_rotation;
}

float const Degrees_per_radian = 180.0f / Pi;
float const Radians_per_degree = Pi / 180.0f;

int main() {
float current = 0.0f;
Vector2 target(-1.0f, 0.0f);
float max_rotation = 10.f * Radians_per_degree;

for (int i=0; i<25; ++i) {
std::cout << (current * Degrees_per_radian) << '\n';
current += rotation_towards_with_limit(current, target, max_rotation);
}
}
For your own sanity, make sure you consistently use radians in your computations. You can convert to and from degrees when you need to interface with a human or with an API that uses degrees (which is a bad thing).

### #17dejaime  Members

4157
Like
2Likes
Like

Posted 05 June 2014 - 09:50 PM

I may have done something very wrong in here, I usually commit giant logic mistakes late night.
But could you check my code?
void rocket::rocket_rotate( sf::RenderWindow * window )
{
sf::Vector2i mouse_position = sf::Mouse::getPosition( *window );
float targetx = mouse_position.x - position.x;
float targety = mouse_position.y - position.y;
rotation = atan2( targety , targetx ); //from -PI to PI
if (rotation < 0) rotation += 2*PI; //from 0 to 2*PI
rotation *= RAD_TO_DEG; //Convert to DEG, ranges from 0 to 360

float current = rocket_sprite.getRotation(); //from 0 to 360

float ease = 0.2;
float difference = current - rotation;

if ( (difference + 360) % 360 < 180 )
// If true, turn right (negative direction)
rocket_sprite.setRotation( rotation - abs(difference) * ease );
else
//If false, turn left (positive direction)
rocket_sprite.setRotation( rotation + abs(difference) * ease );
}

an API that uses degrees (which is a bad thing)

Yes, SFML has this problem. Hundreds of conversions needs to be made every frame which will in turn be converted back (to radians) in order to pass on to OpenGL.
Doesn't make much sense to me, but I guess I got used to it as SDL also uses degrees for angles. It forces us to convert back and forth lots of times whenever we need to do some math. If it was all radians, it would be much easier not having to convert at all (and less error prone).

Edited by dejaime, 06 June 2014 - 07:22 AM.

### #18Lactose!  GDNet+

9652
Like
3Likes
Like

Posted 06 June 2014 - 03:31 AM

rotation = atan2( targety , targetx ) + PI; //from 0 to 2*PI

When using atan, shouldn't you add 2*PI if angle is < 0, and keep as-is if not:

rotation = atan2(targety, targetx);
if (rotation) < 0
{
rotation += 2 * PI; //EDIT: Or + 360 if the atan2 function returns degrees instead of radians.
}

Edited by Lactose!, 06 June 2014 - 04:30 AM.

Hello to all my stalkers.

### #19Kain5056  Members

502
Like
0Likes
Like

Posted 06 June 2014 - 05:15 AM

Not to sound ungrateful here, in fact I'm going to study intensely all the code you all provided, and I am extremely grateful for all the information, but for the moment I'd like to stick to the tutorial code as much as possible so I can keep following it. I can't help but think that there must be a simpler way to do it.

In fact, I have noticed that in the line

rotation = atan2( targety , targetx ) * ( 180 / PI );

the " * ( 180 / PI ) " part  converts radians to degrees, meaning that all the calculations are done in degrees.

float current = rocket_sprite.getRotation();

also returns degrees, as I understand, so the only problem is the range.

I have tried calculating the current angle manually, using this code:

float current = atan2( rocket_sprite.getPosition().y , rocket_sprite.getPosition().x ) * ( 180 / PI );

As you might have guessed, it didn't make any difference. But I still think that there must be a solution along those lines with minimal changes to the tutorial code. I'm just not good enough at math to find it.

Still, I *do* appreciate immensely all the examples and information you provided, and I *will* study them. Just after I manage to finish the tutorial. :-)

So, if you are still willing to help me convert just the range of "getRotation()", I'm still open to suggestions. :-)

Edited by Kain5056, 06 June 2014 - 05:16 AM.

### #20dejaime  Members

4157
Like
1Likes
Like

Posted 06 June 2014 - 07:20 AM

When using atan, shouldn't you add 2*PI if angle is < 0, and keep as-is if not:

Yes, you are correct. I just fixed my previous code. Thanks for the tip!

Edited by dejaime, 06 June 2014 - 07:23 AM.

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.