Sketching on the canvas with quil

For those using a RSS reader, subscribe here: rss.xml

There is a certain joy in creative programming that is unique to it. And the best thing is that it does not have to be a game that you are building. It also applies to making a sketch or animation, which can be done with the same canvas tools. Usually I use Processing or P5.js to make these kinds of prototypes, but recently I found Quil. It is based on Processing, and supports creating these canvas applications in Clojure and ClojureScript with the same familiar API.

Small polygon example in Quil

The library quil comes with a project template, which is setup conveniently that it automatically runs the application when you load the code. I chose to write it in Clojure, and generated the project with `lein new quil my-sketch`. It comes with a default animation of an animated circle, which reminded me of a polygon animation.

The animations are quite similar, and the existing color shifting the template has is a nice touch that we will keep. The original P5.js sketch will also be included at the end for reference. The first extension is to include the radius and amount of sides to the state:

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

  {:color 0
   :angle 0
   :radius 50
   :sides 5})

The polygon animation demonstrates interactivity, and iterates the amount of sides. The minimum is a triangle, and the maximum looks like a circle. For simplicity the mouse input will be used to decrease and increase the amount of sides of the polygon. The radius will remain constant, and the angle will keep increasing to make the polygon rotate. The existing color shifting logic is also kept, which wraps around the hue value of the color.

(defn update-state [state]
  {:color (mod (+ (:color state) 0.7) 255)
   :angle (+ (:angle state) 0.1)
   :radius 50
   :sides (if (q/mouse-pressed?)
            (if (= :left (q/mouse-button))
              (max (dec (:sides state)) 3)
              (min (inc (:sides state)) 20))
            (:sides state))})

Drawing the polygon is a piece of cake if you draw it as a shape. That means that the lines are connected, and you only have to provide the next coordinates for the lines. Those coordinates are just rotations around a point with the correct radius.

(def full-radians (* 2 math/PI))

(defn draw-shapes [state]
  (q/begin-shape)
  (doseq [i (range 0 (:sides state))]
    (let [angle (+ (* (/ full-radians (:sides state)) i) (:angle state))
          radius (:radius state)
          x (* 150 (q/cos angle))
          y (* 150 (q/sin angle))]
      (q/vertex x y)))
  (q/end-shape :close))

(defn draw-state [state]
  (q/background 40)
; (q/angle-mode :degrees) only for cljs, so we use radians
  (q/fill (:color state) 255 255)
  (q/with-translation [(/ (q/width) 2)
                       (/ (q/height) 2)]
    (draw-shapes state)))

Small polygon example in P5.js

const bgColor = 40;

let sliderDOM;

let polygon;

class Polygon {
  constructor(x, y, r, color = 255) {
    this.x = x;
    this.y = y;
    this.r = r;
    this.color = color;
  }

  draw(sides, rotate) {
    this.rotationDeg = frameCount;

    fill(this.color);
    angleMode(DEGREES);

    beginShape();
    for (let i = 0; i < sides; i++) {
      let x = this.x + (this.r * cos(360 / sides * i + this.rotationDeg));
      let y = this.y + (this.r * sin(360 / sides * i + this.rotationDeg));
      vertex(x, y);
    }
    endShape(CLOSE);
  }
}

function setup() {
  createCanvas(500, 500);
  background(bgColor);

  sliderDOM = document.getElementById('range_sides');

  polygon = new Polygon(width / 2, height / 2, 120);
}

function draw() {
  background(bgColor);

  let sides = sliderDOM.value;

  polygon.draw(sides, doRotation);
}