デモ&コード
デモ : http://codepen.io/kenjiSpecial/pen/bNJQKQ
コード : https://github.com/kenjiSpecial/butsuri-js/tree/master/demo-old-dev/old/speculative-contacts-multi-balls
参考にしたサイト
Speculative Contacts – a continuous collision engine approach
http://www.wildbunny.co.uk/blog/2011/03/25/speculative-contacts-an-continuous-collision-engine-approach-part-1/
speculative contactsという衝突判定という手法についてのブログです。
有料ですが、この手法で作られたコード(C#)をダウンロードすることができます。
Speculative Contactsとは
上記のブログによると、衝突クラス(Contactクラス)と衝突判定クラス(Solverクラス)を用いて大半のものを動かしたりするそうです。
ブログの中にあるように物体同士の最短距離dを計測し、距離dを次のフレームでAがBに接触するかを判定します。
接触判定がtrueの場合は、上の画像のような位置関係になるように速度を調整させます。
特徴として物体同士の最短距離が物体同士の1フレームあたりの相対速度を超えない限りはspeculative contactcsは作用しないところにあります。
Contact con = contacts[i];
Vector2 n = con.m_normal;
 
// get all of relative normal velocity
double relNv = (con.m_b.m_Vel-con.m_a.m_Vel).Dot(n);
 
// we want to remove only the amount which leaves them touching next frame
double remove = relNv + con.m_dist/Constants.kTimeStep;
 
if (remove < 0)
{
	// compute impulse
	double imp = remove / (con.m_a.m_InvMass + con.m_b.m_InvMass);
 
	// apply impulse
	con.m_a.m_Vel += imp * n * con.m_a.m_InvMass;
	con.m_b.m_Vel -= imp * n * con.m_b.m_InvMass;
}
speculative contactcsを実装するエッセンスとしてブログでは以上のコードだけが貼られてありました。
ここだけで残りのコードを埋めるのを早々に諦めました。
c#のコードを買いjsに書き換えながら、勉強することにしました。
Vectorクラス
2次元の基本的なベクトル計算はks-vector functionを作成しました。
https://www.npmjs.com/package/ks-vector
Rigid-Bodyクラス
簡易な剛体クラスを作成しました。
https://github.com/kenjiSpecial/butsuri-js/blob/master/demo-old-dev/old/speculative-contacts-multi-balls/components/rigid-body.js
今回は回転なしなので、モーメントや回転速度などのプロパティやメソッドは保持していません。
RigidBody.prototype.update = function(dt) {
  // --------------------
  this.vel.x += this.force.x * this.invMass;
  this.vel.y += this.force.y * this.invMass;
  // ====================
  this.pos.x += this.vel.x * dt;
  this.pos.y += this.vel.y * dt;
  // ====================
  this.force.set(0, 0);
};
基本的な運動方程式を毎フレームupdateメソッドを呼びます。
Rigid-Bodyクラスをベースとして以降クラスを作成していきます。
Ballクラス、Floorクラス
Ballクラス: https://github.com/kenjiSpecial/butsuri-js/blob/master/demo-old-dev/old/speculative-contacts-multi-balls/components/ball.js
Floorクラス: https://github.com/kenjiSpecial/butsuri-js/blob/master/demo-old-dev/old/speculative-contacts-multi-balls/components/floor.js
Ballクラス及びFloorクラスではgetClosestPointsで毎フレームで衝突判定を行うContactオブジェクトを作成します。
Ball.prototype.getClosestPoints = function(rBody) {
  var contacts = [];
  var ballA = this;
  if(rBody instanceof Ball){
  // ボール同士の衝突の場合
    var ballB = rBody;
    var delata = new Vector2( ballB.pos.x - ballA.pos.x, ballB.pos.y - ballA.pos.y );
    var n;
    if( delata.getLength() ){
      n = delata.getNormal();
    }else{
      n = new Vector2(1, 0);
    }
    // generate closes points
    var pa = new Vector2();
    pa.x = ballA.pos.x + n.x * ballA.radius;
    pa.y = ballA.pos.y + n.y * ballA.radius;
    var pb = new Vector2();
    pb.x = ballB.pos.x - n.x * ballB.radius;
    pb.y = ballB.pos.y - n.y * ballB.radius;
    // getdistance
    var dist = delata.getLength() - (ballA.radius + ballB.radius);
    contacts.push(new Contact( ballA, ballB, pa, pb, n, dist ));
  }else if(rBody instanceof Floor){
     // ボールと床の衝突の場合
    var rectangleB = rBody;
    contacts = rectangleB.getClosestPoints(this);
    utils.flipContacts(contacts);
  }else{
    console.error("===== NO getClosestPoints IN Ball =====");
  }
  return contacts;
}
ボール同士(ballA, ballB)の衝突判定を例に取ると、ballAとballB、ballAの座標とballBの座標の差分単位ベクトルn、ballAとballBの上の最短距離d、その座標pa, pbで衝突を扱うContactクラスを作成します。
Contactクラスに代入する値はボール同士以外でもボールと床、箱と床などすべて同じように扱います。
Contactクラス
var Contact = function(A, B, pa, pb, n, dist) {
  this.mA  = A;
  this.mB  = B;
  this.mPa = pa;
  this.mPb = pb;
  this.mNormal = n;
  this.mDist = dist;
  this.mImpulse = 0;
};
Contact.prototype = {
  /**
  * @param {Vector2} imp
  */
  applyImpulses : function(imp) {
    this.mA.vel.addMultipledVector(this.mA.invMass, imp);
    this.mB.vel.subtractMultipledVector(this.mB.invMass, imp);
  },
};
衝突を扱うcontactクラスは非常にシンプルです。
衝突があった時にそれぞれのオブジェクトに力積を与え、速度を変更させます。
Solverクラス
var solver = function(contacts) {
    for (var jj = 0; jj < numInteraction; jj++) {
        for (var ii = 0; ii < contacts.length; ii++) {
            var con = contacts[ii];
            var n = con.mNormal;
            var relNv = con.mB.vel.copy().subtract(con.mA.vel.copy()).dotProduct(n);
            speculativeSolver(con, n, relNv);
        }
    }
}
function speculativeSolver(con, n, relNv) {
    var remove = relNv +   con.mDist / CONSTANTS.timeStep;
    if (remove < 0) {
        var mag = remove / (con.mA.invMass + con.mB.invMass);
        var imp = con.mNormal.copy().multiply(mag );
        con.applyImpulses(imp);
    }
}
オブジェクト間の距離とオブジェクトの相対速度を比較する衝突判定を行い、衝突がある場合は速度を変更させます。
app.js
今回作成したコンポーネントクラスを一つにまとめています。
コンポーネント間の計算を行い、計算結果をcanvasに描画しています。
デモ : http://codepen.io/kenjiSpecial/pen/bNJQKQ
次回
次回はオブジェクトの回転や四角形オブジェクト同士の衝突などを扱っていきます。



