Putting it all together

Let’s use what we know now to enhance the animation we constructed in our last examples.

Placing multiple images on the screen

In the animation we have constructed so far, we placed a single image on the screen at a location we specified in the code. Suppose we want to place more than one image on the screen and let the user control where these images will be located, and in what direction they will move initially.

Configuration files

One common method for doing this is to use a text file to control the startup og the program. We will not get too fancy with this file for now. We will set up just enough information to get our animation going.

File structure

We will use a file to define the number of balls we want to see, then set up a series of numbers for each ball specifying where the ball will be placed (X,Y) and the initial velocity of that ball(Vx,Vy). We used these data items in our original program.

Here is a sample file we might use:

3
100
100
2
3
300
300
4
1
500
500
1
3

The reason we constructed the file this way has to do with now we will need to access the file using only DarkGDK file routines.

DarkGDK file access

We use the following routines to access this text file:

Name Purpose    
dbFileExist(char * filename) Does the named file exist (boolean)    
dbOpenToRead(int fID char * filename) Open for reading use the ID
dbReadString(int fID) Read string from file ID    
dbCloseFile(fID) Close the file    

Note

In the directory where you installed DarkGDK you will find a help file containing the detailed list of functions supported by the library. Use this to find other useful functions for your code!

The DarkGDK string read routine loads a single line of text into memory and returns the address of the memory location. We need to use the standard C++ string copy function to move this string into a buffer in our program, then convert it into a usable form.

Reading the data file

Here is the code we need to read this file.

const int INPUT_FILE = 1;
...

void getConfig() {
    char buffer[128];

    dbOpenToRead(INPUT_FILE,file_name);
    strcpy(buffer,dbReadString(INPUT_FILE));
    NUM_BALLS = atoi(buffer);

    for(int nb=0;nb<NUM_BALLS;nb++){
            strcpy(buffer,dbReadString(INPUT_FILE));
            position[nb][0] = atoi(buffer);
            strcpy(buffer,dbReadString(INPUT_FILE));
            position[nb][1] = atoi(buffer);
            strcpy(buffer,dbReadString(INPUT_FILE));
            speed[nb][0] = atoi(buffer);
            strcpy(buffer,dbReadString(INPUT_FILE));
            speed[nb][1] = atoi(buffer);
    }
}

Displaying multiple balls

Changing the code we used to display a single image so we show multiple images is simple. We will use two arrays to store ball position and speed and loop over the number of balls to display the set:

const int MAX_BALLS = 10;

int speed[MAX_BALLS][2];
int position[MAX_BALLS][2];

Here is the loop we use to display the balls:

while (LoopGDK()) {
        dbCLS(background);
        for(int nb=0;nb<NUM_BALLS;nb++) {
                dbSprite(nb+1,position[nb][0], position[nb][1], 1);
                TableMover(NUM_BALLS, position, speed, EARTH_RADIUS);
                checkCollisions(NUM_BALLS, position, speed, EARTH_RADIUS);
        }
        dbSync();
}

In this example, we modify the TableMover routine used to update ball positions and handle the collisions with the walls. We pass in the arrays holding the position and speed data and let the routine update all ball position and speed data using the same rules we used before. Since the ball position and speed arrays are passed by address, the changes in the routine will be felt throughout the program.

Handling ball to ball collisions

The new bit of code we want to include in this program handles the collisions between the balls so they appear to bounce off of each other. The physics of such collisions is an extension of the method we used for the balls bouncing off of the walls. However, the math gets a bit messier since balls can collide in almost any configuration in the middle of the screen. So, how do we handle this?

Easy - Google it!

The math we want to research is based on the principle of Conservation of Momentum. There are some odd properties of this principle that we want to model. For instance, if a ball at rest is hit by a ball moving straight at it, the first ball will take on the velocity of the second ball and the second ball will stop! Yikes!

Here is an animation showing what we need to do:

../../_images/Collisions.gif

Note

In this example, one ball is moving, and one stationary. In the general case, both balls are moving. (I could not generate a simple animation for that case).

Finding the code you need is a bit difficult. It might be easier to derive the equations yourself. Here is the basic logic:

Equations for determining a bounce

We are given two balls with equal radii and mass. They are moving at a speed defined by the two vector components Vx and Vy. They have a position on the coordinate system defined by X and Y. We will use a trailing number to refer to each ball. So, here are our given parameters:

Vx1 velocity of nball 1 in the X direction (right)
Vy1 velocity of ball 1 in the Y direction (down)
X1 X position of ball 1
Y1 Y position of ball 1
Vx2 velocity of nball 2 in the X direction (right)
Vy2 velocity of ball 2 in the Y direction (down)
X2 X position of ball 2
Y2 Y position of ball 2

Detecting a collision

If the distance between the centers of the ball is less than or equal to twice the radius of a ball, we must have collided. Here is the basic layout of the event:

../../_images/BasicCollision.png

At that point, we need to determine what happens when the collision occurs. Here is some code we could use to determine if two balls have collided:

bool collided(float X1, float Y1, float X2, float Y2, float radius) {
    float dx = X2 - X1;
    float dy = Y2 - Y1;
    float dist = sqrt(dx*dx + dy*dy);
    return dist <= (2.0*radius);
}

Checking for collisions between a set of balls

In order to deal with a set of balls, we need to check every ball against every other ball to see if we have any collisions. However, this will result in checking more cases than needed if we are not careful. If we check ball 1 against ball 2, we do not need to check ball 2 against ball 1 again, we already did that!. And a ball certainly cannot bounce against itself. So, if we use a simple integer to refer to a ball, we can set up a system to check all cases using nested loops. For N balls, we use this:

for(ball1 = 0;ball1<N;ball1++)  // check each ball in turn
    for(ball2=ball1+1;ball2<N;ball2++) { // check all other balls
        // check for collision between ball1 and ball 2
        if collided()) {
            handleCollisison()
        }
    }

Storing data for multiple balls

To make the code easier to construct, we will set up two arrays to hold speed and position data. These arrays will be named:

  • float pos[MAX_BALLS][2]; - (0 = X position, 1 = Y position)
  • float vel[MAX_BALLS][2]; - (0 = X speed, 1 = Y speed)

We also store the radius:

  • float radius;

We are using floats to allow for precise calculations of speed. We will convert these values back to integer for use in the graphics, losing some accuracy, but the simulation should be adequate!

Rewriting the collided function

Now, the collided function can be called with two integers referring to the two balls to check:

bool collided(int ball1, int ball2, int rad) {
    float X1 = pos[ball1][0];
    float Y1 = pos[ball1][1];
    float X2 = pos[ball2][0];
    float Y2 = pos[ball2][1];
    float dx = X2 - X1;
    float dy = Y2 - Y1;
    float dist = sqrt(dx*dx + dy*dy);
    return dist <= (2.0*radius);
}

Total Velocity for a ball

If we have the velocity of the ball expressed in the X component and the Y component, the total velocity is:

  • V1 = sqrt(Vx1*Vx1 + Vy1*Vy1)
  • V2 = sqrt(Vx2*Vx2 + Vy2*Vy2)

Angle between the velocity vector and the X axis

The angle between the velocity vectors and the X axis is:

  • angle1 = atan(Vy1/Vx1) = atan2(Vy1,Vx1)
  • angle2 = atan(Vy2/Vx2) = atan2(Vy2,Vx2)

Note

To avoid division problems in determining the angle, we should use the atan2 function!

Angle between the line between centers and the X axis

The angle between a line drawn between the two ball centers is defined as:

  • dy = Y2 - Y1
  • dx = X2 - X1
  • angleCL = atan2((dy,dx)

Angle between velocity vectors and centerline

We will be transforming the velocity vector into components along the centerline and perpendicular to it. Here is the projection we will use:

../../_images/ProjectedVelocity.png

The angle between the velocity vectors and the centerline are calculated as:

  • proj_angle1 = angle1 - angleCL
  • proj_angle2 = angle2 - angleCL

Calculating the new velocity components

The component of the velocity vector along the centerline is given by:

  • Vparallel1 = V1 * cos(proj_angle1)
  • Vnormal1 = V1 * sin(proj_angle1)
  • Vparallel2 = V2 * cos(proj_angle2)
  • Vnormal2 = V2 * sin(proj_angle2)

Conservation of momentun

The collision satisfies law of conservation of momentum. In breaking the velocity into components along the centerline and perpendicular to is, we can satisfy this conservation law by leaving the velocities perpendicular to the centerline alone, and dealing with just those along the centerline (which is a one dimensional collision). The basic law can be satisfied using this:

  • Vparallel1 = Vparallel2
  • Vparallel2 = Vparallel1 (the velocities swap)

This is true only if the mass of each ball is the same.

Calculating the new velocities

Once we know the new total velocity, we can figure out the new direction, and from that the nex Vx and Vy components:

First we get the new velocity

  • Vfinal1 = sqrt(Vparallel1*Vparallel1 + Vnormal1*Vnormal1)
  • Vfinal1 = sqrt(Vparallel1*Vparallel1 + Vnormal1*Vnormal1)

Then we calculate the new final velocities

The final angle between the new velocity vectors can be calculated by the following code:

Note

The derivation from this point is incomplete while I build a few more graphics. In the meantime, the final code will be chown:

The final code

Here is the collision code. This code can deal with two balls of unequal mass, but needs to be modified to allow the game to deal with this situation. Perhaps we could calculate the mass from the ball radius if ball sizes could vary. That would be an enhancement of the code for a more general game (when planets collide, for example!)

const float mass1 = 1.0;    // for now
const float mass2 = 1.0;

void handleCollision(ball, ball2) {
    dx = X1-X2;
    dy = ball._y-Y2;
    angleCL = atan2(dy, dx);
    V1 = sqrt(Vx1*Vx1+Vy1*Vy1);
    V2 = sqrt(Vx2*Vx2+Vy2*Vy2);
    angle1 = atan2(Vy1, Vx1);
    angle2 = atan2(Vy2, Vx2);
    new_xspeed_1 = V1*cos(angle1-angleCL);
    new_yspeed_1 = V1*sin(angle1-angleCL);
    new_xspeed_2 = V2*cos(angle2-angleCL);
    new_yspeed_2 = V2*sin(angle2-angleCL);
    final_xspeed_1 = ((mass1-mass2)*new_xspeed_1+
        (mass1+mass2)*new_xspeed_2)/(mass1+mass2);
    final_xspeed_2 = ((ball.mass+ball.mass)*new_xspeed_1+
        (mass2-mass1)*new_xspeed_2)/(mass1+mass2);
    final_yspeed_1 = new_yspeed_1;
    final_yspeed_2 = new_yspeed_2;
    Vx1 = cos(angleCL)*final_xspeed_1+cos(angleCL+PI/2)*final_yspeed_1;
    Vy1 = sin(angleCL)*final_xspeed_1+sin(angleCL+PI/2)*final_yspeed_1;
    Vx2 = cos(angleCL)*final_xspeed_2+cos(angleCL+PI/2)*final_yspeed_2;
    Vy2 = sin(angleCL)*final_xspeed_2+sin(angleCL+PI/2)*final_yspeed_2;
}