零基礎制作物理引擎--創造力量


寫在前面

上篇其實有重力,但是重力是經過重心,可以把物體看出質點,問題就變得簡單,經過重心只產生線速度,不產生角速度。

這篇文章的力量其實是指:力(Force)和沖量(Impulse),不一定過重心。

邊寫引擎過程中,邊補習牛頓經典力學體系,但是依然記得大學時候物理老師反復強調:“牛頓錯了,牛頓錯了,牛頓那一套只是狹義相對論在低速下的近似表現”。

所以順帶又科普了一下愛因斯坦那一套東西,后來發現,寫個物理引擎用牛頓足矣。= =!

目標

本篇目標是為剛體新增兩個方法,一個是創造力,一個是創造沖量。
創造力:

applyForce: function (force, point) {

}
  • 參數一:force是一個Vector2對象的實例,代表了力的沿着x、y方向上的大小。
  • 參數二:point是施力點的世界坐標

創造沖量

applyImpulse: function (impulse, point) {

}
  • 參數一:impulse是一個Vector2對象的實例,代表了沖量的沿着x、y方向上的大小。
  • 參數二:point是施放沖量點的世界坐標

准備工作

知識准備

沖量和時間的關系

I = F * t

圓盤轉動慣量

m * r * r / 2 (r為半徑)

長方體轉動慣量

m*(a * a + b * b)/ 12 (a和b分別為長和寬)

更多轉動慣量,請參見: https://en.wikipedia.org/wiki/List_of_moments_of_inertia

I * L / 轉動慣量 = 角加速度 (L為力矩長度)

轉動慣量越大,對轉動的抵抗就越大,就越難讓其旋轉。

圖解

假設一個剛體收到沖量I:

usage

沖量I可以拆分成3個沖量分量,I1、I2、I3,其中:

  • I1產生X軸和Y軸方向線速度
  • I2產生角速度和Y軸方向線速度
  • I3產生角速度和X軸方向線速度

過重心,作一條垂直於I2的垂線,垂線的長度(力矩長度)乘以 沖量分量I2 除以 轉動慣量 = 角加速度,

過重心,作一條垂直於I3的垂線,垂線的長度(力矩長度)乘以 沖量分量I3 除以 轉動慣量 = 角加速度,

I2和I3產生的角加速度需求累加。

程序

applyImpulse: function (impulse, point) {
    var rA = point.clone().sub(this.position);
    //如果不通過重心
    if (rA.x !== 0 || rA.y !== 0) {
        var nv = rA.clone().normalize();
        var tangent = new Newton.Vector2(nv.y, -nv.x);

        var ni = nv.multiply(nv.x * impulse.x + nv.y * impulse.y);
        this.linearVelocity.x += ni.x * this.invMass;
        this.linearVelocity.y += ni.y * this.invMass;

        var tni = tangent.multiply(tangent.x * impulse.x + tangent.y * impulse.y);
        this.linearVelocity.x += tni.x * this.invMass;
        this.linearVelocity.y += tni.y * this.invMass;
        this.angularVelocity += (rA.x * tni.y - rA.y * tni.x) * this.invRotationalInertia;
    } else {  //如果過重心
        this.linearVelocity.x += impulse.x * this.invMass;
        this.linearVelocity.y += impulse.y * this.invMass;
    }
}

上面有個條件分支,當沖量的方向經過重心的時候,只產生線速度。

因為轉動慣量的倒數和質量的倒數使用非常頻繁,所以在構造函數內就提前計算好,如圓的構造函數里提前求好了轉動慣量的倒數:

Newton.Circle = Newton.Body.extend({
    ctor: function (option) {
        this._super(option);
        this.r = option.r;
        this.rSqu = this.r * this.r;
        this.type = Newton.CIRCLE;
        this.invRotationalInertia = 2 / (this.mass * Math.pow(this.r , 2));
    }
});

同樣,在矩形的構造函數里,也提前求出來轉動慣量的倒數:

this.invRotationalInertia = 12 / (this.mass * (Math.pow(this.width, 2) + Math.pow(this.height, 2)));

但是需要注意的是,如果質量發生改變的話,轉動慣量也需要發生相應改變,所以可以使用Object.defineProperty()去定義mass,在set里面改變invRotationalInertia。

有了上面的applyImpulse方法,封裝applyForce就會簡單許多:

applyForce: function (force, point) {
    this.applyImpulse(force.clone().multiply(Newton.World.TimeStep),point);
}

沖量等於力在時間上的累加,這里提供的力的時間長度為最小時間片段。

簡化代碼

通過Vector2的cross方法可以把applyImpulse簡化成如下代碼:

applyImpulse: function (impulse, point) {
    var rA = point.clone().sub(this.position);
    this.linearVelocity.add(impulse.clone().multiply(this.invMass));
    this.angularVelocity += this.invRotationalInertia * rA.cross(impulse);
}

舉個例子

var world = new Newton.World();
Newton.World.Gravity.y=0;

var box = new Newton.Rectangle({
    width: 70,
    height:30,
    position: new Newton.Vector2(100, 300),
    linearVelocity: new Newton.Vector2(0, 0),
    angularVelocity:0
});
world.add(box);

document.querySelector("#ourCanvas").addEventListener("click",function(evt){
    box.applyImpulse(new Newton.Vector2(200,-250),new Newton.Vector2(box.position.x-10,box.position.y-10))
}, false);

var render = new Newton.Render("#ourCanvas");
world.onTick(function () {
    render.clear();
    render.rect(box.position.x, box.position.y, box.width, box.height, box.rotation);
})

world.start();

這里把重力加速度設成了0,角速度和線速度都是0,所有在100,300的位置能夠看到一個靜止的矩形。當點擊Canvas的時候,
在剛體重心的左上方10個像素的位置施加右上(200,-250)的沖量,可以看到它如下圖所示的方式飛出去:

usage

最后

因為多邊形的轉動慣量和重心計算要比圓形和矩形復雜一下,這里先不展開,待物理引擎骨架基本建立之后再做完善。
未完待續...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM