ホームゲームつくろー!衝突判定編<2つの球を当てて跳ね返らせる!

運動編
その1 2つの球を当てて跳ね返らせる!


 ゲームに限らず誰もがやってみたい「パーティクル」。わらわらと動く粒子をゲームに加えると、現実感とボリュームが一気に高まり、ゲームのクォリティーが向上します。ただ、パーティクルと言えば「物理現象」。これはどうしても避けることができません。でも、難しいことはあまりしたくない。そこでこの章では、パーティクルの基本である「球の衝突」をざざっとまとめ、球の衝突の一般式をさっさと導出してしまいます。導出した式を使えば、もう衝突なんて逐次計算になってしまいます。まどろっこしい事をしたくない人は式だけご利用下さい。



@ 私たちが持っている情報から整理

 ゲームにおいて衝突を考える場合、私たちはどのような情報を持っているでしょうか?まず、当然ですが位置情報を持っています。そして、オブジェクトが動こうとする向きを持っているはずです。これは、1次元、2次元また3次元かもしれませんが、とにかく移動方向が絶対にあります。さらに、移動方向に対してどれだけ進むのかという情報が無いと次の位置がわかりません。これは「速さ(velocity)」です。同じ方向に進んでいても、速さが違えば移動距離も変わってきますよね。こういう情報は何らかの形で持っているはずです。位置、移動方向、そして速さがあれば、とりあえずオブジェクトは動くことができるようになります。

 衝突を考える時にもう1つ必要な情報があります。それは「質量(mass)」です。これは、衝突後の位置決定に必要になります。例えば、時速100kmの野球ボールを受け取ると、手は痛くなるでしょうがちゃんと受け止められます。しかし、時速100kmの車を受け止めると私たちは吹っ飛びますよね。同じ速度であっても、衝突後の位置が全然違うわけです。

 以上から衝突に必要な最低限の情報は、

  ・ 位置情報
  ・ 移動方向
  ・ 速さ
  ・ 質量

となります。これらから衝突した後の動きまでを考えて見ましょう。



A 「勢い」を全部足したらいつも同じ〜運動量保存の法則の野暮ったい話し

 パーティクルの衝突を考える時に非常に大切な概念が1つあります。それは運動量保存の法則と呼ばれます。言葉はめんどくさそうなのですが、意外とあっさりとしています。これは、動いている物が持つ「勢い」を全部足すと、いつでも一緒であるという法則です。イメージとしては、宇宙空間に箱を持っていって、そこに玉を入れます。それをず〜っと観察していると、玉はいつまでも動いてそうな感じがしますよね。玉と玉がぶつかると、お互いの速度は変わるのですが、一方が遅くなると他方は速くなりそうで、やっぱり全体が何らかの一定の勢いを持って動いている気がします。

 「勢い」をもう少しちゃんと言うと「運動量」いう大きさになります。野暮ったいことを言いますと、運動量pというのは、質量mのオブジェクトが速度sで動いている時に、

という掛け算で計算されます。式から、勢いは重いほど、そして速いほど強いと言うことが言えます。これは現実感覚に合いますよね。箱の中に玉が沢山入っている場合、箱の中の玉全部が持っている全運動量は、

と単なる足し算で計算されます。色々と制約があるのですが、ある条件下でこの足し算の値はいつでも一緒になります。これは、衝突が起こったとしても同じなんです。これが運動量保存の法則というものでして、衝突を考える鍵となります。例えば2つの玉が正面衝突する前と後で、両者の合計運動量は変わりません。でも、ぶつかる前後で速度が異なりそうな感じはしますね。そこで合計運動量をMなどと置いてみます。ぶつかる前は、

そして、ぶつかった後は、

です。速度は違えど合計は同じという「法則」ですから、これらはイコールで結ばれます。

この式はどんな物理の教科書にも載っている有名な式です。この式は拡張ができまして、3つや4つなど同時に複数の玉がどっかとぶつかる時も、

