ClojureとQuilでClifford Attractor
昔のProcessing Advent Calendarをいろいろ漁っていたらこんな記事があった:
上記の記事の冒頭の絵は
で表されるアトラクターである。
以下の参考サイトにはもっと例が載っている。
まさにClojureのiterate
関数の使いどころという趣があるので、Quilで書いて並べてみた:
コードを簡単に説明する。
a, b, c, d
のパラメータを受け取り、新たに無名関数を返す高階関数clifford
。無名関数lはアトラクターの計算式を表している:
(defn clifford [[a b c d]] (fn [[x y]] [(+ (-> y (* a) q/sin) (-> x (* a) q/cos (* c))) (+ (-> x (* b) q/sin) (-> y (* b) q/cos (* d)))]))
その高階関数に9つの違うパラメータを渡して、9つの違うアトラクター関数を作成する:
(def clifs (vec (map clifford [[-1.4 1.6 1.0 0.7] [1.6 -0.6 -1.2 1.6] [1.7 1.7 0.6 1.2] [1.5 -1.8 1.6 0.9] [-1.7 1.3 -0.1 -1.2] [-1.7 1.8 -1.9 -0.4] [-1.8 -2.0 -0.5 -0.9] [-1.4 -1.6 -1.0 -0.7] [1.8 -1.5 0.4 0.9]])))
各フレームでのアップデートでは、最後に描画した点の座標をアトラクター関数に渡し、その結果をまた同じアトラクター関数に渡し、と200回繰り返して出来た200個の座標をvector
として保持する、というのを各アトラクターごとに(つまり9回)行う。
update-state
で9つの関数と9つのvector
をupdate-series
にmap
し、update-series
内でアトラクターごとに新しい座標200点を算出する:
(defn update-series [f vs] (->> vs last (iterate f) (take 200) vec)) (defn update-state [state] (vec (map update-series clifs state)))
ここでiterate
を使っている。(iterate f x)
は(f x) (f (f x)) (f (f (f x))) ...
の無限リストなのでClifford Attractorを表現するには完璧。
あとは9つの座標リストを少しずつずらして描画するだけ:
(defn draw-state [state] (doseq [[i vs] (map-indexed vector state)] (q/with-translation [(-> i (mod 3) (* 200) (+ 150)) (-> i (/ 3) int (* 200) (+ 150))] (doseq [[x y] vs] (q/point (* 40 x) (* 40 y))))))
けっこう数字をいじっても面白いパターンが現れやすいので楽しい。