deJong.coffee | |
---|---|
Constants and Values | |
Sensitivity of the sketch to mouse coordinates. | sensitivity = 0.02 |
Base of the iterations of the attractor per frame. | iterations = 8000 |
Each time the attractor hits a pixel, the density increases by... | density = 2 |
Start x,y coordinate for the attractor. | start = 0 |
The number of frames to render before being finished. | limit = 200 |
Size of the canvas in dimensional (not necessarily real) pixels. | size = 700
N = size * window.devicePixelRatio or 1 |
Create the | canvas = document.createElement 'canvas'
canvas.width = canvas.height = N
canvas.style.width = canvas.style.height = size + 'px'
canvas.style.marginTop = canvas.style.marginLeft = '-' + (size / 2) + 'px'
document.body.appendChild canvas
context = canvas.getContext '2d' |
Pull some handy math functions off the | {round, log, max, sin, cos} = Math |
Initialize the mouse coordinates to our | mouseX = mouseY = start |
Sketch | |
Our Sketch class handles drawing to the | class Sketch
constructor: ->
@steps = 0
@stopped = no
@button = document.getElementById 'permalink'
@button.addEventListener 'mousedown', @permalink, no
document.addEventListener 'mousedown', @pause, no
document.addEventListener 'mousemove', @record, no
document.addEventListener 'mouseup', @resume, no
@initialSeed()
@attractor = new DeJongAttractor
@loop()
loop: ->
@interval = setInterval @tick, 0
stopLoop: ->
@interval = clearInterval @interval |
Draw a single frame of the sketch. | tick: =>
return @attractor.reseed() if @stopped
@steps += 1
@attractor.plot 5
@stopLoop() if @steps > limit
|
Determine the initial seed. | initialSeed: ->
if hash = window.location.hash.replace('#', '')
[mouseX, mouseY] = (parseInt(num, 10) for num in hash.split(',')) |
Pause the drawing, when we've rendered enough steps. | pause: (e) =>
e.preventDefault()
@stopped = yes
@loop() unless @interval |
Record the current mouse position. | record: (e) =>
if @stopped
mouseX = e.pageX - canvas.offsetLeft
mouseY = e.pageY - canvas.offsetTop |
Resume drawing the sketch. | resume: =>
@stopped = no
@steps = 0
|
Save the permalink of the currently-drawn attractor to the URL. | permalink: (e) =>
e.stopPropagation()
window.location.hash = mouseX + ',' + mouseY |
DeJongAttractor | |
The DeJongAttractor contains the Peter De Jong algorithm. | class DeJongAttractor
constructor: ->
@reseed() |
Clear the recorded exposures before seeding at a different location. | clear: ->
@image = context.createImageData N, N
@density = (0 for i in [0...N] for j in [0...N])
@maxDensity = 0 |
Seed the sketch at the current position of the mouse. | seed: ->
@xSeed = (mouseX * 2 / N - 1) * sensitivity
@ySeed = (mouseY * 2 / N - 1) * sensitivity
[@x, @y] = [N / 2, N / 2] |
De Jong's attractor. Iterates a large number of times through random
coordinates in the attractor space, exposing the | populate: (samples) ->
for i in [0...samples * iterations]
x = ((sin(@xSeed * @y) - cos(@ySeed * @x)) * N * 0.2) + N / 2
y = ((sin(-@xSeed * @x) - cos(-@ySeed * @y)) * N * 0.2) + N / 2
@density[round x][round y] += density
[@x, @y] = [x, y]
@maxDensity = log max.apply(Math, (max.apply(Math, row) for row in @density))
reseed: ->
@clear()
@seed()
@plot 1 |
Soft light color blend between two brighness values. | softLight: (a, b) ->
((a * b) >> 7) + ((a * a) >> 8) - ((a * a * b) >> 15) |
Plots each pixel on the canvas as | plot: (samples) ->
@populate samples
data = @image.data
for i in [0...N]
for j in [0...N]
dens = @density[i][j]
idx = (i * N + j) * 4
data[idx + 3] = 255
continue if dens <= 0
light = log(dens) / @maxDensity * 255
current = data[idx]
color = @softLight light, current
data[idx] = data[idx + 1] = data[idx + 2] = color
context.putImageData @image, 0, 0 |
Kick it off by creating the sketch. | new Sketch()
|