これは一例として衝突時の挙動を処理するのに使うことができます。
また同じくして、運動量保存則が適用される場合には、重心は等速直線運動をするということも示しました。
異なる質量をもつ質点同士の衝突をシミュレーションしようとすれば、考えることは主に2つあります。
①衝突後のそれぞれの質点の速さ
②衝突後のそれぞれの質点の跳ね返りの方向
つまり衝突後の各質点の速度(速さと向き)を決めたいわけですが、残念ながら工夫を凝らさなければこの問題を解くことはできません。
というのも未知数が2つ(2つの質点の速度ベクトル)に対して、式は一つ(運動量保存則)しかないからです。
ということで、適当な仮定を置いて衝突時に何が行われているかを考察することにします。
図のように、大小二つの円盤があったとします。(本当は質点には大きさがないのですが、そうするとそもそも衝突という概念が怪しくなるので大きさのある円盤にします)
そうすると衝突したときには、双方の円盤の共通の接線の方向の壁にぶつかったような挙動になるのではないか?となんとなく想像できます。 つまり通常の床へのボールの衝突と同じように、壁に対して垂直な方向は速度が反転し、水平な方向には速度が保たれる…という性質がありそうな予感です。
しかし一つ問題があります。
床にボールが衝突したときには、絶対座標空間にたいして、床は静止しています。
一方で今二つの円盤の共通接線にあたる壁は、円盤が動くにしたがって移動します。
これはちょっと考えてみるとえらい違いであることが分かります。
たとえば一方の円盤が非常に重くて、もう一方の円盤が非常に軽いとき、
重い円盤が軽い円盤にぶつかったからと言って、床にぶつかったときのように跳ね返ったりはしないでしょう。
むしろ重い円盤は速度をほとんど変えずにそのままほぼ直進するはずです。(仮定をたとえば地球とサッカーボールにしてみれば明らかです)
・・・ではどうすればいいでしょうか?
ここで出てくるのが前回の最後で紹介した重心系の考え方です。
もう一度確認しますが、
運動量保存則が成り立つ限り、重心は等速直線運動します。
じゃぁ重心の速度が0になるように座標系を変え、そこに壁を置けば壁は座標系に対して静止しているのではないか?
ということが考えられます。
今円盤Aが速度Vaで、円盤Bが速度Vbで移動していたとしましょう。
一方重心は速度Vgで移動していたとします。
重心の速度が0になるような座標空間を考えると、その空間では
Va' = Va-Vg
Vb' = Vb-Vg
と単に引き算をしてやれば重心系での速度が求まります。
そうすれば、あとはこの速度で移動する2つの円盤を、静止している壁に対して衝突させるだけの処理になります。
実際のプログラムは以下のようになります。
► スポイラーを表示
まずは円盤クラス
{
public:
Circle( double r, Vector s, Vector v )
:r_(r),m_(r*r),s_(s),v_(v)
{
}
void Update()
{
s_ += v_ * dt;
}
void Draw( int color )
{
DxLib::DrawCircle( s_.x, s_.y, r_, color );
}
double R() const { return r_; }
double M() const { return m_; }
Vector V() const { return v_; }
Vector S() const { return s_; }
void S( Vector s ){ s_ = s; }
void V( Vector v ){ v_ = v; }
private:
double r_; //半径
double m_; //質量
Vector v_; //速度
Vector s_; //位置
};
また生成時に半径をアトランダムに決めており、半径の2乗に比例した質量をもちます。
次にマネージャークラスです
こいつは衝突判定を行って、円盤すべての更新と描写を行います。
キーとなるのはこの↓部分です
bool Detection( Circle& lhs, Circle& rhs )
{
//半径の和の二乗よりも中心間距離の2乗のほうが大きければ衝突しない
Vector d = rhs.S() - lhs.S();
double rsum = lhs.R() + rhs.R();
if( d*d > rsum*rsum )
return false;
//重心系を使って計算させる
//まず重心の速度を求める
Vector gv = ( lhs.M()*lhs.V() + rhs.M()*rhs.V() ) / ( lhs.M() + rhs.M() );
//速度を重心系に変換する
Vector lv = lhs.V() - gv;
Vector rv = rhs.V() - gv;
//速度の中心方向成分を求め、それらを逆転させる
lv += -2.0*Linear::GetComponent( lv, d );
rv += -2.0*Linear::GetComponent( rv, -d );
//速度を絶対座標へ戻す
lhs.V( lv + gv );
rhs.V( rv + gv );
//のめりこんでいる場合は退避させる
Vector dn = d / d.Length();
dn = dn*rsum - d;
rhs.S( rhs.S() + dn * ( lhs.M() / ( lhs.M() + rhs.M() ) ) );
lhs.S( lhs.S() - dn * ( rhs.M() / ( lhs.M() + rhs.M() ) ) );
return true;
}
//半径の和の二乗よりも中心間距離の2乗のほうが大きければ衝突しない
Vector d = rhs.S() - lhs.S();
double rsum = lhs.R() + rhs.R();
if( d*d > rsum*rsum )
return false;
//重心系を使って計算させる
//まず重心の速度を求める
Vector gv = ( lhs.M()*lhs.V() + rhs.M()*rhs.V() ) / ( lhs.M() + rhs.M() );
//速度を重心系に変換する
Vector lv = lhs.V() - gv;
Vector rv = rhs.V() - gv;
重心の速度の求め方は、前回の日記で説明しました。
//速度の中心方向成分を求め、それらを逆転させる
lv += -2.0*Linear::GetComponent( lv, d );
rv += -2.0*Linear::GetComponent( rv, -d );
dベクトルは、円盤の中心間を結んだベクトルで、
Linear::GetComponent( Vector v, Vector ref )関数は、vベクトルのrefベクトル成分を求めます。
ここでは速度ベクトルの中心方向成分を求め、-2.0をかけて加えることによってその方向を逆転しています。
2行目が-dになっているのは、中心間のベクトルは自分から相手に向かって伸びるベクトルであることを仮定しているからです。
最後は重心の速度を加えてやることで、元の座標空間に戻します。
これで衝突処理は終了です。
重心系を用いることで、非常に単純な処理で衝突演算を行えることが分かると思います。
あとは必要に応じて、めり込んだ円盤同士を適切な距離に退避させています。(次のフレームでもう一度衝突するのを防ぐため)
-------------------------------------------------------------------------------------------------------------------------------------------------
このプログラムでは複数の質点間の衝突という処理まで持っていくことができました。
しかし2Dの世界だって、まだまだできていないことはたくさんあります。
大きな要素として、回転があげられます。
大きさのない質点ならば、位置(x座標とy座標)を決めてしまえば終わりですが
大きさを定義した剛体(また説明します)ならば、座標の他に角度(つまり向き)を決めなくてはいけません。
次回に説明するのは「自由度」の話です。