DAY 10
0
Modern Web

## 2D transform Continued

### 旋轉 transform

``````[
0,              0,             1,
]
``````

``````  rotate: rad => {
return [
c, s, 0,
-s, c, 0,
0, 0, 1,
]
},
``````

### 加入平移、縮放、旋轉控制

``````<!-- <form id="controls"> -->
<!-- ... -->
<div class="py-1">
<label for="translate-x">TranslateX</label>
<input
type="range" id="translate-x" name="translate-x"
min="-150" max="150" value="0"
>
</div>
<div class="py-1">
<label for="translate-y">TranslateY</label>
<input
type="range" id="translate-y" name="translate-y"
min="-150" max="150" value="0"
>
</div>
<div class="py-1">
<label for="scale">Scale</label>
<input
type="range" id="scale" name="scale"
min="0" max="10" value="1" step="0.1"
>
</div>
<div class="py-1">
<label for="rotation">Rotation</label>
<input
type="range" id="rotation" name="rotation"
min="0" max="360" value="0"
>
</div>
<!-- </form> -->
``````

`py-1` 為模仿 tailwindCSS 的 padding，因為只有這一個 CSS 所以筆者直接實做在 HTML 中：`.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }`

``````     state: {
texture: 0,
offset: [0, 0],
direction: [Math.cos(directionDeg), Math.sin(directionDeg)],
+      translate: [0, 0],
+      scale: 1,
+      rotation: 0,
speed: 0.08,
},
``````

``````   controlsForm.addEventListener('input', () => {
const formData = new FormData(controlsForm);
app.state.texture = parseInt(formData.get('texture'));
app.state.speed = parseFloat(formData.get('speed'));
const formData = new FormData(controlsForm);
app.state.texture = parseInt(formData.get('texture'));
app.state.speed = parseFloat(formData.get('speed'));
+    app.state.translate[0] = parseFloat(formData.get('translate-x'));
+    app.state.translate[1] = parseFloat(formData.get('translate-y'));
+    app.state.scale = parseFloat(formData.get('scale'));
+    app.state.rotation = parseFloat(formData.get('rotation')) * Math.PI / 180;
});
``````

### 使用旋轉矩陣

`render()` 內，原本 `worldMatrix` 只有平移轉換 `translate(...state.offset);`，現在開始也要由多個矩陣相乘：

``````const worldMatrix = matrix3.multiply(
matrix3.translate(...state.offset),
matrix3.rotate(state.rotation),
);
``````

`matrix3.rotate()` 是基於原點做旋轉的，因此調整一下頂點位置，使得原點在正中間：

``````   // a_position
// ...
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-      0, 0, // A
-      150, 0, // B
-      150, 150, // C
+      -75, -75, // A
+      75, -75, // B
+      75, 75, // C

-      0, 0, // D
-      150, 150, // E
-      0, 150, // F
+      -75, -75, // D
+      75, 75, // E
+      -75, 75, // F
]),
gl.STATIC_DRAW,
);
``````

``````   // function startLoop(app, now = 0) {
// ...
-  if (state.offset[0] + 150 > gl.canvas.width) {
+  if (state.offset[0] > gl.canvas.width) {
state.direction[0] *= -1;
-    state.offset[0] = gl.canvas.width - 150;
+    state.offset[0] = gl.canvas.width;
} else if (state.offset[0] < 0) {
state.direction[0] *= -1;
state.offset[0] = 0;
}

-  if (state.offset[1] + 150 > gl.canvas.height) {
+  if (state.offset[1] > gl.canvas.height) {
state.direction[1] *= -1;
-    state.offset[1] = gl.canvas.height - 150;
+    state.offset[1] = gl.canvas.height;
``````

### 所有 Tranform 我全都要

``````translate(...state.offset) *
rotate(state.rotation) *
scale(state.scale, state.scale) *
translate(...state.translate)
``````

``````  const worldMatrix = matrix3.multiply(
matrix3.multiply(
matrix3.multiply(
matrix3.translate(...state.offset),
matrix3.rotate(state.rotation),
),
matrix3.scale(state.scale, state.scale),
),
matrix3.translate(...state.translate),
);
``````

``````  multiply: (a, b, ...rest) => {
const multiplication = [
a[0]*b[0] + a[3]*b[1] + a[6]*b[2], /**/ a[1]*b[0] + a[4]*b[1] + a[7]*b[2], /**/ a[2]*b[0] + a[5]*b[1] + a[8]*b[2],
a[0]*b[3] + a[3]*b[4] + a[6]*b[5], /**/ a[1]*b[3] + a[4]*b[4] + a[7]*b[5], /**/ a[2]*b[3] + a[5]*b[4] + a[8]*b[5],
a[0]*b[6] + a[3]*b[7] + a[6]*b[8], /**/ a[1]*b[6] + a[4]*b[7] + a[7]*b[8], /**/ a[2]*b[6] + a[5]*b[7] + a[8]*b[8],
];

if (rest.length === 0) return multiplication;
return matrix3.multiply(multiplication, ...rest);
},
``````

`...rest` 的語法叫做 rest parameters，傳超過 2 個參數時再呼叫自己將這回合計算的結果繼續與剩下的矩陣做計算

``````const worldMatrix = matrix3.multiply(
matrix3.translate(...state.offset),
matrix3.rotate(state.rotation),
matrix3.scale(state.scale, state.scale),
matrix3.translate(...state.translate),
);
``````

### 『什麼都不做』轉換

``````  identity: () => ([
1, 0, 0,
0, 1, 0,
0, 0, 1,
]),
``````

``````const worldMatrix = matrix3.multiply(
matrix3.translate(...state.offset),
matrix3.rotate(state.rotation), // 想要暫時取消這行
);
``````

``````const worldMatrix = matrix3.multiply(
matrix3.translate(...state.offset),
matrix3.identity(),
// matrix3.rotate(state.rotation), // 想要暫時取消這行
);
``````

``````const worldMatrix = matrix3.multiply(
matrix3.identity(),
matrix3.translate(...state.offset),
matrix3.rotate(state.rotation),
matrix3.scale(state.scale, state.scale),
matrix3.translate(...state.translate),
);
``````

Texture & 2D Transform 就到這邊，筆者學習到此的時候深刻感受到線性代數的威力，輸入的矩陣與理論結合扎實地反應在螢幕上，並為接下來 3D transform 打好基礎，下個章節將進入 3D，開始嘗試渲染現實世界所看到的樣子