Open Dynamics Engine 入門
【3日目】デモ「カードタワー」
物理シミュレータ ODE(Open Dynamics Engine)の入門編の3日目です。 「【1日目】球の描画と衝突判定」、 「【2日目】オブジェクトのジョイント」で、 ODEの基本形が理解できたので、3日目以降はODEに付属のデモプログラムをいじって、より深く理解していきます。
デモ「カードタワー」
カード状のオブジェクトを組み立てて、タワーを作っています。
キーボード「-」「=」で押すと、タワーの階層を「減らす」「増やす」し、
「(スペース)」でタワーをリセットできます。
(※「カードタワー」という名前は、自分が勝手につけました。)
ちょっと改造
これまで学習したことを踏まえてちょっと改造します。
カード同士の摩擦係数(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は直感的で一見わかりやすいですが、メンバ関数を利用したほうがいいかもしれません。




