dx9.0地形

dx9.0地形

  1. 照相机
    这里照相机有
    3种移动方式:
    1.x轴:left/right
    2.y轴:up/down
    3.z轴:forward/backward
    3种旋转方式:
    1.x轴:rotate on right vector
    2.y轴:rotate on up vector
    3.z轴:rotate on look vector
    计算视图矩阵
    1.平移:将摄像机的位置移动到原点
    在这里插入图片描述
    2.旋转:将摄像机的方向向量(x、y、z)与世界坐标系的轴对齐
    在这里插入图片描述
    合并视图矩阵=平移矩阵*旋转矩阵

    在这里插入图片描述

  2. 地形

    1.从raw文件加载高度图readRawFile
    2.生成地形几何数据:
    顶点缓存

    v[index] = TerrainVertex(
    			(float)x,
    			(float)_heightmap[index],
    			(float)z,
    			(float)j * uCoordIncrementSize,
    			(float)i * vCoordIncrementSize);
    

    索引缓存

    indices[baseIndex] = i * _numVertsPerRow + j;
    indices[baseIndex + 1] = i * _numVertsPerRow + j + 1;
    indices[baseIndex + 2] = (i + 1) * _numVertsPerRow + j;
    
    indices[baseIndex + 3] = (i + 1) * _numVertsPerRow + j;
    indices[baseIndex + 4] = i * _numVertsPerRow + j + 1;
    indices[baseIndex + 5] = (i + 1) * _numVertsPerRow + j + 1;
    

    3.根据高度生成纹理

    DWORD* imageData = (DWORD*)lockedRect.pBits;
    	for (int i = 0; i < texHeight; i++)
    	{
    		for (int j = 0; j < texWidth; j++)
    		{
    			D3DXCOLOR c;
    
    			// get height of upper left vertex of quad.
    			float height = (float)getHeightmapEntry(i, j) / _heightScale;
    
    			if ((height) < 42.5f) 		 c = d3d::BEACH_SAND;
    			else if ((height) < 85.0f)	 c = d3d::LIGHT_YELLOW_GREEN;
    			else if ((height) < 127.5f) c = d3d::PUREGREEN;
    			else if ((height) < 170.0f) c = d3d::DARK_YELLOW_GREEN;
    			else if ((height) < 212.5f) c = d3d::DARKBROWN;
    			else	                     c = d3d::WHITE;
    
    			// fill locked data, note we divide the pitch by four because the
    			// pitch is given in bytes and there are 4 bytes per DWORD.
    			imageData[i * lockedRect.Pitch / 4 + j] = (D3DCOLOR)c;
    		}
    	}
    

    4.根据光线和平面法线的照射角度生成明暗属性

    // build two vectors on the quad
    	D3DXVECTOR3 u(_cellSpacing, heightB - heightA, 0.0f);
    	D3DXVECTOR3 v(0.0f, heightC - heightA, -_cellSpacing);
    
    	// find the normal by taking the cross product of two
    	// vectors on the quad.
    	D3DXVECTOR3 n;
    	D3DXVec3Cross(&n, &u, &v);
    	D3DXVec3Normalize(&n, &n);
    
    	float cosine = D3DXVec3Dot(&n, directionToLight);
    
    	if (cosine < 0.0f)
    		cosine = 0.0f;
    

    5.根据位置判断上下三角形获取位置高度

    //  A   B
    //  *---*
    //  | / |
    //  *---*  
    //  C   D
    

    float A = getHeightmapEntry(row, col);
    float B = getHeightmapEntry(row, col + 1);
    float C = getHeightmapEntry(row + 1, col);
    float D = getHeightmapEntry(row + 1, col + 1);

    //
    // Find the triangle we are in:
    //
    
    // Translate by the transformation that takes the upper-left
    // corner of the cell we are in to the origin.  Recall that our 
    // cellspacing was nomalized to 1.  Thus we have a unit square
    // at the origin of our +x -> 'right' and +z -> 'down' system.
    float dx = x - col;
    float dz = z - row;
    
    // Note the below compuations of u and v are unneccessary, we really
    // only need the height, but we compute the entire vector to emphasis
    // the books discussion.
    float height = 0.0f;
    if (dz < 1.0f - dx)  // upper triangle ABC
    {
    	float uy = B - A; // A->B
    	float vy = C - A; // A->C
    
    	// Linearly interpolate on each vector.  The height is the vertex
    	// height the vectors u and v originate from {A}, plus the heights
    	// found by interpolating on each vector u and v.
    	height = A + d3d::Lerp(0.0f, uy, dx) + d3d::Lerp(0.0f, vy, dz);
    }
    else // lower triangle DCB
    {
    	float uy = C - D; // D->C
    	float vy = B - D; // D->B
    
    	// Linearly interpolate on each vector.  The height is the vertex
    	// height the vectors u and v originate from {D}, plus the heights
    	// found by interpolating on each vector u and v.
    	height = D + d3d::Lerp(0.0f, uy, 1.0f - dx) + d3d::Lerp(0.0f, vy, 1.0f - dz);
    }
    
  3. 原书代码修改部分

这里sprintf_s(_fpsString, 8, “%f”, _fps)的_fps的长度会超过8位,改为stringstream就不用担心这个问题

bool FPSCounter::render(D3DCOLOR color, float timeDelta)
{
	if (_font)
	{
		_frameCnt++;

		_timeElapsed += timeDelta;

		if (_timeElapsed >= 1.0f)
		{
			_fps = (float)_frameCnt / _timeElapsed;

			std::stringstream ss;
			//std::string fpsStr;
			ss << _fps;
			ss >> _fpsString;
			//strncpy_s(_fpsString, 9, fpsStr.c_str(), 9);
			//sprintf_s(_fpsString, 8, "%f", _fps);
			_fpsString[8] = '\0'; // mark end of string

			_timeElapsed = 0.0f;
			_frameCnt = 0;
		}

		_font->DrawText(20, 20, color, _fpsString);
	}
	return true;
}
  1. 运行效果
    在这里插入图片描述
    在这里插入图片描述
    5.原书代码
    d3dfont.h
#pragma once
#include <tchar.h>
#include <D3D9.h>


// Font creation flags
#define D3DFONT_BOLD        0x0001
#define D3DFONT_ITALIC      0x0002
#define D3DFONT_ZENABLE     0x0004

// Font rendering flags
#define D3DFONT_CENTERED_X  0x0001
#define D3DFONT_CENTERED_Y  0x0002
#define D3DFONT_TWOSIDED    0x0004
#define D3DFONT_FILTERED    0x0008




//-----------------------------------------------------------------------------
// Name: class CD3DFont
// Desc: Texture-based font class for doing text in a 3D scene.
//-----------------------------------------------------------------------------
class CD3DFont
{
    TCHAR   m_strFontName[80];            // Font properties
    DWORD   m_dwFontHeight;
    DWORD   m_dwFontFlags;

    LPDIRECT3DDEVICE9       m_pd3dDevice; // A D3DDevice used for rendering
    LPDIRECT3DTEXTURE9      m_pTexture;   // The d3d texture for this font
    LPDIRECT3DVERTEXBUFFER9 m_pVB;        // VertexBuffer for rendering text
    DWORD   m_dwTexWidth;                 // Texture dimensions
    DWORD   m_dwTexHeight;
    FLOAT   m_fTextScale;
    FLOAT   m_fTexCoords[128 - 32][4];
    DWORD   m_dwSpacing;                  // Character pixel spacing per side

    // Stateblocks for setting and restoring render states
    LPDIRECT3DSTATEBLOCK9 m_pStateBlockSaved;
    LPDIRECT3DSTATEBLOCK9 m_pStateBlockDrawText;

public:
    // 2D and 3D text drawing functions
    HRESULT DrawText(FLOAT x, FLOAT y, DWORD dwColor,
        const TCHAR* strText, DWORD dwFlags = 0L);
    HRESULT DrawTextScaled(FLOAT x, FLOAT y, FLOAT z,
        FLOAT fXScale, FLOAT fYScale, DWORD dwColor,
        const TCHAR* strText, DWORD dwFlags = 0L);
    HRESULT Render3DText(const TCHAR* strText, DWORD dwFlags = 0L);

    // Function to get extent of text
    HRESULT GetTextExtent(const TCHAR* strText, SIZE* pSize);

    // Initializing and destroying device-dependent objects
    HRESULT InitDeviceObjects(LPDIRECT3DDEVICE9 pd3dDevice);
    HRESULT RestoreDeviceObjects();
    HRESULT InvalidateDeviceObjects();
    HRESULT DeleteDeviceObjects();

    // Constructor / destructor
    CD3DFont(const TCHAR* strFontName, DWORD dwHeight, DWORD dwFlags = 0L);
    ~CD3DFont();
};

d3dfont.cpp

//-----------------------------------------------------------------------------
// File: D3DFont.cpp
//
// Desc: Texture-based font class
//
// Copyright (c) 1999-2001 Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#include <stdio.h>
#include <tchar.h>
#include <D3DX9.h>
#include "D3DFont.h"
#include "D3DUtil.h"
#include "DXUtil.h"




//-----------------------------------------------------------------------------
// Custom vertex types for rendering text
//-----------------------------------------------------------------------------
#define MAX_NUM_VERTICES 50*6

struct FONT2DVERTEX { D3DXVECTOR4 p;   DWORD color;     FLOAT tu, tv; };
struct FONT3DVERTEX { D3DXVECTOR3 p;   D3DXVECTOR3 n;   FLOAT tu, tv; };

#define D3DFVF_FONT2DVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)
#define D3DFVF_FONT3DVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)

inline FONT2DVERTEX InitFont2DVertex(const D3DXVECTOR4& p, D3DCOLOR color,
    FLOAT tu, FLOAT tv)
{
    FONT2DVERTEX v;   v.p = p;   v.color = color;   v.tu = tu;   v.tv = tv;
    return v;
}

inline FONT3DVERTEX InitFont3DVertex(const D3DXVECTOR3& p, const D3DXVECTOR3& n,
    FLOAT tu, FLOAT tv)
{
    FONT3DVERTEX v;   v.p = p;   v.n = n;   v.tu = tu;   v.tv = tv;
    return v;
}




//-----------------------------------------------------------------------------
// Name: CD3DFont()
// Desc: Font class constructor
//-----------------------------------------------------------------------------
CD3DFont::CD3DFont(const TCHAR* strFontName, DWORD dwHeight, DWORD dwFlags)
{
    _tcsncpy_s(m_strFontName, strFontName, sizeof(m_strFontName) / sizeof(TCHAR));
    m_strFontName[sizeof(m_strFontName) / sizeof(TCHAR) - 1] = _T('\0');
    m_dwFontHeight = dwHeight;
    m_dwFontFlags = dwFlags;
    m_dwSpacing = 0;

    m_pd3dDevice = NULL;
    m_pTexture = NULL;
    m_pVB = NULL;

    m_pStateBlockSaved = NULL;
    m_pStateBlockDrawText = NULL;
}




//-----------------------------------------------------------------------------
// Name: ~CD3DFont()
// Desc: Font class destructor
//-----------------------------------------------------------------------------
CD3DFont::~CD3DFont()
{
    InvalidateDeviceObjects();
    DeleteDeviceObjects();
}




