情報メディア実験 物理エンジンを使ったアプリケーション開発(5)

3Dモデルファイルを用いた形状設定

これまでは球やボックス,円筒など決まった形状のみを扱っていた. しかし,アプリケーションを制作する上で任意の形状を扱いたいと思うだろう. 1つの方法としてはbtCompoundShapeを使うことである. これを使うと複数の形状を組み合わせた形状を作成することができる. ただ,やはり元になる形状は球やボックスなどに限られてしまう.

任意の形状を扱う別の方法として, ポリゴン(三角形メッシュ)で構成された形状をファイルから読み込んで, それをBulletに設定する方法がある. Bullet User Manualのp.19にあるConvex Hull Shapes や Concave Triangle Meshes を使うと, ポリゴン情報から形状を作成できる. 以下ではポリゴン情報が書かれたモデルファイルを読み込んで,任意形状の剛体や弾性体を設定する方法について順番に説明する. なお,User ManualではbtBvhTriangleMeshShapeを使うと書いてあるが, ここではbtGImpactMeshShapeを用いた方法について説明する.

3Dモデルファイルライブラリのダウンロード

3Dモデルファイルとは, Mayaや3D Studio Max,Rhinocerosなどの3Dモデリングソフトで3次元形状を作成し, そのポリゴン(主に三角形ポリゴン)情報をファイルに書き出したものである. (3Dモデリングソフトには無料で使えるものもいくつかあり,代表的なのはBlenderやMetasequoiaLEなどがある. ちなみに実習室のPCにはRhinocerosとMetasequoiaLEがインストールされている). 3Dモデルファイルには様々な形式があり, 代表的なものだけでも,OBJ, 3DS, VRML, DXF, STLなどである (参考:3Dモデルファイルの種類). BulletはOBJ形式(正確にはWavefrontOBJ)の入力には対応しているが, 機能としてはかなり限定的なので,ここでは私の方で作成した3Dモデルファイル入出力ライブラリを用いる. まず,以下よりライブラリをダウンロードしよう.

ダウンロードして解凍すると, sharedとsrcフォルダがあるので,sharedフォルダをプロジェクトのsharedフォルダに上書きコピーする (いくつかのファイルはすでに入れてあったので上書き確認が出るが,すべて上書きしてOK). srcフォルダの中のrx_model_libフォルダにはライブラリのソースコードが入っているので, 興味のある人は見てみるとよい.

3Dモデルファイルライブラリの設定

ライブラリを使用するにはまず,

#include "rx_model.h"

とし,入力ライブラリにrx_model.lib(Debugの場合はrx_modeld.lib)を設定する. Visual C++なら,

#ifdef _DEBUG
 #pragma comment (lib, "rx_modeld.lib")
#else
 #pragma comment (lib, "rx_model.lib")
#endif

でもよい.

3Dモデルファイルの読み込み

3Dモデルファイルを読み込む場合は以下のように設定する.

// 3Dファイル読み込み
rxPolygons poly;
RxModel::Read(filename, poly);

filename(string型)に3Dモデルファイルのパスを設定する. polyにはポリゴンの頂点(poly.vertices)とその接続情報(poly.faces)が分けて格納される. ライブラリは拡張子からファイルの種類を判別する. ライブラリが対応している3Dモデルファイルの種類は,

である.テスト用に3Dモデルファイルをいくつか置いておく.

テキストファイルとしてブラウザ上で表示されてしまう場合は,リンクを右クリック→リンク先を名前を付けて保存 すること.

メッシュをBulletに設定

btRigidBodyの場合はbtTriangleIndexVertexArrayクラス,btSoftBodyの場合はbtSoftBodyHelpers::CreateFromTriMesh関数 を用いてメッシュを形状として設定する. これらにrxPolygons型のままメッシュを渡すことはできないので, polyから配列を取り出す.

int vertex_count = (int)poly.vertices.size(); // 総頂点数
int index_count = (int)poly.faces.size(); // 総ポリゴン数
btScalar *vertices = new btScalar[vertex_count*3]; // 頂点座標を格納する配列
int *indices = new int[index_count*3]; // ポリゴンを構成する頂点番号を格納する配列

// 頂点座標の取り出し
for(int i = 0; i < vertex_count; ++i){
    vertices[3*i] =   poly.vertices[i][0];
    vertices[3*i+1] = poly.vertices[i][1];
    vertices[3*i+2] = poly.vertices[i][2];
}
// ポリゴンを構成する頂点番号の取り出し
for(int i = 0; i < index_count; ++i){
    indices[3*i]   = poly.faces[i][0];
    indices[3*i+1] = poly.faces[i][1];
    indices[3*i+2] = poly.faces[i][2];
}

verticesに頂点の座標情報,indicesにポリゴンを構成する頂点番号が格納されている. 以下でこれらの情報からbtRigidBody, btSoftBodyそれぞれどのようにして設定するかを説明する.

btRigidBodyの場合
btRigidBodyの場合はbtTriangleIndexVertexArrayクラスを用いる.

int vertex_stride = 3*sizeof(btScalar);
int index_stride = 3*sizeof(int);

// 三角形メッシュ形状の作成
btTriangleIndexVertexArray* tri_array = new btTriangleIndexVertexArray(index_count, indices, index_stride,
                                                                       vertex_count, vertices, vertex_stride);

index_stride, vertex_strideはそれぞれindices,vertices配列において, 次のポリゴン,頂点を指し示す位置までのメモリ上のサイズ(Byte)である.

btTriangleIndexVertexArrayを作成したら, btGImpactMeshShapeで形状を設定し,それをbtRigidBodyに設定する. btGImpactMeshShapeを使うためには,まず,以下のインクルードを追加する.

#include "BulletCollision/Gimpact/btGImpactCollisionAlgorithm.h"
#include "BulletCollision/Gimpact/btGImpactShape.h"

そして,btTriangleIndexVertexArray型の変数を引数として,btGImpactMeshShapeをnewする.

btGImpactMeshShape *shape = new btGImpactMeshShape(tri_array);
shape->updateBound();

updateBound()を忘れないように. あとはこれをCollisionShapeとしてbtRigidBodyを設定すればよい. この処理は通常のbtRigidBodyの定義と同じなので省略する.

なお,セットし終わった後のindicesとverticesはdeleteせずにそのまま残しておくこと. 内部的にポインタ参照しているのでこれをdeleteすると参照ミスでプログラムが落ちることがある. ただし,これはbtGImpactMeshShapeの場合で,次の説明ページで使うbtSoftBodyの場合はデータをコピーしているので逆にdeleteする必要があることに注意.

dispatcherをbtGImpactMeshに設定
ポリゴンメッシュ同士を衝突させるには,btGImpactMeshにdispatcherを登録する必要がある. Bulletの初期化関数(InitBullet関数)内のdispatcher定義後に以下のコードを追加する.

btGImpactCollisionAlgorithm::registerAlgorithm(dispatcher);

3Dモデルの描画(興味がある場合のみ読むように)
btGImpactMeshShapeをOpenGLで描画するためのコードはすでにサンプルプログラム2には含まれているので, 描画するために新たに実装は必要ない.ただ,どのようにして描画しているのかを知りたい場合のためにOpenGLでの描画方法を説明する.

btGImpactMeshShapeやbtBvhTriangleMeshShapeはこれまでと異なり, 直接ポリゴンの情報を取得するのではなく, ポリゴンを描画するクラスを定義して,そのインスタンスを渡すことで描画を行う. ポリゴン描画クラスは例えば以下のようなものである(このコードはmain.cppではなくutils.hにある).

// ポリゴン描画処理のためのクラス(btTriangleCallbackの継承クラス)
class TriangleDrawCallback : public btTriangleCallback
{
public:
    TriangleDrawCallback(){}
    // 三角形ポリゴンを処理する関数.必ず設定しなければならない仮想関数(つまり純粋仮想関数)
    virtual void processTriangle(btVector3* triangle, int partId, int triangleIndex)
    {
        // 頂点座標からポリゴン法線を外積により計算
        btVector3 n = ((triangle[0]-triangle[1]).cross(triangle[0]-triangle[2])).normalize();
        glNormal3f(n[0], n[1], n[2]);

        glBegin(GL_TRIANGLES);
        glVertex3d(triangle[0][0], triangle[0][1], triangle[0][2]);
        glVertex3d(triangle[1][0], triangle[1][1], triangle[1][2]);
        glVertex3d(triangle[2][0], triangle[2][1], triangle[2][2]);
        glEnd();
    }
};

btTriangleCallbackクラスを継承し,processTriangle仮想関数が定義されていればどのようなものでもよい.

そして,Bulletオブジェクトを描画する関数(サンプルプログラムではDrawBulletObjects関数)での btRigidBodyの形状に応じた処理部分に以下のように追加する.

else if(shapetype == GIMPACT_SHAPE_PROXYTYPE){
    // 三角形メッシュ(GImpact)
    const btGImpactMeshShape* mesh = static_cast<const btGImpactMeshShape*>(shape);
    TriangleDrawCallback draw_callback; // ポリゴン描画クラスのインスタンス
    mesh->processAllTriangles(&draw_callback, world_min, world_max);
}

ちなみにGImpactはメッシュなどの処理や衝突検出のためのライブラリで, Bulletに統合されている.

bunny.objを読み込んで設定した例
bunny.objを読み込んで設定した例

練習問題1

説明部分にあった3Dモデルファイルを読み込むコードを実装して,三角形ポリゴンの登録を実際にやってみよう.

注) ポリゴン描画したときに一部のポリゴンが正常に描画されない(衝突判定はされている)状態になった場合は, ブロードフェーズにAABB木(btDbvtBroadphase)を使わずに, 3Dスイープ&プルーン(btAxisSweep3 or bt32BitAxisSweep3)などを使うように変更してみてください.

練習問題2(option)

検索すると様々なサイトでフリーの3Dモデルファイルが提供されているので, これらのサイトから対応する3Dモデルファイル(obj,wrl,dxf,3df,offなど)をダウンロードして読み込ませてみよう. (こちらのページ にもいくつかのサイトへのリンクを載せてあるので参照してほしい).

もし読めないファイル or 対応していない形式だったならば,3Dモデリングソフトでインポートして,他の形式(objなど)でエクスポートしてみよう. 実習室のPCにインストールされているソフトウェアの中では, MeshLabは多くの3Dファイル形式に対応している (MeshLabはオープンソースソフトウェアなので自分のPCにもインストールできる).

戻る