上次(→ 使用 HTML Canvas 進行實體模擬)使用 Canvas 建立了斜投影實體模擬。這次我們就來解決上次提到的問題。最後,我們要模擬有空氣阻力時的運動。
多對象支持
上次我們做了單一質量的模擬。這次我們將對其進行改進,以便可以同時進行多個模擬。具體來說,我們將對質點進行分類,以便於處理。
// 類別 Ball { 建構子() {} }
球
該類別有一個位置(十, 是)和速度(軟體, 維) 作為屬性。讓我們可以將初始參數作為物件傳遞給建構函數參數。在這種情況下,初速是速度和角度(v, 程度) 以允許指定它。所有預設值均為0,包括負值。我們還將添加一個屬性來確定物體發射後著陸的位置。
// 球類別 { 建構子(attr) { // 位置 this.x = Math.max(attr.x || 0, 0); this.y = Math.max(attr.y || 0, 0); // 速度 attr.v = Math.max(attr.v || 0, 0), atmin.deg = Mathmax.是 [0, 90] this.vx = attr.v * Math.cos(attr.deg * Math.PI / 180); // 速度的 x 分量 this.vy = attr.v * Math.sin(attr.deg * Math.PI / 180); // 速度的 y 分量 落地了嗎?
位置更新和繪製方法是 球
我會把它交給全班同學。
// 球類 class Ball { ~ // 更新 update(dt) { if (this.landed) return; // 如果已經落地,不做處理 this.x += this.vx * dt; this.y += this.vy * dt; this.y = Math.max(this.y, 0); this.vy } } // 繪製 draw() { ctx.beginPath(); ctx.arc(this.x / scale, this.y / scale, 1, 0, Math.PI * 2); ctx.fill(); } }
然後這個 球
我們也準備一個陣列來儲存實例。在構造函數中自己填充該數組。
const ballList = []; // 用來管理球的陣列 // 球類 class Ball { 建構子(attr) { ~ ballList.push(this); // 將自己儲存在陣列中 } ~ }
進而 請求動畫框架
在回調中,球列表 您所要做的就是逐一取出球,然後更新並繪製它們。
const tick = (time) => { if (!lastTime) lastTime = time; const dt = (time - lastTime) / 1000; // 已用時間 [s] // 清除畫面 ctx.clearRect(0, 0, canvas.width, canvas.height); >dm.height); ; lastTime = time; requestAnimationFrame(tick); };
實際上 球
讓我們創建一些並使它們動起來。
。
看
筆
SHIN Inc.(@shin-inc)的《Projectile Motion 2》
在 CodePen 上。
現在您可以隨意投擲球(當然,投擲太多可能會導致速度變慢或內存不足)。
以下是以 0 度到 90 度之間略有不同的角度發送 200 個物體的範例。
看看筆
SHIN Inc.(@shin-inc)的《Projectile Motion 2 / 200 Balls》
在 CodePen 上。
固定幀率
先前的狀態更新使用的是自上次呼叫以來的時間。但 請求動畫框架
由於是在瀏覽器的渲染時刻調用的,因此幀速率(FPS)無法固定。間隔會根據執行環境和處理負載而變化,因此即使初始條件相同,也可能出現不同的模擬結果。
因此,我想透過計算從開始到現在已經經過了多少幀來固定 FPS,並僅針對自上次呼叫以來的幀增量執行更新過程。
const fps = 30, // FPS dt = 1 / fps; // 每幀的秒數 let startTime, // 開始時間 frameCount = 0; // 已過去的幀數 const tick = (time) => { if (!startTime) startTime = time; // 第一次已調用數 let df = currentFrame - frameCount // 相對於上一次的幀增量 // 透過幀增量重複更新過程 while (df) { // 更新球 ballList.forEach(ball => { ball.update(dt); }); df--; } // 清除屏幕 ctx.garRect(0, 0, can. raw(); }); frameCount = currentFrame; // 更新幀數請求動畫幀(勾選);
如果經過的時間小於幀單位,則更新過程將被跳過並延續到下一次。相反,如果間隔延長,則進行多次更新過程,使間隔保持一致,從而保持幀率恆定。
幀率越高,增量越小,誤差越小。然而,這會增加每個處理會話的負載,使得處理速度更容易變慢。處理速度和準確性之間存在權衡。
這裡採用的斜投影模擬,在判定落地位置時存在誤差,因此到達距離會根據幀速率而略有變化。
有空氣阻力時的斜投影
到目前為止,我們一直致力於改進模擬方法。從這裡開始,我們將改變要模擬的現象。讓我們將空氣阻力添加到我們目前的簡單斜投影中。
眾所周知,當速度較小時,流體阻力(拖曳力)與物體的速度成正比,當速度較大時,流體阻力(拖曳力)與速度的平方成正比;然而,對於在空氣中的運動,可以安全地假設流體阻力大致與速度的平方成正比(在日常尺度上,空氣的黏度較小,流速的貢獻較大)。
新的 球
繼承類別並承受空氣阻力 球空氣阻力
創建一個類別。空氣阻力的大小(係數)會根據物體的形狀而變化,因此可以設定任意值。此外,空氣阻力的增加也導致x軸方向速度的變化。
*當電阻為 0 時 球
因為它與球
您也可以直接擴展它。
現在,空氣阻力的 x 和 y 分量與速度的平方成正比,可如下計算:
(F 是空氣阻力,鉀 為空氣阻力係數,v 是速度,θ 是速度與水平面所成的角度)
// BallAirResistance 類別 class BallAirResistance extends Ball { const (attr) { super(attr); this.k = Math.max(attr.k || 0, 0); // 空氣阻力係數 } update(dt) { if (this.landed) return; this.y = Math.max(this.y, 0); const v = Math.sqrt(this.vx ** 2 + this.vy ** 2); this.vx += -this.k * v * this.vx * dt; // 小球受到空氣阻力,減速 this.vx = Mathmax(this . .vy) * dt; // 小球同時受到重力和空氣阻力 if (dt && this.y === 0) { this.landed = true; // 登陸 } } }
補充說明:空氣阻力加速度係數與質量成反比(公里/米),但由於這裡沒有用到質量參數,所以係數包含了質量。
下面我們有一個模擬器,可以讓你以相同的初始速度在有空氣阻力和無空氣阻力的情況下飛行。注意空氣阻力如何導致速度急劇下降。此外,有空氣阻力的部分塗成紅色,沒有空氣阻力的部分塗成黃色(有關著色系統,請參閱來源)。
看看筆
SHIN Inc.(@shin-inc)的《彈丸運動模擬器 2》
在 CodePen 上。
下一個挑戰
到目前為止,我們一直在模擬重力加速度恆定的場中的運動。我認為我們能夠創造出相當不錯的東西。
但! 這些都是可以透過分析解決的問題(可以計算積分),因此無需進行模擬即可做出準確的預測。那些計算起來比較困難,或是根本無法透過分析解決的問題,是最值得模擬的問題。
比如說重力根據位置而變化的系統……下次我想嘗試模擬天體的運動。