物理エンジンを作る3
反発するようにしてみよう。前回のコードでは衝突した場合に作用法線方向の速度を0にするだけだったのでピタっと止まっていたが、反発させると弾むような動きを作ることができる。衝突後に回転させたりするのも、円と四角が衝突した場合に自然に反応するのも、全てこの計算がベースになる。
ComplexVector
機能追加をする。メソッド3つ。
module ComplexVector refine Complex do def cross(c) self.real * c.imag - self.imag * c.real end def prep Complex(-self.imag, self.real) end def rotate(ang) Complex.polar(self.magnitude, self.angle + ang * Math::PI / 180) end end end
crossは外積だが、ComplexVectorは2Dベクトルを表現するものなので、通常の外積計算結果のzだけを返すものにしている。prepは90度回転。力学計算の外積のところでこの2つを組み合わせて使う。
rotateは今回は使わないけど後で使うのでついでに。
力学計算
apply_impulseを作り直す。引数を1つ増やして衝突点を渡し、重心から衝突点までのベクトルを使って撃力を求める。計算式は物理エンジンを解説してるサイトや本やChipmunk他のソースを参考に。
# 撃力を加える def apply_impulse(ary, normal, cp) o1, o2 = *ary vn = normal.dot(o1.v - o2.v) # 相対速度 cp1 = cp - o1.xy cp2 = cp - o2.xy # 撃力計算 j = (-(1+o1.e*o2.e) * vn) / ((1.0/o1.mass + 1.0/o2.mass) + normal.dot(cp1.prep * (cp1.cross(normal) / o1.moment)) + normal.dot(cp2.prep * (cp2.cross(normal) / o2.moment))) # 速度への反映 o1.v += normal * j / o1.mass o2.v -= normal * j / o2.mass # 回転への反映 o1.av += cp1.cross(normal * j) / o1.moment o2.av += cp2.cross(normal * j) / o2.moment end
と言っても現状では回転の処理ができていないのでこの計算で合っているのかどうかはよくわからない(ダメな気がする)。また、回転できていないのは衝突点を求める計算ができていないからで、じゃあこの引数はどうやって作っているのかというと、すごく適当に重心の中間を求めている。
apply_impulse(ary, normal, (o1.xy + o2.xy) / 2)
あ、今まで座標は四角の左上だったが重心にするように少し手を加えてあるのでxyで得られる位置は重心となっている。んで、回転しないようにするために慣性モーメントはすべてFloat::INFINITYにしてある。
ものすごく適当だがなんとなく反発してくれているのでOKとする。