quil/quil-site

Beating cardioid example. I hope you like it.

Closed this issue · 5 comments

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

(defn rotate-x [theta x y]
  (- (* x (Math/cos theta))
     (* y (Math/sin theta))))

(defn rotate-y [theta x y]
  (+ (* x (Math/sin theta))
      (* y (Math/cos theta))))

(defn cardioid [s t]
  (let [x (* s (- (* 2 (Math/cos t)) (Math/cos (* 2 t))))
         y (* s (- (* 2 (Math/sin t)) (Math/sin (* 2 t))))
        theta (- (/ (.-PI js/Math) 2))
         j (rotate-x theta x y)
        k (rotate-y theta x y)]
    [j k]))

(defn setup []
  (q/frame-rate 2)
  {:points (mapv (partial cardioid (/ (q/width) 6)) (range 0 10 0.001))})

(defn draw [state]
  (q/background 0)
  (q/fill 255 0 0)
  (q/with-translation [(/ (q/width) 2) (/ (q/height) 3)]
    (q/begin-shape)
    (doseq [p (:points state)]
      (apply q/curve-vertex p))
    (q/end-shape)))

(def scaling-fn (atom *))

(defn change [state]
 (letfn [(scale [s f [x y]]
            [(s x f)
             (s y f)])
          (select [f]
            (condp = f
              * /
              / *))]
    (swap! scaling-fn select)
    (update state :points #(map (partial scale @scaling-fn 0.95) %))))

(defn init []
  (q/sketch
   :setup setup
   :draw draw
   :update change
   :host "heart"
   :size [200 200]
   :middleware [m/fun-mode]))

I like the idea, though I think we could make a few improvements to make it more interesting. I'd suggest few additions:

  1. Make beating gradual: for example going from small contracted to expanded takes several frames, the same expanded => contracted. You can even experiment with different speed for expansion and contraction so that it looks like it beats (expands) fast and then slowly contracts.
  2. Use more detailed parametric function to draw heart. For example take a look at these: http://mathworld.wolfram.com/HeartCurve.html.

Also it would be interesting to try changing some coefficients of the heart function on fly so that it changes form and not only size. I don't know, it might end up pretty ugly, but I'd try.

What do you think?

I hadn't have time to work on this last week, I changed the cardioid function to a prettier function called heart. Draw was updated accordingly and I implemented the more natural progressive engorging and deflating of a beating heart. I saw a video of open heart surgery and it looks like the rate of contraction, is the same as distention not faster. I'd rather not play with the constants, I tried but continuous deformation bends the curve beyond recognition.

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

(defn rotate-x [theta x y]
  (- (* x (Math/cos theta))
     (* y (Math/sin theta))))

(defn rotate-y [theta x y]
  (+ (* x (Math/sin theta))
     (* y (Math/cos theta))))

#_(this is the nicer cardioid curve)
(defn heart [s t]
  (let [x (* s (* 16 (Math/pow (Math/sin t) 3)))
        y (* s (- (* 13 (Math/cos t))
                  (* 5 (Math/cos (* 2 t)))
                  (* 2 (Math/cos (* 3 t)))
                  (Math/cos (* 4 t))))
        theta  (.-PI js/Math)
        j (rotate-x theta x y)
        k (rotate-y theta x y)]
    [j k]))

(declare contraction)
(defn setup []
  (q/frame-rate 30)
  {:points (mapv (partial heart (/ (q/height) 40)) (range 0 10 0.1))
   :scaling-function contraction
   :counter 7})

(defn draw [state]
  (q/background 0)
  (q/fill 255 0 0)
  (q/with-translation [(/ (q/width) 2) (/ (q/height) 2)]
    (q/begin-shape)
    (doseq [p (:points state)]
      (apply q/curve-vertex p))
    (q/end-shape)))

(defn contraction [[x y]]
  [(* x 0.99)
   (* y 0.99)])

(defn distention [[x y]]
  [(/ x 0.99)
   (/ y 0.99)])

(defn select [f]
  (condp = f
    contraction distention
    distention contraction))

(defn beat [state]
  (if (zero? (:counter state))
    (-> state
        (update :counter (fn [] 7))
        (update :scaling-function #(select %)))
    (-> state
        (update :counter #(dec %))
        (update :points #(mapv (:scaling-function state) %)))))

(defn init []
  (q/sketch
   :setup setup
   :draw draw
   :update beat
   :host "heart"
   :size [200 200]
   :middleware [m/fun-mode]))

Added example but didn't push live yet. I did some changes to your code to make it simple (I think). I replaced rotate by PI with (- y). Also replaced select function with a map that acts as function. The sketch is here: heart.cljs. If you're fine with current version - I'll publish it live.

I like it, the map change is particularly nice. If you're just going to push the changes, I'm closing the issue, also thanks for the feedback.

Deployed: http://quil.info/?example=heart. Thanks for contributing!