CSS と SVG の transform について


CSS には `transform` というプロパティがあり、 HTML や SVG 要素に対して変形を行うことができる。一方 SVG の要素にも、 `transform` 属性があり、その要素の変形を行うことができる。

これらの特徴について理解するためには MDN における [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function), [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform) それぞれの記事を参照すれば良いのだが、個人的には理解できていない内容や、気になったことがいくつかあった。

そこで、これらの記事の内容と、自力で調べたことを、本記事にまとめていく。

とりわけ、次の点に着目した。

* CSS と SVG の `transform` で指定できる変形関数の違い
* それぞれの変形関数の関係性や行列表現
* 合成に対して変形関数が閉じているか

## そもそも `transform` とは

本記事が説明しようとしている `transform` プロパティや属性とは何なのか、最初に説明する。 `transform` はよく知っているという方は飛ばして次のセクションに進んでも構わない。

`transform` とは HTML や SVG において、画面上に描かれている物体に対して行列変形を行うことができるプロパティや属性である。

例えば次のように HTML を記述したとする。

```html
<img src="img.png" width="100px" height="150px" style="transform:scale(2);" />
```

`style` 属性の `transform` プロパティに `scale(2)` と記されている。これにより画像がその場で2倍の大きさ ( 200px × 300px ) になるはずだ。

SVG においては次のように `transform` 属性が存在する。

```xml
<circle fill="red" cx="0" cy="0" r="50" transform="scale(2)" />
```

こちらも半径が100の円になるはずだ。

どちらの例でも `scale` 関数を例として挙げたが、他にも `translate(dx,dy)` で平行移動させたり、 `rotate(θ)` で回転させたり、 `skew(θx,θy)` で剪断変形させることもできる。

平行移動や回転などそれぞれの変換操作を扱う関数の他に、行列を使って直接変換が指定可能な `matrix(a,b,c,d,e,f)` というものもある。この場合、次の式に従って元のオブジェクトの座標 $(x,y)$ を $(x',y')$ に写像させている。

$$
\begin{pmatrix}\\; x' \\;\\\\\\; y' \\;\\\\\\; 1 \\;\end{pmatrix} =
\begin{pmatrix}\\; a & c & e \\;\\\\\\; b & d & f \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
\begin{pmatrix}\\; x \\;\\\\\\; y \\;\\\\\\; 1 \\;\end{pmatrix}
$$

`matrix` は `transform` 属性で扱うことのできる最も一般的な変形関数の表式であり、 `rotate` や `scale` などの決まった変換操作の関数は全て `matrix(..)` を使った行列により表すことができる。
例えば `transform(dx,dy)` は `matrix(1,0,0,1,dx,dy)` と等価であり、数式だと次のように表される。

$$
\begin{pmatrix}\\; x' \\;\\\\\\; y' \\;\\\\\\; 1 \\;\end{pmatrix} =
\begin{pmatrix}\\; 1 & 0 & d_x \\;\\\\\\; 0 & 1 & d_y \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
\begin{pmatrix}\\; x \\;\\\\\\; y \\;\\\\\\; 1 \\;\end{pmatrix}
$$

### 3次元の変換について

CSS の `transform` では3次元を扱うことができる。
とはいっても画面は2次元なので、 $z$ 方向は画面に垂直な方向 (画面表向きが $+z$ 方向) で、実際には $z$ 成分の違いはオブジェクトの並び順の違いにしか現れない。

CSS には3次元特有の変形関数 `translate3D(dx,dy,dz)` や `perspective(d)` などが存在し、これを使って変形させることができる。

また、 `matrix` は2次元の行列しか扱うことができないので、3次元の行列を扱うための関数 `matrix3d` も用意されている。3次元だからベクトルも4次元になっており、次のようにベクトルを自由に変換できる16成分で記述することになっている。

$
\mathrm{matrix3d}(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)
\quad\cdots\qquad
$
$$
\begin{pmatrix}\\; x' \\;\\\\\\; y' \\;\\\\\\; z' \\;\\\\\\; w' \\;\end{pmatrix} =
\begin{pmatrix}\\; a & b & c & d \\;\\\\\\; e & f & g & h \\;\\\\\\; i & j & k & l \\;\\\\\\; m & n & o & p \\;\end{pmatrix}
\begin{pmatrix}\\; x \\;\\\\\\; y \\;\\\\\\; z \\;\\\\\\; w \\;\end{pmatrix}
$$

※ $w'$ については `perspective(d)` では効いてくるが、他の場所ではあまり使うことはないので、この後の `perspective(d)` の説明において合わせて説明する。

### 複数の変形関数の指定

`transform` 属性やプロパティはスペース区切りで複数の変換を指定することができる。これは複数の変形操作を**右から順に**適用させることを意味する。

例えば $\mathrm{matrix}(a,b,c,d,e,f)\\;\mathrm{matrix}(a',b',c',d',e',f')$ と指定することは、数式だと次のように表される。

$$
\begin{pmatrix}\\; x' \\;\\\\\\; y' \\;\\\\\\; 1 \\;\end{pmatrix} =
\begin{pmatrix}\\; a & c & e \\;\\\\\\; b & d & f \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
\begin{pmatrix}\\; a' & c' & e' \\;\\\\\\; b' & d' & f' \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
\begin{pmatrix}\\; x \\;\\\\\\; y \\;\\\\\\; 1 \\;\end{pmatrix}
$$

一般的に2つの変形関数を入れ替えると、全体として変形の仕方は変わってくる。

$$ \begin{aligned}
	& \mathrm{matrix}(a,b,c,d,e,f)\\;\mathrm{matrix}(a',b',c',d',e',f') \\\\
	& \quad \ne
	\mathrm{matrix}(a',b',c',d',e',f')\\;\mathrm{matrix}(a,b,c,d,e,f)
\end{aligned} $$

## CSS と SVG の `transform` の違い

CSS と SVG の `transform` がどういうものか説明した。どちらも変形関数を1つ以上指定すれば変形ができ、表記の仕方も似ているので、値をコピペしても使うこともできる場合がある。もちろん、全く同じ値が指定できるわけではなく、幾つか違いが存在する。

私が気づいた限りでは、 CSS と SVG の `transform` には次の違いが存在するようである。

