Little refactor (WIP), more mouse interaction

This commit is contained in:
Chris Hodapp 2020-07-12 13:09:36 -04:00
parent 01642f221f
commit 6cd73c7323
2 changed files with 72 additions and 38 deletions

View File

@ -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.)

View File

@ -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)