という式が成り立ちます(速度がvでないと気持ちが悪いかもしれませんが、vは実は後で「ベクトル」として出てくるので、ここでは速度はs(spped)としました。もちろん速度はスカラーです)。

 2つの衝突の運動量保存の法則の式には、質量mとぶつかる前の速度s、そして「ぶつかった後の速度s'」があります。質量mとぶつかる前の速度sはわかっています。私たちが求めたいのは、ぶつかった後の速度s'です。これがあれば、衝突後のベクトルと合わせて衝突後位置を算出できます。ところが、運動量保存の法則の式は1本しかありません。分からない衝突後速度は2つでして、これでは解けません。
 これを解くためには「ぶつかる前と後の速度比」という独特の関係を加えます。これは一般には「反発係数」と呼ばれています。反発係数の式は次のように定義されています:

 この式の分母は一方から見た相手の相対速度を表わしています。例えば自分の乗っている車からみれば、すれ違う車は凄く速く感じられます。そういうのを相対速度と言います。分子はぶつかった後の相対速度です。この比を「反発係数」と定めています。通常、反発係数は0から1の間になります。もしe=1だったら床に落としたボールは永遠に跳ね返り続けてしまいます。逆にe=0ならば、床はボールの勢いを完全に吸収してしまい、どれだけ高スピードで叩きつけてもボールは一切跳ね返りません。このeの値を「適当に」決めると、速度に関するもう1つの式が出来上がりますので、めでたく衝突後速度を求めることが出来ます。

 面倒な定義の話しはとりあえず終了です。私たちが今実用上欲しいのは「2つの球がお互いにぶつかった後どこに位置するか?」という問いに対する答えです。次からは、それを求める式を一気に導いてしまいます。



B 2つのパーティクルの衝突を細か〜く見ていきます

 質量m1、半径r1であるパーティクル1は速度ベクトルv1で、質量m2、r2であるパーティクル2は速度ベクトルv2で等速移動中とします。今この2つのパーティクルが衝突を起こしました。それを図示すると次のようになります。

 赤いベクトルは速度ベクトルで、その長さが速度を表します。青い軸線は双方のパーティクルの中心を結んだものです。2つのパーティクルが衝突した時、衝突はこの青い軸線上に起こります。そこで、双方のパーティクルの速度ベクトルを軸上方向とそうでない方向に分解します。次の図をご覧下さい。

 速度ベクトルv1は接平面方向のベクトルと青い軸へ向けたベクトルに分解されます。軸方向の速度ベクトルをvn1(nは法線Normalの"n")、接平面方向のベクトルをvt1(tは接線Tangentの"t")と名前をつけておきます。同様の事は速度ベクトルv2についても言えます。図から明らかのように、衝突に関係するのは青い軸方向の速度ベクトルvn1です。

 さて、青い軸方向を中心に考えてみると、今の状況は以下の図のようになっています。

 これは列記とした「正面衝突」です。今P1からP2方向を正としますと、P1の速度は正、P2の速度は「負」です。そう定めないと面倒な事になりますので、今のうちに固めてしまいます。この符号判定は速度ベクトルv1とP1P2ベクトルが作る角度、さらに言うとそれら2つのベクトルの内積の符号になります。以下の図をご覧下さい。

 P1P2ベクトルとv1の内積を取ると、左の鋭角の場合は符号はプラス、右の鈍角の場合は符号がマイナスになります。同様の事はパーティクルP2についても言えます。ただ、P1P2ベクトルというのは何とも扱いにくいベクトルです。それよりも、内積によってすぐにsが出せるよう、「P1P2ベクトルを標準化したベクトルc」を新しく定義しておきます。これを用いれば、s1及びs2は次のように符号も苦労せずに表わせます。

また、これを用いるとvn1やvn2は、

であることも図からわかると思います。

 少し話が横道にそれましたが、正面衝突で速度sと質量mがあれば、反発後の速度s'は運動量保存の法則と反発係数の式から簡単に求めることが出来ます。それらの式をもう一度おさらいしますと、


というものでした。この連立方程式を解きますと、衝突後の速度は次のようになります。

 この速度でパーティクルは反発することになりますので、これとベクトルcを掛けますと、反発後の速度ベクトルvn'1とvn'2を求めることが出来ます。「反発してるんだから-cではないの?」と思われるかもしれませんが、その符号の反転は衝突後の速度s'1やs'2にちゃんと含まれています。ですから、素直にcを掛けるのが正しい計算です。とりあえず図にするとこうなります。

 さぁ、もう一息です。反発した速度ベクトルが分かったのですから、先ほどの衝突に関係の無い速度ベクトルvt1と足し算してあげます。これで、衝突後に進むべき速度ベクトルv'1及びv'2がお目見えすることになります。


 ここまで、衝突の様子を非常に細かく見てきました。「順序だてると算出できそうだ」という流れが見えれば十分です。次からは、これを踏まえて衝突式の決定版を作ります!



C これが衝突の式の決定版!

 私たちが今知りたいのは衝突前の速度と質量から衝突後の速度を導く方法です。そのために、今度はここまでの結果を逆算しながらどんどん式を展開していきます。ちょっと目がチカチカして頭が痛くなるかもしれませんが、じっくり見れば何をしているか分かりますので、どうぞゆっくりご覧下さい。

 まず、すぐ上の図からいきましょう。衝突後の速度ベクトルv'1vn'1vt1という2つのベクトルに分解できます。これはP2についても同様です。式にすると、

となります。この式のベクトルvt1は確か衝突前の速度ベクトルv1を分けたベクトルの1つでした。もう一度図を持ってきますと、

でした。ここから、

となりまして、先ほどの式に代入すると、

となります。単に代入しただけですから、まだ大丈夫ですよね(^-^)。

 次に、速度ベクトルvn'1というのは、正面衝突と考えた時の衝突後速度ベクトルでした。図を再度表記すると、

 と、P1P2ベクトルを標準化したベクトルcと衝突後速度の掛け算で表わされていたのでした。もう1つ、式中のvn1vn1=s・cと表わされると大分前の式で示しました。これらをさらに代入するとこうなります。

思いの他結構すっきりしています。

さてさて、さらに続けます。上式で反発後の速度s'1には導出式がありました。と言いましても、もうすっかり忘れてしまいましたので、もう一度持ってきます。

これです。運動量保存の法則と反発係数の式から導いたものでした。これを先ほどの式に代入して整理するとこうなります。

対称性があって非常に美しいです。

 最後になります。s1とs2は、それぞれ次の式で表わされていたのを覚えていますでしょうか。

衝突前速度ベクトルとP1P2の標準化ベクトルcの内積です。これを先ほどの式にそのまま代入して内積部分をちょっと整理すると、ようやく、欲しかった式が出来上がります。

非常に洗練されていますよね。内積1回だけで求める速度ベクトルが算出できてしまいます。これが2つのパーティクルがぶつかった後の速度ベクトルの算出式の決定版です!お疲れ様です!!



C 反発係数はどうしよう・・・

 Bで苦労の末導いた式の中には、反発係数eが含まれています。反発係数は2つの物の間で定義される値です。物質固有ではありません。例えばスーパーボールがどれだけ跳ねるとしても、布団の上に落とせば反発しないでしょう。しかし、ゲーム製作を考えたときに、自分と相手を見て反発係数が決定されると言うのは、なんとも扱いにくい物です。今は厳密なものは何も必要としていませんので、ここは物質固有の反発率のようなものを想定して、自分と相手の反発率から2つの物質間の反発係数を適当に決定する仕組みに変えてしまいましょう。

 大胆にも反発係数は次のように与えられるとしてしまいます(この式は物理的に全く根拠がありませんから、間違っても本物のシミュレーションなどに使わないで下さい!

e = eO1 ・ e O2

eO1、eO2というのがそれぞれ物1および物2の固有反発率です。それを掛け合わせた値を2つの物質の反発係数と定義してしまいます。こうする事で、パーティクルなどで反発の異なる物質の衝突を擬似的に計算できるようになります。この式を入れた完全な式は次の通りです。

 内積部分をさらに整理しますと、驚くほど美しい対称性が垣間見れます。反発係数の部分と内積の部分は上下とも全く同じになります。極僅かな部分だけが異なる本当に綺麗な式になります。この式の中はパーティクルが最初から持っている物、そして衝突した時の一方の中心点から他方の中心点に伸ばした標準化ベクトルcだけです。このベクトルも中心点の位置情報さえあれば簡単に算出できますから、衝突場所さえ判明すればたちどころに衝突後速度ベクトルが求まります。2つのパーティクルの衝突地点については衝突判定編3D衝突編その9「移動する2つの球の衝突場所と時刻を得る」で実はもう求められる形になっています。もう準備は万端なのです(^-^)



D 2つのパーティクルの衝突後位置算出関数

 ここまで来れば、2つのパーティクルが移動して衝突した後の位置を算出する関数は簡単に実装できます。ここでは、その完全ソースを公開致します。コピペですぐに使えますので、どうぞご利用下さい。移動する2つの球の衝突場所と時刻を得ると併用しますと効果絶大です。

パーティクル衝突後位置算出関数
///////////////////////////////////////////////////
// パーティクル衝突後速度位置算出関数
//   pColliPos_A : 衝突中のパーティクルAの中心位置
//   pVelo_A     : 衝突の瞬間のパーティクルAの速度
//   pColliPos_B : 衝突中のパーティクルBの中心位置
//   pVelo_B     : 衝突の瞬間のパーティクルBの速度
//   weight_A    : パーティクルAの質量
//   weight_B    : パーティクルBの質量
//   res_A       : パーティクルAの反発率
//   res_B       : パーティクルBの反発率
//   time        : 反射後の移動時間
//   pOut_pos_A  : パーティクルAの反射後位置
//   pOut_velo_A : パーティクルAの反射後速度ベクトル
//   pOut_pos_B  : パーティクルBの反射後位置
//   pOut_velo_B : パーティクルBの反射後速度ベクトル

bool CalcParticleColliAfterPos(
   D3DXVECTOR3 *pColliPos_A, D3DXVECTOR3 *pVelo_A,
   D3DXVECTOR3 *pColliPos_B, D3DXVECTOR3 *pVelo_B,
   FLOAT weight_A, FLOAT weight_B,
   FLOAT res_A, FLOAT res_B,
   FLOAT time,
   D3DXVECTOR3 *pOut_pos_A, D3DXVECTOR3 *pOut_velo_A,
   D3DXVECTOR3 *pOut_pos_B, D3DXVECTOR3 *pOut_velo_B
)
{
   FLOAT TotalWeight = weight_A + weight_B; // 質量の合計
   FLOAT RefRate = (1 + res_A*res_B); // 反発率
   D3DXVECTOR3 C = *pColliPos_B - *pColliPos_A; // 衝突軸ベクトル
   D3DXVec3Normalize(&C, &C);
   FLOAT Dot = D3DXVec3Dot( &(*pVelo_A-*pVelo_B), &C ); // 内積算出
   D3DXVECTOR3 ConstVec = RefRate*Dot/TotalWeight * C; // 定数ベクトル

   // 衝突後速度ベクトルの算出
   *pOut_velo_A = -weight_B * ConstVec + *pVelo_A;
   *pOut_velo_B = weight_A * ConstVec + *pVelo_B;

   // 衝突後位置の算出
   *pOut_pos_A = *pColliPos_A + time * (*pOut_velo_A);
   *pOut_pos_B = *pColliPos_B + time * (*pOut_velo_B);

   return true;
}




E 同時に3つ以上の衝突

 衝突は時に同時に3つ以上で起こる場合があります。例えばビリヤードのナインボールのブレイクショット。手球を含めて10個のボールが同時に衝突します。ブレイクショット後9つのボールはどう力を干渉し合って飛び散るのか?これを考えるのは実は異常なほど難しいのです。物理シミュレーションを考えているのであればそれも考慮せざるを得ませんが、ちょっとした衝突で良いならば3つ以上の衝突は無視しても殆ど問題は生じません。パーティクルが多くなれば、見た目の違和感は殆ど感じなくなります。ゲームは「らしければ」いいんです。



E 外力の影響

 運動量保存の法則は動いている物に外力(重力等)が加わると成り立たなくなってしまいます。しかし、運動量保存の法則が成り立ってくれないと困る「衝突の瞬間」と言うのは非常に短いと考えられます。この間の外力は無視できるほど小さくなります。ですから、ゲームにおいて衝突時の外力の影響はゼロだと考えます。衝突直前の位置と速度、それさえ定まってしまえばかなり良い線で動きます。


 ゲームで物理現象を扱うときに重要なのは「正確性」よりも「らしさ」です。正確にするがあまりコストを払いすぎてしまうのは得策ではないでしょう。もちろん、物理計算に見合うスペックのハードが出現すれば、改めてより細かな現象を加えていけば良いわけです。状況に見合った式を選ぶことが何よりも大切に思います。