Tiny_RayTracing.ps を読んでみた。

Obfuscated PostScript Contest 1993 の BEST OBFUSCATED ARTWORK で 1 位に選ばれた早川たかし氏の Tiny_RayTracing.ps を読んでみました。
プログラムは http://web.mit.edu/PostScript/obfuscated-1993/Tiny_RayTracing.ps を見てください。

一通り前から順に読む

/A/copy/p/floor/q/gt/S/add/n/exch/i/index/J/ifelse/r/roll/w/div

で、名前と使用する命令の名前をスタックに積み上げています。

/H{{loop}stopped Y}def

で、スタックトップにある実行可能配列をエラーになるまでループを繰り返すという手続きに H と名前をつけています。Y は後で pop と定義されます。

/t/and/C/neg/T/dup/h/exp/Y/pop/d/mul/s/cvi/e/sqrt/R/rlineto

も、名前と使用する命令の名前をスタックに積み上げます。

{load def}H

で、{load def} をエラーになるまで実行します。スタック上にある命令の名前から命令をロードし、1 文字の名前をつけます。

300 T translate

スクリーン座標の原点を (300,300) に移動させます。T は dup です。

(V2L&1i2A00053r45hNvQXz&vUX&UOvQXzFJ!FJ!J!O&Y43d9rE3IaN96r63rvx2dcaN
G&140N7!U&4C577d7!z&&93r6IQO2Z4o3AQYaNlxS2w!!f&nY9wn7wpSps1t1S!D&cjS5o32rS4oS3o
Z&blxC1SdC9n5dh!I&3STinTinTinY!B&V0R0VRVC0R!N&3A3Axe1nwc!l&993dC99Cc96raN!a&1CD
E&YYY!F&&vGYx4oGbxSd0nq&3IGbxSGY4Ixwca3AlvvUkbQkdbGYx4ofwnw!&vlx2w13wSb8Z4wS!J!
c&みj1idjoみid42rd!みX&4I3Ax52r8Ia3A3Ax65rTdCS4iw5o5IxnwTTd32rCST0q&eCST0q&D1!&EYE0!J
&EYEY0!J0q!x&jd5o32rd4odSS!K&WCVW!Q&31C85d4!k&X&E9!&1!J!v&6A!b&7o!o&1r!j&43r!W)

処理部をエンコードした文字列です。

{( )T 0 4 3 r put T(/)q{T(9)q{cvn}{s}J}{($)q{[}{]}J}J cvx}forall

1 文字の命令を元の命令に直すと

{
    ( ) dup 0 4 3 roll put
    dup (/) gt{
        dup (9) gt{
            cvn
        }{
            cvi
        }ifelse
    }{
        ($) gt{
            [
        }{
            ]
        }ifelse
    }ifelse
    cvx
}forall

です。文字列を 1 文字ずつ読み、命令列を作ってスタックに置いていきます。数字は数に、アルファベットは名前に、'&' は命令列の始まりに、'!' と改行文字は命令列の終わりになります。
デコードすると以下の様になります。

V 2
L { 1 i 2 A 0 0 0 5 3 r 4 5 h N v Q X z { v U X { U O v Q X z F J } F J } J }
O { Y 4 3 d 9 r E 3 I a N 9 6 r 6 3 r v x 2 d c a N }
G { 1 4 0 N 7 }
U { 4 C 5 7 7 d 7 }
z { { 9 3 r 6 I Q O 2 Z 4 o 3 A Q Y a N l x S 2 w } }
f { n Y 9 w n 7 w p S p s 1 t 1 S }
D { c j S 5 o 3 2 r S 4 o S 3 o}
Z { b l x C 1 S d C 9 n 5 d h }
I { 3 S T i n T i n T i n Y }
B { V 0 R 0 V R V C 0 R }
N { 3 A 3 A x e 1 n w c }
l { 9 9 3 d C 9 9 C c 9 6 r a N }
a { 1 C D}
E { Y Y Y }
F { { v G Y x 4 o G b x S d 0 n q { 3 I G b x S G Y 4 I x w c a 3 A l v v U k b
Q k d b G Y x 4 o f w n w } { v l x 2 w 1 3 w S b 8 Z 4 w S } J } }
c { j 1 i d j 2 i d 4 2 r d }
X { 4 I 3 A x 5 2 r 8 I a 3 A 3 A x 6 5 r T d C S 4 i w 5 o 5 I x n w T T d 3 2
 r C S T 0 q { e C S T 0 q { D 1 } { E Y E 0 } J} { E Y E Y 0 } J 0 q }
x { j d 5 o 3 2 r d 4 o d S S }
K { W C V W }
Q { 3 1 C 8 5 d 4 }
k { X { E 9 } { 1 } J }
v { 6 A }
b { 7 o }
o { 1 r }
j { 4 3 r }
W

次の 270 で 270 がスタックに置かれます。

{def}H

でスタックに置かれた名前に数値や命令列を定義していきます。
最後に

K{K{L setgray moveto B fill}for Y}for showpage

レイトレーシングの計算と描画を行います。

エンコードされている命令の意味

