Quilでランダムな線を引く
ジェネラティブ・アート Processingによる実践ガイドという本を買ってみた。
Processingでコンピュータを使って図形にランダム性を持たせるとすごく複雑かつ美しい結果が出る(かもしれない)という話。
まずは基礎中の基礎である、ランダム性を持たせた線を描くところから始めてみたい。
個人的にはランダムと言いつつ再現性は非常に重要で、コードを走らせれば簡単に同じ絵を描けるようにしておきたい。なのでとりあえず自分でシードしたJavaクラスが入っているランダム関数を定義。
(def r (java.util.Random. 0)) (defn rand [n] (.nextInt r n))
シードを0に設定している。これでコードを何回走らせても同じ結果が出る。違う結果を見たければシードを変えればいい。しかし乱数というのは本質的に非関数型の典型な気がするので扱いが微妙である。引数も戻り値もかぶっていない関数ふたつの順番を入れ替えると結果が変わる、というのは参照透明性の破壊もいいところだ。
あだしごとはさておき。
設定の定数:
(def n 30) (def width 310) (def height 100)
nがデータポイントの数。
次にヨコ軸の間隔とXの値のシーケンスを定義:
(def x-increment (/ (- width 10) (dec n))) (def X (map #(* % x-increment) (range n)))
描線関数:
(defn line [xs ys] (doall (map q/line xs ys (rest xs) (rest ys))))
まずはY軸には0の値を常に出力することで直線を描いてみる。
(defn setup [] (q/frame-rate 5) (q/background 255 255 255)) (defn draw [] (q/translate 5 (/ height 2)) (line X (repeat 0))) (q/defsketch random-lines :size [width height] :setup setup :draw draw)
次にsin波:
(defn sin [x] (-> x Math/toRadians Math/sin (* 20))) (defn draw [] (q/translate 5 (/ height 2)) (line X (map sin X)))
そしてついに乱数の登場:
(def Y (repeatedly #(-> (rand 20) (- 10)))) (defn draw [] (q/translate 5 (/ height 2)) (line X Y))
これは単に各点において-10~10の間のランダムな数字をとっているだけで、過去の値は全く考慮に入れていない。
Sn = Y1 + Y2 + ... Yn
というランダムウォークにしてみる:
(def Y (repeatedly #(-> (rand 20) (- 10)))) (defn draw [] (q/translate 5 (/ height 2)) (line X (reductions + Y)))
reductionsで簡単にできるのが嬉しい。少し見切れているのは悲しい。
先ほどのsinで周期性を加えてみる。
(defn draw [] (q/translate 5 (/ height 2)) (line X (map + (map sin X) (reductions + Y)))
もうちょっと長めの周期の方が良かったかもしれない。
設定をいじってみる。
(def n 300) (def width 510) (def height 300)
それっぽくなった。ランダムウォークに周期性を足した図となる。ちなみにこのシーケンスでも最初の30のデータポイントはさっきと全く同じ数字になるのがシードしている恩恵である。
(追記:よく見たら完全に間違っている。というか周期が増えていない。(map sin X)
でやっているからだ。(map (comp sin #(* 10 %)) (range))
にしておけばよかった。まあこの書き方はダサいけど。XがそもそもX軸の間隔を内包した定義になっているんだから、それをY軸の引数に使っちゃいけないよね、という話)
clojureのmapやreductionsなどで関数的にデータをいじってから簡単に表示できるのはかなり気持ちがいい。「ジェネラティブ・アート」ではこの乱数要素をさらに円などの形に組み入れていくようなので、どんどん試していきたい。