dependencies
| (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) | ||||||||||