LoginSignup
17
18

More than 5 years have passed since last update.

物理エンジン勉強メモ vol.01

Last updated at Posted at 2015-04-07

balls-loop.gif

デモ&コード

デモ : 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クラス)を用いて大半のものを動かしたりするそうです。

img01.jpg

ブログの中にあるように物体同士の最短距離dを計測し、距離dを次のフレームでAがBに接触するかを判定します。

img02.png

接触判定が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

今回は回転なしなので、モーメントや回転速度などのプロパティやメソッドは保持していません。

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.js
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;
}

img03.jpg

ボール同士(ballA, ballB)の衝突判定を例に取ると、ballAとballB、ballAの座標とballBの座標の差分単位ベクトルn、ballAとballBの上の最短距離d、その座標pa, pbで衝突を扱うContactクラスを作成します。
Contactクラスに代入する値はボール同士以外でもボールと床、箱と床などすべて同じように扱います。

Contactクラス

contact.js
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クラス

solver.js
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

次回

次回はオブジェクトの回転や四角形オブジェクト同士の衝突などを扱っていきます。

17
18
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
18