世界を動かす技術を、日本語で。

Clojureでの宇宙飛行シミュレーターの開発

概要

  • Orbiter 2016 に触発され、自作の宇宙飛行シミュレーター開発を開始
  • Clojure を用いた5年にわたる開発と、その選択理由
  • グラフィックス処理 や大気レンダリングなど、技術的な挑戦と実装例
  • 依存ライブラリ やパフォーマンス最適化の工夫
  • データ管理 や物理エンジンとの連携手法

自作宇宙飛行シミュレーター開発の経緯と言語選択

  • 2017年、 Orbiter 2016 (当時プロプライエタリ、後にMITライセンスでOSS化)に出会い、宇宙飛行シミュレーター開発を志す
  • 最初は C言語GNU Guile (Scheme実装)で物理エンジンやOBJファイルの描画をプロトタイプ
  • Guile はネイティブインターフェースやマクロが優秀だが、 Clojure のマルチメソッド、高速なハッシュマップ・ベクターに魅力を感じ本格開発をClojureで開始
  • Clojure のイミュータブル値や安全な並列処理(atoms, agents, refs)の恩恵を実感
  • 開発初期から最難関の 3Dグラフィックス(大気、雲、影など) に着手

グラフィックス処理・大気レンダリング

  • OpenGL Superbible を参考にOpenGLの機能を理解
  • Orbiter のソース調査で、コードの9割がグラフィックス関連と判明し、グラフィックス重視の方針を正当化
  • 大気は Brunetonの事前計算型大気散乱 アルゴリズムを実装
    • 2D・4Dのテーブルを数値積分で生成
    • Clojureで球面や線分上の積分高階関数を実装
    • テーブル生成は pmap で並列化、数時間かかる
    • 計算結果を ByteBuffer でバイト配列化し、ファイルに保存
    • 起動時にOpenGLテクスチャへロード、シェーダーで参照

OpenGLシェーダーのテンプレート化

  • Comb ライブラリでGLSLシェーダーをテンプレート化
  • 複数オクターブのノイズ合成や柔軟なシェーダー関数定義を実現

惑星レンダリングとデータ管理

  • NASA Bluemarble/Blackmarble/Elevation データを多階層タイル状に変換
  • 各タイルに昼・夜テクスチャ、メッシュ情報、ウォーターマスク、法線マップなどを生成
  • 合計 655,350ファイル を生成、 tar ファイルでまとめて管理
  • Apache Commons Compress でtar内のランダムアクセスを実現
  • LRUキャッシュ で開いたtarファイルを効率管理
  • OpenGLテクスチャのロード/アンロードを future で非同期処理
  • QuadTree 操作用関数をClojureで実装

ソフトウェア依存関係

  • 開発で利用した主なライブラリ

    • Clojure (言語本体)
    • LWJGL (OpenGL, GLFW, Nuklear, stb, assimp等のJavaラッパー)
    • Jolt Physics (車両・メッシュ物理演算)
    • Fastmath (高速数値演算)
    • Comb (シェーダーテンプレート)
    • Instaparse/Gloss (NASAデータファイル解析)
    • Coffi (FFI、C関数との連携)
    • core.memoize (LRUキャッシュ)
    • Apache Commons Compress (tarファイル管理)
    • Malli (関数スキーマ)
    • Immuconf (設定ファイル管理)
    • Claypoole (並列forループ)
    • clj-async-profiler (パフォーマンス解析)
    • slf4j-timbre (ロギング)
  • deps.edn でOSごとにLWJGLネイティブバインディングを管理

    • Windows用は別Gitブランチで管理

太陽系・天体データの扱い

  • Skyfield (Python)や NASA JPL SPICE ツールキットを参考に天体位置・姿勢を計算
  • JPLの2重精度配列(DAF)やテキストファイル(PCK)を GlossInstaparse で解析

物理エンジンとの連携

  • Jolt の車両・衝突機能をCラッパー経由で呼び出し
  • Coffi でClojureからC関数・構造体を利用
  • 例:add_force関数ラップ、vec3型のシリアライズ/デシリアライズ実装

パフォーマンス最適化

  • clj-async-profiler でフレームグラフ生成
  • Java呼び出しのリフレクション警告を warn-on-reflection で検出
  • 数値型のボックス化警告を unchecked-math で検出
  • ZGC 低遅延GCをJVMオプションで指定し、GC停止時間を削減
  • deps.edn でZGCの有効化やメモリ設定を明示

このように、 Clojure と多様なOSSライブラリを活用しつつ、 グラフィックス・物理・データ管理 など幅広い領域で高度な実装と最適化を重ねた宇宙飛行シミュレーター開発の全体像。

Hackerたちの意見

: https://www.gnu.org/software/guile/manual/html_node/Methods-...

Clojureは、C系の言語から来た私には全然違う感じ。コード見ても何が起こってるのか全く分からないよ。

すごい!UnityやUnrealみたいな標準的なゲームフレームワークを使ってないのが印象的だね。

確かにクールだけど、今は自分でグラフィックスエンジンを作る人って少ないのかな?2000年頃に趣味でゲーム開発してた時は、みんな自分のシステムを書いてたけど。

これ最高!マリの実践的な例がすごくいいね!

Jankがどんな風にプロダクションレディになるのか、すごく楽しみ!インディーゲームコミュニティを驚かせること間違いなしだね。早く見たいな。

プログラミングはゲーム開発のほんの一部だよ。インディーゲームコミュニティで何かを変えるようなプログラミング言語はないと思う。歓迎されるのはいいけど、革命的なことにはならないんじゃないかな。

Jankは自分のファンを見つけると思うけど、その中にどれだけインディーゲーム開発者がいるかはわからないな。確かに何人かはいるだろうけど、全体的にはインディーゲーム開発者がClojureを使いたがってるとは思えない。JVMやパフォーマンスの問題がなければ別だけど。多くのインディーゲーム開発者はClojureやJank、さらには関数型プログラミングについても知らないんじゃないかな。インディーゲーム開発者はUnityやUnreal、Godotみたいなエンジンエコシステムと競争してるし、DIY志向の人にはLua(love2D)、C#(monogame)、Javascript(...)がある。パフォーマンスを重視する人にはC++、Rust、Odin、Zig、そして近々Jaiも出てくる。競争が激しい分野で、やっぱりこの領域の人たちは関数型スタイルでプログラミングすることを夢見ているわけじゃないと思う。

すごい仕事だね、Jan!ham-fistedを試してみた?Clojureエコシステムのtechascent部分のライブラリはパフォーマンス的にすごく良いと思うよ。neanderthalも同様に。

これは素晴らしいブログ記事だった、ありがとう!

ジャン、これすごいね!ずっと君の進捗をフォローしてたよ。実は、君のプロジェクトを見つけたのは、昔GitHubにアップした夢追いかけモデルに君がいいねしてくれたからなんだ。これからの展開が楽しみだし、いつか君のシミュレーターも試してみたいな!

Clojure開発者として長いことやってるけど、これは見た中で一番クールなプロジェクトだよ!君のブログ、興味津々で読ませてもらうね!

「シミュレーションにおける関数型言語の使用」について読むたびに、OpenGLバインディングが使われているかチェックするんだ。他に重要なのは物理エンジンと衝突エンジンで、これらが全コードの90%以上を占めるからね。残念ながら、予想通りこのプロジェクトはOpenGL(ほとんどの実装はC++)とC++の衝突/物理エンジンを使ってるみたい。だから、このプロジェクトではClojureはC++の部分をオーケストレーションするための高レベルスクリプトとして使われているだけのようだね。後でゲームスクリプティングの話が出るかもしれないけど、シミュレーターにはRPGのように必要ではないと思う。ClojureはC++よりオーケストレーションには向いてるけど、関数型パラダイムに詳しいアート系の人はほとんど見たことがないから、これは美しい行き止まりに見える。改めて、これは本当に美しくて尊敬すべき成果だけど、ゲーム開発に関わる人たちはこんなアプローチを受け入れないと思う。

ゲーム開発者にとってもっと受け入れられる方法は何だったんだろう?

それこそが高レベルの関数型言語を使う本当の目的なんだよ。アプリのビジネスロジック、つまり大事なコードを表現するために使うんだ。レンダリングや物理の詳細が命令型言語で書かれていることは、ここではあまり重要じゃない。私が気にしてるのはアプリケーションのロジックを維持することで、Clojureのような言語はそれを簡単にしてくれるんだ。

コンピュータは状態を持ち、命令型だから、実際のコンピュータで動く関数型言語は状態を持つ命令型の基盤があるんだ。OpenGLを使うかどうかで関数型かどうかを判断するのはちょっと変な気がするな。

コードのフォーマットが他の人には効いてないの?