#include "stdafx.h" #include "UIAnim.h" #if (_MSC_VER < 1300) #pragma comment(lib, "Delayimp.lib") #pragma comment(linker, "/DelayLoad:d3d9.dll") #endif // _MSC_VER #pragma comment(lib, "d3d9.lib") ///////////////////////////////////////////////////////////////////////////////////// // // #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1) #ifndef PI #define PI 3.1415926535897932384626433832795029L #endif ///////////////////////////////////////////////////////////////////////////////////// // CAnimJobUI::CAnimJobUI(const CAnimJobUI& src) { *this = src; } CAnimJobUI::CAnimJobUI(UITYPE_ANIM AnimType, DWORD dwStartTick, DWORD dwDuration, COLORREF clrBack, COLORREF clrKey, RECT rcFrom, int xtrans, int ytrans, int ztrans, int alpha, FLOAT zrot) { this->AnimType = AnimType; this->dwStartTick = dwStartTick; this->dwDuration = dwDuration; data.plot.clrBack = clrBack; data.plot.clrKey = clrKey; data.plot.rcFrom = rcFrom; data.plot.mFrom.xtrans = xtrans; data.plot.mFrom.ytrans = ytrans; data.plot.mFrom.ztrans = ztrans; data.plot.mFrom.alpha = alpha; data.plot.mFrom.zrot = zrot; data.plot.iInterpolate = INTERPOLATE_COS; } ///////////////////////////////////////////////////////////////////////////////////// // // CAnimationSpooler::CAnimationSpooler() : m_hWnd(NULL), m_bIsAnimating(false), m_bIsInitialized(false), m_pD3D(NULL), m_p3DDevice(NULL), m_p3DBackSurface(NULL), m_nBuffers(0) { ::ZeroMemory(m_p3DVertices, sizeof(m_p3DVertices)); ::ZeroMemory(m_p3DTextures, sizeof(m_p3DTextures)); } CAnimationSpooler::~CAnimationSpooler() { Term(); } bool CAnimationSpooler::Init(HWND hWnd) { ASSERT(::IsWindow(hWnd)); if( m_bIsInitialized ) return true; // This is our master window m_hWnd = hWnd; // Gather window information RECT rcWindow = { 0 }; ::GetWindowRect(hWnd, &rcWindow); if( ::IsRectEmpty(&rcWindow) ) return false; // Is window topmost? HWND hWndFocus = hWnd; while( ::GetParent(hWndFocus) != NULL ) hWndFocus = ::GetParent(hWndFocus); // Is DirectX v9 available at all? HMODULE hMod = ::LoadLibrary("D3D9.DLL"); if( hMod == NULL ) return false; ::FreeLibrary(hMod); // Initialize Direct3D // Fortunately we can delay-load the DirectX9 library so we // don't actually have a link dependency on it. if( m_pD3D != NULL ) m_pD3D->Release(); m_pD3D = ::Direct3DCreate9(D3D_SDK_VERSION); if( m_pD3D == NULL ) return false; HRESULT Hr; D3DDISPLAYMODE d3ddm = { 0 }; Hr = m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm); if( FAILED(Hr) ) return false; m_ColorFormat = d3ddm.Format; Hr = m_pD3D->CheckDeviceType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_ColorFormat, m_ColorFormat, TRUE); if( FAILED(Hr) ) return false; D3DPRESENT_PARAMETERS d3dpp = { 0 }; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;; //D3DSWAPEFFECT_FLIP d3dpp.Windowed = TRUE; d3dpp.hDeviceWindow = hWnd; d3dpp.BackBufferCount = 1; d3dpp.BackBufferFormat = m_ColorFormat; d3dpp.EnableAutoDepthStencil = FALSE; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; Hr = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWndFocus, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &m_p3DDevice); if( FAILED(Hr) ) return false; // Check device caps D3DCAPS9 caps; Hr = m_p3DDevice->GetDeviceCaps(&caps); if( caps.MaxTextureWidth < 128 ) return false; if( (caps.Caps3 & D3DCAPS3_COPY_TO_VIDMEM) == 0 ) return false; if( FAILED(Hr) ) return false; // Set viewport D3DVIEWPORT9 vp; vp.X = vp.Y = 0; vp.Width = rcWindow.right - rcWindow.left; vp.Height = rcWindow.bottom - rcWindow.top; vp.MinZ = 0.0; vp.MaxZ = 1.0; Hr = m_p3DDevice->SetViewport(&vp); if( FAILED(Hr) ) return false; // Set the render flags. m_p3DDevice->SetRenderState(D3DRS_COLORVERTEX, TRUE); m_p3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); m_p3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE); m_p3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE); // Set miscellaneous render states m_p3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); m_p3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); m_p3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); m_p3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); m_p3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); m_p3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); m_p3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); m_p3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); // Use alpha from texture m_p3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); // Use vertex alpha m_p3DDevice->SetVertexShader(NULL); // Signal go... m_bIsInitialized = true; return true; } void CAnimationSpooler::Term() { // Get rid of the animation jobs int i; for( i = 0; i < m_aJobs.GetSize(); i++ ) delete static_cast(m_aJobs[i]); m_aJobs.Empty(); // Release Direct3D references for( i = 0; i < m_nBuffers; i++ ) { m_p3DVertices[i]->Release(); m_p3DTextures[i]->Release(); } m_nBuffers = 0; if( m_p3DBackSurface != NULL ) m_p3DBackSurface->Release(); m_p3DBackSurface = NULL; if( m_p3DDevice != NULL ) m_p3DDevice->Release(); m_p3DDevice = NULL; if( m_pD3D != NULL ) m_pD3D->Release(); m_pD3D = NULL; // Almost done... m_bIsAnimating = false; m_bIsInitialized = false; } bool CAnimationSpooler::AddJob(CAnimJobUI* pJob) { return m_aJobs.Add(pJob); } bool CAnimationSpooler::IsAnimating() const { return m_bIsAnimating; } bool CAnimationSpooler::IsJobScheduled() const { return m_aJobs.GetSize() > 0 && !m_bIsAnimating; } bool CAnimationSpooler::PrepareAnimation(HWND hWnd) { if( !m_bIsInitialized ) return false; // Release old image of window if( m_p3DBackSurface != NULL ) m_p3DBackSurface->Release(); m_p3DBackSurface= NULL; // Create the backdrop surface RECT rcClient; ::GetClientRect(m_hWnd, &rcClient); int cx = rcClient.right - rcClient.left; int cy = rcClient.bottom - rcClient.top; HRESULT Hr = m_p3DDevice->CreateOffscreenPlainSurface(cx, cy, m_ColorFormat, D3DPOOL_SYSTEMMEM, &m_p3DBackSurface, NULL); if( FAILED(Hr) ) return false; // Paint the background HDC hDC = NULL; Hr = m_p3DBackSurface->GetDC(&hDC); if( FAILED(Hr) ) return false; ::SendMessage(hWnd, WM_PRINTCLIENT, (WPARAM) hDC, PRF_CHECKVISIBLE | PRF_CLIENT | PRF_ERASEBKGND | PRF_CHILDREN); m_p3DBackSurface->ReleaseDC(hDC); // Allow each job to prepare its 3D objects for( int i = 0; i < m_aJobs.GetSize(); i++ ) { CAnimJobUI* pJob = static_cast(m_aJobs[i]); switch( pJob->AnimType ) { case UIANIMTYPE_FLAT: if( !PrepareJob_Flat(pJob) ) return false; break; } } // Assign start time DWORD dwTick = ::timeGetTime(); for( int j = 0; j < m_aJobs.GetSize(); j++ ) { CAnimJobUI* pJob = static_cast(m_aJobs[j]); pJob->dwStartTick += dwTick; } m_bIsAnimating = true; return true; } bool CAnimationSpooler::Render() { if( !m_bIsAnimating ) return false; if( !m_bIsInitialized ) return false; // Get render target HRESULT Hr; LPDIRECT3DSURFACE9 p3DTargetSurface; Hr = m_p3DDevice->GetRenderTarget(0, &p3DTargetSurface); if( FAILED(Hr) ) return false; CSafeRelease RefTargetSurface = p3DTargetSurface; // Copy backdrop screen Hr = m_p3DDevice->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0L); Hr = m_p3DDevice->UpdateSurface(m_p3DBackSurface, NULL, p3DTargetSurface, NULL); // Here begins the rendering loop. Hr = m_p3DDevice->BeginScene(); if( FAILED(Hr) ) return false; int nAnimated = 0; DWORD dwTick = ::timeGetTime(); for( int i = 0; i < m_aJobs.GetSize(); i++ ) { const CAnimJobUI* pJob = static_cast(m_aJobs[i]); if( dwTick < pJob->dwStartTick ) continue; DWORD dwTickNow = MIN(dwTick, pJob->dwStartTick + pJob->dwDuration); switch( pJob->AnimType ) { case UIANIMTYPE_FLAT: RenderJob_Flat(pJob, p3DTargetSurface, dwTickNow); break; } if( dwTick < pJob->dwStartTick + pJob->dwDuration ) nAnimated++; } Hr = m_p3DDevice->EndScene(); Hr = m_p3DDevice->Present(NULL, NULL, NULL, NULL); // No more frames to animate? if( nAnimated == 0 ) Term(); return true; } void CAnimationSpooler::CancelJobs() { Term(); } ///////////////////////////////////////////////////////////////////////////////////// // // static double LinearInterpolate(double y1, double y2, double mu) { return y1 * (1.0 - mu) + y2 * mu; } static double CosineInterpolate(double y1, double y2, double mu) { double mu2 = (1.0 - cos(mu * PI)) / 2.0; return y1 * (1.0 - mu2) + y2 * mu2; } ///////////////////////////////////////////////////////////////////////////////////// // // COLORREF CAnimationSpooler::TranslateColor(LPDIRECT3DSURFACE9 pSurface, COLORREF clrColor) const { ASSERT(pSurface); if( clrColor == CLR_INVALID ) return clrColor; // The only way to actually determine what color a certain RGB value gets, is // to put a pixel on the surface and taste it. HDC hDC = NULL; HRESULT Hr = pSurface->GetDC(&hDC); if( FAILED(Hr) ) return false; COLORREF clrOld = ::GetPixel(hDC, 0, 0); ::SetPixel(hDC, 0, 0, clrColor); clrColor = ::GetPixel(hDC, 0,0); ::SetPixel(hDC, 0, 0, clrOld); pSurface->ReleaseDC(hDC); return clrColor; } bool CAnimationSpooler::SetColorKey(LPDIRECT3DTEXTURE9 pTexture, LPDIRECT3DSURFACE9 pSurface, int iTexSize, COLORREF clrColorKey) { ASSERT(pTexture); ASSERT(pSurface); if( clrColorKey == CLR_INVALID ) return true; // Get colorkey's red, green, and blue components // and put the colorkey in the texture's native format DWORD r = GetRValue(clrColorKey); DWORD g = GetGValue(clrColorKey); DWORD b = GetBValue(clrColorKey); DWORD dwColorKey = D3DCOLOR_ARGB(255,r,g,b); HRESULT Hr; LPDIRECT3DTEXTURE9 pTex = NULL; Hr = m_p3DDevice->CreateTexture(iTexSize, iTexSize, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &pTex, NULL); if( FAILED(Hr) ) return false; CSafeRelease RefTex = pTex; LPDIRECT3DSURFACE9 pTexSurf = NULL; Hr = pTex->GetSurfaceLevel(0, &pTexSurf); if( FAILED(Hr) ) return false; CSafeRelease RefTexSurf = pTexSurf; Hr = m_p3DDevice->GetRenderTargetData(pSurface, pTexSurf); if( FAILED(Hr) ) return false; // Lock the texture and scan through each pixel, replacing the colorkey pixels D3DLOCKED_RECT d3dlr; Hr = pTex->LockRect(0, &d3dlr, 0, 0); if( FAILED(Hr) ) return false; DWORD* pBits = static_cast(d3dlr.pBits); for( int y = 0; y < iTexSize; y++ ) { for( int x = 0; x < iTexSize; x++ ) { if( pBits[x] == dwColorKey ) pBits[x] = 0x00000000; else pBits[x] |= 0xff000000; } pBits += d3dlr.Pitch / sizeof(DWORD); } pTex->UnlockRect(0); // Copy modified data back POINT pt = { 0, 0 }; RECT rcDest = { 0, 0, iTexSize, iTexSize }; Hr = m_p3DDevice->UpdateSurface(pTexSurf, &rcDest, pSurface, &pt); return true; } bool CAnimationSpooler::PrepareJob_Flat(CAnimJobUI* pJob) { // Determine actual colorkey pJob->data.plot.clrKey = TranslateColor(m_p3DBackSurface, pJob->data.plot.clrKey); // Prepare surfaces HRESULT Hr; RECT rc = pJob->data.plot.rcFrom; int cx = rc.right - rc.left; int cy = rc.bottom - rc.left; FLOAT z = 0.1f; FLOAT rhw = 1.0f / (z * 990.0f + 10.0f); D3DCOLOR col = 0xffffffff; // Determine texture size int iTexSize = 128; if( cx < 64 ) iTexSize = 64; if( cx < 32 ) iTexSize = 32; FLOAT fTexSize = (FLOAT) iTexSize; // Start building tiles pJob->iBufferStart = m_nBuffers; for( int x = rc.left; x < rc.right; x += iTexSize ) { for( int y = rc.top; y < rc.bottom; y += iTexSize ) { RECT rcTile = { x, y, MIN(rc.right, x + iTexSize), MIN(rc.bottom, y + iTexSize) }; // Adjust texture coordinates, because last tile may only use parts // of the texture... FLOAT tcoordx = (iTexSize - (x + fTexSize - rc.right)) / fTexSize; FLOAT tcoordy = (iTexSize - (y + fTexSize - rc.bottom)) / fTexSize; if( tcoordx > 1.0f ) tcoordx = 1.0f; if( tcoordy > 1.0f ) tcoordy = 1.0f; // Create the vertex buffer CUSTOMFAN verts = { { rcTile.left - 0.5f, rcTile.top - 0.5f, z, rhw, col, 0.0f, 0.0f }, { rcTile.right - 0.5f, rcTile.top - 0.5f, z, rhw, col, tcoordx, 0.0f }, { rcTile.right - 0.5f, rcTile.bottom - 0.5f, z, rhw, col, tcoordx, tcoordy }, { rcTile.left - 0.5f, rcTile.bottom - 0.5f, z, rhw, col, 0.0f, tcoordy } }; LPDIRECT3DVERTEXBUFFER9 pVertex = NULL; Hr = m_p3DDevice->CreateVertexBuffer(4 * sizeof(CUSTOMVERTEX), D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &pVertex, NULL); if( FAILED(Hr) ) return false; CSafeRelease RefVertex = pVertex; memcpy(m_fans[m_nBuffers], verts, sizeof(verts)); LPDIRECT3DTEXTURE9 pTex1 = NULL; Hr = m_p3DDevice->CreateTexture(iTexSize, iTexSize, 1, 0, m_ColorFormat, D3DPOOL_DEFAULT, &pTex1, NULL); if( FAILED(Hr) ) return false; CSafeRelease RefTex1 = pTex1; LPDIRECT3DSURFACE9 pTexSurf1 = NULL; Hr = pTex1->GetSurfaceLevel(0, &pTexSurf1); if( FAILED(Hr) ) return false; CSafeRelease RefTexSurf1 = pTexSurf1; POINT pt = { 0, 0 }; Hr = m_p3DDevice->UpdateSurface(m_p3DBackSurface, &rcTile, pTexSurf1, &pt); if( FAILED(Hr) ) return false; LPDIRECT3DTEXTURE9 pTex2 = NULL; RECT rcDest = { 0, 0, rcTile.right - rcTile.left, rcTile.bottom - rcTile.top }; Hr = m_p3DDevice->CreateTexture(iTexSize, iTexSize, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &pTex2, NULL); if( FAILED(Hr) ) return false; CSafeRelease RefTex2 = pTex2; LPDIRECT3DSURFACE9 pTexSurf2 = NULL; Hr = pTex2->GetSurfaceLevel(0, &pTexSurf2); if( FAILED(Hr) ) return false; CSafeRelease RefTexSurf2 = pTexSurf2; Hr = m_p3DDevice->StretchRect(pTexSurf1, &rcDest, pTexSurf2, &rcDest, D3DTEXF_NONE); if( FAILED(Hr) ) return false; // Replace colorkey pixels with alpha SetColorKey(pTex2, pTexSurf2, iTexSize, pJob->data.plot.clrKey); // Finally, assign the texture m_p3DTextures[m_nBuffers] = RefTex2.Detach(); m_p3DVertices[m_nBuffers] = RefVertex.Detach(); m_nBuffers++; } } pJob->iBufferEnd = m_nBuffers; ASSERT(m_nBuffersdata.plot.clrBack; if( clrBack != CLR_INVALID) { HDC hDC = NULL; Hr = m_p3DBackSurface->GetDC(&hDC); if( FAILED(Hr) ) return false; HBRUSH hBrush = ::CreateSolidBrush(clrBack); ::FillRect(hDC, &rc, hBrush); ::DeleteObject(hBrush); m_p3DBackSurface->ReleaseDC(hDC); } return true; } bool CAnimationSpooler::RenderJob_Flat(const CAnimJobUI* pJob, LPDIRECT3DSURFACE9 /*pSurface*/, DWORD dwTick) { RECT rc = pJob->data.plot.rcFrom; FLOAT mu = (FLOAT)(pJob->dwStartTick + pJob->dwDuration - dwTick) / (FLOAT) pJob->dwDuration; FLOAT scale1 = 0.0; if( pJob->data.plot.iInterpolate == CAnimJobUI::INTERPOLATE_LINEAR ) scale1 = (FLOAT) LinearInterpolate(0.0, 1.0, mu); if( pJob->data.plot.iInterpolate == CAnimJobUI::INTERPOLATE_COS ) scale1 = (FLOAT) CosineInterpolate(0.0, 1.0, mu); FLOAT scale2 = 1.0f - scale1; D3DVECTOR ptCenter = { rc.left + ((rc.right - rc.left) / 2.0f), rc.top + ((rc.bottom - rc.top) / 2.0f) }; FLOAT xtrans = (FLOAT) pJob->data.plot.mFrom.xtrans * scale1; FLOAT ytrans = (FLOAT) pJob->data.plot.mFrom.ytrans * scale1; FLOAT ztrans = 1.0f + ((FLOAT) abs(pJob->data.plot.mFrom.ztrans) * (pJob->data.plot.mFrom.ztrans >= 0.0 ? scale1 : scale2)); FLOAT fSin = (FLOAT) sin(pJob->data.plot.mFrom.zrot * scale1); FLOAT fCos = (FLOAT) cos(pJob->data.plot.mFrom.zrot * scale1); DWORD clrAlpha = ((DWORD)(0xFF - (FLOAT) abs(pJob->data.plot.mFrom.alpha) * (pJob->data.plot.mFrom.alpha >= 0 ? scale1 : scale2)) << 24) | 0xffffff; HRESULT Hr = 0; for( int iBuffer = pJob->iBufferStart; iBuffer < pJob->iBufferEnd; iBuffer++ ) { // Lock the vertex buffer and apply transformation LPDIRECT3DVERTEXBUFFER9 pVBuffer = m_p3DVertices[iBuffer]; LPVOID pVertices = NULL; Hr = pVBuffer->Lock(0, sizeof(CUSTOMFAN), &pVertices, 0); if( FAILED(Hr) ) return false; CUSTOMFAN verts; memcpy(verts, m_fans[iBuffer], sizeof(CUSTOMFAN)); for( int i = 0; i < sizeof(CUSTOMFAN) / sizeof(CUSTOMVERTEX); i++ ) { verts[i].x -= ptCenter.x; verts[i].y -= ptCenter.y; verts[i].x += xtrans; // Translate verts[i].y += ytrans; verts[i].x = verts[i].x * ztrans; // Scale verts[i].y = verts[i].y * ztrans; FLOAT x = verts[i].x; FLOAT y = verts[i].y; // Rotate around Z verts[i].x = x * fCos - y * fSin; verts[i].y = x * fSin + y * fCos; verts[i].x += ptCenter.x; verts[i].y += ptCenter.y; verts[i].color = clrAlpha; } memcpy(pVertices, verts, sizeof(CUSTOMFAN)); pVBuffer->Unlock(); // Paint it Hr = m_p3DDevice->SetStreamSource(0, pVBuffer, 0, sizeof(CUSTOMVERTEX)); Hr = m_p3DDevice->SetFVF(D3DFVF_CUSTOMVERTEX); Hr = m_p3DDevice->SetTexture(0, m_p3DTextures[iBuffer]); Hr = m_p3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2); } return true; }