Microsoft Visual Studio .NET 2003 NVIDIA PhysX SDK v2.8.3 Microsoft DirectX 9.0 SDK (December 2004) |
■PhysXで三角ポリゴンメッシュを使用してみる | Prev Top Next |
関連ページ:PhysX リファレンス |
サンプル3
これまで衝突判定用モデルにPhysX標準のプリミティブを使用していました。 ですが地形モデルのようにプリミティブでは対応できない場合もあります。 今回は衝突判定用モデルにXファイル形式の3Dモデルを使用します。
でこれをやるために前準備がいくつか必要となります。
@まずVisual Studioのインクルードファイルのパスに新たにパスを追加する必要があります。
PhysXのインストール環境によってパスは異なりますので、下記のパスは参考程度にとらえてください。
D:\Program Files\NVIDIA Corporation\NVIDIA PhysX SDK\v2.8.3\SDKs\Cooking\include
A次にVisual Studioの「追加の依存ファイル」にインポートライブラリファイルを追加します。
PhysXCooking.lib
B次に PhysXCooking.dll ファイルをアプリケーション側でロードできるようにします。 これはデフォルトの環境では環境変数の"Path"変数で設定されているフォルダ内にPhysXCooking.dllファイルが存在しないので、PhysXCooking.dllファイルを"Path"変数内で 指定されているフォルダ内にコピーする必要があるためです。筆者のPCの環境は次のようになっています。 ただしPhysXCooking.dllを置く場所は、これを使用するアプリの実行ファイルのパスとか、SYSTEM32直下とかでもいいので参考程度にしてください。
・コピー元フォルダ
C:\Program Files\NVIDIA Corporation\PhysX\Engine\v2.8.3
↓PhysXCooking.dll をコピーする
・コピー先フォルダ
C:\Program Files\NVIDIA Corporation\PhysX\Common
ちなみに C:\Program Files\NVIDIA Corporation\PhysX\Engine フォルダ内には v2.8.3 以外のバージョンのDLLがあります。 開発環境の PhysX SDK のバージョンと同じDLLを使用するようにとのことでしょう。 それはいいけど、DLL をコピーするなんてことエンドユーザーにやらせたくないな。 DLLを二次配布していいのであれば問題ないけど、二次配布やっていいのか不明だし。
C最後にコンパイル時に必要となるCPPファイルとヘッダファイルをプロジェクトに追加します。
必要なファイルは
Stream.cpp
Stream.h
の2つです。
これらのファイルは
D:\Program Files\NVIDIA Corporation\NVIDIA PhysX SDK\v2.8.3\Samples\SampleCommonCode\src
にあります。コピーするなりして使用できるようにしてください。
でこれら4つの追加したものをなんの目的で使用するかは後で説明します。
ではコードを見ていきます。
---PhysX.cpp---
NxTriangleMeshShapeDesc CreateTriangleMesh( NxPhysicsSDK* pNxPhysicsSDK, LPD3DXMESH pMesh ) { LPDIRECT3DDEVICE9 pd3dDevice = NULL; LPD3DXMESH pTempMesh = NULL; NxTriangleMeshDesc meshDesc; D3DVECTOR* pVec = NULL; LPBYTE pIndex = NULL; LPBYTE pBits = NULL; NxTriangleMeshShapeDesc meshShapeDesc; MemoryWriteBuffer buf; NxTriangleMesh* pTriangleMesh = NULL; NxU32 flg = 0; if( pMesh == NULL ) goto ERROR_EXIT; pMesh->GetDevice( &pd3dDevice ); //頂点の座標情報のみをコピーして再生成する(注意1) pMesh->CloneMeshFVF( pMesh->GetOptions(), D3DFVF_XYZ, pd3dDevice, &pTempMesh ); SafeRelease( pd3dDevice ); // インデックスバッファからインデックスのサイズを取得する(注意2) LPDIRECT3DINDEXBUFFER9 pIndexBuffer = NULL; pTempMesh->GetIndexBuffer( &pIndexBuffer ); D3DINDEXBUFFER_DESC desc; pIndexBuffer->GetDesc( &desc ); SafeRelease( pIndexBuffer ); switch( desc.Format ) { case D3DFMT_INDEX16: flg |= NX_CF_16_BIT_INDICES; } // インデックス情報などをセットする meshDesc.flags = flg; // 頂点数 meshDesc.numVertices = pTempMesh->GetNumVertices(); // 1頂点のサイズ meshDesc.pointStrideBytes = pTempMesh->GetNumBytesPerVertex(); // 三角ポリゴン数 meshDesc.numTriangles = pTempMesh->GetNumFaces(); // 三角ポリゴン1個分のインデックスのサイズ if( flg & NX_CF_16_BIT_INDICES ) meshDesc.triangleStrideBytes = 3 * sizeof( WORD ); else meshDesc.triangleStrideBytes = 3 * sizeof( DWORD ); pVec = new D3DVECTOR[ meshDesc.numVertices ]; //頂点バッファを参照して頂点座標を取得し、NxTriangleMeshDescに格納する pTempMesh->LockVertexBuffer( D3DLOCK_READONLY, (void**)&pBits ); CopyMemory( pVec, pBits, meshDesc.pointStrideBytes * meshDesc.numVertices ); meshDesc.points = pVec; pTempMesh->UnlockVertexBuffer(); pIndex = new BYTE[ meshDesc.triangleStrideBytes * meshDesc.numTriangles ]; //インデックスバッファを参照して頂点座標を取得し、NxTriangleMeshDescに格納する pTempMesh->LockIndexBuffer( D3DLOCK_READONLY, (void**)&pBits ); CopyMemory( pIndex, pBits, meshDesc.triangleStrideBytes * meshDesc.numTriangles ); meshDesc.triangles = pIndex; pTempMesh->UnlockIndexBuffer(); // Cookingの初期化(注意3) NxInitCooking(); // NxTriangleMeshDesc クラスオブジェクトをシリアル化する if( NxCookTriangleMesh( meshDesc, buf ) == false ) goto ERROR_EXIT; // シリアル化したデータから NxTriangleMesh クラスを作成する。(注意4) pTriangleMesh = pNxPhysicsSDK->createTriangleMesh( MemoryReadBuffer( buf.data ) ); if( pTriangleMesh == NULL ) goto ERROR_EXIT; // NxTriangleMesh クラスのポインタをコピー meshShapeDesc.meshData = pTriangleMesh; // Cookingの終了処理 NxCloseCooking(); ERROR_EXIT: SafeRelease( pTempMesh ); SafeDeleteArray( pVec ); SafeDeleteArray( pIndex ); return meshShapeDesc; }この関数はID3DXMesh インターフェイスから頂点情報を取得し、その頂点情報をPhysX側で使用できるようにするためのものです。
(注意1)
頂点フォーマットを 頂点座標のみに変更して ID3DXMesh インターフェースを再生成しています。
これは頂点バッファから頂点情報を取得するとき、頂点フォーマットとして頂点の座標のみであると仮定した処理となっているからです。
(注意2)
Direct3Dで使用しているインデックスのサイズを調べます。16ビットか32ビットかでその後の処理を分岐しています。
(注意3)
Cookingの初期化とか記述しましたが、なぜCookingなのかは知りません(笑)。
名称についての疑問は置いといて、とりあえず NxCookTriangleMesh 関数を使用して NxTriangleMeshDesc クラスオブジェクトをシリアル化する必要があると覚えといてください。
でこのCooking系関数を使用するために、インクルードファイルやらインポートライブラリやらを追加する必要があったわけです。
(注意4)
MemoryReadBuffer という謎のクラスが登場します。関数に見えますが、MemoryReadBuffer クラスのコンストラクタを実行しているところです。
でこのクラスは Stream.cpp と Stream.h で定義されています。バイト型の配列のデータを読み込みするだけの単純なクラスです。
---Main.cpp---
//Direct3D static LPDIRECT3D9 m_pdirect3d9 = NULL; static LPDIRECT3DDEVICE9 m_pd3dDevice = NULL; static D3DPRESENT_PARAMETERS m_d3dParameters; //PhysX static NxPhysicsSDK* m_pNxPhysicsSDK = NULL; static NxScene* m_pNxScene = NULL; // PhysXデバッグ用クラス static DEBUG_RENDERER* m_pDebugRenderer = NULL; static LPD3DXMESH m_pMesh = NULL; static char* m_pDynamicMeshName = "DynamicMesh"; //地面モデル static LPD3DXMESH m_pPlaneMesh = NULL; static char* m_pStaticMeshName = "StaticMesh"; static UINT nWidth = 1024; //ウィンドウの横幅 static UINT nHeight = 768; //ウィンドウの縦幅 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmpLine*/, INT /*nCmdShow*/) { HRESULT hr; MSG msg; ZeroMemory( &msg, sizeof(MSG) ); HWND hWnd = NULL; BOOL Windowed = TRUE; TCHAR* AppName = _T("PhysX Tutrial 01"); //**************************************************************************** // ウィンドウ作成 //**************************************************************************** // 省略。。。 //**************************************************************************** // Direct3Dの初期化 //**************************************************************************** // 省略。。。 //**************************************************************************** // 3Dメッシュをロードする //**************************************************************************** D3DXLoadMeshFromX( _T("Plane.x"), D3DXMESH_MANAGED, m_pd3dDevice, NULL, NULL, NULL, NULL, &m_pPlaneMesh ); D3DXLoadMeshFromX( _T("Triangle.x"), D3DXMESH_MANAGED, m_pd3dDevice, NULL, NULL, NULL, NULL, &m_pMesh ); //固定機能パイプラインライティングを設定する D3DLIGHT9 Light; ZeroMemory(&Light, sizeof(D3DLIGHT9)); Light.Type = D3DLIGHT_DIRECTIONAL; Light.Direction = D3DXVECTOR3( -1.0f, -1.0f, 1.0f ); Light.Ambient = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ); Light.Diffuse = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ); Light.Specular = D3DXCOLOR( 0.0f, 0.0f, 0.0f, 0.0f ); m_pd3dDevice->SetLight(0, &Light); m_pd3dDevice->LightEnable(0, TRUE); //**************************************************************************** // PhysXの初期化 //**************************************************************************** // この関数については「PhysXをとりあえず使ってみる」ページを参照すること hr = InitNxPhysX( &m_pNxPhysicsSDK, &m_pNxScene, &m_pDebugRenderer ); if( FAILED( hr ) ) { ::DestroyWindow( hWnd ); return 0; } //**************************************************************************** // 静的な衝突判定用オブジェクトを作成する //**************************************************************************** NxActorDesc actorDesc; // 初期化 actorDesc.setToDefault(); // 地面のアクターを作成する NxPlaneShapeDesc planeDesc; // 初期化 planeDesc.setToDefault(); // 原点からの距離 法線ベクトルと一緒に設定し、地面の位置を決定する planeDesc.d = -5.0f; actorDesc.shapes.pushBack(&planeDesc); // デフォルトのマテリアルを取得する NxMaterial * defaultMaterial = m_pNxScene->getMaterialFromIndex(0); defaultMaterial->setRestitution(0.5f); // 反発係数 defaultMaterial->setDynamicFriction(0.5f); // 動摩擦係数 defaultMaterial->setStaticFriction(0.5f); // 静止摩擦係数 // アクターにモデルを識別するための名称を設定する actorDesc.name = m_pMeshNamePlane; // シーンに対して、指定のアクターを追加 m_pNxScene->createActor(actorDesc); //**************************************************************************** // 動的な衝突判定用オブジェクトを作成する //**************************************************************************** // 初期化 actorDesc.setToDefault(); NxTriangleMeshShapeDesc shapeDesc = CreateTriangleMesh( m_pNxPhysicsSDK, m_pMesh ); shapeDesc.localPose.t = NxVec3( 0, 0, 0 ); actorDesc.shapes.pushBack(&shapeDesc); //動的な衝突判定用モデルの設定 NxBodyDesc bodyDesc; // 初期化 bodyDesc.setToDefault(); // 回転による減衰係数 bodyDesc.angularDamping = 0.5f; actorDesc.body = &bodyDesc; // 質量密度 actorDesc.density = 10.0f; // シーン上の位置 actorDesc.globalPose.t = NxVec3(0.5f, 20.0f, 40.0f); //シーン上の回転 float r = 50.0f; //Z軸回転 //PhysXでは右手座標系となりDirectXと回転方向が異なることに注意すること NxF32 m[] = { cosf( D3DXToRadian( r ) ) , sin( D3DXToRadian( r ) ), 0.0f, -sinf( D3DXToRadian( r ) ), cos( D3DXToRadian( r ) ), 0.0f, 0.0f , 0.0f , 1.0f }; actorDesc.globalPose.M.setColumnMajor( m ); //アクターに名称を設定する actorDesc.name = m_pDynamicMeshName; // シーンに対して、指定のアクターを追加 m_pNxScene->createActor(actorDesc); ::ShowWindow(hWnd, SW_SHOW); ::UpdateWindow(hWnd); //**************************************************************************** // メッセージループ //**************************************************************************** do { if( ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } else { if( MainLoop(hWnd) == FALSE ) ::DestroyWindow( hWnd ); } }while( msg.message != WM_QUIT ); ::UnregisterClass( AppName, hInstance ); return msg.wParam; } LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, UINT wParam, LONG lParam ) { switch (msg) { case WM_KEYUP: if( wParam == VK_ESCAPE ) ::DestroyWindow( hWnd ); break; // 終了処理 case WM_DESTROY: //PhysX SafeDelete( m_pDebugRenderer ); if( m_pNxPhysicsSDK != NULL ) { if( m_pNxScene != NULL ) m_pNxPhysicsSDK->releaseScene( *m_pNxScene ); m_pNxScene = NULL; NxReleasePhysicsSDK( m_pNxPhysicsSDK ); m_pNxPhysicsSDK = NULL; } //Direct3D SafeRelease( m_pMeshPlane ); SafeRelease( m_pMesh ); SafeRelease( m_pd3dDevice ); SafeRelease( m_pdirect3d9 ); ::PostQuitMessage(0); break; default: return ::DefWindowProc( hWnd, msg, wParam, lParam ); } return 0L; } BOOL MainLoop( HWND hWnd ) { D3DXMATRIX matWVP, matWorld, matView, matProj; //ワールド行列 D3DXMatrixIdentity( &matWorld ); m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); //ビュー行列 D3DXMatrixTranslation( &matView, 0.0f, 0.0f, 0.0f ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); //遠近射影行列 //クリップ面はアプリケーションごとに調整すること D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4.0f, 4.0f / 3.0f, 0.01f, 150.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); if( SUCCEEDED( m_pd3dDevice->BeginScene() ) ) { m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x0, 1.0f, 0L ); //**************************************************************************** // 3Dメッシュをレンダリング //**************************************************************************** D3DMATERIAL9 Material; // アクター数を取得 NxU32 actorCount = m_pNxScene->getNbActors(); for( NxU32 i=0; i<actorCount; i++ ) { NxActor* actor = m_pNxScene->getActors()[i]; const char* name = actor->getName(); if( name != NULL ) { //動的メッシュを探す if( strcmp( name, m_pDynamicMeshName ) == 0 ) { NxShape* shape = actor->getShapes()[0]; NxF32 m[16]; //単位行列 ZeroMemory( m, sizeof( D3DXMATRIXA16 ) ); m[0] = m[5] = m[10] = m[15] = 1.0f; //DirectXと異なりPhysXは右手座標系であることに注意する。 actor->getGlobalPose().getColumnMajor44( m ); CopyMemory( &matWorld, m, sizeof( D3DXMATRIXA16 ) ); m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); //マテリアル設定 ZeroMemory( &Material, sizeof( Material ) ); Material.Ambient.r = 0.1f; Material.Ambient.g = 0.1f; Material.Ambient.b = 0.1f; Material.Ambient.a = 1.0f; Material.Diffuse.r = 0.0f; Material.Diffuse.g = 0.0f; Material.Diffuse.b = 1.0f; Material.Diffuse.a = 1.0f; m_pd3dDevice->SetMaterial( &Material ); //メッシュをレンダリングする m_pMesh->DrawSubset(0); } //地面メッシュを探す else if( strcmp( name, m_pMeshNamePlane ) == 0 ) { D3DXMatrixIdentity( &matWorld ); //平行移動情報を取得し、Direct3D側に設定する NxPlane plane = actor->getShapes()[0]->isPlane()->getPlane(); matWorld._41 = 0.0f; matWorld._42 = plane.d; matWorld._43 = 0.0f; m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); //マテリアル設定 ZeroMemory( &Material, sizeof( Material ) ); Material.Ambient.r = 0.1f; Material.Ambient.g = 0.1f; Material.Ambient.b = 0.1f; Material.Ambient.a = 1.0f; Material.Diffuse.r = 0.2f; Material.Diffuse.g = 1.0f; Material.Diffuse.b = 0.0f; Material.Diffuse.a = 1.0f; m_pd3dDevice->SetMaterial( &Material ); //Planeメッシュをレンダリングする m_pMeshPlane->DrawSubset(0); } } } //**************************************************************************** // デバッグ用のワイヤーフレームを描画 //**************************************************************************** if(m_pDebugRenderer) m_pDebugRenderer->RenderData( m_pd3dDevice, m_pNxScene->getDebugRenderable() ); m_pd3dDevice->EndScene(); // 1FPSの時間を指定 // 遅延が発生した場合を考慮しないとだめだろう m_pNxScene->simulate( 1.0f / 60.0f ); //よくわからんが下記の2関数をコールして実際に時間を進めるらしい m_pNxScene->flushStream(); m_pNxScene->fetchResults( NX_RIGID_BODY_FINISHED, true ); } m_pd3dDevice->Present( NULL, NULL, NULL, NULL ); return TRUE; }終了です。