Chapter 48

行列・アフィン変換

miku
miku
2021.11.15に更新

この章ではアフィン変換について扱う。アフィン変換は座標変換の関数である translate(), scale(), rotate() を実装するために必要な機能で、アフィン変換は行列の計算が必要なので、まずは行列について扱う。

行列

\begin{pmatrix} a & b \\ c & d \\ \end{pmatrix}

二次元配列のように数を並べたものを行列と呼ぶ。この a, b, c, d には好きな数を入れていい。

\begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ \end{pmatrix} \begin{pmatrix} 1 & 2 & 3 \\ \end{pmatrix} \begin{pmatrix} 1 \\ 2 \\ \end{pmatrix}

行と列の数は一致している必要はなく、たとえば上記のような形も行列である。

\begin{pmatrix} a_{11} & \cdots & a_{1i} & \cdots & a_{1n}\\ \vdots & \ddots & & & \vdots \\ a_{i1} & & a_{ii} & & a_{in} \\ \vdots & & & \ddots & \vdots \\ a_{m1} & \cdots & a_{mi} & \cdots & a_{mn} \end{pmatrix}

一般化して扱う際、a, b, c, d のように書き並べるのは限度があるので、添字で表現すると上記のようになる。m, n がそれぞれ行数、列数を表し、m x n 行列と呼ぶ。

行列の和

\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ \end{pmatrix} + \begin{pmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \\ \end{pmatrix} = \begin{pmatrix} a_{11} + b_{11} & a_{12} + b_{12} \\ a_{21} + b_{21} & a_{22} + b_{22} \\ \end{pmatrix}
\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix} + \begin{pmatrix} 11 & 12 \\ 13 & 14 \\ \end{pmatrix} = \begin{pmatrix} 1 + 11 & 2 + 12 \\ 3 + 13 & 4 + 14 \\ \end{pmatrix}

行列内の数を成分と呼ぶとすると、行列の和は対応している成分同士を足し合わせた新しい行列を作る。

行列の差

\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ \end{pmatrix} - \begin{pmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \\ \end{pmatrix} = \begin{pmatrix} a_{11} - b_{11} & a_{12} - b_{12} \\ a_{21} - b_{21} & a_{22} - b_{22} \\ \end{pmatrix}
\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix} - \begin{pmatrix} 11 & 12 \\ 13 & 14 \\ \end{pmatrix} = \begin{pmatrix} 1 - 11 & 2 - 12 \\ 3 - 13 & 4 - 14 \\ \end{pmatrix}

行列の差は対応している成分同士で引き算をした新しい行列を作る。

零行列

\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ \end{pmatrix} + \begin{pmatrix} 0 & 0 \\ 0 & 0 \\ \end{pmatrix} = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ \end{pmatrix}

a + 0 = a のように数に0を足したり引いたりしても数の変化は起こらない。行列でも同じように、全ての成分が0である行列を足したり引いたりしても何も変わらないので、このような行列を零行列と呼ぶ。

行列の積

\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ \end{pmatrix} \begin{pmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \\ \end{pmatrix} = \begin{pmatrix} a_{11}b_{11} + a_{12}b_{21} & a_{11}b_{12} + a_{12}b_{22} \\ a_{21}b_{11} + a_{22}b_{21} & a_{21}b_{12} + a_{22}b_{22} \\ \end{pmatrix}
\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix} \begin{pmatrix} 5 & 6 \\ 7 & 8 \\ \end{pmatrix} = \begin{pmatrix} 1 \times 5 + 2 \times 7 & 1 \times 6 + 2 \times 8 \\ 3 \times 5 + 4 \times 7 & 3 \times 6 + 4 \times 8 \\ \end{pmatrix} = \begin{pmatrix} 19 & 22 \\ 43 & 50 \\ \end{pmatrix}

行列の積はかなりややこしい計算だが、定義なのでこの通りに覚えるしかない。左側の行列は横に、右側の行列は縦に見て、それぞれの成分を掛けて足し合わせる。

計算ができないとき

\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ \end{pmatrix} \begin{pmatrix} b_{11} & b_{12} \\ \end{pmatrix} = error

行列の和、差は、行と列の数が一致していないと計算ができない。

\begin{pmatrix} a & b \\ c & d \\ \end{pmatrix} \begin{pmatrix} p \\ q \end{pmatrix} = \begin{pmatrix} ap + bq \\ cp + dq \end{pmatrix}
\begin{pmatrix} p \\ q \end{pmatrix} \begin{pmatrix} a & b \\ c & d \\ \end{pmatrix} = error

行列ABの積は、Aの列数とBの行数が等しくないと計算ができない。

単位行列

\begin{pmatrix} 1 & 0 \\ 0 & 1 \\ \end{pmatrix}

a * 1 = a のように数に1を掛けても数の変化は起こらない。行列でも同じように、何かを掛けてもその行列のままにしたいとき、掛ける行列のことを単位行列と呼ぶ。

アフィン変換

\begin{pmatrix} X \\ Y \end{pmatrix} = \begin{pmatrix} a & b \\ c & d \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} + \begin{pmatrix} tx \\ ty \end{pmatrix}

アフィン変換の式は上記のようになる。

(x, y) が座標で、(a, b, c, d, tx, ty) の値をうまく設定することで移動や拡大縮小、回転を行うことができる。

applyMatrix(a, b, c, d, tx, ty);

p5.jsにはアフィン変換を適用する関数 applyMatrix() があり、上記の (a, b, c, d, tx, ty) の値を設定することができる。

基本形

\begin{pmatrix} X \\ Y \end{pmatrix} = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} + \begin{pmatrix} 0 \\ 0 \end{pmatrix}
applyMatrix(1, 0, 0, 1, 0, 0);

行列の掛け算と足し算をしても x, y が変わらない形にするには、単位行列をかけ合わせたあと、零行列を足し合わせればいい。

なので、(a, b, c, d) を単位行列、(tx, ty) を零行列にした (1, 0, 0, 1, 0, 0) を実行すると何も座標の変化は起こらない。

平行移動

\begin{pmatrix} X \\ Y \end{pmatrix} = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} + \begin{pmatrix} tx \\ ty \end{pmatrix}

現在の座標 (x, y) から (tx, ty) だけ平行移動する式。

// 現在の座標から(tx, ty)だけ移動する
// translate(tx, ty)と同等の機能
applyMatrix(1, 0, 0, 1, dx, dy);

拡大縮小

\begin{pmatrix} X \\ Y \end{pmatrix} = \begin{pmatrix} a & 0 \\ 0 & b \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} + \begin{pmatrix} 0 \\ 0 \end{pmatrix}

現在の座標 (x, y)(a, b) 倍拡大縮小する式。

// 現在の座標から横にa倍、縦にb倍だけ拡大縮小する
// scale(a, b)と同等の機能
applyMatrix(a, 0, 0, b, 0, 0);

回転

\begin{pmatrix} X \\ Y \end{pmatrix} = \begin{pmatrix} cosθ & -sinθ \\ sinθ & cosθ \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} + \begin{pmatrix} 0 \\ 0 \end{pmatrix}

現在の座標 (x, y) を軸に反時計周りに θ だけ回転する式。cossin が入った左側の行列のことを回転行列と呼ぶ。

\begin{pmatrix} X \\ Y \end{pmatrix} = \begin{pmatrix} cosθ & sinθ \\ -sinθ & cosθ \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} + \begin{pmatrix} 0 \\ 0 \end{pmatrix}

時計回りだとこうなる。

// 現在の座標 (x, y) を軸に「反時計回り」に angle ラジアン回転する
// rotate(-angle) と同等の機能
const angle = PI / 6; // 30度
applyMatrix(cos(angle), -sin(angle), sin(angle), cos(angle), 0, 0);
// 現在の座標 (x, y) を軸に「時計回り」に angle ラジアン回転する
// rotate(angle) と同等の機能
const angle = PI / 6; // 30度
applyMatrix(cos(angle), sin(angle), -sin(angle), cos(angle), 0, 0);

アフィン変換を利用した作例

let angle = 0;

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function draw() {
  clear();

  applyMatrix(1, 0, 0, 1, width / 2, height / 2);
  applyMatrix(2, 0, 0, 2, 0, 0);
  applyMatrix(cos(angle), sin(angle), -sin(angle), cos(angle), 0, 0);
  circle(100, 0, 20);

  angle += 0.02;
}