* CSS では2次元と3次元の変形関数が存在するが、 SVG には2次元の変形関数しか存在しない
* CSS ではカンマ区切りで引数を渡す必要があるが、 SVG ではカンマ区切りの他にスペース区切りにも対応している
* 長さを指定する際には CSS では `px` などの長さ単位を付した表記を使用しなければならない一方、 SVG ではビューポートを基準とした単位表記のない値しか使用できない
* 角度に対しても CSS では `deg`, `rad`, `grad`, `turn` に対応しており、値に適切な単位を付す必要があるが、 SVG では `deg` のみに対応しており、単位表記をせずに度数法の値だけを指定する必要がある

## 指定可能な変形関数

次に、 `transform` で指定可能な変形関数を紹介していく。

同一名の変形関数でも MDN の記事では CSS と SVG のそれぞれで別のページにまとめているが、ここでは違いを明確にするため、同一の変形関数はまとめて取り扱うことにする。

それぞれの変形関数間の対応関係や、対応する行列表記についても説明する。

### 平行移動

| | CSS | SVG |
|:--|:--:|:--:|
| `translate(dx,dy)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate) | [◯](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#translate) |
| `translate3d(dx,dy,dz)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d) | × |
| `translateX(dx)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translateX) | × |
| `translateY(dy)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translateY) | × |
| `translateZ(dz)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translateZ) | × |

* オブジェクトを平行移動させることができる。 $x$ 方向に `dx` だけ、 $y$ 方向に `dy` だけ、 $z$ 方向に `dz` だけ移動する
* SVG の場合は単位のない実数での表記 (`viewBox` の座標系になる) が必要で、 CSS の場合は [`px` などの長さの単位を持った値](https://developer.mozilla.org/en-US/docs/Web/CSS/length)、或いは[要素の横幅縦幅に対するパーセント表記](https://developer.mozilla.org/en-US/docs/Web/CSS/percentage)が指定できる
* `translate` は `dx`, `dy` の2つの引数を取るが、1つだけ指定すると `dy=0` とみなして $x$ 方向にだけ平行移動する

次の関係が成立することは明らかである。

$$ \begin{aligned}
	\mathrm{translate}(d_x,d_y) {}={} & \mathrm{translate3d}(d_x,d_y,0) \\\\
	\mathrm{translate}(d_x) {}={} & \mathrm{translate}(d_x,0) \\\\
	\mathrm{translateX}(d_x) {}={} & \mathrm{translate}(d_x,0) \\\\
	\mathrm{translateY}(d_y) {}={} & \mathrm{translate}(0,d_y) \\\\
	\mathrm{translateZ}(d_z) {}={} & \mathrm{translate3d}(0,0,d_z)
\end{aligned} $$

また、 `translate` や `translate3d` は行列表記と次のように対応する。

$$ \begin{aligned}
	\mathrm{translate}(d_x,d_y) \quad\cdots\quad &
	\begin{pmatrix}\\; 1 & 0 & d_x \\;\\\\\\; 0 & 1 & d_y \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} \quad\cdots\quad
	\mathrm{matrix}(1,0,0,1,d_x,d_y) \\\\
	\mathrm{translate3d}(d_x,d_y,d_z) \quad\cdots\quad &
	\begin{pmatrix}\\; 1 & 0 & 0 & d_x \\;\\\\\\; 0 & 1 & 0 & d_y \\;\\\\\\; 0 & 0 & 1 & d_z \\;\\\\\\; 0 & 0 & 0 & 1 \\;\end{pmatrix}
\end{aligned} $$

`matrix` や `matrix3d` では CSS でも単位表記なしで指定するが、 `px` 単位での値を指定することになっている。

### 拡大,縮小

| | CSS | SVG |
|:--|:--:|:--:|
| `scale(kx,ky)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale) | [◯](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#scale) |
| `scale3d(kx,ky,kz)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d) | × |
| `scaleX(kx)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scaleX) | × |
| `scaleY(ky)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scaleY) | × |
| `scaleZ(kz)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scaleZ) | × |

* オブジェクトを拡大、縮小させることができる。 $x$ 方向に `kx` 倍、 $y$ 方向に `ky` 倍、 $z$ 方向に `kz` 倍される
* 上記の `kx`, `ky`, `kz` は全て単位のない実数値が指定可能である
* `scale` は `kx`, `ky` の2つの引数を取るが、1つだけ指定すると `kx=ky` とみなして $x$, $y$ 共に同じ倍率だけ拡大縮小される

次の関係が成立することは明らかである。

$$ \begin{aligned}
	\mathrm{scale}(k_x,k_y) {}={} & \mathrm{scale3d}(k_x,k_y,1) \\\\
	\mathrm{scale}(k) {}={} & \mathrm{scale}(k,k) \\\\
	\mathrm{scaleX}(k_x) {}={} & \mathrm{scale}(k_x,1) \\\\
	\mathrm{scaleY}(k_y) {}={} & \mathrm{scale}(1,k_y) \\\\
	\mathrm{scaleZ}(k_z) {}={} & \mathrm{scale3d}(1,1,k_z)
\end{aligned} $$

また、 `scale` や `scale3d` は行列表記と次のように対応する。

$$ \begin{aligned}
	\mathrm{scale}(k_x,k_y) \quad\cdots\quad &
	\begin{pmatrix}\\; k_x & 0 & 0 \\;\\\\\\; 0 & k_y & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} \quad\cdots\quad
	\mathrm{matrix}(k_x,0,0,k_y,0,0) \\\\
	\mathrm{scale3d}(k_x,k_y,k_z) \quad\cdots\quad &
	\begin{pmatrix}\\; k_x & 0 & 0 & 0 \\;\\\\\\; 0 & k_y & 0 & 0 \\;\\\\\\; 0 & 0 & k_z & 0 \\;\\\\\\; 0 & 0 & 0 & 1 \\;\end{pmatrix}
\end{aligned} $$

### 回転

| | CSS | SVG |
|:--|:--:|:--:|
| `rotate(θ)`<br>`rotate(θ,rx,ry)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate) | [◯](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#rotate) |
| `rotate3d(nx,ny,nz,θ)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d) | × |
| `rotateX(θ)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotateX) | × |
| `rotateY(θ)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotateY) | × |
| `rotateZ(θ)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotateZ) | × |

* オブジェクトを回転させることができる。いずれも `θ` で回転角を与えている
* 関数の違いは回転軸の違いである。 `rotate` と `rotateZ` は2次元でいえば原点 `(0,0)` を中心とした回転であり、3次元であれば $z$ 軸に対しての回転である。 `rotateX` は $x$ 軸に対して、 `rotateY` は $y$ 軸に対しての回転である。 `rotate3d` は `(nx,ny,nz)` で与えられるベクトル方向を回転軸とした回転である
* CSS の `rotate` と `rotateZ` は等価である
* CSS の `rotate` は回転角の引数のみを受け付けるが、 SVG の `rotate` では回転の中心座標 `(rx,ry)` をその後の引数で与えることができる。与えない場合は原点を中心に回転する
* CSS の場合は [`deg` などの角度の単位を持った値](https://developer.mozilla.org/en-US/docs/Web/CSS/angle)を与え、 SVG の場合は単位のない実数値 (`deg` に相当) を与える

次の関係が与えられる。

$$ \begin{aligned}
	\mathrm{rotate}(\theta) {}={} & \mathrm{rotateZ}(\theta) \\\\
	\mathrm{rotate}(\theta,r_x,r_y) {}={} & \mathrm{translate}(r_x,r_y) \\; \mathrm{rotate}(\theta) \\; \mathrm{translate}(-r_x,-r_y) \\\\
	\mathrm{rotateX}(\theta) {}={} & \mathrm{rotate3d}(1,0,0,\theta) \\\\
	\mathrm{rotateY}(\theta) {}={} & \mathrm{rotate3d}(0,1,0,\theta) \\\\
	\mathrm{rotateZ}(\theta) {}={} & \mathrm{rotate3d}(0,0,1,\theta) \\\\
	\mathrm{rotate3d}(n_x,n_y,n_z,\theta) {}={} & \mathrm{rotate3d}(-n_x,-n_y,-n_z,-\theta)
\end{aligned} $$

また、 `rotate3d` 以外のそれぞれの変形関数は行列表記と次のように対応する。

$$ \begin{aligned}
	\mathrm{rotate}(\theta) \quad\cdots\quad &
	\begin{pmatrix}\\; \cos\theta & -\sin\theta & 0 \\;\\\\\\; \sin\theta & \cos\theta & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} \quad\cdots\quad
	\mathrm{matrix}(\cos\theta,\sin\theta,-\sin\theta,\cos\theta,0,0) \\\\
	\mathrm{rotateX}(\theta) \quad\cdots\quad &
	\begin{pmatrix}\\; 1 & 0 & 0 & 0 \\;\\\\\\; 0 & \cos\theta & -\sin\theta & 0 \\;\\\\\\; 0 & \sin\theta & \cos\theta & 0 \\;\\\\\\; 0 & 0 & 0 & 1 \\;\end{pmatrix} \\\\
	\mathrm{rotateY}(\theta) \quad\cdots\quad &
	\begin{pmatrix}\\; \cos\theta & 0 & \sin\theta & 0 \\;\\\\\\; 0 & 1 & 0 & 0 \\;\\\\\\; -\sin\theta & 0 & \cos\theta & 0 \\;\\\\\\; 0 & 0 & 0 & 1 \\;\end{pmatrix} \\\\
	\mathrm{rotateZ}(\theta) \quad\cdots\quad &
	\begin{pmatrix}\\; \cos\theta & -\sin\theta & 0 & 0 \\;\\\\\\; \sin\theta & \cos\theta & 0 & 0 \\;\\\\\\; 0 & 0 & 1 & 0 \\;\\\\\\; 0 & 0 & 0 & 1 \\;\end{pmatrix} \\\\
\end{aligned} $$

SVG における回転の中心が与えられた `rotate` は行列表記と次のように対応する。

$$
	\mathrm{rotate}(\theta,r_x,r_y) \quad\cdots\quad
	\begin{pmatrix}\\;
		\cos\theta & -\sin\theta & r_x - r_x \cos\theta + r_y \sin\theta \\;\\\\\\; \sin\theta & \cos\theta & r_y - r_x \sin\theta - r_y \cos\theta \\;\\\\\\;
		0 & 0 & 1
	\\;\end{pmatrix}
$$

`rotate3d` に関しては、引数 $n_x$, $n_y$, $n_z$ は規格化 ($\\; {n_x}^2+{n_y}^2+{n_z}^2 = 1 \\;$) されているものとし、次のような行列表記で表される。 ([ロドリゲスの回転公式](https://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%89%E3%83%AA%E3%82%B2%E3%82%B9%E3%81%AE%E5%9B%9E%E8%BB%A2%E5%85%AC%E5%BC%8F))

$$ \begin{aligned} &
	\begin{pmatrix}\\; 1 & 0 & 0 & 0 \\;\\\\\\; 0 & 1 & 0 & 0 \\;\\\\\\; 0 & 0 & 1 & 0 \\;\\\\\\; 0 & 0 & 0 & 1 \\;\end{pmatrix}
	- \begin{pmatrix}\\; 1 - {n_x}^2 & - n_x n_y & - n_x n_z & 0 \\;\\\\\\; - n_y n_x & 1 - {n_y}^2 & - n_y n_z & 0 \\;\\\\\\; - n_z n_x & - n_z n_y & 1 - {n_z}^2 & 0 \\;\\\\\\; 0 & 0 & 0 & 0 \\;\end{pmatrix} (1-\cos\theta)
	- \begin{pmatrix}\\; 0 & -n_z & n_y & 0 \\;\\\\\\; n_z & 0 & -n_x & 0 \\;\\\\\\; -n_y & n_x & 0 & 0 \\;\\\\\\; 0 & 0 & 0 & 0 \\;\end{pmatrix} \sin\theta \\\\
	& = \exp\left[\\; \begin{pmatrix}\\; 0 & n_z & -n_y & 0 \\;\\\\\\; -n_z & 0 & n_x & 0 \\;\\\\\\; n_y & -n_x & 0 & 0 \\;\\\\\\; 0 & 0 & 0 & 0 \\;\end{pmatrix} \cdot \theta \\;\right]
\end{aligned} $$

この計算の詳細はこの下で説明している。

#### 計算の詳細

式中に現れた行列を次のように $I$, $n$, $N$ でおく。

$$\begin{aligned} &
	I := \begin{pmatrix}\\; 1 & 0 & 0 & 0 \\;\\\\\\; 0 & 1 & 0 & 0 \\;\\\\\\; 0 & 0 & 1 & 0 \\;\\\\\\; 0 & 0 & 0 & 1 \\;\end{pmatrix}
	\qquad
	n := \begin{pmatrix}\\; 0 & -n_z & n_y & 0 \\;\\\\\\; n_z & 0 & -n_x & 0 \\;\\\\\\; -n_y & n_x & 0 & 0 \\;\\\\\\; 0 & 0 & 0 & 0 \\;\end{pmatrix}
	\\\\ & \qquad\quad
	N := \begin{pmatrix}\\; 1 - {n_x}^2 & - n_x n_y & - n_x n_z & 0 \\;\\\\\\; - n_y n_x & 1 - {n_y}^2 & - n_y n_z & 0 \\;\\\\\\; - n_z n_x & - n_z n_y & 1 - {n_z}^2 & 0 \\;\\\\\\; 0 & 0 & 0 & 0 \\;\end{pmatrix}
\end{aligned} $$

すると、 $n$ と $N$ の間に次のような関係があることがわかるだろう。 ($k$ は正整数)

$$
\left\lbrace\\;\begin{aligned}
	- n^2 =N^k {}={}& N \\\\
	nN {}={}& n
\end{aligned}\right.
\quad\Longrightarrow\quad
\begin{aligned}
	N {}={}& N^k = {\left(-n^2\right)}^k = {(-)}^k n^{2k} \\\\
	n {}={}& n N^{k-1} = {(-)}^{k-1} n^{2k-1}
\end{aligned}
$$

そして `rotate3d` は次のように表せるだろう。

$$
\mathrm{rotate3d}(n_x,n_y,n_z,\theta) = I - N (1-\cos\theta) - n \sin\theta
$$

この第2項、第3項を計算すると

$$ \begin{aligned}
	- N(1-\cos\theta)
	{}={}& - N \cdot \sum_{k=1}^\infty - \frac{{(-)}^k \theta^{2k}}{(2k)!} \\\\
	{}={}& \sum_{k=1}^\infty {(-)}^k n^{2k} \cdot \frac{{(-)}^k \theta^{2k}}{(2k)!} \\\\
	{}={}& \sum_{k=1}^\infty \frac{{(n\theta)}^{2k}}{(2k)!}
	= \sum_{k=1}^\infty \frac{{(-n\theta)}^{2k}}{(2k)!} \\\\
	- n \sin\theta
	{}={}& - n \cdot \sum_{k=1}^\infty - \frac{{(-)}^k \theta^{2k-1}}{(2k-1)!} \\\\
	{}={}& \sum_{k=1}^\infty {(-)}^{k-1} n^{2k-1} \cdot \frac{{(-)}^k \theta^{2k-1}}{(2k-1)!} \\\\
	{}={}& \sum_{k=1}^\infty - \frac{{(n\theta)}^{2k-1}}{(2k-1)!}
	= \sum_{k=1}^\infty \frac{{(-n\theta)}^{2k-1}}{(2k-1)!}
\end{aligned} $$

また、 $I$ については $\displaystyle I =: \frac{{(-n\theta)^0}}{0!} $ だと考えれば、3つの項は同一の式の0次、偶数次、奇数次の和となっている。

$$ \begin{aligned}
	\mathrm{rotate3d}(n_x,n_y,n_z,\theta) {}={}& I - N (1-\cos\theta) - n \sin\theta \\\\
	{}={}& \frac{{(-n\theta)^0}}{0!} + \sum_{k=1}^\infty \frac{{(-n\theta)}^{2k}}{(2k)!} + \sum_{k=1}^\infty \frac{{(-n\theta)}^{2k-1}}{(2k-1)!} \\\\
	{}={}& \sum_{n=0}^\infty \frac{{(-n\theta)}^k}{k!}
	= \exp(-n\theta)
\end{aligned} $$

上記の $n$, $N$ に具体的な式を代入すれば得られる。

### 剪断変形

| | CSS | SVG |
|:--|:--:|:--:|
| `skew(θx,θy)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew) | × |
| `skewX(θx)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX) | [◯](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#skewx) |
| `skewY(θy)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY) | [◯](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#skewy) |

* オブジェクトに剪断変形を与えることができる。つまり、 `θx` や `θy` が正だとしたら、オブジェクトの左上や右下を横方向、或いは縦方向に引っ張ってできる形状を与える。その時の傾き度合いを `θx` や `θy` で与える
* CSS の場合は [`deg` などの角度の単位を持った値](https://developer.mozilla.org/en-US/docs/Web/CSS/angle)を与え、 SVG の場合は単位のない実数値 (`deg` に相当) を与える
* `skew` は `θx`, `θy` の2つの引数を取るが、1つだけ指定すると `θy=0` とみなして $x$ 方向にだけ傾ける

次の関係が成立する。

$$ \begin{aligned}
	\mathrm{skew}(\theta_x) {}={} & \mathrm{skew}(\theta_x,0) \\\\
	\mathrm{skewX}(\theta_x) {}={} & \mathrm{skew}(\theta_x,0) \\\\
	\mathrm{skewY}(\theta_y) {}={} & \mathrm{skew}(0,\theta_y)
\end{aligned} $$

また、それぞれの関数は次のように行列表記と対応する。

$$ \begin{aligned}
	\mathrm{skew}(\theta_x,\theta_y) \quad\cdots\quad &
	\begin{pmatrix}\\; 1 & \tan\theta_x & 0 \\;\\\\\\; \tan\theta_y & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} && \quad\cdots\quad
	\mathrm{matrix}(1,\tan\theta_y,\tan\theta_x,1,0,0) \\\\
	\mathrm{skewX}(\theta) \quad\cdots\quad &
	\begin{pmatrix}\\; 1 & \tan\theta & 0 \\;\\\\\\; 0 & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} && \quad\cdots\quad
	\mathrm{matrix}(1,0,\tan\theta,1,0,0) \\\\
	\mathrm{skewY}(\theta) \quad\cdots\quad &
	\begin{pmatrix}\\; 1 & 0 & 0 \\;\\\\\\; \tan\theta & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} && \quad\cdots\quad
	\mathrm{matrix}(1,\tan\theta,0,1,0,0)
\end{aligned} $$

### 遠近感

| | CSS | SVG |
|:--|:--:|:--:|
| `perspective(d)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/perspective) | × |

* オブジェクトに遠近感をもたらす
* 画面は2次元の面なので通常オブジェクトの $z$ 座標を変えても表示内容にはそのままでは違いが生まれない
	* `perspective` ではオブジェクトの $z$ 座標に基づいて遠くにある物体ほど小さく、近くにある物体ほど大きく見えるように $x$, $y$ 座標も含めて調整される
* 引数には長さの単位を持った正の値を与える。これは視点と $z=0$ の面との間の距離を表す

行列表記と次のように対応する。

$$
	\mathrm{perspective}(d) \quad\cdots\quad
	\begin{pmatrix}\\; 1 & 0 & 0 & 0 \\;\\\\\\; 0 & 1 & 0 & 0 \\;\\\\\\; 0 & 0 & 1 & 0 \\;\\\\\\; 0 & 0 & -1/d & 1 \\;\end{pmatrix}
$$

ここではじめて4行目の非対格成分に $0$ 以外の値が現れた。つまり行列適用後の $w$ 成分が1以外の値になるということだ。そもそも $w$ 座標とは何か。

この $w$ 座標を含む4成分で表す座標系は同次座標系 (Homogeneous Coordinate) と呼ばれる。 $N$ 次元空間を表すのに $N+1$ 次元にすることで、行列の積でアフィン変換を全て表すことができるようになるというのが利点らしい。

行列変換適用後は次のように $w'$ で割ることによって3次元空間での実際の座標が与えられる。

$$
(x',y',z',w') \qquad\longrightarrow\qquad
\left(\frac{x'}{w'},\frac{y'}{w'},\frac{z'}{w'}\right)
$$

因みに、同次座標系の「同次」の意味については[こちら](https://kinakomoti321.hatenablog.com/entry/2024/04/14/201933)の説明が面白い。

スペース区切りで複数個の関数が指定されている場合は、全ての関数が適用された後に $w'$ で割る操作が行われる。

`transform` プロパティに `perspective(d)` のみが含まれているとすると、変換によって座標は次のように変わる。

$$
x' = x \qquad y' = y \qquad z' = z \qquad w' = 1 - \frac{z}{d}
$$
$$
\Longrightarrow\qquad
\frac{x'}{w'} = \frac{x}{1-z/d} \qquad
\frac{y'}{w'} = \frac{y}{1-z/d} \qquad
\frac{z'}{w'} = \frac{z}{1-z/d}
$$

つまり、オブジェクトの大きさは $z$ が $z=d$ に近づくにつれて反比例的に大きくなることがわかる。

### 行列による指定

| | CSS | SVG |
|:--|:--:|:--:|
| `matrix(6個の引数)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix) | [◯](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#matrix) |
| `matrix3d(16個の引数)` | [◯](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix3d) | × |

* 2次元あるいは3次元の任意のアフィン変換を行うことができる
* CSS であっても全ての引数は単位なしで指定する。長さとしては `px` 単位になる

既に示してはいるが、行列との対応関係を改めて以下に示す。

$$ \begin{aligned}
	\mathrm{matrix}(a,b,c,d,e,f) \quad\cdots\quad &
	\begin{pmatrix}\\; a & c & e \\;\\\\\\; b & d & f \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} \\\\
	\mathrm{matrix3d}(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \quad\cdots\quad &
	\begin{pmatrix}\\; a & b & c & d \\;\\\\\\; e & f & g & h \\;\\\\\\; i & j & k & l \\;\\\\\\; m & n & o & p \\;\end{pmatrix}
\end{aligned} $$

## 合成に関して閉じた変形関数

`transform` プロパティあるいは属性にスペース区切りで指定された複数個の変形関数は合成させて1つにまとめることができる。

これは行列操作を考えるとわかりやすい。
例えば $
	\mathrm{translate}(d_x,d_y) \\; \mathrm{scale}(k_x,k_y)
	=
	\mathrm{matrix}(k_x,0,0,k_y,d_x,d_y)
$ であることは、次の行列操作を考えれば明らかである。

$$ \begin{aligned}
	\begin{pmatrix}\\; x' \\;\\\\\\; y' \\;\\\\\\; 1 \\;\end{pmatrix}
	{}={} &
	\begin{pmatrix}\\; 1 & 0 & d_x \\;\\\\\\; 0 & 1 & d_y \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\begin{pmatrix}\\; k_x & 0 & 0 \\;\\\\\\; 0 & k_y & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\begin{pmatrix}\\; x \\;\\\\\\; y \\;\\\\\\; 1 \\;\end{pmatrix} \\\\
	{}={} &
	\begin{pmatrix}\\; k_x & 0 & d_x \\;\\\\\\; 0 & k_y & d_y \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\begin{pmatrix}\\; x \\;\\\\\\; y \\;\\\\\\; 1 \\;\end{pmatrix} \\\\
\end{aligned} $$

以上のような行列計算を行うことで、あらゆる変形操作は2次元なら1つの `matrix` 、3次元なら1つの `matrix3d` で表すことができる。

一方で、2つの `rotate` の合成は $\mathrm{rotate}(\theta_1)\\;\mathrm{rotate}(\theta_2) = \mathrm{rotate}(\theta_1+\theta_2)$ のようにまとめられるはずだ。
これは真面目に行列に変換して計算しても求められるが、「 $\theta_1$ だけ回転させた後、 $\theta_2$ だけ回転させれば、 $\theta_1+\theta_2$ だけ回転させたのと同じ結果が得られる」ということを考えれば明らかであろう。

このように合成したら必ず `matrix` や `matrix3d` で表す必要があるわけではない。特に「 `rotate` と `rotate` を合成して `rotate` を得る」というような `rotate` に「閉じた合成」ができるかどうかを、それぞれの変形関数に対して調査した。

以下では CSS と SVG に含まれている全ての関数に対して閉じているかを調べ、どのようにすれば合成できるのかを示している。

### 閉じている例: 平行移動, 拡大縮小

これらは丁寧に調べるまでもなく明らかなので省略する。

$$ \begin{aligned}
	\mathrm{translate}(d_{x1},d_{y1}) \\; \mathrm{translate}(d_{x2},d_{y2})
	{}={} & \mathrm{translate}(\\;d_{x1}+d_{x2}\\;,\\;d_{y1}+d_{y2}\\;) \\\\
	\mathrm{scale}(k_{x1},k_{y1}) \\; \mathrm{scale}(k_{x2},k_{y2})
	{}={} & \mathrm{scale}(\\;k_{x1}\cdot k_{x2}\\;,\\;k_{y1}\cdot k_{y2}\\;)
\end{aligned} $$

### 閉じている/閉じていない例: 剪断変形

$\mathrm{skewX}(\theta_1) \\; \mathrm{skewX}(\theta_2)$ を行列を使って計算してみよう。

$$
	\begin{pmatrix}\\; 1 & \tan\theta_1 & 0 \\;\\\\\\; 0 & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\begin{pmatrix}\\; 1 & \tan\theta_2 & 0 \\;\\\\\\; 0 & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} =
	\begin{pmatrix}\\; 1 & \tan\theta_1 + \tan\theta_2 & 0 \\;\\\\\\; 0 & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
