Trouble getting started with Quil - NullPointerException

This is my first real program with Clojure (and with Quil). If you’re not aware, Quil is a set of Clojure bindings to the Processing framework.

This is my program so far:

(ns quiltest.core
  (:require [quil.core :as q]
            [quil.middleware :as m]
            [clojure.inspector :as n]))

(def width 500)
(def height 500)
(def ncurves 25)
(def curve-length 100)
(def curve-step 1)
(def noise-scale 0.001)

(defn plot-curve
  [pts]
  (doseq [endpoints (partition 2 1 (first pts) pts)]
    (let [[p1 p2] endpoints
          [x1 y1] p1
          [x2 y2] p2]
     (apply q/line [x1 y1 x2 y2]))))

(defn flow-step
  [{:keys [x y] :as all}]
  (let [noise (get all :noise q/noise)
        step (get all :step curve-step)
        scale (get all :scale noise-scale)
        angle (q/map-range (noise (* scale x) (* scale y)) 0 1 0 q/TWO-PI)]
    [(* step (q/cos angle)) (* step (q/sin angle))]))

(defn flow-line
  [x y & opts]
  (let [noise (get opts :noise q/noise)
        step (get opts :step curve-step)
        scale (get opts :scale noise-scale)]
    (iterate flow-step {:x x :y y :noise noise :step step :scale scale})))

(defn ->xy
  [{:keys [x y]}]
  [x y])

(defn setup []
  ; Set frame rate to 30 frames per second.
  (q/frame-rate 1)
  ; Set color mode to HSB (HSV) instead of default RGB.
  (q/color-mode :hsb)
  ; setup function returns initial state. It contains
  ; circle color and position.
  {:color 0
   :angle 0})

(defn draw-state [state]
  ; Clear the sketch by filling it with light-grey color.
  (q/background 240)
  (q/stroke-weight 10)
  (q/stroke 255 0 0)
  (doseq
    [[x-start y-start] (repeatedly ncurves (fn [] [(rand-int width) (rand-int height)]))]
    (->>
      (take curve-length (flow-line x-start y-start))
      (map ->xy)
      (plot-curve)
      )))

(q/defsketch quiltest
  :title "You spin my circle right round"
  :size [width height]
  ; setup function called only once, during sketch initialization.
  :setup setup
  ; update-state is called on each iteration before draw-state.
  :draw draw-state
  :features [:keep-on-top]
  ; This sketch uses functional-mode middleware.
  ; Check quil wiki for more info about middlewares and particularly
  ; fun-mode.
  :middleware [m/fun-mode])

The basic process looks like this:

  • Select a point in the window.
  • Select a value from the noise function and interpret it as an angle.
  • Take a step from the starting point in the direction of the angle.
  • Repeat until you have a curve.
  • Plot the curve.
  • Repeat with another starting point.

The error I’m getting is below:

Exception in  :draw  function:  #error {
 :cause nil
 :via
 [{:type java.lang.NullPointerException
   :message nil
   :at [clojure.lang.RT floatCast RT.java 1319]}]
 :trace
 [[clojure.lang.RT floatCast RT.java 1319]
  [quil.core$line invokeStatic core.cljc 2344]
  [quil.core$line invoke core.cljc 2328]
  [clojure.lang.AFn applyToHelper AFn.java 165]
  [clojure.lang.AFn applyTo AFn.java 144]
  [clojure.core$apply invokeStatic core.clj 665]
  [clojure.core$apply invoke core.clj 660]
  [quiltest.core$plot_curve invokeStatic core.clj 19]
  [quiltest.core$plot_curve invoke core.clj 13]
  [quiltest.core$draw_state invokeStatic core.clj 76]
  [quiltest.core$draw_state invoke core.clj 66]
  [clojure.lang.Var invoke Var.java 384]
  [quil.middlewares.fun_mode$wrap_draw_update$quil_draw__2077 invoke fun_mode.cljc 16]
  [quil.middlewares.safe_fns$wrap_fn$fn__260 invoke safe_fns.clj 8]
  [quil.middlewares.bind_output$bind_output$iter__298__302$fn__303$fn__318 invoke bind_output.clj 21]
  [quil.applet$_draw invokeStatic applet.clj 217]
  [quil.applet$_draw invoke applet.clj 215]
  [quil.Applet draw nil -1]
  [processing.core.PApplet handleDraw PApplet.java 2475]
  [quil.Applet handleDraw nil -1]
  [processing.awt.PSurfaceAWT$12 callDraw PSurfaceAWT.java 1547]
  [processing.core.PSurfaceNone$AnimationThread run PSurfaceNone.java 313]]} 
stacktrace:  java.lang.NullPointerException: null
 at clojure.lang.RT.floatCast (RT.java:1319)
    quil.core$line.invokeStatic (core.cljc:2344)
    quil.core$line.invoke (core.cljc:2328)
    clojure.lang.AFn.applyToHelper (AFn.java:165)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:665)
    clojure.core$apply.invoke (core.clj:660)
    quiltest.core$plot_curve.invokeStatic (core.clj:19)
    quiltest.core$plot_curve.invoke (core.clj:13)
    quiltest.core$draw_state.invokeStatic (core.clj:76)
    quiltest.core$draw_state.invoke (core.clj:66)
    clojure.lang.Var.invoke (Var.java:384)
    quil.middlewares.fun_mode$wrap_draw_update$quil_draw__2077.invoke (fun_mode.cljc:16)
    quil.middlewares.safe_fns$wrap_fn$fn__260.invoke (safe_fns.clj:8)
    quil.middlewares.bind_output$bind_output$iter__298__302$fn__303$fn__318.invoke (bind_output.clj:21)
    quil.applet$_draw.invokeStatic (applet.clj:217)
    quil.applet$_draw.invoke (applet.clj:215)
    quil.Applet.draw (:-1)
    processing.core.PApplet.handleDraw (PApplet.java:2475)
    quil.Applet.handleDraw (:-1)
    processing.awt.PSurfaceAWT$12.callDraw (PSurfaceAWT.java:1547)
    processing.core.PSurfaceNone$AnimationThread.run (PSurfaceNone.java:313)

That’s a hefty error message, but I gather from it that there’s an error converting something to a floating point number?

Another problem I’m having is that if I supply a dummy noise function (say #(* 0 %1 %2)) to flow-line it still picks up q/noise. I haven’t been able to figure out why that is.

one problem is that your function flow-step returns a vector instead of the map iterate expects. the next time that’s fed into iterate, which is basically doing (flow-step (flow-step (flow-step init))) for say 3 iterations, you get a null pointer error since :x and :y don’t exist in the vector as keys.

user> (let [{:keys [x y]} [1 2]] x)
nil

Destructuring on maps uses get which is permissive (it can work polymorphically on most things…)

user> (get 2 :a)
nil

Also note that you can’t test flow-step or flow-line outside of the sketch, since noise will produce a null pointer since there’s not current sketch…

If you update the iterate call to

 (iterate (fn [m]
               (let [[x y]  (flow-step m)]
                 (assoc m :x x :y y)))
             {:x x :y y :noise noise :step step :scale scale}))

it works with the rest of your functions. I think you really want to “add” the results of the step though:

(defn flow-line
  [x y & opts]
  (let [noise (get opts :noise q/noise)
        step (get opts :step curve-step)
        scale (get opts :scale noise-scale)]
    (iterate (fn [m]
               (let [[dx dy]  (flow-step m)]
                 (-> m
                     (update :x + dx)
                     (update :y + dy))))
             {:x x :y y :noise noise :step step :scale scale})))

Once this is sorted, you get additional problems in plot-curve since your invocation of partition appears to be off. The pad collection you supply (a 2-element vector) ends up yielding a single element (an integer, which throws an error due to destructuring [x y] from it). You actually want to pad by the point, so

(partition 2 1 [(first pts)] pts)

works.

You can also simplify the flow-line/flow-step stuff. Since you’re only using the noise/scale/step options 1x, you can combine them into a simple flow-line:

(defn flow-line
  [x y & {:keys [noise step scale]
          :or {noise q/noise step curve-step scale noise-scale}
          :as opts}]
  (iterate (fn [[x y]]
             (let [angle   (q/map-range (noise (* scale x) (* scale y)) 0 1 0 q/TWO-PI)
                   [dx dy] [(* step (q/cos angle)) (* step (q/sin angle))]]
               [(+ x dx) (+ y dy)])) [x y]))

