Homing missile problem

Started by
23 comments, last by SimonForsman 9 years, 10 months ago

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

Advertisement

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

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)

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

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

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.

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.

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

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.

*mindblown by Alvaro's code*

Where can I read more about these:


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

This topic is closed to new replies.

Advertisement