When multiple moving objects are present, each moving object is tested with the static geometry for intersections and the nearest intersection is recorded. Then the intersection test is performed for collisions among moving objects, where each object is tested with everyone else. The returned intersection is compared with the intersection returned by the static objects and the closest one is take. The whole simulation is updated to that point, (i.e. if the closest intersection would be after 0.5 sec. we would move all the objects for 0.5 seconds), the reflection vector is calculated for the colliding object and the loop is run again for the remaining time.
Special Effects
Explosions
Every time a collision takes place an explosion is triggered at the collision point. A nice way to model explosions is to alpha blend two polygons which are perpendicular to each other and have as the center the point of interest (here intersection point). The polygons are scaled and disappear over time. The disappearing is done by changing the alpha values of the vertices from 1 to 0, over time. Because a lot of alpha blended polygons can cause problems and overlap each other (as it is stated in the Red Book in the chapter about transparency and blending) because of the Z buffer, we borrow a technique used in particle rendering. To be correct we had to sort the polygons from back to front according to their eye point distance, but disabling the Depth buffer writes (not reads) does also the trick (this is also documented in the red book). Notice that we limit our number of explosions to maximum 20 per frame, if additional explosions occur and the buffer is full, the explosion is discarded. The source which updates and renders the explosions is
//render/blend explosions
glEnable(GL_BLEND); //enable blending
glDepthMask(GL_FALSE); //disable depth buffer writtes
glBindTexture(GL_TEXTURE_2D, texture[1]); //upload texture
for(i = 0; i < 20; i++) //update and render explosions
{
if (ExplosionArray[i]._Alpha >= 0) {
glPushMatrix();
ExplosionArray[i]._Alpha -= 0.01f; //update alpha
ExplosionArray[i]._Scale += 0.03f; //update scale
//assign vertices colour yellow with alpha
// colour tracks ambient and diffuse
glColor4f(1, 1, 0, ExplosionArray[i]._Alpha);
//scale
glScalef(ExplosionArray[i]._Scale, ExplosionArray[i]._Scale, ExplosionArray[i]._Scale);
// translate into position taking into acout the offset caused by the scale
glTranslatef((float)ExplosionArray[i]._Position.X() / ExplosionArray[i]._Scale, (float)ExplosionArray[i]._Position.Y() / ExplosionArray[i]._Scale, (float)ExplosionArray[i]._Position.Z() / ExplosionArray[i]._Scale);
glCallList(dlist); // call display list glPopMatrix();
}
}
Sound
For the sound the windows multimedia function PlaySound is used. This is a quick and dirty way to play wav files quickly and without trouble.
Explaining the Code
Congratulations…
If you are still with me you have survived successfully the theory section ;)
Before having fun playing around with the demo, some further explanations about the source code are necessary.
The main flow and steps of the simulation are as follows (in pseudo code).
While (Timestep != 0) {
For each ball {
compute nearest collision with planes;
compute nearest collision with cylinders;
Save and replace if it the nearest intersection in time computed until now;
}
Check for collision among moving balls;
Save and replace if it the nearest intersection in time computed until now;
If (Collision occurred) {
Move All Balls for time equal to collision time;
(We already have computed the point, normal and collision time.)
Compute Response;
Timestep -= CollisonTime;
} else Move All Balls for time equal to Timestep
}
The actual code which implements the above pseudo code is much harder to read but essentially is an exact implementation of the pseudo code above.
// While time step not over
while (RestTime > ZERO) {
lamda = 10000; //initialize to very large value
// For all the balls find closest intersection between balls and planes/cylinders
for (int i = 0; i < NrOfBalls; i++) {
// compute new position and distance
OldPos[i] = ArrayPos[i];
TVector::unit(ArrayVel[i],uveloc);
ArrayPos[i] = ArrayPos[i] + ArrayVel[i] * RestTime;
rt2 = OldPos[i].dist(ArrayPos[i]);
// Test if collision occured between ball and all 5 planes
if (TestIntersionPlane(pl1, OldPos[i], uveloc, rt, norm)) {
// Find intersection time
rt4 = rt * RestTime / rt2;
// if smaller than the one already stored replace and in timestep
if (rt4 <= lamda) {
// if intersection time in current time step
if (rt4 <= RestTime + ZERO) if (! ((rt <= ZERO) && (uveloc.dot(norm) > ZERO)) ) {
normal = norm;
point=OldPos[i] + uveloc * rt;
lamda = rt4;
BallNr = i;
}
}
}
if (TestIntersionPlane(pl2, OldPos[i], uveloc, rt, norm)) {
//…The same as above omitted for space reasons
}
if (TestIntersionPlane(pl3, OldPos[i], uveloc, rt, norm)) {
//…The same as above omitted for space reasons
}
if (TestIntersionPlane(pl4, OldPos[i], uveloc, rt, norm)) {
//…The same as above omitted for space reasons
}
if (TestIntersionPlane(pl5, OldPos[i], uveloc, rt, norm)) {
//…The same as above omitted for space reasons
}
// Now test intersection with the 3 cylinders
if (TestIntersionCylinder(cyl1, OldPos[i], uveloc, rt, norm, Nc)) {
rt4 = rt * RestTime / rt2;
if (rt4 <= lamda) {
if (rt4 <= RestTime + ZERO) if (! ((rt <= ZERO) && (uveloc.dot(norm) > ZERO)) ) {
normal = norm;
point = Nc;
lamda = rt4;
BallNr = i;
}
}
}
if (TestIntersionCylinder(cyl2, OldPos[i], uveloc, rt, norm, Nc)) {
//…The same as above omitted for space reasons
}
if (TestIntersionCylinder(cyl3, OldPos[i], uveloc, rt, norm, Nc)) {
//…The same as above ommited for space reasons
}
}
// After all balls were tested with planes/cylinders test for collision