//-----------------------------------------------------------------------------
// Name: InitDeviceObjects()
// Desc: Initializes device-dependent objects, including the vertex buffer used
//       for rendering text and the texture map which stores the font image.
//-----------------------------------------------------------------------------
HRESULT CD3DFont::InitDeviceObjects(LPDIRECT3DDEVICE9 pd3dDevice)
{
    HRESULT hr;

    // Keep a local copy of the device
    m_pd3dDevice = pd3dDevice;

    // Establish the font and texture size
    m_fTextScale = 1.0f; // Draw fonts into texture without scaling

    // Large fonts need larger textures
    if (m_dwFontHeight > 60)
        m_dwTexWidth = m_dwTexHeight = 2048;
    else if (m_dwFontHeight > 30)
        m_dwTexWidth = m_dwTexHeight = 1024;
    else if (m_dwFontHeight > 15)
        m_dwTexWidth = m_dwTexHeight = 512;
    else
        m_dwTexWidth = m_dwTexHeight = 256;

    // If requested texture is too big, use a smaller texture and smaller font,
    // and scale up when rendering.
    D3DCAPS9 d3dCaps;
    m_pd3dDevice->GetDeviceCaps(&d3dCaps);

    if (m_dwTexWidth > d3dCaps.MaxTextureWidth)
    {
        m_fTextScale = (FLOAT)d3dCaps.MaxTextureWidth / (FLOAT)m_dwTexWidth;
        m_dwTexWidth = m_dwTexHeight = d3dCaps.MaxTextureWidth;
    }

    // Create a new texture for the font
    hr = m_pd3dDevice->CreateTexture(m_dwTexWidth, m_dwTexHeight, 1,
        0, D3DFMT_A4R4G4B4,
        D3DPOOL_MANAGED, &m_pTexture, NULL);
    if (FAILED(hr))
        return hr;

    // Prepare to create a bitmap
    DWORD* pBitmapBits;
    BITMAPINFO bmi;
    ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = (int)m_dwTexWidth;
    bmi.bmiHeader.biHeight = -(int)m_dwTexHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biBitCount = 32;

    // Create a DC and a bitmap for the font
    HDC     hDC = CreateCompatibleDC(NULL);
    HBITMAP hbmBitmap = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS,
        (void**)&pBitmapBits, NULL, 0);
    SetMapMode(hDC, MM_TEXT);

    // Create a font.  By specifying ANTIALIASED_QUALITY, we might get an
    // antialiased font, but this is not guaranteed.
    INT nHeight = -MulDiv(m_dwFontHeight,
        (INT)(GetDeviceCaps(hDC, LOGPIXELSY) * m_fTextScale), 72);
    DWORD dwBold = (m_dwFontFlags & D3DFONT_BOLD) ? FW_BOLD : FW_NORMAL;
    DWORD dwItalic = (m_dwFontFlags & D3DFONT_ITALIC) ? TRUE : FALSE;
    HFONT hFont = CreateFont(nHeight, 0, 0, 0, dwBold, dwItalic,
        FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY,
        VARIABLE_PITCH, m_strFontName);
    if (NULL == hFont)
        return E_FAIL;

    HGDIOBJ hbmOld = SelectObject(hDC, hbmBitmap);
    HGDIOBJ hFontOld = SelectObject(hDC, hFont);

    // Set text properties
    SetTextColor(hDC, RGB(255, 255, 255));
    SetBkColor(hDC, 0x00000000);
    SetTextAlign(hDC, TA_TOP);

    // Loop through all printable character and output them to the bitmap..
    // Meanwhile, keep track of the corresponding tex coords for each character.
    DWORD x = 0;
    DWORD y = 0;
    TCHAR str[2] = _T("x");
    SIZE size;

    // Calculate the spacing between characters based on line height
    GetTextExtentPoint32(hDC, TEXT(" "), 1, &size);
    x = m_dwSpacing = (DWORD)ceil(size.cy * 0.3f);

    for (TCHAR c = 32; c < 127; c++)
    {
        str[0] = c;
        GetTextExtentPoint32(hDC, str, 1, &size);

        if ((DWORD)(x + size.cx + m_dwSpacing) > m_dwTexWidth)
        {
            x = m_dwSpacing;
            y += size.cy + 1;
        }

        ExtTextOut(hDC, x + 0, y + 0, ETO_OPAQUE, NULL, str, 1, NULL);

        m_fTexCoords[c - 32][0] = ((FLOAT)(x + 0 - m_dwSpacing)) / m_dwTexWidth;
        m_fTexCoords[c - 32][1] = ((FLOAT)(y + 0 + 0)) / m_dwTexHeight;
        m_fTexCoords[c - 32][2] = ((FLOAT)(x + size.cx + m_dwSpacing)) / m_dwTexWidth;
        m_fTexCoords[c - 32][3] = ((FLOAT)(y + size.cy + 0)) / m_dwTexHeight;

        x += size.cx + (2 * m_dwSpacing);
    }

    // Lock the surface and write the alpha values for the set pixels
    D3DLOCKED_RECT d3dlr;
    m_pTexture->LockRect(0, &d3dlr, 0, 0);
    BYTE* pDstRow = (BYTE*)d3dlr.pBits;
    WORD* pDst16;
    BYTE bAlpha; // 4-bit measure of pixel intensity

    for (y = 0; y < m_dwTexHeight; y++)
    {
        pDst16 = (WORD*)pDstRow;
        for (x = 0; x < m_dwTexWidth; x++)
        {
            bAlpha = (BYTE)((pBitmapBits[m_dwTexWidth * y + x] & 0xff) >> 4);
            if (bAlpha > 0)
            {
                *pDst16++ = (WORD)((bAlpha << 12) | 0x0fff);
            }
            else
            {
                *pDst16++ = 0x0000;
            }
        }
        pDstRow += d3dlr.Pitch;
    }

    // Done updating texture, so clean up used objects
    m_pTexture->UnlockRect(0);
    SelectObject(hDC, hbmOld);
    SelectObject(hDC, hFontOld);
    DeleteObject(hbmBitmap);
    DeleteObject(hFont);
    DeleteDC(hDC);

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: RestoreDeviceObjects()
// Desc:
//-----------------------------------------------------------------------------
HRESULT CD3DFont::RestoreDeviceObjects()
{
    HRESULT hr;

    // Create vertex buffer for the letters
    int vertexSize = max(sizeof(FONT2DVERTEX), sizeof(FONT3DVERTEX));
    if (FAILED(hr = m_pd3dDevice->CreateVertexBuffer(MAX_NUM_VERTICES * vertexSize,
        D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0,
        D3DPOOL_DEFAULT, &m_pVB, NULL)))
    {
        return hr;
    }

    // Create the state blocks for rendering text
    for (UINT which = 0; which < 2; which++)
    {
        m_pd3dDevice->BeginStateBlock();
        m_pd3dDevice->SetTexture(0, m_pTexture);

        if (D3DFONT_ZENABLE & m_dwFontFlags)
            m_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
        else
            m_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);

        m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
        m_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
        m_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
        m_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
        m_pd3dDevice->SetRenderState(D3DRS_ALPHAREF, 0x08);
        m_pd3dDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
        m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
        m_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
        m_pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE);
        m_pd3dDevice->SetRenderState(D3DRS_CLIPPING, TRUE);
        m_pd3dDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, FALSE);
        m_pd3dDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_DISABLE);
        m_pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE);
        m_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE);
        m_pd3dDevice->SetRenderState(D3DRS_COLORWRITEENABLE,
            D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN |
            D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA);
        m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
        m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
        m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
        m_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
        m_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
        m_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
        m_pd3dDevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0);
        m_pd3dDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
        m_pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
        m_pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE);

        if (which == 0)
            m_pd3dDevice->EndStateBlock(&m_pStateBlockSaved);
        else
            m_pd3dDevice->EndStateBlock(&m_pStateBlockDrawText);
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: InvalidateDeviceObjects()
// Desc: Destroys all device-dependent objects
//-----------------------------------------------------------------------------
HRESULT CD3DFont::InvalidateDeviceObjects()
{
    SAFE_RELEASE(m_pVB);
    SAFE_RELEASE(m_pStateBlockSaved);
    SAFE_RELEASE(m_pStateBlockDrawText);

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DeleteDeviceObjects()
// Desc: Destroys all device-dependent objects
//-----------------------------------------------------------------------------
HRESULT CD3DFont::DeleteDeviceObjects()
{
    SAFE_RELEASE(m_pTexture);
    m_pd3dDevice = NULL;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: GetTextExtent()
// Desc: Get the dimensions of a text string
//-----------------------------------------------------------------------------
HRESULT CD3DFont::GetTextExtent(const TCHAR* strText, SIZE* pSize)
{
    if (NULL == strText || NULL == pSize)
        return E_FAIL;

    FLOAT fRowWidth = 0.0f;
    FLOAT fRowHeight = (m_fTexCoords[0][3] - m_fTexCoords[0][1]) * m_dwTexHeight;
    FLOAT fWidth = 0.0f;
    FLOAT fHeight = fRowHeight;

    while (*strText)
    {
        TCHAR c = *strText++;

        if (c == _T('\n'))
        {
            fRowWidth = 0.0f;
            fHeight += fRowHeight;
        }

        if ((c - 32) < 0 || (c - 32) >= 128 - 32)
            continue;

        FLOAT tx1 = m_fTexCoords[c - 32][0];
        FLOAT tx2 = m_fTexCoords[c - 32][2];

        fRowWidth += (tx2 - tx1) * m_dwTexWidth - 2 * m_dwSpacing;

        if (fRowWidth > fWidth)
            fWidth = fRowWidth;
    }

    pSize->cx = (int)fWidth;
    pSize->cy = (int)fHeight;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DrawTextScaled()
// Desc: Draws scaled 2D text.  Note that x and y are in viewport coordinates
//       (ranging from -1 to +1).  fXScale and fYScale are the size fraction 
//       relative to the entire viewport.  For example, a fXScale of 0.25 is
//       1/8th of the screen width.  This allows you to output text at a fixed
//       fraction of the viewport, even if the screen or window size changes.
//-----------------------------------------------------------------------------
HRESULT CD3DFont::DrawTextScaled(FLOAT x, FLOAT y, FLOAT z,
    FLOAT fXScale, FLOAT fYScale, DWORD dwColor,
    const TCHAR* strText, DWORD dwFlags)
{
    if (m_pd3dDevice == NULL)
        return E_FAIL;

    // Set up renderstate
    m_pStateBlockSaved->Capture();
    m_pStateBlockDrawText->Apply();
    m_pd3dDevice->SetFVF(D3DFVF_FONT2DVERTEX);
    m_pd3dDevice->SetPixelShader(NULL);
    m_pd3dDevice->SetStreamSource(0, m_pVB, 0, sizeof(FONT2DVERTEX));

    // Set filter states
    if (dwFlags & D3DFONT_FILTERED)
    {
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    }

    D3DVIEWPORT9 vp;
    m_pd3dDevice->GetViewport(&vp);
    FLOAT fLineHeight = (m_fTexCoords[0][3] - m_fTexCoords[0][1]) * m_dwTexHeight;

    // Center the text block in the viewport
    if (dwFlags & D3DFONT_CENTERED_X)
    {
        const TCHAR* strTextTmp = strText;
        float xFinal = 0.0f;

        while (*strTextTmp)
        {
            TCHAR c = *strTextTmp++;

            if (c == _T('\n'))
                break;  // Isn't supported.  
            if ((c - 32) < 0 || (c - 32) >= 128 - 32)
                continue;

            FLOAT tx1 = m_fTexCoords[c - 32][0];
            FLOAT tx2 = m_fTexCoords[c - 32][2];

            FLOAT w = (tx2 - tx1) * m_dwTexWidth;

            w *= (fXScale * vp.Height) / fLineHeight;

            xFinal += w - (2 * m_dwSpacing) * (fXScale * vp.Height) / fLineHeight;
        }

        x = -xFinal / vp.Width;
    }
    if (dwFlags & D3DFONT_CENTERED_Y)
    {
        y = -fLineHeight / vp.Height;
    }

    FLOAT sx = (x + 1.0f) * vp.Width / 2;
    FLOAT sy = (y + 1.0f) * vp.Height / 2;
    FLOAT sz = z;
    FLOAT rhw = 1.0f;

    // Adjust for character spacing
    sx -= m_dwSpacing * (fXScale * vp.Height) / fLineHeight;
    FLOAT fStartX = sx;

    // Fill vertex buffer
    FONT2DVERTEX* pVertices;
    DWORD         dwNumTriangles = 0L;
    m_pVB->Lock(0, 0, (void**)&pVertices, D3DLOCK_DISCARD);

    while (*strText)
    {
        TCHAR c = *strText++;

        if (c == _T('\n'))
        {
            sx = fStartX;
            sy += fYScale * vp.Height;
        }

        if ((c - 32) < 0 || (c - 32) >= 128 - 32)
            continue;

        FLOAT tx1 = m_fTexCoords[c - 32][0];
        FLOAT ty1 = m_fTexCoords[c - 32][1];
        FLOAT tx2 = m_fTexCoords[c - 32][2];
        FLOAT ty2 = m_fTexCoords[c - 32][3];

        FLOAT w = (tx2 - tx1) * m_dwTexWidth;
        FLOAT h = (ty2 - ty1) * m_dwTexHeight;

        w *= (fXScale * vp.Height) / fLineHeight;
        h *= (fYScale * vp.Height) / fLineHeight;

        if (c != _T(' '))
        {
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + 0 - 0.5f, sy + h - 0.5f, sz, rhw), dwColor, tx1, ty2);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + 0 - 0.5f, sy + 0 - 0.5f, sz, rhw), dwColor, tx1, ty1);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + w - 0.5f, sy + h - 0.5f, sz, rhw), dwColor, tx2, ty2);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + w - 0.5f, sy + 0 - 0.5f, sz, rhw), dwColor, tx2, ty1);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + w - 0.5f, sy + h - 0.5f, sz, rhw), dwColor, tx2, ty2);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + 0 - 0.5f, sy + 0 - 0.5f, sz, rhw), dwColor, tx1, ty1);
            dwNumTriangles += 2;

            if (dwNumTriangles * 3 > (MAX_NUM_VERTICES - 6))
            {
                // Unlock, render, and relock the vertex buffer
                m_pVB->Unlock();
                m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, dwNumTriangles);
                m_pVB->Lock(0, 0, (void**)&pVertices, D3DLOCK_DISCARD);
                dwNumTriangles = 0L;
            }
        }

        sx += w - (2 * m_dwSpacing) * (fXScale * vp.Height) / fLineHeight;
    }

    // Unlock and render the vertex buffer
    m_pVB->Unlock();
    if (dwNumTriangles > 0)
        m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, dwNumTriangles);

    // Restore the modified renderstates
    m_pStateBlockSaved->Apply();

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DrawText()
// Desc: Draws 2D text. Note that sx and sy are in pixels
//-----------------------------------------------------------------------------
HRESULT CD3DFont::DrawText(FLOAT sx, FLOAT sy, DWORD dwColor,
    const TCHAR* strText, DWORD dwFlags)
{
    if (m_pd3dDevice == NULL)
        return E_FAIL;

    // Setup renderstate
    m_pStateBlockSaved->Capture();
    m_pStateBlockDrawText->Apply();
    m_pd3dDevice->SetFVF(D3DFVF_FONT2DVERTEX);
    m_pd3dDevice->SetPixelShader(NULL);
    m_pd3dDevice->SetStreamSource(0, m_pVB, 0, sizeof(FONT2DVERTEX));

    // Set filter states
    if (dwFlags & D3DFONT_FILTERED)
    {
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    }

    // Center the text block in the viewport
    if (dwFlags & D3DFONT_CENTERED_X)
    {
        D3DVIEWPORT9 vp;
        m_pd3dDevice->GetViewport(&vp);
        const TCHAR* strTextTmp = strText;
        float xFinal = 0.0f;

        while (*strTextTmp)
        {
            TCHAR c = *strTextTmp++;

            if (c == _T('\n'))
                break;  // Isn't supported.  
            if ((c - 32) < 0 || (c - 32) >= 128 - 32)
                continue;

            FLOAT tx1 = m_fTexCoords[c - 32][0];
            FLOAT tx2 = m_fTexCoords[c - 32][2];

            FLOAT w = (tx2 - tx1) * m_dwTexWidth / m_fTextScale;

            xFinal += w - (2 * m_dwSpacing);
        }

        sx = (vp.Width - xFinal) / 2.0f;
    }
    if (dwFlags & D3DFONT_CENTERED_Y)
    {
        D3DVIEWPORT9 vp;
        m_pd3dDevice->GetViewport(&vp);
        float fLineHeight = ((m_fTexCoords[0][3] - m_fTexCoords[0][1]) * m_dwTexHeight);
        sy = (vp.Height - fLineHeight) / 2;
    }

    // Adjust for character spacing
    sx -= m_dwSpacing;
    FLOAT fStartX = sx;

    // Fill vertex buffer
    FONT2DVERTEX* pVertices = NULL;
    DWORD         dwNumTriangles = 0;
    m_pVB->Lock(0, 0, (void**)&pVertices, D3DLOCK_DISCARD);

    while (*strText)
    {
        TCHAR c = *strText++;

        if (c == _T('\n'))
        {
            sx = fStartX;
            sy += (m_fTexCoords[0][3] - m_fTexCoords[0][1]) * m_dwTexHeight;
        }

        if ((c - 32) < 0 || (c - 32) >= 128 - 32)
            continue;

        FLOAT tx1 = m_fTexCoords[c - 32][0];
        FLOAT ty1 = m_fTexCoords[c - 32][1];
        FLOAT tx2 = m_fTexCoords[c - 32][2];
        FLOAT ty2 = m_fTexCoords[c - 32][3];

        FLOAT w = (tx2 - tx1) * m_dwTexWidth / m_fTextScale;
        FLOAT h = (ty2 - ty1) * m_dwTexHeight / m_fTextScale;

        if (c != _T(' '))
        {
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + 0 - 0.5f, sy + h - 0.5f, 0.9f, 1.0f), dwColor, tx1, ty2);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + 0 - 0.5f, sy + 0 - 0.5f, 0.9f, 1.0f), dwColor, tx1, ty1);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + w - 0.5f, sy + h - 0.5f, 0.9f, 1.0f), dwColor, tx2, ty2);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + w - 0.5f, sy + 0 - 0.5f, 0.9f, 1.0f), dwColor, tx2, ty1);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + w - 0.5f, sy + h - 0.5f, 0.9f, 1.0f), dwColor, tx2, ty2);
            *pVertices++ = InitFont2DVertex(D3DXVECTOR4(sx + 0 - 0.5f, sy + 0 - 0.5f, 0.9f, 1.0f), dwColor, tx1, ty1);
            dwNumTriangles += 2;

            if (dwNumTriangles * 3 > (MAX_NUM_VERTICES - 6))
            {
                // Unlock, render, and relock the vertex buffer
                m_pVB->Unlock();
                m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, dwNumTriangles);
                pVertices = NULL;
                m_pVB->Lock(0, 0, (void**)&pVertices, D3DLOCK_DISCARD);
                dwNumTriangles = 0L;
            }
        }

        sx += w - (2 * m_dwSpacing);
    }

    // Unlock and render the vertex buffer
    m_pVB->Unlock();
    if (dwNumTriangles > 0)
        m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, dwNumTriangles);

    // Restore the modified renderstates
    m_pStateBlockSaved->Apply();

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: Render3DText()
// Desc: Renders 3D text
//-----------------------------------------------------------------------------
HRESULT CD3DFont::Render3DText(const TCHAR* strText, DWORD dwFlags)
{
    if (m_pd3dDevice == NULL)
        return E_FAIL;

    // Setup renderstate
    m_pStateBlockSaved->Capture();
    m_pStateBlockDrawText->Apply();
    m_pd3dDevice->SetFVF(D3DFVF_FONT3DVERTEX);
    m_pd3dDevice->SetPixelShader(NULL);
    m_pd3dDevice->SetStreamSource(0, m_pVB, 0, sizeof(FONT3DVERTEX));

    // Set filter states
    if (dwFlags & D3DFONT_FILTERED)
    {
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
        m_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    }

    // Position for each text element
    FLOAT x = 0.0f;
    FLOAT y = 0.0f;

    // Center the text block at the origin (not the viewport)
    if (dwFlags & D3DFONT_CENTERED_X)
    {
        SIZE sz;
        GetTextExtent(strText, &sz);
        x = -(((FLOAT)sz.cx) / 10.0f) / 2.0f;
    }
    if (dwFlags & D3DFONT_CENTERED_Y)
    {
        SIZE sz;
        GetTextExtent(strText, &sz);
        y = -(((FLOAT)sz.cy) / 10.0f) / 2.0f;
    }

    // Turn off culling for two-sided text
    if (dwFlags & D3DFONT_TWOSIDED)
        m_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

    // Adjust for character spacing
    x -= m_dwSpacing / 10.0f;
    FLOAT fStartX = x;
    TCHAR c;

    // Fill vertex buffer
    FONT3DVERTEX* pVertices;
    DWORD         dwNumTriangles = 0L;
    m_pVB->Lock(0, 0, (void**)&pVertices, D3DLOCK_DISCARD);

    while ((c = *strText++) != 0)
    {
        if (c == '\n')
        {
            x = fStartX;
            y -= (m_fTexCoords[0][3] - m_fTexCoords[0][1]) * m_dwTexHeight / 10.0f;
        }

        if ((c - 32) < 0 || (c - 32) >= 128 - 32)
            continue;

        FLOAT tx1 = m_fTexCoords[c - 32][0];
        FLOAT ty1 = m_fTexCoords[c - 32][1];
        FLOAT tx2 = m_fTexCoords[c - 32][2];
        FLOAT ty2 = m_fTexCoords[c - 32][3];

        FLOAT w = (tx2 - tx1) * m_dwTexWidth / (10.0f * m_fTextScale);
        FLOAT h = (ty2 - ty1) * m_dwTexHeight / (10.0f * m_fTextScale);

        if (c != _T(' '))
        {
            *pVertices++ = InitFont3DVertex(D3DXVECTOR3(x + 0, y + 0, 0), D3DXVECTOR3(0, 0, -1), tx1, ty2);
            *pVertices++ = InitFont3DVertex(D3DXVECTOR3(x + 0, y + h, 0), D3DXVECTOR3(0, 0, -1), tx1, ty1);
            *pVertices++ = InitFont3DVertex(D3DXVECTOR3(x + w, y + 0, 0), D3DXVECTOR3(0, 0, -1), tx2, ty2);
            *pVertices++ = InitFont3DVertex(D3DXVECTOR3(x + w, y + h, 0), D3DXVECTOR3(0, 0, -1), tx2, ty1);
            *pVertices++ = InitFont3DVertex(D3DXVECTOR3(x + w, y + 0, 0), D3DXVECTOR3(0, 0, -1), tx2, ty2);
            *pVertices++ = InitFont3DVertex(D3DXVECTOR3(x + 0, y + h, 0), D3DXVECTOR3(0, 0, -1), tx1, ty1);
            dwNumTriangles += 2;

            if (dwNumTriangles * 3 > (MAX_NUM_VERTICES - 6))
            {
                // Unlock, render, and relock the vertex buffer
                m_pVB->Unlock();
                m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, dwNumTriangles);
                m_pVB->Lock(0, 0, (void**)&pVertices, D3DLOCK_DISCARD);
                dwNumTriangles = 0L;
            }
        }

        x += w - (2 * m_dwSpacing) / 10.0f;
    }

    // Unlock and render the vertex buffer
    m_pVB->Unlock();
    if (dwNumTriangles > 0)
        m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, dwNumTriangles);

    // Restore the modified renderstates
    m_pStateBlockSaved->Apply();

    return S_OK;
}

d3dutil.h

#pragma once
#include <D3D9.h>
#include <D3DX9Math.h>




//-----------------------------------------------------------------------------
// Name: D3DUtil_InitMaterial()
// Desc: Initializes a D3DMATERIAL9 structure, setting the diffuse and ambient
//       colors. It does not set emissive or specular colors.
//-----------------------------------------------------------------------------
VOID D3DUtil_InitMaterial(D3DMATERIAL9& mtrl, FLOAT r = 0.0f, FLOAT g = 0.0f,
    FLOAT b = 0.0f, FLOAT a = 1.0f);




//-----------------------------------------------------------------------------
// Name: D3DUtil_InitLight()
// Desc: Initializes a D3DLIGHT structure, setting the light position. The
//       diffuse color is set to white, specular and ambient left as black.
//-----------------------------------------------------------------------------
VOID D3DUtil_InitLight(D3DLIGHT9& light, D3DLIGHTTYPE ltType,
    FLOAT x = 0.0f, FLOAT y = 0.0f, FLOAT z = 0.0f);




//-----------------------------------------------------------------------------
// Name: D3DUtil_CreateTexture()
// Desc: Helper function to create a texture. It checks the root path first,
//       then tries the DXSDK media path (as specified in the system registry).
//-----------------------------------------------------------------------------
HRESULT D3DUtil_CreateTexture(LPDIRECT3DDEVICE9 pd3dDevice, TCHAR* strTexture,
    LPDIRECT3DTEXTURE9* ppTexture,
    D3DFORMAT d3dFormat = D3DFMT_UNKNOWN);




//-----------------------------------------------------------------------------
// Name: D3DUtil_GetCubeMapViewMatrix()
// Desc: Returns a view matrix for rendering to a face of a cubemap.
//-----------------------------------------------------------------------------
D3DXMATRIX D3DUtil_GetCubeMapViewMatrix(DWORD dwFace);




//-----------------------------------------------------------------------------
// Name: D3DUtil_GetRotationFromCursor()
// Desc: Returns a quaternion for the rotation implied by the window's cursor
//       position.
//-----------------------------------------------------------------------------
D3DXQUATERNION D3DUtil_GetRotationFromCursor(HWND hWnd,
    FLOAT fTrackBallRadius = 1.0f);




//-----------------------------------------------------------------------------
// Name: D3DUtil_SetDeviceCursor
// Desc: Builds and sets a cursor for the D3D device based on hCursor.
//-----------------------------------------------------------------------------
HRESULT D3DUtil_SetDeviceCursor(LPDIRECT3DDEVICE9 pd3dDevice, HCURSOR hCursor,
    BOOL bAddWatermark);


//-----------------------------------------------------------------------------
// Name: D3DUtil_D3DFormatToString
// Desc: Returns the string for the given D3DFORMAT.
//       bWithPrefix determines whether the string should include the "D3DFMT_"
//-----------------------------------------------------------------------------
TCHAR* D3DUtil_D3DFormatToString(D3DFORMAT format, bool bWithPrefix = true);


//-----------------------------------------------------------------------------
// Name: class CD3DArcBall
// Desc:
//-----------------------------------------------------------------------------
class CD3DArcBall
{
    INT            m_iWidth;   // ArcBall's window width
    INT            m_iHeight;  // ArcBall's window height
    FLOAT          m_fRadius;  // ArcBall's radius in screen coords
    FLOAT          m_fRadiusTranslation; // ArcBall's radius for translating the target

