topical media & game development

talk show tell print

graphic-directx-game-20-Asteroids-AsteroidsDemo.cpp / cpp



  //=============================================================================
  // AsteroidsDemo.cpp by Frank Luna (C) 2005 All Rights Reserved.
  //
  // Demonstrates picking with a simple asteroids type game.
  //
  // Controls: Use mouse to look and 'W', 'S', 'A', and 'D' keys to move.
  //           Left mouse button to pick asteroids.
  //=============================================================================
  
  include <d3dApp.h>
  include <DirectInput.h>
  include <crtdbg.h>
  include <GfxStats.h>
  include <list>
  include <vector>
  include <ctime>
  include <Terrain.h>
  include <Camera.h>
  include <PSystem.h>
  
  // Create a simple firework particle system which goes off
  // whenever the user picks an asteroid.
  class FireWork : public PSystem
  {
  public:
          FireWork(const std::string& fxName, 
                  const std::string& techName, 
                  const std::string& texName, 
                  const D3DXVECTOR3& accel, 
                  const AABB& box,
                  int maxNumParticles,
                  float timePerParticle)
                  : PSystem(fxName, techName, texName, accel, box, 
                  maxNumParticles, timePerParticle)
          {
                  for(int i = 0; i < mMaxNumParticles; ++i)
                  {
                          initParticle(mParticles[i]);
                  }
          }
  
          void initParticle(Particle& out)
          {
                  out.initialTime = 0.0f;
                  out.initialSize  = GetRandomFloat(12.0f, 15.0f);
                  out.lifeTime = 10.0f;
  
                  // Generate Random Direction
                  D3DXVECTOR3 d;
                  GetRandomVec(d);
  
                  // Compute velocity.
                  float speed = GetRandomFloat(100.0f, 150.0f);
                  out.initialVelocity = speed*d;
  
                  out.initialColor = WHITE;
                  out.mass = GetRandomFloat(2.0f, 4.0f);
  
                  float r = GetRandomFloat(0.0f, 2.0f);
                  out.initialPos = r*d;
          }
  };
   
  // In order to not duplicate firework systems, we create
  // a firework "instance" structure, which stores the position of
  // the instance in world space and its relative time since being
  // created.  In this way, we can draw several fireworks with only 
  // one actual system by drawing the system in different world space
  // positions and at different times.
  struct FireWorkInstance
  {
          float time;
          D3DXMATRIX toWorld;
  };
  
  // A simple asteroid structure to maintain the rotation, position, 
  // and velocity of an asteroid.  
  struct Asteroid
  {
          D3DXVECTOR3 axis;
          float theta;
          D3DXVECTOR3 pos;
          D3DXVECTOR3 vel;
  };
  
  class AsteroidsDemo : public D3DApp
  {
  public:
          AsteroidsDemo(HINSTANCE hInstance, std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP);
          ~AsteroidsDemo();
  
          bool checkDeviceCaps();
          void onLostDevice();
          void onResetDevice();
          void updateScene(float dt);
          void drawScene();
  
          void initAsteroids();
          void buildFX();
          void getWorldPickingRay(D3DXVECTOR3& originW, D3DXVECTOR3& dirW);
  
  private:
          GfxStats* mGfxStats;
  
          // We only need one firework system, as we just draw the same system
          // several times per frame in different positions and at different
          // relative times to simulate multiple systems.
          PSystem* mFireWork;
  
          // A list of firework *instances*
          std::list<FireWorkInstance> mFireWorkInstances;
  
          // A list of asteroids.
          static const int NUM_ASTEROIDS = 300;
          std::list<Asteroid> mAsteroids;
  
          // We only need one actual mesh, as we just draw the same mesh several
          // times per frame in different positions to simulate multiple asteroids.
          ID3DXMesh* mAsteroidMesh;
          std::vector<Mtrl> mAsteroidMtrls;
          std::vector<IDirect3DTexture9*> mAsteroidTextures;
          AABB mAsteroidBox;
  
          // General light/texture FX
          ID3DXEffect* mFX;
          D3DXHANDLE   mhTech;
          D3DXHANDLE   mhWVP;
          D3DXHANDLE   mhWorldInvTrans;
          D3DXHANDLE   mhEyePos;
          D3DXHANDLE   mhWorld;
          D3DXHANDLE   mhTex;
          D3DXHANDLE   mhMtrl;
          D3DXHANDLE   mhLight;
  
          DirLight mLight;
  
          // Default texture if no texture present for subset.
          IDirect3DTexture9* mWhiteTex;
  };
  
  int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
                                     PSTR cmdLine, int showCmd)
  {
          // Enable run-time memory check for debug builds.
          #if defined(DEBUG) | defined(_DEBUG)
                  _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
          #endif
  
          srand(time(0));
  
          // Construct camera before application, since the application uses the camera.
          Camera camera;
          gCamera = &camera;
  
          AsteroidsDemo app(hInstance, "Asteroids Demo", D3DDEVTYPE_HAL, D3DCREATE_HARDWARE_VERTEXPROCESSING);
          gd3dApp = &app;
  
          DirectInput di(DISCL_NONEXCLUSIVE|DISCL_FOREGROUND, DISCL_NONEXCLUSIVE|DISCL_FOREGROUND);
          gDInput = &di;
  
      return gd3dApp->run();
  }
  
  AsteroidsDemo::AsteroidsDemo(HINSTANCE hInstance, std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP)
  : D3DApp(hInstance, winCaption, devType, requestedVP)
  {
          if(!checkDeviceCaps())
          {
                  MessageBox(0, "checkDeviceCaps() Failed", 0, 0);
                  PostQuitMessage(0);
          }
  
          InitAllVertexDeclarations();
  
          mGfxStats = new GfxStats();
          
          // Load the asteroid mesh and compute its bounding box in local space.
          LoadXFile(<asteroid.x>, &mAsteroidMesh, mAsteroidMtrls, mAsteroidTextures);
          VertexPNT* v = 0;
          HR(mAsteroidMesh->LockVertexBuffer(0, (void**)&v));
          HR(D3DXComputeBoundingBox(&v->pos, mAsteroidMesh->GetNumVertices(),
                  mAsteroidMesh->GetNumBytesPerVertex(), 
                  &mAsteroidBox.minPt, &mAsteroidBox.maxPt));
          HR(mAsteroidMesh->UnlockVertexBuffer());
  
          // Initialize camera.
          gCamera->pos() = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
          gCamera->setSpeed(40.0f);
  
          // Initialize the particle system.
          D3DXMATRIX psysWorld;
          D3DXMatrixIdentity(&psysWorld);
  
          AABB psysBox; 
          psysBox.maxPt = D3DXVECTOR3(INFINITY, INFINITY, INFINITY);
          psysBox.minPt = D3DXVECTOR3(-INFINITY, -INFINITY, -INFINITY);
          mFireWork = new FireWork("fireworks.fx", "FireWorksTech", "bolt.dds", 
                  D3DXVECTOR3(0.0f, -9.8f, 0.0f), psysBox, 500, -1.0f);
          mFireWork->setWorldMtx(psysWorld);
  
          // Call update once to put all particles in the "alive" list.
          mFireWork->update(0.0f);
  
          // Load the default texture.
          HR(D3DXCreateTextureFromFile(gd3dDevice, "whitetex.dds", &mWhiteTex));
  
          // Init a light.
          mLight.dirW    = D3DXVECTOR3(0.707f, 0.0f, 0.707f);
          D3DXVec3Normalize(&mLight.dirW, &mLight.dirW);
          mLight.ambient = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
          mLight.diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
          mLight.spec    = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
          
          buildFX();
          initAsteroids();
  
          onResetDevice();
  }
  
  AsteroidsDemo::~AsteroidsDemo()
  {
          delete mGfxStats;
          ReleaseCOM(mWhiteTex);
          ReleaseCOM(mFX);
          delete mFireWork;
  
          ReleaseCOM(mAsteroidMesh);
          for(UINT i = 0; i < mAsteroidTextures.size(); ++i)
                  ReleaseCOM(mAsteroidTextures[i]);
          
          DestroyAllVertexDeclarations();
  }
  
  bool AsteroidsDemo::checkDeviceCaps()
  {
          D3DCAPS9 caps;
          HR(gd3dDevice->GetDeviceCaps(&caps));
  
          // Check for vertex shader version 2.0 support.
          if( caps.VertexShaderVersion < D3DVS_VERSION(2, 0) )
                  return false;
  
          // Check for pixel shader version 2.0 support.
          if( caps.PixelShaderVersion < D3DPS_VERSION(2, 0) )
                  return false;
  
          return true;
  }
  
  void AsteroidsDemo::onLostDevice()
  {
          mGfxStats->onLostDevice();
          HR(mFX->OnLostDevice());
          mFireWork->onLostDevice();
  }
  
  void AsteroidsDemo::onResetDevice()
  {
          mGfxStats->onResetDevice();
          HR(mFX->OnResetDevice());
          mFireWork->onResetDevice();
  
          // The aspect ratio depends on the backbuffer dimensions, which can 
          // possibly change after a reset.  So rebuild the projection matrix.
          float w = (float)md3dPP.BackBufferWidth;
          float h = (float)md3dPP.BackBufferHeight;
          gCamera->setLens(D3DX_PI * 0.25f, w/h, 0.01f, 5000.0f);
  }
  
  void AsteroidsDemo::updateScene(float dt)
  {
          mGfxStats->update(dt);
  
          mGfxStats->setTriCount(mAsteroidMesh->GetNumFaces()*mAsteroids.size());
          mGfxStats->setVertexCount(mAsteroidMesh->GetNumVertices()*mAsteroids.size());
  
          gDInput->poll();
  
          gCamera->update(dt, 0, 0);
  
          // Update the asteroids' orientation and position.
          std::list<Asteroid>::iterator asteroidIter = mAsteroids.begin();
          while( asteroidIter != mAsteroids.end() )
          {
                  asteroidIter->theta += 4.0f*dt;
                  asteroidIter->pos += asteroidIter->vel*dt;
                  ++asteroidIter;
          }
  
          // Update and delete dead firework systems.
          std::list<FireWorkInstance>::iterator fireworkIter = mFireWorkInstances.begin();
          while(fireworkIter != mFireWorkInstances.end())
          {
                  fireworkIter->time += dt;
  
                  // Kill system after 1 seconds.
                  if(fireworkIter->time > 1.0f)
                          fireworkIter = mFireWorkInstances.erase(fireworkIter);
                  else
                          ++fireworkIter;
          }
  }
  
  void AsteroidsDemo::drawScene()
  {
          // Clear the backbuffer and depth buffer.
          HR(gd3dDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff333333, 1.0f, 0));
  
          HR(gd3dDevice->BeginScene());
  
          HR(mFX->SetValue(mhEyePos, &gCamera->pos(), sizeof(D3DXVECTOR3)));
          HR(mFX->SetTechnique(mhTech));
          UINT numPasses = 0;
          HR(mFX->Begin(&numPasses, 0));
          HR(mFX->BeginPass(0));
  
          // Did we pick anything?
          D3DXVECTOR3 originW(0.0f, 0.0f, 0.0f);
          D3DXVECTOR3 dirW(0.0f, 0.0f, 0.0f);
          if( gDInput->mouseButtonDown(0) )
          {
                  getWorldPickingRay(originW, dirW);
          }
  
          std::list<Asteroid>::iterator iter = mAsteroids.begin();
          while( iter != mAsteroids.end() )
          {
                  // Build world matrix based on current rotation and position settings.
                  D3DXMATRIX R, T;
                  D3DXMatrixRotationAxis(&R, &iter->axis, iter->theta);
                  D3DXMatrixTranslation(&T, iter->pos.x, iter->pos.y, iter->pos.z);
  
                  D3DXMATRIX toWorld = R*T;
  
                  // Transform AABB to world space.
                  AABB box;
                  mAsteroidBox.xform(toWorld, box);
  
                  // Did we pick it?
                  if(D3DXBoxBoundProbe(&box.minPt, &box.maxPt, &originW, &dirW))
                  {
                          // Create a firework instance.
                          FireWorkInstance inst;
                          inst.time = 0.0f;
                          inst.toWorld = toWorld;
                          mFireWorkInstances.push_back(inst);
          
                          // Remove asteroid from list and move onto the next node.
                          iter = mAsteroids.erase(iter);
                          continue;
                  }
  
                  // Only draw if AABB is visible.
                  if( gCamera->isVisible( box ) )
                  {
                          HR(mFX->SetMatrix(mhWVP, &(toWorld*gCamera->viewProj())));
                          D3DXMATRIX worldInvTrans;
                          D3DXMatrixInverse(&worldInvTrans, 0, &toWorld);
                          D3DXMatrixTranspose(&worldInvTrans, &worldInvTrans);
                          HR(mFX->SetMatrix(mhWorldInvTrans, &worldInvTrans));
                          HR(mFX->SetMatrix(mhWorld, &toWorld));
  
                          for(UINT j = 0; j < mAsteroidMtrls.size(); ++j)
                          {
                                  HR(mFX->SetValue(mhMtrl, &mAsteroidMtrls[j], sizeof(Mtrl)));
                          
                                  // If there is a texture, then use.
                                  if(mAsteroidTextures[j] != 0)
                                  {
                                          HR(mFX->SetTexture(mhTex, mAsteroidTextures[j]));
                                  }
  
                                  // But if not, then set a pure white texture.  When the texture color
                                  // is multiplied by the color from lighting, it is like multiplying by
                                  // 1 and won't change the color from lighting.
                                  else
                                  {
                                          HR(mFX->SetTexture(mhTex, mWhiteTex));
                                  }
                          
                                  HR(mFX->CommitChanges());
                                  HR(mAsteroidMesh->DrawSubset(j));
                          }
                  }
                  ++iter;
          }
           
          HR(mFX->EndPass());
          HR(mFX->End());
  
          // Draw fireworks.
          std::list<FireWorkInstance>::iterator psysIter = mFireWorkInstances.begin();
          while(psysIter != mFireWorkInstances.end())
          {
                  mFireWork->setTime(psysIter->time);
                  mFireWork->setWorldMtx(psysIter->toWorld);
                  mFireWork->draw();
                  ++psysIter;
          }
  
          mGfxStats->display();
  
          HR(gd3dDevice->EndScene());
  
          // Present the backbuffer.
          HR(gd3dDevice->Present(0, 0, 0, 0));
  }
  
  void AsteroidsDemo::initAsteroids()
  {
          Asteroid a;
          for(int i = 0; i < NUM_ASTEROIDS; ++i)
          {
                  // Generate a random rotation axis.
                  GetRandomVec(a.axis);
  
                  // No rotation to start, but we will rotate as 
                  // a function of time.
                  a.theta = 0.0f;
  
                  // Random position in world space.
                  a.pos.x = GetRandomFloat(-500.0f, 500.0f);
                  a.pos.y = GetRandomFloat(-500.0f, 500.0f);
                  a.pos.z = GetRandomFloat(-500.0f, 500.0f);
  
                  // Random velocity in world space.
                  float speed = GetRandomFloat(10.0f, 20.0f);
                  D3DXVECTOR3 dir;
                  GetRandomVec(dir);
                  a.vel = speed*dir;
  
                  mAsteroids.push_back(a);
          }
  }
  
  void AsteroidsDemo::buildFX()
  {
          // Create the FX from a .fx file.
          ID3DXBuffer* errors = 0;
          HR(D3DXCreateEffectFromFile(gd3dDevice, "PhongDirLtTex.fx", 
                  0, 0, D3DXSHADER_DEBUG, 0, &mFX, &errors));
          if( errors )
                  MessageBox(0, (char*)errors->GetBufferPointer(), 0, 0);
  
          // Obtain handles.
          mhTech            = mFX->GetTechniqueByName("PhongDirLtTexTech");
          mhWVP             = mFX->GetParameterByName(0, "gWVP");
          mhWorldInvTrans   = mFX->GetParameterByName(0, "gWorldInvTrans");
          mhMtrl            = mFX->GetParameterByName(0, "gMtrl");
          mhLight           = mFX->GetParameterByName(0, "gLight");
          mhEyePos          = mFX->GetParameterByName(0, "gEyePosW");
          mhWorld           = mFX->GetParameterByName(0, "gWorld");
          mhTex             = mFX->GetParameterByName(0, "gTex");
  
          HR(mFX->SetValue(mhLight, &mLight, sizeof(DirLight)));
  }
  
  void AsteroidsDemo::getWorldPickingRay(D3DXVECTOR3& originW, D3DXVECTOR3& dirW)
  {
          // Get the screen point clicked.
          POINT s;
          GetCursorPos(&s);
  
          // Make it relative to the client area window.
          ScreenToClient(mhMainWnd, &s);
  
          // By the way we've been constructing things, the entire 
          // backbuffer is the viewport.
  
          float w = (float)md3dPP.BackBufferWidth;
          float h = (float)md3dPP.BackBufferHeight;
  
          D3DXMATRIX proj = gCamera->proj();
  
          float x = (2.0f*s.x/w - 1.0f) / proj(0,0);
          float y = (-2.0f*s.y/h + 1.0f) / proj(1,1);
  
          // Build picking ray in view space.
          D3DXVECTOR3 origin(0.0f, 0.0f, 0.0f);
          D3DXVECTOR3 dir(x, y, 1.0f);
  
          // So if the view matrix transforms coordinates from 
          // world space to view space, then the inverse of the
          // view matrix transforms coordinates from view space
          // to world space.
          D3DXMATRIX invView;
          D3DXMatrixInverse(&invView, 0, &gCamera->view());
  
          // Transform picking ray to world space.
          D3DXVec3TransformCoord(&originW, &origin, &invView);
          D3DXVec3TransformNormal(&dirW, &dir, &invView);
          D3DXVec3Normalize(&dirW, &dirW);
  }


(C) Æliens 20/2/2008

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.