Mesoscopic Programming

タコさんプログラミング専門

ウィンドウズプログラミング講座第15回:Xファイルの表示

概要
  1. Xファイルを表示できるようにしました。
  2. ツリービュークラスをやめてアプリケーションクラス直属にしました。
  3. ビュークラスも不要になったのでドキュメントビュークラスに戻しました。

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;
                }
            }
        }
    }
}
解説

Direct3D インタフェースを作成して Direct3D デバイスインタフェースを作成してます。
何で2段階なのかなぁ?なんて思いますけど、細かいことは気にしない。
プレゼントパラメータの設定値で初期画面サイズを定義してますが、DirectX9 だとこれがゼロだと失敗するんですよね。
他のサイトさんのサンプルを見るとこれがゼロだったりしますが、たぶん DirectX8 なのかなぁ?でも気にしない。

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;
    }
}
解説

確保したものは解放する。これは大人のマナー。


実行画面

実行ファイル
  1. Windows.zip

以上です。