Instantly share code, notes, and snippets.

Embed
What would you like to do?
A subtle variation of Jack Frigaard's _Bézier Clock_ implemented in ClojureScript. Rather than just rendering the current time, this separates the value generation from interpolating the Bézier control points that go up to make the digits using communicating sequential processes, as supplied by _Big-bang_ and _core.async_ channels. The generator…
(ns enchilada.bezier-numbers
(:require
[jayq.core :refer [show]]
[cljs.core.async :as async]
[dommy.core :refer [insert-after! set-attr! show!]]
[big-bang.core :refer [big-bang]]
[big-bang.components :refer [dropdown]]
[enchilada :refer [canvas ctx canvas-size value-of]]
[enchilada.bezier-numbers.lerp :as lerp]
[enchilada.bezier-numbers.generator :as gen])
(:require-macros
[dommy.macros :refer [sel1 node]]))
(defn get-digit [n]
(let [divisor (Math/pow 10 n)]
(fn [x]
(mod (quot x divisor) 10))))
(defn zip [& colls]
(apply map list colls))
(defn make-dropdown [chan]
(node
[:div
(dropdown
:id :type
:label-text "Generator:"
:initial-value (value-of :type "rng")
:options gen/options
:send-channel chan)]))
(defn start []
(let [updates-chan (async/chan)
broadcast-chan (async/chan)
notify-mult (async/mult broadcast-chan)
notifos (fn [] (let [c (async/chan)] (async/tap notify-mult c) c))]
(insert-after!
(make-dropdown updates-chan)
(-> (sel1 :#canvas-area) (set-attr! :height 200)))
(show canvas)
;;; INTERPOLATOR:
; these big-bangs are responsible for rendering tween digits - they listen
; for new values, but filter the value according to the specific digits
; they are configured to listen for
(doseq [[n x] (zip (range 5 -1 -1) (range 0 1680 (:w lerp/digit-bounds)))]
(big-bang
:initial-state (lerp/initial-state x (get-digit n))
:on-tick lerp/update-state
:receive-channel (notifos)
:on-receive lerp/create-new-interpolator
:to-draw lerp/render))
;;; GENERATOR:
; this big-bang generates values, and broadcasts them to the INTERPOLATOR's
(big-bang
:initial-state (gen/initial-state (value-of :type :rng))
:on-tick gen/update-state
:tick-rate 1000
:send-channel broadcast-chan
:receive-channel updates-chan
:on-receive (fn [event world-state] (gen/initial-state (event :type))))))
(start)
(ns enchilada.bezier-numbers.generator
(:require
[big-bang.package :refer [make-package]]))
(defn gen-primes [] ; http://stackoverflow.com/a/7625207/260541
(let [reinsert (fn [table x prime] (update-in table [(+ prime x)] conj prime))]
(defn primes-step [table d]
(if-let [factors (get table d)]
(recur (reduce #(reinsert %1 d %2) (dissoc table d) factors) (inc d))
(lazy-seq (cons d (primes-step (assoc table (* d d) (list d)) (inc d))))))
(primes-step {} 2)))
(defn current-time []
(let [d (js/Date.)]
(reduce + (map * [(.getHours d) (.getMinutes d) (.getSeconds d)] [10000 100 1]))))
(def options {
"clock" "Clock"
"rng" "Random numbers"
"int" "Integers"
"prime" "Primes"})
(def generators
{:clock (repeatedly current-time)
:rng (repeatedly #(rand-int 999999))
:int (iterate inc 0)
:prime (gen-primes)})
(defn initial-state [generator-type]
{:type (keyword generator-type)
:values (generators (keyword generator-type))})
(defn update-state [event {:keys [values] :as world-state}]
(make-package
(update-in world-state [:values] next)
{:value (first values)}))
(ns enchilada.bezier-numbers.lerp
(:require
[enchilada :refer [canvas ctx canvas-size]]
[monet.canvas :refer [clear-rect fill-style fill fill-rect circle
save restore scale translate
stroke-style stroke-width stroke-cap stroke-join stroke
begin-path move-to line-to bezier-curve-to]]))
(def digit-bounds
(let [[w h] (canvas-size)]
{:x 80 :y 0 :w 310 :h 500}))
(def digits [
; <-start-> <-control-points--> <--end-->
[[254 47] [159 84] [123 158] [131 258] ; 0
[139 358] [167 445] [256 446]
[345 447] [369 349] [369 275]
[369 201] [365 81] [231 75]]
[[138 180] [226 99] [230 58] [243 43] ; 1
[256 28] [252 100] [253 167]
[254 234] [254 194] [255 303]
[256 412] [254 361] [255 424]]
[[104 111] [152 55] [208 26] [271 50] ; 2
[334 74] [360 159] [336 241]
[312 323] [136 454] [120 405]
[104 356] [327 393] [373 414]]
[[ 96 132] [113 14] [267 17] [311 107] ; 3
[355 197] [190 285] [182 250]
[174 215] [396 273] [338 388]
[280 503] [110 445] [ 93 391]]
[[374 244] [249 230] [192 234] [131 239] ; 4
[70 244] [142 138] [192 84]
[242 30] [283 -30] [260 108]
[237 246] [246 435] [247 438]]
[[340 52] [226 42] [153 44] [144 61] ; 5
[135 78] [145 203] [152 223]
[159 243] [351 165] [361 302]
[371 439] [262 452] [147 409]]
[[301 26] [191 104] [160 224] [149 296] ; 6
[138 368] [163 451] [242 458]
[321 465] [367 402] [348 321]
[329 240] [220 243] [168 285]]
[[108 52] [168 34] [245 42] [312 38] ; 7
[379 34] [305 145] [294 166]
[283 187] [243 267] [231 295]
[219 323] [200 388] [198 452]]
[[243 242] [336 184] [353 52] [240 43] ; 8
[127 34] [143 215] [225 247]
[307 279] [403 427] [248 432]
[ 93 437] [124 304] [217 255]]
[[322 105] [323 6] [171 33] [151 85] ; 9
[131 137] [161 184] [219 190]
[277 196] [346 149] [322 122]
[298 95] [297 365] [297 448]]])
(defn interpolate-line [from to n]
(let [from (vec from)
size (count from)
step (fn [f] (fn [a b] (double (/ (- (f b) (f a)) n))))
delta (mapv (juxt (step first) (step second)) from to)]
(fn [idx t]
(when (and (< idx size) (<= t n))
(let [[dx dy] (nth delta idx)
[x y] (nth from idx)]
[(+ x (* dx t)) (+ y (* dy t))])))))
(defn initial-state [x-offset digit-fn]
{ :scale 0.4
:x-offset x-offset
:digit-fn digit-fn
:draw-control-points? true
:interpolator (constantly nil)})
(defn update-state [event world-state]
(update-in world-state [:t] inc))
(defn create-new-interpolator [event {:keys [digit-fn] :as world-state}]
(let [curr (get event :value)
prev (get world-state :value 0)
f (comp digits digit-fn)]
(assoc world-state
:interpolator (interpolate-line (f prev) (f curr) 40)
:value curr
:t 0)))
(defn draw-bezier-curves [ctx interpolator t]
(let [[x y] (interpolator 0 t)]
(->
ctx
(begin-path)
(move-to x y)))
(loop [i 1]
(when (< i 11)
(let [[cp1x cp1y] (interpolator i t)
[cp2x cp2y] (interpolator (+ i 1) t)
[x y] (interpolator (+ i 2) t)]
(bezier-curve-to ctx cp1x cp1y cp2x cp2y x y)
(recur (+ i 3)))))
(stroke ctx))
(defn render [{:keys [interpolator t] :as world-state}]
(when (interpolator 0 t)
(->
ctx
(save)
(scale (world-state :scale) (world-state :scale))
(translate (world-state :x-offset) 0)
;(fill-style :red)
;(fill-rect digit-bounds)
(clear-rect digit-bounds)
(stroke-style :black)
(stroke-width 10)
(stroke-join :miter)
(draw-bezier-curves interpolator t)
(restore))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment