noise

0.1.0-SNAPSHOT


Naive Perlin noise implementation

dependencies

org.clojure/clojure
1.7.0
org.clojure/clojurescript
1.7.122
org.clojure/core.async
0.1.346.0-17112a-alpha



(this space intentionally left almost blank)
 
(ns ^:figwheel-always noise.core
    (:require))
(enable-console-print!)

Perlin Noise (naive & slow implementation)

See http://flafla2.github.io/2014/08/09/perlinnoise.html

Perlin noise is "pseudo-random" noise, which gives it a more organic look than truly random noise, because the noise function will be similar for two nearby points. This is accomplished by placing a grid on a coordinate plane and associating random vectors with each intersection point on the grid. For each point in the plane, its value is a function of the dot product between a set of vectors from the points to each corner of its bounding box, and the pseudorandom gradients associated with those corners.

generate 8 random gradients

(def gradients
  (vec (take 8 (repeatedly (fn []
                             (let [x (rand)
                                   y (rand)
                                   mult (if (> (rand) (rand)) 1.0 -1.0)
                                   mult2 (if (> (rand) (rand)) 1.0 -1.0)]
                               [(* x mult) (* y mult2)]))))))

find the gradient associated with a corner of a grid cell. This stands in for the hash function in the real implementation, and the purpose is to map data of arbitrary size to data of fixed size (our 8 random gradients). I think this, along with the dot function, account for the slowness. The theoretical explanations all involve the dot product but I don't understand how it's implemented in most real implentations, including Ken Perlin's Java reference implementation.

(defn get-gradient 
  [[a b]]
  (let [ndx (int (mod (+ a b) 7))]
    (get gradients ndx)))

for a given point, find the corners of the bounding box (grid cell) it is contained within

(defn get-corners
  [x y]
  (let [nw [(- x (mod x 100)) (- y (mod y 100))]
        [x y] nw
        ne [(+ 100 x) y]
        sw [x (+ 100 y)]
        se [(+ 100 x) (+ 100 y)]]
    [nw ne sw se]))

vector dot product

(defn dot
  [X Y]
  (reduce + (map * X Y)))

linear interpolation function

(defn lerp
  [t a b]
  (+ a (* t (- b a))))
(defn ease
  [t]
  (- (* 3 (.pow js/Math t 2))
     (* 2 (.pow js/Math t 3))))

find the gradients of each corner of the bounding box

(defn corner-gradients
  [x y]
  (map get-gradient (get-corners x y)))

find the vectors from the point to the bounding box corners

(defn corner-to-point-vectors
  [x y]
  (map (fn [[cx cy]] [(- x cx) (- y cy)])
       (get-corners x y)))

compute the 'influences' of the corner gradients, by taking the dot product of the corner gradient and the vector from the point to that corner

(defn influences
  [x y]
  (let [gs (corner-gradients x y)
        vs (corner-to-point-vectors x y)]
    (map dot gs vs)))

compute the noise function value for a given pixel

(defn noise
  [x y]
  (let [
        ;;situate the point within a unit square
        rel-x (/ x 100)
        rel-y (/ y 100)
        ;;find the coordinates within the unit square
        frac-x (mod rel-x 1)
        frac-y (mod rel-y 1)
        ;;exaggerate proximity to corner
        Sx (ease frac-x)
        Sy (ease frac-y)
        ;;compute influences of corner gradients
        [nw ne sw se] ((fn []
                         (influences x y)))
        ;;linearly interpolate between the exaggerated point the "influenced" point
        a (lerp Sx nw ne)
        b (lerp Sx sw se)
        z (lerp Sy a b)]
    ;;I forgot why this was needed :(
    (.abs js/Math (/ z 10))))

drawing related stuff

multiplication by 40 is just so the gradients are visible on the drawn grid

(defn xPlusGrad [x y]
  (+ x (* 40 (first (get-gradient [x y])))))
(defn yPlusGrad [x y]
  (+ y (* 40 (second  (get-gradient [x y])))))

helper function to draw a stroke on the canvas

(defn stroke
  [ctx {:keys [startx starty endx endy]}]
  (.beginPath ctx)
  (.moveTo ctx startx starty)
  (.lineTo ctx endx endy)
  (.stroke ctx))

draw the grid which shows the corner vectors and vectors to corners for an example point

(defn drawGrid
  []
  (let [canvas (.getElementById js/document "surface")
        ctx (.getContext canvas "2d")]
    (set! (.-strokeStyle ctx) "blue")
    ;; crisper lines on the canvas
    (.translate ctx 0.5 0.5)
    (doseq [n (range 100 500 100)]
      (stroke ctx
              { :startx 100 :starty n
                :endx 400 :endy n })
      (stroke ctx
              { :startx n :starty 100
                :endx n :endy 400 }))))

draw the corner gradients on the example grid

(defn drawGradients
  []
  (let [canvas (.getElementById js/document "surface")
        ctx (.getContext canvas "2d")]
    (set! (.-strokeStyle ctx) "red")
    (doseq [x (range 100 500 100)]
      (doseq [y (range 100 500 100)]
        (stroke ctx
                {:startx x :starty y
                 :endx (xPlusGrad x y) :endy (yPlusGrad x y) })))))

draw the example point on the grid

(defn drawPoint
  []
  (let [canvas (.getElementById js/document "surface")
        ctx (.getContext canvas "2d")]
    (set! (.-strokeStyle ctx) "green")
    (.fillRect ctx 221 139 3 3)))

draw the vectors from the point to the corners of its bounding box

(defn drawPointVectors
  []
  (let [canvas (.getElementById js/document "surface")
        ctx (.getContext canvas "2d")
        x 221
        y 139]
    (set! (.-strokeStyle ctx) "pink")
    (doseq [[cx cy] (get-corners x y)]
      (stroke ctx
              {:startx cx :starty cy
               :endx x :endy y }))))

draw a canvas with the noise function computed for each pixel

(defn drawNoiseCanvas
  []
  (let [canvas (.getElementById js/document "noise")
        ctx (.getContext canvas "2d")]
    (doseq [x (range 100 400)
            y (range 100 400)
            :let [n  (int (* 256 (noise x y)))
                  color (str "rgb(" n "," n "," n ")")]]
      (set! (.-fillStyle ctx) color)
      (.fillRect ctx x y 1 1))))
(drawGrid)
(drawGradients)
(drawPointVectors)
(drawPoint)
(drawNoiseCanvas)