Jump to content

  • Log In with Google      Sign In   
  • 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.

  • You cannot reply to this topic
24 replies to this topic

#1 Kain5056   Members   -  Reputation: 455

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();
    bool rocket_load();
    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;
}

bool rocket::rocket_load()
{
    rocket_texture.loadFromFile( "rocket.png" );
    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. :-)

Thank you in advance. :-)


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


Sponsor:

#2 Orymus3   Crossbones+   -  Reputation: 7512

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)



#3 Orymus3   Crossbones+   -  Reputation: 7512

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.


#4 haegarr   Crossbones+   -  Reputation: 4155

Like
6Likes
Like

Posted 05 June 2014 - 07:45 AM

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.


#5 Kain5056   Members   -  Reputation: 455

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...



#6 haegarr   Crossbones+   -  Reputation: 4155

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   Crossbones+   -  Reputation: 12866

Like
5Likes
Like

Posted 05 June 2014 - 09:21 AM

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.



#8 Eck   Members   -  Reputation: 2065

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



#9 Álvaro   Crossbones+   -  Reputation: 12866

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 const Radians_per_degree = 1.0f / Degrees_per_radian;

float convert_to_angle_in_degrees(C z) {
  return std::arg(z) * Radians_per_degree;
}

C convert_from_angle_in_degrees(float angle) {
  return std::polar(1.0f, angle * Degrees_per_radian);
}

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.



#10 Orymus3   Crossbones+   -  Reputation: 7512

Like
0Likes
Like

Posted 05 June 2014 - 11:31 AM

*mindblown by Alvaro's code*

 

Where can I read more about these:


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



#11 Álvaro   Crossbones+   -  Reputation: 12866

Like
0Likes
Like

Posted 05 June 2014 - 12:12 PM

Where can I read more about these:
 

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...

#12 Orymus3   Crossbones+   -  Reputation: 7512

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)



#13 jHaskell   Members   -  Reputation: 987

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.



#14 Kain5056   Members   -  Reputation: 455

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   Crossbones+   -  Reputation: 12866

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:
* radians -vs- degrees,
* 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   Crossbones+   -  Reputation: 12866

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).

#17 dejaime   Crossbones+   -  Reputation: 4002

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?
#define RAD_TO_DEG 57.2957795
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.


#18 Lactose!   GDNet+   -  Reputation: 3207

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.


#19 Kain5056   Members   -  Reputation: 455

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.


#20 dejaime   Crossbones+   -  Reputation: 4002

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.



PARTNERS