LAB 3 -- Walls and obstacles

In this lab we extend our simulation of billiard/pinball by adding objects describing walls and obstacles, and by simulating collisions between the balls. The Object Oriented Approach allows us to add extra functionality at little cost.

Discussion

The design of Lab 2 was not satisfactory -- walls were not represented as objects (their description was by four global constants, which is really bad style). We now introduce a new class Wall to represent walls. Walls are essentially solid rectangles with color and a factor by which they change a ball's velocity on reflection. You will need 4 walls to create closed screen space. Let us call a wall placed inside that space an obstacle. Unlike the border walls, all four surfaces of an obstacle will be reflecting balls.

To implement collisions among the balls, we need to check for NUM_BALLS2 possible collisions on each tick, because any ball can collide with any other ball. To simplify programming (to the point when our model is a physicist's nightmare) we use the following
Hack:

In each case, we check the next position of a ball before actually drawing it, so that we don't paint over owr walls. If a collision as defined above will happen, we change the velocities of balls to avoid it (reflection). Reflection off another ball is not very different from reflection off a wall.

Bug: If a wall is thin enough, and a ball is fast enough, its next position can be already behind the wall (and not intersecting with it). In this case our hack finds no collision, i.e. the ball "tunnels" through the wall, just as in Quantum Mechanics!

The code for the simulation will be as follows:

Wall w[ NUM_WALLS ]; 
Ball b[ NUM_BALLS ];

// initialize the walls somehow to create a closed space, with an 
// obstacle in the middle

for(int i=0; i< NUM_WALLS; i++ )  // draw the walls
  w[i].Draw();
           
for(int t=0; t<1000; t++){  // time
  for( int j=0; j < NUM_BALLS; j++ ){  // for each ball
    
    // check for collisions with other balls
    for( int l=0; l < NUM_BALLS; l++ ) 
      if( b[j].Collides( b[l] ) ) 
        b[j].Reflect(  b[l] );
   
    // check for collisions with walls
    for( int k=0; k < NUM_WALLS; k++ )
      if( b[j].Collides( w[k] ) ) 
        b[j].Reflect(  w[k] );
        
    b[j].Move();
  }
  Delay(30);
}
Some points about the code:

Question: How many checks for intersection of two rectangles are made on each tick before the picture is re-drawn? Make sure you understand this. (The code uses the so-called nested loops when one loop is enclosed inside another; the inner loop will run once for each value of the counter variable of the outer loop.)

Task 1

Design and implement class Wall that represents a rectangular wall. You will need at least 5 walls, 4 to enclose the table space and one to put in the middle (see my example in Task 3 below). A struct rather that a class can be recommended because methods of class Ball will need to peek at Wall's data. A more sophisticated approach is to declare Ball to be a friend of Wall. If a class is declared as a friend of another class, by adding the line

  friend friendly_class_name; 
in the definition of a class, then all member functions of the friendly class can access the private members of the class.

Change the functions Collide and Reflect of Ball so that there are two forms of each,

void Ball::Collide( const Ball & b );
void Ball::Collide( const Wall & w );
void Ball::Reflect( const Ball & b );
void Ball::Reflect( const Wall & w );
that check for collisions and handle reflections with a wall and a ball respectively. Use the "flat" give-away code as an example.

So far, you can place the balls randomly on the screen, and re-run if it happens that they initially overlap with the obstacles and other balls.

Task 2

Write the code so that the balls are still placed randomly with random radii and speeds, but they do not overlap either with walls or with other balls. Increase the number of obstacles and make sure your program is still correct. Clearly, you will need to check each random ball being created for overlaps and get other random values if there is an overlap. You need not put all of this logic into the constructor, because the a constructor is usually not expected to know about other existing instances of the class. Use nested loops.

Task 3

Create two walls, red and blue, such that the red one speeds up each ball it reflect, while the blue one slows it down. Be aware of rounding problems: the coordinates at which you draw the ball have to be integers, while a velocity can be a double.

There are many different ways to set the walls after they have been created by the default constructor Wall::Wall(). My example is

  
Wall w[ NUM_WALLS ];  // Default Wall() constructor called NUM_WALLS 
                      // times. It cannot do much useful work, 
                      // expect allocating space.

// enclosing walls
w[0] = Wall( MakeRect( 50, 50, 350, 60   ) );        
w[1] = Wall( MakeRect( 50, 350, 360, 360 ) );
w[2] = Wall( MakeRect( 50, 60, 60, 360   ) );
w[3] = Wall( MakeRect( 350, 50, 360, 360 ) );

// red wall, speeds up the balls by 1.5
w[4] = Wall( MakeRect( 190, 220, 220, 250 ), 1.5, 255, 0, 0 );  
// ...
and it uses the fact that structs and class can assigned using a default operator=, which does member-wise assignment. MakeRect(int,int,int,int) is a standard CoreTools function that makes and returns a Rect, the built-in Mac struct with no methods (see Lecture notes for Lab 1). You can use myRect instead, or just four integers.

Hand in: