ITエンジニア勉強ブログ

自分が学んだことを共有していきます。

"Decoding A City In A Bottle"を読んで

2022年4月に以下のツイートが一部界隈で話題になったようです。筆者は今更知りました。

このコードは、以下のようなビル群っぽい風景をレイトレーシングした動画を生成するGenerative Artです。

https://pbs.twimg.com/card_img/1551148159510138881/p0yvSRho?format=jpg&name=small より

元ツイートには実際の動画が貼り付けられていますので、ぜひ見てみてください。

有志の方々の解析

何人かの方が、読みやすく書き直したコードを紹介しています。

observablehq.com

fukuno.jig.jp

zenn.dev


大まかにまとめると、A City In A Bottleは以下の処理を行なっているようです。

  • setIntervalで連続して描画処理を呼び出す。呼び出し毎にカメラ位置を右にずらす。
    • 一重ループでカメラの各ピクセル方向ごとにレイを飛ばす。ループ変数を除算と剰余で二次元の方向に分解することで、一重ループで二次元の走査を実現している。
      • 一重ループでレイ=直線の媒介変数を少しずつ動かす。動かす毎に建物との衝突検知を判定する。
        • 建物に衝突していたら窓枠っぽい模様を塗る。

なお、座標系は左手系です。すなわち左右がx、高さがy、奥行きがzです。

筆者による解析(?)

レイの衝突判定に記述されている建物の形状を固定カメラで描画してみました。

以下は"Decoding A City In A Bottle"の解析の一部コードを少し改変したものです。

const GROUND_PLANE = 6;
const CITY_DISTANCE = 32;
const AVENUE_WIDTH = 27;
const AVENUE_PERIOD = 99;
const BUILDING_WIDTH = 9;
const BUILDING_DEPTH = 8;
const BUILDING_HEIGHT = 45;

function buildingHeight(X, Z) {
  return (CITY_DISTANCE<Z&AVENUE_WIDTH<X%AVENUE_PERIOD&&X/BUILDING_WIDTH^Z/BUILDING_DEPTH)*8%(BUILDING_HEIGHT+1);
}

ビルの高度マップ 3Dバージョン

x-z毎に高さがbuildingHeightのキューブを並べて描画するThree.jsのコードです。


See the Pen
Decoding "A City In A Bottle", 3D version
by Imai (@imai1)
on CodePen.

ビルの高度マップ 2Dバージョン

x-z毎にbuildingHeightで色分けしてキャンバスにfillRectで色付けしたものです。


See the Pen
Decoding "A City In A Bottle", 2D version
by Imai (@imai1)
on CodePen.

ビルの高度マップ ランダムバージョン

以下のように、Math.randを使って生成した通常の乱数でも似たような結果になるようです。

const buffer = new Array(30);
for (let z = 0; z < buffer.length; ++z) {
  buffer[z] = new Array(buffer.length);
}

function getBuffer(x, z) {
  const X = Math.floor(x / BUILDING_WIDTH);
  const Z = Math.floor(z / BUILDING_DEPTH);
  if (buffer[Z][X] == undefined) {
    buffer[Z][X] = (Math.random() - 0.1) * 10000;
  }
  return buffer[Z][X];
}

function buildingHeight(X, Z) {
  return (CITY_DISTANCE<Z&AVENUE_WIDTH<X%AVENUE_PERIOD&&getBuffer(X, Z))*8%(BUILDING_HEIGHT+1);
}


See the Pen
Untitled
by Imai (@imai1)
on CodePen.

反対に言えば、多次元座標の各成分のXORをとると簡易的な乱数になるようですね。周期は短いのかもしれませんが、このような用途では便利そうです。

グローバルイルミネーションを99行のC++

余談ですが、以下の画像を生成する99行のC++というのもあります。

https://www.kevinbeason.com/smallpt/result640.jpg より

www.kevinbeason.com