Previous Section  < Day Day Up >  Next Section

Visualization Experience

On the CD-ROM, you will find a demo named Basement Billiards. Here's a brief description from the programmer:

Basement Billiards is a practical demonstration of 2D collision between circles. Although the program is written in OpenGL and encompasses 3D space, the world has been rotated so that the negative z-axis faces down (as opposed to the traditional arrangement, in which the negative y-axis defines the down direction). This lets all calculations be done in the XY plane. Conservation of momentum is also demonstrated through perfectly elastic collisions, such as those between balls, and inelastic collisions, such as those between a ball and the rails. The entirety of the game code has been placed in the function DrawGLScene, which resides in OGL_Wrapper.cpp. This was done for ease of comprehension. Each section of code that ordinarily would have been allocated to a separate function is clearly marked by a large comment block. All game collision calculations are done in the section "Shooting Code," and some basic ray-to-circle collision techniques appear in the section "Prediction Code." Because this is a demo, no game mechanics are implemented, such as players taking turns or reracking the balls after they have all been pocketed.

You can move the camera with the arrow keys and zoom in and out with the A and Z keys, respectively. The C key toggles the camera's focus from the center of the table to the cue ball's current position. Clicking the cue ball begins the game's aiming phase, at which point the left and right arrows aim and the up and down arrows decrease and increase the shot's power. Holding down the Shift key allows for more precise aiming. Clicking the large cue ball in the upper-left corner adjusts the English placed on the shot. Right-clicking at any time cancels the aiming phase without shooting, and clicking fires the shot. Rinse and repeat as desired. Pressing Esc quits the game, and pressing F1 toggles between windowed and full-screen (full-screen is recommended).

Kaveh Kahrizi

Go ahead and run the demo by double-clicking Pool.exe. You'll see a pool table ready for you to break. Go ahead and play the game, paying close attention to the different types of collisions (balls colliding with each other, balls colliding against the bumper). The code for this game can be quite intimidating for a new programmer. Listing 13.1 shows the section that handles the collisions. See if you can figure out which formulas are at the root of this code.

Listing 13.1. Basement Billiards

//////////////////////////////////////////////////////////////////

// Ball-to-ball collision

//////////////////////////////////////////////////////////////////

      for(int j = i + 1; j < 16; ++j)

      {

         if(!ball[j].pocketed)

         {

            if(ball[i].collision(ball[j]) && !invalid_collision[i][j])

            {

               // Allow top or bottom english to affect the

               // cue ball

               if(!i)

               {

                  apply_english = true;

               }



               invalid_collision[i][j] = invalid_collision[j][i] =1;



               GLT_VECTOR3 intersect = { 0, };

               // Calculate the unit length vector defined by the

               // centers of the two balls

               // This is a special case where the cue ball is

               // colliding with the ball which was predicted

               // before the shot was taken. In this

               // we apply pixel-perfect collision

               if(i == 0 && j == predicted_ball)

               {

                  intersect[0] = ball[j].x - col.x;

                  intersect[1] = ball[j].y - col.y;

                  predicted_ball = 0;

                  ball[0].x = col.x;

                  ball[0].y = col.y;

               }

               // Otherwise, proceed as normal

               else

               {

                  intersect[0] = ball[j].x - ball[i].x;

                  intersect[1] = ball[j].y - ball[i].y;

               }



               gltNormalize(intersect);



               // Small optimization; no need to calculate vectors

               // and whatnot if the ball isn't moving

               bool b1_moving = false;

               bool b2_moving = false;



               if(ball[i].vec[0] || ball[i].vec[1])

                  b1_moving = true;

               if(ball[j].vec[0] || ball[j].vec[1])

                  b2_moving = true;





               // Unify the forward vectors of the two balls in

               // question

               if(b1_moving)

                  gltNormalize(ball[i].vec);

               if(b2_moving)

                  gltNormalize(ball[j].vec);



               if(ball[i].vec[0] > .95f && fabs(ball[i].vec[1]) < .05f)

                  bool what_the_heck = true;

               // Calculate the angle between the balls and the

               // vector of collision

               float angle1 = 0, angle2 = 0;

               if(b1_moving)

                  angle1 = acosf(gltDotProduct(ball[i].vec, intersect));

               if(b2_moving)

                  angle2 = acosf(fabs(gltDotProduct(ball[j].vec, intersect)));



               // This occurs only in the event that this

               // collision is invalid and is a result of

               // a previous collision's not being completely

               // resolved from the last frame. This collision

               // will be ignored.

//                        if(angle1 > (90 * GLT_PI_DIV_180) && angle1 < 270 * GLT_PI_DIV_180)

//                        continue;



               if(i == 0)

                  started = true;

               // These variables hold the components of the

               // respective ball's vector.

               // comp1 will hold the component transferred to

               // the owning ball, and comp2 will hold the

               // component transferred to the colliding ball.

               float b1_comp1 = 0, b1_comp2 = 0, b2_comp1 = 0, b2_comp2 =0;



               // Calculate the components of ball1 (defined by

               // [i])

               if(b1_moving)

               {

                  b1_comp1 = sinf(angle1) * ball[i].velocity;

                  b1_comp2 = cosf(angle1) * ball[i].velocity;

               }

               // Calculate the components of ball2 (defined by

               // [j])

               if(b2_moving)

               {

                  b2_comp1 = sinf(angle2) * ball[j].velocity;

                  b2_comp2 = cosf(angle2) * ball[j].velocity;

               }



               // Calculate the vector that is perpendicular to

               // the vector of collision

               GLT_VECTOR3 norm_intersect = { 0, };

               gltCrossProduct(world_up, intersect, norm_intersect);



               // These are the resultant component vectors of the

               // ball's travel vector

               GLT_VECTOR3 b1_vcomp1 = {0, };

               GLT_VECTOR3 b1_vcomp2 = {0, };

               GLT_VECTOR3 b2_vcomp1 = {0, };

               GLT_VECTOR3 b2_vcomp2 = {0, };



               // Calculate the resultant vector of ball1 ([i])

               if(gltGetAngleDeg(ball[i].vec, norm_intersect) < 90)

               {

                  b1_vcomp1[0] = norm_intersect[0] * b1_comp1;

                  b1_vcomp1[1] = norm_intersect[1] * b1_comp1;

               }

               else

               {

                  b1_vcomp1[0] = -norm_intersect[0] * b1_comp1;

                  b1_vcomp1[1] = -norm_intersect[1] * b1_comp1;

               }

               b1_vcomp2[0] = -intersect[0] * b2_comp2;

               b1_vcomp2[1] = -intersect[1] * b2_comp2;



               ball[i].vec[0] = b1_vcomp1[0] + b1_vcomp2[0];

               ball[i].vec[1] = b1_vcomp1[1] + b1_vcomp2[1];



               ball[i].velocity = sqrtf(ball[i].vec[0] * 

               ball[i].vec[0] + ball[i].vec[1] * ball[i].vec[1]);



               // Calculate the resultant vector of ball2 ([j])

               if(gltGetAngleDeg(ball[j].vec, norm_intersect) < 90)

               {

                  b2_vcomp1[0] = norm_intersect[0] * b2_comp1;

                  b2_vcomp1[1] = norm_intersect[1] * b2_comp1;

               }

               else

               {

                  b2_vcomp1[0] = -norm_intersect[0] * b2_comp1;

                  b2_vcomp1[1] = -norm_intersect[1] * b2_comp1;

               }

               b2_vcomp2[0] = intersect[0] * b1_comp2;

               b2_vcomp2[1] = intersect[1] * b1_comp2;



               // Finally we sum up each ball's respective

               // vector and assign it to the ball

               ball[j].vec[0] = b2_vcomp1[0] + b2_vcomp2[0];

               ball[j].vec[1] = b2_vcomp1[1] + b2_vcomp2[1];

               ball[j].velocity = sqrtf(ball[j].vec[0] * 

               ball[j].vec[0] + ball[j].vec[1] * ball[j].vec[1]);



               // Update the vector perpendicular to our forward

               // vector for purposes of spinning the balls

               ball[i].nvec[0] = -ball[i].vec[1];

               ball[i].nvec[1] = ball[i].vec[0];

               ball[j].nvec[0] = -ball[j].vec[1];

               ball[j].nvec[1] = ball[j].vec[0];

               gltNormalize(ball[i].nvec);

               gltNormalize(ball[j].nvec);



//               soundPlayer.playSample(collision);

            }

            // Small buffer to correct some minor collision

            // glitches

            else

            {

               invalid_collision[i][j] = invalid_collision[j][i] = 0;

            }

         }

      }

   }

}

    Previous Section  < Day Day Up >  Next Section