TL;DR
- Elm の 3D 物理エンジン elm-physics は、新版で World コンテナを廃止し、物体のリストを受け取る
simulate関数だけを持つ API になった。 - 制約や衝突を関数で書く、型で単位や座標系や素材を区別するなど、物理以外にも使える設計の考え方を紹介する。
- 代償として cannon.js より約 2 倍遅いが、バンドルが小さく、積み上げた箱が崩れにくい。
内容
World を手放すまで
elm-physics は JavaScript の物理エンジン cannon.js の移植から始まった。移植した当初の API には単位がなく、Int の ID の扱いも面倒だった。そこで elm-geometry の形と elm-units の単位を使う型付きの API にし、elm-obj-file で任意の 3D モデルを凸の衝突形状に変換できるようにした。1 フレーム進める関数は、2018 年の step : Time -> World -> World、2020 年の simulate : Duration -> World data -> World data を経て、2026 年の v6 では World が型から消え、List ( id, Body ) を受け取って返す形になった。
リストと標準の操作
物体の状態はただのリストになったので、追加は ::、削除は List.filter、描画は List.map と、Elm の標準の操作で済む。物理エンジンの API は simulate だけである。物体は「何でできているか」を形と素材の組で書くだけで、質量や重心や慣性は形から計算される。形は足し算と引き算で作れる。ただし、これは幾何のブーリアン演算ではなく、変わるのは質量や重心や慣性である。
規則は関数、型は境界に
鎖の制約は、どの物体とどの物体をつなぐかを返す関数として simulate の設定に渡すので、鎖はどこにも保存されない。型では、動く物体には密度のある素材を要求し、表面だけの素材を渡すとコンパイルエラーになるようにしている。状態は不変なので、巻き戻せる。タイムトラベルは過去の状態のリストを逆にたどるだけで済む。
トレードオフ
125 個の箱を 3600 フレーム動かす比較では、cannon.js より約 2 倍遅い。衝突判定は Elm のほうが速く、差はソルバーで生じる。一方、minify と gzip の後のサイズは、物理エンジンだけではほぼ互角で、描画込みでは elm-physics のほうが小さい。積み木を 3600 フレーム動かした後の平均のずれは、elm-physics が 130 mm、cannon.js が 847 mm である。
まとめ
コンテナはたいていリストのようなものなので、開けてしまえばよい。規則はデータでなく関数にすれば合成できる。型は単位や座標や素材といった境界に置く。純粋な state → state の関数なので、リプレイも取り消しもできる。代償を知ったうえで意図して選ぶ。
キーワード
- Elm
- elm-physics
- cannon.js
- elm-geometry
- elm-units