fbpx

使用 HTML Canvas 進行軌跡模擬

上次兩次前我們對拋物線運動進行了物理模擬。正如承諾的那樣,這次我想討論天體的運動。

眾所周知,當有三個或更多天體(三體問題)時,受引力束縛天體的運動無法得到解析求解。基於計算機的數值模擬對於解決這些類型的問題很有用。

但是處理多個引力比較麻煩,所以我將首先嘗試模擬一個繞著單個天體運行的小物體……具體來說,是繞地球運行的太空船或人造衛星。與地球相比,衛星的質量可以忽略不計,因此我們可以認為唯一的引力就是地球的引力,這大大簡化了問題。

準備畫布

上次到目前為止,座標系的原點位於左下角。這次我們使用具有中心原點的常見笛卡爾座標系。而且,由於它是圓週運動,所以正方形比長方形更好。

</畫布>
const canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'); const dpr = window.devicePixelRatio || 1, // 裝置像素比 width = canvas.width, Canvas 畫布尺寸*= dpr; canvas.height *= dpr; // 使用 CSS 恢復原始大小 canvas.style.width = width + 'px'; canvas.style.height = height + 'px'; // 放大 Canvas 繪製本身 1/scale(dprx.); ,使原點位於中心 ctx.translate(width / 2, -height / 2);

規模翻譯 如果你把所有內容寫在一行上,它看起來像這樣。

ctx.transform(dpr, 0, 0, -dpr, 寬度* dpr / 2, 高度* dpr / 2);

思維方式

上次和以前一樣,我們測量時間併計算位置和速度的變化,但這一次,加速度(重力)會根據位置而變化。

作用於兩個物體之間的引力可以用以下公式計算。
F 是引力的大小, 萬有引力常數,毫米 是每個物體的質量,r 是物體間的距離)

F = r 2

將其代入運動方程,我們得到

一個 = r 2
一個 = r 2

可以被擦除。因此我們可以看出,物體在單受重力作用時運動的加速度只取決於重力源的質量,與物體本身的質量無關(這也是為什麼重物體與輕物體下落速度相同的原因)。

換句話說,我們不需要考慮衛星的質量,因為假設它足夠小,不會施加任何引力。您所需要的只是地球的質量及其與地心的距離。此外,地球中心固定在座標原點[0,0]。

單位和常數

距離的單位是公里,質量的單位是公斤。應使用。

萬有引力常數為 6.674 × 10-20公里3s-2公斤-1 被使用。此外,地球的質量為 5.972 × 1024公斤,半徑為 6.378 × 103假設為 km。這些常量應該提前定義。

const G = 6.674 * 1e-20, // 萬有引力常數 Me = 5.972 * 1e24, // 地球質量 Re = 6.378 * 1e3; // 地球半徑

由於我們這次只考慮地球引力,所以我們也將準備一個地球引力係數常數,它是地球質量和引力常數的乘積。我們還需要準備一個比例常數,並確定 1px 代表多少公里。

// 地球重力係數 const K = G * Me; // 比例 const scale = 30; // [km/px]

天文課

建立一個類別來處理天體。此位置在 XY 座標中內部處理,但初始化時可以在極座標(角度和距離)中指定。

三角函數 數學.cos, 數學 由於以弧度作為參數,因此建立一個可以使用直覺的度數方法執行計算的輔助函數很有用。

