summaryrefslogtreecommitdiff
path: root/static/src/assets/viz/2/viz/core.cljs
diff options
context:
space:
mode:
Diffstat (limited to 'static/src/assets/viz/2/viz/core.cljs')
-rw-r--r--static/src/assets/viz/2/viz/core.cljs264
1 files changed, 264 insertions, 0 deletions
diff --git a/static/src/assets/viz/2/viz/core.cljs b/static/src/assets/viz/2/viz/core.cljs
new file mode 100644
index 0000000..caeb386
--- /dev/null
+++ b/static/src/assets/viz/2/viz/core.cljs
@@ -0,0 +1,264 @@
+(ns viz.core
+ (:require [quil.core :as q]
+ [quil.middleware :as m]
+ [viz.forest :as forest]
+ [viz.grid :as grid]
+ [viz.ghost :as ghost]
+ [viz.dial :as dial]
+ [goog.string :as gstring]
+ [goog.string.format]
+ ))
+
+(defn- debug [& args]
+ (.log js/console (clojure.string/join " " (map str args))))
+(defn- observe [v] (debug v) v)
+
+(defn- positive [n] (if (> 0 n) (- n) n))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; initialization
+
+(defn- window-partial [k]
+ (int (aget js/document "documentElement" k)))
+
+(def window-size
+ (let [w (int (min 1024 (window-partial "clientWidth")))]
+ [w (int (min (* w 0.75) (window-partial "clientHeight")))]))
+
+(def window-half-size (apply vector (map #(float (/ %1 2)) window-size)))
+
+(defn- set-grid-size [state]
+ (let [h (int (* (window-size 1)
+ (float (/ (:grid-width state) (window-size 0)))))]
+ (assoc state :grid-size [(:grid-width state) h])))
+
+(defn- add-ghost [state ghost-def]
+ (let [[forest id] (forest/add-node (:forest state) (:start-pos ghost-def))
+ ghost (-> (ghost/new-ghost)
+ (ghost/add-active-node id)
+ (assoc :ghost-def ghost-def))
+ ]
+ (assoc state
+ :forest forest
+ :ghosts (cons ghost (:ghosts state)))))
+
+(defn- new-state []
+ (-> {:frame-rate 15
+ :color-cycle-period 8
+ :tail-length 7
+ :frame 0
+ :grid-width 45 ; from the center
+ :forest (forest/new-forest grid/isometric)
+ }
+ (set-grid-size)
+ (add-ghost {:start-pos [-10 -10]
+ :color-fn (fn [state]
+ (let [frames-per-color-cycle
+ (* (:color-cycle-period state) (:frame-rate state))]
+ (q/color
+ (/ (mod (:frame state) frames-per-color-cycle)
+ frames-per-color-cycle)
+ 1 1)))
+ })
+ ))
+
+(defn setup []
+ (q/color-mode :hsb 1 1 1)
+ (new-state))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; scaling and unit conversion related
+
+(defn- curr-second [state]
+ (float (/ (:frame state) (:frame-rate state))))
+
+(defn- scale [grid-size xy]
+ (map-indexed #(* %2 (float (/ (window-half-size %1)
+ (grid-size %1)))) xy))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; poss-fn
+
+(def bounds-buffer 1)
+
+(defn- in-bounds? [grid-size pos]
+ (let [[w h] (apply vector (map #(- % bounds-buffer) grid-size))]
+ (every?
+ #(and (>= (% 1) (- (% 0))) (<= (% 1) (% 0)))
+ (map vector [w h] pos))))
+
+(defn- dist-from-sqr [pos1 pos2]
+ (reduce + (map #(* % %) (map - pos1 pos2))))
+
+(defn- dist-from [pos1 pos2]
+ (q/sqrt (dist-from-sqr pos1 pos2)))
+
+(defn take-adj-poss [grid-width pos adj-poss]
+ (let [dist-from-center (dist-from [0 0] pos)
+ width grid-width
+ dist-ratio (/ (- width dist-from-center) width)
+ ]
+ (take
+ (int (* (q/map-range (rand) 0 1 0.75 1)
+ dist-ratio
+ (count adj-poss)))
+ adj-poss)))
+
+(defn- mk-poss-fn [state]
+ (let [grid-size (:grid-size state)]
+ (fn [pos adj-poss]
+ (->> adj-poss
+ (filter #(in-bounds? grid-size %))
+ (sort-by #(dist-from-sqr % [0 0]))
+ (take-adj-poss (grid-size 0) pos)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; update
+
+(defn- update-ghost-forest [state update-fn]
+ (let [[ghosts forest]
+ (reduce (fn [[ghosts forest] ghost]
+ (let [[ghost forest] (update-fn ghost forest)]
+ [(cons ghost ghosts) forest]))
+ [nil (:forest state)]
+ (:ghosts state))]
+ (assoc state :ghosts (reverse ghosts) :forest forest)))
+
+(defn- ghost-incr [state poss-fn]
+ (update-ghost-forest state #(ghost/incr %1 %2 poss-fn)))
+
+(defn rm-nodes [state node-ids]
+ (update-ghost-forest state (fn [ghost forest]
+ [(reduce ghost/rm-active-node ghost node-ids)
+ (reduce forest/remove-node forest node-ids)])))
+
+(defn- maybe-remove-roots [state]
+ (if (>= (:tail-length state) (:frame state))
+ state
+ (rm-nodes state (map :id (forest/roots (:forest state))))))
+
+(defn- ghost-set-color [state]
+ (update-ghost-forest state (fn [ghost forest]
+ (let [color ((get-in ghost [:ghost-def :color-fn]) state)]
+ [(assoc ghost :color color) forest]))))
+
+(defn update-state [state]
+ (let [poss-fn (mk-poss-fn state)]
+ (-> state
+ (ghost-set-color)
+ (ghost-incr poss-fn)
+ (maybe-remove-roots)
+ (update-in [:frame] inc)
+ )))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; draw
+
+(defn- draw-ellipse [pos size scale-fn] ; size is [w h]
+ (let [scaled-pos (scale-fn pos)
+ scaled-size (map int (scale-fn size))]
+ (apply q/ellipse (concat scaled-pos scaled-size))))
+
+(defn- in-line? [& nodes]
+ (apply = (map #(apply map - %1)
+ (partition 2 1 (map :pos nodes)))))
+
+(defn- draw-node [node active? scale-fn]
+ (let [pos (:pos node)
+ stroke (get-in node [:meta :color])
+ fill (if active? stroke 0xFFFFFFFF)
+ ]
+ (q/stroke stroke)
+ (q/fill fill)
+ (draw-ellipse pos [0.30 0.30] scale-fn)))
+
+(defn- draw-line [node parent scale-fn]
+ (let [node-color (get-in node [:meta :color])
+ parent-color (get-in node [:meta :color])
+ color (q/lerp-color node-color parent-color 0.5)
+ ]
+ (q/stroke color)
+ (q/stroke-weight 1)
+ (apply q/line (map scale-fn (map :pos (list parent node))))))
+
+(defn- draw-lines [forest parent node scale-fn]
+ "Draws the lines of all children leading from the node, recursively"
+ (let [children (map #(forest/get-node forest %) (:child-ids node))]
+
+ (if-not parent
+ (doseq [child children] (draw-lines forest node child scale-fn))
+ (let [in-line-child (some #(if (in-line? parent node %) %) children)
+ ]
+ (doseq [child children]
+ (if (and in-line-child (= in-line-child child))
+ (draw-lines forest parent child scale-fn)
+ (draw-lines forest node child scale-fn)))
+ (when-not in-line-child
+ (draw-line node parent scale-fn))
+ ))
+
+ ; we also take the opportunity to draw the leaves
+ (when (empty? children)
+ (draw-node node false scale-fn))
+ ))
+
+(defn draw-dial [state dial posL posR]
+ (let [dial-norm (q/norm (:val dial) (:min dial) (:max dial))
+ dial-pos (map #(q/lerp %1 %2 dial-norm) posL posR)]
+ (q/stroke 0xFF000000)
+ (q/stroke-weight 1)
+ (q/fill 0xFF000000)
+ (apply q/line (concat posL posR))
+ (apply q/ellipse (concat dial-pos [5 5]))
+ ))
+
+(defn draw-state [state]
+ ; Clear the sketch by filling it with light-grey color.
+ (q/background 0xFFFFFFFF)
+ (q/with-translation window-half-size
+
+ (let [grid-size (:grid-size state)
+ scale-fn #(scale grid-size %)
+ ghost (:ghost state)
+ forest (:forest state)
+ roots (forest/roots forest)
+ ]
+
+ (doseq [root roots]
+ (draw-lines forest nil root scale-fn))
+
+ (doseq [ghost (:ghosts state)]
+ (doseq [active-node (map #(forest/get-node forest %)
+ (:active-node-ids ghost))]
+ (draw-node active-node true scale-fn)))
+
+ ))
+
+ ;(draw-dial state (:dial state) [30 30] [100 30])
+
+ ;(q/text (clojure.string/join
+ ; "\n"
+ ; (list
+ ; (gstring/format "frame:%d" (:frame state))
+ ; (gstring/format "second:%f" (curr-second state))
+ ; (gstring/format "spawn-chance:%d" (spawn-chance state))))
+ ; 30 30)
+ )
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; def
+
+(q/defsketch viz
+ :title ""
+ :host "viz"
+ :size window-size
+ ; setup function called only once, during sketch initialization.
+ :setup setup
+ ; update-state is called on each iteration before draw-state.
+ :update update-state
+ :draw draw-state
+ :features [:keep-on-top]
+ ; This sketch uses functional-mode middleware.
+ ; Check quil wiki for more info about middlewares and particularly
+ ; fun-mode.
+ :middleware [m/fun-mode])