Generating asteroids
Last updated: 17-06-2006
Project: Game Programming with DirectX 9
Prerequisites: Completion of all the preceding tutorials in this project is required.
Downloads: DirectXAsteroids.zip
In this version of our application we are going to create asteroids.
The source code that comes with this tutorial (see Downloads section above) extends the previous solution.
To see all the tutorials that belong to this project click on the project name above.
We will implement asteroids in the same way we did fireballs in a previous tutorial and will take advantage of the Fireball class we already have.
vector<Fireball> asteroids;
The generateAsteroid() method creates a new asteroid and adds it to the asteroids vector. It randomly selects the X and Y asteroid's position values. The Z value is always the same and is defined by the MAX_ASTEROID_Z constant.
void generateAsteroid(){
    
    srand((unsigned)time(0)); 
    int randomX = (rand()%10); // random between 0 and n-1
    if(randomX % 2 == 0){randomX = -randomX;}

    srand((unsigned)time(0)); 
    int randomY = (rand()%5); // random between 0 and n-1
    if(randomY % 2 == 0){randomY = -randomY;}
    
    D3DXVECTOR3 position = D3DXVECTOR3(randomX, randomY, MAX_ASTEROID_Z);
    LPD3DXMESH mesh = 0;

    D3DXCreateSphere(
            directXDevice,
            1.2f, // radius
            12,
            2,
            &mesh,
            0);

    Fireball fireball(position, mesh);
    asteroids.push_back(fireball);
}

The drawAsteroidMove() method is very similar to the drawBoxFireMove() method we have developed in a previous tutorial. The only difference here is that our asteroids move in the opposite direction from the fireballs. That's why we increase the asteroid's Z position value each time with the timeDelta (in place of subtracting the timeDelta value like it is the case with fireballs). Notice that we do not create an asteroid sphere here, since it was already created inside the generateAsteroid() method.
void drawAsteroidMove(D3DXVECTOR3 & position, LPD3DXMESH mesh){

    if(position.z <= MIN_ASTEROID_Z) return;
    
    D3DXVECTOR3 direction = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 vectorA, vectorB;
    D3DXMATRIX matrix;

    vectorA = D3DXVECTOR3(position.x, position.y, position.z);
    position.z += timeDelta;
    vectorB = D3DXVECTOR3(position.x, position.y, position.z);

    direction = vectorA - vectorB;
    D3DXVec3Normalize(&direction, &direction);
    position = vectorA + (direction * timeDelta);
    D3DXMatrixTranslation(&matrix, position.x, position.y, position.z);

    directXDevice->SetTransform(D3DTS_WORLD, &matrix);
    mesh->DrawSubset(0);
}
Because we generate a significant amount of objects we also must to clean up the memory in the run time and not only when we exit the program. The candidates for run time cleanup are all the invisible objects. The eraseInvisibleObjects() method bellow does just that by checking the Z position value of the first element of the vector. If first element of the vector did reach his maximum (for fireballs) or his minimum (for asteroids) Z position value then we can release its mesh from memory and update the label text accordingly. Notice that we did add new labels to the control panel. These show the number of elements in fireballs and asteroids vectors for testing purposes.
void eraseInvisibleObjects(){
    // erase vector's first fired invisible fireball
    if(fireballs.size() > 0){
        if(fireballs.begin()->position.z >= MAX_FIREBALL_Z){
                if(fireballs.begin()->mesh){
                    fireballs.begin()->mesh->Release();
                    fireballs.begin()->mesh = NULL;
                }
                fireballs.erase(fireballs.begin());            
        }
    }

    // update fireballs vector label
    SendMessage(hwndControlPanel, WM_COMMAND, ID_FIREBALLS_VECTOR, 
    (LPARAM)CStr(fireballs.size()).c_str());

    // erase vector's first generated invisible asteroid
    if(asteroids.size() > 0){
        if(asteroids.begin()->position.z <= MIN_ASTEROID_Z){
            if(asteroids.begin()->mesh){
                asteroids.begin()->mesh->Release();
                asteroids.begin()->mesh = NULL;
            }
            asteroids.erase(asteroids.begin());
        }
    }

    // update asteroids vector label
    SendMessage(hwndControlPanel, WM_COMMAND, ID_ASTEROIDS_VECTOR, 
    (LPARAM)CStr(asteroids.size()).c_str());
}
When you run the application you can see that after fireballs and asteroids became invisible they are erased.
Sometimes we have to take care that all the objects are cleaned from the screen and from the memory (not only the invisible objects). This is what we do in the eraseAllObjects() method. It loops through fireballs and asteroids vectors and releases the mesh of each object from the memory. It also updates associated with the vectors labels.
void eraseAllObjects(){

    // eraase all fireballs    
    for(vector<Fireball>::iterator iterFireballs = fireballs.begin(); 
            iterFireballs != fireballs.end(); ++iterFireballs){
                if(iterFireballs->mesh){
                    iterFireballs->mesh->Release();
                    iterFireballs->mesh = NULL;
                }
    }
    fireballs.clear();

    // erase all asteroids
    for(vector<Fireball>::iterator iterAsteroids = asteroids.begin(); 
            iterAsteroids != asteroids.end(); ++iterAsteroids){
                if(iterAsteroids->mesh){
                    iterAsteroids->mesh->Release();
                    iterAsteroids->mesh = NULL;
                }
    }
    asteroids.clear();

    // update fireballs vector label
    SendMessage(hwndControlPanel, WM_COMMAND, ID_FIREBALLS_VECTOR, 
    (LPARAM)CStr(fireballs.size()).c_str());

    // update asteroids vector label
    SendMessage(hwndControlPanel, WM_COMMAND, ID_ASTEROIDS_VECTOR, 
    (LPARAM)CStr(asteroids.size()).c_str());
}

