CSSの3Dトランスフォームで正多面体を作る

CSSの理解を深めるために立体図形を作成した。
何か間違ってたらごめんなさい。

# 正多面体とは

これら5つの立体のこと。これら以外には存在しない。

正四面体|正六面体|正八面体|正十二面体|正二十面体
:-:|:-:|:-:|:-:|:-:
![120px-Tetrahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/ddf60b31-687a-6f41-edb0-b4e42c9a3d9c.gif)|![120px-Hexahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/c317af62-ce14-37ce-4b90-1da4b704aeff.gif)|![120px-Octahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/bb8c3188-3a56-a4b5-f3d7-73afbde46a9b.gif)|![120px-Dodecahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/a8e383e5-2af3-1146-6227-7b388ab1b1f6.gif)|![120px-Icosahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/1bbbdfc1-d9b9-5558-26ef-5555afc5ecd3.gif)

画像は [正多面体 - Wikipedia](https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%A4%9A%E9%9D%A2%E4%BD%93) より。

全ての面が等しい正多角形で、全ての面で接する頂点の数が等しい。

**※注： 十二面体、二十面体は完成しませんでした。** ~~誰か作って~~

# 正六面体

![120px-Hexahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/c317af62-ce14-37ce-4b90-1da4b704aeff.gif)

いわゆる立方体。サイコロ。
なぜ正四面体から始めないのか？
正六面体が一番お手軽だからだ。

サンプル。
http://jsrun.it/zakuroishikuro/WKlN - [jsdo.it](http://jsdo.it/zakuroishikuro/WKlN)

## 作成手順

色付け等の装飾要素を省き、正六面体を表示するための最低限のソースを書く。
上記のサンプルと異なり、枠線のみ描画される。

`div`を6枚用意し、一つのまとまりとするために`div`で包む。(obj)
消失点(perspective-origin)ごと移動可能とするため、さらに`div`で包む。(base)
これだけでhtml側は完成。

```html:cube.html
<div class="base">
  <div class="obj">
    <div class="left"></div>
    <div class="right"></div>

    <div class="top"></div>
    <div class="bottom" ></div>

    <div class="back"></div>
    <div class="front"></div>
  </div>
</div>
```

CSSで組み立てていく。
まず各面を3Dトランスフォームするための準備。

```css:cube_common.css
div {
  height: 200px; width: 200px; /* divは全て200px * 200pxにする */
  position: absolute; /* 全て重ねる */
}

.base {
  perspective: 900px; /* z軸の原点(0)までの距離を設定 (画面の900px前に視点を移動) */
}

.obj {
  transform-style: preserve-3d; /* 子要素を立体的に表示 */
}
```

各面を組み立てていく。前面はいじる必要がない。

```css:cube_polygon.css
.left { /* 左の面 */
  transform-origin: left; /* 原点を左の辺に置く */
  transform: rotateY(90deg); /* Y軸を90度回転 */
}

.right { /* 右の面 */
  transform-origin: right; /* 原点を右の辺に置く */
  transform: rotateY(-90deg); /* Y軸を-90度回転 */
}

.top { /* 上の面 */
  transform-origin: top; /* 原点を上の辺に置く */
  transform: rotateX(-90deg) /* X軸を-90度回転 */;
}

.bottom { /* 下の面 */
  transform-origin: bottom; /* 原点を下の辺に置く */
  transform: rotateX(90deg); /* X軸を90度回転 */
}

.back { /* 後ろの面 */
  transform: translateZ(-200px); /* 面を後ろに移動するだけ */
}
```

立体と分かるよう、枠線を表示し、回転させて完成。

```css:cube_anim.css
.obj > div {
  box-sizing: border-box; /* 枠線が面からはみ出ないようにする */
  border: 10px dotted black;  /* 幅10ピクセルの点線 */
}

@keyframes spin{
  from {transform: rotateY(0turn)}
  to {transform: rotateY(1turn)} /* Y軸で360度回転 */
}

.obj {
  /* オブジェクトは奥に作成されているので、その真ん中(z軸-100px)を回す */
  transform-origin: 50% 50% -100px;
  animation: 10s spin linear infinite;
}
```

正六面体に必要なソースはこれだけ。

面は全て正方形なので、`div`をそのまま使える。
面と面の間の角(二面角)は90度なので、何も計算せずに作れる。
かんたん。

また、原点を移動せずに`translate`や`position: absolute`で面を移動してから回転させる方法もある。

# 正四面体

![120px-Tetrahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/ddf60b31-687a-6f41-edb0-b4e42c9a3d9c.gif)

サンプル。
http://jsrun.it/zakuroishikuro/SoHx - [jsdo.it](http://jsdo.it/zakuroishikuro/SoHx)

正六面体では、面が正方形だったので`div`に色を塗るだけで作れた。
正四面体では、面が正三角形なのでSVGで描く。

## SVGで正三角形を描く

```html
<svg viewBox="0,0 2 2">
  <path d="M0,2 h2 l-1,-1.7320508075688772 Z" fill="black"/>
</svg>
```

これで1辺の長さが2の正三角形を描ける。
2px * 2pxと非常に小さいが、SVGは無限に拡大可能なので、CSSでサイズ指定してあげればいい。

簡単に説明すると、`viewBox`で表示範囲の座標(0,0)と大きさ(2*2)を指定。
座標(0,2)から水平に長さ2の線を引き、その位置からX軸 $-1$, Y軸 $-\sqrt{3}$ の座標へ線を引いている。
高さが$\sqrt{3}$なのは三平方の定理による。

## 正四面体の二面角

灰色の正三角形が底面で、青色の正三角形との間にオレンジの直角三角形が挟まれている。
その左下の「？」が二面角。この角度が分かれば面をどれだけ傾ければよいか分かる。

<img width="428" alt="スクリーンショット 2016-05-17 21.17.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/18902/1088b927-2a16-be72-8b67-cb05e30137e5.png">

http://jsrun.it/zakuroishikuro/IWai - [jsdo.it](http://jsdo.it/zakuroishikuro/IWai)

「？」の角度を求めるには逆三角関数を使う。
三角比の逆で、直角三角形の2辺の比から角度を計算できる。
「$\sin^{-1}$」のように書いて「アークサイン」等と読む。

右下は正四面体を上から見た図。
$a$の長さは、底辺からちょうど正三角形の重心まで。$a = c \div 3$
灰色は青色と合同なので、$c = $ 灰色の高さ。

$c = 3$として、「？」の角度を計算する。

```math
\cos^{-1} \frac{1}{3} = 70.52877936550931...
```

「？」の角度は約70度であることが分かった。
ということは、90° - 約70°の **19.47122063449069°** 傾ければいい。($\sin^{-1} \frac{1}{3}$でも可)

JavaScriptでは`90 - Math.acos(1/3) * 180 / Math.PI`のように計算できる。

## 作成手順

正三角形を表示する方法と二面角が分かったので、組み立てていく。
4枚の正三角形(SVG)を作る。SVGの色付けはCSSでも可能だが、今回はHTML側で行う。

```html:reg_tetra.html
<div class="base">
  <div class="obj">
    <svg class="front" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="red"/></svg>
    <svg class="left" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="green"/></svg>
    <svg class="right" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="blue"/></svg>
    <svg class="bottom" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="silver"/></svg>
  </div>
</div>
```

CSS。ここは正六面体とだいたい同じ。
最初のルールのセレクタを`div, svg`としたのと、不透明度(opacity)を追加した。

```css:reg_tetra_common.css
div, svg {
  height: 200px; width: 200px; /* divは全て200px * 200pxにする */
  position: absolute; /* 全て重ねる */
}

.base {
  perspective: 900px; /* z軸の原点(0)までの距離を設定 (画面の900px前に視点を移動) */
}

.obj {
  transform-style: preserve-3d; /* 子要素を立体的に表示 */
}

.obj > svg {
  opacity: 0.6; /* 半透明にする */
}
```

面を傾けていく。
正三角形の一つの角は60°なので、Y軸は60°回す。

```css:reg_tetra.css
.front { /* 前面 */
  transform-origin: bottom; /* 原点を底辺に移動し */
  transform: rotateX(19.47122063449069deg); /* X軸を約19°傾ける */
}

.left { /* 左の面 */
  transform-origin: bottom left; /* 原点を左下に移動し */
  transform: rotateY(60deg) rotateX(-19.47122063449069deg); /* Y軸を60°回し、X軸を約-19°傾ける */
}

.right { /* 右の面 */
  transform-origin: bottom right; /* 原点を右下に移動し */
  transform: rotateY(-60deg) rotateX(-19.47122063449069deg); /* Y軸を-60°回し、X軸を約-19°傾ける */
}

.bottom { /* 底面 */
  transform-origin: bottom; /* 原点を底辺に移動し */
  transform: rotateX(90deg); /* X軸を90度回す */
}
```

アニメーション。z軸の原点の計算がややめんどい。

```css:reg_tetra_anim.css
@keyframes spin{
  from {transform: rotateY(0turn)}
  to {transform: rotateY(1turn)}
}
.obj {
  /* オブジェクトは奥に作成されているので、その真ん中(z軸-約57px)を回す */
  transform-origin: 50% 50% calc(-200px / 2 * (1.7320508075688772 / 3));
  animation: 10s spin linear infinite;
}
```

# 正八面体

![120px-Octahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/bb8c3188-3a56-a4b5-f3d7-73afbde46a9b.gif)

サンプル。
http://jsrun.it/zakuroishikuro/0dKl - [jsdo.it](http://jsdo.it/zakuroishikuro/0dKl)

いわゆるラミエル。
面は正三角形なので、正四面体から流用できる。

## 正八面体の二面角

上半分のビラミッド型の図形を考える。
このピラミッド型は4枚の正三角形で構成されており、上から見ると正方形になっている。
正三角形の1辺の長さ = 正方形の1辺の長さ。

正四面体の時に考えた、面に挟まれたオレンジ三角形を考える。

正三角形の1辺の長さを2とすると、三平方の定理より高さは$\sqrt{3}$。これがオレンジ三角形の斜辺。
正方形の1辺の長さも2なので中心までは長さ1。これがオレンジ三角形の底辺。
2辺の比が分かったので角度を計算できる。

```math
\cos^{-1}\frac{1}{\sqrt{3}} = 54.735610317245346...
```

$90° - 約54°$で **35.264389682754654°**傾ければいい。
(ちなみに二面角は2倍の109.47122063449069...。)

## 作成手順

正四面体のsvgを4枚増やしたような感じ。

```html
<div class="base">
  <div class="obj">
    <svg class="front" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="red"/></svg>
    <svg class="back" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="silver"/></svg>
    <svg class="left" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="green"/></svg>
    <svg class="right" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="blue"/></svg>

    <svg class="bottom front" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="silver"/></svg>
    <svg class="bottom back" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="red"/></svg>
    <svg class="bottom left" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="blue"/></svg>
    <svg class="bottom right" viewBox="0,0 2 2"><path d="M0,2 h2 l-1,-1.7320508075688772" fill="green"/></svg>
  </div>
</div>
```

ここは正四面体と全く同じ。

```css:reg_hexa_common.css
div, svg {
  height: 200px; width: 200px; /* divは全て200px * 200pxにする */
  position: absolute; /* 全て重ねる */
}

.base {
  perspective: 900px; /* z軸の原点(0)までの距離を設定 (画面の900px前に視点を移動) */
}

.obj {
  transform-style: preserve-3d; /* 子要素を立体的に表示 */
}

.obj > svg {
  opacity: 0.6; /* 半透明にする */
}
```

傾ける。枚数増えたからやたらめんどくさい。

```css:reg_hexa.css
.front {
  transform-origin: bottom;
  transform: rotateX(35.26438968275467deg);
}

.back {
  transform-origin: bottom;
  transform: translateZ(-200px) rotateX(-35.26438968275467deg);
}

.left {
  transform-origin: bottom left;
  transform: rotateY(90deg) rotateX(-35.26438968275467deg);
}

.right {
  transform-origin: bottom right;
  transform: rotateY(-90deg) rotateX(-35.26438968275467deg);
}

.bottom.front {
  transform-origin: bottom;
  transform: rotateX(144.73561031724535deg); /* 180° - 約35° */
}

.bottom.back {
  transform-origin: bottom;
  transform: translateZ(-200px) rotateX(-144.73561031724535deg);
}

.bottom.left {
  transform-origin: bottom left;
  transform: rotateY(90deg) rotateX(-144.73561031724535deg);
}

.bottom.right {
  transform-origin: bottom right;
  transform: rotateY(-90deg) rotateX(-144.73561031724535deg);
}
```

アニメーション。

```css:reg_hexa_anim.css
@keyframes spin{
  from {transform: rotateY(0turn)}
  to {transform: rotateY(1turn)}
}
.obj {
  /* オブジェクトは奥に作成されているので、その真ん中(z軸-100px)を回す */
  transform-origin: 50% 50% 100px;
  animation: 10s spin linear infinite;
}
```

正四面体よりもだいぶ雑になったけどおしまい。
上半分と下半分で作り分けたほうがいいんだと思う。

# 正十二面体

![120px-Dodecahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/a8e383e5-2af3-1146-6227-7b388ab1b1f6.gif)

ここから急に難度が跳ね上がる。
枚数が増えてめんどくささも格段に増す。
新しく正五角形のSVGも書かなければいけない。

いままでの図形はY軸と底辺を回すだけで作れた。
ここからは、回す軸の座標と角度も指定しなければならない。
完全に別ゲー。

## 正五角形のSVGを描く

正n角形を描く方法を考える。

正多角形の全ての頂点は、一つの円の円周上にある。
ということは、円周上に均等にnつの点を打って繋げばいい。
円周を3で割って繋げば正三角形ができる。