/** sin */ const sin = deg => { deg %= 360; if (deg === 180) { return 0; // Math.sin 有錯誤,因此傳回正確的值。 90 || deg === 270) { return 0; // Math.cos 有錯誤,因此回傳正確的值。

距中心距離 r ,它與 x 軸的夾角 程度 我們假設一下。

let objectList = []; // 管理陣列/**天體類別 */ class AstroObject { const (attr) { // 位置 this.x = attr.r * cos(attr.deg); this.y = attr.r * sin(attr.deg); } } }

類似地,速度可以透過大小和角度來指定。參考角度可以是任何位置,但這裡我們將使用 r 軸(徑向)作為參考。極座標系的旋轉方向恆定為90度。這個角度加上位置角就是速度相對於 x 軸的角度。

速度的大小v,相對於 r 軸的角度為電壓水平我們假設一下。

/** 天體類別 */ class AstroObject { 建構子(attr) { ... // 速度 this.vx = attr.v * cos(attr.vdeg + attr.deg);this.vy = attr.v * sin(attr.vdeg + attr.deg);this.vy = attr.v * sin(attr.vdeg + attr.deg);

如上所述,品質不是必需的。為了容納多顆衛星,我們將陣列本身儲存在建構函數中。

距中心距離r和角度拉德, 程度我們也來設定一個可以進行向後計算的 getter。

... // 取得與中心的距離 r() { return Math.hypot(this.x, this.y); } // 取得角度 rad() { // x = 0 if (!this.x) { if (!this.y) { return 0; // 如果 [0, 0] 則回傳 0 if ( 90° } } let rad = Math.atan(this.y / this.x); // arctan(y/x) : -π/2 到 π/2 if (this.x < 0) { // 第二和第三象限 rad += Math.PI; } else if (this.x > 0 & 象限數: rad; } 取得 deg() { 回傳 this.rad * 180 / Math.PI; } ...

新增繪圖和更新方法。

... draw() { // 繪製一個 4px x 4px 的正方形 ctx.beginPath(); ctx.rect(this.x / scale - 2, this.y / scale - 2, 4, 4); ctx.fill(); } update(this. x * dt; this.y += this.vy * dt; this.vx += grave * cos(this.rad) * dt; this.vy += grave * sin(this.rad) * dt; } ...

集會

在畫布上繪圖和更新上次和以前基本上一樣。我們只需添加一點點來融入繪製地球的過程。

const fps = 30, // FPS dt = 1 / fps; // 每幀的秒數 let startTime, // 開始時間 frameCount = 0; // 已過去的幀數 / ** 繪製地球 */ const drawEarth = () => { ctx.save(); ctx.fillStybe 'ctx. x.arc(0, 0, Re / scale, 0, Math.PI * 2); // 在中心畫一個圓圈 ctx.fill(); ctx.restore(); } /** tick */ const tick = (time) => { if (!startTime) startTime = 折扣; // 迄今為止已過去的幀數 let df = currentFrame - frameCount // 從上次增加// 以訊框增量重複更新程序 while (df) { // 更新物件 objectList.forEach((obj, i) => { if (obj) { obj.update(dt); // 碰撞檢查 if (obj.r <= Re) { objectList[i] = null + 15;陣列中移除碰撞的物件。

現在您就可以開始了。

實踐

讓我們實際發射一顆衛星看看。在這裡我們將把它放置在高度400公里的圓形軌道上,與國際太空站相同。

圓形軌道的軌道速度可用以下公式計算:

v = r

這約為7.67公里/秒。使用該值對物件進行分類。 天文物體 創建 的一個實例。

const satellite = new AstroObject({ r: Re + 400, // 地球半徑 + 高度 deg: 90, v: 7.67, vdeg: 90 });

進而 打鉤() 啟動程式以開始模擬。



SHIN Inc.(@shin-inc)的 Orbital Motion
在 CodePen 上。

你怎麼認為? 它可能看起來就像您看到的一個圓圈和一個小正方形。 30公里顯示為1px,因此以每秒7.67公里的速度移動1px大約需要4秒。由於速度相對於尺度來說太慢,所以看起來好像它們幾乎沒有移動。在400公里的高度,它完成一次旋轉大約需要一個半小時。

如果一直這樣觀看一個半小時會比較困難,所以我要進行改進,讓它能夠以兩倍的速度播放。方法很簡單,以雙倍的速度重複一幀內的更新過程。

const speed = 200; // 播放速度... /** tick */ const tick = (time) => { ... let df = currentFrame - frameCount // 與上一次相比的幀增量 df *= speed; // 按速度增加進程數 // 按幀增量重複更新過程 while (df) { ... } ... }

這將以更快的速度播放。



SHIN Inc.(@shin-inc)的 Orbital Motion x200
在 CodePen 上。

你可以清楚地看到它沿著地球的圓形軌道運行。嘗試不同的速度和高度。

同時操作多顆衛星是可能的,但隨著衛星數量的增加,負載自然也會增加。另外,請注意,速度越高,負載越大,處理速度越慢。

任務

這次我們創造了一個地球軌道的模擬。最後,以下是一些未來的改進:

改變中心恆星

如果把中心恆星變成一個物體,以便可以自由調整它的參數,而不是僅僅把它固定在地球上,應用範圍將大大擴展。您將能夠自由模擬圍繞虛構行星(包括火星和金星)的軌道。

多重重力源

我希望能夠處理多個引力源,例如地球和月球,而不是像人造衛星這樣的小天體。對於繞地球運行的軌道,可以忽略月球的引力,但如果你想處理靠近月球的軌道,你必須考慮兩者的引力。

當處理多個重力源時,必須分別處理重力加速度和位置更新的計算,這使得處理更加複雜。

數值改進

現在的積分計算方法叫歐拉法,這種方法不太準確。因此,可以考慮用跳蛙法等更精確的方法來取代它。如果計算不精確,歐拉方法也可以,因此優先順序較低。

獎金

我們將介紹物體在軌道上如何以違反地面直覺的方式運作。

前方紅色物體飛行高度400公里,速度為7.67公里/秒。有一藍色物體以相同的高度和8公里/秒的速度追趕。



SHIN Inc.(@shin-inc)的 Orbital Chaser Motion
在 CodePen 上。

可以看到,一開始好像距離越來越近,但是逐漸地距離增加,最後藍色物體就追不上紅色物體了。如果您試圖在賽道上追逐某人,僅僅提高速度是行不通的。

那你該怎麼做呢?

類別: 前端開發

您想開始免費諮詢前端開發嗎?

我們提供免費諮詢,以便我們可以根據您的需求提出最佳建議。
請隨時與我們聯繫。

網上面試申請

首先,請您介紹一下網路會議的情況。

(電話、電子郵件、會議均可)