//----------------------------------------------------------------------------- // File: PointSprites.cpp // // Desc: Sample showing how to use point sprites to do particle effects // // Copyright (c) 1997-2001 Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #define STRICT #include #include #include #include "D3DApp.h" #include "D3DFont.h" #include "D3DUtil.h" #include "DXUtil.h" #include "resource.h" // Helper function to stuff a FLOAT into a DWORD argument inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); } //----------------------------------------------------------------------------- // Custom vertex types //----------------------------------------------------------------------------- struct COLORVERTEX { D3DXVECTOR3 v; D3DCOLOR color; FLOAT tu; FLOAT tv; }; #define D3DFVF_COLORVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) struct POINTVERTEX { D3DXVECTOR3 v; D3DCOLOR color; }; #define D3DFVF_POINTVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE) //----------------------------------------------------------------------------- // Global structs and data for the ground object //----------------------------------------------------------------------------- #define GROUND_WIDTH 256.0f #define GROUND_HEIGHT 256.0f #define GROUND_GRIDSIZE 8 #define GROUND_TILE 32 #define GROUND_COLOR 0xcccccccc //----------------------------------------------------------------------------- // Global data for the particles //----------------------------------------------------------------------------- struct PARTICLE { BOOL m_bSpark; // Sparks are less energetic particles that // are generated where/when the main particles // hit the ground D3DXVECTOR3 m_vPos; // Current position D3DXVECTOR3 m_vVel; // Current velocity D3DXVECTOR3 m_vPos0; // Initial position D3DXVECTOR3 m_vVel0; // Initial velocity FLOAT m_fTime0; // Time of creation D3DXCOLOR m_clrDiffuse; // Initial diffuse color D3DXCOLOR m_clrFade; // Faded diffuse color FLOAT m_fFade; // Fade progression PARTICLE* m_pNext; // Next particle in list }; enum PARTICLE_COLORS { COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, NUM_COLORS }; D3DXCOLOR g_clrColor[NUM_COLORS] = { D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ), D3DXCOLOR( 1.0f, 0.5f, 0.5f, 1.0f ), D3DXCOLOR( 0.5f, 1.0f, 0.5f, 1.0f ), D3DXCOLOR( 0.125f, 0.5f, 1.0f, 1.0f ) }; DWORD g_clrColorFade[NUM_COLORS] = { D3DXCOLOR( 1.0f, 0.25f, 0.25f, 1.0f ), D3DXCOLOR( 1.0f, 0.25f, 0.25f, 1.0f ), D3DXCOLOR( 0.25f, 0.75f, 0.25f, 1.0f ), D3DXCOLOR( 0.125f, 0.25f, 0.75f, 1.0f ) }; //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- class CParticleSystem { protected: FLOAT m_fRadius; DWORD m_dwBase; DWORD m_dwFlush; DWORD m_dwDiscard; DWORD m_dwParticles; DWORD m_dwParticlesLim; PARTICLE* m_pParticles; PARTICLE* m_pParticlesFree; // Geometry LPDIRECT3DVERTEXBUFFER8 m_pVB; public: CParticleSystem( DWORD dwFlush, DWORD dwDiscard, FLOAT fRadius ); ~CParticleSystem(); HRESULT RestoreDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice ); HRESULT InvalidateDeviceObjects(); HRESULT Update( FLOAT fSecsPerFrame, DWORD dwNumParticlesToEmit, const D3DXCOLOR &dwEmitColor, const D3DXCOLOR &dwFadeColor, FLOAT fEmitVel, D3DXVECTOR3 vPosition ); HRESULT Render( LPDIRECT3DDEVICE8 pd3dDevice ); }; //----------------------------------------------------------------------------- // Name: class CMyD3DApplication // Desc: Application class. The base class (CD3DApplication) provides the // generic functionality needed in all Direct3D samples. CMyD3DApplication // adds functionality specific to this sample program. //----------------------------------------------------------------------------- class CMyD3DApplication : public CD3DApplication { CD3DFont* m_pFont; CD3DFont* m_pFontSmall; // Ground stuff LPDIRECT3DTEXTURE8 m_pGroundTexture; D3DXPLANE m_planeGround; LPDIRECT3DVERTEXBUFFER8 m_pGroundVB; LPDIRECT3DINDEXBUFFER8 m_pGroundIB; DWORD m_dwNumGroundIndices; DWORD m_dwNumGroundVertices; // Particle stuff LPDIRECT3DTEXTURE8 m_pParticleTexture; CParticleSystem* m_pParticleSystem; DWORD m_dwNumParticlesToEmit; DWORD m_dwParticleColor; BOOL m_bAnimateEmitter; BYTE m_bKey[256]; BOOL m_bDrawReflection; BOOL m_bCanDoAlphaBlend; BOOL m_bDrawHelp; // Variables for determining view position D3DXVECTOR3 m_vPosition; D3DXVECTOR3 m_vVelocity; FLOAT m_fYaw; FLOAT m_fYawVelocity; FLOAT m_fPitch; FLOAT m_fPitchVelocity; D3DXMATRIX m_matView; D3DXMATRIX m_matOrientation; HRESULT ConfirmDevice( D3DCAPS8*, DWORD, D3DFORMAT ); protected: HRESULT OneTimeSceneInit(); HRESULT InitDeviceObjects(); HRESULT RestoreDeviceObjects(); HRESULT InvalidateDeviceObjects(); HRESULT DeleteDeviceObjects(); HRESULT Render(); HRESULT FrameMove(); HRESULT FinalCleanup(); VOID UpdateCamera(); public: LRESULT MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); CMyD3DApplication(); }; //----------------------------------------------------------------------------- // Name: WinMain() // Desc: Entry point to the program. Initializes everything, and goes into a // message-processing loop. Idle time is used to render the scene. //----------------------------------------------------------------------------- INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ) { CMyD3DApplication d3dApp; if( FAILED( d3dApp.Create( hInst ) ) ) return 0; return d3dApp.Run(); } //----------------------------------------------------------------------------- // Name: CMyD3DApplication() // Desc: Application constructor. Sets attributes for the app. //----------------------------------------------------------------------------- CMyD3DApplication::CMyD3DApplication() { m_strWindowTitle = _T("PointSprites: Using particle effects"); m_bUseDepthBuffer = TRUE; m_pFont = new CD3DFont( _T("Arial"), 12, D3DFONT_BOLD ); m_pFontSmall = new CD3DFont( _T("Arial"), 9, D3DFONT_BOLD ); m_pParticleSystem = new CParticleSystem( 512, 2048, 0.03f ); m_pParticleTexture = NULL; m_dwNumParticlesToEmit = 10; m_bAnimateEmitter = FALSE; m_dwParticleColor = COLOR_WHITE; m_pGroundTexture = NULL; m_dwNumGroundVertices = (GROUND_GRIDSIZE + 1) * (GROUND_GRIDSIZE + 1); m_dwNumGroundIndices = (GROUND_GRIDSIZE * GROUND_GRIDSIZE) * 6; m_pGroundVB = NULL; m_pGroundIB = NULL; m_planeGround = D3DXPLANE( 0.0f, 1.0f, 0.0f, 0.0f ); m_bDrawReflection = FALSE; m_bCanDoAlphaBlend = FALSE; m_bDrawHelp = FALSE; ZeroMemory( m_bKey, 256 ); m_vPosition = D3DXVECTOR3( 0.0f, 3.0f,-4.0f ); m_vVelocity = D3DXVECTOR3( 0.0f, 0.0f, 0.0f ); m_fYaw = 0.03f; m_fYawVelocity = 0.0f; m_fPitch = 0.5f; m_fPitchVelocity = 0.0f; D3DXMatrixTranslation( &m_matView, 0.0f, 0.0f, 10.0f ); D3DXMatrixTranslation( &m_matOrientation, 0.0f, 0.0f, 0.0f ); } //----------------------------------------------------------------------------- // Name: OneTimeSceneInit() // Desc: Called during initial app startup, this function performs all the // permanent initialization. //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::OneTimeSceneInit() { return S_OK; } //----------------------------------------------------------------------------- // Name: FrameMove() // Desc: Called once per frame, the call is the entry point for animating // the scene. //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::FrameMove() { // Slow things down for the REF device if( m_d3dCaps.DeviceType == D3DDEVTYPE_REF ) m_fElapsedTime = 0.05f; // Determine emitter position D3DXVECTOR3 vEmitterPostion( 0.0f, 0.0f, 0.f ); if( m_bAnimateEmitter ) vEmitterPostion = D3DXVECTOR3( 3*sinf(m_fTime), 0.0f, 3*cosf(m_fTime) ); // Update particle system m_pParticleSystem->Update( m_fElapsedTime, m_dwNumParticlesToEmit, g_clrColor[m_dwParticleColor], g_clrColorFade[m_dwParticleColor], 8.0f, vEmitterPostion ); return S_OK; } //----------------------------------------------------------------------------- // Name: Render() // Desc: Called once per frame, the call is the entry point for 3d // rendering. This function sets up render states, clears the // viewport, and renders the scene. //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::Render() { // Update the camera here rather than in FrameMove() so you can // move the camera even when the scene is paused UpdateCamera(); if( FAILED( m_pd3dDevice->BeginScene() ) ) return S_OK; // Clear the viewport m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0L ); // Draw reflection of particles if( m_bDrawReflection ) { D3DXMATRIX matReflectedView; D3DXMatrixReflect( &matReflectedView, &m_planeGround ); D3DXMatrixMultiply( &matReflectedView, &matReflectedView, &m_matView ); m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matReflectedView ); m_pd3dDevice->SetTexture( 0, m_pParticleTexture ); m_pParticleSystem->Render( m_pd3dDevice ); m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); } // Draw the ground if( m_bDrawReflection ) { m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); } m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_matView ); m_pd3dDevice->SetTexture( 0, m_pGroundTexture ); m_pd3dDevice->SetVertexShader( D3DFVF_COLORVERTEX ); m_pd3dDevice->SetStreamSource( 0, m_pGroundVB, sizeof(COLORVERTEX) ); m_pd3dDevice->SetIndices( m_pGroundIB, 0L ); m_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, m_dwNumGroundVertices, 0, m_dwNumGroundIndices/3 ); // Draw particles m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); m_pd3dDevice->SetTexture(0, m_pParticleTexture ); m_pParticleSystem->Render( m_pd3dDevice ); m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); // Output statistics m_pFont->DrawText( 2, 0, D3DCOLOR_ARGB(255,255,255,0), m_strFrameStats ); m_pFont->DrawText( 2, 20, D3DCOLOR_ARGB(255,255,255,0), m_strDeviceStats ); // Draw help if( m_bDrawHelp ) { m_pFontSmall->DrawText( 2, 40, 0xffffffff, _T("Keyboard controls:") ); m_pFontSmall->DrawText( 20, 60, 0xffffffff, _T("Move\nTurn\nPitch\nSlide\n\nHelp\n" "Change device\nAnimate emitter\n" "Change color\n\nToggle reflection\n" "Exit") ); m_pFontSmall->DrawText( 160, 60, 0xffffffff, _T("W,S\nE,Q\nA,Z\nArrow keys\n\n" "F1\nF2\nF3\nF4\n\nR\nEsc") ); } else m_pFontSmall->DrawText( 2, 40, 0xffffffff, _T("Press F1 for help") ); // End the scene. m_pd3dDevice->EndScene(); return S_OK; } //----------------------------------------------------------------------------- // Name: UpdateCamera() // Desc: //----------------------------------------------------------------------------- VOID CMyD3DApplication::UpdateCamera() { FLOAT fElapsedTime; if( m_fElapsedTime > 0.0f ) fElapsedTime = m_fElapsedTime; else fElapsedTime = 0.05f; FLOAT fSpeed = 3.0f*fElapsedTime; FLOAT fAngularSpeed = 1.0f*fElapsedTime; // De-accelerate the camera movement (for smooth motion) m_vVelocity *= 0.9f; m_fYawVelocity *= 0.9f; m_fPitchVelocity *= 0.9f; // Process keyboard input if( m_bKey[VK_RIGHT] ) m_vVelocity.x += fSpeed; // Slide Right if( m_bKey[VK_LEFT] ) m_vVelocity.x -= fSpeed; // Slide Left if( m_bKey[VK_UP] ) m_vVelocity.y += fSpeed; // Slide Up if( m_bKey[VK_DOWN] ) m_vVelocity.y -= fSpeed; // Slide Down if( m_bKey['W'] ) m_vVelocity.z += fSpeed; // Move Forward if( m_bKey['S'] ) m_vVelocity.z -= fSpeed; // Move Backward if( m_bKey['E'] ) m_fYawVelocity += fSpeed; // Turn Right if( m_bKey['Q'] ) m_fYawVelocity -= fSpeed; // Turn Left if( m_bKey['Z'] ) m_fPitchVelocity += fSpeed; // Turn Down if( m_bKey['A'] ) m_fPitchVelocity -= fSpeed; // Turn Up if( m_bKey[VK_ADD] ) if( m_dwNumParticlesToEmit < 10 ) m_dwNumParticlesToEmit++; if( m_bKey[VK_SUBTRACT] ) if( m_dwNumParticlesToEmit > 0 ) m_dwNumParticlesToEmit--; // Update the position vector D3DXVECTOR3 vT = m_vVelocity * fSpeed; D3DXVec3TransformNormal( &vT, &vT, &m_matOrientation ); m_vPosition += vT; if( m_vPosition.y < 1.0f ) m_vPosition.y = 1.0f; // Update the yaw-pitch-rotation vector m_fYaw += fAngularSpeed * m_fYawVelocity; m_fPitch += fAngularSpeed * m_fPitchVelocity; if( m_fPitch < 0.0f ) m_fPitch = 0.0f; if( m_fPitch > D3DX_PI/2 ) m_fPitch = D3DX_PI/2; // Set the view matrix D3DXQUATERNION qR; D3DXQuaternionRotationYawPitchRoll( &qR, m_fYaw, m_fPitch, 0.0f ); D3DXMatrixAffineTransformation( &m_matOrientation, 1.25f, NULL, &qR, &m_vPosition ); D3DXMatrixInverse( &m_matView, NULL, &m_matOrientation ); } //----------------------------------------------------------------------------- // Name: InitDeviceObjects() // Desc: Initialize scene objects. //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::InitDeviceObjects() { HRESULT hr; // Create textures if( FAILED( D3DUtil_CreateTexture( m_pd3dDevice, _T("Ground2.bmp"), &m_pGroundTexture ) ) ) return D3DAPPERR_MEDIANOTFOUND; if( FAILED( D3DUtil_CreateTexture( m_pd3dDevice, _T("Particle.bmp"), &m_pParticleTexture ) ) ) return D3DAPPERR_MEDIANOTFOUND; // Set up the fonts and textures m_pFont->InitDeviceObjects( m_pd3dDevice ); m_pFontSmall->InitDeviceObjects( m_pd3dDevice ); // Check if we can do the reflection effect m_bCanDoAlphaBlend = (m_d3dCaps.SrcBlendCaps & D3DPBLENDCAPS_SRCALPHA) && (m_d3dCaps.DestBlendCaps & D3DPBLENDCAPS_INVSRCALPHA); if( m_bCanDoAlphaBlend ) m_bDrawReflection = TRUE; // Create ground object { // Create vertex buffer for ground object hr = m_pd3dDevice->CreateVertexBuffer( m_dwNumGroundVertices*sizeof(COLORVERTEX), D3DUSAGE_WRITEONLY, D3DFVF_COLORVERTEX, D3DPOOL_MANAGED, &m_pGroundVB ); if( FAILED(hr) ) return E_FAIL; // Fill vertex buffer COLORVERTEX* pVertices; if( FAILED( m_pGroundVB->Lock( 0, 0, (BYTE**)&pVertices, NULL ) ) ) return hr; // Fill in vertices for( DWORD zz = 0; zz <= GROUND_GRIDSIZE; zz++ ) { for( DWORD xx = 0; xx <= GROUND_GRIDSIZE; xx++ ) { pVertices->v.x = GROUND_WIDTH * (xx/(FLOAT)GROUND_GRIDSIZE-0.5f); pVertices->v.y = 0.0f; pVertices->v.z = GROUND_HEIGHT * (zz/(FLOAT)GROUND_GRIDSIZE-0.5f); pVertices->color = GROUND_COLOR; pVertices->tu = xx*GROUND_TILE/(FLOAT)GROUND_GRIDSIZE; pVertices->tv = zz*GROUND_TILE/(FLOAT)GROUND_GRIDSIZE; pVertices++; } } m_pGroundVB->Unlock(); // Create the index buffer WORD* pIndices; hr = m_pd3dDevice->CreateIndexBuffer( m_dwNumGroundIndices*sizeof(WORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &m_pGroundIB ); if( FAILED(hr) ) return E_FAIL; // Fill the index buffer m_pGroundIB->Lock( 0, m_dwNumGroundIndices*sizeof(WORD), (BYTE**)&pIndices, 0 ); if( FAILED(hr) ) return E_FAIL; // Fill in indices for( DWORD z = 0; z < GROUND_GRIDSIZE; z++ ) { for( DWORD x = 0; x < GROUND_GRIDSIZE; x++ ) { DWORD vtx = x + z * (GROUND_GRIDSIZE+1); *pIndices++ = (WORD)( vtx + 1 ); *pIndices++ = (WORD)( vtx + 0 ); *pIndices++ = (WORD)( vtx + 0 + (GROUND_GRIDSIZE+1) ); *pIndices++ = (WORD)( vtx + 1 ); *pIndices++ = (WORD)( vtx + 0 + (GROUND_GRIDSIZE+1) ); *pIndices++ = (WORD)( vtx + 1 + (GROUND_GRIDSIZE+1) ); } } m_pGroundIB->Unlock(); } return S_OK; } //----------------------------------------------------------------------------- // Name: RestoreDeviceObjects() // Desc: //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::RestoreDeviceObjects() { HRESULT hr; m_pFont->RestoreDeviceObjects(); m_pFontSmall->RestoreDeviceObjects(); // Set the world matrix D3DXMATRIX matWorld; D3DXMatrixIdentity( &matWorld ); m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); // Set projection matrix D3DXMATRIX matProj; FLOAT fAspect = ((FLOAT)m_d3dsdBackBuffer.Width) / m_d3dsdBackBuffer.Height; D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, fAspect, 0.1f, 100.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); m_pd3dDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); m_pd3dDevice->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_FLAT ); // Initialize the particle system if( FAILED( hr = m_pParticleSystem->RestoreDeviceObjects( m_pd3dDevice ) ) ) return hr; return S_OK; } //----------------------------------------------------------------------------- // Name: InvalidateDeviceObjects() // Desc: //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::InvalidateDeviceObjects() { m_pFont->InvalidateDeviceObjects(); m_pFontSmall->InvalidateDeviceObjects(); m_pParticleSystem->InvalidateDeviceObjects(); return S_OK; } //----------------------------------------------------------------------------- // Name: DeleteDeviceObjects() // Desc: Called when the app is exiting, or the device is being changed, // this function deletes any device dependent objects. //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::DeleteDeviceObjects() { m_pFont->DeleteDeviceObjects(); m_pFontSmall->DeleteDeviceObjects(); SAFE_RELEASE( m_pGroundTexture ); SAFE_RELEASE( m_pParticleTexture ); SAFE_RELEASE( m_pGroundVB ); SAFE_RELEASE( m_pGroundIB ); return S_OK; } //----------------------------------------------------------------------------- // Name: FinalCleanup() // Desc: Called before the app exits, this function gives the app the chance // to cleanup after itself. //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::FinalCleanup() { SAFE_DELETE( m_pFont ); SAFE_DELETE( m_pFontSmall ); SAFE_DELETE( m_pGroundTexture ); SAFE_DELETE( m_pParticleTexture ); SAFE_DELETE( m_pParticleSystem ); return S_OK; } //----------------------------------------------------------------------------- // Name: ConfirmDevice() // Desc: Called during device intialization, this code checks the device // for some minimum set of capabilities //----------------------------------------------------------------------------- HRESULT CMyD3DApplication::ConfirmDevice( D3DCAPS8* pCaps, DWORD dwBehavior, D3DFORMAT Format ) { // Make sure device can do ONE:ONE alphablending if( 0 == ( pCaps->SrcBlendCaps & D3DPBLENDCAPS_ONE ) ) return E_FAIL; if( 0 == ( pCaps->DestBlendCaps & D3DPBLENDCAPS_ONE ) ) return E_FAIL; // Make sure HW TnL devices can do point sprites if( (dwBehavior & D3DCREATE_HARDWARE_VERTEXPROCESSING ) || (dwBehavior & D3DCREATE_MIXED_VERTEXPROCESSING ) ) { if( pCaps->MaxPointSize <= 1.0f ) return E_FAIL; } return S_OK; } //----------------------------------------------------------------------------- // Name: MsgProc() // Desc: Message proc function to handle key and menu input //----------------------------------------------------------------------------- LRESULT CMyD3DApplication::MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { // Record key presses if( WM_KEYDOWN == uMsg ) { m_bKey[wParam] = 1; } // Perform commands when keys are rleased if( WM_KEYUP == uMsg ) { m_bKey[wParam] = 0; switch( wParam ) { case VK_F1: m_bDrawHelp = !m_bDrawHelp; return 1; case 'R': if( m_bCanDoAlphaBlend ) m_bDrawReflection = !m_bDrawReflection; break; case VK_F3: m_bAnimateEmitter = !m_bAnimateEmitter; break; case VK_F4: if( ++m_dwParticleColor >= NUM_COLORS ) m_dwParticleColor = COLOR_WHITE; break; } } return CD3DApplication::MsgProc( hWnd, uMsg, wParam, lParam ); } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- CParticleSystem::CParticleSystem( DWORD dwFlush, DWORD dwDiscard, float fRadius ) { m_fRadius = fRadius; m_dwBase = dwDiscard; m_dwFlush = dwFlush; m_dwDiscard = dwDiscard; m_dwParticles = 0; m_dwParticlesLim = 2048; m_pParticles = NULL; m_pParticlesFree = NULL; m_pVB = NULL; } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- CParticleSystem::~CParticleSystem() { InvalidateDeviceObjects(); while( m_pParticles ) { PARTICLE* pSpark = m_pParticles; m_pParticles = pSpark->m_pNext; delete pSpark; } while( m_pParticlesFree ) { PARTICLE *pSpark = m_pParticlesFree; m_pParticlesFree = pSpark->m_pNext; delete pSpark; } } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- HRESULT CParticleSystem::RestoreDeviceObjects( LPDIRECT3DDEVICE8 pd3dDevice ) { HRESULT hr; // Create a vertex buffer for the particle system. The size of this buffer // does not relate to the number of particles that exist. Rather, the // buffer is used as a communication channel with the device.. we fill in // a bit, and tell the device to draw. While the device is drawing, we // fill in the next bit using NOOVERWRITE. We continue doing this until // we run out of vertex buffer space, and are forced to DISCARD the buffer // and start over at the beginning. if(FAILED(hr = pd3dDevice->CreateVertexBuffer( m_dwDiscard * sizeof(POINTVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY | D3DUSAGE_POINTS, D3DFVF_POINTVERTEX, D3DPOOL_DEFAULT, &m_pVB ))) { return E_FAIL; } return S_OK; } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- HRESULT CParticleSystem::InvalidateDeviceObjects() { SAFE_RELEASE( m_pVB ); return S_OK; } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- HRESULT CParticleSystem::Update( FLOAT fSecsPerFrame, DWORD dwNumParticlesToEmit, const D3DXCOLOR &clrEmitColor, const D3DXCOLOR &clrFadeColor, float fEmitVel, D3DXVECTOR3 vPosition ) { PARTICLE *pParticle, **ppParticle; static float fTime = 0.0f; fTime += fSecsPerFrame; ppParticle = &m_pParticles; while( *ppParticle ) { pParticle = *ppParticle; // Calculate new position float fT = fTime - pParticle->m_fTime0; float fGravity; if( pParticle->m_bSpark ) { fGravity = -5.0f; pParticle->m_fFade -= fSecsPerFrame * 2.25f; } else { fGravity = -9.8f; pParticle->m_fFade -= fSecsPerFrame * 0.25f; } pParticle->m_vPos = pParticle->m_vVel0 * fT + pParticle->m_vPos0; pParticle->m_vPos.y += (0.5f * fGravity) * (fT * fT); pParticle->m_vVel.y = pParticle->m_vVel0.y + fGravity * fT; if( pParticle->m_fFade < 0.0f ) pParticle->m_fFade = 0.0f; // Kill old particles if( pParticle->m_vPos.y < m_fRadius || pParticle->m_bSpark && pParticle->m_fFade <= 0.0f ) { // Emit sparks if( !pParticle->m_bSpark ) { for( int i=0; i<4; i++ ) { PARTICLE *pSpark; if( m_pParticlesFree ) { pSpark = m_pParticlesFree; m_pParticlesFree = pSpark->m_pNext; } else { if( NULL == ( pSpark = new PARTICLE ) ) return E_OUTOFMEMORY; } pSpark->m_pNext = pParticle->m_pNext; pParticle->m_pNext = pSpark; pSpark->m_bSpark = TRUE; pSpark->m_vPos0 = pParticle->m_vPos; pSpark->m_vPos0.y = m_fRadius; FLOAT fRand1 = ((FLOAT)rand()/(FLOAT)RAND_MAX) * D3DX_PI * 2.00f; FLOAT fRand2 = ((FLOAT)rand()/(FLOAT)RAND_MAX) * D3DX_PI * 0.25f; pSpark->m_vVel0.x = pParticle->m_vVel.x * 0.25f + cosf(fRand1) * sinf(fRand2); pSpark->m_vVel0.z = pParticle->m_vVel.z * 0.25f + sinf(fRand1) * sinf(fRand2); pSpark->m_vVel0.y = cosf(fRand2); pSpark->m_vVel0.y *= ((FLOAT)rand()/(FLOAT)RAND_MAX) * 1.5f; pSpark->m_vPos = pSpark->m_vPos0; pSpark->m_vVel = pSpark->m_vVel0; D3DXColorLerp( &pSpark->m_clrDiffuse, &pParticle->m_clrFade, &pParticle->m_clrDiffuse, pParticle->m_fFade ); pSpark->m_clrFade = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f); pSpark->m_fFade = 1.0f; pSpark->m_fTime0 = fTime; } } // Kill particle *ppParticle = pParticle->m_pNext; pParticle->m_pNext = m_pParticlesFree; m_pParticlesFree = pParticle; if(!pParticle->m_bSpark) m_dwParticles--; } else { ppParticle = &pParticle->m_pNext; } } // Emit new particles DWORD dwParticlesEmit = m_dwParticles + dwNumParticlesToEmit; while( m_dwParticles < m_dwParticlesLim && m_dwParticles < dwParticlesEmit ) { if( m_pParticlesFree ) { pParticle = m_pParticlesFree; m_pParticlesFree = pParticle->m_pNext; } else { if( NULL == ( pParticle = new PARTICLE ) ) return E_OUTOFMEMORY; } pParticle->m_pNext = m_pParticles; m_pParticles = pParticle; m_dwParticles++; // Emit new particle FLOAT fRand1 = ((FLOAT)rand()/(FLOAT)RAND_MAX) * D3DX_PI * 2.0f; FLOAT fRand2 = ((FLOAT)rand()/(FLOAT)RAND_MAX) * D3DX_PI * 0.25f; pParticle->m_bSpark = FALSE; pParticle->m_vPos0 = vPosition + D3DXVECTOR3( 0.0f, m_fRadius, 0.0f ); pParticle->m_vVel0.x = cosf(fRand1) * sinf(fRand2) * 2.5f; pParticle->m_vVel0.z = sinf(fRand1) * sinf(fRand2) * 2.5f; pParticle->m_vVel0.y = cosf(fRand2); pParticle->m_vVel0.y *= ((FLOAT)rand()/(FLOAT)RAND_MAX) * fEmitVel; pParticle->m_vPos = pParticle->m_vPos0; pParticle->m_vVel = pParticle->m_vVel0; pParticle->m_clrDiffuse = clrEmitColor; pParticle->m_clrFade = clrFadeColor; pParticle->m_fFade = 1.0f; pParticle->m_fTime0 = fTime; } return S_OK; } //----------------------------------------------------------------------------- // Name: Render() // Desc: Renders the particle system using either pointsprites (if supported) // or using 4 vertices per particle //----------------------------------------------------------------------------- HRESULT CParticleSystem::Render( LPDIRECT3DDEVICE8 pd3dDevice ) { HRESULT hr; // Set the render states for using point sprites pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE ); pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE, TRUE ); pd3dDevice->SetRenderState( D3DRS_POINTSIZE, FtoDW(0.08f) ); pd3dDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(0.00f) ); pd3dDevice->SetRenderState( D3DRS_POINTSCALE_A, FtoDW(0.00f) ); pd3dDevice->SetRenderState( D3DRS_POINTSCALE_B, FtoDW(0.00f) ); pd3dDevice->SetRenderState( D3DRS_POINTSCALE_C, FtoDW(1.00f) ); // Set up the vertex buffer to be rendered pd3dDevice->SetStreamSource( 0, m_pVB, sizeof(POINTVERTEX) ); pd3dDevice->SetVertexShader( D3DFVF_POINTVERTEX ); PARTICLE* pParticle = m_pParticles; POINTVERTEX* pVertices; DWORD dwNumParticlesToRender = 0; // Lock the vertex buffer. We fill the vertex buffer in small // chunks, using D3DLOCK_NOOVERWRITE. When we are done filling // each chunk, we call DrawPrim, and lock the next chunk. When // we run out of space in the vertex buffer, we start over at // the beginning, using D3DLOCK_DISCARD. m_dwBase += m_dwFlush; if(m_dwBase >= m_dwDiscard) m_dwBase = 0; if(FAILED(hr = m_pVB->Lock(m_dwBase * sizeof(POINTVERTEX), m_dwFlush * sizeof(POINTVERTEX), (BYTE **) &pVertices, m_dwBase ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD))) { return hr; } // Render each particle while( pParticle ) { D3DXVECTOR3 vPos(pParticle->m_vPos); D3DXVECTOR3 vVel(pParticle->m_vVel); FLOAT fLengthSq = D3DXVec3LengthSq(&vVel); UINT dwSteps; if( fLengthSq < 1.0f ) dwSteps = 2; else if( fLengthSq < 4.00f ) dwSteps = 3; else if( fLengthSq < 9.00f ) dwSteps = 4; else if( fLengthSq < 12.25f ) dwSteps = 5; else if( fLengthSq < 16.00f ) dwSteps = 6; else if( fLengthSq < 20.25f ) dwSteps = 7; else dwSteps = 8; vVel *= -0.04f / (FLOAT)dwSteps; D3DXCOLOR clrDiffuse; D3DXColorLerp(&clrDiffuse, &pParticle->m_clrFade, &pParticle->m_clrDiffuse, pParticle->m_fFade); DWORD dwDiffuse = (DWORD) clrDiffuse; // Render each particle a bunch of times to get a blurring effect for( DWORD i = 0; i < dwSteps; i++ ) { pVertices->v = vPos; pVertices->color = dwDiffuse; pVertices++; if( ++dwNumParticlesToRender == m_dwFlush ) { // Done filling this chunk of the vertex buffer. Lets unlock and // draw this portion so we can begin filling the next chunk. m_pVB->Unlock(); if(FAILED(hr = pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, m_dwBase, dwNumParticlesToRender))) return hr; // Lock the next chunk of the vertex buffer. If we are at the // end of the vertex buffer, DISCARD the vertex buffer and start // at the beginning. Otherwise, specify NOOVERWRITE, so we can // continue filling the VB while the previous chunk is drawing. m_dwBase += m_dwFlush; if(m_dwBase >= m_dwDiscard) m_dwBase = 0; if(FAILED(hr = m_pVB->Lock(m_dwBase * sizeof(POINTVERTEX), m_dwFlush * sizeof(POINTVERTEX), (BYTE **) &pVertices, m_dwBase ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD))) { return hr; } dwNumParticlesToRender = 0; } vPos += vVel; } pParticle = pParticle->m_pNext; } // Unlock the vertex buffer m_pVB->Unlock(); // Render any remaining particles if( dwNumParticlesToRender ) { if(FAILED(hr = pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, m_dwBase, dwNumParticlesToRender ))) return hr; } // Reset render states pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE ); pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE, FALSE ); return S_OK; }