@d_etteiu8383です。BlenderのGeometryNodesでBrainf*ckを実行したので、BlenderのGeometryNodesでBrainf*ckを実行する話をします。
Blenderとは
Blender is the free and open source 3D creation suite.
https://www.blender.org/about/
無料の3DCG制作ソフトウェアです。
いろいろできるので3DCG制作以外のために使用している人もいます。
GeometryNodesとは
ノードベースの操作でオブジェクトのジオメトリを変更する
https://docs.blender.org/manual/ja/dev/modeling/geometry_nodes/introduction.html
ためのシステムです。
いろいろできるのでジオメトリの変更以外のために使用している人もいます。
僕はというと、シューティングゲームを作ったりしていました。
shooting game with blender geometry nodes simulation zone...#Blender #b3d #GeometryNodes pic.twitter.com/57YZDCqCgs
— eyemono.moe / でって (@eyemono_moe) June 28, 2023
Brainf*ckとは
Brainf*ckは、1993年にUrban Müllerによって作られたプログラミング言語です。いわゆる難解プログラミング言語の一つとして知られており、その命令セットはたった8つの文字から成り立っています。それぞれの文字は、メモリの操作やデータの変更などの機能を持っています。
命令 | 動作 |
---|---|
> |
ポインタをインクリメントする |
< |
ポインタをデクリメントする |
+ |
ポインタが指す値をインクリメントする |
- |
ポインタが指す値をデクリメントする |
. |
ポインタが指す値を出力に書き出す |
, |
入力から1バイト読み込んで、ポインタが指す先に代入する |
[ |
ポインタが指す値が0なら、対応する] の直後にジャンプする |
] |
ポインタが指す値が0でないなら、対応する[ の直後にジャンプする |
引用: https://ja.wikipedia.org/wiki/Brainfuck#Brainfuckの言語仕様 2023/07/11
これらの命令セットを組み合わせることで、プログラムを作成します。
例: ++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
(引用: https://esolangs.org/wiki/Brainfuck#Examples 2023/07/11)
上の例では、Hello World!
が出力されます。
仕様がシンプルであることから、様々な場面で実装に挑戦する人が生まれています。
初代DOOMみたいなアレです。
この記事の目標
BlenderのGeometryNodesを用い、Brainf*ckのインタプリタぽいものを作る。
Why?
Why not?
できるので、やります。単純にGeometryNodesの使い方を勉強したかったのもあります(そろそろShaderNodes芸人辞めてGeometryNodes芸を身につけたかった)。
戦略
Blender 3.6 LTSから正式実装されたSimulation Nodesを用いることで簡単に実装できそうです。
なお、今回の実装では最適化などは行わず、仕様通りの動作を素直にノードで再現していきました。Blenderのアニメーション再生時に、(約)1命令/1フレームのペースで命令を実行することとします。
Simulation Nodes
Simulation is defined by the “Simulation Zone”, connecting the Simulation Input and Output.
SIM IN
On the first frame, the inputs of the Simulation Input node are evaluated.
In later frames the inputs aren’t evaluated anymore, the node outputs the result of the previous frame.SIM OUT
The Simulation Output node saves the state for the next frame.
https://www.blender.org/download/releases/3-6/
Simulation Nodesは、シミュレーションの入力と出力を定義するノードです。
Simulation Inputノードへの入力は、最初のフレームでのみ評価され、以降のフレームでは前のフレームの結果が出力されます。Simulation Outputノードは、次のフレームのために状態を保存します(保存しつつ出力もする)。
例えば上記画像のようにノードを組むと、以下のように動作します。
- 1フレーム目:
- "シミュレーション入力"ノードの
数
ソケットへの入力が評価される- ここでは"値"ノードからの
10.0
が評価される
- ここでは"値"ノードからの
- "シミュレーション入力"ノードの
数
ソケットの出力に、1.0を加算する- 10.0 + 1.0 = 11.0
- "シミュレーション出力"ノードの
数
ソケットに11.0が保存される - "シミュレーション出力"ノードの
数
ソケットから11.0が出力される
- "シミュレーション入力"ノードの
- 2フレーム目:
- "シミュレーション入力"ノードの
数
ソケットの出力は、前のフレームで保存された11.0になる - "シミュレーション入力"ノードの
数
ソケットの出力に、1.0を加算する- 11.0 + 1.0 = 12.0
- "シミュレーション出力"ノードの
数
ソケットに12.0が保存される - "シミュレーション出力"ノードの
数
ソケットから12.0が出力される
- "シミュレーション入力"ノードの
- ...
今回の実装では、上記の動作を利用してフレーム間でのメモリ状態の保存を行います。
メモリの再現
Brainf*ckのインタプリタ実装にあたって最低限必要な要素は以下の通りです。
- メモリ
- メモリを指すポインタ
- 入出力ストリーム
これらに加え、ループ処理等で必要な情報も含めて、全ての情報をジオメトリの属性として保存します。
今回は、事前に用意したPointの属性を利用して、メモリ等を再現します。下記画像は、今回実装したBrainf*ckのインタプリタの動作後のPointの属性です。数百個のPointを用意し、各点を配列の要素のように扱っています。
実装
(実装の説明いる????)実装方針だけ示します。最終的に作ったblendファイルを以下に置いておきます。自由に動かして遊んでみてください。WTFPLで公開します。Brainf*ckだし。
メモリやポインタ等の情報を、Buffer
ソケットにつないでいるジオメトリに属性として保存し、このジオメトリをシミュレーションゾーン内で毎フレーム更新しています。
Bufferには以下の属性を持たせています。
- Memory: メモリ
- Pointer: ポインタ
- CommandIndex: 現在のフレームに対応する命令のインデックス
- LoopPosition: ループの開始位置の保存先
- Output: 出力
- Nest: ループのスキップ処理に使用するネストの深さ
GetCommand
グループで、入力プログラム文字列から現在のフレームに対応する命令を取得しています。
この命令に応じて、pointerCommand
やMemoryCommand
グループ内で、Bufferの属性を更新しています。
画像では省略していますが、Bufferの更新をスキップすれば命令の実行も一時停止できるので、Nフレーム毎の実行等も可能です。
入力ストリームはきれいな実装が思いつかなかったので今回は実装していません。誰も怒らないと思います。
動かしてみる
愚直に実装したおかげで"1フレーム1命令の実行"となっているため、メモリやポインタ、命令位置をいい感じに可視化するといい感じになります。
命令を読み取るロボットっぽい娘やメモリっぽいものを作り、属性として保存している情報を使用してジオメトリを追加・移動することで、以下のようなアニメーションを作ってみました。
シミュレーションゾーンでBrainfuckのインタプリタ実装#Blender #b3d #GeometryNodes pic.twitter.com/norqVmdiR6
— eyemono.moe / でって (@eyemono_moe) July 6, 2023
なお、文字コードから文字列への変換には、(少しずるいですが)以下のようなノードを用いています。
いかがでしたか?
Pythonでスクリプトを書いたほうが良い