Inside the drawScene() method we add the rendering of asteroids, same way we did draw fireballs.
// draw asteroids
for(vector<Fireball>::reverse_iterator iterAsteroids = asteroids.rbegin(); 
    iterAsteroids != asteroids.rend(); ++iterAsteroids){
        drawAsteroidMove(iterAsteroids->position, iterAsteroids->mesh);
}
Notice the order in which objects are rendered inside the drawScene() method. You have always to take care that farer from the camera objects are generated first and closer to the camera objects are generated in the end otherwise the objects will not be rendered correctly. You can not really see the difference in a wireframe mode, but when we switch to a solid mode later you could experiment with this ordering and see the difference.
In the end of drawScene() method we call the eraseInvisibleObjects() method.
Inside the DlgProcControlPanel() method we define and instantiate the following variables:
static int    numberOfAsteroidsGenerated = 0,
            numberOfFireballsFired = 0;
static float lastTimeAsteroid = (float)timeGetTime(); 
float currTime  = (float)timeGetTime();
float timeDeltaAsteroid = (currTime - lastTimeAsteroid)*0.001f;
As you can see the number of asteroids and fireballs both are static, so they will preserve their values between the method calls. The rest of the variables are necessary to calculate the difference in time between the last time we have entered this method in attempt to generate an asteroid and the current time of executing this method. Notice that the lastTimeAsteroid is also a static variable for that purpose. In order to work with time we have to include the following library:
#include <ctime>
Inside the DlgProcControlPanel() method we also add the following cases:
case ID_FIREBALLS_VECTOR:
    SetDlgItemText(hwnd, ID_FIREBALLS_VECTOR, (LPCSTR)lParam);
break;

case ID_ASTEROIDS:
{
    if(timeDeltaAsteroid > 1.0f){
        generateAsteroid();
        numberOfAsteroidsGenerated += 1;
        lastTimeAsteroid = currTime;
    }
    SetDlgItemText(hwnd, ID_ASTEROIDS, 
    (LPCSTR)CStr(numberOfAsteroidsGenerated).c_str());    
}
break;

case ID_ASTEROIDS_VECTOR:
    SetDlgItemText(hwnd, ID_ASTEROIDS_VECTOR, (LPCSTR)lParam);
break;

The first case sets the text of a label showing the number of elements in the fireballs vector. The second case is responsible for the conditional generation of new asteroids. It checks if difference between the previous and the current time is greater than 1.0f and if yes it will generate a new asteroid and update the number of asteroids and the last time of asteroid's generation variables. Finally the third case sets the text of a label showing the number of elements in the asteroids vector.
Inside the WinMain() method exactly before the call to drawScene() methode we add the following condition to process asteroids:
srand((unsigned)time(0)); 
int randomNumber = (rand()%11); // random between 0 and n-1
if(randomNumber % 3 == 0){ 
    SendMessage(hwndControlPanel, WM_COMMAND, ID_ASTEROIDS, 0);
}
What we do here is first selecting a random number between 0 and 10, then checking if the result of a modulo 3 operation on that number is 0 (which means that the number is dividable by 3, leaving us with numbers 3, 6 and 9 as an acceptable range). If the randomly generated number is in the acceptable range then we send a message to the hwndControlPanel window to generate a new asteroid. This will then go through the time check described above and if successful a new asteroid will be created. Notice that we did pick up this method to control the random generation of asteroids, but you could use any other preferred by you method to do the same. After experimenting with the results you can figure out what formula works better in your situation.
Finally in the end of WinMain() just before cleanup() we call the eraseAllObjects() method.
In the next tutorial we explain the basics of Collision Detection.