ウィンドウズプログラミング講座第15回:Xファイルの表示
概要
- Xファイルを表示できるようにしました。
- ツリービュークラスをやめてアプリケーションクラス直属にしました。
- ビュークラスも不要になったのでドキュメントビュークラスに戻しました。
Xファイルの読込みは D3DXLoadMeshFromX() 関数一発なので超簡単…とは行かず、まあまあな処理量になりました。
久しぶりに DirectX 使うので、いろいろ忘れてた知識をグーグル先生経由で他のプログラミング系サイトさんに教えてもらいました。
どうもありがとうございました。
ドキュメントビュークラス
Xファイル表示関係の追加対応部分だけです。
クラス宣言部
class DocView : public View { enum TimerID { timerXFile, }; static const UINT timeXFile = 50;
オブジェクトを回転表示させるのに使うタイマーIDとインターバル時間です。
LPDIRECT3D9 pD3d; LPDIRECT3DDEVICE9 pD3dDev; D3DPRESENT_PARAMETERS d3dpp; DWORD dwMaterials; LPD3DXMESH pMesh; D3DMATERIAL9 * d3dMaterials; LPDIRECT3DTEXTURE9 * pD3dTextures;
Direct3D 関係の変数です。
BOOL Load( LPCTSTR path ); VOID DrawXFile( HDC hdc, INT x, INT y ); BOOL CreateResource(); VOID ReleaseResource(); LRESULT OnWmCreate(); LRESULT OnWmDestroy(); LRESULT OnWmSize(); };
Direct3D 関係の関数です。
Direct3D の初期化
LRESULT DocView :: OnWmCreate() { HDC hdc; D3DDISPLAYMODE d3ddm; pD3d = Direct3DCreate9( D3D_SDK_VERSION ); if ( pD3d != NULL && SUCCEEDED( pD3d->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, & d3ddm ) ) ) { ZeroMemory( & d3dpp, sizeof d3dpp ); d3dpp.BackBufferWidth = 1; d3dpp.BackBufferHeight = 1; d3dpp.BackBufferFormat = d3ddm.Format; d3dpp.BackBufferCount = 1; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = TRUE; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; if ( FAILED( pD3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, & d3dpp, & pD3dDev ) ) ) { if ( FAILED( pD3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, & d3dpp, & pD3dDev ) ) ) { if ( FAILED( pD3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, & d3dpp, & pD3dDev ) ) ) { pD3dDev = NULL; } } } } }
Xファイルの読込み
BOOL DocView :: Load( LPCTSTR path ) { TCHAR drive[ _MAX_DRIVE ]; TCHAR dir [ _MAX_DIR ]; TCHAR fname[ _MAX_FNAME ]; TCHAR ext [ _MAX_EXT ]; RECT rc; _splitpath( path, drive, dir, fname, ext ); if ( stricmp( ext, ".x" ) == 0 && CreateResource() ) { GetClientRect( hwnd, & rc ); docType = typeXFile; docWidth = rc.right - rc.left; docHeight = rc.bottom - rc.top; UpdateSize(); SetTimer( hwnd, timerXFile, timeXFile, NULL ); }
解説
拡張子が ".x" ならばXファイルじゃね?一応リソース作成してみっか、で失敗したらバイナリファイル扱いとする。
成功だったらサイズ更新イベントで Direct3D デバイスインタフェースを現在のウィンドウサイズにリセットする。
回転表示のためにタイマーをセットして終了。
リソースの作成
BOOL DocView :: CreateResource() { LPD3DXBUFFER pD3dxBuf; D3DXMATERIAL * d3dxMaterials; TCHAR path [ _MAX_PATH ]; TCHAR drive[ _MAX_DRIVE ]; TCHAR dir [ _MAX_DIR ]; TCHAR fname[ _MAX_FNAME ]; TCHAR ext [ _MAX_EXT ]; if( pD3d != NULL && pD3dDev != NULL ) { ReleaseResource(); if ( SUCCEEDED( D3DXLoadMeshFromX( filePath, D3DXMESH_SYSTEMMEM, pD3dDev, NULL, & pD3dxBuf, NULL, & dwMaterials, & pMesh ) ) ) { d3dxMaterials = ( D3DXMATERIAL * ) pD3dxBuf->GetBufferPointer(); d3dMaterials = new D3DMATERIAL9[ dwMaterials ]; pD3dTextures = new LPDIRECT3DTEXTURE9[ dwMaterials ]; _splitpath( filePath, drive, dir, fname, ext ); for ( DWORD i = 0; i < dwMaterials; i++ ) { d3dMaterials[ i ] = d3dxMaterials[ i ].MatD3D; d3dMaterials[ i ].Ambient = d3dMaterials[ i ].Diffuse; pD3dTextures[ i ] = NULL; if ( d3dxMaterials[ i ].pTextureFilename != NULL && lstrlen( d3dxMaterials[ i ].pTextureFilename ) > 0 ) { sprintf( path, "%s%s%s", drive, dir, d3dxMaterials[ i ].pTextureFilename ); if ( FAILED( D3DXCreateTextureFromFile( pD3dDev, path, & pD3dTextures[ i ] ) ) ) { pD3dTextures[ i ] = NULL; } } } pD3dxBuf->Release(); return TRUE; } } return FALSE; }
解説
まず前のリソースが残ってるかもしれないから消しておく。
次に D3DXLoadMeshFromX() 関数一発でXファイルを読み込む。これで本当ならめんどくさい部分はほぼ終了。
あとはマテリアルの数だけマテリアルを作成する。
テクスチャファイル名はフルパスにしないと失敗する。なぜなら D3DXCreateTextureFromFile() 関数はカレントディレクトリ相対だからだ。
マテリアル変数にコピーし終わったバッファの方は忘れずに解放する。
オブジェクトの表示
VOID DocView :: DrawXFile( HDC hdc, INT x, INT y ) { D3DXMATRIX matWorld; D3DXMATRIX matView; D3DXMATRIX matProj; if ( pD3d != NULL && pD3dDev != NULL) { pD3dDev->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, RGB( 32, 32, 32 ), 1.0f, 0 ); pD3dDev->BeginScene(); D3DXMatrixRotationY( & matWorld, timeGetTime() / 1000.0f ); D3DXMatrixLookAtLH( & matView, & D3DXVECTOR3( 0.0f, 3.0f, 4.0f ), & D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), & D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) ); D3DXMatrixPerspectiveFovLH( & matProj, 60.0f * D3DX_PI / 180.0f, 1.0, 0.01f, 100.0f ); pD3dDev->SetTransform( D3DTS_WORLD, & matWorld ); pD3dDev->SetTransform( D3DTS_VIEW, & matView ); pD3dDev->SetTransform( D3DTS_PROJECTION, & matProj ); pD3dDev->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); pD3dDev->SetRenderState( D3DRS_LIGHTING, FALSE ); pD3dDev->SetRenderState( D3DRS_ZENABLE, TRUE ); pD3dDev->SetRenderState( D3DRS_AMBIENT, 0xffffffff ); for ( DWORD i = 0; i < dwMaterials; i++ ) { pD3dDev->SetMaterial( & d3dMaterials[ i ] ); pD3dDev->SetTexture( 0, pD3dTextures[ i ] ); pMesh->DrawSubset( i ); } pD3dDev->EndScene(); pD3dDev->Present( NULL, NULL, NULL, NULL ); } }
解説
まずバックバッファをクリアする。
そしてビギンシーン。
カメラ設定のためワールド、ビュー、プロジェクションの各マトリックスを設定する。
レンダリングのためのライト設定などをする。
マテリアルの数だけメッシュを描画する。
これでエンドシーン。
実画面にバックバッファをプレゼントして完了。
ウィンドウサイズの変更
LRESULT DocView :: OnWmSize() { RECT rc; if ( docType == typeXFile ) { GetClientRect( hwnd, & rc ); docWidth = rc.right - rc.left; docHeight = rc.bottom - rc.top; } if ( pD3dDev != NULL && docWidth > 0 && docHeight > 0 ) { d3dpp.BackBufferWidth = docWidth; d3dpp.BackBufferHeight = docHeight; pD3dDev->Reset( & d3dpp ); } }
解説
バックバッファのサイズをウィンドウ(クライアント)サイズと同じするためにリセットしてます。
サイズがゼロだと失敗するのでチェックしてます。
リソースの解放と終了処理
VOID DocView :: ReleaseResource() { if ( pD3dTextures != NULL ) { for ( DWORD i = 0; i < dwMaterials; i++ ) { if ( pD3dTextures[ i ] != NULL ) { pD3dTextures[ i ]->Release(); } } delete [] pD3dTextures; pD3dTextures = NULL; } if ( d3dMaterials != NULL ) { delete [] d3dMaterials; d3dMaterials = NULL; } if ( pMesh != NULL ) { pMesh->Release(); pMesh = NULL; } dwMaterials = 0; } LRESULT DocView :: OnWmDestroy() { if ( pD3dDev != NULL ) { pD3dDev->Release(); pD3dDev = NULL; } if ( pD3d != NULL ) { pD3d->Release(); pD3d = NULL; } }
解説
確保したものは解放する。これは大人のマナー。