7 | - Picked up as an early 'learn clojure' project
8 | - Not a web project
9 | - Not just programming, reminded me of some basic physics
10 |
11 |
12 | # Jackson Pollock
13 |
14 | 
15 |
16 |
17 | - Famous 20th century abstract painter
18 | - Made 'drip painting' famous, a form of action paiting
19 | - Drips paints off brushes, sticks or bits of wood onto the cavnas
20 | - We are going to start our model by considering a single point of paint, rather than a path
21 |
22 |
23 | # Defining our space - I
24 |
25 | 
26 |
27 |
28 | - We need a space to work in and some basic physical details
29 |
44 | - Facts defined using def and given a name
45 | - These are composed of basic clojure types
46 | - All our measurements are in SI units with pixel conversion in drawing
47 |
48 |
49 | # Picking a starting point
50 |
51 | A gesture has to start somewhere inside of our defined space.
52 |
53 | 
54 |
55 | ```{.clojure}
56 | (defn starting-point []
57 | (map rand space))
58 | ```
59 |
60 |
61 | - A gesture has to start somewhere
62 | - A very expressive set of characters
63 |
70 | - Our point needs to hit the canvas
71 | - Using basic equations of motion
72 | - We know the the canvas is at y = 0
73 | - We need ot find time to impact in order to find other values
74 |
91 | - Solved nicely using Quadratic Equation
92 | - One value will be negative so always pick the max
93 |
94 |
95 | # Projection - III
96 |
97 | ```{.clojure}
98 | (defn position-at [time initial-position initial-velocity acceleration]
99 | (+ initial-position
100 | (* initial-velocity time)
101 | (/ (* acceleration time time) 2)))
102 |
103 | (defn velocity-at [time initial-velocity acceleration]
104 | (+ (* acceleration time) initial-velocity))
105 |
106 | (defn project-point [position velocity]
107 | (let [[i j k] position
108 | [vi vj vk] velocity
109 | [ai aj ak] gravity
110 | time (time-to-canvas j vj aj)
111 | projected-position [(position-at time i vi ai)
112 | 0
113 | (position-at time k vk ak)]
114 | projected-velocity [(velocity-at time vi ai)
115 | (velocity-at time vj aj)
116 | (velocity-at time vk ak)]]
117 | [projected-position
118 | projected-velocity]))
119 | ```
120 |
121 |
122 | - Now we have time to impact it is easy to derive everything else
123 | - For the position we can use the same function as we derieved the time with:
124 | + $r = r0 + v0 * t + \frac{at^2}{2}$
125 | - For velocity we can use: $v = at + v0$
126 |
127 | - An important aspect of Pollock's paintings is the splatter made
128 | as he flicks paint against the canvas, we are now going to look
129 | at how to work out whether this impact should splatter
130 |
137 | - We need to work out whether an impact should splatter
138 | + look at the impact force
139 | + Impact force derived from work-energy principle:
140 | * W=\Delta E_k=\tfrac12mv_2^2-\tfrac12mv_1^2,
141 | - We need to define a cutoff for this
142 | - If it does splatter we need to bounce the vector
143 | - Collision is inelastic so it should absorb some of the vector
144 | + inelastic means conservation of energy is not maintained and energy is lost
145 | through various means
146 |
169 | The equation to bounce a vector, $V$, off a plane with normal, $N$, is:
170 |
171 | - $N$ is the normal vector of the plane
172 | - $V$ = the incoming vector
173 | - $B$ is the outgoing, bounced, vector
174 |
175 | $B = V - (2 * (V.N) * N)$
176 |
183 | - Parametric curves used in vector graphics because they scale indefinitely
184 | - Gives nice smooth curves
185 | - We need anchor, or control, points to calculate a bezier curve
186 | - Do a random walk in a random direction to define these
187 |
210 | - Lazy seqs, they are aces
211 | - Random total distance and number of steps
212 | - These are then used to work out the anchor points
213 | - A known end point is used
214 |
221 | - De casteljau is a recursive method for evaluating Bezier curves
222 | - Finding a point between two points (along a line)
223 | - De casteljau is just that on mainly lines in parallel
224 |
290 |
291 | # Pull it all together
292 |
293 |
294 |
295 |
296 |
297 |
298 |
--------------------------------------------------------------------------------
/src/uk/co/tombooth/pollock.cljs:
--------------------------------------------------------------------------------
1 | (ns uk.co.tombooth.pollock
2 | (:require [quil.core :as q :include-macros true]))
3 |
4 | ;; ---
5 | ;; title: Painting in Clojure
6 | ;; ...
7 |
8 | ;; Learning Clojure by building a digital Jackson Pollock. This
9 | ;; article and the source code backing it can be [found on
10 | ;; GitHub](https://www.github.com/tombooth/painting-in-clojure).
11 | ;; Below is an example of what we will be building, running the code
12 | ;; found in this page.
13 |
14 |
15 | ;;
16 | ;;
17 | ;;
18 |
19 |
20 |
21 | ;; ## Jackson Pollock
22 |
23 | ;; He was an abstract artist who lived through the first half of the
24 | ;; 20th century and is most famous for his drip paintings. This style
25 | ;; of painting involves him using sticks brushes, sticks and cans to
26 | ;; apply paint to the canvas with the motion of his gestures causing
27 | ;; the artworks to come alive. You can get a good idea of how this
28 | ;; comes together from [this youtube video](https://www.youtube.com/watch?v=7bICqvmKL5s).
29 |
30 | ;; ## Setting the scene
31 |
32 | ;; We want to define some facts about the space that our digital
33 | ;; Pollock will work in. These facts will not change over the
34 | ;; execution of our model and fit Clojure's preference for
35 | ;; immutability perfectly. For those who have not come across the idea
36 | ;; of mutability before it is simply whether something can be changed
37 | ;; in place. In most languages if you set the label `some_number` to
38 | ;; equal `5`, further on you can increment the value of `some_number` to
39 | ;; `6` or even `7`. In Clojure if you tried to increment `some_number`
40 | ;; you would get a new value rather than changing `some_number`.
41 |
42 |
43 | ;; Clojure will let us define facts using one of the following value types:
44 | ;;
45 | ;; - A number. This could be `5` an integer, `3/2` a ratio/fraction
46 | ;; or `3.14` a floating point number;
47 | ;; - A string, represented as a sequence of characters, for example
48 | ;; `"Hello world!"`;
49 | ;; - A keyword, which are very similar to strings in appearance
50 | ;; except they are preceded by a colon e.g. `:an-identifier`. As
51 | ;; alluded to in the example they are usually used for identifiers or
52 | ;; labels and do not allow spaces.
53 | ;; - A list `(...)`, this is a way of grouping values into a
54 | ;; collection with an explicit order. You may notice all of
55 | ;; the code written takes for
56 | ;; form of lists. By default if you have written `(...)` Clojure
57 | ;; will assume the first item is a function and the rest are
58 | ;; arguments to be passed in. In order for the list not to be
59 | ;; executed you should prefix it with a `'`;
60 | ;; - A vector `[...]`, which is a lot like a list except that they are optimised for
61 | ;; appending to the end of the sequence rather than to the front;
62 | ;; - A set `#{...}`. If you are not particularly bothered by the order of
63 | ;; the values stored in your collection then you can use a set;
64 | ;; - Lastly there are maps `{...}`, these store pairs of values
65 | ;; where the first is a key and the second is a value.
66 |
67 | ;; If you would like to learn more about the basic types in Clojure, I
68 | ;; suggest you read [this great blog
69 | ;; post](http://aphyr.com/posts/302-clojure-from-the-ground-up-basic-types)
70 | ;; by Aphyr.
71 |
72 | ;; The most important fact about the space is its size. We will use
73 | ;; metres to measure the size only converting to pixels when we need
74 | ;; to draw to the screen. We are going to define size as a vector
75 | ;; containing its width, height and depth.
76 |
77 | (def space [8 ;; width
78 | 5 ;; height
79 | 6]) ;; depth
80 |
81 | ;; We need to know the gravity of the space so it can influence the
82 | ;; flow of paint as it leaves the brush. This will be defined as a
83 | ;; vector that represents acceleration influenced by gravity.
84 |
85 | (def gravity [0 -9.8 0])
86 |
87 | ;; Lastly, we need to know the normal of the surface of the canvas that
88 | ;; the paint will impact with. This will be used to dictate how paint
89 | ;; acts when it spatters from the impact with the canvas.
90 |
91 | (def canvas-normal [0 1 0])
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | ;; ## Starting points and projection
100 |
101 | ;; Our digital Pollock is going to start a stroke of the brush by
102 | ;; picking a random point in space. This point will then be projected
103 | ;; to find where it impacts with the canvas.
104 |
105 | ;; In order to generate a random point inside of the space we need to
106 | ;; define a function that each time it is called will emit a vector
107 | ;; containing the position of the point. Function values can be
108 | ;; created by calling `(fn [...] ...)` with the first vector being the
109 | ;; arguments the function should receive and any follow items in the
110 | ;; list are the body of the function and executed with the arguments
111 | ;; bound. Rather than calling `(def name (fn ...))` Clojure has provided a
112 | ;; shortcut function `(defn name [...] ...)`. An example of a defined
113 | ;; function is `(defn say-hello [name] (str "Hello " name))`, this
114 | ;; creates a function called say-hello that when called `(say-hello
115 | ;; "James")` it will return the string "Hello James".
116 |
117 | ;; We are going to cover a common functional idiom when dealing with
118 | ;; lists to change the dimensions of the space above into a random
119 | ;; point inside that space. To do this we want to iterate over each
120 | ;; dimension of the size of space, generate a random number between 0
121 | ;; and the magnitude of each dimension and then return the resultant
122 | ;; list of random numbers as a list. To generate a random number in
123 | ;; Clojure we can use the `(rand)` function, which will return a
124 | ;; random number between 0 (inclusive) and 1 (exclusive). The rand
125 | ;; function can take an optional parameter `(rand 100), this will
126 | ;; define the bounds of the number generated to 0 and 100.
127 |
128 | ;; The function map `(map [fn] [sequence])` will iterate through the
129 | ;; sequence executing the function with the current value of the
130 | ;; sequence as its first parameter, the values returned from the
131 | ;; function will be stored in a list the same length as the sequence
132 | ;; and returned by the function.
133 |
134 | ;; We can now define a random point inside of space as follows
135 |
136 | (defn starting-point [] (map rand space))
137 |
138 |
139 | ;; Now that we can generate a random point in space we want to project
140 | ;; this to the canvas. We are going to use [Newtonian equations of
141 | ;; motion](https://en.wikipedia.org/wiki/Equations_of_motion),
142 | ;; we know the position, velocity and acceleration of the
143 | ;; point and we want to know what the position and velocity are when y
144 | ;; is 0. In order to work out final positions we need to know the
145 | ;; total time the point spent falling, we can do this using the y
146 | ;; position as we know that the final position should be 0.
147 |
148 | ;; To work out the time it takes for the point to reach the canvas we
149 | ;; will solve the following equation for t:
150 | ;;
151 | ;; - $r$ = final displacement,
152 | ;; - $r_{0}$ = initial displacement,
153 | ;; - $v_{0}$ = initial velocity,
154 | ;; - $a$ = acceleration,
155 | ;; - $t$ = time.
156 | ;;
157 | ;; $r = r_{0} + v_{0} * t + \frac{at^2}{2}$
158 | ;;
159 | ;; This rearranges to:
160 | ;;
161 | ;; $at^2 + 2v_{0}t + 2r_{0} - 2r = 0$
162 | ;;
163 | ;; We can solve this using the Quadratic Equation, but this will yield
164 | ;; us two results. In general we can say that we are interested in the
165 | ;; result with the maximum value.
166 | ;;
167 | ;; In the next block of code you can see an example of call out to Java(Script).
168 | ;; Clojure doesn't have an in-built square root function, so we are calling out
169 | ;; to the Java(Script) version. A function named in the form `foo/bar` means
170 | ;; it will call the function `bar` in the namespace `foo`. You might
171 | ;; be wondering, what is a namespace?.
172 |
173 | ;; All good languages need a way to
174 | ;; bundle up code that is related, so that it can be reused and
175 | ;; accessed only when needed. Clojure's take on this is to provide
176 | ;; namespaces. Every Clojure source file will declare its namespace at
177 | ;; the top of the file so that other files can reference it, extract
178 | ;; values and use functions. Given that Clojure is a hosted language
179 | ;; its namespace will related to packages in Java and Google Closure
180 | ;; Library namespaces in Javascript.
181 |
182 | ;; When hosted on Java all of java.util.* is automatically imported
183 | ;; and on JavaScript assorted core and Google Closure Library modules
184 | ;; are imported. Both of these languages provide us with a Math
185 | ;; namespace which contains a `sqrt` function.
186 |
187 | ;; If you want to learn more about Clojure -> Java(Script) interop
188 | ;; then have a read [of this article](http://clojure-doc.org/articles/language/interop.html).
189 |
190 | (defn time-to-canvas [position velocity acceleration]
191 | (let [a acceleration
192 | b (* 2 velocity)
193 | c (* 2 position)
194 | discriminant (- (* b b) (* 4 a c))
195 | minus-b (- 0 b)
196 | add-sqrt (/ (+ minus-b (Math/sqrt discriminant)) (* 2 a))
197 | minus-sqrt (/ (- minus-b (Math/sqrt discriminant)) (* 2 a))]
198 | (max add-sqrt minus-sqrt)))
199 |
200 | ;; We can now calculate the time to impact but we want the final position and
201 | ;; velocity. For position we can use the same function that we
202 | ;; rearranged above to derive the time.
203 |
204 | (defn position-at [time initial-position initial-velocity acceleration]
205 | (+ initial-position
206 | (* initial-velocity time)
207 | (/ (* acceleration time time) 2)))
208 |
209 | ;; For velocity we can use another equation of motion:
210 | ;;
211 | ;; $v = at + v_{0}$
212 |
213 | (defn velocity-at [time initial-velocity acceleration]
214 | (+ (* acceleration time) initial-velocity))
215 |
216 |
217 | ;; These functions we just implemented can be joined up so that, given
218 | ;; an initial position and velocity we can return the final position and
219 | ;; velocity. This function doesn't explicitly ask for the acceleration
220 | ;; acting on the paint, it assumes only gravity is acting using the
221 | ;; constant defined earlier on.
222 |
223 | (defn project-point [position velocity]
224 | (let [[i j k] position
225 | [vi vj vk] velocity
226 | [ai aj ak] gravity
227 |
228 | time (time-to-canvas j vj aj)
229 |
230 | projected-position [(position-at time i vi ai)
231 | 0 ;; we don't need to calculate as it
232 | ;; should be 0, on the canvas
233 | (position-at time k vk ak)]
234 |
235 | projected-velocity [(velocity-at time vi ai)
236 | (velocity-at time vj aj)
237 | (velocity-at time vk ak)]]
238 | [projected-position
239 | projected-velocity]))
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | ;; ## Paint splatter
248 |
249 | ;; An important aspect of Pollocks painting is the splatter of the
250 | ;; paint hitting the canvas and what this adds to the images. We are
251 | ;; going to add a simple splatter model based of the velocity at
252 | ;; impact we calculated in the last part.
253 |
254 | ;; Not all paint that hits the canvas will splatter, so we need to
255 | ;; work out the impact force of the paint and use this as a cutoff for
256 | ;; whether the paint should splatter.
257 |
258 | ;; We will work out the impact force of the paint by taking the
259 | ;; velocity at impact and calculating the force required to reduce
260 | ;; that velocity to 0 over a set impact distance.
261 |
262 | (def impact-distance 0.05)
263 |
264 | ;; We can now use the work-energy principle
265 | ;; (https://en.wikipedia.org/wiki/Work_(physics)#Work_and_energy) to
266 | ;; calculate the impact force. On one side of the equation we will
267 | ;; have the forces at play and the other the energy:
268 |
269 | ;; - $F_{i}$ = impact force,
270 | ;; - d = impact distance,
271 | ;; - m = mass,
272 | ;; - g = gravity,
273 | ;; - v = velocity at impact.
274 |
275 | ;; $-F_{i}d + mgd = 0 - \frac{1}{2}mv^2$
276 |
277 | ;; This equation can be rearranged to:
278 |
279 | ;; $F_{i} = mg + \frac{mv^2}{2d}$
280 |
281 | ;; For simplicity of code we are just going to consider the y axis as
282 | ;; this is the most important when it comes to working out the impact
283 | ;; force of the paint into the canvas. The above equation can
284 | ;; therefore be expressed as:
285 |
286 | (defn impact-force [mass velocity]
287 | (let [y-gravity (second gravity)
288 | y-velocity (second velocity)]
289 | (+ (* mass y-gravity) (/ (* mass y-velocity y-velocity)
290 | (* 2 impact-distance)))))
291 |
292 | ;; Based of this function to calculate the impact force we can define
293 | ;; a predicate that will tell us whether paint should splatter based
294 | ;; off its mass and velocity. It is idiomatic in Clojure to end
295 | ;; predicates with a `?`. We are going to add some randomness to this
296 | ;; function so that we don't necessarily just get a uniform line of
297 | ;; points. Also defined is a minimum force for us to consider whether
298 | ;; some paint could splatter.
299 |
300 | (def min-impact-force-for-splatter 30)
301 |
302 | (defn does-impact-splatter? [mass velocity]
303 | (and (> (impact-force mass velocity) min-impact-force-for-splatter)
304 | (> (rand) 0.8)))
305 |
306 | ;; If an impact splatters then we will need to bounce its velocity
307 | ;; vector as this is the direction it will leave its current position.
308 |
309 | ;; The equation to bounce a vector, $V$, off a plane with normal, $N$, is:
310 | ;;
311 | ;; - $N$ is the normal vector of the plane
312 | ;; - $V$ = the incoming vector
313 | ;; - $B$ is the outgoing, bounced, vector
314 | ;;
315 | ;; $B = V - (2 * (V.N) * N)$
316 | ;;
317 | ;; You can find out a bit more about the derivation on this [Wolfram
318 | ;; page](http://mathworld.wolfram.com/Reflection.html).
319 |
320 | ;; We are missing a few of the required vector operations used in this
321 | ;; equation so we should define some more functions before trying to
322 | ;; implement it. The first is the vector dot product, this is defined
323 | ;; as the sum of the multiples of each dimension. Otherwise we need
324 | ;; subtraction of two vectors and a function to multiply a vector by
325 | ;; a constant.
326 |
327 | (defn dot-product [vector1 vector2]
328 | (reduce + (map * vector1 vector2)))
329 |
330 | (defn vector-subtraction [vector1 vector2]
331 | (map - vector1 vector2))
332 |
333 | ;; This function will introduce a shorthand for defining functions
334 | ;; that is very useful in combination with functions like `map` and
335 | ;; `reduce`. Rather than writing `(fn [args...] body)` you can use
336 | ;; `#(body)` and if you want access to the arguments use `%n` where
337 | ;; `n` is the position of the argument. If you are only expecting one
338 | ;; argument then you can use just `%` on its own.
339 |
340 | (defn vector-multiply-by-constant [vector constant]
341 | (map #(* % constant) vector))
342 |
343 |
344 | ;; Using the above functions we can now implement the vector bouncing
345 | ;; equation. I have pulled $(2 * (V.N) * N)$ out into a variable
346 | ;; called extreme for clarity.
347 |
348 | (defn bounce-vector [vector normal]
349 | (let [vector-dot-normal (dot-product vector normal)
350 | extreme (vector-multiply-by-constant normal (* 2 vector-dot-normal))]
351 | (vector-subtraction vector extreme)))
352 |
353 |
354 | ;; When an impact splatters it will only take a fraction of the
355 | ;; velocity, otherwise know as being an inelastic rather than elastic
356 | ;; collision. We can define a constant that will be
357 | ;; used to reduce the total velocity of the bounced vector to reflect
358 | ;; this elasticity.
359 |
360 |
361 | (def splatter-dampening-constant 0.7)
362 |
363 | (defn splatter-vector [velocity]
364 | (let [bounced-vector (bounce-vector velocity canvas-normal)]
365 | (vector-multiply-by-constant bounced-vector
366 | splatter-dampening-constant)))
367 |
368 |
369 |
370 |
371 |
372 |
373 | ;; ## Paths vs Points
374 |
375 | ;; All of the gestures Pollock makes are fluid paths, even if the
376 | ;; velocity along the path might be rather erratic. We now need to
377 | ;; work out how to generate a path of points that we can then use the
378 | ;; code we have written above to project and splatter.
379 |
380 | ;; A [Bezier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
381 | ;; is a commonly used curve for generating a smooth curve that can be
382 | ;; scaled indefinitely allowing us to have as many points along our
383 | ;; path as we care to calculate.
384 |
385 | ;; Bezier curves are defined by an list of control points, so we need
386 | ;; to be able to generate a potential unbounded list of random control
387 | ;; points that should give use limitless different paths to paint with.
388 |
389 | ;; In order to generate a list of control points we will need to be
390 | ;; able to:
391 |
392 | ;; - get a random number between two points for distance and steps,
393 | ;; - get a random unit vector for the initial direction of the generation,
394 | ;; - add vectors together to move between our control points.
395 |
396 | (defn random-between [lower-bound upper-bound]
397 | (+ lower-bound (rand (- upper-bound lower-bound))))
398 |
399 | ;; Below is an algorithm that will give well distributed random unit
400 | ;; vectors. It was ported from code found [in GameDev the
401 | ;; forums](http://www.gamedev.net/topic/499972-generate-a-random-unit-vector/#entry4261773).
402 |
403 | (defn random-unit-vector []
404 | (let [asimuth (* (rand) 2 Math/PI)
405 | k (- (rand 2) 1)
406 | a (Math/sqrt (- 1 (* k k)))
407 | i (* (Math/cos asimuth) a)
408 | j (* (Math/sin asimuth) a)]
409 | [i j k]))
410 |
411 | (defn vector-add [vector1 vector2]
412 | (map + vector1 vector2))
413 |
414 | ;; Now that we have a random direction in which to move we need to
415 | ;; generate an unbounded path that will move in that direction, but
416 | ;; randomise the position of each point within provided bounds.
417 |
418 | ;; Firstly, we can define a function that will generate a random
419 | ;; vector inside of lower and upper bounds that can be combined with
420 | ;; the non-randomised position to provide a randomised path.
421 |
422 | (defn random-vector-between [lower upper]
423 | [(random-between lower upper)
424 | (random-between lower upper)
425 | (random-between lower upper)])
426 |
427 | ;; In order to provide an unbounded path we can use a lazy sequence.
428 | ;; This function returns a value that is somewhat akin to list that
429 | ;; never ends. Every time you try to look at the next value in the list
430 | ;; it will generate one just in time for you to see no end.
431 |
432 | ;; In this function the first value returned should always be the
433 | ;; initial starting position, each following value should be a step
434 | ;; along the path. You can see this below, it returns the position
435 | ;; argument cons'd with another iteration of random-path with the
436 | ;; position randomised.
437 |
438 | (defn random-path [position step-vector bounds]
439 | (cons position
440 | (lazy-seq (random-path (vector-add (vector-add position step-vector)
441 | (random-vector-between (- 0 bounds) bounds))
442 | step-vector bounds))))
443 |
444 | ;; We can now use this random-path lazy sequence to generate a list of
445 | ;; control points given an initial starting point and some bounding
446 | ;; variables. The distance, step and variation allow us to request long
447 | ;; winding paths or short flicks.
448 |
449 | (defn control-points [position min-distance max-distance min-steps max-steps variation]
450 | (let [direction (random-unit-vector)
451 | distance (random-between min-distance max-distance)
452 | steps (random-between min-steps max-steps)
453 | step-vector (vector-multiply-by-constant direction (/ distance steps))
454 | random-positions (take steps (random-path position step-vector variation))
455 | end-position (vector-add position
456 | (vector-multiply-by-constant step-vector steps))]
457 | (conj (vec random-positions) end-position)))
458 |
459 |
460 | ;; In order to turn this list of control points into a list of points
461 | ;; that represent a path we need an algorithm. The most commonly used
462 | ;; is a recursive algorithm proved by De Casteljau. There is a [great
463 | ;; video on YouTube](https://www.youtube.com/watch?v=YATikPP2q70)
464 | ;; explaining this algorithm that I recommend you watch.
465 |
466 | ;; At the core of the algorithm is an equation that will return a
467 | ;; point along a line weighted by a variable, $t$ which dictates how
468 | ;; close it is to each end of the line:
469 |
470 | ;; $P = (1 - t)P_{0} + tP_{1}$
471 |
472 | ;; For example, if a line runs
473 | ;; from $P_{0}$ to $P_{1}$ and $t$ is 0 then the outputted point with
474 | ;; be equal to $P_{0}$ and if it is 1 then $P_{1}$.
475 |
476 | ;; De Casteljau's algorithm recursively creates a new set of points by
477 | ;; using the above equation for a fixed $t$ against all the lines
478 | ;; created by the control points. It does this until there is just a
479 | ;; single point, this is a point on the bezier curve. It $t$ from 0 to
480 | ;; 1 and for each step gets a point along the curve.
481 |
482 |
483 | (defn recur-relation [t a b]
484 | (+ (* t b) (* a (- 1 t))))
485 |
486 | (defn for-component [t component-vals]
487 | (if (= (count component-vals) 1)
488 | (first component-vals)
489 | (for-component t
490 | (map #(recur-relation t %1 %2) component-vals (rest component-vals)))))
491 |
492 | (defn for-t [t components]
493 | (map #(for-component t %) components))
494 |
495 | (defn de-casteljau [control-points step-amount]
496 | (let [x-vals (map first control-points)
497 | y-vals (map second control-points)
498 | z-vals (map #(nth % 2) control-points)
499 | points (map #(for-t % [x-vals y-vals z-vals]) (range 0 1 step-amount))]
500 | points))
501 |
502 | ;; This can generate paths that go below the canvas, we should set
503 | ;; these to 0 as it is the equivalent of painting on the canvas
504 |
505 | (defn ensure-above-canvas [path]
506 | (map (fn [[i j k]] [i (if (< j 0) 0 j) k]) path))
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 | ;; ## Motion, going through the paces
515 |
516 | ;; All the points along the generated path should have an associated
517 | ;; velocity. To start with we can generate a linear velocity along the
518 | ;; path, given a randomised total time to traverse the path and the
519 | ;; total length of the path.
520 |
521 | ;; In order to calculate the length of the paths, we will want to do
522 | ;; something similar to a map but with pairs of values. Using this we
523 | ;; can take two points, calculate the distance between them and then
524 | ;; sum all the distances
525 |
526 | (defn map-2 [f coll]
527 | (when-let [s (seq coll)]
528 | (let [s1 (first s)
529 | s2 (second s)]
530 | (if (not (nil? s2))
531 | (cons (f (first s) (second s)) (map-2 f (rest s)))))))
532 |
533 | ;; in order to find the distance between two points we need subtract
534 | ;; the two vectors, square and sum the resultant dimensions and then
535 | ;; take the root. (https://en.wikipedia.org/wiki/Euclidean_distance)
536 |
537 | (defn vector-multiply [vector1 vector2]
538 | (map * vector1 vector2))
539 |
540 | (defn distance-between-points [point1 point2]
541 | (let [difference-vector (vector-subtraction point1 point2)
542 | summed-vector (reduce + (vector-multiply difference-vector difference-vector))]
543 | (Math/sqrt summed-vector)))
544 |
545 | (defn path-length [path]
546 | (reduce + (map-2 distance-between-points path)))
547 |
548 |
549 | (defn vector-divide-by-const [vector const]
550 | (map #(/ % const) vector))
551 |
552 | (defn velocity-between [point1 point2 total-time total-distance]
553 | (let [difference-vector (vector-subtraction point1 point2)
554 | time-between (* total-time (/ (distance-between-points point1 point2)
555 | total-distance))]
556 | (vector-divide-by-const difference-vector time-between)))
557 |
558 | ;; This calculation will leave off the last points velocity, we can just set it to 0
559 |
560 | (defn path-velocities [path total-time]
561 | (let [total-distance (path-length path)
562 | number-of-points (count path)]
563 | (conj (vec (map-2 #(velocity-between %1 %2
564 | total-time
565 | total-distance)
566 | path))
567 | [0 0 0])))
568 |
569 | ;; As well as the velocity at each point along the path, we also need
570 | ;; how much paint there is falling. Again to keep life simple we are
571 | ;; going to model this as a linear flow along the path with there
572 | ;; always being no paint left.
573 |
574 | (defn path-masses [path initial-mass]
575 | (let [number-of-points (count path)
576 | step (- 0 (/ initial-mass number-of-points))]
577 | (take number-of-points (range initial-mass 0 step))))
578 |
579 |
580 |
581 |
582 |
583 |
584 | ;; ## Putting it all together
585 |
586 | ;; I've pulled a bunch of colours that Pollock used in his seminal
587 | ;; work "Number 8" so that each flick of paint can be rendered in a
588 | ;; random colour out of this palette
589 |
590 | (def canvas-colour [142 141 93])
591 |
592 | (def paint-colours
593 | [[232 51 1]
594 | [248 179 10]
595 | [247 239 189]
596 | [29 16 8]])
597 |
598 | (defn pick-a-colour []
599 | (nth paint-colours (rand-int (count paint-colours))))
600 |
601 | ;; Now we need to assemble all of the above functions into something
602 | ;; the resembles Jackson Pollock applying paint to a canvas. We start
603 | ;; with a point, project a path, work out masses and velocities,
604 | ;; project and then splatter. This is all then packaged up with a
605 | ;; colour for drawing onto our canvas.
606 |
607 | (defn fling-paint []
608 | (let [position (starting-point)
609 | total-time (random-between 1 5)
610 | path (ensure-above-canvas (de-casteljau (control-points position 0.1 2 3 15 0.4) 0.01))
611 | velocities (path-velocities path total-time)
612 | masses (path-masses path (random-between 0.1 1))
613 | projected-path (map #(project-point %1 %2) path velocities)
614 | splatter (map (fn [[position velocity] mass]
615 | (if (does-impact-splatter? mass velocity)
616 | [position (splatter-vector velocity) (* mass splatter-dampening-constant)]
617 | nil))
618 | projected-path masses)
619 | projected-splatter (map (fn [[position velocity mass :as point]]
620 | (if (nil? point)
621 | nil
622 | (conj (vec (project-point position velocity)) mass)))
623 | splatter)]
624 | {:colour (pick-a-colour)
625 | :air-path path
626 | :canvas-path (map #(conj %1 %2) projected-path masses)
627 | :splatter (filter #(not-any? nil? %) (partition-by nil? projected-splatter))}))
628 |
629 |
630 |
631 |
632 |
633 |
634 | ;; ## Rendering the canvas
635 |
636 | ;; We need to know the available size for the outputted image to fit
637 | ;; in. To work this out we are going to have to interface with
638 | ;; JavaScript directly. Luckily ClojureScript makes this very easy
639 | ;; using the `js` namespace.
640 |
641 | (def image-width (.-clientWidth (.querySelector js/document "#pollock")))
642 |
643 | ;; Now we have the width of the image we can use the dimensions of the
644 | ;; space to work out the pixel size of the image and how to convert
645 | ;; between metres and pixels.
646 |
647 | (def pixels-in-a-metre
648 | (let [[width _ _] space]
649 | (/ image-width width)))
650 |
651 | (defn metres-to-pixels [metres]
652 | (Math/floor (* metres pixels-in-a-metre)))
653 |
654 | ;; We can now use this function to work out the size the sketch should
655 | ;; be and how to convert a position in metres over to a position to be
656 | ;; drawn in the image.
657 |
658 | (def sketch-size
659 | (let [[width _ height] space]
660 | [(metres-to-pixels width)
661 | (metres-to-pixels height)]))
662 |
663 | (defn position-to-pixel [[i j k]]
664 | [(metres-to-pixels i)
665 | (metres-to-pixels k)])
666 |
667 |
668 | ;; Now the dimensions of the image our calculated we can use Quil to
669 | ;; define the sketch that we will draw into. We also need to define a
670 | ;; function that will initialise the image into the state we want it.
671 | ;; This function will be run when the sketch is defined.
672 |
673 | (defn setup-image []
674 | (apply q/background canvas-colour)
675 | (q/fill 0))
676 |
677 | (q/defsketch pollock
678 | :setup setup-image
679 | :host "pollock" ;; the id of the