作為物理模擬開場的第一進程,當然就要來講一下最經典的物理模擬案例:『彈跳球』~
其實很多國外的Canvas特效教程都會把這一篇當成第一個介紹案例,比方說
這邊推薦一下 Apress Physics for JavaScript Games Animation and Simulations
這本書,因為在學習物理模擬的路上這本書給了我不少幫助XD~
在這個案例中我們除了會介紹彈跳球的案例,還會介紹一些關於這個案例的基礎物理常識,最後還會帶到一些更進階的物理模擬實作。
在一開始我們還不會馬上的帶到程式源碼,而是要先來討論高中數理的向量
、反射
與斜向拋射
,由於我們在這個案例中會持續用到的三個基礎概念,所以我打算在一開始就講清楚物理模擬在這三部分的相關概念。
我們在這篇文章中會先討論到Canvas
中向量類
的建立,就讓我們接著開始吧~
向量是什麼樣的概念?
我們其實在前面的文章有提過向量
,向量指的是一種從座標A移動到座標B的附帶方向的移動量,從數學的角度上來看,假設今天有一個質點即將從(1,2)
移動到(2,4)
,則我們可說這個質點被附加了一個(1,2)
的移動向量。
向量如果要轉變成純量,那麼就必須要取該向量X,Y值的平方和,然後再開根號(畢氏定理),以我們剛剛提到的(1,2)
,他的純量就是√5
(也就是該質點一共移動了√5
的距離長度)。
向量再轉變成純量的過程中會丟失他的方向屬性,而變成單純的量值,所以如果今天換成另外一個案例,假設我們只知道移動的距離是√5
而不知道這個移動的起始點和結束點; 想要把√5這個距離轉變成向量(也就是要知道水平和垂直移動的距離),那我們就必須要先獲知該純量的方向(也就是下圖中的角度θ
),然後用三角函數來把√5
轉變成1
(水平移動量)和2
(垂直移動量)。

1
| (cosθ * √5, sinθ * √5) = (1,2)
|
除了向量變純量, 純量變向量的運算以外,向量之間有其他類型的運算,像是:
以下面這張圖為例,我們可以可以把紫色向量看作是向量a(紅色向量)和向量b(藍色向量)的和。
所以反過來也可以推導紫色向量
- 向量a
= 向量b

內積
是一個有趣的概念,求取兩個向量內積
的方法如下:
1 2
| 假設向量a為(ax,ay),向量b則是(bx,by) 則向量a與向量b的內積是ax*bx+ay*by
|
內積的結果會是一個純量,他的幾何意義在於我們可以透過內積取得兩個向量的夾角。
透過內積取得夾角的公式如下:

一般來說,內積的值大於0
,代表兩向量夾角低於90度
,
內積的值等於0
,代表兩個向量互相垂直
,
內積的值小於0
,代表兩個向量夾角介於90度到180度
之間。
對公式推導有興趣的人可以看這邊
用javascript建立向量類(Vector Class)
在前端開發的環境下,我們其實可以利用ES6的class(當然也可以用ES5的構築式)去給向量
建立一個獨立的類。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
| class Vector2D { constructor(x, y) { this.x = x; this.y = y; }
length() { return Math.sqrt(this.lengthSquared()); }
clone() { return new Vector2D(this.x, this.y); }
negate() { this.x = - this.x; this.y = - this.y; }
normalize() { let length = this.length(); if (length > 0) { this.x /= length; this.y /= length; } return this.length(); }
add(vec) { return new Vector2D(this.x + vec.x, this.y + vec.y); }
incrementBy(vec) { this.x += vec.x; this.y += vec.y; }
subtract(vec) { return new Vector2D(this.x - vec.x, this.y - vec.y); }
decrementBy(vec) { this.x -= vec.x; this.y -= vec.y; }
multiply(k) { return new Vector2D(k * this.x, k * this.y); }
scaleBy(k) { this.x *= k; this.y *= k; }
dotProduct(vec) { return this.x * vec.x + this.y * vec.y; }
projection(vec) { const length = this.length(); const lengthVec = vec.length(); let proj; if ((length == 0) || (lengthVec == 0)) { proj = 0; } else { proj = (this.x * vec.x + this.y * vec.y) / lengthVec; } return proj; }
project(vec) { return vec.para(this.projection(vec)); }
para(u, positive = true) {
const length = this.length(); const vec = new Vector2D(this.x, this.y); if (positive) { vec.scaleBy(u / length); } else { vec.scaleBy(-u / length); } return vec; }
static angleBetween = function (vec1, vec2) { return Math.acos(vec1.dotProduct(vec2) / (vec1.length() * vec2.length())); }
}
|
這邊我其實是參照Apress Physics for JavaScript Games Animation and Simulations, With HTML5 Canvas
上的寫法,改寫成ES6 Class,並刪除部分不常用到的方法。
我們在接下來的文章中會持續的用到由這邊建立好的向量類,所以各位同學可以看一下這個類裡面都有些什麼方法~
下一篇文我們將會講到如何在Canvas中實作反射(Reflection)行為,敬請期待~