Canvasでやってみるとこう。
http://jsrun.it/zakuroishikuro/G3dQ - [jsdo.it](http://jsdo.it/zakuroishikuro/G3dQ)

```js:draw_reglar_polygon.js
  function draw(n){
    reset_canvas(); // キャンバスの内容を消去する関数

    // ctxはcanvasのコンテキスト
    // rは円の半径(ctx.canvas.width / 2)
    ctx.beginPath();
    for (var i = 0; i < n; i++){
      var rad = Math.PI * 2 / n * i;
      var x = Math.cos(rad) * r;
      var y = Math.sin(rad) * r;
      ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.stroke();
  }
```
よく分かんない人は『数学ガールの秘密ノート 丸い三角関数』を読むと幸せになれる。
(この方法が直接的に書かれているわけではないけど。)

もろもろ省略して正五角形のSVGはこんなん。

```html
<svg viewBox="0,0 2 2">
  <path d="
    M1,0
    L1.9510565162951536,0.6909830056250525
    L1.5877852522924731,1.8090169943749475
    L0.412214747707527,1.8090169943749475
    L0.04894348370484636,0.6909830056250528
    Z" fill="silver"/>
</svg>
```

## 面を傾ける方法

今までは **底辺を軸に** 面を傾けるだけで何とかなった。
今度は五角形の **各辺を軸に** 傾けなければいけない。

イメージはこんなん。
300px * 300pxの矩形の左下で、縦100px * 横200pxの直角三角形が斜辺を軸に回っている。
http://jsrun.it/zakuroishikuro/YqjU - [jsdo.it](http://jsdo.it/zakuroishikuro/YqjU)

辺を軸に回転させるには、原点を辺の中心に移動し、rotate3dでその辺に沿ったベクトルを指定する。
上のイメージでは以下のようにして回転させている。

```css
@keyframes spin{
  /* X軸方向に2、Y軸方向に1、Z軸方向に0のベクトルを軸にして360度回転させる */
  from {transform: rotate3d(2, 1, 0, 0deg)}
  to {transform: rotate3d(2, 1, 0, 360deg)}
}

svg {
  /* 原点を移動。矩形は300px * 300pxで、X軸はその半分、Y軸は3/4 */
  transform-origin: calc(300px / 2) calc(300px / 4 * 3);

  animation: spin 3s linear infinite;
}
```

## お詫び

私の園卒の脳と集中力ではここまでが限界でした。(動物園)
心よりお詫びいたします。

http://jsrun.it/zakuroishikuro/cOai - [jsdo.it](http://jsdo.it/zakuroishikuro/cOai)

せっかく途中まで書いたので、投稿します。
申し訳ございませんでした。

あ、[Wikipedia](https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%8D%81%E4%BA%8C%E9%9D%A2%E4%BD%93)によると二面角は$\cos^{-1}\frac{-1}{\sqrt{5}}$で約116°らしいです。

# 正二十体

![120px-Icosahedron-slowturn.gif](https://qiita-image-store.s3.amazonaws.com/0/18902/1bbbdfc1-d9b9-5558-26ef-5555afc5ecd3.gif)

頑張れ。

# あとがき

間違ってもCSSでガンダムを建造しようとか考えないこと。
複雑な3Dを扱いたい場合はJavaScriptとThree.jsとかのライブラリ使ってやること。

あとCSSの角度の単位は度数(deg)だけでなくラジアン(rad)も使える。
Math.atanなどの逆三角関数はラジアンを返すけど、わざわざ度数に変換しなくてもいい。
ちなみに1回転(turn)や400°を1回転とする(grad)も使える。

あとあとここでは面の裏表は考慮していないのでbackface-visibilityをhiddenにすると表示できません。

# 参考リンク

MDN

- [transform-function](https://developer.mozilla.org/ja/docs/Web/CSS/transform-function)
- [transform](https://developer.mozilla.org/ja/docs/Web/CSS/transform)

Wikipedia

- [正多面体](https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%A4%9A%E9%9D%A2%E4%BD%93)
- [正四面体](https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%9B%9B%E9%9D%A2%E4%BD%93)
- [正六面体](https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%85%AD%E9%9D%A2%E4%BD%93)
- [正八面体](https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%85%AB%E9%9D%A2%E4%BD%93)
- [正十二面体](https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%8D%81%E4%BA%8C%E9%9D%A2%E4%BD%93)
- [正二十面体](https://ja.wikipedia.org/wiki/%E6%AD%A3%E4%BA%8C%E5%8D%81%E9%9D%A2%E4%BD%93)

日本語のページは絶望的に情報が少ないので英語のページを見たほうがいい。