24
Sin & Cos: The Programmer's Pals! [email protected] Introduction In this article I shall discuss several game programming techniques, all revolving around a central theme: the sine and cosine functions. This article will explain sine, cosine, vectors, atan2, and some useful special effects such as how to make homing missiles and how bitmap rotation works. I shall start with the very basics, but later on I'll cover some more advanced programming techniques. This article comes with twelve reallife coded examples, which you can download here . They are tested with DJGPP, and a makefile for DJGPP is supplied. If you have DJGPP, all you have to do is unzip the source and the makefile into a directory, and then run "make". You don't need any libraries other than Allegro. Vectors Let's start with something that can sometimes be difficult to understand for beginners, because it is highly abstract the vector. A vector can be visualized in different ways. First of all, you can imagine it as an arrow to a point in space. In the case of twodimensional space, you need two values to define the vector one for the xcoordinate and one for the ycoordinate. In the case of threedimensional space, you will need a third value for the zcoordinate. This article will mostly deal with twodimensional space though. Threedimensional space is more complicated, and I'm no expert in that. Maybe somebody else feels the urge to write an article on that topic...? In the figure above I have drawn a vector with an xcoordinate of 3 and a ycoordinate of 2. But these two values are not the end of the story. For example, if you draw this vector out on paper, you can measure the length of the vector as 3.6 and the angle between the vector and the xaxis as 34 degrees. If you think about this further, you can see that you don't even need the (x,y) coordinates of the vector if you already know its length and the angle it makes with the xaxis. It is perfectly possible to define a vector fully just by its length and angle. If you use the x and ycoordinates, you are using Cartesian coordinates. If you use the angle and length of the vector, you are using polar coordinates. Let's have an example. Suppose you are writing a topdown racing game (something like Micro Machines). You will need a way to store the velocity (speed and direction) of a racing car. And how do we do that? With a vector. This velocity vector is in fact the change in the racing car's position from one frame to the next (see figure below). The question is, should we use Cartesian coordinates or polar coordinates for this vector?

Sin & Cos_ the Programmer's Pals

Embed Size (px)

DESCRIPTION

Learn the basis for game programming movement.

Citation preview

Sin & Cos: The Programmer's [email protected]

Introduction

In this article I shall discuss several game programming techniques, all revolving around a central theme:the sine and cosine functions. This article will explain sine, cosine, vectors, atan2, and some useful specialeffects such as how to make homing missiles and how bitmap rotation works. I shall start with the verybasics, but later on I'll cover some more advanced programming techniques. This article comes with twelvereal­life coded examples, which you can download here. They are tested with DJGPP, and a makefile forDJGPP is supplied. If you have DJGPP, all you have to do is unzip the source and the makefile into adirectory, and then run "make". You don't need any libraries other than Allegro.

Vectors

Let's start with something that can sometimes be difficult to understand for beginners, because it is highlyabstract ­ the vector. A vector can be visualized in different ways.

First of all, you can imagine it as an arrow to a point in space. In the case of two­dimensional space, youneed two values to define the vector ­ one for the x­coordinate and one for the y­coordinate. In the case ofthree­dimensional space, you will need a third value for the z­coordinate. This article will mostly deal withtwo­dimensional space though. Three­dimensional space is more complicated, and I'm no expert in that.Maybe somebody else feels the urge to write an article on that topic...?

In the figure above I have drawn a vector with an x­coordinate of 3 and a y­coordinate of 2. But these twovalues are not the end of the story. For example, if you draw this vector out on paper, you can measure thelength of the vector as 3.6 and the angle between the vector and the x­axis as 34 degrees.

If you think about this further, you can see that you don't even need the (x,y) coordinates of the vector ifyou already know its length and the angle it makes with the x­axis. It is perfectly possible to define a vectorfully just by its length and angle.

If you use the x­ and y­coordinates, you are using Cartesian coordinates. If you use the angle and length ofthe vector, you are using polar coordinates.

Let's have an example. Suppose you are writing a top­down racing game (something like Micro Machines).You will need a way to store the velocity (speed and direction) of a racing car. And how do we do that?With a vector. This velocity vector is in fact the change in the racing car's position from one frame to thenext (see figure below). The question is, should we use Cartesian coordinates or polar coordinates for thisvector?

Well, storing only the Cartesian coordinates has the advantage that it is very easy to calculate the newposition of the racing car at each step. Suppose you store the (x,y) coordinates of the velocity vector in thevariables vel_x and vel_y, and the position of the racing car in the variables pos_x and pos_y. All you needto do in the game loop is:

pos_x += vel_x;pos_y += vel_y;

What could be simpler?

On the other hand, storing the length and angle of the velocity vector has its advantages, in that it makes iteasier to implement the racing car controls. Think about it ­ if the player presses LEFT, you want the racingcar to turn left. Supposing you store the angle in the integer car_angle, you could use the following code:

if (key[KEY_LEFT]) car_angle ‐= 1; // turn one degree to the leftif (key[KEY_RIGHT]) car_angle += 1; // turn one degree to the right

And how would you do that if you only stored x and y? You would need to change both of them, but how?That is a lot more difficult! Furthermore, if the player presses UP you want the racing car to go faster. Youcan achieve this by simply increasing the length of the vector. If you store x and y, you have to change bothof them a bit, which is again more complicated.

Sin & cos

Okay, so now we know there are two ways to store a vector ­ with polar coordinates and with Cartesiancoordinates ­ and that in this case they both have their advantages. So which one do we actually use? Well,it wouldn't be a big problem if we knew a way to calculate the angle and speed from the x­ and y­coordinates, and vice versa.

And I wouldn't be writing this article if that wasn't possible!

First I'll talk about converting from polar to Cartesian coordinates. It is, of course, also possible to convertthe other way, but I will talk about that later on.

There are two functions available to us to accomplish this. These functions are sine and cosine (sin andcos). Whoa! You didn't see that coming, did you?

The sine can be used to calculate the y­coordinate of a vector, and the cosine can be used to calculate the x­coordinate. (Sometimes you see this the other way round, and one or both coordinate values may benegated. I encourage you to think about the effect this would have when you have learned more about thefunctions.) The sin() and cos() functions take only one parameter: the angle. They return a number between­1 and 1. If you multiply this number by the length of the vector, you will get the exact Cartesiancoordinates of the vector. So your code will look like this:

speed_x = speed_length * cos (speed_angle);speed_y = speed_length * sin (speed_angle);

So that's it: for a racing game you just store the angle and the length of the velocity vector. You adjust theseaccording to the player's input, and you calculate the x­ and y­coordinates when you are ready to update theposition of the racing car.

Drawing a circle

Do you want to see a real example? Guess so. But you'll have to wait a bit. First I'll give a more simpleexample of what sin and cos actually do. In fact, this is probably the simplest program using sin and cosyou'll ever see that still does something more or less useful (see circ1.c).

void draw_circle () int x, y; int length = 50; float angle = 0.0; float angle_stepsize = 0.1;

// go through all angles from 0 to 2 * PI radians while (angle < 2 * PI) // calculate x, y from a vector with known length and angle x = length * cos (angle); y = length * sin (angle);

putpixel (screen, x + SCREEN_W / 2, y + SCREEN_H / 2, makecol (255, 255, 255)); angle += angle_stepsize;

Output:

So let's run this function. What does it do? Well, it draws sixty­odd points on the screen, which togetherform a perfect circle. So how does it work?

As you can see there is a variable called length and a variable called angle. These two represent the lengthand the angle of a vector! We first calculate the x­ and y­coordinates from these variables, in the same wayas before, using cos and sin. After that we plot a pixel at the calculated x­ and y­coordinates. Finally weincrease the angle by a small increment, but we do not change the length. We iterate several times, thusgoing through a lot of different angles. And what happens if you draw points at a constant distance from afixed point in different directions? You get a circle!

About radians

But what is that? There are some strange things in this piece of code. First of all, why does it say: while

(angle < 2 * PI)? What does that mean? And why is the angle_increment such a low value? You wouldthink that with such a low value the points on the circle would be very close to each other, but they aren't.How can we explain that?

The answer is that the sin and cos functions don't take regular degrees, which you might be used to, as anargument. There are 360 degrees in a full circle ­ this number is thought to have come from an old estimateof the number of days in the year, or to have been chosen because it has so many factors. But the sin andcos functions want radians, not degrees. There are 2 * PI radians in a circle, PI being a mathematicalconstant of roughly 3.1415927. So there are roughly 6.282 radians in a circle. Why do they make things sodifficult, you may ask? Well, that is all figured out by mathematicians, and mathematicians are amysterious kind of people.

To make our lives easier, we can calculate the number of degrees from the number of radians and viceversa. We do it as follows:

degrees = radians * 180 / PI;radians = degrees * PI / 180;

Let's consider our angle increment of 0.1 radians. 0.1 radians = 0.1 * 180 / 3.142 = 5.7 degrees. If you takea look at the output, you'll notice that this value looks about right for what was drawn.

Actually the reason for introducing radians is as follows. The length of the circumference of a unit circle (acircle of radius 1) is exactly 2 * PI. That means that the length of the circumference is exactly equal to thenumber of radians in a full circle. Do we gain any advantage by this knowledge? No, but mathematiciansthink it is cool. Programmers, on the other hand, don't ­ unless they are doing stuff which is much moremathematically advanced than what we're doing. Later on we shall see what good programmers (likeShawn Hargreaves) think is a better way to define an angle for most applications.

Use fixed, not float

But I already hear some people say: you are using floats! Floats are so slooooooooow! Why don't you usefixed­point numbers? On newer computers, there is not much speed difference, but on older computers thespeed gain of using fixed numbers is significant. Here I'll present you with exactly the same function, onlyusing fixed point arithmetic. First, however, I'll quickly go over how to manipulate fixed­point numberswith Allegro. Note that if you use C++ you can use the fix class, which is a bit easier to work with, though Ishan't explain it here. If you program in C++ and you want to use the fix class, you will have to look it up inthe Allegro docs.

Rule #1: you can convert between floats and fixeds, and between ints and fixeds, with the functionsfixtoi, fixtof, itofix and ftofix.

fixed_1 = itofix (int_1);int_1 = fixtoi (fixed_1);float_1 = fixtof (fixed_1);

Rule #2: you can add and subtract two fixed numbers, but not an int and a fixed. You will need toconvert types.

fixed_3 = fixed_1 + fixed_2;fixed_3 = fixed_1 ‐ fixed_2;fixed_3 = fixed_1 + itofix (int_2);

Rule #3: you can divide and multiply by an int, but not by another fixed number. You will need touse the functions fmul() and fdiv() (which are in fact macros, so you don't need to worry aboutspeed).

fixed_3 = fixed_1 * int_2;fixed_3 = fmul (fixed_1, fixed_2);fixed_3 = fdiv (fixed_1, fixed_2);

Now back to the draw_circle function: Here it is again but now using fixed point arithmetic (circ2.c):

void draw_circle_fixed () fixed x, y; int length = 50; fixed angle = 0; fixed angle_stepsize = itofix (5);

// go through all angles from 0 to 255 while (fixtoi (angle) < 256) // calculate x, y from a vector with known length and angle x = length * fcos (angle); y = length * fsin (angle);

putpixel (screen, fixtoi(x) + SCREEN_W / 2, fixtoi(y) + SCREEN_H / 2, makecol (255, 255, 255)); angle += angle_stepsize;

Note we have to use fsin() and fcos() when using fixed point math.

Introducing another way of representing angles

But what the...? Now it says: while (fixtoi (angle) < 256). You just went through a lengthy explanation ofradians, and now this?

Well here you see the way programmers sometimes prefer to handle angles: they make use of a circle that isdivided into 256 parts, ranging from 0 to 255. (Strictly speaking we include the numbers between 255 and256, but not 256 itself.) Let's call these parts Allegro­degrees, for want of a better name (though this systemis not an invention of Allegro, as the name would suggest). Why 256 and not 360? Well here is the thing.What will happen when you have an angle of 361 regular degrees? Because a circle is round (by definition),361 degrees represents the same point as 1 degree. In much the same way, 3 * PI radians is the same as 1 *PI radians, and 257 allegro degrees is the same as 1 allegro degree. To check for out­of­bound anglesmeasured in degrees and convert them to the proper range, you will need to do something like this:

int angle_in_degrees;while (angle_in_degrees >= 360) angle_in_degrees ‐= 360;while (angle_in_degrees < 0) angle_in_degrees += 360;

But because Allegro­degrees range from 0 to 255, and this range can be stored in exactly 8 bits (in the caseof an int), we only need reset all the other bits and we can be sure we have a neat angle ranging from 0 to255. We just have to mask out all bits except the lower 8 bits. We can do this using the bitwise ANDoperator (&):

int allegro_degrees;allegro_degrees &= 0xFF; // keep the lowest 8 bits

For those people who don't exactly understand the bitwise AND (&) operator: just trust me, it is a very easyway to make sure the angle is within range. Just use the code above and you can be absolutely sure theangle is between 0 and 255.

If we use a fixed point number to represent degrees we have to do it a little bit differently, because we alsohave 16 bits representing the part to the right of the point. So what we need to do is exactly the same,except instead of 8 bits we keep 16 + 8 = 24 bits. Here is what we do:

fixed allegro_degrees;allegro_degrees &= 0xFFFFFF; // keep the lowest 24 bits

If you understand this then you will now understand why a 256­degree scale is often best for game

programmers. Remember: normal people use regular degrees, mathematicians use radians and gameprogrammers use Allegro­degrees. Okay, maybe that is a bit oversimplified. If you use floats, it is better touse radians, because the normal functions sin() and cos() take values in radians. If you use fixed, as I do inmost examples, it is better to use Allegro­degrees, because the functions fsin() and fcos() use them, and wecan keep an angle in range with a simple bitwise AND.

Just as we did with radians and degrees, we can calculate the number of Allegro­degrees from radians andregular degrees. Without further explanation, here is how to do it:

allegro_degrees = regular_degrees * 256 / 360;allegro_degrees = radians * 128 / PI;

To give you a better idea of what sine and cosine actually do, I have written the following function(circ3.c):

void draw_sine () int length = 50; fixed x, y; fixed angle = 0; fixed angle_stepsize = itofix (5);

while (fixtoi(angle) < 256) // the angle is plotted along the x‐axis x = angle; // the sine function is plotted along the y‐axis y = length * fsin (angle);

putpixel (screen, fixtoi (x), fixtoi (y) + SCREEN_H / 2, makecol (255, 255, 255));

angle += angle_stepsize;

Output:

The function looks more or less the same as the draw_circle function, but it does something different. It justplots the sine function on the screen. As you can see the sine function looks like a wave ­ hence the name'sine wave'. You can use the sine function in your games for all kinds of wavy movement, such as aliensmoving in waves.

You can change the code above to plot the cosine function too. The image below was created with amodified version of circ3.c. You can see a sine wave plotted in white and a cosine wave plotted in red. Thefunctions are continuous and repeating; they don't stop when you reach 256 Allegro­degrees. If you lookclosely you can see that the cosine and sine waves have the same shape, the only difference being that thecosine function is displaced a little bit. The displacement is exactly 64 Allegro­degrees or 90 normal

degrees.

In the table below are some key values from the sine and cosine functions. As you can see, both functionsreach their maxima and minima at multiples of 90 regular degrees.normal degrees radians allegro degrees sine cosine0 0 0 0 190 1/2 pi 64 1 0180 pi 128 0 ­1270 3/2 pi 192 ­1 0360 2 pi 256 0 1

A real racing car

Ok, a small summary of what we have learned so far. We now know two different ways to store vectors,namely polar and Cartesian coordinates. We have also learned how to calculate the Cartesian coordinates ofa vector if we know its polar coordinates. Finally, we have seen three different ways to store angles:degrees, radians and what we have termed Allegro­degrees.

But now we go back to the example we started with ­ the racing car. Actually the racing car here is really acircle with a line representing the direction the car is facing in, but with a little imagination it can be aracing car. Well here you go (circ4.c):

void racing_car () // length and angle of the racing car's velocity vector fixed angle = itofix (0); fixed length = itofix (0); // x‐ and y‐coordinates of the velocity vector fixed vel_x, vel_y;

// x‐ and y‐position of the racing car fixed x = itofix (SCREEN_W / 2); fixed y = itofix (SCREEN_H / 2);

while (!key[KEY_ESC]) // erase the old image circlefill (screen, fixtoi(x), fixtoi(y), 10, makecol (0, 0, 0));

// check the keys and move the car if (key[KEY_UP] && length < itofix (2)) length += ftofix (0.005); if (key[KEY_DOWN] && length > itofix (0)) length ‐= ftofix (0.005); if (key[KEY_LEFT]) angle = (angle ‐ itofix (1)) & 0xFFFFFF; if (key[KEY_RIGHT]) angle = (angle + itofix (1)) & 0xFFFFFF;

// calculate the x‐ and y‐coordinates of the velocity vector vel_x = fmul (length, fcos (angle)); vel_y = fmul (length, fsin (angle));

// move the car, and make sure it stays within the screen x += vel_x; if (x >= itofix (SCREEN_W)) x ‐= itofix(SCREEN_W); if (x < itofix (0)) x += itofix(SCREEN_W); y += vel_y; if (y >= itofix (SCREEN_H)) y ‐= itofix(SCREEN_H); if (y < itofix (0)) y += itofix(SCREEN_H);

// draw the racing car circle (screen, fixtoi(x), fixtoi(y), 10, makecol (0, 0, 255)); line (screen, fixtoi(x), fixtoi(y), fixtoi (x + 9 * fcos (angle)), fixtoi (y + 9 * fsin (angle)), makecol (255, 0, 0));

// wait for 10 milliseconds, or else we'd go too fast rest (10);

Everything in this program has been explained already. The velocity of the racing car is represented by anangle and a length. If the player presses UP, the length of the velocity vector (the speed) is increased; if hepresses DOWN, the length is decreased. The angle is changed if the player presses LEFT or RIGHT. Withthe 24­bit mask (0xFFFFFF), we make sure that the angle remains within the basic range of Allegro­degrees. After the speed and direction have been adjusted, the Cartesian coordinates vel_x and vel_y arecalculated with sin() and cos(). In each iteration of the loop, these coordinates are added to the coordinatesof the racing car.

Another useful thing you can do with sin and cos

If you understand all this, you will have no problems with the following program. It is another smallexample of what you can do with this basic knowledge of sin and cos. This time we'll use sin and cos toanimate the orbit of a planet. The planet will be represented by a small dot, which will move around in acircle. Here is the code (circ5.c):

void orbit () int x = 0, y = 0;

fixed angle = itofix (0); fixed angle_stepsize = itofix (1);

// These determine the radius of the orbit. // See what happens if you change length_x to 100 :) int length_x = 50; int length_y = 50;

// repeat this until a key is pressed while (!keypressed()) // erase the point from the old position putpixel (screen, fixtoi(x) + SCREEN_W / 2, fixtoi(y) + SCREEN_H / 2, makecol (0, 0, 0));

// calculate the new position x = length_x * fcos (angle); y = length_y * fsin (angle);

// draw the point in the new position putpixel (screen, fixtoi(x) + SCREEN_W / 2, fixtoi(y) + SCREEN_H / 2,

makecol (255, 255, 255));

// increment the angle so that the point moves around in circles angle += angle_stepsize;

// make sure angle is in range angle &= 0xFFFFFF;

// wait 10 milliseconds, or else it'd go too fast rest (10);

Try experimenting with different values for length_x and length_y. If these two are different, the result willbe that the planet doesn't move in a circle anymore, but in an ellipse. (Note that elliptical orbits do not worklike this in real life.)

If you want to try this out, why not try to make a simulation of the solar system? Making the moon orbit theearth while the earth is orbiting the sun is the tricky part.

Drawing a circle in yet another way

In the first chapters I explained two different ways to draw a circle, one using floats and one using fixeds.But if you take a look at gfx.c in the allegro/src dir, you will see that the source of Allegro's circle()function is nothing like the draw_circle function I've put here. In fact, you won't find a single sin or cos inthe entire function! So how is that possible?

The code makes use of one useful property of circles: all points in a circle are at the same distance from thecenter. Say you start at the top of the circle. The coordinates of the top are easily calculated: the x­coordinate is 0 and the y­coordinate is equal to the radius of the circle (but negative with screencoordinates). So we draw a pixel at these coordinates. For the next pixel we can either go one pixel to theright, or one pixel down and then one pixel to the right. So which do we choose?

The solution is to calculate for each of the two possibilities the distance to the center with Pythagoras'sTheorem. We draw the pixel whose distance from the center is less different from the radius of the circle.

We only have to do this for one eighth of the circle. The rest of the circle can be drawn by making use ofthe horizontal, vertical and diagonal axes of symmetry of the circle, as you can see in the figure below.Note that you can draw all red and yellow sections for the price of one, simply by mirroring it along thegreen lines.

Here is the actual code from circ6.c:

void my_draw_circle (BITMAP *bmp, int center_x, int center_y, int r, int color) // x and y are the current position in the circle. int x = 0, y = r;

while (x <= y) // We make use of 8 axes of symmetry in a circle. // This way we have fewer points to calculate on its circumference. putpixel (bmp, center_x + x, center_y + y, color); putpixel (bmp, center_x ‐ x, center_y + y, color); putpixel (bmp, center_x + x, center_y ‐ y, color); putpixel (bmp, center_x ‐ x, center_y ‐ y, color); putpixel (bmp, center_x + y, center_y + x, color); putpixel (bmp, center_x ‐ y, center_y + x, color); putpixel (bmp, center_x + y, center_y ‐ x, color); putpixel (bmp, center_x ‐ y, center_y ‐ x, color);

// This is the most important part of the function. // We go to the right in all cases (x++). // We need to decide whether to go down (y‐‐). // This depends on which point is // closest to the path of the circle. // Good old Pythagoras will tell us what to do. x++; if (abs (x*x + y*y ‐ r*r) > abs (x*x + (y‐1)*(y‐1) ‐ r*r)) y‐‐;

This code still doesn't look much like the code in Allegro's gfx.c file, but that is mainly because thestatement:

if (abs (x*x + y*y ‐ r*r) > abs (x*x + (y‐1)*(y‐1) ‐ r*r))

can still be optimized a lot. In fact, if you do all possible optimizations, you arrive at Allegro's actualcircle() function. I shan't discuss these optimizations here though. It has all been figured out by a smart guyworking at IBM named Bresenham. He also figured out a smart way to draw lines quickly, so you mightcome across his name sometimes in FAQs and tutorials.

Link for Bresenham Line & Circle Algorithms: http://www.gamedev.net/reference/articles/article767.asp

Vectors the other way round

We have seen how to go from polar to Cartesian coordinates with sin and cos like this:

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

Now I will explain how to go the other way. Calculating the length is the easy part, for you only need toknow Pythagoras's Theorem: a^2 + b^2 = c^2. Or more practically:

length = sqrt (x * x + y * y)

Calculating the angle is a bit harder. There is a mathematical function called the tangent, implemented in Cin the function tan(), which can be used to calculate the ratio of y to x as follows:

tan (angle) = y / x

This can in fact be written as:

tan (angle) = sin (angle) / cos (angle)

This means the tan function is a combination of the sin and cos functions.

The inverse function of the tangent is called the arctangent. The C function for this is atan(). This functioncan be used to calculate an angle if you know the ratio of y to x, like this:

angle = atan (y / x)

But there is a minor problem: this will sometimes give you an incorrect result. In the figure below, you seetwo vectors, a red one and a yellow one. They both have the same y­to­x ratio. That means, if you calculatethe arctangents of them, you will get the same result, which is 45 degrees. This is correct only for theyellow vector. In addition, you have to check for cases in which x is 0, to avert division by zero.

