衝突5

分割と跳ね返り係数

左クリック:吸い寄せ 右クリック:瞬間移動

 フレーム分割法は有効だと思っていましたが精度が低いように感じます。
 以下、教材に従い衝突時間の計算をして処理する方法を学習していきます。

衝突時間


 現在は上の図で解決を図っていますが問題があります。

 衝突時間を計算していないので分かりやすく誤差が生じます。
 誤差を0に近づけるためにフレームを大きな数で分割しなければならないフレーム分割法では限界があります。

衝突時間を計算する


 教材はかなり詳細に計算されていましたが、上図のようになると思います。
class DataCollision{
  float time;//衝突時間
  int n1, n2;//衝突したオブジェクトのID
}
...
DataCollision data = new DataCollistion();
...
void calcCollisionTime(n1, n2){
//p = p2 - p1; v = v2 - v1; r = r1 + r2;
//t = (-dot(v, p) - sqrt(sq(dot(v, p)) - sq(v) * (sq(p) - sq(r)))) / sq(v);
  PVector p = new PVector.sub(obj[n2].pos, obj[n1].pos);
  PVector v = new PVector.sub(obj[n2].spd, obj[n1].spd);
  float dotPV = p.dot(v);
  float sqV = v.dot(v);
  float sqP = p.dot(p);
  float sqR = sq((obj[n1].r + obj[n2].r) / 2);
  float judge = sq(dotPV) - sqV * (sqP - sqR);
  //速度ベクトルと判別式による条件分岐
  if ((sqV != 0) && (judge >= 0)){
    float time_m = (-dotPV - sqrt(judge)) / sqV;
    if ((time_m >= 0) && (time_m <= 1)){
      if (data.time > time_m){
        data.time = time_m;
        data.n1 = n1; data.n2 = n2;
      }
    }
  }
}

メインループ

 衝突時間を計算する方式は、処理方法を大幅に変更する必要があります。
  //衝突、移動処理
  残り時間 = 1.0;//
  while(残り時間 > 0){
    //次に衝突する2つのオブジェクトIDと経過時間を計算
    //衝突しない場合または経過時間が残り時間を超えている場合
    経過時間 = 残り時間;
    //衝突はなかったことにする
    //(可能であれば次回へスタックしたい)

    //全てのオブジェクトを移動
    位置 += 速度 * 経過時間;

    //衝突がある場合
    //衝突する2つのオブジェクトの衝突後の速度ベクトル計算

    残り時間 -= 経過時間;
  }
 この手法を用いると、めりこみが発生しなくなると思われます。
 ただ、複数のオブジェクトが密着している場合の計算回数が心配です。

結果

 以下ブラウザが落ちること数十回の格闘記録です。

衝突処理が行われている箇所に赤線、めりこみ補正が適用されている箇所に緑線を表示するようにしてデバッグしている画像です。
赤線はもう少し多いと思いますが、緑線の描画が後出しなので上書きされている可能性があります。


 起動直後の状態です。
 密な状態で起動した場合には重なったオブジェクトが多いため、がんばって補正処理を施していることが伺えます。

 隅に密集している状態です。
 ほとんど動かないように見える状態でも速度が0にならないためか補正し続けています。
 衝突計算がほとんど行われずめりこみ補正が目立つのは、どこかで間違えている可能性が高いということでしょう。

 実際に間違えており、この修正とその周辺に計算回数を減らす条件を付けたところ劇的に速度が向上しました。
 現在は密集状態からの回復速度が満足できる数値に達しています。


 衝突時間計算のみ抜き出してみました。
 眺めていると電気が走っているようできれいですが、ブラウザが落ちないかという恐怖でいっぱいです。

 めり込み回避が必要なのですが、これも間違えているかもしれません。

追記


 フレーム分割法でめりこみチェックをしたところ、円が接したような状態で補正が終了していないことが判明しました。
 ログをみると、

 上のように0に限りなく近い値を示していました。
 0ではないから補正してくれると思いますが待てる時間ではなさそうです。
  float k = 1 - (obj[num].d + obj[i].d) / (obj[num].pos.dist(obj[i].pos) * 2);
  if ((k < -0.01) && (i != num)){
    //めりこみ補正
  }
 精度を求めているわけではないので、条件 (k < 0) の右辺を少し小さく(大きく?)して対処しました。
 当然不要な計算が減ったので密集状態時の過負荷状態が減少しました。