#include "Ch18p1_ParticleEmitter.h"

CParticleEmitter::CParticleEmitter()
{
  // default particle system configuration - boring!
  SetGravity(D3DXVECTOR3(0.0f,0.0f,0.0f));
  SetPos(D3DXVECTOR3(0.0f,0.0f,0.0f));
  SetMinEmitRate(30.0f);
  SetMaxEmitRate(30.0f);
  SetColor1(D3DXCOLOR(1.0f,1.0f,1.0f,1.0f));
  SetColor2(D3DXCOLOR(1.0f,1.0f,1.0f,1.0f));
  SetMinSize(1.0f);
  SetMaxSize(1.0f);

  SetSpawnDir1(D3DXVECTOR3(-1.0f, -1.0f, -1.0f));
  SetSpawnDir2(D3DXVECTOR3(1.0f, 1.0f, 1.0f));
  
  // initialize misc. other things
  m_pd3dDevice = NULL;
  SetVBSize(NUMPARTICLES / 10);

  m_fNumNewPartsExcess = 0.0f;

}

CParticleEmitter::~CParticleEmitter()
{
}

HRESULT CParticleEmitter::RestoreDeviceObjects(LPDIRECT3DDEVICE8 pDev, const char *strTextureFilename)
{
  HRESULT hr;
  m_pd3dDevice = pDev;
  m_Particles.DeleteAll();

  // load up the particle system texture
  if (FAILED(hr = D3DXCreateTextureFromFile(m_pd3dDevice, 
    strTextureFilename, &m_texParticle))) {
    return(hr);
  }
  
  // create vertex buffer
  if(FAILED(hr = m_pd3dDevice->CreateVertexBuffer(m_iVBSize * sizeof(VERTEX_PARTICLE), 
    D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY | D3DUSAGE_POINTS, 
    D3DFVF_PARTICLE, D3DPOOL_DEFAULT, &m_vbParticles))) {
    return(hr);
  }
  return S_OK;	
}

void CParticleEmitter::InvalidateDeviceObjects()
{
  SAFE_RELEASE(m_texParticle);
  SAFE_RELEASE(m_vbParticles);
}

void CParticleEmitter::Update(float fElapsedTime, float fTimeDelta)
{
  
  // update existing particles
  {
    for (int q=0; q < m_Particles.GetTotalElements(); q++) {
      if (m_Particles.IsAlive(q)) {
        CParticle &part = m_Particles.GetAt(q);

        if (!part.Update(fTimeDelta)) {
          m_Particles.Delete(&part);
        }
      } // is alive
    } // next particle
  }
  

  // create new particles
  {
    // determine the number of particles we need to create
    
    float fEmitRateThisFrame = RandomNumber(m_fMinEmitRate, m_fMaxEmitRate);
    int iNumNewParts = fEmitRateThisFrame * fTimeDelta;
    m_fNumNewPartsExcess += (float)(fEmitRateThisFrame * fTimeDelta)-iNumNewParts;

    if (m_fNumNewPartsExcess > 1.0f) {
      iNumNewParts += (int)m_fNumNewPartsExcess;
      m_fNumNewPartsExcess -= (int)m_fNumNewPartsExcess;
    }

    for (int q=0; q < iNumNewParts; q++) {
      try {
        CParticle *part = m_Particles.New();
        
        // determine a random vector between dir1 and dir2
        float fRandX = RandomNumber(m_vSpawnDir1.x, m_vSpawnDir2.x);
        float fRandY = RandomNumber(m_vSpawnDir1.y, m_vSpawnDir2.y);
        float fRandZ = RandomNumber(m_vSpawnDir1.z, m_vSpawnDir2.z);

        part->m_vDir = D3DXVECTOR3(fRandX, fRandY, fRandZ);
        part->m_vPos = m_vPos;

        float fRandR = RandomNumber(m_Color1.r, m_Color2.r);
        float fRandG = RandomNumber(m_Color1.g, m_Color2.g);
        float fRandB = RandomNumber(m_Color1.b, m_Color2.b);
        float fRandA = RandomNumber(m_Color1.a, m_Color2.a);

        part->m_Color = D3DXCOLOR(fRandR, fRandG, fRandB, fRandA);
        
      } catch(...) { q = iNumNewParts; }
    }
  }
}

HRESULT CParticleEmitter::Render()
{
  HRESULT hr;

  // Set the render states for using point sprites
  m_pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE );
  m_pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE,  TRUE );
  m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE,     FtoDW(100.0f) );
  m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(0.00f) );
  m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE_MAX, FtoDW(1000.00f) );
  m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_A,  FtoDW(0.00f) );
  m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_B,  FtoDW(0.00f) );
  m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_C,  FtoDW(1.00f) );

  // Set up the vertex buffer to be rendered
  m_pd3dDevice->SetStreamSource( 0, m_vbParticles, sizeof(VERTEX_PARTICLE) );
  m_pd3dDevice->SetVertexShader( D3DFVF_PARTICLE );

  m_pd3dDevice->SetTexture(0, m_texParticle);
  
  VERTEX_PARTICLE *pVertices;
  DWORD dwNumParticlesToRender = 0;

  if(FAILED(hr = m_vbParticles->Lock(0, m_iVBSize * sizeof(VERTEX_PARTICLE),
    (BYTE **) &pVertices, D3DLOCK_DISCARD)))
  {
	  return hr;
  }

  // Render each particle
  for (int q=0; q < NUMPARTICLES; q++) {
    
    // Render each particle a bunch of times to get a blurring effect
    if (m_Particles.IsAlive(q)) {
      CParticle &part = m_Particles.GetAt(q);
    
      pVertices->position = part.m_vPos;
      pVertices->pointsize = 1.0f;
      pVertices->color = (DWORD)part.m_Color;
      pVertices++;

      if( ++dwNumParticlesToRender == m_iVBSize ) {
        // Done filling this chunk of the vertex buffer.  Lets unlock and
        // draw this portion so we can begin filling the next chunk.

        m_vbParticles->Unlock();

        if(FAILED(hr = m_pd3dDevice->DrawPrimitive( 
          D3DPT_POINTLIST, 0, dwNumParticlesToRender)))
	        return hr;

        if(FAILED(hr = m_vbParticles->Lock(0, m_iVBSize * sizeof(VERTEX_PARTICLE),
		      (BYTE **) &pVertices, D3DLOCK_DISCARD)))
          return hr;

        dwNumParticlesToRender = 0;
      }
    }
  }

  // Unlock the vertex buffer
  m_vbParticles->Unlock();

  // Render any remaining particles
  if( dwNumParticlesToRender )
  {
    if(FAILED(hr = m_pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, dwNumParticlesToRender )))
		return hr;
  }

  // Reset render states
  m_pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE );
  m_pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE,  FALSE );

  return S_OK;


}