$$

角度 $\theta$ を次のように定義すれば、右辺は $\mathrm{skewX}(\theta)$ と表すことができる。

$$
\tan\theta = \tan\theta_1 + \tan\theta_2
$$

$\mathrm{skewY}$ についても同様であり、以上から、 `skewX`, `skewY` はそれぞれ閉じていることがわかる。

$$ \begin{aligned}
	\mathrm{skewX}(\theta_1) \\; \mathrm{skewX}(\theta_2)
	{}={} & \mathrm{skewX}(\theta) \\\\
	\mathrm{skewY}(\theta_1) \\; \mathrm{skewY}(\theta_2)
	{}={} & \mathrm{skewY}(\theta)
\end{aligned} $$

一方、 CSS に含まれているものの SVG には含まれていない `skew` は、合成に対して閉じていない。

$$ \begin{aligned} &
	\begin{pmatrix}\\; 1 & \tan\theta_{x1} & 0 \\;\\\\\\; \tan\theta_{y1} & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\begin{pmatrix}\\; 1 & \tan\theta_{x2} & 0 \\;\\\\\\; \tan\theta_{y2} & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\\\\ & =
	\begin{pmatrix}\\; 1 + \tan\theta_{x1} \tan\theta_{y2} & \tan\theta_{x1} + \tan\theta_{x2} & 0 \\;\\\\\\; \tan\theta_{y1} + \tan\theta_{y2} & 1 + \tan\theta_{x2} \tan\theta_{y1} & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
\end{aligned} $$

また、 `skewX` や `skewY` を合成させても `skew` になることはない。

$$ \begin{aligned} &
	\begin{pmatrix}\\; 1 & \tan\theta_x & 0 \\;\\\\\\; 0 & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\begin{pmatrix}\\; 1 & 0 & 0 \\;\\\\\\; \tan\theta_y & 1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\\\\ & =
	\begin{pmatrix}\\; 1 & \tan\theta_x & 0 \\;\\\\\\; \tan\theta_y & 1 + \tan\theta_x \tan\theta_y & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
\end{aligned} $$

### 閉じている例: 回転

$\mathrm{rotate}(\theta_1)\\;\mathrm{rotate}(\theta_2)$ を行列を使って計算してみよう。

$$ \begin{aligned} &
	\begin{pmatrix}\\; \cos\theta_1 & -\sin\theta_1 & 0 \\;\\\\\\; \sin\theta_1 & \cos\theta_1 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix}
	\begin{pmatrix}\\; \cos\theta_2 & -\sin\theta_2 & 0 \\;\\\\\\; \sin\theta_2 & \cos\theta_2 & 0 \\;\\\\\\; 0 & 0 & 1 \\;\end{pmatrix} \\\\
	& = \begin{pmatrix}\\;
		\mathrm{c}\theta_1 \\; \mathrm{c}\theta_2 - \mathrm{s}\theta_1 \\; \mathrm{s}\theta_2 &
		{} - \mathrm{c}\theta_1 \\; \mathrm{s}\theta_2 - \mathrm{s}\theta_1 \\; \mathrm{c}\theta_2 &
		0 \\;\\\\\\;
		\mathrm{s}\theta_1 \\; \mathrm{c}\theta_2 + \mathrm{c}\theta_1 \\; \mathrm{s}\theta_2 &
		{} - \mathrm{s}\theta_1 \\; \mathrm{s}\theta_2 + \mathrm{c}\theta_1 \\; \mathrm{c}\theta_2 &
		0 \\;\\\\\\;
		0 & 0 & 1
	\\;\end{pmatrix} \\\\
	& =
	\begin{pmatrix}\\;
		\cos(\theta_1+\theta_2) & -\sin(\theta_1+\theta_2) & 0 \\;\\\\\\;
		\sin(\theta_1+\theta_2) & \cos(\theta_1+\theta_2) & 0 \\;\\\\\\;
		0 & 0 & 1
	\\;\end{pmatrix}
\end{aligned} $$

計算によっても $\mathrm{rotate}(\theta_1+\theta_2)$ が得られた。

座標を入れ替えるなどすれば `rotateX`, `rotateY`, `rotateZ` でも同様に成り立つのは明らかであろう。

$$ \begin{alignedat}{4}
	\mathrm{rotateX}&(\theta_1) \\;& \mathrm{rotateX}&(\theta_2) & {}={} &&
	\mathrm{rotateX}&(\\; \theta_1+\theta_2 \\;) \\\\
	\mathrm{rotateY}&(\theta_1) \\;& \mathrm{rotateY}&(\theta_2) & {}={} &&
	\mathrm{rotateY}&(\\; \theta_1+\theta_2 \\;) \\\\
	\mathrm{rotateZ}&(\theta_1) \\;& \mathrm{rotateZ}&(\theta_2) & {}={} &&
	\mathrm{rotateZ}&(\\; \theta_1+\theta_2 \\;)
\end{alignedat} $$

#### 回転中心が指定されている場合の `rotate`

SVG の `rotate` では回転中心を与えていた。この場合は**合成する `rotate` のパラメータの条件次第で閉じている場合もあれば、閉じていない場合もある。**

$\mathrm{rotate}(\theta_1,x_1,y_1)\\;\mathrm{rotate}(\theta_2,x_2,y_2)$ の計算結果だけ示すと

$$
\begin{pmatrix}\\;
	\cos(\theta_1+\theta_2) & - \sin(\theta_1+\theta_2) &
	x_1 - (x_1-x_2) \\; \mathrm{c}_1 + (y_1-y_2) \\; \mathrm{s}_1
	- x_2 \mathrm{c}\_{12} + y_2 \mathrm{s}\_{12}
	\\;\\\\\\;
	\sin(\theta_1+\theta_2) & \cos(\theta_1+\theta_2) &
	y_1 - (x_1-x_2) \\; \mathrm{s}_1 - (y_1-y_2) \\; \mathrm{c}_1
	- x_2 \mathrm{s}\_{12} - y_2 \mathrm{c}\_{12}
	\\;\\\\\\;
	0 & 0 & 1
\\;\end{pmatrix}
$$

表記を簡単にするため $\mathrm{c}_1 := \cos\theta_1$, $\mathrm{s}_2 := \sin\theta_2$, $\mathrm{c}\_{12} := \cos(\theta_1+\theta_2)$ などを導入した。

この行列表記が次の表記に当てはめられれば、合成した変形関数は $\mathrm{rotate}(\\; \theta_1+\theta_2 \\;,\\; x' \\;,\\; y' \\;)$ と表される。

$$
\begin{pmatrix}\\;
	\cos(\theta_1+\theta_2) & - \sin(\theta_1+\theta_2) &
	x' - x' \mathrm{c}\_{12} + y' \mathrm{s}\_{12}
	\\;\\\\\\;
	\sin(\theta_1+\theta_2) & \cos(\theta_1+\theta_2) &
	y' - x' \mathrm{s}\_{12} - y' \mathrm{c}\_{12}
	\\;\\\\\\;
	0 & 0 & 1
\\;\end{pmatrix}
$$

**変な条件でないことを仮定して**計算すると $x'$, $y'$ はそれぞれ次のように与えられる。

$$ \begin{alignedat}{4}
	x' = {} &&
	\frac{x_1+x_2}{2} &&
	{} - \frac{x_1-x_2}{2} &
	\frac{\mathrm{c}_1-\mathrm{c}_2}{1-\mathrm{c}\_{12}} &
	{} + \frac{y_1-y_2}{2} &
	\frac{\mathrm{s}_1+\mathrm{s}_2-\mathrm{s}\_{12}}{1-\mathrm{c}\_{12}}
	\\\\
	y' = {} &&
	\frac{y_1+y_2}{2} &&
	{} - \frac{x_1-x_2}{2} &
	\frac{\mathrm{s}_1+\mathrm{s}_2-\mathrm{s}\_{12}}{1-\mathrm{c}\_{12}} &
	{} - \frac{y_1-y_2}{2} &
	\frac{\mathrm{c}_1-\mathrm{c}_2}{1-\mathrm{c}\_{12}}
\end{alignedat} $$

変な条件として、 $ \mathrm{c}\_{12} = 1 $ の場合はこの式では求められない。これは $ \theta_1 + \theta_2 = 0 $ の場合、すなわち合成したら回転していないことになる場合である。

この時には $ \mathrm{s}\_{12} = 0 $ となることを踏まえて、改めて $\mathrm{rotate}(\theta_1,x_1,y_1)\\;\mathrm{rotate}(\theta_2,x_2,y_2)$ を示すと次の通りになる。

$$
\begin{aligned}
	& \begin{pmatrix}\\;
		1 & 0 &
		(x_1-x_2) \\; (1-\mathrm{c}_1) + (y_1-y_2) \\; \mathrm{s}_1
		\\;\\\\\\;
		0 & 1 &
		(y_1-y_2) \\; (1-\mathrm{c}_1) - (x_1-x_2) \\; \mathrm{s}_1
		\\;\\\\\\;
		0 & 0 & 1
	\\;\end{pmatrix} \\\\
	& = \mathrm{translate}\begin{pmatrix}\\;
		(x_1-x_2) \\; (1-\mathrm{c}_1) + (y_1-y_2) \\; \mathrm{s}_1
		\\;\\\\\\;
		(y_1-y_2) \\; (1-\mathrm{c}_1) - (x_1-x_2) \\; \mathrm{s}_1
	\\;\end{pmatrix}
\end{aligned}
$$

ということで、平行移動として表現されており、 `rotate` に閉じていない。 (まあ狭義の `rotate` と `translate` の組み合わせで構成されるんだから、 `translate` が出てきてもおかしくないよね)

但し、 $ (x_1,y_1) = (x_2,y_2) $ の場合は全く平衡移動しておらず、何も変換が起こっていない。何も変換が起こっていないという意味では $\mathrm{rotate}(0,x,y)$ の場合と同じであるから、閉じていると言える。

#### `rotate3d`

CSS に含まれる `rotate3d` については計算が大変であるが、一応閉じていることを示すことができる。

例えば

$$
	\mathrm{rotate3d}(l_x,l_y,l_z,\theta)\\;\mathrm{rotate3d}(m_x,m_y,m_z,\varphi) =
	\mathrm{rotate3d}(n_x,n_y,n_z,\psi) 
$$

という関係が成り立っているとした場合、合成後の回転軸ベクトル $\boldsymbol{n}$ と回転角 $\psi$ は次の計算により与えられる。

$$ \begin{aligned}
	\cos\psi {} = {} & F_1 \\\\
	\boldsymbol{n} \sin\psi {} = {} &
	F_2 \\; \boldsymbol{l} +
	F_3 \\; \boldsymbol{m} +
	F_4 \\; (\boldsymbol{l}\times\boldsymbol{m})
\end{aligned} $$

$\boldsymbol{l}\times\boldsymbol{m}$ は $\boldsymbol{l}$ と $\boldsymbol{m}$ の外積であり、 $\boldsymbol{l}$ と $\boldsymbol{m}$ に対して垂直な単位ベクトルを表す。