A partial work­around is possible like this (partial because we don't check for the case where x is 0):

if (x > 0) angle = atan (y / x);else angle = PI + atan (y / x);

But to simplify things the function atan2() is available for programmers. Here is an example:

angle = atan2 (y, x)

This function will always produce the correct angle for any x,y pair. Of course, for fixed­point mathematicsAllegro provides the counterpart fatan2().

Using atan2(): homing missiles

Suppose you are writing a game in which the player can fire homing missiles. You decide to do it asfollows. First you calculate the direction of the target as seen from the missile. Then you compare this anglewith the current angle of the missile. If the target angle is greater than the current angle, the angle should beincreased, and vice versa.

This is a pretty good idea, but how do you calculate the direction of the target as seen from the missile?You can visualize this as a vector from the missile to the target. The x­ and y­coordinates of this vector canbe calculated very easily ­ just subtract the coordinates of the missile from the coordinates of the target.Given the x­ and y­coordinates of the vector, you can calculate its angle and length, using the atan2()function as described above. The length is not so important (it is a measure of the distance to the target) butthe angle is just what we need.

In the code below, the position of the missile is represented by the variables x and y. The velocity of themissile is represented by length and angle. First, the program determines whether there is already a targetset, and if there isn't one the program chooses one at random. After that the missile is moved and drawn tothe screen. Finally the program determines which way the angle of the missile should change. The angle tothe target is calculated in this line:

target_angle = fatan2 (target_y ‐ y, target_x ‐ x);

The program uses this calculated angle to determine whether the missile's direction angle should increase ordecrease. It calculates the difference between the target angle and the current angle (angle ­ target_angle).After that it makes sure this difference is in the proper range: (angle ­ target_angle) & 0xFFFFFF. If thisangle is less than 128 Allegro­degrees (180 normal degrees), the direction angle is decreased. Otherwise itis increased.

if (((angle‐target_angle) & 0xFFFFFF) < itofix(128)) angle = (angle ‐ angle_stepsize) & 0xFFFFFF; else angle = (angle + angle_stepsize) & 0xFFFFFF;

Here is the whole piece of code (circ7.c):

void home_in () // the x, y position of the homing missile fixed x = itofix(SCREEN_W / 2); fixed y = itofix(SCREEN_H / 2); // the angle and length of the missile's velocity vector

fixed angle = 0; int length = 1; fixed angle_stepsize = itofix (3); // determines whether the missile has reached // the target and a new one should be chosen int new_target = TRUE; // angle to the target fixed target_angle; // position of the target fixed target_x, target_y;

while (!keypressed()) clear (screen); // choose new target randomly when needed if (new_target) target_x = itofix((SCREEN_W + rand() % (2 * SCREEN_W)) / 4); target_y = itofix((SCREEN_H + rand() % (2 * SCREEN_H)) / 4); new_target = FALSE;

// move the missile x += length * fcos (angle); y += length * fsin (angle);

// if we are very close to the target, set a new target if (abs (x ‐ target_x) + abs (y ‐ target_y) < itofix(10)) new_target = TRUE;

// draw a pixel where the target is putpixel (screen, fixtoi(target_x), fixtoi(target_y), makecol (255, 255, 255));

// draw the missile // (actually a circle with a line representing the angle) circle (screen, fixtoi(x), fixtoi(y), 10, makecol (0, 0, 255)); line (screen, fixtoi(x), fixtoi(y), fixtoi(x) + fixtoi (9 * fcos (angle)), fixtoi(y) + fixtoi (9 * fsin (angle)), makecol (255, 0, 0));

// calculate the angle from the missile to the target target_angle = fatan2 (target_y ‐ y, target_x ‐ x);

// Determine whether we should turn left or right. // Note that itofix (128) represents half a circle. // We use & 0xFFFFFF as a trick to get an angle // between 0 and 256. if (((angle‐target_angle) & 0xFFFFFF) < itofix(128)) angle = (angle ‐ angle_stepsize) & 0xFFFFFF; else angle = (angle + angle_stepsize) & 0xFFFFFF;

rest (10);

Here is a screenshot. As you can see, the missile is represented by a blue circle with a red line in it. Thecurrent target is represented by a white dot.

Using the dot product

The solution above tackles the problem very well but that doesn't mean there aren't any other possiblesolutions. In mathematics textbooks you can find the following formula to calculate the angle between twovectors a and b:

cos (angle) = (xa * xb + ya * yb) / (length (a) * length (b))

The subexpression (xa * xb + ya * yb) is called the dot product, and is equal to the product of the lengthsmultiplied by the cosine of the angle between the vectors. We want our homing missile to go one way if theangle between its current direction and the target is between 0 and 180 degrees, and the other way if theangle between the current direction and the target is between 180 and 360 degrees.

Because of its nature, arccosine cannot be used to determine the difference between the range below 180degrees and the range above 180 degrees. You can determine the difference between the range below 90and above 270, and the range between 90 and 270, because the cosine is positive in the first case andnegative in the second. If you can't see this, take a look at the picture of the cosine wave again.

If we rotate one vector by 90 degrees, we can simply check if the result of the dot product is below or above0, to see whether we should turn left or right. To do this, we make use of a simple trick: we swap thecoordinates and change the sign of one of them. In other words, we replace xa by ya and ya by ­xa:

cos (angle) = (ya * xb ‐ xa * yb) / (length (a) * length (b))

Because we need to know if the result is positive or negative, and we don't need to know the actual value ofthe result, we can leave out the calculation of the lengths of the vectors:

result = ya * xb ‐ xa * yb

If the result is positive, we turn one way; if the result is negative, we turn the other way. The result is thefollowing piece of code:

if (fmul(dy,(target_x ‐ x)) + fmul(‐dx,(target_y ‐ y)) > 0) angle = (angle ‐ angle_stepsize) & 0xFFFFFF; else angle = (angle + angle_stepsize) & 0xFFFFFF;

In this code, dx and dy represent the missile velocity vector and target_x­x and target_y­y represent thevector to the target. Here is the entire example (circ8.c):

void dot_product_home_in () // the position of the homing missile fixed x = itofix(SCREEN_W / 2); fixed y = itofix(SCREEN_H / 2); // the angle and length of the missile's velocity vector fixed angle = 0; int length = 1; fixed angle_stepsize = itofix (3);

// determines whether the missile has reached // the target and a new one should be chosen int new_target = TRUE; // position of the target fixed target_x, target_y; // vector of missile movement fixed dx, dy;

while (!keypressed()) clear (screen); // choose new target randomly when needed if (new_target) target_x = itofix((SCREEN_W + rand() % (2 * SCREEN_W)) / 4); target_y = itofix((SCREEN_H + rand() % (2 * SCREEN_H)) / 4); new_target = FALSE;

// Move the missile. // We store dx and dy in variables so that // we can use them later on in the dot product. dx = length * fcos (angle); dy = length * fsin (angle); x += dx; y += dy;

// if we are very close to the target, set a new target if (abs (x ‐ target_x) + abs (y ‐ target_y) < itofix(10)) new_target = TRUE;

// draw a pixel where the target is putpixel (screen, fixtoi(target_x), fixtoi(target_y), makecol (255, 255, 255));

// draw the missile // (actually a circle with a line representing the angle) circle (screen, fixtoi(x), fixtoi(y), 10, makecol (0, 0, 255)); line (screen, fixtoi(x), fixtoi(y), fixtoi(x) + fixtoi (9 * fcos (angle)), fixtoi(y) + fixtoi (9 * fsin (angle)), makecol (255, 0, 0));

// Determine whether we should turn left or right // using the dot product. // We use & 0xFFFFFF as a trick to get an angle // between 0 and 256. if (fmul(dy,(target_x ‐ x)) + fmul(‐dx,(target_y ‐ y)) > 0) angle = (angle ‐ angle_stepsize) & 0xFFFFFF; else angle = (angle + angle_stepsize) & 0xFFFFFF;

rest (10);

(Thanks to Ben "entheh" Davis for pointing this out to me in EFNet #allegro.)

For some reason I can't quite put my finger on, some experienced game programmers are reluctant to useatan2() and prefer the dot product. Maybe it is because atan2() can introduce rounding errors? I don't knowfor sure. In the case of homing missiles, both methods work equally well, and it is a matter of personalpreference which one you should use.

Proof­reader's note: Since I am the aforementioned "entheh", I couldn't resist the temptation to explain whyI, and some other programmers, prefer the dot product. The dot product provides a method involvingnothing more complex than multiplication. Most implementations of atan2() implicate a couple of

comparisons (for the special cases), a division and the trigonometric function atan(), all of which areslower than multiplication. It is clear to see which method is both faster and more elegant. Besides, weproggers like to show off with obscure methods :)

Sin, cos and bitmaps: rotate_sprite

If you have read through this article, you should by now be a master of sin and cos. But this doesn't meanyou're familiar with their widespread applications. In fact, there is no end to the possibilities offered bythese two functions. I'll give you yet another example: rotating sprites.

Don't think, 'This is probably complicated and the Allegro library provides me with a sprite rotationfunction already so I don't need this.' Instead, think of all the useful modifications you can make if youknow how the sprite rotation function works ­ rotating tilemaps, for example. Or maybe you need themasked_rotate_flip_mirror_alpha_blit function. The Allegro library doesn't provide it, so you would haveto write your own.

So how does it work then? There are two ways to approach the problem. One way, the most obvious, is toloop through all pixels of the sprite you want to rotate, calculate for each pixel where on the screen itshould go, and then copy the pixel. This is certainly possible, but there will not be a one­to­onecorrespondence to the pixels on the screen. The final picture of the rotated sprite will contain gaps, whilesome pixels will be drawn twice to the screen. So we should look for another way.

Here is the alternative. We loop through all pixels on the target bitmap (often the screen) and calculatewhich pixel from the sprite should go there. This way we are sure that every pixel on the screen is filled,and no pixel is copied twice to the screen.

Let's start at screen position (0,0). Which pixel from the bitmap should go there? To make things easier weput sprite position (0,0) there. We have to start somewhere, don't we? Then we move one to the right on thescreen, to position (1,0). Which pixel from the sprite should go there? That depends on the angle we wantto rotate. If we rotate 0 degrees, we should put sprite pixel (1,0) there. If we rotate 270 degrees, we shouldput pixel (0,1) there. Here is how to calculate that for any angle:

sprite_x = cos (angle);sprite_y = sin (angle);

Then we go one more to the right. The position in the sprite we have to use now is:

sprite_x = 2 * cos (angle);sprite_y = 2 * sin (angle);

And so on. Since we are proceeding in a linear fashion, we can simply calculate the sin and cos once, andadd them to the position in the sprite each time we go one pixel to the right on the destination. Take a lookat the source below (circ9.c):

void my_rotate_sprite (BITMAP *dest_bmp, BITMAP *src_bmp, fixed angle, fixed scale) // current position in the source bitmap fixed src_x, src_y;

// current position in the destination bitmap int dest_x, dest_y;

// src_x and src_y will change each time by dx and dy fixed dx, dy;

// src_x and src_y will be initialized to start_x and start_y // at the beginning of each new line fixed start_x = 0, start_y = 0;

// We create a bit mask to make sure x and y are in bounds. // Unexpected things will happen

// if the width or height are not powers of 2. int x_mask = src_bmp‐>w ‐ 1; int y_mask = src_bmp‐>h ‐ 1;

// calculate increments for the coordinates in the source bitmap // for when we move right one pixel on the destination bitmap dx = fmul (fcos (angle), scale); dy = fmul (fsin (angle), scale);

for (dest_y = 0; dest_y < dest_bmp‐>h; dest_y++) // set the position in the source bitmap to the // beginning of this line src_x = start_x; src_y = start_y;

for (dest_x = 0; dest_x < dest_bmp‐>w; dest_x++) // Copy a pixel. // This can be optimized a lot by using // direct bitmap access. putpixel (dest_bmp, dest_x, dest_y, getpixel (src_bmp, fixtoi (src_x) & x_mask, fixtoi (src_y) & y_mask));

// advance the position in the source bitmap src_x += dx; src_y += dy;

// for the next line we have a different starting position start_x ‐= dy; start_y += dx;

Screenshot:

If you take a look at these lines:

dx = fmul (fcos (angle), scale); dy = fmul (fsin (angle), scale);

Here we calculate the sin and cos of the angle. The 'd' in 'dx' and 'dy' stands for 'delta'. These represent thechange in position in the sprite as we go to the next pixel on the screen. As you can see, a scale factor isintroduced, so we can zoom in and out.

In the following lines:

putpixel (dest_bmp, dest_x, dest_y, getpixel (src_bmp,

fixtoi (src_x) & x_mask, fixtoi (src_y) & y_mask));

...the pixel is copied from the source bitmap (the sprite) to the target bitmap (often the screen). Of course,'dest' stands for 'destination' and 'src' stands for 'source'. A mask is used to make sure the position in thesource bitmap is valid, so we won't get a pixel that is outside the source bitmap. This method only works ifthe dimensions of the source bitmap are powers of 2. For example, bitmaps of 32x32 or 64x256 wouldwork, but a bitmap of 100x100 wouldn't, because 100 is not a power of 2.

With the following lines, we move to the next position on the screen. dest_x is incremented in the for loop,and src_x and src_y are increased by the previously calculated dx and dy:

src_x += dx; src_y += dy;

After the whole line is completed, the position on the source bitmap is reset to the start position stored instart_x and start_y. Of course, start_x and start_y have to be changed in order to go one line down. Becausea position down one 'pixel' is perpendicular to a position one to the right, we use the same trick we usedfurther up with the dot product method: we replace dx with ­dy and dy with dx. So here is how the startposition is changed:

start_x ‐= dy; start_y += dx;

Rotation

Suppose in a certain game you want to rotate a point around another point. For example the player canjump on to a rope and swing from one platform to another. You can implement the swinging motion as arotation of the player around the point where the rope is attached. In order to do this, you need to calculatethe vector going from the player to the center of rotation (where the rope is attached), take the angle of thisvector, increase it a bit, and recalculate the player's position.

This is not so practical in this case though, because you most likely store the player's position in Cartesiancoordinates. You have to calculate the angle of the vector from the player to the center of rotation withatan2(). After you have increased the angle, you can calculate the new x­ and y­coordinates with sin andcos. Here is an example:

angle = atan2 (y, x);length = sqrt (x * x + y * y);angle += 1;new_x = length * cos (angle);new_y = length * sin (angle);

Because you convert from Cartesian coordinates to polar coordinates and then back again, you can loseprecision. There is a better way: you can make use of a rotation matrix. Rotation matrices ('matrices' beingthe plural of 'matrix') are widely used in the 3D graphics world, but they can be used in 2D just as well. Inshort, they provide a way to rotate a vector without converting to polar coordinates. Here I present theequations:

new_x = x * cos (angle) ‐ y * sin (angle)new_y = x * sin (angle) + y * cos (angle)

In this case, 'angle' is the angle by which you want to rotate the vector. 'x' and 'y' are the old Cartesiancoordinates of the vector, and 'new_x' and 'new_y' are the new Cartesian coordinates of this vector. As Isaid before, some people find the atan2 function awkward and like to use it as little as possible. With thismethod, you can perform rotations without using atan2. It is sensible to precalculate cos (angle) and sin(angle), since you need each one twice.

Here is a complete example using this method (circ10.c). All it does is rotate four points around the centerof the screen.

void projection_test() // initialize the coordinates of four dots fixed dot_x[4] = itofix(‐50), itofix(‐50), itofix(50), itofix(50); fixed dot_y[4] = itofix(‐50), itofix(50), itofix(50), itofix(‐50);

fixed angle = 0; fixed angle_stepsize = itofix (1);

// proj_x and proj_y will contain the projection of the dots fixed proj_x[4]; fixed proj_y[4];

int i;

// repeat this loop until Esc is pressed while (!key[KEY_ESC]) // project all the dots to their new positions after rotation for (i = 0; i < 4; i++) proj_x[i] = fmul (dot_x[i], fcos (angle)) ‐ fmul (dot_y[i], fsin (angle)); proj_y[i] = fmul (dot_x[i], fsin (angle)) + fmul (dot_y[i], fcos (angle));

// draw the four dots for (i = 0; i < 4; i++) putpixel (screen, fixtoi (proj_x[i]) + SCREEN_W / 2, fixtoi (proj_y[i]) + SCREEN_H / 2, makecol (255 ,255, 255));

rest (10); clear (screen);

angle += angle_stepsize;

For information on rotation matrices look at this link: http://www.student.hk­r.se/~pt93mm/thesis/techniques/3d_tutorial/3d.html. This also contains information on 3D projection,which is used in the next section.

There is a special case of this rotation matrix: a rotation by 90 degrees. Here is how to do that: suppose youhave a point with the coordinates (4, 8) and you want to rotate it by 90 degrees around the origin. You usethe formulae:

new_x = x * cos (90) ‐ y * sin (90)new_y = x * sin (90) + y * cos (90)

cos (90) is 0 and sin (90) is 1, so we can simplify this equation to the following:

new_x = ‐ynew_y = x

So the new coordinates are ­8, 4. We have already used this trick twice; now you know why it works!

If you want to rotate the point (4, 8) by 180 degrees around the origin, you get the following:

new_x = x * cos (180) ‐ y * sin (180)new_y = x * sin (180) + y * cos (180)

Or:

new_x = ‐xnew_y = ‐y

So the new coordinates are (­4, ­8).

In the table below you can look up how to rotate by 90, 180 and 270 degrees in this way. 90 degrees 180 degrees 270 degrees 360 degreesnew x value ­y ­x y xnew y value x ­y ­x y

Super Nintendo Mode 7

Some games make use of a cool trick to draw a bitmap in 3D. This trick can be used to draw textured floorsor ceilings, or any plane as long as it is horizontal or vertical. It is used by games like Jazz Jackrabit andWacky Racers. The Super Nintendo even has a special graphics mode to draw graphics this way and that iswhy there are lots of Super Nintendo games that use this trick. Amongst them are Mario Kart, F­Zero andeven the Squaresoft classic Secret of Mana. Nintendo calls this trick "Mode 7" and that is what I shall becalling it too. The only Allegro game that I know of that makes use of Mode 7 is Sheep, written by BenDavis.

F­Zero on the Super Nintendo:

How does it work? It is almost the same as drawing a rotated sprite, except that we have to scale eachhorizontal line to make lines that are farther away smaller. The only difficult thing is that we have to knowhow much we want to scale each line. For this we have to understand 3D projection a bit.

3D projection is only as difficult as you want it to be. What it all comes down to is that you start withcoordinates in 3D space (space_x, space_y and space_z) and you want to turn them into coordinates on thescreen (screen_x and screen_y). That means you have to make one number disappear, namely space_z.How can you do that? By dividing everything by space_z!

space_x / space_z = screen_xspace_y / spaze_z = screen_yspace_z / space_z = 1

It seems strange but it works! space_z is the distance to the point in space, and in the case of Mode 7,space_y is the height of the camera above the plane.

In Mode 7 you draw each horizontal line scaled down according to distance. So you need to know whichspace_z goes with a certain screen_y. This means we have to turn the equation around:

space_z = space_y / screen_y

So we know the distance of a certain line we want to draw. How do we know how much to scale this line

then? You have to calculate the distance in space between two points on this line. This means you want tocalculate what the difference is in space_x if you change screen_x by 1. Here is the equation:

space_x = screen_x * space_z = 1 * space_z = 1 * space_y / screen_y

There is another small problem: what is the relationship between a space coordinate and a screencoordinate? In other words: if space_x is 12, how many pixels is that? To solve this problem we have tointroduce an extra pair of variables: scale_x and scale_y. These will scale the screen_x and screen_y theright way. I have found that a scale_x and a scale_y of 200.0 are right for my purposes.

Link for Mode 7: http://members.madasafish.com/~kefka/mode7.htm

Here is the code (circ11.c). To test your intelligence, I use not space_y but space_z to represent the upwarddirection.

/* MODE_7_PARAMS is a struct containing all the different parametersthat are relevant for Mode 7, so you can pass them to the functionsas a single unit */typedef struct MODE_7_PARAMS fixed space_z; // this is the height of the camera above the plane int horizon; // this is the number of pixels line 0 is below the horizon fixed scale_x, scale_y; // this determines the scale of space coordinates // to screen coordinates MODE_7_PARAMS;

void mode_7 (BITMAP *bmp, BITMAP *tile, fixed angle, fixed cx, fixed cy, MODE_7_PARAMS params) // current screen position int screen_x, screen_y;

// the distance and horizontal scale of the line we are drawing fixed distance, horizontal_scale;

// masks to make sure we don't read pixels outside the tile int mask_x = (tile‐>w ‐ 1); int mask_y = (tile‐>h ‐ 1);

// step for points in space between two pixels on a horizontal line fixed line_dx, line_dy;

// current space position fixed space_x, space_y;

for (screen_y = 0; screen_y < bmp‐>h; screen_y++) // first calculate the distance of the line we are drawing distance = fmul (params.space_z, params.scale_y) / (screen_y + params.horizon); // then calculate the horizontal scale, or the distance between // space points on this horizontal line horizontal_scale = fdiv (distance, params.scale_x);

// calculate the dx and dy of points in space when we step // through all points on this line line_dx = fmul (‐fsin(angle), horizontal_scale); line_dy = fmul (fcos(angle), horizontal_scale);

// calculate the starting position space_x = cx + fmul (distance, fcos(angle)) ‐ bmp‐>w/2 * line_dx; space_y = cy + fmul (distance, fsin(angle)) ‐ bmp‐>w/2 * line_dy;

// go through all points in this screen line for (screen_x = 0; screen_x < bmp‐>w; screen_x++) // get a pixel from the tile and put it on the screen

putpixel (bmp, screen_x, screen_y, getpixel (tile, fixtoi (space_x) & mask_x, fixtoi (space_y) & mask_y)); // advance to the next position in space space_x += line_dx; space_y += line_dy;

Screenshot:

In this example, first a struct is defined that holds all parameters that are needed to draw a Mode 7 plane.This struct is called MODE_7_PARAMS. This struct holds the scale of the view, the height of the cameraabove the plane, and the height of the horizon on the screen.

As in the rotate_sprite example, we use two mask variables to make sure that we remain inside the texturebitmap and do not attempt to copy a pixel from outside. This of course only works if the texture bitmap hasdimensions that are powers of 2, so 32x16 and 8x8 are fine, but 100x50 is out of the question.

In contrast to the rotate_sprite function, we can't calculate the dx and dy only once. We have to do that foreach line, because each line has a different scale factor. The scale factor depends on the distance of the linefrom the camera, so we have to calculate that first:

distance = fmul (params.space_z, params.scale_y) / (screen_y + params.horizon);

horizontal_scale = fdiv (distance, params.scale_x);

line_dx = fmul (‐fsin(angle), horizontal_scale); line_dy = fmul (fcos(angle), horizontal_scale);

Now line_dx and line_dy contain the dx and dy for this line, that is, the difference in position on the spriteif we go from one screen position to the next.

After that we have to calculate the starting position on the bitmap, which is stored in space_x and space_y.We then copy a point from the source bitmap to the destination bitmap:

putpixel (bmp, screen_x, screen_y, getpixel (tile, fixtoi (space_x) & mask_x, fixtoi (space_y) & mask_y));

...and each time round, we increment space_x and space_y by the calculated line_dx and line_dy. You cansee clearly that this Mode 7 effect has a lot in common with the sprite rotation effect.

Mode 7 with objects

We also want to draw objects, or sprites, in our projection. For example, in Mario Kart, the sprites are the

'karts'. These objects become smaller when they are farther away. Also the coordinates of the object on thebitmap have to be transformed into screen coordinates with respect to the camera. You can accomplish thiswith a rotation matrix as described earlier.

We need to introduce an extra pair of variables to MODE_7_PARAMS in order to be able to scale thesprites. These variables are named obj_scale_x and obj_scale_y. If you run the program circ12.c, you cansee a blue sphere sitting in a position on the bitmap.

Here is the code (circ12.c):

/* MODE_7_PARAMS is a struct containing all the different parametersthat are relevant for Mode 7, so you can pass them to the functionsas a single unit */typedef struct MODE_7_PARAMS fixed space_z; // this is the height of the camera above the plane int horizon; // this is the number of pixels line 0 is below the horizon fixed scale_x, scale_y; // this determines the scale of space coordinates // to screen coordinates fixed obj_scale_x, obj_scale_y; // this determines the relative size of // the objects MODE_7_PARAMS;

/* draw_object just draws a single object at a fixed position, althoughthis can easily be modified to allow for more objects.bmp = bitmap to draw to. obj = sprite for the object.angle, cx, cy define the camera position.*/void draw_object (BITMAP *bmp, BITMAP *obj, fixed angle, fixed cx, fixed cy, MODE_7_PARAMS params) int width, height; int screen_y, screen_x;

// The object in this case is at a fixed position of (160, 100). // Calculate the position relative to the camera. fixed obj_x = itofix(160) ‐ cx; fixed obj_y = itofix(100) ‐ cy;

// use a rotation transformation to rotate the object by the camera // angle fixed space_x = fmul (obj_x, fcos (angle)) + fmul (obj_y, fsin (angle)); fixed space_y = ‐fmul (obj_x, fsin (angle)) + fmul (obj_y, fcos (angle));

// calculate the screen coordinates that go with these space coordinates // by dividing everything by space_x (the distance) screen_x = bmp‐>w/2 + fixtoi (fmul (fdiv (params.scale_x, space_x), space_y)); screen_y = fixtoi (fdiv (fmul (params.space_z, params.scale_y), space_x)) ‐ params.horizon;

// the size of the object has to be scaled according to the distance height = fixtoi (obj‐>h * fdiv(params.obj_scale_y, space_x)); width = fixtoi (obj‐>w * fdiv(params.obj_scale_x, space_x));

// draw the object stretch_sprite (bmp, obj, screen_x ‐ width / 2, screen_y ‐ height, width, height);

Screenshot:

Conclusion

In this article I set out to answer some of the most common questions on sine and cosine, or trigonometry ingeneral. I could give you a more mathematical explanation of sine and cosine, but I wanted this article to beof practical use to game programmers, especially to Allegro game programmers, not to give anencyclopedic description of abstract mathematics. I hope that this has been of some use to you, my dearreader. Please send me an e­mail if you have something to say about this article, whether you like it, dislikeit, find it useful, or just want to say hi. If you have any questions you can ask them on the forums athttp://www.allegro.cc. It is very likely that I'll see it there. And if you ever write an effect in a demo orgame using the explanations in this article, I would very much like to see the result.

Amarillion E­mail: [email protected] Home page: http://www.helixsoft.nl/