Another Quil sketch

Draft of 2016.08.18

Continuing from yesterday afternoon, I spent a bit more time working in ClojureScript and Quil just now, replicating a little thing from 1994 or so. It’s still not interactive, but it’s finally sinking in that working in ClojureScript can really be a lot like writing Clojure.

(ns leaves.core
  (:require [quil.core :as q :include-macros true]
            [quil.middleware :as m]))

(defn random-angle
  "returns a random number of radians, to initialize a circular vector"
  (rand 6.28318))

(defrecord Particle [x y vx vy speed-limit])

(defn lots-of-particles
  "produce a collection of randomly positioned leaves"
  (repeatedly how-many #(->Particle (rand-int 500)
                                    (rand-int 500)
                                    (rand-int 10)
                                    (rand-int 10)
                                    (+ 3 (rand)))))

(defn lots-of-forces
  "produce a collection of force vectors in random orientations"
  (repeatedly how-many #(random-angle)))

(defn setup
  "set up globals, construct initial force field and leaf pile"
  (q/frame-rate 30)
  (q/color-mode :hsb)
  {:vectors (lots-of-forces 25)
   :particles (lots-of-particles 1000)

(defn grid-position
  "returns the force field index of a leaf's position; each force field block is 100 pixels on a side, and they're numbered sequentially from the upper left corner"
  (let [lx (:x leaf)
        ly (:y leaf)
        posn (+ (* 5 (int (/ ly 100))) (int (/ lx 100)))]

(defn slow-speed
  "apply a braking force to leaves moving faster than their speed limits"
  [v limit]
  (if (pos? (- (js/Math.abs v) limit))
    (* v 0.9)

(defn adjust-leaf
  "apply the appropriate force to a leaf, given its position, changing its position and velocity"
  [leaf force-angle]
  (let [force-x (q/cos force-angle)
        force-y (q/sin force-angle)
        old-x  (:x leaf)
        old-y  (:y leaf)
        old-vx (:vx leaf)
        old-vy (:vy leaf)
        limit  (:speed-limit leaf)]
    (->Particle (mod (+ old-x old-vx) 500)
                (mod (+ old-y old-vy) 500)
                (slow-speed (+ old-vx force-x) limit)
                (slow-speed (+ old-vy force-y) limit)

(defn vary-force
  "rotate a force field value by a small random angle"
  (+ f (/ (rand) 100)))

(defn update-state
  "change all the forces randomly, and update all the leaves"
  (let [forces (:vectors state)]
    {:vectors    (map #(vary-force %) forces)
     :particles  (map #(adjust-leaf 
                       (nth forces (grid-position %))) (:particles state))

(defn draw-leaf
  "draw a little circle where a single leaf is"
  (q/ellipse (:x leaf) (:y leaf) 2 2))

(defn draw-state
  "clear the background, and draw all the leaves"
  (q/background 220)
  (q/fill 200 100 0)
  (doall (map draw-leaf (:particles state))))

(q/defsketch leaves
  :host "leaves"
  :features [:no-start]
  :size [500 500]
  :setup setup
  :update update-state
  :draw draw-state
  :middleware [m/fun-mode])