また、 $F_1$, $F_2$, $F_3$, $F_4$ はスカラー関数である。2つの角度 $\theta$, $\varphi$ と、 $\boldsymbol{l}$ と $\boldsymbol{m}$ の内積である $\boldsymbol{l}\cdot\boldsymbol{m}$ に依存して次のように与えられる。

$$ \begin{aligned}
	F_1(\\;\theta,\\;\varphi,\\;\boldsymbol{l}\cdot\boldsymbol{m}\\;) {} := {} &
		\frac{1}{2} \Bigl\lbrace\\;
			(1+\cos\theta)(1+\cos\varphi)
			- 2 (\boldsymbol{l}\cdot\boldsymbol{m}) \sin\theta \sin\varphi
			+ {(\boldsymbol{l}\cdot\boldsymbol{m})}^2 (1-\cos\theta)(1-\cos\varphi)
		\\;\Bigr\rbrace - 1 \\\\
	F_2(\\;\theta,\\;\varphi,\\;\boldsymbol{l}\cdot\boldsymbol{m}\\;) {} := {} &
		\frac{1}{2} \Bigl\lbrace\\;
			(1+\cos\varphi) \sin\theta
			- (\boldsymbol{l}\cdot\boldsymbol{m}) (1-\cos\theta) \sin\varphi
		\\;\Bigr\rbrace \\\\
	F_3(\\;\theta,\\;\varphi,\\;\boldsymbol{l}\cdot\boldsymbol{m}\\;) {} := {} &
		\frac{1}{2} \Bigl\lbrace\\;
			(1+\cos\theta) \sin\varphi
			- (\boldsymbol{l}\cdot\boldsymbol{m}) (1-\cos\varphi) \sin\theta
		\\;\Bigr\rbrace \\\\
	F_4(\\;\theta,\\;\varphi,\\;\boldsymbol{l}\cdot\boldsymbol{m}\\;) {} := {} &
		\frac{1}{2} \Bigl\lbrace\\;
			- \sin\theta \sin\varphi
			+ (\boldsymbol{l}\cdot\boldsymbol{m}) (1-\cos\theta) (1-\cos\varphi)
		\\;\Bigr\rbrace
\end{aligned} $$

求めるにあたっては $ \mathrm{rotate3d}(\boldsymbol{n},\psi) = \mathrm{rotate3d}(-\boldsymbol{n},-\psi) $ であることを利用して $ 0 < \psi < \pi $ に限定し、 $\cos\psi$ を求めてから $\psi$ と $\sin\psi$ を求め、 $\sin\psi$ で割って $\boldsymbol{n}$ を求める、という流れになりそうだ。

$ \sin\psi = 0 $ となる $ \psi = 0,\pi $ の場合はこの方法で $\boldsymbol{n}$ が求められない。 $ \psi = 0 $ に関しては回転していないということだから、 $\boldsymbol{n}$ が意味をなさないため問題とならないが、 $ \psi = \pi $ に関しては $\boldsymbol{n}$ が意味をなすので別の計算方法で $\boldsymbol{n}$ を求めなくてはならない。

回転軸の方向が同じ ( $\boldsymbol{l}=\boldsymbol{m}$ ) 場合、 $\boldsymbol{l}\cdot\boldsymbol{m} = 1$ かつ $\boldsymbol{l}\times\boldsymbol{m} = \boldsymbol{0}$ となるから $F_1$, $F_2$, $F_3$, $F_4$ は次の通りになる。

$$
	\begin{aligned}
		F_1 {}={}& \cos(\theta+\varphi) \\\\
		F_2 {}={}& \frac{1}{2} \sin(\theta+\varphi) + \frac{1}{2}\bigl(\sin\theta-\sin\varphi\bigr) \\\\
		F_3 {}={}& \frac{1}{2} \sin(\theta+\varphi) + \frac{1}{2}\bigl(\sin\varphi-\sin\theta\bigr) \\\\
		F_4 {}={}& \frac{1}{2} \Bigl( \cos(\theta+\varphi) - \cos\theta - \cos\varphi + 1 \Bigr)
	\end{aligned}
$$

故に

$$
	\cos\psi = \cos(\theta+\varphi)
	\qquad
	\boldsymbol{n} \sin\psi = \boldsymbol{l} \sin(\theta+\varphi) = \boldsymbol{m} \sin(\theta+\varphi)
$$

となるから、回転軸は変わらない ( $\boldsymbol{n} = \boldsymbol{l} = \boldsymbol{m}$ ) で、単純な角度の足し算 ( $\psi = \theta + \varphi$ ) になっていることがわかる。

### 閉じている例: 遠近感

$\mathrm{perspective}(d_1)\\;\mathrm{perspective}(d_2)$ を行列を使って計算してみよう。

$$
\begin{pmatrix}\\; 1 & 0 & 0 & 0 \\;\\\\\\; 0 & 1 & 0 & 0 \\;\\\\\\; 0 & 0 & 1 & 0 \\;\\\\\\; 0 & 0 & -1/d_1 & 1 \\;\end{pmatrix}
\begin{pmatrix}\\; 1 & 0 & 0 & 0 \\;\\\\\\; 0 & 1 & 0 & 0 \\;\\\\\\; 0 & 0 & 1 & 0 \\;\\\\\\; 0 & 0 & -1/d_2 & 1 \\;\end{pmatrix} =
\begin{pmatrix}\\; 1 & 0 & 0 & 0 \\;\\\\\\; 0 & 1 & 0 & 0 \\;\\\\\\; 0 & 0 & 1 & 0 \\;\\\\\\; 0 & 0 & - 1/d_1 - 1/d_2 & 1 \\;\end{pmatrix}
$$

長さ $d$ を次のように表すことで、右辺は $\mathrm{perspective}(d)$ と表すことができる。

$$
\frac{1}{d} = \frac{1}{d_1} + \frac{1}{d_2}
$$

## まとめ

本記事では CSS の `transform` プロパティや SVG の `transform` 属性について、2者の違いや、比較することで現れる変わった特徴についてまとめた。

HTML や SVG の記述で `transform` を指定する場合や、 `transform` の実装について気になったら、この記事の内容を思い返せるようにしたい。