quil/quil-site

Lissajous Table

ampersanda opened this issue · 8 comments

http://quil.info/sketches/local/be3b9156d926ba1b15fdc7bfd39d2fad257cf2d0f02b79c24206300662efe8d1

(ns lissajous.core
  (:require [quil.core :as q]
            [quil.middleware :as m]))

(def circle-width 62.5)
(def rules (atom {:columns 0
                  :rows    0
                  :curves  nil}))

(defn setup []
  (q/frame-rate 60)
  (q/color-mode :hsb)

  (let [cols (dec (/ (q/width) circle-width))
        rows (dec (/ (q/height) circle-width))]

    (swap! rules assoc :columns cols)
    (swap! rules assoc :rows rows)
    (swap! rules assoc :curves (vec (repeat cols (vec (repeat rows {:x nil :y nil :points []}))))))

  {:angle 0})

(defn update-state [{:keys [angle]}]
  {:angle (+ angle 0.04)})

(defn create-circles-and-guides [angle mode]
  (let [hcw (/ circle-width 2)]
    (doseq [i (range (mode @rules))]
      (let [def-cx (+ (* i circle-width) hcw circle-width)
            cx (cond (= :columns mode) def-cx
                     (= :rows mode) hcw)

            cy (cond (= :columns mode) hcw
                     (= :rows mode) def-cx)

            diameter (- circle-width 10)
            r (/ diameter 2)

            angle-speed (* angle (inc i))
            angle-set-to-zero (- angle-speed q/HALF-PI)

            x (* r (q/cos angle-set-to-zero))
            y (* r (q/sin angle-set-to-zero))

            point-x (+ cx x)
            point-y (+ cy y)

            create-circle (fn []
                            (q/stroke-weight 1)
                            (q/ellipse cx cy diameter diameter))

            create-point (fn []
                           (q/stroke-weight 8)
                           (q/point point-x point-y))

            create-line (fn []
                          (q/stroke-weight 1)
                          (cond (= :columns mode) (q/line point-x 0 point-x (q/height))
                                (= :rows mode) (q/line 0 point-y (q/width) point-y)))]


        (create-circle)
        (create-point)
        (create-line)

        (cond
          (= :columns mode) (doseq [j (range (:rows @rules))] (swap! rules assoc-in [:curves j i :x] point-x))
          (= :rows mode) (doseq [j (range (:columns @rules))] (swap! rules assoc-in [:curves i j :y] point-y)))))))

(defn draw-state [{:keys [angle]}]
  (q/background 0)

  (q/no-fill)
  (q/stroke 255)

  (create-circles-and-guides angle :columns)
  (create-circles-and-guides angle :rows)

  (doseq [r (range (:rows @rules))
          c (range (:columns @rules))]
    (let [draw-lissajous (fn [points]
                           (q/stroke 255)
                           (q/stroke-weight 1)
                           (q/no-fill)
                           (q/begin-shape)

                           (doseq [x (range (count points))]
                             (let [p (nth points x)
                                   posx (:x p)
                                   posy (:y p)]
                               (q/vertex posx posy)))

                           (q/end-shape))

          current (nth (nth (:curves @rules) r) c)]
      (swap! rules update-in [:curves r c :points] conj {:x (:x current) :y (:y current)})
      (draw-lissajous (:points current)))))


(q/defsketch lissajous
             :title "Ampersanda - Lissajous"
             :size [500 500]
             :setup setup
             :update update-state
             :draw draw-state
             :features [:keep-on-top]
             :middleware [m/fun-mode])

Thanks, that looks cool! But I'm concerned a little by performance: when I run this sketch is starts very fast but as time goes on it slows down. I suspect there is some kind of memory leak. Looking at the code you do a lot of changes to rules atom inside draw. Could you explain what those updates do? I feel there is an opportunity to optimize them.

Looked at the code closer. Seems like you are adding a new point to all curves on each invocation of draw so it's keep growing indefinitely, right? You don't really need to store all the points because they start repeating after some time. Maybe you can calculate that for current [i, j] circle you "completed" the loop and no longer need to add new points?

Oh okay, I'll revised them. Thank you for the suggestion 🙃.
I think I should just subvec the first point which I added to the atom before redraw it after the angle is more than TWO-PI

Thanks for the quick fix! The updated version feels much faster.

One last thing. The sketch will be shown in size 200x200 so only 2 circles will be displayed and the first 2 circles (2x2) are the most basic. What if instead of using the first 2 circles we randomly selected angle speeds for them? That way each time you refresh small 200x200 sketch you'll get different patterns. Though I'm not sure how to nicely handle bigger case like 500x500 where you current implementation looks neat. Maybe something like number of rows/cols less than certain N (e.g. 4) then angle speed is selected randomly.

What do you think?

Thanks Mochamad! Added the example: http://quil.info/?example=lissajous%20table. I made some changes to the sketch: removed fun-mode as seems like you are updating most of state through atoms anyway and angle can be calculated based on frame-count. Updated function that generates speed to produce unique speeds and be compatible with 500x500. Also added a few comments.

http://quil.info/sketches/show/example_lissajous-table

Thank you for the code refactoring (I still figure out how to code better in Clojure) and comments at the sketch codes. I forgot to add explanation about what I code.