Sin & Cos: The Programmer's Pals!: Homing Missiles Problem
Merlin Magician

Dear Sirs,

some weeks ago I found Amarillions great tutorial and i leand a lot from it.
I was really fascinated, so i decided to translate your tutorial into german.
Now i'm trying to translate and update the codes too. I want to translate them into C++ with grphics.h(WinBGIM), C++ with SFML, C++ with SDL, C++ with Allegro 5, Java, Java-Script, Blitz 3D, Blitz Max, FreeBasic and Pure Basic. Perhaps there are more coding languages to come. But i've got a problem with the circ7 (home_in) from the homing missiles example. My rocket hit the target for only one time, a second time the rocket misses the target. When i set the target_x and the target_y manually (for example),

#SelectExpand
1 if (new_target == true) 2 { 3 target_x = 300; 4 target_y = 400; 5 new_target = false; 6 }

the rocket misses it's target every time.
Perhaps you can have a look at the code and help me find out what's wrong????!!!!
I used CodeBlocks as IDE and the totally simple grphics.h(WinBGIM) as graphical engine.

Here the full code:

#SelectExpand
1#include<windows.h> 2#include<graphics.h> 3#include<cmath> 4 5// circ7 6 7void home_in(); 8 9int SCREEN_W = 600; 10int SCREEN_H = 400; 11 12main(int argc, char*argv[]) 13{ 14 // Deklaration und Initialisierung des Grafiktreibers 15 int graphdriver, graphmode; 16 graphdriver = DETECT; 17 initgraph(&graphdriver, &graphmode, "Draw_Sine"); // graphics.h kann keine Windowstitel setzen 18 19 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 20 cleardevice(); // Löschen des Ausgabefensters 21 22 setcolor(15); // Textfarbe weiss (15) 23 settextstyle(10, HORIZ_DIR, 2); // Fontsyle, Richtung und Grösse 24 //outtextxy(20, 120, "Sinuskurve zeichnen: "); // Textausgabe 25 26 home_in(); // Funktionsaufruf von draw_circle() 27 28 return 0; 29} 30 31void home_in () 32{ 33 // the x, y position of the homing missile 34 int x = SCREEN_W / 2; 35 int y = SCREEN_H / 2; 36 // the angle and length of the missile's velocity vector 37 int angle = 0; 38 int length = 1; 39 int angle_stepsize = 1; 40 // determines whether the missile has reached 41 // the target and a new one should be chosen 42 bool new_target = true; 43 // angle to the target 44 int target_angle; 45 // position of the target 46 int target_x; 47 int target_y; 48 49 while (!GetAsyncKeyState(VK_ESCAPE)) 50 { 51 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 52 cleardevice(); // Löschen des Ausgabefensters 53 // choose new target randomly when needed 54 if (new_target == true) 55 { 56 target_x = ((SCREEN_W + rand() % (2 * SCREEN_W)) / 4); 57 target_y = ((SCREEN_H + rand() % (2 * SCREEN_H)) / 4); 58 new_target = false; 59 } 60 61 // draw a pixel where the target is 62 putpixel ((target_x), (target_y),15); 63 64 // draw the missile 65 // (actually a circle with a line representing the angle) 66 setcolor(WHITE); 67 circle (x, y, 20); 68 line (x, y, x + (9 * cos (angle)), y + (9 * sin (angle))); 69 70 // move the missile 71 x = x + length * cos (angle); 72 y = y + length * sin (angle); 73 74 // if we are very close to the target, set a new target 75 if (abs (x - target_x) + abs (y - target_y) < 10) 76 { 77 new_target = true; 78 } 79 80 // calculate the angle from the missile to the target 81 target_angle = atan2 (target_y - y, target_x - x); 82 83 // Determine whether we should turn left or right. 84 // Note that itofix (128) represents half a circle. 85 // We use & 0xFFFFFF as a trick to get an angle 86 // between 0 and 256. 87 if (((angle - target_angle) & 0xFFFFFF) < 128) 88 { 89 angle = (angle - angle_stepsize) & 0xFFFFFF; 90 } 91 else 92 { 93 angle = (angle + angle_stepsize) & 0xFFFFFF; 94 } 95 delay (20); 96 } 97}

I applied the whole project, compressed with 7Zip, to this e-mail.

Best regards and thx a lot for your great Tutorial and your help,
Merlin

Elias

You can't use int for the angles. Change them to double, i.e.:

double angle = 0;
double target_angle;

And then just remove the stuff with 0xffffff as you are not using itofix anymore, i.e.:

if (fmod(angle - target_angle, 2 * pi) < pi) {
    angle -= angle_stepsize;
}

Lastly, angle_stepsize of 1 is a very large angle, you probably want 1 * pi / 180 instead.

Btw. "Grösse" is not a German word except in Switzerland/Liechtenstein, you probably want to use "Größe" for a general audience.

Merlin Magician

Dear Elias,

i changed my code, but now "rien ne vas plus". The rocket is missing its target every time?!

My Code:

#SelectExpand
1void home_in () 2{ 3 // the x, y position of the homing missile 4 int x = SCREEN_W / 2; 5 int y = SCREEN_H / 2; 6 // the angle and length of the missile's velocity vector 7 double angle = 0.0; 8 int length = 1; 9 int angle_stepsize = 1* PI / 180; 10 // determines whether the missile has reached 11 // the target and a new one should be chosen 12 bool new_target = true; 13 // angle to the target 14 double target_angle; 15 // position of the target 16 int target_x; 17 int target_y; 18 19 while (!GetAsyncKeyState(VK_ESCAPE)) 20 { 21 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 22 cleardevice(); // Löschen des Ausgabefensters 23 // choose new target randomly when needed 24 if (new_target == true) 25 { 26 target_x = ((SCREEN_W + rand() % (2 * SCREEN_W)) / 4); 27 target_y = ((SCREEN_H + rand() % (2 * SCREEN_H)) / 4); 28 new_target = false; 29 } 30 31 // draw a pixel where the target is 32 putpixel ((target_x), (target_y),15); 33 34 // draw the missile 35 // (actually a circle with a line representing the angle) 36 setcolor(WHITE); 37 circle (x, y, 20); 38 line (x, y, x + (9 * cos (angle)), y + (9 * sin (angle))); 39 40 // move the missile 41 x = x + length * cos (angle); 42 y = y + length * sin (angle); 43 44 // if we are very close to the target, set a new target 45 if (abs (x - target_x) + abs (y - target_y) < 10) 46 { 47 new_target = true; 48 } 49 50 // calculate the angle from the missile to the target 51 target_angle = atan2 (target_y - y, target_x - x); 52 53 if (fmod(angle - target_angle, 2*PI) < PI) 54 { 55 angle = (angle - angle_stepsize) ; 56 } 57 else 58 { 59 angle = (angle + angle_stepsize) ; 60 } 61 delay (20); 62 } 63}

Thanx a lot for your advice,
Merlin

Audric

int angle_stepsize = 1* PI / 180;

This has a value of exactly zero, because the computation gives around 0.017 and then you store it as an integer. Change the variable to a double.

Also, you may want to get the habit of typing literal numbers with a decimal point when you want floating computation (ie 1.0 * PI / 180.0). Here you have no problem, but there is a huge difference between 1/2 * x and 1.0/2.0 * x.

Edit: Edit: In fact you'll also need to set x and y as doubles, because your steps are tiny (1 pixel), so this part :

        x = x + length * cos (angle);
        y = y + length * sin (angle);

will modify x or y by a maximum of 1 pixel at a time.

amarillion

Great, it's nice to see my tutorial is still useful to people!

Audric has the right answer for your problem. Be careful for rounding floating point values. The tutorial recommends fixed point values, which is a bit outmoded and also makes it all Allegro 4 specific. Perhaps I should update it one of these days. Nowadays, I would say (directly contradicting the tutorial): use double/float instead of fixed.

If you manage to get the code right for the various graphics library, I can reference them in the article if you like.

Merlin_MLN

Dear Audric and Amarillion,

i changed everything to double but now, sadly the rocket flies in a circle and never hits the target.??????

@Amarillion
It's very friendy from you to reference me in your article, let me finish it and i will send you the codes!:D

Here the code:

#SelectExpand
1#include<windows.h> 2#include<graphics.h> 3#include<cmath> 4 5#define PI 3.141592654 6 7// circ7 8 9void home_in(); 10 11int SCREEN_W = 600; 12int SCREEN_H = 400; 13 14main(int argc, char*argv[]) 15{ 16 // Deklaration und Initialisierung des Grafiktreibers 17 int graphdriver, graphmode; 18 graphdriver = DETECT; 19 initgraph(&graphdriver, &graphmode, "Draw_Sine"); // graphics.h kann keine Windowstitel setzen 20 21 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 22 cleardevice(); // Löschen des Ausgabefensters 23 24 setcolor(15); // Textfarbe weiss (15) 25 settextstyle(10, HORIZ_DIR, 2); // Fontsyle, Richtung und Größe 26 //outtextxy(20, 120, "Sinuskurve zeichnen: "); // Textausgabe 27 28 home_in(); // Funktionsaufruf von home_in() 29 30 return 0; 31} 32 33void home_in() 34{ 35 // the x, y position of the homing missile 36 double x = SCREEN_W / 2; 37 double y = SCREEN_H / 2; 38 // the angle and length of the missile's velocity vector 39 double angle = 0; 40 double length = 1; 41 double angle_stepsize = 1 * PI / 180; 42 // determines whether the missile has reached 43 // the target and a new one should be chosen 44 bool new_target = true; 45 // angle to the target 46 double target_angle; 47 // position of the target 48 double target_x; 49 double target_y; 50 51 while (!GetAsyncKeyState(VK_ESCAPE)) 52 { 53 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 54 cleardevice(); // Löschen des Ausgabefensters 55 // choose new target randomly when needed 56 if (new_target == true) 57 { 58 target_x = ((SCREEN_W + rand() % (2 * SCREEN_W)) / 4); 59 target_y = ((SCREEN_H + rand() % (2 * SCREEN_H)) / 4); 60 new_target = false; 61 } 62 63 // draw a pixel where the target is 64 putpixel ((target_x), (target_y),15); 65 66 // draw the missile 67 // (actually a circle with a line representing the angle) 68 setcolor(WHITE); 69 circle (x, y, 20); 70 line (x, y, x + (9 * cos (angle)), y + (9 * sin (angle))); 71 72 // move the missile 73 x = x + length * cos (angle); 74 y = y + length * sin (angle); 75 76 77 if (abs (x - target_x) + abs (y - target_y) < 10) 78 { 79 new_target = true; 80 } 81 82 // calculate the angle from the missile to the target 83 target_angle = atan2 (target_y - y, target_x - x); 84 85 86 if (fmod(angle - target_angle, 2*PI) < PI) 87 { 88 angle = (angle - angle_stepsize) ; 89 } 90 else 91 { 92 angle = (angle + angle_stepsize) ; 93 } 94 delay (1); 95 } 96}

Thanks for for help and advice,
Merlin_MLN

amarillion

It's possible that if the turning speed (i.e. angle_stepsize) is slow compared to the speed (i.e. length), you can never turn fast enough to reach the target. (Think of the international space station constantly turning towards earth, but missing it because it moves so fast)

Try increasing the angle_stepsize, or decreasing the speed (-> length). Does that help?

Merlin_MLN

No Amarillion, the rocket is still flying on a circle!
I'm really not understanding what I'm doing wrong!???:-[

#SelectExpand
1#include<windows.h> 2#include<graphics.h> 3#include<cmath> 4 5#define PI 3.141592654 6 7// circ7 8 9void home_in(); 10 11int SCREEN_W = 600; 12int SCREEN_H = 400; 13 14main(int argc, char*argv[]) 15{ 16 // Deklaration und Initialisierung des Grafiktreibers 17 int graphdriver, graphmode; 18 graphdriver = DETECT; 19 initgraph(&graphdriver, &graphmode, "Draw_Sine"); // graphics.h kann keine Windowstitel setzen 20 21 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 22 cleardevice(); // Löschen des Ausgabefensters 23 24 setcolor(15); // Textfarbe weiss (15) 25 settextstyle(10, HORIZ_DIR, 2); // Fontsyle, Richtung und Größe 26 //outtextxy(20, 120, "Sinuskurve zeichnen: "); // Textausgabe 27 28 home_in(); // Funktionsaufruf von home_in() 29 30 return 0; 31} 32 33void home_in() 34{ 35 // the x, y position of the homing missile 36 double x = SCREEN_W / 2; 37 double y = SCREEN_H / 2; 38 // the angle and length of the missile's velocity vector 39 double angle = 0; 40 double length = 0.1; 41 double angle_stepsize = 1 * PI / 180; 42 // determines whether the missile has reached 43 // the target and a new one should be chosen 44 bool new_target = true; 45 // angle to the target 46 double target_angle; 47 // position of the target 48 double target_x; 49 double target_y; 50 51 while (!GetAsyncKeyState(VK_ESCAPE)) 52 { 53 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 54 cleardevice(); // Löschen des Ausgabefensters 55 // choose new target randomly when needed 56 if (new_target == true) 57 { 58 target_x = ((SCREEN_W + rand() % (2 * SCREEN_W)) / 4); 59 target_y = ((SCREEN_H + rand() % (2 * SCREEN_H)) / 4); 60 61 new_target = false; 62 } 63 64 // draw a pixel where the target is 65 putpixel ((target_x), (target_y),15); 66 67 // draw the missile 68 // (actually a circle with a line representing the angle) 69 setcolor(WHITE); 70 circle (x, y, 20); 71 line (x, y, x + (9 * cos (angle)), y + (9 * sin (angle))); 72 73 // move the missile 74 x = x + length * cos (angle); 75 y = y + length * sin (angle); 76 77 78 if (fabs (x - target_x) + abs (y - target_y) < 10) 79 { 80 new_target = true; 81 } 82 83 // calculate the angle from the missile to the target 84 target_angle = atan2 (target_y - y, target_x - x); 85 86 if ((angle - target_angle, 2*PI) < PI) 87 { 88 angle = (angle - angle_stepsize) ; 89 } 90 else 91 { 92 angle = (angle + angle_stepsize) ; 93 } 94 delay (1); 95 } 96}

Thanx a lot for your advice,
Merlin

Elias
Quote:

if ((angle - target_angle, 2*PI) < PI)

Did you lose the fmod there? The idea is this:

angle = 90, target_angle = 0, fmod(90, 360) = 90 -> angle = angle - 1 = 89

angle = 270, target_angle = 0, fmod(270, 360) = 270 -> angle = angle + 1 = 271

angle = 0, target_angle = 90, fmod(-90, 360) = 270 -> angle = angle - 1 = 359

angle = 0, target_angle = 0, fmod(0, 360) = 0 -> angle = -1
angle = -1, target_angle = 0, fmod(-1, 360) = 369 -> angle = 0

Without the fmod you always turn left and fly in a circle. Did you actually read the original article?

Merlin_MLN

Dear Sirs,

here is a working version of the code (with fixed)!!!!
I will provide a (double) version as soon as possible!

@Elias
Yes i really lost the fmod(), I think I lost it while I did copy&paste :-[ or so!
Sure I read the original article :). Next time I will pay more attention!!!!

Thanks for your help and advice,
Merlin

#SelectExpand
1#include<windows.h> 2#include<graphics.h> 3#include<cmath> 4#include <iostream> 5using namespace std; 6 7// circ7 8 9void home_in(); 10 11int SCREEN_W = 600; 12int SCREEN_H = 400; 13 14main(int argc, char*argv[]) 15{ 16 // Deklaration und Initialisierung des Grafiktreibers 17 int graphdriver, graphmode; 18 graphdriver = DETECT; 19 initgraph(&graphdriver, &graphmode, "Draw_Sine"); // graphics.h kann keine Windowstitel setzen 20 21 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 22 cleardevice(); // Löschen des Ausgabefensters 23 24 setcolor(15); // Textfarbe weiss (15) 25 settextstyle(10, HORIZ_DIR, 2); // Fontsyle, Richtung und Grösse 26 //outtextxy(20, 120, "Sinuskurve zeichnen: "); // Textausgabe 27 28 home_in(); // Funktionsaufruf von draw_circle() 29 30 return 0; 31} 32 33void home_in () 34{ 35 // the x, y position of the homing missile 36 double x = SCREEN_W / 2; 37 double y = SCREEN_H / 2; 38 // the angle and length of the missile's velocity vector 39 int angle = 0; 40 int length = 1; 41 int angle_stepsize = 1; 42 // determines whether the missile has reached 43 // the target and a new one should be chosen 44 bool new_target = true; 45 // angle to the target 46 int target_angle; 47 // position of the target 48 int target_x; 49 int target_y; 50 51 while (!GetAsyncKeyState(VK_ESCAPE)) 52 { 53 setbkcolor(0); // Hintergrundfarbe des Ausgabefensters schwarz 54 cleardevice(); // Löschen des Ausgabefensters 55 // choose new target randomly when needed 56 if (new_target == true) 57 { 58 target_x = ((SCREEN_W + rand() % (2 * SCREEN_W)) / 4); 59 target_y = ((SCREEN_H + rand() % (2 * SCREEN_H)) / 4); 60 new_target = false; 61 } 62 63 // draw a pixel where the target is 64 putpixel ((target_x), (target_y),15); 65 66 // draw the missile 67 // (actually a circle with a line representing the angle) 68 setcolor(WHITE); 69 circle (x, y, 20); 70 line (x, y, x + (9 * cos (angle)), y + (9 * sin (angle))); 71 72 // move the missile 73 x = x + length * cos (angle); 74 y = y + length * sin (angle); 75 76 // if we are very close to the target, set a new target 77 if (abs (x - target_x) + abs (y - target_y) < 10) 78 { 79 new_target = true; 80 } 81 82 // calculate the angle from the missile to the target 83 target_angle = atan2 (target_y - y, target_x - x); 84 85 // Determine whether we should turn left or right. 86 // Note that itofix (128) represents half a circle. 87 // We use & 0xFFFFFF as a trick to get an angle 88 // between 0 and 256. 89 if (((angle - target_angle) & 0xFFFFFF) < 128) 90 { 91 angle = (angle - angle_stepsize) & 0xFFFFFF; 92 } 93 else 94 { 95 angle = (angle + angle_stepsize) & 0xFFFFFF; 96 } 97 // cout << "Winkel:" << angle ; 98 delay (10); 99 } 100}

Niunio

The tutorial recommends fixed point values, which is a bit outmoded and also makes it all Allegro 4 specific. Perhaps I should update it one of these days. Nowadays, I would say (directly contradicting the tutorial): use double/float instead of fixed.

Just comment that I find fixed-point still useful. You don't need to check values to know if angle overflows the circle, you can do fast calculations with bit operations and if you need to get a lot of cos/sin values in a row the use of an array would be faster (depending on the CPU cache, of course).

Just my opinion.

Chris Katko

Fixed-point and custom formats are actually very common in AAA studios. For one reason, the self-wrapping feature Niunio mentioned.

You'll see SINGLE BYTE, unsigned floats in places where memory bandwidth is key and the underlying DATA being represented can both survive the granularity, as well as take advantage of domains smaller than all real numbers, such as unsigned. For an easy example. If your number can't be outside 0.0 to 1.0, a float or double wastes bits. (In areas where that data is a bottleneck.)

That said, >90% of us don't need them for an indie game where computing resources usually far exceed our ability to create content that exploits them. (Barring stupid decisions like using algorithms of unnecessarily high time-complexity.)

Elias

float is actually very dangerous if you don't make sure to wrap it back into the 2pi range. It takes only a few 1000 rotations in the same direction otherwise before accuracy drops down to full angles.

It's even worse with x/y positions... if your level is big enough that you can reach around pixel 200,000 then you're down to 2 decimal points, i.e.:

float x = 300000;
float y = x + 0.01;
if (y > x) // false
if (y == x) // true

It will probably mean while your physics work perfectly fine close to the center you get very strange behavior farther away. Of course simply using double instead of float should fix that in most cases :)

Chris Katko

Likewise,

Fun fact: At 16,777,216, floats no longer have the resolution to increment by one.

The code:

for(float i = 0; i <= 16,777,216 + 1; i++)
//16,777,216 + 1 = 16,777,216 (rounded down)

will never terminate.

So you should never rely on a float for an integer value that exceeds that value. And likewise, for decimal numbers that you care about that exceed the precision. And on the flip side, you CAN use a float for things like array indices if you need to. (Using float x for position in space, and when you land on a space station, you x = the map array index.) But it's still probably a bad idea unless you NEED that optimization...

Elias said:

It will probably mean while your physics work perfectly fine close to the center you get very strange behavior farther away

I had that happen to me, even with doubles. I was making a 2-D KSP using actual universe scale dimensions. It would fall apart adding a small distance every frame (velocity) at huge distances. fixed point are great for that in the sense that their precision never changes where ever you are in the grid.

I still wonder sometimes why CPU's don't natively support fixed operations. ???

amarillion

All good points. But I think I should at least update the article to explain the pros and cons. And for a beginner tutorial, fixed vs float might be too much of a distraction of the real point, which is about trigonometry. Maybe I should write a separate article on fixed vs float.

Niunio

All good points. But I think I should at least update the article to explain the pros and cons. And for a beginner tutorial, fixed vs float might be too much of a distraction of the real point, which is about trigonometry. Maybe I should write a separate article on fixed vs float.

Agree.

Chris Katko

Oh yeah, I don't think it's be great for the article. I was just talking about some cool stuff I learned about fixed/float.

Felix-The-Ghost

I'm bookmarking this thread. I had used these functions in A4 for turrets that always face the player, but now that I am reviewing this material again with much more experience I can appreciate better the ingenuity of using integers in lieu of floats when granularity is low. I feel like this would be great in saving data or networking, so you can send 1-2 bytes of data for a float instead of 4+ bytes.

Thread #616805. Printed from Allegro.cc