簡易剛体運動(自動車)は何をしているの?
簡易剛体運動を使用して自動車を動かす説明です。
通常、剛体運動を行うには複雑な計算が必要ですが、複雑な計算を行わずに剛体を動かしてみようというものです。
はじめに
簡易剛体運動は、剛体として定義した8つの質点を全てラインで繋ぎ、 繋がれている質点同士をバネモデルとして移動することによって、剛体を表現しています。
質点の定義
簡易剛体運動を行うには、まず剛体の質点を定義する必要があります。 質点は、実際に剛体として動かすオブジェクトのバウンディングボックスでもかまいませんし、 バウンディングボックスとは異なる大きさのものでもかまいません。 図1のP0~P7は8つの質点のポイントです。
質点を定義する際に注意しなければならないのは、現実の自動車でもそうですが、 例えば極端に縦に長いなど、有りえない形の質点を定義してしまうと、 実際に質点を移動した時に、すぐに倒れてしまったりします。 自動車でなければ、そのような形でも問題ないと思いますが、今回は自動車なので注意してください。
質点を定義したら、図2のように各質点を繋ぐラインを作成します。 このラインには、情報としてそれぞれどの質点同士がつながっているのかと、繋がっている質点の距離を保存しておきます。 バネモデルでは、ここで保存した距離に近づこうと各質点を移動させます。
図ではわかりにくい部分もあるので、各質点を繋ぐラインをリストアップしてみます。
| P0-P1 | P2-P3 | P0-P2 | P1-P3 | P4-P5 |
| P6-P7 | P4-P6 | P5-P7 | P0-P4 | P1-P5 |
| P2-P6 | P3-P7 | P0-P7 | P1-P6 | P2-P5 |
| P3-P4 | P0-P3 | P1-P2 | P4-P7 | P5-P6 |
| P0-P6 | P2-P4 | P1-P7 | P3-P5 | P0-P5 |
| P1-P4 | P2-P7 | P3-P6 |
このように、28本のラインが必要になります。 このライン数は減らすことも可能ですが、減らすと剛体が安定しなかったりしますので、減らす場合はいろいろ調整してみてください。
剛体(自動車)を動かす
質点とラインの設定が終わったら、いよいよ剛体を動かしてみます。
この剛体は自動車ですので、前進させるにはアクセルが必要です。 実際の自動車でもそうですが、アクセルを踏むとタイヤが回って前進します。
簡易剛体運動では、質点をタイヤに見立てて移動させます。 前進する時に動かす質点は、底面にあるP0~P3です。 四輪駆動車ならばP0~P3すべてを動かし、前輪駆動車ならばP0とP1、後輪駆動車ならばP2とP3を動かします。 前進させる時の質点の方向は、P0-P2ベクトルの方向です。 もちろん、このベクトルのマイナス方向へ移動させれば後退します。
同様にステアリングを切った時に動かす質点は、底面にあるP0とP1になります。 本来は自動車が停止中にステアリングを切っても動くことはないので、 停止しているかどうかを調べてから、ステアリング用の質点は動かさなければなりません。 何も考えずにこれらの質点を動かすと、停止しているのにその場でぐるぐる回る自動車になってしまいます (ゲームの仕様でそのような動きをするマシンならそれでも構いません)。 ステアリングを切った時の質点の移動方向は、P0-P1ベクトルの方向です。 このベクトルのプラス方向に動かすかマイナス方向に動かすかは、 ステアリングを右に切ったか左に切ったかで変わります。
それではまず、アクセルを踏んだだけの状態を四輪駆動車で考えてみます。 アクセルを踏んで前進させた直後の剛体の状態は図3のようにP0~P3だけが移動した状態になっています。 P0'~P3'は、移動後のP0~P3です。 前進をさせた時点では、P4~P7はまだ移動させません。
タイヤの移動が終わったら、各ラインに繋がれた質点の位置を調整します。 調整は、ラインに繋がれた2つの質点の位置を使って、数値微分と数値積分を行います。 微分の係数や積分の回数は、プログラマのさじ加減です。 微分係数と積分回数が小さすぎたりすると、剛体が安定しなかったりします。 面白いので、このあたりの数値はいろいろと調整してみると良いかもしれません。
まずはラインに繋がれた質点1と質点2の差分を計算(コード①)し、その距離(コード②)と質点1-質点2の単位ベクトル(コード③)を求めます。 次に、現在の質点1の位置から先ほどの単位ベクトルの方向へ、数値微分を行った値だけ移動します(コード④)。 同様に質点2も移動します(コード⑤)。質点2の場合、固定距離と差分距離の減算する方向に注意してください。 この計算は、質点同士を繋ぐ全てのラインに対して、たとえそのフレームに質点の移動が行われなかったとしても毎フレーム行います。
具体的には以下のコードのような計算を行って、徐々に本来の固定距離に質点同士を近づけて行きます。 (サンプルコードのalpLine、iIntegralはあらかじめ初期化されているものとします)
class CRigidBodyLine{
private:
D3DXVECTOR3 m_vec3Point1; //ラインに繋がれた質点1の位置
D3DXVECTOR3 m_vec3Point2; //ラインに繋がれた質点2の位置
float m_fFixedDistance; //ラインに繋がれた質点1と質点2の本来の固定距離
float m_fDifferential; //微分の係数
public:
CRigidBodyLine(void) {}
~CRigidBodyLine(void) {}
//初期化
void Initialize(D3DXVECTOR3* p_lpvec3Point1, D3DXVECTOR3* p_lpvec3Point2,
float p_fDifferential);
//数値微分を行って距離を調整
void AdjustDistance(void);
};
//初期化
void CRigidBodyLine::Initialize(D3DXVECTOR3* p_lpvec3Point1, D3DXVECTOR3* p_lpvec3Point2,
float p_fDifferential)
{
m_vec3Point1 = *p_lpvec3Point1;
m_vec3Point2 = *p_lpvec3Point2;
m_fFixedDistance = D3DXVec3Length(&(m_vec3Point1 - m_vec3Point2));
m_fDifferential = p_fDifferential;
}
//数値微分を行って距離を調整
void CRigidBodyLine::AdjustDistance(void)
{
D3DXVECTOR3 vec3Diff; //質点1と質点2の差分
float fDistance; //質点1と質点2の距離
D3DXVECTOR3 vec3DiffNormal; //質点1と質点2の差分の正規化ベクトル
//現在の質点の位置の差分を計算
vec3Diff = m_vec3Point1 - m_vec3Point2; ……… ①
fDistance = D3DXVec3Length(&vec3Diff); ……… ②
D3DXVec3Normalize(&vec3DiffNormal, &vec3Diff); ……… ③
//固定距離を調整して新しい位置を計算
m_vec3Point1 = m_vec3Point1
+ (vec3DiffNormal * (m_fFixedDistance - fDistance) * m_fDifferential); ……… ④
m_vec3Point2 = m_vec3Point2
+ (vec3DiffNormal * (fDistance - m_fFixedDistance) * m_fDifferential); ………
}
int iIntegral; //積分の回数
//数値微分と数値積分を使って質点の位置を調整
for(int i = 0; i < iIntegral; ++i){
for(int j = 0; j <= 27; ++j){
alpLine[j]->AdjustDistance();
}
}
全ての質点の調整が終わると、P4~P7の質点も自動的にちょうど良い位置に移動しています。
一応、以上の処理だけでも剛体の移動は可能なのですが、もう少し動きにリアルさを出すために、 重力加速度やフロントとリアにかかるダウンフォース(ダウンフォースは速度によって変化させると効果的です)を、 質点の移動に加えたりしてみると良いと思います。 これらの力を加える場合は、P0~P3の質点に対してP0-P4ベクトル方向へ加えます。 ただし、これらの力を単純に加えているだけではどんどん剛体が下へ行くだけになってしまいますので、 ナビゲーションメッシュと組み合わせて、質点が乗っているセルよりは下に行かないように、 Y座標を調整しなければならないのは必須です。
また、アクセルを離したとしても、質点の移動が突然ピタリと止まるわけではなく、実際はある程度惰性で動き続けます。 質点の移動を行う際には、前回の質点の調整によって生まれた力を惰性として加えてみると良いと思います。 惰性は、質点の調整が終わった時点で各質点に対して、次のように計算しておきます。
(質点の調整後の位置 - 質点の調整前の位置) * 惰性係数
惰性係数は大きければ惰性が大きく、小さければあまり惰性がかからないようになります。 この計算で出てきたベクトルを、次の質点の移動の際に加えてやります。
簡易剛体運動を行う一連の流れを仮想言語で書くと次のようになります。 この流れには、ナビゲーションメッシュを利用した調整も含まれています。
PrevPoint[8] //質点の前回の位置
Inertial[8] //惰性
Line[28] //質点を結ぶライン
loop{
ゲームのいろいろな処理
//惰性を加える
for(i = 0; i <= 7; ++i){
Point[i] += Inertial[i]
}
//重量加速度を加える
for(i = 0; i <= 3; ++i){
Point[i] += 1フレームにかかる重力加速度
}
//ダウンフォースを加える
for(i = 0; i <= 1; ++i){
Point[i] += 現在の速度に対するフロントのダウンフォース
}
for(i = 2; i <= 3; ++i){
Point[i] += 現在の速度に対するリアのダウンフォース
}
//ステアリングを切る
for(i = 0; i <= 1; ++i){
Point[i] += normal(PrevPoint[0] - PrevPoint[1]) * ステアリングの値
}
//前進させる(四輪駆動の場合)
for(i = 0; i <= 3; ++i){
Point[i] += normal(PrevPoint[0] - PrevPoint[2]) * アクセルの値
}
//質点の調整を行う
for(i = 0; i < 数値積分回数; ++i){
for(j = 0; j <= 27; ++j){
Line[j]の数値微分を行う
}
}
//ナビゲーションメッシュを確認
for(i = 0; i <= 3; ++i){
Point[i] = PrevPoint[i]からPoint[i]の移動をナビゲーションメッシュで解決した位置
}
//今回の移動を終了
for(i = 0; i <= 3; ++i){
Inertial[i] = (Point[i] - PrevPoint[i]) * 惰性係数
PrevPoint[i] = Point[i]
}
レンダリングとかゲームのいろいろな処理
if(ゲーム終了?){
break
}
}
ゲーム終わり
最後に
このように、簡易剛体運動は比較的簡単な計算で実現できます。
簡易剛体運動は、ナビゲーションメッシュと組み合わせて動かすと楽しいと思います。 ナビゲーションメッシュと組み合わせる場合は、底面のP0~P3の質点をそれぞれ個別にナビゲーション上を移動させます。 すると、例えばP0だけが壁に衝突して移動が突然停止した場合、剛体が斜め左前のめりになるように質点が調整されますので、 それなりにリアルっぽく見えると思います。 また、凸凹したナビゲーション上を移動させた方が、剛体の動きは面白いです。 これらを行っている剛体の動きは、公開されているデモ5で実際に確認できます。 (デモでは、車が絶対にひっくり返らないようなスタビライザー機構が追加で実装されています)
誤りなどを見つけましたら、ご連絡ください。
(文: 2008/5/28 ラメイジュ 田中)
この文章は、予告なく改編される場合がございます。
この文章の無断での転用を禁止いたします。
このページへのリンクは大歓迎です。



