ウラムの螺旋 3D Three.js編



<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ウラムの螺旋を3D可視化した <a href="https://t.co/P0P5OuO5mo">pic.twitter.com/P0P5OuO5mo</a></p>&mdash; ✝RIP✝ (@hakaicode) <a href="https://twitter.com/hakaicode/status/1178242333743054848?ref_src=twsrc%5Etfw">September 29, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

デモサイト: [ulam 3D](https://dobashi.github.io/dist/index.html) 
https://dobashi.github.io/dist/index.html
スマホのSafariでも動くよ！

世の中には「[共感覚](https://ja.wikipedia.org/wiki/共感覚)」というのを持っている人たちがいます。数字の共感覚者は、数列が螺旋状に見えたり色がついて見えるといいます。

凡人には想像するしかできないのですが、前回作ったウラムの螺旋を3Dでグリグリしたら共感覚に近いんじゃないかなと思い[Three.js](https://threejs.org/)を使って書いてみました。

ウラムの螺旋 2D jCanvas編: https://qiita.com/hakaicode/items/b07ff1d1f23aa956690d


## 準備

フロントエンドなんもわからん

たしかsnapにnpmとnodejsがなかった


```bash:install
sudo apt install -y npm nodejs
```

プロジェクト用のディレクトリを作成したらそこに移動して下記のコマンド。

```bash:npm
npm i -D webpack webpack-cli typescript ts-loader
npm i three @types/three
npm i glob
```

package.jsonというものを作る必要があるらしい。three.jsは2019-09時点で最新が108。


```json:package.json
{
  "name": "ulam_spiral",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "watch": "webpack -w"
  },
  "devDependencies": {
    "ts-loader": "^6.1.2",
    "typescript": "^3.6.3",
    "webpack": "^4.40.2",
    "webpack-cli": "^3.3.9"
  },
  "dependencies": {
    "three": "^0.108.0"
  }
}
```

webpack.configとtsconfigとかいうやつも必要らしいがなんもわからなすぎて解説することがないので必要に応じてソース見てほしい。

https://github.com/dobashi/ulam_spiral

なおsetup.shにこの辺の一連の流れが入っているけど、環境つくりながら作ったファイルで未検証なので、これを実行しただけで同じ状況になるか自信がない。使ってみる人がいたらフィードバックください。

## 3D 文字表示

Three.jsで文字を表示するにはFontをロードする必要があり、これは非同期読み込みになる。ネットで情報を漁るとコールバックの中でscene.add()してるようなサンプルが見つかるが、処理を先送り先送りしていくのは典型的なスパゲッティソースだし、必要なものを全部引数で引き回さなきゃいけなくなるので真似してはいけない。

ここではFontsクラスを作って、コールバックでは読み込んだFontをMapに保存するだけとし、利用側はawait get(fontname)してFontが読み込まれるまで待つようにした。

```typescript:Fonts

class Fonts {
  private loader: FontLoader
  private map: Map<string, Font>
  constructor() {
    this.map = new Map()
    this.loader = new FontLoader()
    this.loader.load('fonts/helvetiker_regular.typeface.json', (font) => this.map.set('helvetiker', font))
  }
  public get(name): Promise<Font> {
    return new Promise((resolve, reject) => {
      const font = this.map.get(name)
      if (this.map.has(name)) resolve(font)
      else setTimeout(() => this.get(name), 200)
    })
  }
}
```

このソースのままだとfont名の指定を間違えると永遠に待ち続けるので、この辺を再利用するように組み込む時はエラーになるまでのタイムアウトを設定したほうが親切(読み込めないFontを指定したことがわかるので)。

### OrbitControl

これを入れるとマウスでぐりぐり動かすのとかをいちいち自分で書かなくてもやってくれる。サンプルとかStackoverflowで書かれてるようなexample配下のファイルを参照しろとか、バージョンを全部合わせろとか、いろいろやってみても駄目だったのでjsファイルを直接プロジェクトディレクトリ内においた。

置いたら一発で動いたのでなんかすごい無駄な調査努力をしてしまった。tsから参照するためにjsファイルに`export`だけ追記したかも。

### ウラムの螺旋

前回作成したulam.min.jsをtypescriptでそのまま利用できないので型を教えてやる必要がある。自動生成ツールみたいなのを使ってみたけどなんか未完成らしく、anyで逃げるでもなくエラーがでていてつらいので手で書いた。が、


```bash
ERROR in ./src/ulam.d.ts
Module build failed (from ./node_modules/ts-loader/index.js):
Error: TypeScript emitted no output for /home/user/projects/practice/ulam_spiral/src/ulam.d.ts.
    at makeSourceMapAndFinish (/home/user/projects/practice/ulam_spiral/node_modules/ts-loader/dist/index.js:80:15)
    at successLoader (/home/user/projects/practice/ulam_spiral/node_modules/ts-loader/dist/index.js:68:9)
    at Object.loader (/home/user/projects/practice/ulam_spiral/node_modules/ts-loader/dist/index.js:22:12)
 @ multi ./src/main.ts ./src/Stage.ts ./src/ulam.d.ts main[2]
```



ulam.d.tsっていうのが悪いんだけど、このエラーメッセージ情報量ゼロじゃない？ts-loaderの何行目でエラーが通知してるかなんてどうでもよくて、ulam.d.tsの何行目が悪いのかを知りたいんだけど。ts-loader使いの人たちはエスパーなんだろうか。

むかついたのでリファクタリングついでにulamのjsを全部tsで書き直した。ロジックは変わっていないので解説はしない。リポジトリ上では元のulam.htmlとulam.min.jsをdist/ディレクトリ上に残してある。

### build & 実行

コンパイルは`npm run build`。ソースを修正する度に自動で再コンパイルを走らせるようにするには`npm run watch`。package.jsonに指定してある。

実行と言ってもコンパイル後は静的なJavascriptになるだけ。.tsファイルがコンパルされてpackage.jsonで指定したjsファイルになる(今回はindex.js)ので、そのjsを読み込むようなhtmlを書けば完了

```
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <script defer src="index.js"></script>
</head>
</html>
```

### TODO

- 最大値を入力で指定できるようにすると便利そう(今あるmaxはダミー)
- 2Dの時みたいに数字の紐付けのラインを引きたい

### 感想とか

- Three.jsで作られたAndroidアプリもあるとの事だったので、これをやれば3Dゲーム作成出来るようになるのか？と思ってたけど重すぎて厳しい。やっぱりUnityを勉強しないといけないのか
- jCanvasを使うとjQuery依存になってしまうので、Three.jsで一気にjCanvasも脱却だ！と思ったけど、3Dだと考慮する事が一気に増えるのでjCanvasの代わりには成れない、モダンな2D canvasライブラリを探さないと
- vscodeはIDEに比べると機能は貧弱だけど動作はそこそこ重くて、使ってて嬉しさが少ない。vimがいいのかお金出してでもWebStromがいいのか、あ、vscodeがサクサク動く環境を手に入れるのが一番いいか(WebStormは機能に比して安いとは思うけど、年次更新とかライセンス管理とか考えると面倒、tsいじるのが仕事ならいいかも
- 動画じゃなくてオンラインで動くサンプルサイトを用意したい -> 静的なjsだからgithub.ioでできた。iPhoneでも動いてすごい！でも熱い！
- laptop(Core i7-8565U)で1000まで描画すると重すぎなのはCPUが省電力版だからいけないのかな、もしくはIntelのGPUが悪いのか


### 参考

Three.js公式 https://threejs.org/ 
公式って情報が多すぎてどこから読めばいいかわからなかったり、情報が足りなかったり前頭としてる知識がわからなくてblogサイトを漁らないといけないことが多いけど、今回ここがすごく役に立った。

Qiitaへの動画埋め込みにtwitterを使う
https://qiita.com/akameco/items/6ff78b0918e96f871f57