From 6cd73c7323ed38bf05dc69e6cf860cf9a59b587f Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Sun, 12 Jul 2020 13:09:36 -0400 Subject: [PATCH] Little refactor (WIP), more mouse interaction --- README.md | 10 ++-- src/curlnoise/core.cljc | 100 +++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 405d80f..33b6646 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,9 @@ to run the ClojureScript version in the browser. - Better docs. - Make things more interactive, e.g. let the user place obstacles. -- Factor out the potential function rather than burying it in - `update-state`. - Visualize amplitude function & potential function. I have `draw-field` which `show-fn` enables, but it is rudimentary. -- Figure out reasonable boundary behavior. I'd like to do toroidal, - but I would need to gradually blend the potential function at the - edges for this to work right (otherwise particles are just stuck at - the boundaries due to the discontinuity). - Use 'real' simplex or OpenSimplex noise implementation that has a - gradient instead of doing it numerically? + gradient instead of doing it numerically. +- Add multi-octave noise and look into what the paper says about + turbulence. (Look up 'advection' technique it mentions too.) diff --git a/src/curlnoise/core.cljc b/src/curlnoise/core.cljc index 76c0422..f14a5b8 100644 --- a/src/curlnoise/core.cljc +++ b/src/curlnoise/core.cljc @@ -6,9 +6,9 @@ (def res-x 500) (def res-y res-x) ;; Number of particles to use: -(def particles 2000) +(def particles 1000) ;; Lower alpha produces *longer* particle trails -(def alpha 40) +(def alpha 30) (def renderer #?(:clj :java2d :cljs :p2d)) @@ -56,32 +56,70 @@ l (+ (dist (max 0.0 dx) (max 0.0 dy)) (min (max dx dy) 0.0))] l)) + +;; Domain scale for noise function: +(def scale 500.0) +;; Amplitude multiplier for noise: +(def noise-scale (* scale 5.0)) + +(def f-inv (/ scale)) +;; Potential function (2D + time): +(defn potential [x y t] + "2D (+ time) potential function. Returns a scalar. + + The absolute value of the scalar doesn't matter, but its gradient of + determines particle velocity. " + (* noise-scale + (+ (q/noise (* f-inv x) (* f-inv y) (* f-inv t)) + (q/noise (* f-inv x 2.0) (* f-inv y 2.0) (* f-inv t 1.61)) + ))) +;; 1.61 is sort of arbitrarily chosen so that periods of the octaves +;; don't line up exactly + +;; Delta used for 'gradient'. Multiplying screen width/height by 1e-3 +;; to 1e-4 usually gives an acceptable value. +(def eps 0.5) +(def eps-inv (/ eps)) + +(defn gradient [p-fn x y t] + "Numerical gradient of potential function 'p-fn' via finite differences. + + 'p-fn' should be a function that takes 3 arguments - (x,y,t) - and + returns a scalar for the potential at that position and time. + + Returns [d/dx, d/dy] of 'p-fn' at (x, y, t)." + (let [p (p-fn x y t) + p-dx (p-fn (+ x eps) y t) + p-dy (p-fn x (+ y eps) t) + grad-x (* (- p-dx p) eps-inv) + grad-y (* (- p-dy p) eps-inv)] + [grad-x grad-y])) + +(defn move-point [x y] + "Move a particle by the potential at a point. + + Returns [x y] of the 'updated' point." + ) + (defn update-state [state] (let [w (q/width) h (q/height) ;; Overall multiplier for velocity of particle: vf 0.1 - ;; Domain scale for noise function: - scale 500.0 - ;; Amplitude multiplier for noise: - noise-scale (* scale 10.0) ;; Radius for mouse-thingy: rad 20.0 ;; Radius for rounded corners: rect-rad 100.0 margin 0 - eps (* w 1e-3) mx (q/mouse-x) my (q/mouse-y) - f-inv (/ scale) ;; "width of the modified region": - d0 150.0 + d0 200.0 ;; distance of point to a circle of radius 'rad' ;; centered at mouse cursor: d-mouse #(if (q/mouse-pressed?) (- (dist (- mx %1) (- my %2)) rad) - 1e6 ;; arbitrarily large value - ) + 1e6) ;; function for distance to the border: d-border #(- rect-rad (sdf-box (- %1 rect-rad) ; x @@ -90,20 +128,17 @@ (- h (* rect-rad 2)) ; height )) ;; potential modulation function - takes (x,y): - amp-fn (fn [_ _] 1.0) + amp-fn (fn [x y] (ramp (/ (d-mouse x y) d0))) ;; #(ramp (min (/ (d-mouse %1 %2) d0) ;; (/ (d-border %1 %2) d0) ;; )) - mouse-drift #(if (q/mouse-pressed?) + mouse-drift #(if (or (< mx 0) (< my 0) (> mx w) (> my h)) + 0.0 (+ - (* (- (/ mx w) 0.5) %2 0.01) - (* (- (/ my h) 0.5) %1 -0.01)) - 0.0) + (* (- (/ mx w) 0.5) %2 20) + (* (- (/ my h) 0.5) %1 -20))) ;; Noise function - must take 3 arguments, (x,y,z): - n-fn #(* noise-scale - (+ - (mouse-drift %1 %2) - (q/noise (* f-inv %1) (* f-inv %2) (* f-inv %3)))) + n-fn #(+ (mouse-drift %1 %2) (potential %1 %2 %3)) ;; Overall amplitude function: p-fn #(* vf (amp-fn %1 %2) (n-fn %1 %2 %3)) points @@ -113,25 +148,28 @@ border (if (and (and (> x margin) (< x (- w margin))) (and (> y margin) (< x (- h margin)))) 1.0 0.0) - ;; Potential at (x, y, z): - n (p-fn x y z) - n-dx (p-fn (+ x eps) y z) - n-dy (p-fn x (+ y eps) z) - ;; Velocity by finite differences: - vx (/ (- n-dy n) eps) - vy (/ (- n n-dx) eps) - ;; Updated point position: - x2 (+ x vx) - y2 (+ y vy) + + f #(* vf + (+ (mouse-drift %1 %2) + (* (potential %1 %2 %3) + (amp-fn %1 %2)))) + [gx gy] (gradient f x y z) + + ;; Update points (move perpendicular to gradient): + x2 (+ x gy) + y2 (- y gx) [x3 y3] (if (or (< x2 0) (> x2 w) (< y2 0) (> y2 h)) [(q/random w) (q/random h)] [x2 y2]) ;; This boundary behavior is a little more ;; interesting: when a particle leaves the edges, ;; it just reappears in a random place. + [x4 y4] (if (and (q/mouse-pressed?) (< (dist (- mx x) (- my y)) rad)) + [(q/random w) (q/random h)] + [x3 y3]) ] - [x3 y3] + [x4 y4] )) (:grid state))] (-> state (update :frame inc)