In this part of our series about transforming imperative Processing code into functional Clojure code we are deconstructing a ‘real’ loop for the first time. The code to be discussed is taken from Matt Pearson’s book ‘Generative Art‘, and we are using Quil for accessing the power of Processing in Clojure. Our focus is less on understanding the math behind the images drawn but on the different approach when it comes to functional and data-driven programming.
Last time the Processing code used the implicit loop hidden behind Processing’s draw method. We solved this with a loop/recur construct and obtained the same result. This time we are turning the screw a bit more with a while loop.
This is what we want:
Well, that’s at least a bit more interesting than this lonely circle in the last part. Drawing concentric circles can be done with this Processing code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
int diam = 10; float centX, centY; void setup() { size(500, 300); frameRate(24); smooth(); background(180); centX = width/2; centY = height/2; stroke(0); strokeWeight(5); fill(255, 50); } void draw() { if (diam <= 400) { background(180); strokeWeight(5); fill(255, 50); ellipse(centX, centY, diam, diam); strokeWeight(0); noFill(); int tempdiam = diam; // loop within loop while (tempdiam > 10) { ellipse(centX, centY, tempdiam, tempdiam); tempdiam -= 10; } diam += 10; // increase diam for next loop } }  |
The draw() method paints a circle, followed by further circles within that outer one. Then the canvas is cleared and the whole procedure repeats until the diameter has reached 400. This is not an overly elegant solution, and an improved version will follow immediately, but we are not talking about efficiency at this time. Let us focus on the draw() method once more, especially its inner while loop. Here additional circles are drawn with decreasing diameters starting at size diam.
This is my solution in Clojure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
(ns for-the-glory-of-art (:require [quil.core :refer :all])) (defn setup [] (smooth) (frame-rate 24) (background 180) (stroke 0) (stroke-weight 5) (fill 255 50)) (defn draw [] (let [cent-x (/ (width) 2) cent-y (/ (height) 2)] (loop [diam 10] (when (<= diam 400) (background 180) (stroke-weight 5) (fill 255 50) (ellipse cent-x cent-y diam diam) (stroke-weight 0) (no-fill) (loop [tempdiam diam] (when (> tempdiam 10) (ellipse cent-x cent-y tempdiam tempdiam) (recur (- tempdiam 10)))) (recur (+ diam 10)))))) (defsketch firstone :title "2.3" :size [500 300] :setup setup :draw draw) (defn -main [&args]) |
We already have seen most of the initialization work in defsketch and setup; the bindings for the symbols cent-x and cent-y have been moved to draw, because we need them there.
Most of draw’s part is built like we did last time: we are repeatedly drawing a circle until it reaches a diameter of 400 pixels. This is done in the outer loop that is repeatedly targeted by recur, binding diam to a value increased by 10. The when condition breaks as soon as diam is reaching a value of 400. The inner loop first binds a symbol tempdiam to the recent value of diam, checks for tempdiam’s size and possibly draws a circle. Then we are entering the inner loop: this one also expects a parameter tempdiam, starting with the value of diam. It is repeatedly evaluated with a decreased (by 10) tempdiam while tempdiam is greater than 10.
But drawing concentric cirlces can be done a lot easier. Have a look at this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
int diam = 10; float centX, centY; void setup() { size(500, 300); frameRate(24); smooth(); background(180); centX = width/2; centY = height/2; stroke(0); strokeWeight(1); noFill(); // or fill(255,25); } void draw() { if (diam <= 400) {  ellipse(centX, centY, diam, diam); diam += 10; } } |
In our first example we were drawing a circle, then its inner circles, then repeating this until the diameter reaches 400 pixels. In this second improved version we are just drawing circles by increasing the diameter by 10. This omits the permanent cleaning and redrawing of the image at each step. Here is the appropriate Clojure code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
(ns for-the-glory-of-art (:require [quil.core :refer :all])) (defn setup [] (smooth) (frame-rate 24) (background 180) (stroke 0) (stroke-weight 0.5) (no-fill)) ; or (fill 255 25)) (defn draw [] (let [cent-x (/ (width) 2) cent-y (/ (height) 2)] (loop [diam 10] (when (<= diam 400) (ellipse cent-x cent-y diam diam) (recur (+ diam 10)))))) (defsketch firstone :title "2.3.2" :size [500 300] :setup setup :draw draw) (defn -main [&args]) |
This code is much simpler and looks a lot like the code we discussed last time. The only thing missing is the erasing of the background in draw. This way each new circle is simply drawn above all others, resulting in the same figure. When you replace the (no-fill) with (fill 255 25) in the last line of setup, then you are getting this result: