はじめに
なんか1次変換を教えることが続いたので、記事にしました。
教えた時に「なんでこれで、回転とか平行移動ができるんだ?」って疑問を持つ人が多かったので、
変換行列導出について、丁寧に記載しました。
数学的に厳密でないところが多々あると思います。
予めご了承ください。
なお、アフィン変換について知っている人は応用編のところからお読みください。
前提
・中学レベルの数学を理解していること
・三角関数の加法定理を知っていること
・行列の掛け算ができること
変換するとは
xとyが以下の、①、②式でx'とy'になるとする。
x'=2x-y…①\\
y'=x+y…②
①、②式のx,yに具体的な値を入れてみましょう。
例えば、①、②式に\\
x=3,y=1を代入してみる。\\
すると、\\
x'=2 \times 3 - 1 = 5\\
y'=3 + 1 = 4\\
になる。
これが変換です。
xとyの次数が1次なので、「1次変換」って言います。
座標の変換に使われるのが「行列」
①、②式は行列を使うと、以下のように書ける。
\begin{pmatrix}
x'\\
y'\\
\end{pmatrix}
=
\begin{pmatrix}
2 & -1\\
1 & 1\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
\end{pmatrix}…③\\
行列を使って表示すると、すっきりして見える。
実際、③式を計算すると、
\begin{pmatrix}
x'\\
y'\\
\end{pmatrix}
=
\begin{pmatrix}
2x-y\\
x+y\\
\end{pmatrix}…④
となり、④式は①、②式をまとめて書いたものだということがわかる。
以下で、よく使われるを3つ(拡大行列、回転行列、平行移動行列)求めてみる。
ちなみに、拡大、回転、平行移動、せん断による変換を「アフィン変換」と呼びます。
※せん断については本記事では取り上げません。
拡大行列を求めてみよう
xがs_{x}倍、yがs_{y}倍する2行2列の行列を求める。\\
\\
求めたい拡大行列を\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}\\
とおく。\\
\\
xがs_{x}倍、yがs_{y}倍とされるとき\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
\end{pmatrix}
=
\begin{pmatrix}
s_{x}x\\
s_{y}y\\
\end{pmatrix}…⑤となる。\\
\\
⑤式の左辺を計算すると、\\
\begin{pmatrix}
ax+by\\
cx+dy\\
\end{pmatrix}…⑥\\
となる。\\
⑤、⑥式の係数を比較して\\
a=s_{x}\\
b=0\\
c=0\\
d=s_{y}\\
\\
a,b,c,dが求まったので、拡大行列は、\\
\begin{pmatrix}
s_{x} & 0\\
0 & s_{y}\\
\end{pmatrix}…⑦\\
となる。
確認
\begin{pmatrix}
2\\
2\\
\end{pmatrix}をxに2倍、yに3倍する行列を掛けて、
\begin{pmatrix}
4\\
6\\
\end{pmatrix}となるか検証します。\\
\\
xに2倍、yに3倍する拡大行列は⑦より\\
\begin{pmatrix}
2 & 0\\
0 & 3\\
\end{pmatrix}\\
\\
この拡大行列に
\begin{pmatrix}
2\\
2\\
\end{pmatrix}を掛けると\\
\begin{pmatrix}
2 & 0\\
0 & 3\\
\end{pmatrix}
\begin{pmatrix}
2\\
2\\
\end{pmatrix}
=
\begin{pmatrix}
2 \times 2 + 0 \times 2\\
0 \times 2 + 3 \times 2\\
\end{pmatrix}
=
\begin{pmatrix}
4\\
6\\
\end{pmatrix}
となり、思った通りの結果が得られました。
回転行列を求めてみよう
{\theta}回転する2行2列の行列を求める。\\
\\
求めたい回転行列を\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}\\
とおく。\\
\\
まず\\
\begin{pmatrix}
1\\
0\\
\end{pmatrix}\\
が{\theta}回転する場合を考える。\\
\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}
\begin{pmatrix}
1\\
0\\
\end{pmatrix}
=
\begin{pmatrix}
\cos{\theta} \\
\sin{\theta}\\
\end{pmatrix}…⑧となる。\\
\\
⑧式の左辺を計算すると、\\
\begin{pmatrix}
a \times 1 + b \times 0\\
c \times 1 + d \times 0\\
\end{pmatrix}
=
\begin{pmatrix}
a\\
c\\
\end{pmatrix}…⑨\\
となる。\\
⑧、⑨式より、\\
a=\cos{\theta}\\
c=\sin{\theta}\\
\\
次に\\
\begin{pmatrix}
0\\
1\\
\end{pmatrix}\\
が{\theta}回転する場合を考える。\\
\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}
\begin{pmatrix}
0\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
-\sin{\theta} \\
\cos{\theta}\\
\end{pmatrix}…⑩となる。\\
\\
⑩式の左辺を計算すると、\\
\begin{pmatrix}
a \times 0 + b \times 1\\
c \times 0 + d \times 1\\
\end{pmatrix}
=
\begin{pmatrix}
b\\
d\\
\end{pmatrix}…⑪\\
となる。\\
⑩、⑪式より、\\
b=-\sin{\theta}\\
d=\cos{\theta}\\
\\
a,b,c,dが求まったので、回転行列は\\
\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}…⑫\\
となる。
確認
\begin{pmatrix}
\frac{\sqrt{3}}{2}\\
\frac{1}{2}\\
\end{pmatrix}を\frac{\pi}{6}(30度)回転する行列を掛けて、
\begin{pmatrix}
\frac{1}{2}\\
\frac{\sqrt{3}}{2}\\
\end{pmatrix}となるか検証します。\\
\\
\frac{\pi}{6}回転する行列は⑫より\\
\begin{pmatrix}
\cos\frac{\pi}{6} & -\sin\frac{\pi}{6}\\
\sin\frac{\pi}{6} & \cos\frac{\pi}{6}\\
\end{pmatrix}
=
\begin{pmatrix}
\frac{\sqrt{3}}{2} & -\frac{1}{2}\\
\frac{1}{2} & \frac{\sqrt{3}}{2}\\
\end{pmatrix}\\
\\
この回転行列に
\begin{pmatrix}
\frac{\sqrt{3}}{2}\\
\frac{1}{2}\\
\end{pmatrix}を掛けると\\
\begin{pmatrix}
\frac{\sqrt{3}}{2} & -\frac{1}{2}\\
\frac{1}{2} & \frac{\sqrt{3}}{2}\\
\end{pmatrix}
\begin{pmatrix}
\frac{\sqrt{3}}{2}\\
\frac{1}{2}\\
\end{pmatrix}
=
\begin{pmatrix}
\frac{\sqrt{3}}{2} \times \frac{\sqrt{3}}{2} + (-\frac{1}{2}) \times \frac{1}{2}\\
\frac{1}{2} \times \frac{\sqrt{3}}{2} + \frac{\sqrt{3}}{2} \times \frac{1}{2}\\
\end{pmatrix}\\
=
\begin{pmatrix}
\frac{3}{4}-\frac{1}{4} \\
\frac{\sqrt{3}}{4} + \frac{\sqrt{3}}{4}\\
\end{pmatrix}
=
\begin{pmatrix}
\frac{2}{4} \\
\frac{2\sqrt{3}}{4}\\
\end{pmatrix}
=
\begin{pmatrix}
\frac{1}{2} \\
\frac{\sqrt{3}}{2}\\
\end{pmatrix}
となり、思った通りの結果が得られました。
「同次座標系」の導入
2次元の点の平行移動は2行2列の行列では表現できません。
そこで、行列は3行3列にします。
座標も2次元から3次元にして、3次元目を1にします。
3次元目が1なので、実質2次元です。
ちょっと気持ち悪いかもしれませんが、「便宜的に」そうします。
これを「同次座標系」といいます。
\begin{pmatrix}
x\\
y\\
\end{pmatrix}
を同次座標系で表すと
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
です。
ところで、先ほど求めた2行2列の拡大行列、回転行列を
同次座標系の3行3列の行列表す場合、どうすればいいのでしょうか?
実は、
こんな感じで3行目と3列目に赤色で囲った数字を埋めます。
こんなことしていいのでしょうか?
以下で、そうしてよいことを証明します。
まず、2行2列の行列と座標を掛けてみます。\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
\end{pmatrix}
=
\begin{pmatrix}
ax+by \\
cx+dy\\
\end{pmatrix}…⑬\\
\\
次に「同次座標系」の3行3列の行列と座標を掛けてみます。\\
\begin{pmatrix}
a & b & 0\\
c & d & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
ax+by+ 0\times 1\\
cx+dy+ 0\times 1\\
0\times x + 0\times y + 1\\
\end{pmatrix}
=
\begin{pmatrix}
ax+by\\
cx+dy\\
1\\
\end{pmatrix}…⑭\\
\\
⑬と⑭を比較すると、x値とy値が等しいことがわかります。
これで2行2列の変換行列は、同次座標系の3行3列の行列で書き直すことができます。
ここからは、同次座標系のみついてのみ考えていきます。
早速、同次座標系の拡大行列と回転行列を求めておきましょう。
同次座標系の拡大行列と回転行列
⑦式より、xをs_{x}倍、yをs_{y}倍する同次座標系の拡大行列は\\
\begin{pmatrix}
s_{x} & 0 & 0\\
0 & s_{y} & 0\\
0 & 0 & 1\\
\end{pmatrix}…⑮\\
となります。\\
\\
⑫式より、\theta回転する同次座標系の回転行列は\\
\begin{pmatrix}
\cos{\theta} & -\sin{\theta} & 0\\
\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}…⑯\\
となります。\\
同次座標系で平行移動行列を求めてみよう
xがt_{x}、yがt_{y}平行移動する3行3列の行列を求める。\\
\\
求めたい平行移動行列を\\
\begin{pmatrix}
a & b & c\\
d & e & f\\
g & h & i\\
\end{pmatrix}\\
とおく。\\
\\
xがt_{x}、yがt_{y}平行移動するとき\\
\begin{pmatrix}
a & b & c\\
d & e & f\\
g & h & i\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
x+t_{x}\\
y+t_{y}\\
1\\
\end{pmatrix}…⑰となる。\\
\\
⑰式の左辺を計算すると、\\
\begin{pmatrix}
ax+by+c\times 1\\
dx+ey+f\times 1\\
gx+hy+i\times 1\\
\end{pmatrix}
=
\begin{pmatrix}
ax+by+c\\
dx+ey+f\\
gx+hy+i\\
\end{pmatrix}…⑱\\
となる。\\
⑰、⑱式の係数を比較して\\
a=1\\
b=0\\
c=t_{x}\\
d=0\\
e=1\\
f=t_{y}\\
g=0\\
h=0\\
i=1\\
\\
a,b,c,d,e,f,g,h,iが求まったので、平行移動行列は、\\
\begin{pmatrix}
1 & 0 & t_{x}\\
0 & 1 & t_{y}\\
0 & 0 & 1\\
\end{pmatrix}…⑲\\
となる。
確認
\begin{pmatrix}
2\\
5\\
\end{pmatrix}をxに3、yに-4平行移動する行列を掛けて、
\begin{pmatrix}
5\\
1\\
\end{pmatrix}となるか検証します。\\
\\
xに3、yに-4平行移動する行列は⑲式より\\
\begin{pmatrix}
1 & 0 & 3\\
0 & 1 & -4\\
0 & 0 & 1
\end{pmatrix}\\
\\
この平行移動行列に
\begin{pmatrix}
2\\
5\\
1\\
\end{pmatrix}を掛けると\\
\begin{pmatrix}
1 & 0 & 3\\
0 & 1 & -4\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
2\\
5\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
1 \times 2 + 0 \times 5 + 3 \times 1\\
0 \times 2 + 1 \times 5 + (-4) \times 1\\
0 \times 2 + 0 \times 5 + 1 \times 1\\
\end{pmatrix}
=
\begin{pmatrix}
5\\
1\\
1\\
\end{pmatrix}
となり、思った通りの結果が得られました。
行列クラス作成
同次座標系での変換の行列が求まったので、プログラムを書いてみます。
// 行列クラス
class Matrix {
// 行列の積
static multiply(m1, m2) {
return [
m1[0] * m2[0] + m1[1] * m2[3] + m1[2] * m2[6],
m1[0] * m2[1] + m1[1] * m2[4] + m1[2] * m2[7],
m1[0] * m2[2] + m1[1] * m2[5] + m1[2] * m2[8],
m1[3] * m2[0] + m1[4] * m2[3] + m1[5] * m2[6],
m1[3] * m2[1] + m1[4] * m2[4] + m1[5] * m2[7],
m1[3] * m2[2] + m1[4] * m2[5] + m1[5] * m2[8],
m1[6] * m2[0] + m1[7] * m2[3] + m1[8] * m2[6],
m1[6] * m2[1] + m1[7] * m2[4] + m1[8] * m2[7],
m1[6] * m2[2] + m1[7] * m2[5] + m1[8] * m2[8]
];
}
// 行列とベクトルの積(戻り値はベクトル)
static multiplyVec(m, v) {
return {
x: m[0] * v.x + m[1] * v.y + m[2],
y: m[3] * v.x + m[4] * v.y + m[5]
};
}
// 平行移動行列を作成する
static translate(x, y) {
return [1, 0, x, 0, 1, y, 0, 0, 1];
}
// 拡大行列を作成する
static scale(x, y) {
return [x, 0, 0, 0, y, 0, 0, 0, 1];
}
// 回転行列を作成する(angleはラジアンで指定すること)
static rotate(angle) {
return [Math.cos(angle), -Math.sin(angle), 0, Math.sin(angle), Math.cos(angle), 0, 0, 0, 1];
}
}
行列とベクトルの演算では3次元目は省略しています。
気になる人は修正して使ってください。
アフィン変換の行列の性質
性質その1 - 3行目が 0 0 1
ここまでで、拡大行列、回転行列、平行移動行列を求めてきました。
これらの行列をよーく見てみると、3行目が 0 0 1 となっています。
3行目が0 0 1 である行列同士を掛け算すると、面白いことがわかります。
\begin{pmatrix}
a & b & c\\
d & e & f\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
g & h & i\\
j & k & l\\
0 & 0 & 1\\
\end{pmatrix}\\
=
\begin{pmatrix}
ag + bj + c\times 0 & ah + bk + c\times 0 & ai + bl + c\times 1\\
dg + ej + f\times 0 & dh + ek + f\times 0 & di + el + f\times 1\\
0\times g + 0\times j + 1\times 0 & 0\times h + 0\times k + 1\times 0 & 0\times i + 0\times l + 1\times 1\\
\end{pmatrix}\\
=
\begin{pmatrix}
ag + bj & ah + bk & ai + bl + c\\
dg + ej & dh + ek & di + el + f\\
0 & 0 & 1\\
\end{pmatrix}
となり、掛け算した行列の3行目も 0 0 1 であることが分かります。
JavaScriptのcanvasのsetTransformという座標変換用のAPI関数の引数は6つなのですが、
3行3列の行列のうち、1行目と2行目の成分の数値が6つであることが理由です。
性質その2 - 逆行列を簡単に求められる
まず逆行列の表記ですが、逆行列は行列の右上に-1付けることで表します。
\begin{pmatrix}
a & b & c\\
d & e & f\\
0 & 0 & 1\\
\end{pmatrix}の逆行列は
\begin{pmatrix}
a & b & c\\
d & e & f\\
0 & 0 & 1\\
\end{pmatrix}^{-1}と表示します。
では逆行列について考えてみます。
アフィン変換の逆行列は変換の逆を幾何的に考えればよいです。
x方向に4倍する変換の逆は「x方向に1/4倍する」ことですよね。
同様に、
Θ回転する変換の逆は「-Θ回転する」ことで、
xに4平行移動する変換の逆は「xに-4平行移動する」ことです。
で、その変換の逆がアフィン変換における「逆行列」です。
しかも、既に導いたアフィン変換の行列をそのまま使えるので、逆行列を計算することなく求めることができます。
xがs_{x}倍、yがs_{y}倍する行列の逆行列は、\\
xが\frac{1}{s_{x}}倍、yが\frac{1}{s_{y}}倍する行列なので、⑮式より\\
\begin{pmatrix}
s_{x} & 0 & 0\\
0 & s_{y} & 0\\
0 & 0 & 1\\
\end{pmatrix}^{-1}
=
\begin{pmatrix}
\frac{1}{s_{x}} & 0 & 0\\
0 & \frac{1}{s_{y}} & 0\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
\\
\theta回転する行列の逆行列は\\
-\theta回転する行列なので、⑯式より、\\
\begin{pmatrix}
\cos{\theta} & -\sin{\theta} & 0\\
\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}^{-1}
=
\begin{pmatrix}
\cos{(-\theta)} & -\sin{(-\theta)} & 0\\
\sin{(-\theta)} & \cos{(-\theta)} & 0\\
0 & 0 & 1\\
\end{pmatrix}
=
\begin{pmatrix}
\cos{\theta} & \sin{\theta} & 0\\
-\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
\\
xがt_{x}、yがt_{y}平行移動する行列の逆行列は、\\
xが-t_{x}、yが-t_{y}平行移動する行列なので、⑲式より、\\
\begin{pmatrix}
1 & 0 & t_{x}\\
0 & 1 & t_{y}\\
0 & 0 & 1\\
\end{pmatrix}^{-1}
=
\begin{pmatrix}
1 & 0 & -t_{x}\\
0 & 1 & -t_{y}\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
性質その3 - 掛ける順番が重要
点Pを平行移動した後、回転した時と
回転してから平行移動した場合、変換後の座標が異なるってことです。
実際に計算してそれを示してみます。
変換前の座標を(1,0)\\
平行移動はyに+1\\
回転角度は\frac{\pi}{4}とします。\\
\\
まず平行移動してから、回転させてみます。\\
\begin{align}
&\begin{pmatrix}
\cos{\frac{\pi}{4}} & -\sin{\frac{\pi}{4}} & 0\\
\sin{\frac{\pi}{4}} & \cos{\frac{\pi}{4}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & 0\\
0 & 1 & 1\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
0\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\cos{\frac{\pi}{4}} & -\sin{\frac{\pi}{4}} & 0\\
\sin{\frac{\pi}{4}} & \cos{\frac{\pi}{4}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
1\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\frac{1}{\sqrt{2}} & -\frac{1}{\sqrt{2}} & 0\\
\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
1\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
0\\
\sqrt{2}\\
1\\
\end{pmatrix}…⑳
\end{align}\\
\\
次に回転した後に平行移動させてみます。\\
\begin{align}
&\begin{pmatrix}
1 & 0 & 0\\
0 & 1 & 1\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\cos{\frac{\pi}{4}} & -\sin{\frac{\pi}{4}} & 0\\
\sin{\frac{\pi}{4}} & \cos{\frac{\pi}{4}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
0\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
1 & 0 & 0\\
0 & 1 & 1\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\frac{1}{\sqrt{2}} & -\frac{1}{\sqrt{2}} & 0\\
\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
0\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
1 & 0 & 0\\
0 & 1 & 1\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\frac{1}{\sqrt{2}}\\
\frac{1}{\sqrt{2}}\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\frac{1}{\sqrt{2}}\\
\frac{1}{\sqrt{2}} + 1\\
1\\
\end{pmatrix}…㉑\\
\end{align}\\
\\
⑳と㉑が一致しないことが確認できました。
基本的な内容はここで終わりです。
以下で、実務的なというかまあ普通こういう変換を求められるよね、って事について書いていきます。
任意の座標で回転 - 応用その1
応用として、任意の座標で回転をする行列を求めてみたいと思います。
イメージは下図です。点Pが点Cを中心にθ回転して点P'へ変換されます。
これについては、以下のように考えます。
\begin{align}
&[考え方]\\
& \\
&1: 回転中心を原点に移動するような平行移動をする \\
&(xを-c_{x},yを-c_{y}平行移動する)\\
& \\
&2: \theta回転する\\
& \\
&3: 回転中心をもとの位置に戻すような平行移動をする\\
&(xをc_{x},yをc_{y}平行移動する)\\
\end{align}\\
点P(x, y)が点C(c_{x},c_{y})を中心に\theta回転させた点をP(x',y')とすると、\\
\begin{pmatrix}
1 & 0 & c_{x}\\
0 & 1 & c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\cos{\theta} & -\sin{\theta} & 0\\
\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & -c_{x}\\
0 & 1 & -c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
x'\\
y'\\
1\\
\end{pmatrix}\\
\\
よって求める行列は\\
\begin{pmatrix}
1 & 0 & c_{x}\\
0 & 1 & c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\cos{\theta} & -\sin{\theta} & 0\\
\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & -c_{x}\\
0 & 1 & -c_{y}\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
考え方を示せたので、これ以上計算はせずにこれを結果とします。
これをプログラムで書くと、こんな感じです。
/**
* ある座標を中心に回転する
* @param {Object} center 回転中心座標
* @param {Number} angle 回転角度(単位はラジアン)
* @param {Object} pos 変換前の座標
* @return {Object} 変換後の座標
*/
function rotate(center, angle, pos) {
// 原点へ回転中心を平行移動
const transMat = Matrix.translate(-center.x, -center.y);
// 回転する
const rotateMat = Matrix.rotate(angle);
// 回転中心をもとの位置へ戻す
const revTransMat = Matrix.translate(center.x, center.y);
// 3つの行列を掛ける
let matrix = Matrix.multiply(rotateMat, transMat);
matrix = Matrix.multiply(revTransMat, matrix);
// 行列に座標(ベクトル)を掛ける
const ret = Matrix.multipleVec(matrix, pos);
return ret;
}
任意の座標で拡大 - 応用その2
応用第2弾として、任意の座標における拡大をする行列を求めてみたいと思います。
イメージは下図です。点Pが点Cを中心にxをSx倍、yをSy倍して点Pへ変換されます。
これについては、先ほどの「任意の点を中心に回転」と同じように考えます。
\begin{align}
&[考え方]\\
& \\
&1: 拡大中心を原点に移動するような平行移動をする \\
&(xを-c_{x},yを-c_{y}平行移動する)\\
& \\
&2: xをs_{x}倍、yをs_{y}倍する\\
& \\
&3: 拡大中心をもとの位置に戻すような平行移動をする\\
&(xをc_{x},yをc_{y}平行移動する)\\
\end{align}\\
点P(x, y)が点C(c_{x},c_{y})を中心にxをs_{x}倍、yをs_{y}倍させた点をP(x',y')とすると、\\
\begin{pmatrix}
1 & 0 & c_{x}\\
0 & 1 & c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
s_{x} & 0 & 0\\
0 & s_{y} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & -c_{x}\\
0 & 1 & -c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
x'\\
y'\\
1\\
\end{pmatrix}\\
\\
よって求める行列は\\
\begin{pmatrix}
1 & 0 & c_{x}\\
0 & 1 & c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
s_{x} & 0 & 0\\
0 & s_{y} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & -c_{x}\\
0 & 1 & -c_{y}\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。
応用その1同様、これ以上計算しないでおきます。
これをプログラムで書くと、こんな感じです。
/**
* ある座標を中心に拡大する
* @param {Object} center 拡大中心座標
* @param {Object} scale 拡大率
* @param {Object} pos 変換前の座標
* @return {Object} 変換後の座標
*/
function scale(center, scale , pos) {
// 原点へ拡大中心を平行移動
const transMat = Matrix.translate(-center.x, -center.y);
// 拡大する
const scaleMat = Matrix.scale(scale.x, scale.y);
// 拡大中心をもとの位置へ戻す
const revTransMat = Matrix.translate(center.x, center.y);
// 3つの行列を掛ける
let matrix = Matrix.multiply(scaleMat , transMat);
matrix = Matrix.multiply(revTransMat, matrix);
// 行列に座標(ベクトル)を掛ける
const ret = Matrix.multipleVec(matrix, pos);
return ret;
}
原点を通る直線(y=mx)に折り返し - 応用その3
応用第3弾として、原点を通る直線に対する折り返しをする行列を求めてみたいと思います。
イメージは下図です。PがP'へy=mxに対称に変換されます。
今まで通り、アフィン変換を組み合わせて求めます。
考え方としては、
\begin{align}
&[考え方]\\
& \\
&y=mxとx軸のなす角を\thetaとする。\\
& \\
&1: y=mxがx軸と重なるような回転をする \\
&(-\theta回転する)\\
& \\
&2: x軸に折り返す\\
&(xを1倍、yを-1倍する)\\
& \\
&3: x軸が元のy=mxに重なるように回転をする\\
&(\theta回転する)\\
\end{align}\\
といった感じです。
今まで解き方と似ていますね。
勝手にΘって置いていいのかって思うかもしれませんが、まあ最終的に消えていればOKです。
そういえば今回は平行移動行列が出てきませんね、ってことで途中まで「同次座標系」ではなく2次元でやってみます。
y=mxとx軸のなす角を\thetaとする。\\
点P(x, y)がy=mxに対称に移動した点をP(x',y')とすると、\\
\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}
\begin{pmatrix}
1 & 0\\
0 & -1\\
\end{pmatrix}
\begin{pmatrix}
\cos{(-\theta)} & -\sin{(-\theta)}\\
\sin{(-\theta)} & \cos{(-\theta)}\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
\end{pmatrix}
=
\begin{pmatrix}
x'\\
y'\\
\end{pmatrix}
\\
\\
よって求める行列は\\
\begin{align}
&\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}
\begin{pmatrix}
1 & 0\\
0 & -1\\
\end{pmatrix}
\begin{pmatrix}
\cos{(-\theta)} & -\sin{(-\theta)}\\
\sin{(-\theta)} & \cos{(-\theta)}\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}
\begin{pmatrix}
1 & 0\\
0 & -1\\
\end{pmatrix}
\begin{pmatrix}
\cos{\theta} & \sin{\theta}\\
-\sin{\theta} & \cos{\theta}\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}
\begin{pmatrix}
\cos{\theta} & \sin{\theta}\\
\sin{\theta} & -\cos{\theta}\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\cos^{2}{\theta} - \sin^{2}{\theta} & \sin{\theta}\cos{\theta} + \cos{\theta}\sin{\theta}\\
\sin{\theta}\cos{\theta} + \cos{\theta}\sin{\theta} & -(\cos^{2}{\theta} - \sin^{2}{\theta})\\
\end{pmatrix}…㉒
\end{align}\\
また、\\
\cos{\theta}=\frac{1}{\sqrt{m^2+1}},
\sin{\theta}=\frac{m}{\sqrt{m^2+1}}\\
なので、㉒式に代入して、
=
\begin{pmatrix}
\frac{1-m^{2}}{m^{2}+1} & \frac{2m}{m^{2}+1}\\
\frac{2m}{m^{2}+1} & \frac{m^{2}-1}{m^{2}+1}
\end{pmatrix} \\
これを同次座標系で表すと\\
\begin{pmatrix}
\frac{1-m^{2}}{m^{2}+1} & \frac{2m}{m^{2}+1} & 0\\
\frac{2m}{m^{2}+1} & \frac{m^{2}-1}{m^{2}+1} & 0\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
\\
また、㉒に三角関数の加法定理(2倍角の公式)を用いると\\
\begin{pmatrix}
\cos{2\theta} & \sin{2\theta}\\
\sin{2\theta} & -\cos{2\theta}\\
\end{pmatrix}\\
となります。\\
\cos{2\theta} と \sin{2\theta}が事前にわかっている場合、こちらを使ってもよいでしょう。\\
次の応用その4で、今回の結果を使うので、応用その1、その2とは違い計算しました。
ついでに、この結果をMatrixクラスのメソッドに追加しましょう。
// 原点を通る直線(y=mx)に折り返す
static symmmetic(m) {
return [(1 - m * m) / (m * m + 1), 2 * m / (m * m + 1), 0, 2 * m / (m * m + 1), (m * m - 1) / (m * m + 1), 0, 0, 0, 1];
}
直線(y=mx+n)に折り返し - 応用その4
応用第4弾として、普通の直線に対する折り返しをする行列を求めてみたいと思います。
イメージは下図です。PがP'へy=mx+nに対称に変換されます。
今度は直線が原点を通るとは限りません。
先ほど求めた原点を通る直線の折り返しの変換を使いましょう。
\begin{align}
&[考え方]\\
& \\
&1: 直線のY切片を原点に移動\\
& \\
&2: 直線(原点を通る)で折り返す\\
& \\
&3: 直線のY切片をもとの位置へ戻す\\
\end{align}\\
考え方は、応用その1、応用その2と似ています。
こういう考え方にもずいぶん慣れてきたんじゃないでしょうか。
応用その3で、原点を通る直線での折り返しを我々は求めていたので、このような考え方をしましたが、
それを知らない場合でも、
\begin{align}
&["原点を通る直線での折り返し"を知らない場合の考え方]\\
& \\
&y=mx+nとx軸のなす角を\thetaとする。\\
& \\
&1: 直線のY切片を原点に平行移動(y方向に-n平行移動)\\
& \\
&2: 直線がx軸と重なるような回転をする \\
&(-\theta回転する)\\
& \\
&3: x軸に折り返す\\
&(xを1倍、yを-1倍する)\\
& \\
&4: x軸が元のy=mxに重なるように回転をする\\
&(\theta回転する)\\
& \\
&5: 直線のY切片をもとの位置へ戻す\\
\end{align}\\
と考えればOKです。
これをプログラムで書くと、こんな感じです。
/**
* ある直線(y=mx+n)で折り返す
* @param {Number} m y=mx+nのm
* @param {Number} n y = mx+nのn
* @param {Object} pos 変換前の座標
* @return {Object} 変換後の座標
*/
function symmetricLine2(m, n, pos) {
// 切片を原点へ平行移動
const transMat = Matrix.translate(0, -n);
// 直線で折り返す
const symmmeticMat = Matrix.symmmetic(m);
// 切片をもとの位置へ戻す
const revTransMat = Matrix.translate(0, n);
// 3つの行列を掛ける
let matrix = Matrix.multiply(symmmeticMat, transMat);
matrix = Matrix.multiply(revTransMat, matrix);
// 行列に座標(ベクトル)を掛ける
const ret = Matrix.multipleVec(matrix, pos);
return ret;
}
JavaScriptによるアフィン変換のサンプルプログラムの使い方
サンプルプログラムはこちらです。
①変換前の座標を入力し、「座標確定」ボタンを押す。
②行いたい変換のパラメータを入力した後、変換する。
③変換した座標が計算され、図示(目盛りはないけど)される。
図示ですが、canvasを使って描画しております。
注意!!
canvasのサイズは200x200で、X+方向は右、Y+方向は下です。
ですので、原点はcanvasの左上隅で、+回転は時計周りとなります。
最後に
行列には、今回紹介した以外にも興味深い性質が多数ありますので、是非調べてみてください。
なお、本記事の内容は数学的にかなりあいまいであると思うので、一度ちゃんとした書籍などで勉強することを強く強くお勧めします。