    D3DXQUATERNION m_qDown;               // Quaternion before button down
    D3DXQUATERNION m_qNow;                // Composite quaternion for current drag
    D3DXMATRIXA16  m_matRotation;         // Matrix for arcball's orientation
    D3DXMATRIXA16  m_matRotationDelta;    // Matrix for arcball's orientation
    D3DXMATRIXA16  m_matTranslation;      // Matrix for arcball's position
    D3DXMATRIXA16  m_matTranslationDelta; // Matrix for arcball's position
    BOOL           m_bDrag;               // Whether user is dragging arcball
    BOOL           m_bRightHanded;        // Whether to use RH coordinate system

    D3DXVECTOR3 ScreenToVector(int sx, int sy);

public:
    LRESULT     HandleMouseMessages(HWND, UINT, WPARAM, LPARAM);

    D3DXMATRIX* GetRotationMatrix() { return &m_matRotation; }
    D3DXMATRIX* GetRotationDeltaMatrix() { return &m_matRotationDelta; }
    D3DXMATRIX* GetTranslationMatrix() { return &m_matTranslation; }
    D3DXMATRIX* GetTranslationDeltaMatrix() { return &m_matTranslationDelta; }
    BOOL        IsBeingDragged() { return m_bDrag; }

    VOID        SetRadius(FLOAT fRadius);
    VOID        SetWindow(INT w, INT h, FLOAT r = 0.9);
    VOID        SetRightHanded(BOOL bRightHanded) { m_bRightHanded = bRightHanded; }

    CD3DArcBall();
    VOID        Init();
};




//-----------------------------------------------------------------------------
// Name: class CD3DCamera
// Desc:
//-----------------------------------------------------------------------------
class CD3DCamera
{
    D3DXVECTOR3 m_vEyePt;       // Attributes for view matrix
    D3DXVECTOR3 m_vLookatPt;
    D3DXVECTOR3 m_vUpVec;

    D3DXVECTOR3 m_vView;
    D3DXVECTOR3 m_vCross;

    D3DXMATRIXA16  m_matView;
    D3DXMATRIXA16  m_matBillboard; // Special matrix for billboarding effects

    FLOAT       m_fFOV;         // Attributes for projection matrix
    FLOAT       m_fAspect;
    FLOAT       m_fNearPlane;
    FLOAT       m_fFarPlane;
    D3DXMATRIXA16  m_matProj;

public:
    // Access functions
    D3DXVECTOR3 GetEyePt() { return m_vEyePt; }
    D3DXVECTOR3 GetLookatPt() { return m_vLookatPt; }
    D3DXVECTOR3 GetUpVec() { return m_vUpVec; }
    D3DXVECTOR3 GetViewDir() { return m_vView; }
    D3DXVECTOR3 GetCross() { return m_vCross; }

    FLOAT       GetFOV() { return m_fFOV; }
    FLOAT       GetAspect() { return m_fAspect; }
    FLOAT       GetNearPlane() { return m_fNearPlane; }
    FLOAT       GetFarPlane() { return m_fFarPlane; }

    D3DXMATRIX  GetViewMatrix() { return m_matView; }
    D3DXMATRIX  GetBillboardMatrix() { return m_matBillboard; }
    D3DXMATRIX  GetProjMatrix() { return m_matProj; }

    VOID SetViewParams(D3DXVECTOR3& vEyePt, D3DXVECTOR3& vLookatPt,
        D3DXVECTOR3& vUpVec);
    VOID SetProjParams(FLOAT fFOV, FLOAT fAspect, FLOAT fNearPlane,
        FLOAT fFarPlane);

    CD3DCamera();
};

d3dutil.cpp

#define STRICT
#include <Windows.h>
#include <WindowsX.h>
#include <tchar.h>
#include <stdio.h>
#include "D3DUtil.h"
#include "DXUtil.h"
#include "D3DX9.h"




//-----------------------------------------------------------------------------
// Name: D3DUtil_InitMaterial()
// Desc: Initializes a D3DMATERIAL9 structure, setting the diffuse and ambient
//       colors. It does not set emissive or specular colors.
//-----------------------------------------------------------------------------
VOID D3DUtil_InitMaterial(D3DMATERIAL9& mtrl, FLOAT r, FLOAT g, FLOAT b,
    FLOAT a)
{
    ZeroMemory(&mtrl, sizeof(D3DMATERIAL9));
    mtrl.Diffuse.r = mtrl.Ambient.r = r;
    mtrl.Diffuse.g = mtrl.Ambient.g = g;
    mtrl.Diffuse.b = mtrl.Ambient.b = b;
    mtrl.Diffuse.a = mtrl.Ambient.a = a;
}




//-----------------------------------------------------------------------------
// Name: D3DUtil_InitLight()
// Desc: Initializes a D3DLIGHT structure, setting the light position. The
//       diffuse color is set to white; specular and ambient are left as black.
//-----------------------------------------------------------------------------
VOID D3DUtil_InitLight(D3DLIGHT9& light, D3DLIGHTTYPE ltType,
    FLOAT x, FLOAT y, FLOAT z)
{
    D3DXVECTOR3 vecLightDirUnnormalized(x, y, z);
    ZeroMemory(&light, sizeof(D3DLIGHT9));
    light.Type = ltType;
    light.Diffuse.r = 1.0f;
    light.Diffuse.g = 1.0f;
    light.Diffuse.b = 1.0f;
    D3DXVec3Normalize((D3DXVECTOR3*)&light.Direction, &vecLightDirUnnormalized);
    light.Position.x = x;
    light.Position.y = y;
    light.Position.z = z;
    light.Range = 1000.0f;
}




//-----------------------------------------------------------------------------
// Name: D3DUtil_CreateTexture()
// Desc: Helper function to create a texture. It checks the root path first,
//       then tries the DXSDK media path (as specified in the system registry).
//-----------------------------------------------------------------------------
HRESULT D3DUtil_CreateTexture(LPDIRECT3DDEVICE9 pd3dDevice, TCHAR* strTexture,
    LPDIRECT3DTEXTURE9* ppTexture, D3DFORMAT d3dFormat)
{
    HRESULT hr;
    TCHAR strPath[MAX_PATH];

    // Get the path to the texture
    if (FAILED(hr = DXUtil_FindMediaFileCb(strPath, sizeof(strPath), strTexture)))
        return hr;

    // Create the texture using D3DX
    return D3DXCreateTextureFromFileEx(pd3dDevice, strPath,
        D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, d3dFormat,
        D3DPOOL_MANAGED, D3DX_FILTER_TRIANGLE | D3DX_FILTER_MIRROR,
        D3DX_FILTER_TRIANGLE | D3DX_FILTER_MIRROR, 0, NULL, NULL, ppTexture);
}




//-----------------------------------------------------------------------------
// Name: D3DUtil_GetCubeMapViewMatrix()
// Desc: Returns a view matrix for rendering to a face of a cubemap.
//-----------------------------------------------------------------------------
D3DXMATRIX D3DUtil_GetCubeMapViewMatrix(DWORD dwFace)
{
    D3DXVECTOR3 vEyePt = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 vLookDir;
    D3DXVECTOR3 vUpDir;

    switch (dwFace)
    {
    case D3DCUBEMAP_FACE_POSITIVE_X:
        vLookDir = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
        vUpDir = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
        break;
    case D3DCUBEMAP_FACE_NEGATIVE_X:
        vLookDir = D3DXVECTOR3(-1.0f, 0.0f, 0.0f);
        vUpDir = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
        break;
    case D3DCUBEMAP_FACE_POSITIVE_Y:
        vLookDir = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
        vUpDir = D3DXVECTOR3(0.0f, 0.0f, -1.0f);
        break;
    case D3DCUBEMAP_FACE_NEGATIVE_Y:
        vLookDir = D3DXVECTOR3(0.0f, -1.0f, 0.0f);
        vUpDir = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
        break;
    case D3DCUBEMAP_FACE_POSITIVE_Z:
        vLookDir = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
        vUpDir = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
        break;
    case D3DCUBEMAP_FACE_NEGATIVE_Z:
        vLookDir = D3DXVECTOR3(0.0f, 0.0f, -1.0f);
        vUpDir = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
        break;
    }

    // Set the view transform for this cubemap surface
    D3DXMATRIXA16 matView;
    D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookDir, &vUpDir);
    return matView;
}




//-----------------------------------------------------------------------------
// Name: D3DUtil_GetRotationFromCursor()
// Desc: Returns a quaternion for the rotation implied by the window's cursor
//       position.
//-----------------------------------------------------------------------------
D3DXQUATERNION D3DUtil_GetRotationFromCursor(HWND hWnd,
    FLOAT fTrackBallRadius)
{
    POINT pt;
    RECT  rc;
    GetCursorPos(&pt);
    GetClientRect(hWnd, &rc);
    ScreenToClient(hWnd, &pt);
    FLOAT sx = (((2.0f * pt.x) / (rc.right - rc.left)) - 1);
    FLOAT sy = (((2.0f * pt.y) / (rc.bottom - rc.top)) - 1);
    FLOAT sz;

    if (sx == 0.0f && sy == 0.0f)
        return D3DXQUATERNION(0.0f, 0.0f, 0.0f, 1.0f);

    FLOAT d2 = sqrtf(sx * sx + sy * sy);

    if (d2 < fTrackBallRadius * 0.70710678118654752440) // Inside sphere
        sz = sqrtf(fTrackBallRadius * fTrackBallRadius - d2 * d2);
    else                                                 // On hyperbola
        sz = (fTrackBallRadius * fTrackBallRadius) / (2.0f * d2);

    // Get two points on trackball's sphere
    D3DXVECTOR3 p1(sx, sy, sz);
    D3DXVECTOR3 p2(0.0f, 0.0f, fTrackBallRadius);

    // Get axis of rotation, which is cross product of p1 and p2
    D3DXVECTOR3 vAxis;
    D3DXVec3Cross(&vAxis, &p1, &p2);

    // Calculate angle for the rotation about that axis
    D3DXVECTOR3 vecDiff = p2 - p1;
    FLOAT t = D3DXVec3Length(&vecDiff) / (2.0f * fTrackBallRadius);
    if (t > +1.0f) t = +1.0f;
    if (t < -1.0f) t = -1.0f;
    FLOAT fAngle = 2.0f * asinf(t);

    // Convert axis to quaternion
    D3DXQUATERNION quat;
    D3DXQuaternionRotationAxis(&quat, &vAxis, fAngle);
    return quat;
}




//-----------------------------------------------------------------------------
// Name: D3DUtil_SetDeviceCursor
// Desc: Gives the D3D device a cursor with image and hotspot from hCursor.
//-----------------------------------------------------------------------------
HRESULT D3DUtil_SetDeviceCursor(LPDIRECT3DDEVICE9 pd3dDevice, HCURSOR hCursor,
    BOOL bAddWatermark)
{
    HRESULT hr = E_FAIL;
    ICONINFO iconinfo;
    BOOL bBWCursor;
    LPDIRECT3DSURFACE9 pCursorSurface = NULL;
    HDC hdcColor = NULL;
    HDC hdcMask = NULL;
    HDC hdcScreen = NULL;
    BITMAP bm;
    DWORD dwWidth;
    DWORD dwHeightSrc;
    DWORD dwHeightDest;
    COLORREF crColor;
    COLORREF crMask;
    UINT x;
    UINT y;
    BITMAPINFO bmi;
    COLORREF* pcrArrayColor = NULL;
    COLORREF* pcrArrayMask = NULL;
    DWORD* pBitmap;
    HGDIOBJ hgdiobjOld;

    ZeroMemory(&iconinfo, sizeof(iconinfo));
    if (!GetIconInfo(hCursor, &iconinfo))
        goto End;

    if (0 == GetObject((HGDIOBJ)iconinfo.hbmMask, sizeof(BITMAP), (LPVOID)&bm))
        goto End;
    dwWidth = bm.bmWidth;
    dwHeightSrc = bm.bmHeight;

    if (iconinfo.hbmColor == NULL)
    {
        bBWCursor = TRUE;
        dwHeightDest = dwHeightSrc / 2;
    }
    else
    {
        bBWCursor = FALSE;
        dwHeightDest = dwHeightSrc;
    }

    // Create a surface for the fullscreen cursor
    if (FAILED(hr = pd3dDevice->CreateOffscreenPlainSurface(dwWidth, dwHeightDest,
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pCursorSurface, NULL)))
    {
        goto End;
    }

    pcrArrayMask = new DWORD[dwWidth * dwHeightSrc];

    ZeroMemory(&bmi, sizeof(bmi));
    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
    bmi.bmiHeader.biWidth = dwWidth;
    bmi.bmiHeader.biHeight = dwHeightSrc;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;

    hdcScreen = GetDC(NULL);
    hdcMask = CreateCompatibleDC(hdcScreen);
    if (hdcMask == NULL)
    {
        hr = E_FAIL;
        goto End;
    }
    hgdiobjOld = SelectObject(hdcMask, iconinfo.hbmMask);
    GetDIBits(hdcMask, iconinfo.hbmMask, 0, dwHeightSrc,
        pcrArrayMask, &bmi, DIB_RGB_COLORS);
    SelectObject(hdcMask, hgdiobjOld);

    if (!bBWCursor)
    {
        pcrArrayColor = new DWORD[dwWidth * dwHeightDest];
        hdcColor = CreateCompatibleDC(hdcScreen);
        if (hdcColor == NULL)
        {
            hr = E_FAIL;
            goto End;
        }
        SelectObject(hdcColor, iconinfo.hbmColor);
        GetDIBits(hdcColor, iconinfo.hbmColor, 0, dwHeightDest,
            pcrArrayColor, &bmi, DIB_RGB_COLORS);
    }

    // Transfer cursor image into the surface
    D3DLOCKED_RECT lr;
    pCursorSurface->LockRect(&lr, NULL, 0);
    pBitmap = (DWORD*)lr.pBits;
    for (y = 0; y < dwHeightDest; y++)
    {
        for (x = 0; x < dwWidth; x++)
        {
            if (bBWCursor)
            {
                crColor = pcrArrayMask[dwWidth * (dwHeightDest - 1 - y) + x];
                crMask = pcrArrayMask[dwWidth * (dwHeightSrc - 1 - y) + x];
            }
            else
            {
                crColor = pcrArrayColor[dwWidth * (dwHeightDest - 1 - y) + x];
                crMask = pcrArrayMask[dwWidth * (dwHeightDest - 1 - y) + x];
            }
            if (crMask == 0)
                pBitmap[dwWidth * y + x] = 0xff000000 | crColor;
            else
                pBitmap[dwWidth * y + x] = 0x00000000;

            // It may be helpful to make the D3D cursor look slightly 
            // different from the Windows cursor so you can distinguish 
            // between the two when developing/testing code.  When
            // bAddWatermark is TRUE, the following code adds some
            // small grey "D3D" characters to the upper-left corner of
            // the D3D cursor image.
            if (bAddWatermark && x < 12 && y < 5)
            {
                // 11.. 11.. 11.. .... CCC0
                // 1.1. ..1. 1.1. .... A2A0
                // 1.1. .1.. 1.1. .... A4A0
                // 1.1. ..1. 1.1. .... A2A0
                // 11.. 11.. 11.. .... CCC0

                const WORD wMask[5] = { 0xccc0, 0xa2a0, 0xa4a0, 0xa2a0, 0xccc0 };
                if (wMask[y] & (1 << (15 - x)))
                {
                    pBitmap[dwWidth * y + x] |= 0xff808080;
                }
            }
        }
    }
    pCursorSurface->UnlockRect();

    // Set the device cursor
    if (FAILED(hr = pd3dDevice->SetCursorProperties(iconinfo.xHotspot,
        iconinfo.yHotspot, pCursorSurface)))
    {
        goto End;
    }

    hr = S_OK;

End:
    if (iconinfo.hbmMask != NULL)
        DeleteObject(iconinfo.hbmMask);
    if (iconinfo.hbmColor != NULL)
        DeleteObject(iconinfo.hbmColor);
    if (hdcScreen != NULL)
        ReleaseDC(NULL, hdcScreen);
    if (hdcColor != NULL)
        DeleteDC(hdcColor);
    if (hdcMask != NULL)
        DeleteDC(hdcMask);
    SAFE_DELETE_ARRAY(pcrArrayColor);
    SAFE_DELETE_ARRAY(pcrArrayMask);
    SAFE_RELEASE(pCursorSurface);
    return hr;
}



