ODEのジョイントで角度ばねを実現する

2013-05-30

snake

物理エンジンと遺伝的アルゴリズムを使ってなんかする、という動画たち

が面白かったので、俺もそういうのをやってみたい、と思った。

WEB上で動かせるといいので、JavaScriptで動く物理エンジンを探してみたところ、Physijsというものがあった。 これはThree.jsというJavaScript用の3D描画エンジンのプラグインになっているので、3D表示が統合されていて便利。 物理計算はammo.jsという、C++で書かれたBullet Physicsという物理エンジンをEmscriptenでJavaScriptにコンバートしたものを使っている。 そこそこ動いてよかったんだけど、剛体間に角度バネみたいな拘束をさせたくて、剛体にapplyForce()をしてみたところ物体が消えてしまい、原因や解決方法がよくわからなかった。

しかたがないので一旦基本に戻って、C++でやってみることにした。 使う物理エンジンはBullet Physicsじゃなくて、導入の簡単さからOpen Dynamics Engineで試してみた。 ODEは使う浮動小数点数型をSINGLE(float)かDOUBLEを選べるんだけど、ちょっと試したところSINGLEだと計算エラーが発生しやすい感じだったので、configure時に–enable-double-precisionをつけてやったほうがよさそう。 それでも頻繁に計算エラーや発散が発生するので、実際のアプリでの利用は大丈夫なのか心配。

で角度バネをどうやって実装しようかとぐぐったところ、ODEのジョイント(拘束)は固いバネ+ダンパーのような仕組みでできているので、うまくパラメータを設定するとそのようなことが実現できるらしい(Problems, Solutions, and Ideas: Implicit springs in the Open Dynamics Engine (ODE))。 でジョイントは角度などの最小値と最大値を設定できるので、両方共に同じ値を設定してやれば希望の角度にダンパー付きバネで収束してくれる。

dReal angle1 = M_PI / 2, angle2 = M_PI / 2;
dJointID joint = dJointCreateUniversal(world, 0);
dJointAttach(joint, body1, body2);
dJointSetUniversalAnchor(joint, x, y, z);
dJointSetUniversalAxis1(joint, 0, 0, 1);
dJointSetUniversalAxis2(joint, 0, 1, 0);

dReal h = 0.02; // タイムステップ
dReal kp = 4; // バネ係数
dReal kd = 1.0; // ダンパー係数
dReal erp = h * kp / (h * kp + kd);
dReal cfm = 1.0 / (h * kp + kd);
dJointSetUniversalParam(joint, dParamLoStop, angle1);
dJointSetUniversalParam(joint, dParamHiStop, angle1);
dJointSetUniversalParam(joint, dParamStopERP, erp);
dJointSetUniversalParam(joint, dParamStopCFM, cfm);
// 0x100をorすると2つめのパラメータの設定になる
dJointSetUniversalParam(joint, dParamLoStop | 0x100, angle2);
dJointSetUniversalParam(joint, dParamHiStop | 0x100, angle2);
dJointSetUniversalParam(joint, dParamStopERP | 0x100, erp);
dJointSetUniversalParam(joint, dParamStopCFM | 0x100, cfm);

ということで、複数の剛体をユニバーサルジョイント(2軸回転)でつないで、角度バネを設定して、へびっぽい物体を作ってみた。

今後は遺伝的アルゴリズムというのを試してみたい。