在〈寫個 2D 矩陣庫〉寫了個簡單的程式庫,只不過自行實現矩陣運算好像有點麻煩,有沒有現成的程式庫?
在我撰寫這份文件之前,我曾經玩過〈WebGL〉,在矩陣運算方面,有個 glMatrix 程式庫可以使用,實際上,〈寫個 2D 矩陣庫〉中簡單的程式庫,就是仿造 glMatrix 的簡單版本。
撰寫本文時,glMatrix 版本是 2.0,src 目錄中的原始碼是基於 ES6 實作,使用 ES6 模組功能來管理不同的模組原始碼,可以在 dist 中找到 gl-matrix.js,這是透過建構工具建構後,不使用 ES6 相關語法的版本,在瀏覽器載入的話,會有個全域變數 glMatrix
,擁有的特性是 src 看到的各個模組名稱。
gl-matrix-min.js 是 gl-matrix.js 的壓縮版本,實際上線後的頁面可以使用這個版本。
如果要查詢 API 文件,可以查看 Documentation,基本上就是將原始碼中註解文件的部份用 JSDoc 網頁化,我是都直接看原始碼中的註解。
glMatrix 採用 OpenGL/WebGL 的慣例,以線性陣列來實作矩陣時,都是採取行為主(column-major),矩陣相關函式的參數部份可以接受 Array
、Float32Array
或 Array-like 實例,不過傳回型態多半是 Float32Array
而不是 Array
,這是為了配合 WebGL,不過 Float32Array
是 Array-like,必要時也可以使用 Array.from
轉為 Array
,用來搭配 p5.js,倒也不會有什麼大問題。
如果 glmatrix-min.js 放在 js 資料夾,可以透過 <script src="js/gl-matrix-min.js"></script>
來引入 glmatrix-min.js,那麼該怎麼使用 mat3
呢?直接透過 glMatrix.mat3
當然也可以,另一個方式是指定給變數,若不想要 glMatrix
全域變數,也可將之刪除:
<script src="js/p5.min.js"></script>
<script src="js/gl-matrix-min.js"></script>
<script>
const mat3 = glMatrix.mat3;
delete window.glMatrix;
...你的程式碼
</script>
這麼一來 mat3
就成為全域變數了,若不想要它是全域變數呢?在 p5.js 的〈libraries tutorial〉中介紹了,如何將你的程式庫介接至 p5.js,根據該文件,最簡單的方式是:
<script src="js/p5.min.js"></script>
<script src="js/gl-matrix-min.js"></script>
<script>
p5.prototype.mat3 = glMatrix.mat3;
delete window.glMatrix;
...你的程式碼
</script>
這麼一來就解決了全域變數的問題,而在 p5.js 的 setup
、draw
函式中,也可以直接使用 mat3
這個名稱(其實是以 p5
實例為名稱空間,這是 p5.js 實作上的一點小技巧)。
來看幾個 glMatrix 簡單的使用案例,以 mat3
為例,要建立 3 x 3 單位矩陣,可以使用 mat3.create
,也就是傳回的矩陣若以陣列表示會是:
[
1, 0, 0,
0, 1, 0,
0, 0, 1
]
mat3.translate(m, m, [10, 10])
的第一個參數會用來儲存位移操作後的結果,第二個參數是要參與位移運算的矩陣,就上例來說,m
的內容會被改變,也就是 m
結果會是:
[
1, 0, 0,
0, 1, 0,
10, 10, 1
]
如果不想改變 m
,就是在第一個參數指定另一個矩陣,例如:
const m = mat3.create();
console.log(m);
const r = mat3.translate(mat3.create(), m, [10, 10]);
console.log(m); // 可以看到 m 內容沒有改變
console.log(r); // 矩陣運算後的結果
因此,對於〈寫個 2D 矩陣庫〉中第二個範例,就可以改寫為以下:
<html>
<body>
<script src="js/p5.min.js"></script>
<script src="js/gl-matrix-min.js"></script>
<script>
p5.prototype.mat3 = glMatrix.mat3;
delete window.glMatrix;
p5.prototype.forApplyMatrix = function(m) {
return m.filter((elem, idx) => (idx + 1) % 3 !== 0);
};
const d1 = 150;
const d2 = 50;
function setup() {
createCanvas(300, 300);
frameRate(15);
}
let angle = 0;
function draw() {
angle = (angle + 10) % 360;
const m = mat3.create();
mat3.translate(m, m, [width / 2, height / 2]);
mat3.rotate(m, m, angle * PI / 180);
background(255, 0, 0);
applyMatrix(...forApplyMatrix(m)); // 套用矩陣
circle(d1 / 2, 0, d2);
}
</script>
</body>
</html>
執行的結果與〈寫個 2D 矩陣庫〉第二個範例是相同的。
接下來只是個人的實驗,先前文件的範例都使用了 p5.js-widget 提供的編輯器來展示,只不過,該怎麼讓編輯器外掛程式庫啊?單純使用 script
標籤沒有用,p5.js-widget 官方沒有提供方法。
在不修改 p5.js-widget 原始碼的情況下,我的做法是寫個 script
函式來動態載入外部程式庫,載入後將程式庫中要用到的名稱,掛到 window
(畢竟 p5.js-widget 只用來示範一些小程式,就別太在意全域變數的問題了),例如:
可以將編輯區的捲軸往下來,就可以看到相關的程式碼了,因為 script
函式是非同步,為了在執行 setup
、draw
前能準備好 glMatrix,這邊阻斷了 JavaScript 的執行(阻斷了 0.5 秒),希望這段時間內瀏覽器能完成下載(在瀏覽器的執行緒,不是 JavaScript 的執行緒),這是個不得不為的做法,然而絕大部份情況下能夠成功執行。
之所以不得不用迴圈阻斷的方式,是因為使用 p5.js-widget 其實有些限制,只能以全域模式執行 p5.js,編輯器中的全域範圍又拿不到 p5 這個名稱,因此無法透過 p5.js 的 preload
機制(之後文件中再介紹),來處理非同步下載程式庫後再呼叫 setup
、draw
。
其實 p5.js 有內建的矩陣運算 p5.Matrix,只不過是在 WebGL 模式,也就是 3D 模式中使用(4 x 4 矩陣),也沒有公開在 Reference,然而研究一下它的原始碼後,如果真的要使用 p5.Matrix
的話,可以如下: