物理エンジンを作る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とする。

おしまい

ソースはこちら。いろいろ作りかけだったりするので使ってないメソッドがあったりもするが、まあ、とりあえず動く。
この計算がうまくいけば(ダメな気がするが)、あとは回転した四角の衝突点をうまく求めることさえできれば四角同士の衝突が自然にできるはずである。どうやってそれをやるのかが問題で、このへんは力学じゃなくて幾何学の世界になるのでこれはこれで難しい。