Requirements & Installation ] Conceptual Overview ] The Part Editor ] The Geometry Editor ] Importing 3ds models ] Examples ] OpenGL Subsystem ] Inside the Part Editor ] Input Device Details ] [ Inside CoolPool ] Inside Lunar Lander ] Deploying Applications ] Resources and Links ]

Appendix D. Inside "CoolPool"

 PgPoolGameShell

PgPoolGameShell is the shell presenter for the CoolPool sample application. The application uses a single CcOpenglView to render a view of a pool table and enclosing room. Keyboard input is based on the Dolphin event mechanism. The game makes use of CcToolManager and CcSelectionManager for ball and cue stick selection and manipulation via the mouse. An instance of CcTimer is used to control the animation process.

PgPoolGameShell is responsible for initializing scene components and keystroke mappings, responding to keyboard input and menu commands, and starting and stopping the animation process. The shell uses an instance of PgPoolEngine to control ball movement and perform ball-to-ball and ball-to-bumper collision detection. PgPoolEngine, in turn, uses an instance of CcBallCollisionDetector to find the ball-to-ball collisions.

During initialization, PgPoolGameShell loads the pool table and room from disk (see roomTable2.stb) and adds them to the root node of its scene graph. It then initializes the balls and cue (subinstances of CcQuadricShape) and applies appropriate textures. Balls are then set into their "racked" positions (see #rackEmUp).

The game operates in two distinct modes. In the first mode, the animation process is controlled by timer ticks generated by the CcTimer and keyboard input is disabled. This mode is active when 1) at least one ball on the table is in motion (has a non-zero velocity) or 2) when the camera is automatically being aligned with the cue ball following a shot or 3) when a cue stroke is in progress. When none of these conditions is met, the game is in its second mode where the CcTimer is disabled and animation, if any, occurs as the result of keyboard events generated as the user positions the viewpoint within the scene or adjusts the orientation of the cue.

When the game shell first opens, all balls are stationary and the viewpoint and cue can be positioned with the keyboard. If the shell is subsequently sent the message #break, a cue stroke operation is started (see #animateCueStroke) and the CcTimer is activated. When the cue stroke is completed, the shell receives the message #shoot: which sets the cue ball in motion. Once the cue ball has reached the vicinity of the racked balls, the racked balls are given their break velocities. The animation process continues under the control of the CcTimer until all balls have come to rest.

PgPoolGameShell receives the message #timerTick: each time a tick event is generated by the CcTimer. PgPoolGameShell>>timerTick: determines the time elapsed since the previous timer tick and, with the assistance of PgPoolEngine, updates the state of the balls based on that interval. It also checks to see if all balls have come to rest and, if so, disables the timer (Note: methods #activateTimer and #deactivateTimer on PgPoolGameShell ensure that keyboard input is ignored while the timer active). After the state of the balls has been updated, #timerTick: refreshes the view.

 

 Collision Detection

For every #timerTick: message it receives, PgPoolGameShell sends the PgPoolEngine message #advanceStateBy:. The message argument is the interval of time by which the engine is to advance the state of the balls. The engine accomplishes this by determining the series of collisions that will occur during the interval and adjusting ball positions and velocities accordingly. The steps involved in this process are as follows (remember that a ball’s velocity includes both direction and magnitude):

  1. Let’s call the initial time interval, provided as the argument to #timerTick:, "deltaTimeTotal". First, determine all collisions that will occur within deltaTimeTotal based strictly on ball positions and velocities. This involves finding collision participants (ball-to-ball or ball-to-bumper) and the point in time at which each collision occurs.

  2. From the set of collisions found in step 1, select the collision that occurs at the earliest point in time. This collision is the only "valid" collision since the data used in step 1 to determine the other, "later" collisions (namely, ball velocities and positions) is no longer valid once the first collision has occurred (At least one ball’s velocity is changed by that collision!) Call the time interval to the first collision "deltaTime1".

  3. Move each ball by the amount (deltaTime1 X ballVelocity) and adjust the velocity(s) of the collision participant(s) found in step 2 based on collision characteristics (e.g., speed and angle of incidence).

  4. At this point, ball states have been advanced by deltaTime1. However, the engine is responsible for advancing ball states by the entire deltaTimeTotal interval so there’s still a ways to go.

  5. Repeat the process starting at step 1 but now the interval for which the complete set of collisions is to be calculated is reduced by deltaTime1. i.e., deltaTimeTotal := deltaTimeTotal – deltaTime1. The process is repeated until no collisions are detected within the remaining deltaTimeTotal. At that point, control is returned to PgPoolGameShell>>timerTick:. (Of course, if no collisions are detected within the first pass, ball states are simply updated based on the initial deltaTimeTotal interval and control is returned immediately).

In addition to performing collision detection, the engine decelerates ball velocities as they roll across the table and checks to see if any balls have rolled into a pocket. The engine also generates sound effects for ball-to-ball collisions.