HOME > natural science Laboratory > コンピュータ・シミュレーション講座 > ODE入門

Open Dynamics Engine 入門
【3日目】デモ「カードタワー」

文責:遠藤 理平 (2011年2月13日) カテゴリ:ODE入門(9)仮想物理実験室(268)

物理シミュレータ ODE(Open Dynamics Engine)の入門編の3日目です。 「【1日目】球の描画と衝突判定」、 「【2日目】オブジェクトのジョイント」で、 ODEの基本形が理解できたので、3日目以降はODEに付属のデモプログラムをいじって、より深く理解していきます。

デモ「カードタワー」

カード状のオブジェクトを組み立てて、タワーを作っています。
キーボード「-」「=」で押すと、タワーの階層を「減らす」「増やす」し、 「(スペース)」でタワーをリセットできます。
(※「カードタワー」という名前は、自分が勝手につけました。)

20110212-2.gif

ちょっと改造

これまで学習したことを踏まえてちょっと改造します。 カード同士の摩擦係数(0.1)と地面との摩擦係数(5.0)を別々に指定します。 カード同士がすべるのでタワーは崩壊します。
ちなみにデフォルトでは摩擦係数は「5.0」です。

プログラムソース

以下のプログラムソースは、C:/ode-0.11.1/ode\demo/demo_cards.cpp を利用させていただいております。

#include <vector>
#include <ode/ode.h>
#include <drawstuff/drawstuff.h>

int WindowWidth  = 640; //ウィンドウの幅
int WindowHeight = 480; //ウィンドウの高さ


#ifndef DRAWSTUFF_TEXTURE_PATH
#define DRAWSTUFF_TEXTURE_PATH "C:/ode-0.11.1/drawstuff/textures"
#endif
#ifdef dDOUBLE
#define dsDrawBox dsDrawBoxD
#endif

static int levels = 10;
static int ncards = 0;

static dSpaceID space;
static dWorldID world;
static dJointGroupID contactgroup;
dGeomID ground;

struct Card {//カードの構造体
    dBodyID body;//動力学計算用ボディID
    dGeomID geom;//衝突計算用ジオメトリID
    static const dReal sides[3];//カードのサイズ(lx,ly,lz)

    Card()//カード構造体のメンバ関数
    {
        body = dBodyCreate(world); //ボディの生成
        geom = dCreateBox(space, sides[0], sides[1], sides[2]);//ジオメトリの生成
        dGeomSetBody(geom, body);  //ジオメトリとボディとを関連付ける
        dGeomSetData(geom, this);  //本オブジェクトのポインタをセットする
        dMass mass;                //質量クラスのオブジェクトの生成
        mass.setBox(1, sides[0], sides[1], sides[2]);//ボックス型の質量をセットする
        dBodySetMass(body, &mass); //ボディに質量をセットする
    }
    ~Card()
    {
        dBodyDestroy(body);
        dGeomDestroy(geom);
    }
    void draw() const //カード描画用のメンバ関数
    {
        dsDrawBox(dBodyGetPosition(body),
                  dBodyGetRotation(body), sides);
    }
};
static const dReal cwidth=.5, cthikness=.02, clength=1;      //カードの幅、厚み、長さの変数を定義
const dReal Card::sides[3] = { cwidth, cthikness, clength }; //カードのサイズをセット


std::vector<Card*> cards;//可変長配列としてカードオブジェクトを生成

int getncards(int levels)//カードの総数を計算する関数
{
    return (3*levels*levels + levels) / 2;
}

void place_cards()//カードを設置(再配置)する関数
{
    ncards = getncards(levels);
    // destroy removed cards (if any)
    int oldcards = cards.size();
    for (int i=ncards; i<oldcards; ++i)
        delete cards[i];  //メモリの開放
    cards.resize(ncards);
    // construct new cards (if any)
    for (int i=oldcards; i<ncards; ++i)
        cards[i] = new Card; //メモリの確保
    // for each level
    int c = 0;
    dMatrix3 right, left, hrot;//回転行列用の変数の定義
    dReal angle = 20*M_PI/180.;
    dRFromAxisAndAngle(right, 1, 0, 0, -angle); //右回転行列の取得
    dRFromAxisAndAngle(left, 1, 0, 0, angle);   //左回転行列の取得
    dRFromAxisAndAngle(hrot, 1, 0, 0, 91*M_PI/180.); 
    
    dReal eps = 0.05; //余スペース
    dReal vstep = cos(angle)*clength + eps;//横方向への移動幅
    dReal hstep = sin(angle)*clength + eps;//縦方向への移動幅
    
    for (int lvl=0; lvl<levels; ++lvl) {//各階層ごとにカードを配置する
        // there are 3*(levels-lvl)-1 cards in each level, except last
        int n = (levels-lvl);
        dReal height = (lvl)*vstep + vstep/2;
        // inclined cards
        for (int i=0; i<2*n; ++i, ++c) {
            dBodySetPosition(cards[c]->body, //位置をセットするボディID
                    0,                       //x座標
                    -n*hstep + hstep*i,      //y座標
                    height                   //z座標
                    );
            if (i%2)
                dBodySetRotation(cards[c]->body, left);
            else
                dBodySetRotation(cards[c]->body, right);
        }
        
        if (n==1) // top of the house
            break;
        
        // horizontal cards
        for (int i=0; i<n-1; ++i, ++c) {
            dBodySetPosition(cards[c]->body,
                    0,
                    -(n-1 - (clength-hstep)/2)*hstep + 2*hstep*i,
                    height + vstep/2);
            dBodySetRotation(cards[c]->body, hrot);
        }
    }
    
}

void start()
{
  static float xyz[3] = {3.0,0.0,1.0}; //カメラの位置
  static float hpr[3] = {-180.0,0.0,0.0}; //カメラの方向
	//(z軸, y軸, x軸)における回転角度({0.0,0.0,0.0}でx軸方向を向いている)
	//上の場合は、y軸の方向を向いている
	dsSetViewpoint (xyz,hpr); //カメラの設定

    puts("Controls:");
    puts("   SPACE - reposition cards");
    puts("   -     - one less level");
    puts("   =     - one more level");
}

static void nearCallback (void *data, dGeomID o1, dGeomID o2)
{
    // exit without doing anything if the two bodies are connected by a joint
    dBodyID b1 = dGeomGetBody(o1);
    dBodyID b2 = dGeomGetBody(o2);

    const int MAX_CONTACTS = 8;
    dContact contact[MAX_CONTACTS];
    
    int numc = dCollide (o1, o2, MAX_CONTACTS,
                        &contact[0].geom,
                        sizeof(dContact));
    int isGround = ((ground == o1) || (ground == o2));
      for (int i=0; i<numc; i++) {
          contact[i].surface.mode = dContactApprox1;
          if(isGround) contact[i].surface.mu = 5;  //<-------------------地面との摩擦係数
          else         contact[i].surface.mu = 0.1;//<-------------------カード同士の摩擦係数
          dJointID c = dJointCreateContact (world, contactgroup, contact+i);
          dJointAttach (c, b1, b2);
      }  
}
void simLoop(int pause)
{
    if (!pause) {
        dSpaceCollide (space, 0, &nearCallback);
        dWorldQuickStep(world, 0.01);
        dJointGroupEmpty(contactgroup);
    }
    
    dsSetColor (1,1,0);
    for (int i=0; i<ncards; ++i) {
        dsSetColor (1, dReal(i)/ncards, 0);
        cards[i]->draw();
    } 
}

void command(int c)
{
    switch (c) {
        case '=':
            levels++;
            place_cards();
            break;
        case '-':
            levels--;
            if (levels <= 0)
                levels++;
            place_cards();
            break;
        case ' ':
            place_cards();
            break;
    }
}

int main(int argc, char **argv)
{
    dInitODE();
    
    // setup pointers to drawstuff callback functions
    dsFunctions fn;
    fn.version = DS_VERSION;
    fn.start = &start;
    fn.step = &simLoop;
    fn.command = &command;
    fn.stop = 0;
    fn.path_to_textures = DRAWSTUFF_TEXTURE_PATH;
    
    
    world = dWorldCreate();
    dWorldSetGravity(world, 0, 0, -0.5);
    dWorldSetQuickStepNumIterations(world, 50); // <-- increase for more stability
    
    space = dSimpleSpaceCreate(0);
    contactgroup = dJointGroupCreate(0);
    ground = dCreatePlane(space, 0, 0, 1, 0);
    
    place_cards();
    
    // run simulation
    dsSimulationLoop (argc,argv,WindowWidth, WindowHeight,&fn); //シミュレーション用の無限ループ

    levels = 0;
    place_cards();
    
    dJointGroupDestroy(contactgroup);
    dWorldDestroy(world);
    dSpaceDestroy(space);
    
    dCloseODE();
}

サンプルプログラムで理解したこと

次の2つのソースは同じ動作を示します。

//箱型質量オブジェクトの設定
dMass mass;                  //質量クラスのオブジェクトを生成
dReal lx=1.0, ly=1.0, lz=1.0;//辺の長さ
dReal density=1.0;           //密度

mass.setBox(weight, lx, ly, lz); //ボックス型の質量をセットする
dBodySetMass(body, &mass);   //ボディに質量をセットする
//箱型質量オブジェクトの設定
dMass mass;                  //質量クラスのオブジェクトを生成
dReal lx=1.0, ly=1.0, lz=1.0;//辺の長さ
dReal density=1.0;           //密度

dMassSetBox(&mass, weight, lx, ly, lz);//ボックス型の質量をセットする
dBodySetMass(body, &mass);   //ボディに質量をセットする

違うのは、質量クラスのオブジェクトである変数 mass に値を代入する時に、
上は、質量クラスのメンバ関数であるSet関数を利用し、
下は、ODEのAPIであるdBodySetMass関数を利用していることです。
VisualC++ではIntelliSense(インテリセンス)機能が利用できるため(VisualC++ 2010 Expressでは現在利用できないようです)、 「mass.」というようにオブジェクトに「.」をつけると、メンバ変数とメンバ関数の一覧が候補としてリストされます。 つまり、どんなメンバ変数やメンバ関数があるのかわかるため、何ができるのかが一目でわかります(メンバの名前を見ればなんとなく見当がつきます)。 たとえ、どんなAPIがあるのかがわからなくても、オブジェクトに「.」をつけるだけなので、非常に効率的にプログラミングすることができます。
APIは直感的で一見わかりやすいですが、メンバ関数を利用したほうがいいかもしれません。



タグ: ,

▲このページのトップNPO法人 natural science トップ

関連記事

ODE入門

仮想物理実験室







▲このページのトップNPO法人 natural science トップ