一路沿着本系列教程學習的朋友可能會發現,前面教程中都儘可能避免說起質量的概念,不少運動概念也時刻提醒你們這不是真實的物體運動。由於真實的物體運動其實跟質量都是密不可分的,並且質量的引入天然必須說起力學概念,因此爲了避免內容冗餘才忽略了質量。
從本篇開始,將會正式引入物理力學概念,給每一個物體賦予質量概念,爲了更加真實的模擬現實環境的物體運動。
閱讀本篇前請先打好前面的基礎。
本人能力有限,歡迎牛人共同討論,批評指正。javascript
【科普】通常而言,一個物體的動量指的是這個物體在它運動方向上保持運動的趨勢。動量其實是牛頓第必定律的一個推論。
動量便是「物體運動的量」,是物體的質量和速度的乘積,是矢量,可以反應出運動的效果,通常用p表示。舉個例子,低速運動的重物,跟高速運動的子彈,擁有相同的威力。html
p = m * v
【科普】動量是守恆量。動量守恆定律表示爲:一個系統不受外力或者所受外力之和爲零,這個系統中全部物體的總動量保持不變。它的一個推論爲:在沒有外力干預的狀況下,任何系統的質心都將保持勻速直線運動或靜止狀態不變。動量守恆定律可由機械能對空間平移對稱性推出。
動量守恆即系統在碰撞前的總動量等於系統在碰撞後的總動量。其中的系統簡單理解就是物體的集合。在能夠忽略碰撞之外的因素時,動量是守恆的。java
(m0 * v0) + (m1 * v1) = (m0 * v0Final) + (m1 * v1Final)
這條公式是咱們計算碰撞後速度的基礎,由於咱們假定咱們的物體都是剛體,而且忽略外力作碰撞。如今只要推導出末速度v0Final和v1Final的公式,就能夠應用到咱們的模擬碰撞的編程動畫中。推導過程以下:git
其實推導過程不重要,只要記得結論:github
v1Final = (2 * m0 * v0) + v1 * (m1 - m0) / (m0 + m1) v0Final = (2 * m1 * v1) - v0 * (m0 - m1) / (m0 + m1) // 兩者可直接轉換 v1Final = (v0 - v1) + v0Final
咱們開始使用前面推導出的公式,先來個最簡單的單軸碰撞例子,這裏演示了兩個球相撞的效果,mass定義了他們的質量,因爲他們初始速度相同,因此依據動量守恆碰撞後ball0的速度變爲-1/3,而ball1的速度變爲5/3。編程
【PS】這裏有個細節,碰撞時可能出現球已經重疊的狀況,這個例子只是簡單將末速度加給碰撞後的球,用以彈開他們,這是不嚴謹但有效的作法。
完整示例:單軸碰撞canvas
/** * 單軸碰撞 * */ window.onload = function () { const canvas = document.getElementById('canvas'); const context = canvas.getContext('2d'); const ball0 = new Ball(); const ball1 = new Ball(); // 定義ball0的屬性 ball0.mass = 2; ball0.x = 50; ball0.y = canvas.height / 2; ball0.vx = 1; // 定義ball1的屬性 ball1.mass = 1; ball1.x = 300; ball1.y = canvas.height / 2; ball1.vx = -1; (function drawFrame() { window.requestAnimationFrame(drawFrame, canvas); context.clearRect(0, 0, canvas.width, canvas.height); // 移動兩個物體的位置 ball0.x += ball0.vx; ball1.x += ball1.vx; const dist = ball1.x - ball0.x; // 碰撞檢測 if (Math.abs(dist) < ball0.radius + ball1.radius) { // 運用動量守恆計算碰撞後速度 const vxTotal = ball0.vx - ball1.vx; ball0.vx = ((ball0.mass - ball1.mass) * ball0.vx + 2 * ball1.mass * ball1.vx) / (ball0.mass + ball1.mass); ball1.vx = vxTotal + ball0.vx; // 將速度加到兩物體的位置上實現彈開 ball0.x += ball0.vx; ball1.y += ball1.vx; } // 繪製兩球 ball0.draw(context); ball1.draw(context); }()); };
現實狀況不多會出現單軸碰撞,若是兩個軸上都有速度,處理起來會比較麻煩,把速度分解出來再代入動量守恆公式,這裏運用到上一篇中關於座標旋轉的知識。 函數
基本思路:學習
示例是兩個隨機初始速度的球在空間內碰撞,碰到邊界也會反彈,因爲代碼量較大,這裏只截取部分核心代碼:
注意:旋轉是以ball0爲原點進行的,也就是說旋轉中的全部位置和速度都是相對於ball0的,全部迴旋後的位置和速度須要轉換成相對於相對區域位置。
完整示例:雙軸碰撞動畫
// 座標旋轉函數 function rotate(x, y, sin, cos, reverse) { return { x: (reverse) ? (x * cos + y * sin) : (x * cos - y * sin), y: (reverse) ? (y * cos - x * sin) : (y * cos + x * sin), }; } // 檢查碰撞 function checkCollision() { const dx = ball1.x - ball0.x; const dy = ball1.y - ball0.y; const dist = Math.sqrt(dx ** 2 + dy ** 2); // 基於距離的碰撞檢測 if (dist < ball0.radius + ball1.radius) { // 以ball0爲中心點旋轉 const angle = Math.atan2(dy, dx); const sin = Math.sin(angle); const cos = Math.cos(angle); // ball0在中心點 const pos0 = { x: 0, y: 0, }; // 依據ball1與ball0的相對距離計算旋轉後的座標(反向) const pos1 = rotate(dx, dy, sin, cos, true); // 旋轉ball0的速度(反向) const vel0 = rotate(ball0.vx, ball0.vy, sin, cos, true); // 旋轉ball1的速度(反向) const vel1 = rotate(ball1.vx, ball1.vy, sin, cos, true); // 計算相對速度 const vxTotal = vel0.x - vel1.x; // 計算相撞後速度 vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) / (ball0.mass + ball1.mass); vel1.x = vxTotal + vel0.x; // 計算相撞後位置 pos0.x += vel0.x; pos1.x += vel1.x; // 迴旋位置 const pos0F = rotate(pos0.x, pos0.y, sin, cos, false); const pos1F = rotate(pos1.x, pos1.y, sin, cos, false); // 將相對ball0位置轉換爲相對區域位置 ball1.x = ball0.x + pos1F.x; ball1.y = ball0.y + pos1F.y; ball0.x += pos0F.x; ball0.y += pos0F.y; // 迴旋速度 const vel0F = rotate(vel0.x, vel0.y, sin, cos, false); const vel1F = rotate(vel1.x, vel1.y, sin, cos, false); ball0.vx = vel0F.x; ball0.vy = vel0F.y; ball1.vx = vel1F.x; ball1.vy = vel1F.y; } }
加入多個物體,只是把兩個物體的碰撞檢測,改變成全部物體兩兩間作碰撞檢測。
基本思路:
依據這個思路咱們獲得了這樣一個示例,球的質量、大小和初始速度都是隨機的,碰撞代碼基本和前面是同樣的。
完整示例:多物體碰撞(無處理重疊)
仔細觀察示例,會發現這裏會出現一個問題:小球會重疊到一塊兒而且沒法分離。這是由以下緣由形成的:
【PS】爲何沒法徹底分開?由於咱們分開兩物體的作法是將新速度加到新位置上,若是舊位置已經重疊,那就永遠沒法分離了。
改變分開兩物體的處理辦法就能解決這個問題,這裏有個較爲簡單但不是很精確的辦法:
完整示例:多物體碰撞
改造後核心代碼以下:
function checkCollision(ball0, ball1) { const dx = ball1.x - ball0.x; const dy = ball1.y - ball0.y; const dist = Math.sqrt(dx ** 2 + dy ** 2); // 基於距離的碰撞檢測 if (dist < ball0.radius + ball1.radius) { // 以ball0爲中心點旋轉 const angle = Math.atan2(dy, dx); const sin = Math.sin(angle); const cos = Math.cos(angle); // ball0在中心點 const pos0 = { x: 0, y: 0, }; // 依據ball1與ball0的相對距離計算旋轉後的座標(反向) const pos1 = rotate(dx, dy, sin, cos, true); // 旋轉ball0的速度(反向) const vel0 = rotate(ball0.vx, ball0.vy, sin, cos, true); // 旋轉ball1的速度(反向) const vel1 = rotate(ball1.vx, ball1.vy, sin, cos, true); // 計算相對速度 const vxTotal = vel0.x - vel1.x; // 計算相撞後速度 vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) / (ball0.mass + ball1.mass); vel1.x = vxTotal + vel0.x; // 計算出絕對速度和重疊量,分離避免物體重疊 const absV = Math.abs(vel0.x) + Math.abs(vel1.x); const overlap = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x); pos0.x += vel0.x / absV * overlap; pos1.x += vel1.x / absV * overlap; // 迴旋位置 const pos0F = rotate(pos0.x, pos0.y, sin, cos, false); const pos1F = rotate(pos1.x, pos1.y, sin, cos, false); // 將相對ball0位置轉換爲相對區域位置 ball1.x = ball0.x + pos1F.x; ball1.y = ball0.y + pos1F.y; ball0.x += pos0F.x; ball0.y += pos0F.y; // 迴旋速度 const vel0F = rotate(vel0.x, vel0.y, sin, cos, false); const vel1F = rotate(vel1.x, vel1.y, sin, cos, false); ball0.vx = vel0F.x; ball0.vy = vel0F.y; ball1.vx = vel1F.x; ball1.vy = vel1F.y; } }