Skip to content

Commit a4d8484

Browse files
committed
Adding explanations
1 parent de379b7 commit a4d8484

1 file changed

Lines changed: 27 additions & 13 deletions

File tree

src/volumetric_clouds/main.clj

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,39 +39,47 @@
3939
;;
4040
;; [Worley noise](https://en.wikipedia.org/wiki/Worley_noise) is a type of structured noise which is defined for each pixel using the distance to the nearest seed point.
4141
;;
42+
;; ### Noise parameters
43+
;;
4244
;; First we define a function to create parameters of the noise.
4345
;;
4446
;; * **size** is the size of each dimension of the noise array
4547
;; * **divisions** is the number of subdividing cells in each dimension
4648
;; * **dimensions** is the number of dimensions
47-
4849
(defn make-noise-params
4950
[size divisions dimensions]
5051
{:size size :divisions divisions :cellsize (/ size divisions) :dimensions dimensions})
5152

52-
53+
;; Here is a corresponding Midje test.
54+
;; Note that ideally you practise [Test Driven Development (TDD)](https://martinfowler.com/bliki/TestDrivenDevelopment.html), i.e. you start with writing one failing test.
55+
;; Because this is a Clojure notebook, the unit tests are displayed after the implementation.
5356
(fact "Noise parameter initialisation"
5457
(make-noise-params 256 8 2) => {:size 256 :divisions 8 :cellsize 32 :dimensions 2})
5558

5659

60+
;; ### 2D and 3D vectors
61+
;;
62+
;; Next we need a function which allows us to create 2D or 3D vectors depending on the number of input parameters.
5763
(defn vec-n
5864
([x y] (vec2 x y))
5965
([x y z] (vec3 x y z)))
6066

61-
6267
(facts "Generic vector function for creating 2D and 3D vectors"
6368
(vec-n 2 3) => (vec2 2 3)
6469
(vec-n 2 3 1) => (vec3 2 3 1))
6570

6671

72+
;; ### Random points
73+
;;
74+
;; The following method generates a random point in a cell specified by the cell indices.
6775
(defn random-point-in-cell
68-
[{:keys [cellsize]} & args]
76+
[{:keys [cellsize]} & indices]
6977
(let [random-seq (repeatedly #(rand cellsize))
70-
dimensions (count args)]
71-
(add (mult (apply vec-n (reverse args)) cellsize)
78+
dimensions (count indices)]
79+
(add (mult (apply vec-n (reverse indices)) cellsize)
7280
(apply vec-n (take dimensions random-seq)))))
7381

74-
82+
;; We test the method by replacing the random function with a deterministic function.
7583
(facts "Place random point in a cell"
7684
(with-redefs [rand (fn [s] (* 0.5 s))]
7785
(random-point-in-cell {:cellsize 1} 0 0) => (vec2 0.5 0.5)
@@ -81,13 +89,13 @@
8189
(random-point-in-cell {:cellsize 2} 2 3 5) => (vec3 11.0 7.0 5.0)))
8290

8391

92+
;; We can now use the `random-point` method to generate a grid of random points.
8493
(defn random-points
8594
[{:keys [divisions dimensions] :as params}]
8695
(tensor/clone
8796
(tensor/compute-tensor (repeat dimensions divisions)
8897
(partial random-point-in-cell params))))
8998

90-
9199
(facts "Greate grid of random points"
92100
(let [params-2d (make-noise-params 32 8 2)
93101
params-3d (make-noise-params 32 8 3)]
@@ -99,21 +107,23 @@
99107
(dtype/shape (random-points params-3d)) => [8 8 8]
100108
((random-points params-3d) 2 3 5) => (vec3 22.0 14.0 10.0))))
101109

102-
110+
;; Here is a scatter plot showing one random point placed in each cell.
103111
(let [points (tensor/reshape (random-points (make-noise-params 256 8 2)) [(* 8 8)])
104112
scatter (tc/dataset {:x (map first points) :y (map second points)})]
105113
(-> scatter
106114
(plotly/base {:=title "Random points"})
107115
(plotly/layer-point {:=x :x :=y :y})))
108116

109117

118+
;; ### Modular distance
119+
;;
120+
;; In order to get a periodic noise array, we need to component-wise wrap around distance vectors.
110121
(defn mod-vec
111122
[{:keys [size]} v]
112123
(let [size2 (/ size 2)
113124
wrap (fn [x] (-> x (+ size2) (mod size) (- size2)))]
114125
(apply vec-n (map wrap v))))
115126

116-
117127
(facts "Wrap around components of vector to be within -size/2..size/2"
118128
(mod-vec {:size 8} (vec2 2 3)) => (vec2 2 3)
119129
(mod-vec {:size 8} (vec2 5 2)) => (vec2 -3 2)
@@ -128,12 +138,12 @@
128138
(mod-vec {:size 8} (vec3 2 -5 1)) => (vec3 2 3 1)
129139
(mod-vec {:size 8} (vec3 2 3 -5)) => (vec3 2 3 3))
130140

131-
141+
;;
142+
;; Using the `mod-dist` function we can calculate the distance between two points in the periodic noise array.
132143
(defn mod-dist
133144
[params a b]
134145
(mag (mod-vec params (sub b a))))
135146

136-
137147
(tabular "Wrapped distance of two points"
138148
(fact (mod-dist {:size 8} (vec2 ?ax ?ay) (vec2 ?bx ?by)) => ?result)
139149
?ax ?ay ?bx ?by ?result
@@ -148,13 +158,17 @@
148158
0 5 0 0 3.0)
149159

150160

161+
;; ### Modular lookup
162+
;;
163+
;; We also need to lookup elements with wrap around.
164+
;; We recursively use `tensor/select` and then finally the tensor as a function to lookup along each axis.
151165
(defn wrap-get
152166
[t & args]
153167
(if (> (count (dtype/shape t)) (count args))
154168
(apply tensor/select t (map mod args (dtype/shape t)))
155169
(apply t (map mod args (dtype/shape t)))))
156170

157-
171+
;; A tensor with index vectors is used to test the lookup.
158172
(facts "Wrapped lookup of tensor values"
159173
(let [t (tensor/compute-tensor [4 6] vec2)]
160174
(wrap-get t 2 3) => (vec2 2 3)

0 commit comments

Comments
 (0)