力積一発で終わらせる
一言でいうと
衝突応答は、力ではなく力積で計算します。2つの物体が互いに離れるように、速度を一度に変える値を解いて、すぐに適用します。
なぜ必要なのか
衝突を力として扱うには、接触している間の力を、時間で積分する必要があります。ところが、硬い物体の接触時間は、1ミリ秒にもならないので、60分の1秒間隔で回るシミュレーションでは、その瞬間が、まるごと1ステップの中に入ります。力をどれだけ大きく取っても、1ステップの中では、物体がすでに互いにめり込んだあとです。
そのため、物理エンジンは、接触の過程を真似ずに、結果だけを計算します。衝突の前後の速度の関係を方程式に立てて、それを満たす速度の変化量を、一度に求めます。この変化量が力積で、単位は、力×時間、つまり運動量と同じです。
どう動くのか
2つの物体の相対速度を、衝突の法線方向で見た値が、核心です。
v_rel = dot(v2 - v1, n) # n 은 1에서 2를 향하는 단위 벡터
この値が正なら、2つの物体はすでに離れつつあるので、何もしません。この検査を抜かすと、すでに離れていく物体を、もう一度引き寄せることになって、物体が表面に張り付きます。
負なら、力積を求めます。反発係数eは、衝突後の相対速度が、衝突前の何倍で戻ってくるかを決める値で、1なら完全弾性(エネルギー保存)、0なら完全非弾性(くっついて一緒に動く)です。
j = -(1 + e) * v_rel / (inv_m1 + inv_m2)
v1 -= j * inv_m1 * n
v2 += j * inv_m2 * n
ここで、質量ではなく逆質量を使うのが、慣習です。割り算を掛け算に変えて、少し速くなることもありますが、本当の理由は、動かない物体を、逆質量0として自然に表現できるからです。無限大の質量をコードに入れると、割り算のたびに特別な処理が必要ですが、逆質量0は、ただの数です。
この計算は、運動量を常に保存します。2つの物体が、大きさが同じで向きが反対の力積を受けるので、構造的にそうなります。一方、運動エネルギーは、eが1のときだけ保存され、それより小さければ、その分だけ失われます。失われたエネルギーは、現実では、音と熱と変形に行きます。
摩擦は、同じ計算を、接線方向にもう一度行いますが、大きさに上限を置きます。
t = normalize(v - dot(v, n) * n) # 법선 성분을 뺀 나머지 = 접선 방향
jt = -dot(v, t) / inv_m # 접선 속도를 0 으로 만드는 값
jt = clamp(jt, -mu * jn, +mu * jn) # 쿨롱 마찰의 원뿔
接線速度を完全になくすには、いくら必要かを先に求めて、その値がmu * jnを超えたら、切り詰めます。超えなければ、物体がその場で止まり(静止摩擦)、超えれば、切り詰めた値の分だけ適用されて滑ります(動摩擦)。条件文1つで、2つの摩擦がすべて出ます。
回転が入ると、式が少し長くなります。接触点が重心から離れていると、力積が回転も生み出すからです。そのときは、分母に、各物体の慣性モーメントから来る項が加わり、力積を適用するとき、線速度だけでなく、角速度もあわせて変える必要があります。構造は同じです。相対速度を求めて、それを望む値にする力積を解き、適用します。このラボが回転を除く理由は、式の骨格を先に見てもらうためです。
反発係数は、物体の性質ではありません。2つの物体が出会う組み合わせの性質で、ぶつかる速度によっても変わります。実務では、2つの物体の値を掛けたり、小さいほうを選んだりして、大雑把に合わせますが、どちらにしても、物理的な根拠は弱いです。値を正確に合わせようと苦労するより、望む感じが出る値を選ぶほうが、良いです。
現場での姿
低い高さで跳ね続ける物体が、止まらずに、微細に震える問題が、よくあります。反発係数が小さくても、0でなければ、跳ねる速度が0に収束しないからです。実務での解決法は、相対速度がある敷居値より小さければ、反発係数を0に強制することです。
もう1つは、複数の接触が同時にあるときです。接触を1つずつ順番に解くと、あとで解いたものが、先に解いたものを壊します。そのため、同じステップの中で、何度も繰り返して解きますが、これが、次のモジュールのテーマです。
最後に、符号と方向の約束を、もう一度押さえておきます。法線が1から2へ向かうと決めたなら、コード全体がその約束を守る必要があります。検出側で逆に返すと、物体が互いを押しのける代わりに引き寄せて、その症状は、「物体がくっついて離れない」として現れて、原因を推測しにくくなります。そのため、検出の関数と応答の関数の間のこの約束は、コメントではなく、テストで固定しておくほうが、安全です。
次のラボですること
力積の公式を自分で作り、反発係数によって運動量とエネルギーがどうなるかを、数字で確認し、ボールが跳ねる高さが毎回e²倍に減ることを、グラフで見ます。次に、クーロン摩擦で滑る箱が止まる距離を求めて、解析的な答えと合わせてみて、最後に、ボール5つを箱の中で跳ねさせて、軌跡を絵に残します。