//-----------------------------------------------------------------------------
// Name: D3DFormatToString
// Desc: Returns the string for the given D3DFORMAT.
//-----------------------------------------------------------------------------
TCHAR* D3DUtil_D3DFormatToString(D3DFORMAT format, bool bWithPrefix)
{
    TCHAR* pstr = NULL;
    switch (format)
    {
    case D3DFMT_UNKNOWN:         pstr = (TCHAR*)TEXT("D3DFMT_UNKNOWN"); break;
    case D3DFMT_R8G8B8:          pstr = (TCHAR*)TEXT("D3DFMT_R8G8B8"); break;
    case D3DFMT_A8R8G8B8:        pstr = (TCHAR*)TEXT("D3DFMT_A8R8G8B8"); break;
    case D3DFMT_X8R8G8B8:        pstr = (TCHAR*)TEXT("D3DFMT_X8R8G8B8"); break;
    case D3DFMT_R5G6B5:          pstr = (TCHAR*)TEXT("D3DFMT_R5G6B5"); break;
    case D3DFMT_X1R5G5B5:        pstr = (TCHAR*)TEXT("D3DFMT_X1R5G5B5"); break;
    case D3DFMT_A1R5G5B5:        pstr = (TCHAR*)TEXT("D3DFMT_A1R5G5B5"); break;
    case D3DFMT_A4R4G4B4:        pstr = (TCHAR*)TEXT("D3DFMT_A4R4G4B4"); break;
    case D3DFMT_R3G3B2:          pstr = (TCHAR*)TEXT("D3DFMT_R3G3B2"); break;
    case D3DFMT_A8:              pstr = (TCHAR*)TEXT("D3DFMT_A8"); break;
    case D3DFMT_A8R3G3B2:        pstr = (TCHAR*)TEXT("D3DFMT_A8R3G3B2"); break;
    case D3DFMT_X4R4G4B4:        pstr = (TCHAR*)TEXT("D3DFMT_X4R4G4B4"); break;
    case D3DFMT_A2B10G10R10:     pstr = (TCHAR*)TEXT("D3DFMT_A2B10G10R10"); break;
    case D3DFMT_A8B8G8R8:        pstr = (TCHAR*)TEXT("D3DFMT_A8B8G8R8"); break;
    case D3DFMT_X8B8G8R8:        pstr = (TCHAR*)TEXT("D3DFMT_X8B8G8R8"); break;
    case D3DFMT_G16R16:          pstr = (TCHAR*)TEXT("D3DFMT_G16R16"); break;
    case D3DFMT_A2R10G10B10:     pstr = (TCHAR*)TEXT("D3DFMT_A2R10G10B10"); break;
    case D3DFMT_A16B16G16R16:    pstr = (TCHAR*)TEXT("D3DFMT_A16B16G16R16"); break;
    case D3DFMT_A8P8:            pstr = (TCHAR*)TEXT("D3DFMT_A8P8"); break;
    case D3DFMT_P8:              pstr = (TCHAR*)TEXT("D3DFMT_P8"); break;
    case D3DFMT_L8:              pstr = (TCHAR*)TEXT("D3DFMT_L8"); break;
    case D3DFMT_A8L8:            pstr = (TCHAR*)TEXT("D3DFMT_A8L8"); break;
    case D3DFMT_A4L4:            pstr = (TCHAR*)TEXT("D3DFMT_A4L4"); break;
    case D3DFMT_V8U8:            pstr = (TCHAR*)TEXT("D3DFMT_V8U8"); break;
    case D3DFMT_L6V5U5:          pstr = (TCHAR*)TEXT("D3DFMT_L6V5U5"); break;
    case D3DFMT_X8L8V8U8:        pstr = (TCHAR*)TEXT("D3DFMT_X8L8V8U8"); break;
    case D3DFMT_Q8W8V8U8:        pstr = (TCHAR*)TEXT("D3DFMT_Q8W8V8U8"); break;
    case D3DFMT_V16U16:          pstr = (TCHAR*)TEXT("D3DFMT_V16U16"); break;
    case D3DFMT_A2W10V10U10:     pstr = (TCHAR*)TEXT("D3DFMT_A2W10V10U10"); break;
    case D3DFMT_UYVY:            pstr = (TCHAR*)TEXT("D3DFMT_UYVY"); break;
    case D3DFMT_YUY2:            pstr = (TCHAR*)TEXT("D3DFMT_YUY2"); break;
    case D3DFMT_DXT1:            pstr = (TCHAR*)TEXT("D3DFMT_DXT1"); break;
    case D3DFMT_DXT2:            pstr = (TCHAR*)TEXT("D3DFMT_DXT2"); break;
    case D3DFMT_DXT3:            pstr = (TCHAR*)TEXT("D3DFMT_DXT3"); break;
    case D3DFMT_DXT4:            pstr = (TCHAR*)TEXT("D3DFMT_DXT4"); break;
    case D3DFMT_DXT5:            pstr = (TCHAR*)TEXT("D3DFMT_DXT5"); break;
    case D3DFMT_D16_LOCKABLE:    pstr = (TCHAR*)TEXT("D3DFMT_D16_LOCKABLE"); break;
    case D3DFMT_D32:             pstr = (TCHAR*)TEXT("D3DFMT_D32"); break;
    case D3DFMT_D15S1:           pstr = (TCHAR*)TEXT("D3DFMT_D15S1"); break;
    case D3DFMT_D24S8:           pstr = (TCHAR*)TEXT("D3DFMT_D24S8"); break;
    case D3DFMT_D24X8:           pstr = (TCHAR*)TEXT("D3DFMT_D24X8"); break;
    case D3DFMT_D24X4S4:         pstr = (TCHAR*)TEXT("D3DFMT_D24X4S4"); break;
    case D3DFMT_D16:             pstr = (TCHAR*)TEXT("D3DFMT_D16"); break;
    case D3DFMT_L16:             pstr = (TCHAR*)TEXT("D3DFMT_L16"); break;
    case D3DFMT_VERTEXDATA:      pstr = (TCHAR*)TEXT("D3DFMT_VERTEXDATA"); break;
    case D3DFMT_INDEX16:         pstr = (TCHAR*)TEXT("D3DFMT_INDEX16"); break;
    case D3DFMT_INDEX32:         pstr = (TCHAR*)TEXT("D3DFMT_INDEX32"); break;
    case D3DFMT_Q16W16V16U16:    pstr = (TCHAR*)TEXT("D3DFMT_Q16W16V16U16"); break;
    case D3DFMT_MULTI2_ARGB8:    pstr = (TCHAR*)TEXT("D3DFMT_MULTI2_ARGB8"); break;
    case D3DFMT_R16F:            pstr = (TCHAR*)TEXT("D3DFMT_R16F"); break;
    case D3DFMT_G16R16F:         pstr = (TCHAR*)TEXT("D3DFMT_G16R16F"); break;
    case D3DFMT_A16B16G16R16F:   pstr = (TCHAR*)TEXT("D3DFMT_A16B16G16R16F"); break;
    case D3DFMT_R32F:            pstr = (TCHAR*)TEXT("D3DFMT_R32F"); break;
    case D3DFMT_G32R32F:         pstr = (TCHAR*)TEXT("D3DFMT_G32R32F"); break;
    case D3DFMT_A32B32G32R32F:   pstr = (TCHAR*)TEXT("D3DFMT_A32B32G32R32F"); break;
    case D3DFMT_CxV8U8:          pstr = (TCHAR*)TEXT("D3DFMT_CxV8U8"); break;
    default:                     pstr = (TCHAR*)TEXT("Unknown format"); break;
    }
    if (bWithPrefix || _tcsstr(pstr, TEXT("D3DFMT_")) == NULL)
        return pstr;
    else
        return pstr + lstrlen(TEXT("D3DFMT_"));
}


//-----------------------------------------------------------------------------
// Name: D3DXQuaternionUnitAxisToUnitAxis2
// Desc: Axis to axis quaternion double angle (no normalization)
//       Takes two points on unit sphere an angle THETA apart, returns
//       quaternion that represents a rotation around cross product by 2*THETA.
//-----------------------------------------------------------------------------
inline D3DXQUATERNION* WINAPI D3DXQuaternionUnitAxisToUnitAxis2
(D3DXQUATERNION* pOut, const D3DXVECTOR3* pvFrom, const D3DXVECTOR3* pvTo)
{
    D3DXVECTOR3 vAxis;
    D3DXVec3Cross(&vAxis, pvFrom, pvTo);    // proportional to sin(theta)
    pOut->x = vAxis.x;
    pOut->y = vAxis.y;
    pOut->z = vAxis.z;
    pOut->w = D3DXVec3Dot(pvFrom, pvTo);
    return pOut;
}




//-----------------------------------------------------------------------------
// Name: D3DXQuaternionAxisToAxis
// Desc: Axis to axis quaternion 
//       Takes two points on unit sphere an angle THETA apart, returns
//       quaternion that represents a rotation around cross product by theta.
//-----------------------------------------------------------------------------
inline D3DXQUATERNION* WINAPI D3DXQuaternionAxisToAxis
(D3DXQUATERNION* pOut, const D3DXVECTOR3* pvFrom, const D3DXVECTOR3* pvTo)
{
    D3DXVECTOR3 vA, vB;
    D3DXVec3Normalize(&vA, pvFrom);
    D3DXVec3Normalize(&vB, pvTo);
    D3DXVECTOR3 vHalf(vA + vB);
    D3DXVec3Normalize(&vHalf, &vHalf);
    return D3DXQuaternionUnitAxisToUnitAxis2(pOut, &vA, &vHalf);
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
CD3DArcBall::CD3DArcBall()
{
    Init();
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
void CD3DArcBall::Init()
{
    D3DXQuaternionIdentity(&m_qDown);
    D3DXQuaternionIdentity(&m_qNow);
    D3DXMatrixIdentity(&m_matRotation);
    D3DXMatrixIdentity(&m_matRotationDelta);
    D3DXMatrixIdentity(&m_matTranslation);
    D3DXMatrixIdentity(&m_matTranslationDelta);
    m_bDrag = FALSE;
    m_fRadiusTranslation = 1.0f;
    m_bRightHanded = FALSE;
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DArcBall::SetWindow(int iWidth, int iHeight, float fRadius)
{
    // Set ArcBall info
    m_iWidth = iWidth;
    m_iHeight = iHeight;
    m_fRadius = fRadius;
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
D3DXVECTOR3 CD3DArcBall::ScreenToVector(int sx, int sy)
{
    // Scale to screen
    FLOAT x = -(sx - m_iWidth / 2) / (m_fRadius * m_iWidth / 2);
    FLOAT y = (sy - m_iHeight / 2) / (m_fRadius * m_iHeight / 2);

    if (m_bRightHanded)
    {
        x = -x;
        y = -y;
    }

    FLOAT z = 0.0f;
    FLOAT mag = x * x + y * y;

    if (mag > 1.0f)
    {
        FLOAT scale = 1.0f / sqrtf(mag);
        x *= scale;
        y *= scale;
    }
    else
        z = sqrtf(1.0f - mag);

    // Return vector
    return D3DXVECTOR3(x, y, z);
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DArcBall::SetRadius(FLOAT fRadius)
{
    m_fRadiusTranslation = fRadius;
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
LRESULT CD3DArcBall::HandleMouseMessages(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam)
{
    UNREFERENCED_PARAMETER(hWnd);

    static int         iCurMouseX;      // Saved mouse position
    static int         iCurMouseY;
    static D3DXVECTOR3 s_vDown;         // Button down vector

    // Current mouse position
    int iMouseX = GET_X_LPARAM(lParam);
    int iMouseY = GET_Y_LPARAM(lParam);

    switch (uMsg)
    {
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
        // Store off the position of the cursor when the button is pressed
        iCurMouseX = iMouseX;
        iCurMouseY = iMouseY;
        return TRUE;

    case WM_LBUTTONDOWN:
        // Start drag mode
        m_bDrag = TRUE;
        s_vDown = ScreenToVector(iMouseX, iMouseY);
        m_qDown = m_qNow;
        return TRUE;

    case WM_LBUTTONUP:
        // End drag mode
        m_bDrag = FALSE;
        return TRUE;

    case WM_MOUSEMOVE:
        // Drag object
        if (MK_LBUTTON & wParam)
        {
            if (m_bDrag)
            {
                // recompute m_qNow
                D3DXVECTOR3 vCur = ScreenToVector(iMouseX, iMouseY);
                D3DXQUATERNION qAxisToAxis;
                D3DXQuaternionAxisToAxis(&qAxisToAxis, &s_vDown, &vCur);
                m_qNow = m_qDown;
                m_qNow *= qAxisToAxis;
                D3DXMatrixRotationQuaternion(&m_matRotationDelta, &qAxisToAxis);
            }
            else
                D3DXMatrixIdentity(&m_matRotationDelta);
            D3DXMatrixRotationQuaternion(&m_matRotation, &m_qNow);
            m_bDrag = TRUE;
        }
        else if ((MK_RBUTTON & wParam) || (MK_MBUTTON & wParam))
        {
            // Normalize based on size of window and bounding sphere radius
            FLOAT fDeltaX = (iCurMouseX - iMouseX) * m_fRadiusTranslation / m_iWidth;
            FLOAT fDeltaY = (iCurMouseY - iMouseY) * m_fRadiusTranslation / m_iHeight;

            if (wParam & MK_RBUTTON)
            {
                D3DXMatrixTranslation(&m_matTranslationDelta, -2 * fDeltaX, 2 * fDeltaY, 0.0f);
                D3DXMatrixMultiply(&m_matTranslation, &m_matTranslation, &m_matTranslationDelta);
            }
            else  // wParam & MK_MBUTTON
            {
                D3DXMatrixTranslation(&m_matTranslationDelta, 0.0f, 0.0f, 5 * fDeltaY);
                D3DXMatrixMultiply(&m_matTranslation, &m_matTranslation, &m_matTranslationDelta);
            }

            // Store mouse coordinate
            iCurMouseX = iMouseX;
            iCurMouseY = iMouseY;
        }
        return TRUE;
    }

    return FALSE;
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
CD3DCamera::CD3DCamera()
{
    // Set attributes for the view matrix
    D3DXVECTOR3 vEyePt(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 1.0f);
    D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);
    SetViewParams(vEyePt, vLookatPt, vUpVec);

    // Set attributes for the projection matrix
    SetProjParams(D3DX_PI / 4, 1.0f, 1.0f, 1000.0f);
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DCamera::SetViewParams(D3DXVECTOR3& vEyePt, D3DXVECTOR3& vLookatPt,
    D3DXVECTOR3& vUpVec)
{
    // Set attributes for the view matrix
    m_vEyePt = vEyePt;
    m_vLookatPt = vLookatPt;
    m_vUpVec = vUpVec;
    D3DXVECTOR3 vDir = m_vLookatPt - m_vEyePt;
    D3DXVec3Normalize(&m_vView, &vDir);
    D3DXVec3Cross(&m_vCross, &m_vView, &m_vUpVec);

    D3DXMatrixLookAtLH(&m_matView, &m_vEyePt, &m_vLookatPt, &m_vUpVec);
    D3DXMatrixInverse(&m_matBillboard, NULL, &m_matView);
    m_matBillboard._41 = 0.0f;
    m_matBillboard._42 = 0.0f;
    m_matBillboard._43 = 0.0f;
}




//-----------------------------------------------------------------------------
// Name:
// Desc:
//-----------------------------------------------------------------------------
VOID CD3DCamera::SetProjParams(FLOAT fFOV, FLOAT fAspect, FLOAT fNearPlane,
    FLOAT fFarPlane)
{
    // Set attributes for the projection matrix
    m_fFOV = fFOV;
    m_fAspect = fAspect;
    m_fNearPlane = fNearPlane;
    m_fFarPlane = fFarPlane;

    D3DXMatrixPerspectiveFovLH(&m_matProj, fFOV, fAspect, fNearPlane, fFarPlane);
}

dxutil.h

#pragma once
//-----------------------------------------------------------------------------
// Miscellaneous helper functions
//-----------------------------------------------------------------------------
#define SAFE_DELETE(p)       { if(p) { delete (p);     (p)=NULL; } }
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p);   (p)=NULL; } }
#define SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } }


#ifndef UNDER_CE
//-----------------------------------------------------------------------------
// Name: DXUtil_GetDXSDKMediaPath() and DXUtil_FindMediaFile() 
// Desc: Returns the DirectX SDK path, as stored in the system registry
//       during the SDK install.
//-----------------------------------------------------------------------------
HRESULT DXUtil_GetDXSDKMediaPathCch(TCHAR* strDest, int cchDest);
HRESULT DXUtil_GetDXSDKMediaPathCb(TCHAR* szDest, int cbDest);
HRESULT DXUtil_FindMediaFileCch(TCHAR* strDestPath, int cchDest, TCHAR* strFilename);
HRESULT DXUtil_FindMediaFileCb(TCHAR* szDestPath, int cbDest, TCHAR* strFilename);
#endif // !UNDER_CE


//-----------------------------------------------------------------------------
// Name: DXUtil_Read*RegKey() and DXUtil_Write*RegKey()
// Desc: Helper functions to read/write a string registry key 
//-----------------------------------------------------------------------------
HRESULT DXUtil_WriteStringRegKey(HKEY hKey, TCHAR* strRegName, TCHAR* strValue);
HRESULT DXUtil_WriteIntRegKey(HKEY hKey, TCHAR* strRegName, DWORD dwValue);
HRESULT DXUtil_WriteGuidRegKey(HKEY hKey, TCHAR* strRegName, GUID guidValue);
HRESULT DXUtil_WriteBoolRegKey(HKEY hKey, TCHAR* strRegName, BOOL bValue);

HRESULT DXUtil_ReadStringRegKeyCch(HKEY hKey, TCHAR* strRegName, TCHAR* strDest, DWORD cchDest, TCHAR* strDefault);
HRESULT DXUtil_ReadStringRegKeyCb(HKEY hKey, TCHAR* strRegName, TCHAR* strDest, DWORD cbDest, TCHAR* strDefault);
HRESULT DXUtil_ReadIntRegKey(HKEY hKey, TCHAR* strRegName, DWORD* pdwValue, DWORD dwDefault);
HRESULT DXUtil_ReadGuidRegKey(HKEY hKey, TCHAR* strRegName, GUID* pGuidValue, GUID& guidDefault);
HRESULT DXUtil_ReadBoolRegKey(HKEY hKey, TCHAR* strRegName, BOOL* pbValue, BOOL bDefault);


//-----------------------------------------------------------------------------
// Name: DXUtil_Timer()
// Desc: Performs timer opertations. Use the following commands:
//          TIMER_RESET           - to reset the timer
//          TIMER_START           - to start the timer
//          TIMER_STOP            - to stop (or pause) the timer
//          TIMER_ADVANCE         - to advance the timer by 0.1 seconds
//          TIMER_GETABSOLUTETIME - to get the absolute system time
//          TIMER_GETAPPTIME      - to get the current time
//          TIMER_GETELAPSEDTIME  - to get the time that elapsed between 
//                                  TIMER_GETELAPSEDTIME calls
//-----------------------------------------------------------------------------
enum TIMER_COMMAND {
    TIMER_RESET, TIMER_START, TIMER_STOP, TIMER_ADVANCE,
    TIMER_GETABSOLUTETIME, TIMER_GETAPPTIME, TIMER_GETELAPSEDTIME
};
FLOAT __stdcall DXUtil_Timer(TIMER_COMMAND command);


//-----------------------------------------------------------------------------
// UNICODE support for converting between CHAR, TCHAR, and WCHAR strings
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertAnsiStringToWideCch(WCHAR* wstrDestination, const CHAR* strSource, int cchDestChar);
HRESULT DXUtil_ConvertWideStringToAnsiCch(CHAR* strDestination, const WCHAR* wstrSource, int cchDestChar);
HRESULT DXUtil_ConvertGenericStringToAnsiCch(CHAR* strDestination, const TCHAR* tstrSource, int cchDestChar);
HRESULT DXUtil_ConvertGenericStringToWideCch(WCHAR* wstrDestination, const TCHAR* tstrSource, int cchDestChar);
HRESULT DXUtil_ConvertAnsiStringToGenericCch(TCHAR* tstrDestination, const CHAR* strSource, int cchDestChar);
HRESULT DXUtil_ConvertWideStringToGenericCch(TCHAR* tstrDestination, const WCHAR* wstrSource, int cchDestChar);
HRESULT DXUtil_ConvertAnsiStringToWideCb(WCHAR* wstrDestination, const CHAR* strSource, int cbDestChar);
HRESULT DXUtil_ConvertWideStringToAnsiCb(CHAR* strDestination, const WCHAR* wstrSource, int cbDestChar);
HRESULT DXUtil_ConvertGenericStringToAnsiCb(CHAR* strDestination, const TCHAR* tstrSource, int cbDestChar);
HRESULT DXUtil_ConvertGenericStringToWideCb(WCHAR* wstrDestination, const TCHAR* tstrSource, int cbDestChar);
HRESULT DXUtil_ConvertAnsiStringToGenericCb(TCHAR* tstrDestination, const CHAR* strSource, int cbDestChar);
HRESULT DXUtil_ConvertWideStringToGenericCb(TCHAR* tstrDestination, const WCHAR* wstrSource, int cbDestChar);


//-----------------------------------------------------------------------------
// Readme functions
//-----------------------------------------------------------------------------
VOID DXUtil_LaunchReadme(HWND hWnd, TCHAR* strLoc = NULL);

//-----------------------------------------------------------------------------
// GUID to String converting 
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertGUIDToStringCch(const GUID* pGuidSrc, TCHAR* strDest, int cchDestChar);
HRESULT DXUtil_ConvertGUIDToStringCb(const GUID* pGuidSrc, TCHAR* strDest, int cbDestChar);
HRESULT DXUtil_ConvertStringToGUID(const TCHAR* strIn, GUID* pGuidOut);


//-----------------------------------------------------------------------------
// Debug printing support
// See dxerr9.h for more debug printing support
//-----------------------------------------------------------------------------
VOID    DXUtil_Trace(TCHAR* strMsg, ...);

#if defined(DEBUG) | defined(_DEBUG)
#define DXTRACE           DXUtil_Trace
#else
#define DXTRACE           sizeof
#endif


//-----------------------------------------------------------------------------
// Name: ArrayListType
// Desc: Indicates how data should be stored in a CArrayList
//-----------------------------------------------------------------------------
enum ArrayListType
{
    AL_VALUE,       // entry data is copied into the list
    AL_REFERENCE,   // entry pointers are copied into the list
};


//-----------------------------------------------------------------------------
// Name: CArrayList
// Desc: A growable array
//-----------------------------------------------------------------------------
class CArrayList
{
protected:
    ArrayListType m_ArrayListType;
    void* m_pData;
    UINT m_BytesPerEntry;
    UINT m_NumEntries;
    UINT m_NumEntriesAllocated;

public:
    CArrayList(ArrayListType Type, UINT BytesPerEntry = 0);
    ~CArrayList(void);
    HRESULT Add(void* pEntry);
    void Remove(UINT Entry);
    void* GetPtr(UINT Entry);
    UINT Count(void) { return m_NumEntries; }
    bool Contains(void* pEntryData);
    void Clear(void) { m_NumEntries = 0; }
};

//-----------------------------------------------------------------------------
// WinCE build support
//-----------------------------------------------------------------------------

#ifdef UNDER_CE

#define CheckDlgButton(hdialog, id, state) ::SendMessage(::GetDlgItem(hdialog, id), BM_SETCHECK, state, 0)
#define IsDlgButtonChecked(hdialog, id) ::SendMessage(::GetDlgItem(hdialog, id), BM_GETCHECK, 0L, 0L)
#define GETTIMESTAMP GetTickCount
#define _TWINCE(x) _T(x)

__inline int GetScrollPos(HWND hWnd, int nBar)
{
    SCROLLINFO si;
    memset(&si, 0, sizeof(si));
    si.cbSize = sizeof(si);
    si.fMask = SIF_POS;
    if (!GetScrollInfo(hWnd, nBar, &si))
    {
        return 0;
    }
    else
    {
        return si.nPos;
    }
}

#else // !UNDER_CE

#define GETTIMESTAMP timeGetTime
#define _TWINCE(x) x

#endif // UNDER_CE

dxutil.cpp

//-----------------------------------------------------------------------------
// File: DXUtil.cpp
//
// Desc: Shortcut macros and functions for using DX objects
//
// Copyright (c) Microsoft Corporation. All rights reserved
//-----------------------------------------------------------------------------
#ifndef STRICT
#define STRICT
#endif // !STRICT
#include <windows.h>
#include <mmsystem.h>
#include <tchar.h>
#include <stdio.h> 
#include <stdarg.h>
#include "DXUtil.h"


#ifdef UNICODE
typedef HINSTANCE(WINAPI* LPShellExecute)(HWND hwnd, LPCWSTR lpOperation, LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd);
#else
typedef HINSTANCE(WINAPI* LPShellExecute)(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd);
#endif


#ifndef UNDER_CE
//-----------------------------------------------------------------------------
// Name: DXUtil_GetDXSDKMediaPathCch()
// Desc: Returns the DirectX SDK media path
//       cchDest is the size in TCHARs of strDest.  Be careful not to 
//       pass in sizeof(strDest) on UNICODE builds.
//-----------------------------------------------------------------------------
HRESULT DXUtil_GetDXSDKMediaPathCch(TCHAR* strDest, int cchDest)
{
    if (strDest == NULL || cchDest < 1)
        return E_INVALIDARG;

    lstrcpy(strDest, TEXT(""));

    // Open the appropriate registry key
    HKEY  hKey;
    LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
        _T("Software\\Microsoft\\DirectX SDK"),
        0, KEY_READ, &hKey);
    if (ERROR_SUCCESS != lResult)
        return E_FAIL;

    DWORD dwType;
    DWORD dwSize = cchDest * sizeof(TCHAR);
    lResult = RegQueryValueEx(hKey, _T("DX9SDK Samples Path"), NULL,
        &dwType, (BYTE*)strDest, &dwSize);
    strDest[cchDest - 1] = 0; // RegQueryValueEx doesn't NULL term if buffer too small
    RegCloseKey(hKey);

    if (ERROR_SUCCESS != lResult)
        return E_FAIL;

    const TCHAR* strMedia = _T("\\Media\\");
    if (lstrlen(strDest) + lstrlen(strMedia) < cchDest)
        _tcscat_s(strDest, cchDest, strMedia);
    else
        return E_INVALIDARG;

    return S_OK;
}
#endif // !UNDER_CE



#ifndef UNDER_CE
//-----------------------------------------------------------------------------
// Name: DXUtil_FindMediaFileCch()
// Desc: Returns a valid path to a DXSDK media file
//       cchDest is the size in TCHARs of strDestPath.  Be careful not to 
//       pass in sizeof(strDest) on UNICODE builds.
//-----------------------------------------------------------------------------
HRESULT DXUtil_FindMediaFileCch(TCHAR* strDestPath, int cchDest, TCHAR* strFilename)
{
    HRESULT hr;
    HANDLE file;
    TCHAR* strShortNameTmp = NULL;
    TCHAR strShortName[MAX_PATH];
    int cchPath;

    if (NULL == strFilename || NULL == strDestPath || cchDest < 1)
        return E_INVALIDARG;

    lstrcpy(strDestPath, TEXT(""));
    lstrcpy(strShortName, TEXT(""));

    // Build full path name from strFileName (strShortName will be just the leaf filename)
    cchPath = GetFullPathName(strFilename, cchDest, strDestPath, &strShortNameTmp);
    if ((cchPath == 0) || (cchDest <= cchPath))
        return E_FAIL;
    if (strShortNameTmp)
        lstrcpyn(strShortName, strShortNameTmp, MAX_PATH);

    // first try to find the filename given a full path
    file = CreateFile(strDestPath, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);
    if (INVALID_HANDLE_VALUE != file)
    {
        CloseHandle(file);
        return S_OK;
    }

    // next try to find the filename in the current working directory (path stripped)
    file = CreateFile(strShortName, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);
    if (INVALID_HANDLE_VALUE != file)
    {
        _tcsncpy_s(strDestPath, cchDest, strShortName, cchDest);
        strDestPath[cchDest - 1] = 0; // _tcsncpy doesn't NULL term if it runs out of space
        CloseHandle(file);
        return S_OK;
    }

    // last, check if the file exists in the media directory
    if (FAILED(hr = DXUtil_GetDXSDKMediaPathCch(strDestPath, cchDest)))
        return hr;

    if (lstrlen(strDestPath) + lstrlen(strShortName) < cchDest)
        lstrcat(strDestPath, strShortName);
    else
        return E_INVALIDARG;

    file = CreateFile(strDestPath, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);
    if (INVALID_HANDLE_VALUE != file)
    {
        CloseHandle(file);
        return S_OK;
    }

    // On failure, just return the file as the path
    _tcsncpy_s(strDestPath, cchDest, strFilename, cchDest);
    strDestPath[cchDest - 1] = 0; // _tcsncpy doesn't NULL term if it runs out of space
    return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
#endif // !UNDER_CE




//-----------------------------------------------------------------------------
// Name: DXUtil_ReadStringRegKeyCch()
// Desc: Helper function to read a registry key string
//       cchDest is the size in TCHARs of strDest.  Be careful not to 
//       pass in sizeof(strDest) on UNICODE builds.
//-----------------------------------------------------------------------------
HRESULT DXUtil_ReadStringRegKeyCch(HKEY hKey, TCHAR* strRegName, TCHAR* strDest,
    DWORD cchDest, TCHAR* strDefault)
{
    DWORD dwType;
    DWORD cbDest = cchDest * sizeof(TCHAR);

    if (ERROR_SUCCESS != RegQueryValueEx(hKey, strRegName, 0, &dwType,
        (BYTE*)strDest, &cbDest))
    {
        _tcsncpy_s(strDest, cchDest, strDefault, cchDest);
        strDest[cchDest - 1] = 0;

        if (dwType != REG_SZ)
            return E_FAIL;

        return S_OK;
    }

    return E_FAIL;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_WriteStringRegKey()
// Desc: Helper function to write a registry key string
//-----------------------------------------------------------------------------
HRESULT DXUtil_WriteStringRegKey(HKEY hKey, TCHAR* strRegName,
    TCHAR* strValue)
{
    if (NULL == strValue)
        return E_INVALIDARG;

    DWORD cbValue = ((DWORD)_tcslen(strValue) + 1) * sizeof(TCHAR);

    if (ERROR_SUCCESS != RegSetValueEx(hKey, strRegName, 0, REG_SZ,
        (BYTE*)strValue, cbValue))
        return E_FAIL;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ReadIntRegKey()
// Desc: Helper function to read a registry key int
//-----------------------------------------------------------------------------
HRESULT DXUtil_ReadIntRegKey(HKEY hKey, TCHAR* strRegName, DWORD* pdwDest,
    DWORD dwDefault)
{
    DWORD dwType;
    DWORD dwLength = sizeof(DWORD);

    if (ERROR_SUCCESS != RegQueryValueEx(hKey, strRegName, 0, &dwType,
        (BYTE*)pdwDest, &dwLength))
    {
        *pdwDest = dwDefault;
        if (dwType != REG_DWORD)
            return E_FAIL;

        return S_OK;
    }

    return E_FAIL;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_WriteIntRegKey()
// Desc: Helper function to write a registry key int
//-----------------------------------------------------------------------------
HRESULT DXUtil_WriteIntRegKey(HKEY hKey, TCHAR* strRegName, DWORD dwValue)
{
    if (ERROR_SUCCESS != RegSetValueEx(hKey, strRegName, 0, REG_DWORD,
        (BYTE*)&dwValue, sizeof(DWORD)))
        return E_FAIL;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ReadBoolRegKey()
// Desc: Helper function to read a registry key BOOL
//-----------------------------------------------------------------------------
HRESULT DXUtil_ReadBoolRegKey(HKEY hKey, TCHAR* strRegName, BOOL* pbDest,
    BOOL bDefault)
{
    DWORD dwType;
    DWORD dwLength = sizeof(BOOL);

    if (ERROR_SUCCESS != RegQueryValueEx(hKey, strRegName, 0, &dwType,
        (BYTE*)pbDest, &dwLength))
    {
        *pbDest = bDefault;
        if (dwType != REG_DWORD)
            return E_FAIL;

        return S_OK;
    }

    return E_FAIL;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_WriteBoolRegKey()
// Desc: Helper function to write a registry key BOOL
//-----------------------------------------------------------------------------
HRESULT DXUtil_WriteBoolRegKey(HKEY hKey, TCHAR* strRegName, BOOL bValue)
{
    if (ERROR_SUCCESS != RegSetValueEx(hKey, strRegName, 0, REG_DWORD,
        (BYTE*)&bValue, sizeof(BOOL)))
        return E_FAIL;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ReadGuidRegKey()
// Desc: Helper function to read a registry key guid
//-----------------------------------------------------------------------------
HRESULT DXUtil_ReadGuidRegKey(HKEY hKey, TCHAR* strRegName, GUID* pGuidDest,
    GUID& guidDefault)
{
    DWORD dwType;
    DWORD dwLength = sizeof(GUID);

    if (ERROR_SUCCESS != RegQueryValueEx(hKey, strRegName, 0, &dwType,
        (LPBYTE)pGuidDest, &dwLength))
    {
        *pGuidDest = guidDefault;
        if (dwType != REG_BINARY)
            return E_FAIL;

        return S_OK;
    }

    return E_FAIL;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_WriteGuidRegKey()
// Desc: Helper function to write a registry key guid
//-----------------------------------------------------------------------------
HRESULT DXUtil_WriteGuidRegKey(HKEY hKey, TCHAR* strRegName, GUID guidValue)
{
    if (ERROR_SUCCESS != RegSetValueEx(hKey, strRegName, 0, REG_BINARY,
        (BYTE*)&guidValue, sizeof(GUID)))
        return E_FAIL;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_Timer()
// Desc: Performs timer opertations. Use the following commands:
//          TIMER_RESET           - to reset the timer
//          TIMER_START           - to start the timer
//          TIMER_STOP            - to stop (or pause) the timer
//          TIMER_ADVANCE         - to advance the timer by 0.1 seconds
//          TIMER_GETABSOLUTETIME - to get the absolute system time
//          TIMER_GETAPPTIME      - to get the current time
//          TIMER_GETELAPSEDTIME  - to get the time that elapsed between 
//                                  TIMER_GETELAPSEDTIME calls
//-----------------------------------------------------------------------------
FLOAT __stdcall DXUtil_Timer(TIMER_COMMAND command)
{
    static BOOL     m_bTimerInitialized = FALSE;
    static BOOL     m_bUsingQPF = FALSE;
    static BOOL     m_bTimerStopped = TRUE;
    static LONGLONG m_llQPFTicksPerSec = 0;

    // Initialize the timer
    if (FALSE == m_bTimerInitialized)
    {
        m_bTimerInitialized = TRUE;

        // Use QueryPerformanceFrequency() to get frequency of timer.  If QPF is
        // not supported, we will timeGetTime() which returns milliseconds.
        LARGE_INTEGER qwTicksPerSec;
        m_bUsingQPF = QueryPerformanceFrequency(&qwTicksPerSec);
        if (m_bUsingQPF)
            m_llQPFTicksPerSec = qwTicksPerSec.QuadPart;
    }

    if (m_bUsingQPF)
    {
        static LONGLONG m_llStopTime = 0;
        static LONGLONG m_llLastElapsedTime = 0;
        static LONGLONG m_llBaseTime = 0;
        double fTime;
        double fElapsedTime;
        LARGE_INTEGER qwTime;

        // Get either the current time or the stop time, depending
        // on whether we're stopped and what command was sent
        if (m_llStopTime != 0 && command != TIMER_START && command != TIMER_GETABSOLUTETIME)
            qwTime.QuadPart = m_llStopTime;
        else
            QueryPerformanceCounter(&qwTime);

        // Return the elapsed time
        if (command == TIMER_GETELAPSEDTIME)
        {
            fElapsedTime = (double)(qwTime.QuadPart - m_llLastElapsedTime) / (double)m_llQPFTicksPerSec;
            m_llLastElapsedTime = qwTime.QuadPart;
            return (FLOAT)fElapsedTime;
        }

        // Return the current time
        if (command == TIMER_GETAPPTIME)
        {
            double fAppTime = (double)(qwTime.QuadPart - m_llBaseTime) / (double)m_llQPFTicksPerSec;
            return (FLOAT)fAppTime;
        }

        // Reset the timer
        if (command == TIMER_RESET)
        {
            m_llBaseTime = qwTime.QuadPart;
            m_llLastElapsedTime = qwTime.QuadPart;
            m_llStopTime = 0;
            m_bTimerStopped = FALSE;
            return 0.0f;
        }

        // Start the timer
        if (command == TIMER_START)
        {
            if (m_bTimerStopped)
                m_llBaseTime += qwTime.QuadPart - m_llStopTime;
            m_llStopTime = 0;
            m_llLastElapsedTime = qwTime.QuadPart;
            m_bTimerStopped = FALSE;
            return 0.0f;
        }

        // Stop the timer
        if (command == TIMER_STOP)
        {
            if (!m_bTimerStopped)
            {
                m_llStopTime = qwTime.QuadPart;
                m_llLastElapsedTime = qwTime.QuadPart;
                m_bTimerStopped = TRUE;
            }
            return 0.0f;
        }

        // Advance the timer by 1/10th second
        if (command == TIMER_ADVANCE)
        {
            m_llStopTime += m_llQPFTicksPerSec / 10;
            return 0.0f;
        }

        if (command == TIMER_GETABSOLUTETIME)
        {
            fTime = qwTime.QuadPart / (double)m_llQPFTicksPerSec;
            return (FLOAT)fTime;
        }

        return -1.0f; // Invalid command specified
    }
    else
    {
        // Get the time using timeGetTime()
        static double m_fLastElapsedTime = 0.0;
        static double m_fBaseTime = 0.0;
        static double m_fStopTime = 0.0;
        double fTime;
        double fElapsedTime;

        // Get either the current time or the stop time, depending
        // on whether we're stopped and what command was sent
        if (m_fStopTime != 0.0 && command != TIMER_START && command != TIMER_GETABSOLUTETIME)
            fTime = m_fStopTime;
        else
            fTime = GETTIMESTAMP() * 0.001;

        // Return the elapsed time
        if (command == TIMER_GETELAPSEDTIME)
        {
            fElapsedTime = (double)(fTime - m_fLastElapsedTime);
            m_fLastElapsedTime = fTime;
            return (FLOAT)fElapsedTime;
        }

        // Return the current time
        if (command == TIMER_GETAPPTIME)
        {
            return (FLOAT)(fTime - m_fBaseTime);
        }

        // Reset the timer
        if (command == TIMER_RESET)
        {
            m_fBaseTime = fTime;
            m_fLastElapsedTime = fTime;
            m_fStopTime = 0;
            m_bTimerStopped = FALSE;
            return 0.0f;
        }

        // Start the timer
        if (command == TIMER_START)
        {
            if (m_bTimerStopped)
                m_fBaseTime += fTime - m_fStopTime;
            m_fStopTime = 0.0f;
            m_fLastElapsedTime = fTime;
            m_bTimerStopped = FALSE;
            return 0.0f;
        }

        // Stop the timer
        if (command == TIMER_STOP)
        {
            if (!m_bTimerStopped)
            {
                m_fStopTime = fTime;
                m_fLastElapsedTime = fTime;
                m_bTimerStopped = TRUE;
            }
            return 0.0f;
        }

        // Advance the timer by 1/10th second
        if (command == TIMER_ADVANCE)
        {
            m_fStopTime += 0.1f;
            return 0.0f;
        }

        if (command == TIMER_GETABSOLUTETIME)
        {
            return (FLOAT)fTime;
        }

        return -1.0f; // Invalid command specified
    }
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertAnsiStringToWideCch()
// Desc: This is a UNICODE conversion utility to convert a CHAR string into a
//       WCHAR string. 
//       cchDestChar is the size in TCHARs of wstrDestination.  Be careful not to 
//       pass in sizeof(strDest) 
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertAnsiStringToWideCch(WCHAR* wstrDestination, const CHAR* strSource,
    int cchDestChar)
{
    if (wstrDestination == NULL || strSource == NULL || cchDestChar < 1)
        return E_INVALIDARG;

    int nResult = MultiByteToWideChar(CP_ACP, 0, strSource, -1,
        wstrDestination, cchDestChar);
    wstrDestination[cchDestChar - 1] = 0;

    if (nResult == 0)
        return E_FAIL;
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertWideStringToAnsi()
// Desc: This is a UNICODE conversion utility to convert a WCHAR string into a
//       CHAR string. 
//       cchDestChar is the size in TCHARs of strDestination
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertWideStringToAnsiCch(CHAR* strDestination, const WCHAR* wstrSource,
    int cchDestChar)
{
    if (strDestination == NULL || wstrSource == NULL || cchDestChar < 1)
        return E_INVALIDARG;

    int nResult = WideCharToMultiByte(CP_ACP, 0, wstrSource, -1, strDestination,
        cchDestChar * sizeof(CHAR), NULL, NULL);
    strDestination[cchDestChar - 1] = 0;

    if (nResult == 0)
        return E_FAIL;
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertGenericStringToAnsi()
// Desc: This is a UNICODE conversion utility to convert a TCHAR string into a
//       CHAR string. 
//       cchDestChar is the size in TCHARs of strDestination
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertGenericStringToAnsiCch(CHAR* strDestination, const TCHAR* tstrSource,
    int cchDestChar)
{
    if (strDestination == NULL || tstrSource == NULL || cchDestChar < 1)
        return E_INVALIDARG;

#ifdef _UNICODE
    return DXUtil_ConvertWideStringToAnsiCch(strDestination, tstrSource, cchDestChar);
#else
    strncpy_s(strDestination, cchDestChar, tstrSource, cchDestChar);
    strDestination[cchDestChar - 1] = '\0';
    return S_OK;
#endif   
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertGenericStringToWide()
// Desc: This is a UNICODE conversion utility to convert a TCHAR string into a
//       WCHAR string. 
//       cchDestChar is the size in TCHARs of wstrDestination.  Be careful not to 
//       pass in sizeof(strDest) 
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertGenericStringToWideCch(WCHAR* wstrDestination, const TCHAR* tstrSource,
    int cchDestChar)
{
    if (wstrDestination == NULL || tstrSource == NULL || cchDestChar < 1)
        return E_INVALIDARG;

#ifdef _UNICODE
    wcsncpy(wstrDestination, tstrSource, cchDestChar);
    wstrDestination[cchDestChar - 1] = L'\0';
    return S_OK;
#else
    return DXUtil_ConvertAnsiStringToWideCch(wstrDestination, tstrSource, cchDestChar);
#endif    
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertAnsiStringToGeneric()
// Desc: This is a UNICODE conversion utility to convert a CHAR string into a
//       TCHAR string. 
//       cchDestChar is the size in TCHARs of tstrDestination.  Be careful not to 
//       pass in sizeof(strDest) on UNICODE builds
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertAnsiStringToGenericCch(TCHAR* tstrDestination, const CHAR* strSource,
    int cchDestChar)
{
    if (tstrDestination == NULL || strSource == NULL || cchDestChar < 1)
        return E_INVALIDARG;

#ifdef _UNICODE
    return DXUtil_ConvertAnsiStringToWideCch(tstrDestination, strSource, cchDestChar);
#else
    strncpy_s(tstrDestination, cchDestChar, strSource, cchDestChar);
    tstrDestination[cchDestChar - 1] = '\0';
    return S_OK;
#endif    
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertAnsiStringToGeneric()
// Desc: This is a UNICODE conversion utility to convert a WCHAR string into a
//       TCHAR string. 
//       cchDestChar is the size in TCHARs of tstrDestination.  Be careful not to 
//       pass in sizeof(strDest) on UNICODE builds
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertWideStringToGenericCch(TCHAR* tstrDestination, const WCHAR* wstrSource,
    int cchDestChar)
{
    if (tstrDestination == NULL || wstrSource == NULL || cchDestChar < 1)
        return E_INVALIDARG;

#ifdef _UNICODE
    wcsncpy(tstrDestination, wstrSource, cchDestChar);
    tstrDestination[cchDestChar - 1] = L'\0';
    return S_OK;
#else
    return DXUtil_ConvertWideStringToAnsiCch(tstrDestination, wstrSource, cchDestChar);
#endif
}





//-----------------------------------------------------------------------------
// Name: DXUtil_LaunchReadme()
// Desc: Finds and opens the readme.txt for this sample
//-----------------------------------------------------------------------------
VOID DXUtil_LaunchReadme(HWND hWnd, TCHAR* strLoc)
{

#ifdef UNDER_CE
    // This is not available on PocketPC
    MessageBox(hWnd, TEXT("For operating instructions, please open the ")
        TEXT("readme.txt file included with the project."),
        TEXT("DirectX SDK Sample"), MB_ICONWARNING | MB_OK);

    return;
#else 

    bool bSuccess = false;
    bool bFound = false;
    TCHAR strReadmePath[1024];
    TCHAR strExeName[MAX_PATH];
    TCHAR strExePath[MAX_PATH];
    TCHAR strSamplePath[MAX_PATH];
    TCHAR* strLastSlash = NULL;

    lstrcpy(strReadmePath, TEXT(""));
    lstrcpy(strExePath, TEXT(""));
    lstrcpy(strExeName, TEXT(""));
    lstrcpy(strSamplePath, TEXT(""));

    // If the user provided a location for the readme, check there first.
    if (strLoc)
    {
        HKEY  hKey;
        LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
            _T("Software\\Microsoft\\DirectX SDK"),
            0, KEY_READ, &hKey);
        if (ERROR_SUCCESS == lResult)
        {
            DWORD dwType;
            DWORD dwSize = MAX_PATH * sizeof(TCHAR);
            lResult = RegQueryValueEx(hKey, _T("DX9SDK Samples Path"), NULL,
                &dwType, (BYTE*)strSamplePath, &dwSize);
            strSamplePath[MAX_PATH - 1] = 0; // RegQueryValueEx doesn't NULL term if buffer too small

            if (ERROR_SUCCESS == lResult)
            {
                _sntprintf_s(strReadmePath, 1023, TEXT("%s\\C++\\%s\\readme.txt"),
                    strSamplePath, strLoc);
                strReadmePath[1023] = 0;

                if (GetFileAttributes(strReadmePath) != 0xFFFFFFFF)
                    bFound = TRUE;
            }
        }

        RegCloseKey(hKey);
    }

    // Get the exe name, and exe path
    GetModuleFileName(NULL, strExePath, MAX_PATH);
    strExePath[MAX_PATH - 1] = 0;

    strLastSlash = _tcsrchr(strExePath, TEXT('\\'));
    if (strLastSlash)
    {
        _tcsncpy_s(strExeName, &strLastSlash[1], MAX_PATH);
        strExeName[MAX_PATH - 1] = 0;

        // Chop the exe name from the exe path
        *strLastSlash = 0;

        // Chop the .exe from the exe name
        strLastSlash = _tcsrchr(strExeName, TEXT('.'));
        if (strLastSlash)
            *strLastSlash = 0;
    }

    if (!bFound)
    {
        // Search in "%EXE_DIR%\..\%EXE_NAME%".  This matchs the DirectX SDK layout
        _tcscpy_s(strReadmePath, strExePath);

        strLastSlash = _tcsrchr(strReadmePath, TEXT('\\'));
        if (strLastSlash)
            *strLastSlash = 0;
        lstrcat(strReadmePath, TEXT("\\"));
        lstrcat(strReadmePath, strExeName);
        lstrcat(strReadmePath, TEXT("\\readme.txt"));
        if (GetFileAttributes(strReadmePath) != 0xFFFFFFFF)
            bFound = TRUE;
    }

    if (!bFound)
    {
        // Search in "%EXE_DIR%\"
        _tcscpy_s(strReadmePath, strExePath);
        lstrcat(strReadmePath, TEXT("\\readme.txt"));
        if (GetFileAttributes(strReadmePath) != 0xFFFFFFFF)
            bFound = TRUE;
    }

    if (!bFound)
    {
        // Search in "%EXE_DIR%\.."
        _tcscpy_s(strReadmePath, strExePath);
        strLastSlash = _tcsrchr(strReadmePath, TEXT('\\'));
        if (strLastSlash)
            *strLastSlash = 0;
        lstrcat(strReadmePath, TEXT("\\readme.txt"));
        if (GetFileAttributes(strReadmePath) != 0xFFFFFFFF)
            bFound = TRUE;
    }

    if (!bFound)
    {
        // Search in "%EXE_DIR%\..\.."
        _tcscpy_s(strReadmePath, strExePath);
        strLastSlash = _tcsrchr(strReadmePath, TEXT('\\'));
        if (strLastSlash)
            *strLastSlash = 0;
        strLastSlash = _tcsrchr(strReadmePath, TEXT('\\'));
        if (strLastSlash)
            *strLastSlash = 0;
        lstrcat(strReadmePath, TEXT("\\readme.txt"));
        if (GetFileAttributes(strReadmePath) != 0xFFFFFFFF)
            bFound = TRUE;
    }

    if (bFound)
    {
        // GetProcAddress for ShellExecute, so we don't have to include shell32.lib 
        // in every project that uses dxutil.cpp
        LPShellExecute pShellExecute = NULL;
        HINSTANCE hInstShell32 = LoadLibrary(TEXT("shell32.dll"));
        if (hInstShell32 != NULL)
        {
#ifdef UNICODE
            pShellExecute = (LPShellExecute)GetProcAddress(hInstShell32, _TWINCE("ShellExecuteW"));
#else
            pShellExecute = (LPShellExecute)GetProcAddress(hInstShell32, _TWINCE("ShellExecuteA"));
#endif
            if (pShellExecute != NULL)
            {
                if (pShellExecute(hWnd, TEXT("open"), strReadmePath, NULL, NULL, SW_SHOW) > (HINSTANCE)32)
                    bSuccess = true;
            }

            FreeLibrary(hInstShell32);
        }
    }

    if (!bSuccess)
    {
        // Tell the user that the readme couldn't be opened
        MessageBox(hWnd, TEXT("Could not find readme.txt"),
            TEXT("DirectX SDK Sample"), MB_ICONWARNING | MB_OK);
    }

#endif // UNDER_CE
}





//-----------------------------------------------------------------------------
// Name: DXUtil_Trace()
// Desc: Outputs to the debug stream a formatted string with a variable-
//       argument list.
//-----------------------------------------------------------------------------
VOID DXUtil_Trace(TCHAR* strMsg, ...)
{
#if defined(DEBUG) | defined(_DEBUG)
    TCHAR strBuffer[512];

    va_list args;
    va_start(args, strMsg);
    _vsntprintf_s(strBuffer, 512, strMsg, args);
    va_end(args);

    OutputDebugString(strBuffer);
#else
    UNREFERENCED_PARAMETER(strMsg);
#endif
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertStringToGUID()
// Desc: Converts a string to a GUID
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertStringToGUID(const TCHAR* strSrc, GUID* pGuidDest)
{
    UINT aiTmp[10];

    if (_stscanf_s(strSrc, TEXT("{%8X-%4X-%4X-%2X%2X-%2X%2X%2X%2X%2X%2X}"),
        &pGuidDest->Data1,
        &aiTmp[0], &aiTmp[1],
        &aiTmp[2], &aiTmp[3],
        &aiTmp[4], &aiTmp[5],
        &aiTmp[6], &aiTmp[7],
        &aiTmp[8], &aiTmp[9]) != 11)
    {
        ZeroMemory(pGuidDest, sizeof(GUID));
        return E_FAIL;
    }
    else
    {
        pGuidDest->Data2 = (USHORT)aiTmp[0];
        pGuidDest->Data3 = (USHORT)aiTmp[1];
        pGuidDest->Data4[0] = (BYTE)aiTmp[2];
        pGuidDest->Data4[1] = (BYTE)aiTmp[3];
        pGuidDest->Data4[2] = (BYTE)aiTmp[4];
        pGuidDest->Data4[3] = (BYTE)aiTmp[5];
        pGuidDest->Data4[4] = (BYTE)aiTmp[6];
        pGuidDest->Data4[5] = (BYTE)aiTmp[7];
        pGuidDest->Data4[6] = (BYTE)aiTmp[8];
        pGuidDest->Data4[7] = (BYTE)aiTmp[9];
        return S_OK;
    }
}




//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertGUIDToStringCch()
// Desc: Converts a GUID to a string 
//       cchDestChar is the size in TCHARs of strDest.  Be careful not to 
//       pass in sizeof(strDest) on UNICODE builds
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertGUIDToStringCch(const GUID* pGuidSrc, TCHAR* strDest, int cchDestChar)
{
    int nResult = _sntprintf_s(strDest, cchDestChar, cchDestChar, TEXT("{%0.8X-%0.4X-%0.4X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X}"),
        pGuidSrc->Data1, pGuidSrc->Data2, pGuidSrc->Data3,
        pGuidSrc->Data4[0], pGuidSrc->Data4[1],
        pGuidSrc->Data4[2], pGuidSrc->Data4[3],
        pGuidSrc->Data4[4], pGuidSrc->Data4[5],
        pGuidSrc->Data4[6], pGuidSrc->Data4[7]);

    if (nResult < 0)
        return E_FAIL;
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CArrayList constructor
// Desc: 
//-----------------------------------------------------------------------------
CArrayList::CArrayList(ArrayListType Type, UINT BytesPerEntry)
{
    if (Type == AL_REFERENCE)
        BytesPerEntry = sizeof(void*);
    m_ArrayListType = Type;
    m_pData = NULL;
    m_BytesPerEntry = BytesPerEntry;
    m_NumEntries = 0;
    m_NumEntriesAllocated = 0;
}



//-----------------------------------------------------------------------------
// Name: CArrayList destructor
// Desc: 
//-----------------------------------------------------------------------------
CArrayList::~CArrayList(void)
{
    if (m_pData != NULL)
        delete[] m_pData;
}




//-----------------------------------------------------------------------------
// Name: CArrayList::Add
// Desc: Adds pEntry to the list.
//-----------------------------------------------------------------------------
HRESULT CArrayList::Add(void* pEntry)
{
    if (m_BytesPerEntry == 0)
        return E_FAIL;
    if (m_pData == NULL || m_NumEntries + 1 > m_NumEntriesAllocated)
    {
        void* pDataNew;
        UINT NumEntriesAllocatedNew;
        if (m_NumEntriesAllocated == 0)
            NumEntriesAllocatedNew = 16;
        else
            NumEntriesAllocatedNew = m_NumEntriesAllocated * 2;
        pDataNew = new BYTE[NumEntriesAllocatedNew * m_BytesPerEntry];
        if (pDataNew == NULL)
            return E_OUTOFMEMORY;
        if (m_pData != NULL)
        {
            CopyMemory(pDataNew, m_pData, m_NumEntries * m_BytesPerEntry);
            delete[] m_pData;
        }
        m_pData = pDataNew;
        m_NumEntriesAllocated = NumEntriesAllocatedNew;
    }

    if (m_ArrayListType == AL_VALUE)
        CopyMemory((BYTE*)m_pData + (m_NumEntries * m_BytesPerEntry), pEntry, m_BytesPerEntry);
    else
        *(((void**)m_pData) + m_NumEntries) = pEntry;
    m_NumEntries++;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CArrayList::Remove
// Desc: Remove the item at Entry in the list, and collapse the array. 
//-----------------------------------------------------------------------------
void CArrayList::Remove(UINT Entry)
{
    // Decrement count
    m_NumEntries--;

    // Find the entry address
    BYTE* pData = (BYTE*)m_pData + (Entry * m_BytesPerEntry);

    // Collapse the array
    MoveMemory(pData, pData + m_BytesPerEntry, (m_NumEntries - Entry) * m_BytesPerEntry);
}




//-----------------------------------------------------------------------------
// Name: CArrayList::GetPtr
// Desc: Returns a pointer to the Entry'th entry in the list.
//-----------------------------------------------------------------------------
void* CArrayList::GetPtr(UINT Entry)
{
    if (m_ArrayListType == AL_VALUE)
        return (BYTE*)m_pData + (Entry * m_BytesPerEntry);
    else
        return *(((void**)m_pData) + Entry);
}




//-----------------------------------------------------------------------------
// Name: CArrayList::Contains
// Desc: Returns whether the list contains an entry identical to the 
//       specified entry data.
//-----------------------------------------------------------------------------
bool CArrayList::Contains(void* pEntryData)
{
    for (UINT iEntry = 0; iEntry < m_NumEntries; iEntry++)
    {
        if (m_ArrayListType == AL_VALUE)
        {
            if (memcmp(GetPtr(iEntry), pEntryData, m_BytesPerEntry) == 0)
                return true;
        }
        else
        {
            if (GetPtr(iEntry) == pEntryData)
                return true;
        }
    }
    return false;
}




//-----------------------------------------------------------------------------
// Name: BYTE helper functions
// Desc: cchDestChar is the size in BYTEs of strDest.  Be careful not to 
//       pass use sizeof() if the strDest is a string pointer.  
//       eg.
//       TCHAR* sz = new TCHAR[100]; // sizeof(sz)  == 4
//       TCHAR sz2[100];             // sizeof(sz2) == 200
//-----------------------------------------------------------------------------
HRESULT DXUtil_ConvertAnsiStringToWideCb(WCHAR* wstrDestination, const CHAR* strSource, int cbDestChar)
{
    return DXUtil_ConvertAnsiStringToWideCch(wstrDestination, strSource, cbDestChar / sizeof(WCHAR));
}

HRESULT DXUtil_ConvertWideStringToAnsiCb(CHAR* strDestination, const WCHAR* wstrSource, int cbDestChar)
{
    return DXUtil_ConvertWideStringToAnsiCch(strDestination, wstrSource, cbDestChar / sizeof(CHAR));
}

HRESULT DXUtil_ConvertGenericStringToAnsiCb(CHAR* strDestination, const TCHAR* tstrSource, int cbDestChar)
{
    return DXUtil_ConvertGenericStringToAnsiCch(strDestination, tstrSource, cbDestChar / sizeof(CHAR));
}

HRESULT DXUtil_ConvertGenericStringToWideCb(WCHAR* wstrDestination, const TCHAR* tstrSource, int cbDestChar)
{
    return DXUtil_ConvertGenericStringToWideCch(wstrDestination, tstrSource, cbDestChar / sizeof(WCHAR));
}

HRESULT DXUtil_ConvertAnsiStringToGenericCb(TCHAR* tstrDestination, const CHAR* strSource, int cbDestChar)
{
    return DXUtil_ConvertAnsiStringToGenericCch(tstrDestination, strSource, cbDestChar / sizeof(TCHAR));
}

HRESULT DXUtil_ConvertWideStringToGenericCb(TCHAR* tstrDestination, const WCHAR* wstrSource, int cbDestChar)
{
    return DXUtil_ConvertWideStringToGenericCch(tstrDestination, wstrSource, cbDestChar / sizeof(TCHAR));
}

HRESULT DXUtil_ReadStringRegKeyCb(HKEY hKey, TCHAR* strRegName, TCHAR* strDest, DWORD cbDest, TCHAR* strDefault)
{
    return DXUtil_ReadStringRegKeyCch(hKey, strRegName, strDest, cbDest / sizeof(TCHAR), strDefault);
}

HRESULT DXUtil_ConvertGUIDToStringCb(const GUID* pGuidSrc, TCHAR* strDest, int cbDestChar)
{
    return DXUtil_ConvertGUIDToStringCch(pGuidSrc, strDest, cbDestChar / sizeof(TCHAR));
}

#ifndef UNDER_CE
HRESULT DXUtil_GetDXSDKMediaPathCb(TCHAR* szDest, int cbDest)
{
    return DXUtil_GetDXSDKMediaPathCch(szDest, cbDest / sizeof(TCHAR));
}

HRESULT DXUtil_FindMediaFileCb(TCHAR* szDestPath, int cbDest, TCHAR* strFilename)
{
    return DXUtil_FindMediaFileCch(szDestPath, cbDest / sizeof(TCHAR), strFilename);
}
#endif // !UNDER_CE

fps.h

#pragma once
#include "d3dfont.h"
#include <sstream>

class FPSCounter
{
public:
	FPSCounter(IDirect3DDevice9* device);
	~FPSCounter();

	bool render(D3DCOLOR color, float timeDelta);
private:
	IDirect3DDevice9* _device;

	CD3DFont* _font;
	DWORD     _frameCnt;
	float     _timeElapsed;
	float     _fps;
	char      _fpsString[9];

};

fps.cpp

#include "fps.h"
#include <cstdio>

FPSCounter::FPSCounter(IDirect3DDevice9* device)
{
	_device = device;

	_font = new CD3DFont("Times New Roman", 24, 0);
	_font->InitDeviceObjects(_device);
	_font->RestoreDeviceObjects();

	_frameCnt = 0;
	_timeElapsed = 0.0f;
	_fps = 0.0f;
}

FPSCounter::~FPSCounter()
{
	if (_font)
	{
		_font->InvalidateDeviceObjects();
		_font->DeleteDeviceObjects();
		delete _font;
	}
}

bool FPSCounter::render(D3DCOLOR color, float timeDelta)
{
	if (_font)
	{
		_frameCnt++;

		_timeElapsed += timeDelta;

		if (_timeElapsed >= 1.0f)
		{
			_fps = (float)_frameCnt / _timeElapsed;

			std::stringstream ss;
			//std::string fpsStr;
			ss << _fps;
			ss >> _fpsString;
			//strncpy_s(_fpsString, 9, fpsStr.c_str(), 9);
			//sprintf_s(_fpsString, 8, "%f", _fps);
			_fpsString[8] = '\0'; // mark end of string

			_timeElapsed = 0.0f;
			_frameCnt = 0;
		}

		_font->DrawText(20, 20, color, _fpsString);
	}
	return true;
}

terrain.h

#pragma once
#include "d3dUtility.h"
#include <string>
#include <vector>

class Terrain
{
public:
	Terrain(
		IDirect3DDevice9* device,
		std::string heightmapFileName,
		int numVertsPerRow,
		int numVertsPerCol,
		int cellSpacing,    // space between cells
		float heightScale);

	~Terrain();

	int  getHeightmapEntry(int row, int col);
	void setHeightmapEntry(int row, int col, int value);

	float getHeight(float x, float z);

	bool  loadTexture(std::string fileName);
	bool  genTexture(D3DXVECTOR3* directionToLight);
	bool  draw(D3DXMATRIX* world, bool drawTris);

private:
	IDirect3DDevice9* _device;
	IDirect3DTexture9* _tex;
	IDirect3DVertexBuffer9* _vb;
	IDirect3DIndexBuffer9* _ib;

	int _numVertsPerRow;
	int _numVertsPerCol;
	int _cellSpacing;

	int _numCellsPerRow;
	int _numCellsPerCol;
	int _width;
	int _depth;
	int _numVertices;
	int _numTriangles;

	float _heightScale;

	std::vector<int> _heightmap;

	// helper methods
	bool  readRawFile(std::string fileName);
	bool  computeVertices();
	bool  computeIndices();
	bool  lightTerrain(D3DXVECTOR3* directionToLight);
	float computeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLight);

	struct TerrainVertex
	{
		TerrainVertex() {}
		TerrainVertex(float x, float y, float z, float u, float v)
		{
			_x = x; _y = y; _z = z; _u = u; _v = v;
		}
		float _x, _y, _z;
		float _u, _v;

		static const DWORD FVF;
	};
};

terrain.cpp

#include "terrain.h"
#include <fstream>
#include <cmath>

const DWORD Terrain::TerrainVertex::FVF = D3DFVF_XYZ | D3DFVF_TEX1;

Terrain::Terrain(IDirect3DDevice9* device,
	std::string heightmapFileName,
	int numVertsPerRow,
	int numVertsPerCol,
	int cellSpacing,
	float heightScale)
{
	_device = device;
	_numVertsPerRow = numVertsPerRow;
	_numVertsPerCol = numVertsPerCol;
	_cellSpacing = cellSpacing;

	_numCellsPerRow = _numVertsPerRow - 1;
	_numCellsPerCol = _numVertsPerCol - 1;

	_width = _numCellsPerRow * _cellSpacing;
	_depth = _numCellsPerCol * _cellSpacing;

	_numVertices = _numVertsPerRow * _numVertsPerCol;
	_numTriangles = _numCellsPerRow * _numCellsPerCol * 2;

	_heightScale = heightScale;

	// load heightmap
	if (!readRawFile(heightmapFileName))
	{
		::MessageBox(0, "readRawFile - FAILED", 0, 0);
		::PostQuitMessage(0);
	}

	// scale heights
	for (int i = 0; i < _heightmap.size(); i++)
		_heightmap[i] *= heightScale;

	// compute the vertices
	if (!computeVertices())
	{
		::MessageBox(0, "computeVertices - FAILED", 0, 0);
		::PostQuitMessage(0);
	}

	// compute the indices
	if (!computeIndices())
	{
		::MessageBox(0, "computeIndices - FAILED", 0, 0);
		::PostQuitMessage(0);
	}
}

Terrain::~Terrain()
{
	d3d::Release<IDirect3DVertexBuffer9*>(_vb);
	d3d::Release<IDirect3DIndexBuffer9*>(_ib);
	d3d::Release<IDirect3DTexture9*>(_tex);
}

int Terrain::getHeightmapEntry(int row, int col)
{
	return _heightmap[row * _numVertsPerRow + col];
}

void Terrain::setHeightmapEntry(int row, int col, int value)
{
	_heightmap[row * _numVertsPerRow + col] = value;
}

bool Terrain::computeVertices()
{
	HRESULT hr = 0;

	hr = _device->CreateVertexBuffer(
		_numVertices * sizeof(TerrainVertex),
		D3DUSAGE_WRITEONLY,
		TerrainVertex::FVF,
		D3DPOOL_MANAGED,
		&_vb,
		0);

	if (FAILED(hr))
		return false;

	// coordinates to start generating vertices at
	int startX = -_width / 2;
	int startZ = _depth / 2;

	// coordinates to end generating vertices at
	int endX = _width / 2;
	int endZ = -_depth / 2;

	// compute the increment size of the texture coordinates
	// from one vertex to the next.
	float uCoordIncrementSize = 1.0f / (float)_numCellsPerRow;
	float vCoordIncrementSize = 1.0f / (float)_numCellsPerCol;

	TerrainVertex* v = 0;
	_vb->Lock(0, 0, (void**)&v, 0);

	int i = 0;
	for (int z = startZ; z >= endZ; z -= _cellSpacing)
	{
		int j = 0;
		for (int x = startX; x <= endX; x += _cellSpacing)
		{
			// compute the correct index into the vertex buffer and heightmap
			// based on where we are in the nested loop.
			int index = i * _numVertsPerRow + j;

			v[index] = TerrainVertex(
				(float)x,
				(float)_heightmap[index],
				(float)z,
				(float)j * uCoordIncrementSize,
				(float)i * vCoordIncrementSize);

			j++; // next column
		}
		i++; // next row
	}

	_vb->Unlock();

	return true;
}

bool Terrain::computeIndices()
{
	HRESULT hr = 0;

	hr = _device->CreateIndexBuffer(
		_numTriangles * 3 * sizeof(WORD), // 3 indices per triangle
		D3DUSAGE_WRITEONLY,
		D3DFMT_INDEX16,
		D3DPOOL_MANAGED,
		&_ib,
		0);

	if (FAILED(hr))
		return false;

	WORD* indices = 0;
	_ib->Lock(0, 0, (void**)&indices, 0);

	// index to start of a group of 6 indices that describe the
	// two triangles that make up a quad
	int baseIndex = 0;

	// loop through and compute the triangles of each quad
	for (int i = 0; i < _numCellsPerCol; i++)
	{
		for (int j = 0; j < _numCellsPerRow; j++)
		{
			indices[baseIndex] = i * _numVertsPerRow + j;
			indices[baseIndex + 1] = i * _numVertsPerRow + j + 1;
			indices[baseIndex + 2] = (i + 1) * _numVertsPerRow + j;

			indices[baseIndex + 3] = (i + 1) * _numVertsPerRow + j;
			indices[baseIndex + 4] = i * _numVertsPerRow + j + 1;
			indices[baseIndex + 5] = (i + 1) * _numVertsPerRow + j + 1;

			// next quad
			baseIndex += 6;
		}
	}

	_ib->Unlock();

	return true;
}

bool Terrain::loadTexture(std::string fileName)
{
	HRESULT hr = 0;

	hr = D3DXCreateTextureFromFile(
		_device,
		fileName.c_str(),
		&_tex);

	if (FAILED(hr))
		return false;

	return true;
}

bool Terrain::genTexture(D3DXVECTOR3* directionToLight)
{
	// Method fills the top surface of a texture procedurally.  Then
	// lights the top surface.  Finally, it fills the other mipmap
	// surfaces based on the top surface data using D3DXFilterTexture.

	HRESULT hr = 0;

	// texel for each quad cell
	int texWidth = _numCellsPerRow;
	int texHeight = _numCellsPerCol;

	// create an empty texture
	hr = D3DXCreateTexture(
		_device,
		texWidth, texHeight,
		0, // create a complete mipmap chain
		0, // usage
		D3DFMT_X8R8G8B8,// 32 bit XRGB format
		D3DPOOL_MANAGED, &_tex);

	if (FAILED(hr))
		return false;

	D3DSURFACE_DESC textureDesc;
	_tex->GetLevelDesc(0 /*level*/, &textureDesc);

	// make sure we got the requested format because our code 
	// that fills the texture is hard coded to a 32 bit pixel depth.
	if (textureDesc.Format != D3DFMT_X8R8G8B8)
		return false;

	D3DLOCKED_RECT lockedRect;
	_tex->LockRect(0/*lock top surface*/, &lockedRect,
		0 /* lock entire tex*/, 0/*flags*/);

	DWORD* imageData = (DWORD*)lockedRect.pBits;
	for (int i = 0; i < texHeight; i++)
	{
		for (int j = 0; j < texWidth; j++)
		{
			D3DXCOLOR c;

			// get height of upper left vertex of quad.
			float height = (float)getHeightmapEntry(i, j) / _heightScale;

			if ((height) < 42.5f) 		 c = d3d::BEACH_SAND;
			else if ((height) < 85.0f)	 c = d3d::LIGHT_YELLOW_GREEN;
			else if ((height) < 127.5f) c = d3d::PUREGREEN;
			else if ((height) < 170.0f) c = d3d::DARK_YELLOW_GREEN;
			else if ((height) < 212.5f) c = d3d::DARKBROWN;
			else	                     c = d3d::WHITE;

			// fill locked data, note we divide the pitch by four because the
			// pitch is given in bytes and there are 4 bytes per DWORD.
			imageData[i * lockedRect.Pitch / 4 + j] = (D3DCOLOR)c;
		}
	}

	_tex->UnlockRect(0);

	if (!lightTerrain(directionToLight))
	{
		::MessageBox(0, "lightTerrain() - FAILED", 0, 0);
		return false;
	}

	hr = D3DXFilterTexture(
		_tex,
		0, // default palette
		0, // use top level as source level
		D3DX_DEFAULT); // default filter

	if (FAILED(hr))
	{
		::MessageBox(0, "D3DXFilterTexture() - FAILED", 0, 0);
		return false;
	}

	return true;
}

bool Terrain::lightTerrain(D3DXVECTOR3* directionToLight)
{
	HRESULT hr = 0;

	D3DSURFACE_DESC textureDesc;
	_tex->GetLevelDesc(0 /*level*/, &textureDesc);

	// make sure we got the requested format because our code that fills the
	// texture is hard coded to a 32 bit pixel depth.
	if (textureDesc.Format != D3DFMT_X8R8G8B8)
		return false;

	D3DLOCKED_RECT lockedRect;
	_tex->LockRect(
		0,          // lock top surface level in mipmap chain
		&lockedRect,// pointer to receive locked data
		0,          // lock entire texture image
		0);         // no lock flags specified

	DWORD* imageData = (DWORD*)lockedRect.pBits;
	for (int i = 0; i < textureDesc.Height; i++)
	{
		for (int j = 0; j < textureDesc.Width; j++)
		{
			// index into texture, note we use the pitch and divide by 
			// four since the pitch is given in bytes and there are 
			// 4 bytes per DWORD.
			int index = i * lockedRect.Pitch / 4 + j;

			// get current color of quad
			D3DXCOLOR c(imageData[index]);

			// shade current quad
			c *= computeShade(i, j, directionToLight);;

			// save shaded color
			imageData[index] = (D3DCOLOR)c;
		}
	}

	_tex->UnlockRect(0);

	return true;
}

float Terrain::computeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLight)
{
	// get heights of three vertices on the quad
	float heightA = getHeightmapEntry(cellRow, cellCol);
	float heightB = getHeightmapEntry(cellRow, cellCol + 1);
	float heightC = getHeightmapEntry(cellRow + 1, cellCol);

	// build two vectors on the quad
	D3DXVECTOR3 u(_cellSpacing, heightB - heightA, 0.0f);
	D3DXVECTOR3 v(0.0f, heightC - heightA, -_cellSpacing);

	// find the normal by taking the cross product of two
	// vectors on the quad.
	D3DXVECTOR3 n;
	D3DXVec3Cross(&n, &u, &v);
	D3DXVec3Normalize(&n, &n);

	float cosine = D3DXVec3Dot(&n, directionToLight);

	if (cosine < 0.0f)
		cosine = 0.0f;

	return cosine;
}

bool Terrain::readRawFile(std::string fileName)
{
	// Restriction: RAW file dimensions must be >= to the
	// dimensions of the terrain.  That is a 128x128 RAW file
	// can only be used with a terrain constructed with at most
	// 128x128 vertices.

	// A height for each vertex
	std::vector<BYTE> in(_numVertices);

	std::ifstream inFile(fileName.c_str(), std::ios_base::binary);

	/*if (inFile.is_open())
		return false;*/

	inFile.read(
		(char*)&in[0], // buffer
		in.size());// number of bytes to read into buffer

	inFile.close();

	// copy BYTE vector to int vector
	_heightmap.resize(_numVertices);

	for (int i = 0; i < in.size(); i++)
		_heightmap[i] = in[i];

	return true;
}

float Terrain::getHeight(float x, float z)
{
	// Translate on xz-plane by the transformation that takes
	// the terrain START point to the origin.
	x = ((float)_width / 2.0f) + x;
	z = ((float)_depth / 2.0f) - z;

	// Scale down by the transformation that makes the 
	// cellspacing equal to one.  This is given by 
	// 1 / cellspacing since; cellspacing * 1 / cellspacing = 1.
	x /= (float)_cellSpacing;
	z /= (float)_cellSpacing;

	// From now on, we will interpret our positive z-axis as
	// going in the 'down' direction, rather than the 'up' direction.
	// This allows to extract the row and column simply by 'flooring'
	// x and z:

	float col = ::floorf(x);
	float row = ::floorf(z);

	// get the heights of the quad we're in:
	// 
	//  A   B
	//  *---*
	//  | / |
	//  *---*  
	//  C   D

	float A = getHeightmapEntry(row, col);
	float B = getHeightmapEntry(row, col + 1);
	float C = getHeightmapEntry(row + 1, col);
	float D = getHeightmapEntry(row + 1, col + 1);

	//
	// Find the triangle we are in:
	//

	// Translate by the transformation that takes the upper-left
	// corner of the cell we are in to the origin.  Recall that our 
	// cellspacing was nomalized to 1.  Thus we have a unit square
	// at the origin of our +x -> 'right' and +z -> 'down' system.
	float dx = x - col;
	float dz = z - row;

	// Note the below compuations of u and v are unneccessary, we really
	// only need the height, but we compute the entire vector to emphasis
	// the books discussion.
	float height = 0.0f;
	if (dz < 1.0f - dx)  // upper triangle ABC
	{
		float uy = B - A; // A->B
		float vy = C - A; // A->C

		// Linearly interpolate on each vector.  The height is the vertex
		// height the vectors u and v originate from {A}, plus the heights
		// found by interpolating on each vector u and v.
		height = A + d3d::Lerp(0.0f, uy, dx) + d3d::Lerp(0.0f, vy, dz);
	}
	else // lower triangle DCB
	{
		float uy = C - D; // D->C
		float vy = B - D; // D->B

		// Linearly interpolate on each vector.  The height is the vertex
		// height the vectors u and v originate from {D}, plus the heights
		// found by interpolating on each vector u and v.
		height = D + d3d::Lerp(0.0f, uy, 1.0f - dx) + d3d::Lerp(0.0f, vy, 1.0f - dz);
	}

	return height;
}

bool Terrain::draw(D3DXMATRIX* world, bool drawTris)
{
	HRESULT hr = 0;

	if (_device)
	{
		_device->SetTransform(D3DTS_WORLD, world);

		_device->SetStreamSource(0, _vb, 0, sizeof(TerrainVertex));
		_device->SetFVF(TerrainVertex::FVF);
		_device->SetIndices(_ib);

		_device->SetTexture(0, _tex);

		// turn off lighting since we're lighting it ourselves
		_device->SetRenderState(D3DRS_LIGHTING, false);

		hr = _device->DrawIndexedPrimitive(
			D3DPT_TRIANGLELIST,
			0,
			0,
			_numVertices,
			0,
			_numTriangles);

		_device->SetRenderState(D3DRS_LIGHTING, true);

		if (drawTris)
		{
			_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
			hr = _device->DrawIndexedPrimitive(
				D3DPT_TRIANGLELIST,
				0,
				0,
				_numVertices,
				0,
				_numTriangles);

			_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
		}

		if (FAILED(hr))
			return false;
	}

	return true;
}

camera.h

#pragma once
#include <d3dx9.h>

class Camera
{
public:
	enum CameraType { LANDOBJECT, AIRCRAFT };
	Camera();
	Camera(CameraType cameraType);
	~Camera();

	void strafe(float units);	//left/right
	void fly(float units);	//up/down
	void walk(float units);	//forward/backward

	void pitch(float angle);	//rotate on right vector
	void yaw(float angle);	//rotate on up vector
	void roll(float angle);	//rotate on look vector

	void getViewMatrix(D3DXMATRIX* V);
	void setCameraType(CameraType cameraType);
	void getPosition(D3DXVECTOR3* pos);
	void setPosition(D3DXVECTOR3* pos);

	void getRight(D3DXVECTOR3* right);
	void getUp(D3DXVECTOR3* up);
	void getLook(D3DXVECTOR3* look);

private:
	CameraType _cameraType;
	D3DXVECTOR3 _right;
	D3DXVECTOR3 _up;
	D3DXVECTOR3 _look;
	D3DXVECTOR3 _pos;
};

camera.cpp

#include "camera.h"

Camera::Camera()
{
	_cameraType = LANDOBJECT;

	_pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
	_up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
	_look = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
}

Camera::Camera(CameraType cameraType)
{
	_cameraType = cameraType;

	_pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
	_up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
	_look = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
}

Camera::~Camera()
{

}

void Camera::getPosition(D3DXVECTOR3* pos)
{
	*pos = _pos;
}

void Camera::setPosition(D3DXVECTOR3* pos)
{
	_pos = *pos;
}

void Camera::getRight(D3DXVECTOR3* right)
{
	*right = _right;
}

void Camera::getUp(D3DXVECTOR3* up)
{
	*up = _up;
}

void Camera::getLook(D3DXVECTOR3* look)
{
	*look = _look;
}

void Camera::walk(float units)
{
	if (_cameraType == LANDOBJECT)
		_pos += D3DXVECTOR3(_look.x, 0.0f, _look.z) * units;
	if (_cameraType == AIRCRAFT)
		_pos += _look * units;
}

void Camera::strafe(float units)
{
	if (_cameraType == LANDOBJECT)
		_pos += D3DXVECTOR3(_right.x, 0.0f, _right.z) * units;
	if (_cameraType == AIRCRAFT)
		_pos += _right * units;
}

void Camera::fly(float units)
{
	if (_cameraType == LANDOBJECT)
		_pos.y += units;
	if (_cameraType == AIRCRAFT)
		_pos += _up * units;
}

void Camera::pitch(float angle)
{
	D3DXMATRIX T;
	D3DXMatrixRotationAxis(&T, &_right, angle);

	D3DXVec3TransformCoord(&_up, &_up, &T);
	D3DXVec3TransformCoord(&_look, &_look, &T);
}

void Camera::yaw(float angle)
{
	D3DXMATRIX T;
	if (_cameraType == LANDOBJECT)
		D3DXMatrixRotationY(&T, angle);
	if (_cameraType == AIRCRAFT)
		D3DXMatrixRotationAxis(&T, &_up, angle);

	D3DXVec3TransformCoord(&_right, &_right, &T);
	D3DXVec3TransformCoord(&_look, &_look, &T);
}

void Camera::roll(float angle)
{
	if (_cameraType == AIRCRAFT)
	{
		D3DXMATRIX T;
		D3DXMatrixRotationAxis(&T, &_look, angle);

		D3DXVec3TransformCoord(&_up, &_up, &T);
		D3DXVec3TransformCoord(&_right, &_right, &T);
	}
}

void Camera::getViewMatrix(D3DXMATRIX* V)
{
	//保持相机轴彼此正交
	D3DXVec3Normalize(&_look, &_look);

	D3DXVec3Cross(&_up, &_look, &_right);
	D3DXVec3Normalize(&_up, &_up);

	D3DXVec3Cross(&_right, &_up, &_look);
	D3DXVec3Normalize(&_right, &_right);

	//构建视图矩阵
	float x = -D3DXVec3Dot(&_right, &_pos);
	float y = -D3DXVec3Dot(&_up, &_pos);
	float z = -D3DXVec3Dot(&_look, &_pos);

	//视图矩阵=平移矩阵*旋转矩阵
	(*V)(0, 0) = _right.x;	(*V)(0, 1) = _up.x;	(*V)(0, 2) = _look.x;	(*V)(0, 3) = 0.0f;
	(*V)(1, 0) = _right.y;	(*V)(1, 1) = _up.y; (*V)(1, 2) = _look.y;	(*V)(1, 3) = 0.0f;
	(*V)(2, 0) = _right.z;	(*V)(2, 1) = _up.z; (*V)(2, 2) = _look.z;	(*V)(2, 3) = 0.0f;
	(*V)(3, 0) = x;			(*V)(3, 1) = y;		(*V)(3, 2) = z;			(*V)(3, 3) = 1.0f;
}

void Camera::setCameraType(CameraType cameraType)
{
	_cameraType = cameraType;
}

d3dutil.h

#pragma once
#include <d3d9.h>
#include <d3dx9math.h>

namespace d3d
{
	bool InitD3D(HINSTANCE hInstance, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9** device);
	int EnterMsgLoop(bool (*ptr_display)(float timeDelta));
	LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

	template<class T> void Release(T t)
	{
		if (t)
		{
			t->Release();
			t = nullptr;
		}
	}

	template<class T> void Delete(T t)
	{
		if (t)
		{
			delete t;
			t = nullptr;
		}
	}

	const D3DXCOLOR WHITE(D3DCOLOR_XRGB(255, 255, 255));
	const D3DXCOLOR BLACK(D3DCOLOR_XRGB(0, 0, 0));
	const D3DXCOLOR RED(D3DCOLOR_XRGB(255, 0, 0));
	const D3DXCOLOR GREEN(D3DCOLOR_XRGB(0, 255, 0));
	const D3DXCOLOR BLUE(D3DCOLOR_XRGB(0, 0, 255));
	const D3DXCOLOR YELLOW(D3DCOLOR_XRGB(255, 255, 0));
	const D3DXCOLOR CYAN(D3DCOLOR_XRGB(0, 255, 255));
	const D3DXCOLOR MAGENTA(D3DCOLOR_XRGB(255, 0, 255));

	const D3DXCOLOR BEACH_SAND(D3DCOLOR_XRGB(255, 249, 157));
	const D3DXCOLOR DESERT_SAND(D3DCOLOR_XRGB(250, 205, 135));

	const D3DXCOLOR LIGHTGREEN(D3DCOLOR_XRGB(60, 184, 120));
	const D3DXCOLOR  PUREGREEN(D3DCOLOR_XRGB(0, 166, 81));
	const D3DXCOLOR  DARKGREEN(D3DCOLOR_XRGB(0, 114, 54));

	const D3DXCOLOR LIGHT_YELLOW_GREEN(D3DCOLOR_XRGB(124, 197, 118));
	const D3DXCOLOR  PURE_YELLOW_GREEN(D3DCOLOR_XRGB(57, 181, 74));
	const D3DXCOLOR  DARK_YELLOW_GREEN(D3DCOLOR_XRGB(25, 123, 48));

	const D3DXCOLOR LIGHTBROWN(D3DCOLOR_XRGB(198, 156, 109));
	const D3DXCOLOR DARKBROWN(D3DCOLOR_XRGB(115, 100, 87));

	//材质
	D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p);
	const D3DMATERIAL9 WHITE_MTRL = InitMtrl(WHITE, WHITE, WHITE, BLACK, 8.0f);
	const D3DMATERIAL9 RED_MTRL = InitMtrl(RED, RED, RED, BLACK, 8.0f);
	const D3DMATERIAL9 GREEN_MTRL = InitMtrl(GREEN, GREEN, GREEN, BLACK, 8.0f);
	const D3DMATERIAL9 BLUE_MTRL = InitMtrl(BLUE, BLUE, BLUE, BLACK, 8.0f);
	const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 8.0f);

	//光源
	D3DLIGHT9 InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color);
	//D3DLIGHT9 InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color);
	//D3DLIGHT9 InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color);

	// Function references "desert.bmp" internally.  This file must
	// be in the working directory.
	bool DrawBasicScene(
		IDirect3DDevice9* device,// Pass in 0 for cleanup.
		float scale);            // uniform scale 

	struct Vertex
	{
		Vertex() {}
		Vertex(float x, float y, float z,
			float nx, float ny, float nz,
			float u, float v)
		{
			_x = x;  _y = y;  _z = z;
			_nx = nx; _ny = ny; _nz = nz;
			_u = u;  _v = v;
		}
		float _x, _y, _z;
		float _nx, _ny, _nz;
		float _u, _v;

		static const DWORD FVF;
	};

	//
	// Randomness
	//

	// Desc: Return random float in [lowBound, highBound] interval.
	float GetRandomFloat(float lowBound, float highBound);


	// Desc: Returns a random vector in the bounds specified by min and max.
	void GetRandomVector(
		D3DXVECTOR3* out,
		D3DXVECTOR3* min,
		D3DXVECTOR3* max);

	//
	// Conversion
	//
	DWORD FtoDw(float f);

	//
	// Interpolation
	//

	float Lerp(float a, float b, float t);
}

d3dutil.cpp

#include "d3dUtility.h"

// vertex formats
const DWORD d3d::Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;

bool d3d::InitD3D(HINSTANCE hInstance, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9** device)
{
	//注册wndclass
	WNDCLASS wndclass;
	wndclass.style = CS_VREDRAW | CS_HREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = nullptr;
	wndclass.lpszClassName = TEXT("d3d");
	
	if (!RegisterClass(&wndclass))
	{
		MessageBox(nullptr, TEXT("RegisterClass() - FAILED"), TEXT("ERROR"), MB_ICONERROR);
		return false;
	}

	//创建窗口
	HWND hwnd = CreateWindow(TEXT("d3d"), TEXT("d3d window"),
		WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, width, height,
		nullptr, nullptr, hInstance, nullptr);

	//设备列举和创建IDirect3DDevice9对象
	IDirect3D9* d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

	//检测硬件顶点处理
	D3DCAPS9 caps;
	d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps);
	int vp = 0;
	if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;	//支持硬件顶点处理
	else
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;	//不支持

	D3DDISPLAYMODE d3ddm;
	if (FAILED(d3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
		return false;

	//填充D3DPRESENT_PARAMETERS结构
	D3DPRESENT_PARAMETERS d3dpp;
	d3dpp.BackBufferWidth = width;
	d3dpp.BackBufferHeight = height;
	//d3dpp.BackBufferFormat = D3DFMT_A8B8G8R8;//像素格式
	d3dpp.BackBufferFormat = d3ddm.Format;
	d3dpp.BackBufferCount = 1;
	d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality = 0;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.hDeviceWindow = hwnd;
	d3dpp.Windowed = windowed;//full screen
	d3dpp.EnableAutoDepthStencil = true;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;//depth format
	d3dpp.Flags = 0;
	d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	//创建IDirect3DDevice9对象
	HRESULT hr = d3d9->CreateDevice(D3DADAPTER_DEFAULT, deviceType, hwnd, vp, &d3dpp, device);
	if (FAILED(hr))
	{
		MessageBox(nullptr, TEXT("CraeteDevice() - FAILED"), TEXT("ERROR"), MB_ICONERROR);
		return false;
	}

	d3d9->Release();

	return true;
}

int d3d::EnterMsgLoop(bool(*ptr_display)(float timeDelta))
{
	MSG msg;
	memset(&msg, 0, sizeof(msg));
	static float lastTime = (float)timeGetTime();

	while (true)
	{
		if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
				break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			float currTime = (float)timeGetTime();
			float timeDelta = (currTime - lastTime)*0.001f;
			ptr_display(timeDelta);
			lastTime = currTime;
		}
	}
	return msg.wParam;
}

LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)
			DestroyWindow(hwnd);
		break;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

D3DMATERIAL9 d3d::InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p)
{
	D3DMATERIAL9 mtrl;

	mtrl.Ambient = a;	//环境光
	mtrl.Diffuse = d;	//漫反射
	mtrl.Specular = s;	//镜面反射
	mtrl.Emissive = e;	//表面添加颜色
	mtrl.Power = p;		//镜面高光

	return mtrl;
}

D3DLIGHT9 d3d::InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color)
{
	D3DLIGHT9 light;
	ZeroMemory(&light, sizeof(light));
	light.Type = D3DLIGHT_DIRECTIONAL;
	light.Diffuse = *color;
	light.Ambient = *color * 0.3f;
	light.Specular = *color * 0.6f;
	light.Direction = *direction;

	return light;
}

bool d3d::DrawBasicScene(IDirect3DDevice9* device, float scale)
{
	static IDirect3DVertexBuffer9* floor = nullptr;
	static IDirect3DTexture9* tex = nullptr;
	static ID3DXMesh* pillar = nullptr;

	HRESULT hr = 0;
	if (device == nullptr)
	{
		if (floor && tex && pillar)
		{
			d3d::Release<IDirect3DVertexBuffer9*>(floor);
			d3d::Release<IDirect3DTexture9*>(tex);
			d3d::Release<ID3DXMesh*>(pillar);
		}
	}
	else if (!floor && !tex && !pillar)
	{
		device->CreateVertexBuffer(
			6 * sizeof(d3d::Vertex),
			0,
			d3d::Vertex::FVF,
			D3DPOOL_MANAGED,
			&floor,
			0);
		Vertex* v = 0;
		floor->Lock(0, 0, (void**)&v, 0);

		v[0] = Vertex(-20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
		v[1] = Vertex(-20.0f, -2.5f, 20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
		v[2] = Vertex(20.0f, -2.5f, 20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);

		v[3] = Vertex(-20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
		v[4] = Vertex(20.0f, -2.5f, 20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
		v[5] = Vertex(20.0f, -2.5f, -20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);

		floor->Unlock();

		//使用左手坐标系创建包含缸的网格
		D3DXCreateCylinder(device, 0.5f, 0.5f, 5.0f, 20, 20, &pillar, nullptr);

		D3DXCreateTextureFromFile(device, TEXT("desert.bmp"), &tex);
	}
	else
	{
		//设置过滤器
		device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
		device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);

		//设置光源
		D3DXVECTOR3 dir(0.707f, -0.707f, 0.707f);
		D3DXCOLOR col(1.0f, 1.0f, 1.0f, 1.0f);
		D3DLIGHT9 light = d3d::InitDirectionalLight(&dir, &col);

		device->SetLight(0, &light);
		device->LightEnable(0, true);
		device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
		device->SetRenderState(D3DRS_SPECULARENABLE, true);

		//渲染
		D3DXMATRIX T, R, P, S;

		//生成沿x、y、z轴缩放的矩阵
		D3DXMatrixScaling(&S, scale, scale, scale);

		//用于旋转圆柱体以与世界y轴平行
		D3DXMatrixRotationX(&R, -D3DX_PI * 0.5f);

		//draw floor
		D3DXMatrixIdentity(&T);
		T = T * S;
		device->SetTransform(D3DTS_WORLD, &T);
		device->SetMaterial(&d3d::WHITE_MTRL);
		device->SetTexture(0, tex);
		device->SetStreamSource(0, floor, 0, sizeof(Vertex));
		device->SetFVF(Vertex::FVF);
		device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

		//draw pillars
		device->SetMaterial(&d3d::BLUE_MTRL);
		device->SetTexture(0, 0);
		for (int i = 0; i < 5; i++)
		{
			D3DXMatrixTranslation(&T, -5.0f, 0.0f, -15.0f + (i * 7.5f));
			P = R * T * S;
			device->SetTransform(D3DTS_WORLD, &P);
			pillar->DrawSubset(0);

			D3DXMatrixTranslation(&T, 5.0f, 0.0f, -15.0f + (i * 7.5f));
			P = R * T * S;
			device->SetTransform(D3DTS_WORLD, &P);
			pillar->DrawSubset(0);
		}
	}
	return true;
}

float d3d::GetRandomFloat(float lowBound, float highBound)
{
	if (lowBound >= highBound) // bad input
		return lowBound;

	// get random float in [0, 1] interval
	float f = (rand() % 10000) * 0.0001f;

	// return float in [lowBound, highBound] interval. 
	return (f * (highBound - lowBound)) + lowBound;
}

void d3d::GetRandomVector(
	D3DXVECTOR3* out,
	D3DXVECTOR3* min,
	D3DXVECTOR3* max)
{
	out->x = GetRandomFloat(min->x, max->x);
	out->y = GetRandomFloat(min->y, max->y);
	out->z = GetRandomFloat(min->z, max->z);
}

DWORD d3d::FtoDw(float f)
{
	return *((DWORD*)&f);
}

float d3d::Lerp(float a, float b, float t)
{
	return a - (a * t) + (b * t);
}

terrainDriver.cpp

#include "d3dUtility.h"
#include "terrain.h"
#include "camera.h"
#include "fps.h"

#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
#pragma comment(lib, "winmm.lib")

#define KEY_DOWN(vk_code) (GetAsyncKeyState(vk_code) & 0x8000 ? 1 : 0)

//
// Globals
//

IDirect3DDevice9* device = 0;

const int Width = 640;
const int Height = 480;

Terrain* TheTerrain = 0;
Camera   TheCamera(Camera::LANDOBJECT);

FPSCounter* FPS = 0;

//
// Framework Functions
//
bool setup()
{
	//
	// Create the terrain.
	//

	D3DXVECTOR3 lightDirection(0.0f, 1.0f, 0.0f);
	TheTerrain = new Terrain(device, "coastMountain64.raw", 64, 64, 10, 0.5f);
	TheTerrain->genTexture(&lightDirection);

	//
	// Create the font.
	//

	FPS = new FPSCounter(device);

	//
	// Set texture filters.
	//

	device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

	//
	// Set projection matrix.
	//

	D3DXMATRIX proj;
	D3DXMatrixPerspectiveFovLH(
		&proj,
		D3DX_PI * 0.25f, // 45 - degree
		(float)Width / (float)Height,
		1.0f,
		1000.0f);
	device->SetTransform(D3DTS_PROJECTION, &proj);

	return true;
}

void cleanup()
{
	d3d::Delete<Terrain*>(TheTerrain);
	d3d::Delete<FPSCounter*>(FPS);
}

bool display(float timeDelta)
{
	//
	// Update the scene:
	//

	if (device)
	{
		if (KEY_DOWN(VK_UP))
			TheCamera.walk(100.0f * timeDelta);

		if (KEY_DOWN(VK_DOWN))
			TheCamera.walk(-100.0f * timeDelta);

		if (KEY_DOWN(VK_LEFT))
			TheCamera.yaw(-1.0f * timeDelta);

		if (KEY_DOWN(VK_RIGHT))
			TheCamera.yaw(1.0f * timeDelta);

		if (KEY_DOWN('N'))
			TheCamera.strafe(-100.0f * timeDelta);

		if (KEY_DOWN('M'))
			TheCamera.strafe(100.0f * timeDelta);

		if (KEY_DOWN('W'))
			TheCamera.pitch(1.0f * timeDelta);

		if (KEY_DOWN('S'))
			TheCamera.pitch(-1.0f * timeDelta);

		D3DXVECTOR3 pos;
		TheCamera.getPosition(&pos);
		float height = TheTerrain->getHeight(pos.x, pos.z);
		pos.y = height + 5.0f; // add height because we're standing up
		TheCamera.setPosition(&pos);

		D3DXMATRIX V;
		TheCamera.getViewMatrix(&V);
		device->SetTransform(D3DTS_VIEW, &V);

		//
		// Draw the scene:
		//

		device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff000000, 1.0f, 0);
		device->BeginScene();

		D3DXMATRIX I;
		D3DXMatrixIdentity(&I);

		if (TheTerrain)
			TheTerrain->draw(&I, false);

		if (FPS)
			FPS->render(0xffffffff, timeDelta);

		device->EndScene();
		device->Present(0, 0, 0, 0);
	}
	return true;
}

//
// WinMain
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	if (!d3d::InitD3D(hInstance, Width, Height, true, D3DDEVTYPE_HAL, &device))
	{
		MessageBox(nullptr, TEXT("InitD3D() - FAILED"), TEXT("ERROR"), MB_ICONERROR);
		return 0;
	}

	if (!setup())
	{
		MessageBox(nullptr, TEXT("setup() - FAILED"), TEXT("ERROR"), MB_ICONERROR);
		return 0;
	}

	d3d::EnterMsgLoop(display);

	cleanup();

	device->Release();

	return 1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值