Bullet 2.76 Physics SDK マニュアル
7. 剛体ダイナミクス
はじめに
剛体ダイナミクスには衝突判定モジュールが実装されており、力や質量、慣性、速度や拘束条件を扱います。
- btRigidBodyは基本となる剛体オブジェクトで、動きがあり、0より大きい質量と慣性を持ちます。btRigidBodyはbtCollisionObjectから派生したクラスですので、ワールド変換行列、摩擦、反発係数を継承しており、それに加えて直線速度と回転速度を持ちます。
- btTypeConstraintは剛体拘束条件の基本クラスです。下位クラスとしてbtHingeConstraint, btPoint2PointConstraint, btConeTwistConstraint, btSliderConstraint and btGeneric6DOFconstraintがあります。
- btDiscreteDynamicsWorldはbtCollisionWorldから派生したクラスで、剛体と拘束条件をもつコンテナです。stepSimulationメソッドを提供します。
静的/動的/Kinematic剛体
Bulletでは、オブジェクトには次の3つの種類があります。
- 動的(動きのある)剛体
- 正の質量
- 毎フレームごとにワールド変換行列が更新される
- 静的剛体
- 質量ゼロ
- 衝突はするが、動かない
- Kinematic剛体
- 質量ゼロ
- ユーザーが動かすことができる。動的オブジェクトを押したりすることはできるが、オブジェクトからは影響を受けない。つまり一方通行。
これらは全て力学ワールドに登録して使う必要があります。剛体には衝突形状を割り当てることができます。この形状は質量分布の計算に使用することができ、これは慣性テンソルとも呼ばれます。
ワールド変換行列
Bulletでは、剛体のワールド変換行列は常に質量の中心とイコールであり、それを前提として慣性も定義されています。ローカルの慣性テンソルは形状依存であるため、btCollisionShapeクラスは質量を引数としてローカル慣性を計算するメソッドを提供しています。
このワールド変換行列は剛体変換を表しています。すなわちスケーリングや変形を含みません。オブジェクトをスケーリングしたい場合は、衝突形状をスケーリングしてください。その他変形などについては、必要であれば三角形メッシュの頂点に直接行ってください。
衝突形状が質量の中心と一致していない場合、一致させるように動かすことができます。これには、btCompoundShapeを使って子の衝突形状を子の変換行列を使って動かします。
MotionStateとは?
MotionStateはBulletが一生懸命働いて導き出したオブジェクトのワールド変換行列を、あなたのプログラムに受け渡すインターフェースです。
大抵の場合、ゲームループ内でシミュレーション対象となるすべてのオブジェクトに対して繰り返し操作を行い、その中でオブジェクトの位置を更新し、描画を行うことになると思います。BulletはこれをMotionStateというもので効率的に処理することができます。
MotionSteteには、以下のような利点もあります。
- 動いた物体の周辺だけ、計算を行います。動かなかったオブジェクトに対して位置を毎回更新しても意味がありません。
- 不要なものを描画しなくてよくなります。物体が動き、更新の必要があるものだけネットワークで通知するという効果的なネットワークコードを書くことができます。
- 画面に映っている状況においてのみ、補間処理を行うことができます。Bulletは物体の補間処理をMotionStateを通して管理します。
- グラフィックオブジェクトと質量の中心を一致させておくことができます。
- 簡単に使えます。
補間処理
Bulletは物体の動きを補間してくれます。上述の通り、補間処理はMotionStateにて管理されます。
btCollisionObject::getWorldTransform もしくは btRigidBody::getCenterOfMassTransformを使うと、最新時刻の位置が返ってきます。これはこれで非常に便利ですが、場合によっては補間処理を行った上で描画したい場合があります。Bulletは、setWorldTransformに値を渡す前に、物体位置を補間することができます。
もし補間処理が必要なく、最後の物理計算時刻で計算された位置が必要な場合は、btRigidBody::getWorldTransform()を直接使ってください。
使い方
BulletではMotionStateは以下の2箇所で使用されます。
1. 1つは、物体が生成されたときです。Bulletはシミュレーションが開始されたときに、物体の初期位置をMotionStateから取得します。
- Bulletは変換情報を入れる変数の参照を引数としてgetWorldTransformを呼びます。
- BulletはKinematicオブジェクトでも同じようにgetWorldTransformを呼び出します。これについては後述します。
2. 1の更新が行われた後、Bulletは周辺物体のMotionStateを呼び出し、物体を動かします。
- BulletはsetWorldTransformを呼び、適切にオブジェクトを更新します。
これらを自身で実装する場合は、btMotionStateを継承して getWorldTransform と setWorldTransformをオーバーライドしてください。
DefaultMotionState
自分でbtMotionStateの派生クラスを作る必要はありません。BulletはデフォルトのMotionStateを提供しています。デフォルトの変換を行う場合は、以下のようにMotionStateを作成してください。
btDefaultMotionState* ms =new btDefaultMotionState();
付録にて、Ogre3D MotionStateの例を掲載しています。
Kinematicオブジェクト
静的オブジェクトを動かす必要がある場合は、Kinematicフラグを立てて使ってください。またアニメーションしている間はsleeping/deactivationを無効にしてください。これは、Bulletの力学ワールドが毎フレームbtMotionStateから新しいワールド変換行列を取得するためです。
body->setCollisionFlags( body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
body->setActivationState(DISABLE_DEACTIVATION);
もしKinematicオブジェクトを使う場合は、毎ステップごとにgetWorldTransformを呼んでください。Kinematicオブジェクトが他のオブジェクトを押し出す仕組みを持っているためです。
シミュレーションと補間処理
デフォルトでは、Bulletの物理シミュレーションは内部フレームレート60Hz(0.01666)で固定されています。ゲームやアプリケーションでは、異なるフレームレート、または変動フレームレートを使っている場合があります。アプリケーションのフレームレートとシミュレーションのフレームレートが一致しない場合は、自動補間メソッドがstepSimurationに組み込まれます。アプリケーションの差分時間が内部の固定タイムスタンプよりも小さい場合、Bulletは物理シミュレーションを行わずに、ワールド変換行列を補間し、それをbtMotionStateへ送ります。アプリケーションタイムスタンプのほうが大きい場合は、各stepSimulationで物理シミュレーションステップを1回多く行います。このシミュレーションステップの最大値は、2番目の引数にてユーザーが指定することができます。
剛体が生成されたとき、btMotionState::getWorldTransformメソッドを使ってMotionStateから初期ワールド変換行列を取得します。stepSimurationによりシミュレーションが実行されると、btMotionState::setWorldTransformにより対象の剛体について、新しいワールド変換行列が設定されます。
剛体は正の質量を持ち、その動きはシミュレーションにより決定されます。静的剛体とKinematic剛体は質量ゼロです。静的オブジェクトはユーザーの手で動かすべきではありません。