Homing missile problem

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

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

My highschool taught me matrices but not that.

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

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.

@Á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 );
}

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

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

Hello to all my stalkers.

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

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!

This topic is closed to new replies.

Advertisement