|
205 | 205 |
|
206 | 206 |
|
207 | 207 | ;; ### Sampling Worley noise |
| 208 | +;; |
208 | 209 | ;; Using above functions one can now implement Worley noise. |
209 | 210 | ;; For each pixel the distance to the closest seed point is calculated. |
210 | 211 | ;; The distance to each random point in all neighbouring cells is calculated and the minimum is taken. |
|
226 | 227 | ;; Here a 256x256 Worley noise tensor is created. |
227 | 228 | (def worley (worley-noise (make-noise-params 256 8 2))) |
228 | 229 |
|
229 | | -;; The values are normalised to be between 0 and 255. |
| 230 | +;; The values are inverted and normalised to be between 0 and 255. |
230 | 231 | (def worley-norm |
231 | 232 | (dfn/* (/ 255 (- (dfn/reduce-max worley) (dfn/reduce-min worley))) |
232 | 233 | (dfn/- (dfn/reduce-max worley) worley))) |
|
304 | 305 | (plotly/base {:=title "Random gradients" :=mode "lines"}) |
305 | 306 | (plotly/layer-point {:=x :x :=y :y}))) |
306 | 307 |
|
307 | | - |
| 308 | +;; ### Corner vectors |
| 309 | +;; |
| 310 | +;; The next step is to determine the vectors to the corners of the cell for a given point. |
| 311 | +;; First we define a function to determine the fractional part of a number. |
308 | 312 | (defn frac |
309 | 313 | [x] |
310 | | - (- x (Math/floor x))) |
311 | | - |
| 314 | + (mod x 1.0)) |
312 | 315 |
|
313 | 316 | (facts "Fractional part of floating point number" |
314 | 317 | (frac 0.25) => 0.25 |
315 | 318 | (frac 1.75) => 0.75 |
316 | 319 | (frac -0.25) => 0.75) |
317 | 320 |
|
318 | 321 |
|
| 322 | +;; This function can be used to determine the relative position of a point in a cell. |
319 | 323 | (defn cell-pos |
320 | 324 | [{:keys [cellsize]} point] |
321 | 325 | (apply vec-n (map frac (div point cellsize)))) |
322 | 326 |
|
323 | | - |
324 | 327 | (facts "Relative position of point in a cell" |
325 | 328 | (cell-pos {:cellsize 4} (vec2 2 3)) => (vec2 0.5 0.75) |
326 | 329 | (cell-pos {:cellsize 4} (vec2 7 5)) => (vec2 0.75 0.25) |
327 | 330 | (cell-pos {:cellsize 4} (vec3 7 5 2)) => (vec3 0.75 0.25 0.5)) |
328 | 331 |
|
329 | | - |
| 332 | +;; A tensor converting the corner vectors can be computed by subtracting the corner coordinates from the point coordinates. |
330 | 333 | (defn corner-vectors |
331 | 334 | [{:keys [dimensions] :as params} point] |
332 | 335 | (let [cell-pos (cell-pos params point)] |
333 | 336 | (tensor/compute-tensor |
334 | 337 | (repeat dimensions 2) |
335 | 338 | (fn [& args] (sub cell-pos (apply vec-n (reverse args))))))) |
336 | 339 |
|
337 | | - |
338 | 340 | (facts "Compute relative vectors from cell corners to point in cell" |
339 | 341 | (let [v2 (corner-vectors {:cellsize 4 :dimensions 2} (vec2 7 6)) |
340 | 342 | v3 (corner-vectors {:cellsize 4 :dimensions 3} (vec3 7 6 5))] |
|
344 | 346 | (v2 1 1) => (vec2 -0.25 -0.5) |
345 | 347 | (v3 0 0 0) => (vec3 0.75 0.5 0.25))) |
346 | 348 |
|
347 | | - |
| 349 | +;; ### Extract gradients of cell corners |
| 350 | +;; |
| 351 | +;; The function below retrieves the gradient values at a cell's corners, utilizing `wrap-get` for modular access. |
348 | 352 | (defn corner-gradients |
349 | 353 | [{:keys [dimensions] :as params} gradients point] |
350 | 354 | (let [division (map (partial division-index params) point)] |
351 | 355 | (tensor/compute-tensor |
352 | 356 | (repeat dimensions 2) |
353 | 357 | (fn [& coords] (apply wrap-get gradients (map + (reverse division) coords)))))) |
354 | 358 |
|
355 | | - |
356 | 359 | (facts "Get 2x2 tensor of gradients from a larger tensor using wrap around" |
357 | 360 | (let [gradients2 (tensor/compute-tensor [4 6] (fn [y x] (vec2 x y))) |
358 | 361 | gradients3 (tensor/compute-tensor [4 6 8] (fn [z y x] (vec3 x y z))) ] |
|
369 | 372 | ((corner-gradients {:cellsize 4 :dimensions 3} gradients3 (vec3 9 6 3)) 0 0 0) |
370 | 373 | => (vec3 2 1 0))) |
371 | 374 |
|
372 | | - |
| 375 | +;; ### Influence values |
| 376 | +;; |
| 377 | +;; The influence value is the function value of the function with the selected random gradient at a corner. |
373 | 378 | (defn influence-values |
374 | 379 | [gradients vectors] |
375 | 380 | (tensor/compute-tensor |
376 | 381 | (repeat (count (dtype/shape gradients)) 2) |
377 | 382 | (fn [& args] (dot (apply gradients args) (apply vectors args))) |
378 | 383 | :double)) |
379 | 384 |
|
380 | | - |
381 | 385 | (facts "Compute influence values from corner vectors and gradients" |
382 | 386 | (let [gradients2 (tensor/compute-tensor [2 2] (fn [_y x] (vec2 x 10))) |
383 | 387 | vectors2 (tensor/compute-tensor [2 2] (fn [y _x] (vec2 1 y))) |
|
391 | 395 | (influence2 1 1) => 11.0 |
392 | 396 | (influence3 1 1 1) => 111.0)) |
393 | 397 |
|
394 | | - |
| 398 | +;; ### Interpolating the influence values |
| 399 | +;; |
| 400 | +;; For interpolation the following "ease curve" is used. |
395 | 401 | (defn ease-curve |
396 | 402 | [t] |
397 | 403 | (-> t (* 6.0) (- 15.0) (* t) (+ 10.0) (* t t t))) |
398 | 404 |
|
399 | | - |
400 | 405 | (facts "Monotonously increasing function with zero derivative at zero and one" |
401 | 406 | (ease-curve 0.0) => 0.0 |
402 | 407 | (ease-curve 0.25) => (roughly 0.103516 1e-6) |
403 | 408 | (ease-curve 0.5) => 0.5 |
404 | 409 | (ease-curve 0.75) => (roughly 0.896484 1e-6) |
405 | 410 | (ease-curve 1.0) => 1.0) |
406 | 411 |
|
407 | | - |
| 412 | +;; The ease curve monotonously increases in the interval from zero to one. |
408 | 413 | (-> (tc/dataset {:t (range 0.0 1.025 0.025) |
409 | 414 | :ease (map ease-curve (range 0.0 1.025 0.025))}) |
410 | 415 | (plotly/base {:=title "Ease Curve"}) |
411 | 416 | (plotly/layer-line {:=x :t :=y :ease})) |
412 | 417 |
|
413 | | - |
| 418 | +;; The interpolation weights are recursively calculated from the ease curve and the coordinate distances of the point to upper and lower cell boundary. |
414 | 419 | (defn interpolation-weights |
415 | 420 | ([params point] |
416 | 421 | (interpolation-weights (cell-pos params point))) |
|
422 | 427 | (tensor/->tensor [(dfn/* (ease-curve w1) elem) (dfn/* (ease-curve w2) elem)])) |
423 | 428 | 1.0))) |
424 | 429 |
|
425 | | - |
426 | 430 | (facts "Interpolation weights" |
427 | 431 | (let [weights2 (interpolation-weights {:cellsize 8} (vec2 2 7)) |
428 | 432 | weights3 (interpolation-weights {:cellsize 8} (vec3 2 7 3))] |
|
433 | 437 | (weights3 0 0 0) => (roughly 0.010430 1e-6))) |
434 | 438 |
|
435 | 439 |
|
| 440 | +;; ### Sampling Perlin noise |
| 441 | +;; |
| 442 | +;; A Perlin noise sample is computed by |
| 443 | +;; * Getting the random gradients for the cell corners. |
| 444 | +;; * Getting the corner vectors for the cell corners. |
| 445 | +;; * Computing the influence values which have the desired gradients. |
| 446 | +;; * Determining the interpolation weights. |
| 447 | +;; * Computing the weighted sum of the influence values. |
436 | 448 | (defn perlin-sample |
437 | 449 | [params gradients point] |
438 | 450 | (let [gradients (corner-gradients params gradients point) |
|
441 | 453 | weights (interpolation-weights params point)] |
442 | 454 | (dfn/reduce-+ (dfn/* weights influence)))) |
443 | 455 |
|
444 | | - |
| 456 | +;; Now one can sample the Perlin noise by performing above computation for the center of each pixel. |
445 | 457 | (defn perlin-noise |
446 | 458 | [{:keys [size dimensions] :as params}] |
447 | 459 | (let [gradients (random-gradients params)] |
448 | 460 | (tensor/clone |
449 | 461 | (tensor/compute-tensor |
450 | 462 | (repeat dimensions size) |
451 | 463 | (fn [& args] |
452 | | - (let [center (add (apply vec-n (reverse args)) |
453 | | - (apply vec-n (repeat dimensions 0.5)))] |
| 464 | + (let [center (apply vec-n (map #(+ % 0.5) (reverse args)))] |
454 | 465 | (perlin-sample params gradients center))) |
455 | 466 | :double)))) |
456 | 467 |
|
457 | | - |
| 468 | +;; Here a 256x256 Perlin noise tensor is created. |
458 | 469 | (def perlin (perlin-noise (make-noise-params 256 8 2))) |
459 | 470 |
|
| 471 | +;; The values are normalised to be between 0 and 255. |
460 | 472 | (def perlin-norm |
461 | 473 | (dfn/* (/ 255 (- (dfn/reduce-max perlin) (dfn/reduce-min perlin))) |
462 | 474 | (dfn/- perlin (dfn/reduce-min perlin)))) |
463 | 475 |
|
| 476 | +;; Finally one can display the noise. |
464 | 477 | (bufimg/tensor->image perlin-norm) |
465 | 478 |
|
466 | 479 | ;; ## Combination of Worley and Perlin noise |
|
0 commit comments