単純な命令を元の名前に直し、コメントを加えてみました。

V 2   % V 刻みで描画
L {
    % y x => y x y B
    % スクリーン上の点 (x, y) の色 B を計算する
    1 index
    % 視点座標 (0,0,0) と
    % 視線方向((x,y,1024) 方向の単位ベクトル) の計算
    2 copy
    0 0 0
    5 3 roll 4 5 exp N
    % 視線方向上に球 Q があるか判定
    6 copy Q X
    {
        % 球 Q がある場合
        z
    } {
        % 球 Q がない場合
        % 視線方向上に球 U があるか判定
        6 copy U X
        {
            % 球 U がある場合
            % 球面での反射位置と反射方向計算
            U O
            % 反射方向上にした球 Q があるか判定
            6 copy Q X
            {
                % 球 Q がある場合球 Q 上の明るさ計算
                z
            } {
                % 球 Q がない場合床または空の明るさを計算
                F
            } ifelse 
        } {
            % 球 U がない場合床または空の明るさ計算
            F
        } ifelse 
    } ifelse 
}
O {
    % x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4 any => x3 y3 z3 nnx nny nnz
    % 反射光の計算
    % 点 (x1,y1,z1) から (x2,y2,z2) 方向に出た光が
    % 中心 (x4,y4,z4) の球上の点 (x3,y3,z3) で反射すると、
    % 点 (x3,y3,z3) から (nnx,nny,nnz) 方向に出た光になる
    %
    pop
    4 3 mul 9 roll E
    3 I a N
    9 6 roll 6 3 roll
    6 copy x
    2 mul c a N 
}
G {
    % => nx ny nz h
    % 地面を指定するパラメータ
    % 地面に対する法線ベクトル (nx,ny,nz) 
    % 点 (x,y,z) は nx*x+ny*y+ny*z+h=0 を満たすとき地面
    %
    % (nx,ny,nz)=(1/sqrt(17), 4/sqrt(17), 0)
    1 4 0 N
    % h=7
    7 
}
U {
    % => x y z r
    % 鏡面をもっている方の球の座標 (x,y,z) と半径 r
    4 neg 5 7 7 mul 7
}
z {
    % x1 y1 z1 z2 y2 z2 x3 y3 z3=> R
    %   球 Q 上の点 (x3,y3,z3) を (x2,y2,z2) 方向から見た時の明るさを求める
    %
    % 鏡面反射分の計算
    9 3 roll
    6 I Q O
    2 Z
    % ランバートの余弦則による明るさ計算
    4 1 roll 3 copy
    Q pop a N
    l
    x
    % 平均
    add 2 div 
}
f {
    % x y z => (int(floor(z/9+floor(x/7)))&1)+1
    % 床上の点 (x,y,z) の明るさが白の何分の一か
    exch pop
    9 div exch 7 div floor
    add floor cvi
    1 and 1 add 
}
D {
    % x1 y1 z1 x2 y2 z2 r => x1+x2*r y1+y2*r z1+z2*r
    % 三次元ベクトル演算 V1+V2*r
    %
    c
    4 3 roll add
    5 1 roll 3 2 roll add
    4 1 roll add
    3 1 roll
}
Z {
    % x1 y1 z1 x2 y2 z2 a => 9**(-5*a*(1-(x2*nx+y2*ny+z3*nz)))
    %   光源 (lx,ly,lz) から出て (x1,y1,z1) に来た光が (x2,y2,z2) 方向に
    %   反射される時の鏡面反射の計算
    %   a は光源の広がり具合のパラメータ? 大きいほど点に近い
    %
    %   (nx,ny,nz)=(lx-x1,ly-y1,lz-z1) 方向の単位ベクトル
    %
    7 1 roll l
    x
    neg 1 add mul neg
    9 exch 5 mul exp 
}
I {
    % any_n+3 any_n+2 any_n+1 ... any_2 any_1 n =>
    % any_n+3 any_n+2 any_n+1 ... any_2 any_1 any_n+3 any_n+2 any_n+1
    % スタックの n+1 から n+3 番目の要素をスタックのトップにコピー
    %
    3 add
    dup index exch
    dup index exch
    dup index exch
    pop 
}
B {
    % コの字に線を引く
    % 後で fill を実行すると、1辺 V の正方形を塗りつぶす
    V 0 rlineto
    0 V rlineto
    V neg 0 rlineto 
}
N {
    % x y z =>
    % x/sqrt(x*x+y*y+z*z) y/sqrt(x*x+y*y+z*z) z/sqrt(x*x+y*y+z*z)
    % 三次元単位ベクトルを計算
    %
    3 copy 3 copy x
    sqrt 1 exch div c 
}
l {
    % x1 y1 z1 x2 y2 z2 => x2 y2 z2 nx ny nz
    % 点 (x1,y1,z1) に対する光源 (lx, ly, lz) の方向の
    % 単位ベクトルを計算する
    %
    % 光源座標 (lx, ly, lz)=(-81 243 -81)
    9 9 3 mul neg 9 9 neg c
    % 単位ベクトル
    9 6 roll
    a N 
}
a {
    % x1 y1 z1 x2 y2 z2 => x1-x2 y1-y2 z1-z2
    % 3次元ベクトルの引き算
    %
    1 neg D
}
E {
    % any1 any2 any3 =>
    % スタックから 3 個取り出し捨てる
    %
    pop pop pop
}
F {
    % x1 y1 z1 x2 y2 z2 => Tf
    % 座標 (x1,y1,z1) から (x2,y2,z2) 方向の床または空を
    % 見たときの明るさ
    %
    % 視線方向が床の法線と同じ側を向いているか
    6 copy
    G pop x
    % 視点が床の法線側にあるか
    4 1 roll 
    G 7 1 roll
    x add mul
    0 exch gt {
        % 床を見ている場合
        % 見ている床の座標を計算
        3 I G
        7 1 roll
        x add
        G pop 4 I
        x div c
        a
        % 床が影になることによる明るさの変化
        3 copy l
        % 球 U の影で明るさが何分の一になるか
        6 copy 6 copy U k
        % 球 Q の影で明るさが何分の一になるか
        7 1 roll Q k
        mul
        % 床自体の明るさ
        % ランバートの余弦則
        7 1 roll G pop x
        % 床の模様の寄与
        4 1 roll f
        % 明るさ
        div exch div 
    } {
        % 空を見ている場合
        %
        % 視線方向と光源の方向の角度θのとき
        % cos(θ)/2+1/3
        6 copy l
        x
        2 div 1 3 div add
        % 視線方向に光源がある場合の明るさ
        7 1 roll
        8 Z
        4 div
        % 和
        add 
    } ifelse
}
c {
    % x y z r => x*r y*r z*r
    % 3次元ベクトルのスカラー倍
    %
    4 3 roll 1 index mul
    4 3 roll 2 index mul
    4 2 roll mul 
}
X {
    % x1 y1 z1 x2 y2 z2 x3 y3 z3 a => xs ys zs true
    % x1 y1 z1 x2 y2 z2 x3 y3 z3 a => false
    %  点 (x1,y1,z1) から (x2,y2,z2) の方向に出た直線が
    %  中心 (x3,y3,z3) 半径 a の球に交わる座標を計算する
    %  交わらないときは false
    %
    % X1=(x1,y1,z1), X2=(x2,y2,z2), X3=(x3,y3,z3) として
    % t の2次方程式 |X1+t*X2-X3|^2=a^2 を解く
    4 I 3 copy x
    5 2 roll
    8 I a
    3 copy 3 copy x
    6 5 roll
    dup mul neg add
    4 index div
    5 1 roll
    5 I x
    exch div
    dup dup mul
    3 2 roll
    neg add
    % 判別式の符号で分岐
    dup 0 gt {
        % 直線と球が交点を持つ場合
        sqrt neg add
        dup 0 gt {
            % 2 次方程式の解の小さい方が正の場合
            % (x2,y2,z2) 方向上に球がある
            %
            % X1+t*X2
            D 1 
        } {
            % 2 次方程式の解の小さい方が負の場合
            % (x1,y1,z1) が球の内部であるか
            % (x2,y2,z2) 方向の逆側に球がある
            E pop E 0 
        } ifelse
    } {
        % 直線と球が交点を持たない場合
        E pop E pop 0 
    } ifelse
    0 gt 
}
x {
    % x1 y1 z1 x2 y2 z2 => x1*x2+y2*y1+z2*z1
    % 3次元ベクトルの内積
    %
    4 3 roll mul
    5 1 roll 3 2 roll mul
    4 1 roll mul
    add add 
}
K {
    % => -W V W
    % 描画のループの上限、加減とステップ
    W neg V W 
}
Q {
    % => x y z r
    % 黒い球の座標 (x,y,z) と半径 r
    3 1 neg 8 5 mul 4
}
k {
    % x1 y1 z1 x2 y2 z2 x3 y3 z3 a => 1 or 9
    %  座標 (x1,y1,z1) から (x2,y2,z2) の方向に出た光が
    %  中心 (x3,y3,z3) 半径 a の球に遮られるなら 9
    %  遮られないときは 1
    X
    {
        E 9
    } {
        1
    } ifelse 
}
v {
    % a1 a2 a3 a4 a5 a6 =>
    % a1 a2 a3 a4 a5 a6 a1 a2 a3 a4 a5 a6
    % スタックの上から 6 個をコピーしてスタックに置く
    %
    6 copy
}
b {
    % a_7 ... a_1 n => a_1 a_7 ... a_2 
    % スタックの上から 7 個を回転させ、a_2 をスタックトップにする
    %
    7 o
}
o {
    % a_n ... a_1 n => a_1 a_n ... a_2 
    % スタックの上から n 個を回転させ、a_2 をスタックトップにする
    %
    1 roll
}
j {
    % a_4 ... a_1 n => a_3 ... a_1 a_4
    % スタックの上から 4 個を回転させ、a_4 をスタックトップにする
    %
    4 3 roll
}
% 描画範囲
% 縦横 -W から W までの領域を描画する
W 270