main関数

さて、sample0で主処理を行っているmain関数を読んでみましょう。

読むファイルは引き続きsample0.cppです。

初期化

// ワールドの作成
std::cout << "make world" << std::endl;
partix::World< SampleTraits >* world =
    new partix::World< SampleTraits >;

物理的事象全てを扱うWorldオブジェクトを生成しています。

partix内部ではstatic変数・global変数を使っていないので、

複数の世界を作りたければ複数のインスタンスを作ればOKです。

// モデルの読み込み(2体)
std::cout << "load models" << std::endl;
partix::SoftVolume< SampleTraits >* volume0;        
partix::SoftVolume< SampleTraits >* volume1;
partix::TetrahedralMesh< SampleTraits >* mesh0;
partix::TetrahedralMesh< SampleTraits >* mesh1;

make_softvolume( "data/pig", PIG_MASS, volume0, mesh0 );
make_softvolume( "data/pig", PIG_MASS, volume1, mesh1 );
world->add_body( volume0 );
world->add_body( volume1 );

volume0->teleport( VectorTraits::make_vector(  0, 3.0f, 0 ) );
volume1->teleport( VectorTraits::make_vector(  0, 1.0f, 0 ) );

SoftVolumeを2つ作っています。

make_softvolumeの中で、TetrahedralMeshをSoftVolumeにアサインし、さらにそのSoftVolumeをworld->add_bodyで先ほど作ったWorldに追加しています。make_softvolume関数は大したことはしてないので、適当にソースを読んでください。

partixでは、ライブラリユーザがnewしたオブジェクトの所持権を受け取ることはありませんので、world->add_bodyしたからといって削除の必要がなくなるわけではありません。そういったわけで、各オブジェクトは、可能であればnewする代わりにスタックに積んだりしてもかまいません。ただ現在のところ内部では動的割り当てをしまくっており、それらをカスタマイズするインターフェイスは用意していないので、見えているオブジェクトだけスタックに積んでもさほど意味はないと思いますが。

余裕ができたらカスタマイズ可能にする予定です。

// 床の作成
std::cout << "make floor" << std::endl;
partix::BoundingPlane< SampleTraits >* floor = 
    new partix::BoundingPlane< SampleTraits >(
        VectorTraits::make_vector( 0, -1.0f, 0 ),     // 中心座標
        VectorTraits::make_vector( 0,  1.0f, 0 )      // 法線
        );
world->add_body( floor );

床オブジェクト(有向平面)を作っています。

動作的には、平面というか、半空間に近いような変な働きをします。交差しているオブジェクトを平面の向きの方向に押し出すような働きです。

// 重力の設定
std::cout << "set gravity" << std::endl;
world->set_global_force( VectorTraits::make_vector( 0, -3.0f, 0 ) );

重力を設定しています。

今回はだいたいこれで終わりです。

シミュレーション処理

// シミュレーション
std::cout << "start simulation" << std::endl;
world->restart();
for( int i = 0 ; i <= 250 ; i++ ) {
    world->update( SIMULATION_TICK );

    // SVGで出力
    if( i % 10 == 0 ) {
        char filename[256];
        sprintf( filename, "result/world%03d.svg", i / 10 );
        svgout svg( filename, 512, 512 );

        svgout_mesh( svg, mesh0, "navy" );
        svgout_mesh( svg, mesh1, "blueviolet" );
        std::cout << "output: " << filename << std::endl;
    }
}

これが主処理なんですが、あまり説明することはないですね。

svgout_meshは投影変換してsvgに出力する関数ですが、大したことはしていないのでソースを読んでください。

……おっと、一個だけ割と重要なことがありました。world->update()に与える経過時間です。

こういった物理シミュレーションでは、多くの場合、画面更新よりも頻繁なタイミングで物理世界の時間更新を行う必要があります。

仮に画面の更新が1/60秒だとすると、物理は1/100〜1/200くらいの間隔で更新を行わないと、衝突判定などに支障がでてしまいます。

partixも例外ではないのですが、continuous collision detection*1を行わない物理エンジンの中では比較的寛容な方だと思います。

もちろん内部で動かす物体の最大のスピードがどのくらいかとか、物体のサイズ比の最大がどのくらいか、などによっても話は変わってくるのですが、例えばはちゅね山積みのケースでは1/60(画面更新と同じ)で普通に動いています。はちゅねボーリングでは1/60だとボールがひしゃげすぎてよろしくなかったので、1/120にしました。

このworld->updateに与える経過時間は一定であることを強要していない(はず)なので、うまく使ってください。

後始末

// 後始末
delete floor;
delete mesh0;
delete mesh1;
delete volume0;
delete volume1;
delete world;

ユーザプログラム中でnewしたオブジェクトをdeleteすれば終わりです。

*1:すり抜けのない継続的な衝突判定