引用元:https://www.ioccc.org/2012/blakely/blakely.c
審査員・作者による説明:https://www.ioccc.org/2012/blakely/hint.html
動作
逆ポーランド記法で書いた式をコマンドライン引数に与えると、その式を3次元の曲面としてレンダリングした図をアニメGIFとして出力する。
$ gcc -o blakely blakely.c -lm
$ ./blakely "xx*yy*+" 64 > paraboloid.gif
第2引数は画像のサイズ。次のコマンドは20分くらいかかった。
$ ./blakely xy* 250 > saddle.gif
次のコマンドは1時間以上。
$ ./blakely xx*yy*1++d5*ct/ 250 > ripple.gif
解説
IOCCCでは比較的おなじみのネタ(逆ポーランド記法、アニメGIF生成、レイトレーシング)を1つにまとめたというネタ。
式の指定で使えるのは、四則演算、指数関数、sin/cos、定数、dup、swap、x座標、y座標。
3つの座標がすべて-1から1に入っている範囲だけが描画される。
コード形状は、非常に荒く横長なドットで、”x” “y” “z”を表現している。
賞名の”GIFted”は、gifted(天賦の才のある)とGIFをかけている。
いくつか作者からの読者への挑戦的な質問がある。
逆ポーランド記法のところを除いて、sin()
とcos()
を使っていない。どうやって視点を回転しているか?
次は、フレームごとに実行されるコードをまともな見た目にしたもの。
N = S;
E_ = 1 - J * J / 2;
T = J * (1 - J * J / 6);
S = S * E_ + C * T;
C = C * E_ - N * T;
A = -(C * C - S * S);
P = -A * C;
E = A * S;
G = S * C * 2;
I = -G * S;
F = G * C;
J
は (3.1415 / フレーム数 * 2) という値。
ポイントは最初の5行程度。
E_ = 1 - J * J / 2
はcos(J)
の近似式。同様にT
はsin(J)
の近似式。どちらもテイラー展開で、Jが0に近いとき、高精度の近似となる。
S = S * E_ + C * T
はsin()
の加法定理で、角度をJ
だけ増やすことになる。同様にC
の定義はcos()
の加法定理。
このようにして、フレームごとに角度をJ
ずつ変えている。
アニメーションは何回繰り返すか?
フレーム数のことなら、サイズに指定した回数だけ生成するもよう。
解像度の上限が250x250なのはどこから来ているか?
これは無圧縮GIFの出力方法に依存した微妙な話だと思う。
ちゃんとは確認していないのでざっくり説明する。
256色のGIFは初期状態で258要素のテーブル(256個の色コードと、2つの制御コード)を持っていて、1ピクセル出力するごとにこのテーブルは長くなる。
このテーブル長が511未満ならば1ピクセル出力するのに8ビット、つまりちょうど1バイトで表現できるのだが、このしきい値を超えると桁上りして9ビット必要になり、ビットストリームを扱う必要が起きてしまう。
そこで、しきい値を超える前に「クリアコード」という制御コードを出力することで、伸びたテーブルを縮める。
これが無圧縮GIFを出力する基本技法。
おそらくクリアコードを1行表示し終えたら出力するようにしているので、横幅がこれを超えるとビットのずれが生じてしまうのだろう。
詳細はGIFのファイルフォーマットや、その圧縮アルゴリズムのLZWを調べてほしい。
b[0]
のポイントはなにか?
これはよくわからない。
b[0]
とb[1]
にはそれぞれ画像サイズ(16ビット)の上位バイトと下位バイトが入っているが、前述の通り250以下なので、b[0]
はつねに0となる。
そして、char
型の変数Z
に対してb[0] = Z >> 9
で初期化しているように見えるので、未定義動作になっているような気もする。
ソースの中に隠された”NETSCAPE”と”GIF”の語はどこにあるか?
上記の、cos()
とsin()
を近似計算しているコード。代入されている変数名が順にNETSCAPEGIF
になっている。