物理エンジンODEで学ぶC言語 [STEP4:構造体]力とトルクの与え方

ブロック崩しを作ろう!

物理エンジンで学ぶC言語のStep4です.今回は構造体と物体へ力やトルクを加える方法,さらにシミュレーションのリセット法などを学びます.構造体の概要については既にわかっているものとし,サンプルコードを示すことにより具体的な使い方を学びます.例として、Step4とStep5でスティーブン・ジョブスがAtari社時代に、アップルの共同設立者ウォズニアックと開発に携わったBreakout(ブロック崩し)風のゲームを作ってみましょう。

○ 構造体

配列では同じ型しかまとめることができませんでしたが,構造体では違う型をまとめて扱うことができるのでシミュレーションなど物体に多くの属性がある場合に便利です.このサンプルプログラムでは物体をdmObjectという構造体で次のように表しています.dm4.hの16行目に定義しています.

typedef struct{
    dBodyID body; // ボディのID
    dGeomID geom; // ジオメトリのID
    const double *p; // x, y, z [m]
    const double *R;   // 回転行列 要素数4x3
    double m; // 質量 [kg]
    double r,l; // 半径 [m], 長さ [m]
    const double *side; // サイズ x,y,z
    const double *color; // 色 r,g,b
} dmObject;

また、STEP3まではdmLoop関数の引数にstep関数やcommand関数を渡していましたが、ここでは関数のポインタを要素に持つ構造体dmFunctionsを定義します。今まではdmLoopに渡す関数が増えるとその数だけ引数が多くなってしまいましたが、この構造体だけを引数として渡せば良いのでプログラムがすっきりします。

typedef struct
{
    void (*start)();            // 初期化関数
    void (*step) (int pause);	// ステップ関数
    void (*command) (int cmd);	// キー関数
} dmFunctions;

 衝突検出だけ働く物体の作り方

物理エンジンODEでは物体は2つの属性からできています。一つは動力学計算の対象となるbody(ボディ)、もう一つは衝突検出の対象となるgeometry(ジオメトリ)です。ゲームでは動かない建物や壁など動力学計算を働かせる必要のない物体は衝突検出計算だけを適応すれば計算処理が軽くて済みます。ここでは、衝突検出計算だけ働く物体を作るAPIを紹介します。

  • void dmCreateBoxStatic(dmObject *obj, double p[3], double R[12], double m, double side[3], double color[3]);

衝突検出計算しか作用しない物体を作成します.dmCreateBoxの後にStatic(静的)を付け加えたAPI名です。引数のp[3]は位置(x,y,z)[m],R[12]は姿勢(回転行列), mは質量[kg],sideは直方体の各辺の長さ[m],color[3]は色(赤,緑,青各成分、値は0以上1以下)です.同様なAPIとして、dmCreateSphereStatic, dmCreateCylinderStatic, dmCreateCapsuleStaticがあります。

姿勢の変更

物体の姿勢を変更するためには次のAPIを使い回転行列Rの値を変更します.ここで,Rは回転行列が格納されている配列へのポインタ,ax, ay, axは回転軸ベクトル.angleは回転角度となります.なお,dだけで始まるAPIはODEのAPIです.

  • dRFromAxisAndAngle(double R[12], double  ax, doulbe  ay, double  az, double angle);

○ 力,トルクの加え方

  • void dmAddForce(dmObject *obj, double fx, double fy, double fz)
    • 物体objの重心に力(fx,fy,fz)を加える
  • void dmAddTorque(dmObject *obj, double fx, double fy, double fz)
    • 物体objの重心にトルク(fx, fy, fz)を加える.fx,fy,fzはそれぞれx, y, z軸まわりのトルク

○ 高速なシミュレーション

前回のサンプルではシミュレーションのステップ関数としてdmWorldStep()を使いましたが,ここではより高速なdmWorldQuickStep()を使います.ただし,dmWorldStep()と比較して精度が悪くなります.

○ シミュレーションのリセット

rまたはRキーを押すと,resetSim関数が呼ばれてシミュレーションがリセットされます.resetSimの中身はシミュレーションループが1回以上呼び出されたときにdmInit関数を呼び出して初期化し,物体を再度生成しています.

○ ソースコード

/* step4 ブロック崩し 2016-06-22  */
#include "dm4.h"

static int STEPS = 0;   // シミュレーションのステップ数
double red[3]     = {1.3, 0.0, 0.0}; // 赤色
double yellow[3] = {1.3, 1.3, 0.0}; // 黄色
double green[3] = {0.0, 0.8, 0.0}; // 緑色

dmObject ball, field, fence[3], bar; // ドミノ, フィールド, 柵
dmFunctions dmf; // 描画関数の構造体

/***  シミュレーションループ ***/
void simLoop(int pause)
{
    int i;
    dmAddForce(&ball, 0.0001, 0, 0); // ボールの落下速度を決める
    dmWorldQuickStep(); // シミュレーションを1ステップ進める(高速版)

    dmDraw(&ball);
    dmDraw(&bar);
    dmDraw(&field);
    for (i =0; i < 3; i++)
    {
        dmDraw(&fence[i]);
    }

    STEPS++;
}

void resetSim(int n)
{
    double m = 0.1; // 質量
    double side[3] = {0.2, 0.05, 0.5}; // サイズ
    double R[12] = {1,0,0,0, 0,1,0,0, 0,0,1,0}; // 姿勢
    double ball_pos[3]={-0.91, 0.25, 0.7}, ball_r = 0.03, ball_m = 0.1; // ボールの位置、半径、質量
    double bar_pos[3]={-0.08, 0, 0.6}, bar_side[3] = {0.01, 0.1, 0.1}, bar_m = 0.1; // ボールの位置、半径、質量
    double field_pos[3]={-0.5, 0, 0.5}, field_side[3] = {1, 1, 0.01}, field_m; // フィールドの位置、サイズ, 質量
    double fence_pos[3][3] = {{-1.0, 0, 0.6}, {-0.5, -0.5, 0.6},{-0.5, 0.5, 0.6}}; // 柵の位置
    double fence_side[3][3] = {{0.05, 1.05, 0.1}, {1.0, 0.05, 0.1}, {1.0, 0.05, 0.1}};
    double fence_m[3] = {0.1, 0.1, 0.1};
    int i;

    // シミュレーションの終了
    if (STEPS != 0)
    {
        dmClose();
    }

    dmInit(); // 初期化

    //  ボールの生成
    dmCreateSphere(&ball, ball_pos, R, ball_m, ball_r, red);

    // バーの生成
    dmCreateBoxStatic(&bar, bar_pos, R, bar_m, bar_side, yellow);

    // フィールドの生成
    dmCreateBoxStatic(&field, field_pos, R, field_m, field_side, green);
    for (i =0; i < 3; i++) {
        dmCreateBoxStatic(&fence[i], fence_pos[i], R, fence_m[i], fence_side[i], green);
    }
}

/*** キ―入力関数 ***/
void command(int cmd)
{
    switch (cmd)
    {
    case 'r':
    case 'R':
        resetSim(0);
        break;
     default:
        printf("Input r, R, f, F key \n");
        break;
    }
}

/*** カメラの位置と姿勢設定 ***/
void setCamera()
{
    float x =  -0.5, y = 0.0, z = 1.5;    // カメラの位置
    float roll = 0, pitch = -90, yaw = -180; // カメラの方向[°]
    dmSetCamera(x,y,z,roll,pitch,yaw);  // カメラの設定
}

/*** 描画用構造体の設定 ***/
void setDraw()
{
    dmf.start       = &setCamera;
    dmf.step        = &simLoop;
    dmf.command = &command;
}

/*** main関数 ***/
int main()
{
    resetSim(0); // シミュレーションのリセット
    setDraw();   // 描画関数の設定
    dmLoop(800, 600, &dmf);  // ウインドウの幅,高, ループ関数,コマンド関数
    dmClose(); // 終了
    return 0;
}

ホームワーク5

  1. step4-160622.tgzをダウンロードして実行しよう!
  2. 黄色のバーをfまたはFキーを押すと左、jまたはJキーを押すと右にバーが動くように変更しよう!
    ヒント: 黄色のバーには動力学を適応させていないのでdGeomSetPosition(dGeomID geom, double x, double y, double z);を使って強制的に位置を移動させる。ここで、1番目の引数にはbar.geomを入れればよい。2から4番目の引数は、絶対座標系での位置のx, y, z成分。
  3. ボールが斜めに落ちるように、初めに横方向に力を加えよう!
    ヒント:dmAddForce(dmObject *obj, double fx, double fy, double fz); を使おう。resetSim関数内のdmCreateSphere関数のすぐ後にdmAddForceを入れよう。なお、力の方向や大きさを乱数を使って変化させるとゲームが面白くなる。
  4. ブロックを3行10列、合計30個表示しよう!各行で色を変えると見栄えがするよ。
    ヒント: dmObject  fence[3]; ボール落下防止用の柵に関するコードを参考にすると良い。
  以上

コメント

タイトルとURLをコピーしました