物理エンジンで学ぶC言語のStep4です.今回は構造体と物体へ力やトルクを加える方法,さらにシミュレーションのリセット法などを学びます.構造体の概要については既にわかっているものとし,サンプルコードを示すことにより具体的な使い方を学びます.例として、エアーホッケー風のゲームを作り、最終的には対戦相手のAIプログラムを作ってみよう。
○ 構造体
配列では同じ型しかまとめることができませんでしたが,構造体では違う型をまとめて扱うことができるのでシミュレーションなど物体に多くの属性がある場合に便利です.このサンプルプログラムでは物体を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 */ /* step4 エアーホッケー風ゲーム 2017-06-30 */ #include "dm4.h" #define FENCE_NUM 6 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[FENCE_NUM], bar, bar_ai; // ドミノ, フィールド, 柵 dmFunctions dmf; // 描画関数の構造体 /*** シミュレーションループ ***/ void simLoop(int pause) { int i; if (STEPS == 1) { dmAddForce(&ball, 0.8, 0, 0); // ボールの落下速度を決める } dmWorldQuickStep(); // シミュレーションを1ステップ進める(高速版) dmDraw(&ball); dmDraw(&bar); dmDraw(&bar_ai); dmDraw(&field); for (i =0; i < FENCE_NUM; 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 R_BAR[12] = {1,0,0,0, 0,1,0,0, 0,0,1,0}; // 姿勢 double R_BAR_AI[12] = {1,0,0,0, 0,1,0,0, 0,0,1,0}; // 姿勢 // double ball_pos[3]={-0.90, 0.25, 0.7}, ball_r = 0.03, ball_m = 0.1; // ボールの位置、半径、質量 double ball_pos[3]= {-0.5, 0, 0.525}, ball_r = 0.03, ball_m = 0.1; // ボールの位置、半径、質量 double bar_pos[3]= {-0.08, 0, 0.5}, bar_side[3] = {0.01, 0.1, 0.1}, bar_m = 0.1; // ボールの位置、半径、質量 double bar_ai_pos[3]= {-0.9, 0, 0.6}; double field_pos[3]= {-0.5, 0, 0.5}, field_side[3] = {1, 1, 0.01}, field_m; // スロープの位置、サイズ, 質量 double fence_pos[FENCE_NUM][3] = {{-1.0, 0.3125, 0.6}, {-1.0,-0.3125, 0.6},{0.0, 0.3125, 0.6},{0.0,-0.3125, 0.6},{-0.5, -0.5, 0.6},{-0.5, 0.5, 0.6}}; double fence_side[FENCE_NUM][3] = {{0.05, 0.425, 0.1}, {0.05, 0.425, 0.1},{0.05, 0.425, 0.1}, {0.05, 0.425, 0.1},{1.0, 0.05, 0.1}, {1.0, 0.05, 0.1} }; double fence_m[3] = {0.1, 0.1, 0.1}; double slope_R[12]= {1,0,0,0, 0,1,0,0, 0,0,0}; int i; STEPS = 0; // シミュレーションの終了 if (STEPS != 0) { dmClose(); } dmInit(); // 初期化 // ボールの生成 dmCreateSphere(&ball, ball_pos, R, ball_m, ball_r, red); // バーの生成 dRFromAxisAndAngle(R_BAR,0,0,1,M_PI/6); dRFromAxisAndAngle(R_BAR_AI,0,0,1,0); dmCreateBoxStatic(&bar, bar_pos, R_BAR, bar_m, bar_side, yellow); dmCreateBoxStatic(&bar_ai, bar_ai_pos, R_BAR_AI, bar_m, bar_side, red); // フィールドの生成 dmCreateBoxStatic(&field, field_pos, R, field_m, field_side, green); for (i =0; i < FENCE_NUM; 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
- step4-170630.zipをダウンロードして実行しよう!
- 黄色のバーをキーを押すと上下左右と姿勢を変化できるようにしよう。
ヒント: 黄色のバーには動力学を適応させていないのでdGeomSetPosition(dGeomID geom, double x, double y, double z);を使って強制的に位置を移動させる。ここで、1番目の引数にはbar.geomを入れればよい。2から4番目の引数は、絶対座標系での位置のx, y, z成分。姿勢はdGeomSetRotation(dGeomID geom, const dMatrix3 R);を使う。2番目の引数は回転行列R。好きな姿勢のRを取得するにはdRFromAxisAndAngle(dMatrix3 R, dReal ax, dReal ay, dReal az, dReal angle);を使う。 - 一人で遊べるようにしよう!
- 対戦相手を自動で動きプレーできるようにAIプログラムを作ろう!
コメント