This returns [x y] pairs directly, which means you can drop ->xy from the pipeline in draw-state (no need to unpack them).

You caught a few of my typos :sweat_smile: I meant for flow-step to return the new point, not just the [dx dy], I just forgot to add dx and dy to the original positions.

The program works now, but I’ve left flow-step and flow-line separate because flow-step is really a building block for more complicated things (this is also why I keep maps around instead of 2-vectors).

One piece I’m confused by:

I don’t understand how this is any different from what I’m currently doing, especially if I pass a dummy noise function as mentioned in my original post.

Because flow-step returns a function, if we think in terms of types, of

state → [x y], where state :: {:keys [x y noise scale step]}

Iterate expects a function of state → state, but you supplied flow-step, which is state → [x y] .
So on the second iteration, the result from yours was an [x y] vector pair (what flow-step yields), which is then fed as input to flow-step again. This fails because flow-step expects a {:keys [x y noise scale step]} map, and tries to destructure :x and :y from the map, and failes, because it’s not a map, it’s a vector. Vectors actually obey hashmap semantics and can be viewed as a map of integer keys to values, so get/assoc will work if there’s an integer key. Since you are trying to get a keyword like :x from the vector, and get is somewhat liberal in its contract (returns nil if the key cannot be gotten, regardless of input type), the destructured map ends up being {:x nil :y nil}, and you provided defaults for the other scale/step/noise keys. Still, x and y are bound to nil, which pops a null pointer error once math is involved.

All I did was ensure that the values being passed to/from iterate by way of its function argument are consistent. I pass it a function that wraps flow-step, uses flow-step to compute the dx dy as per the original, but then I package it back into the input map so we have {:x (+ x dy) :y (+ y dy) …} as input for the next iteration, instead of the vector [x y]. The second iteration on my implementation then works, because flow-step gets a map as it expects, with non-nil x and y keys.

This is a minimal reproduction of what you did:

;;fails with null pointer error since we try to add nil...
(let [next-item (fn [{:keys [x]}] (inc x))] 
  (->> (iterate next-item {:x 0}) (take 5) (map :x) (reduce +)))

;;succeeds
(let [next-item (fn [{:keys [x]}] (inc x))] 
   (->> (iterate (fn [{:keys [x] :as m}] {:x (next-item m)}) {:x 0}) (take 5) (map :x) (reduce +)))

;;also succeeds
(let [next-item (fn [{:keys [x]}] {:x (inc x)})] 
   (->> (iterate next-item {:x 0}) (take 5) (map :x) (reduce +)))

I wasn’t specific and there’s been a miscommunication. the part I was confused by was regarding q/noise.

This is what my current flow-step and flow-line look like:

(defn flow-step
  [{:keys [x y] :as all}]
  (let [noise (get all :noise q/noise)
        step (get all :step curve-step)
        scale (get all :scale noise-scale)
        angle (q/map-range (noise (* scale x) (* scale y)) 0 1 0 q/TWO-PI)]
    (-> all
        (update :x + (* step (q/cos angle)))
        (update :y + (* step (q/sin angle))))))

(defn flow-line
  [x y & opts]
  (iterate flow-step (assoc opts :x x :y y)))

What version of clojure are you running? There were some changes in 1.11 (pending) toward keyword arg processing that may be in play.

Under clojure 1.10.3, I have to destructure the args to indicate intent to collect them as a map:

(defn flow-line
  [x y & {:as opts}]
  (iterate flow-step (assoc opts :x x :y y)))

From there, I can use the dummy function:

(defn draw-state [state]
  ; Clear the sketch by filling it with light-grey color.
  (q/background 240)
  (q/stroke-weight 10)
  (q/stroke 255 0 0)
  (doseq
    [[x-start y-start] (repeatedly ncurves (fn [] [(rand-int width) (rand-int height)]))]
    (->>
     (take curve-length (flow-line x-start y-start :noise #(* 0 %1 %2)))
     (map (juxt :x :y))
     (plot-curve))))

and I get no noise…

Everything is working now, I think I got something mixed up from all the changes I was making. Thanks for your help!