buddhabrot.coffee

The Buddhabrot is a relative of the Mandelbrot fractal. You iterate the usual Mandelbrot set for random points on the picure plane, and if the point exits at high speed, you re-iterate the point, recording its progress around the page. Here's the original Usenet post from 1993, discovering the rendering.

Adjustable constants. Tweak these in order to change the resolution and exposure of the buddhabrot.

iterations = 500
points     = 100000
limit      = 50

Grab the canvas and the drawing context from the DOM. N is the width and height dimension of the square canvas. canvas = document.getElementById 'canvas'

context = canvas.getContext '2d'
N       = canvas.width
image   = context.createImageData N, N

State variables for storing recorded exposures values, time elapsed, the maximum exposure seen so far, and so on...

exposures   = (0 for i in [0..N * N])
maxexposure = 0
time        = 0
drawing     = false
timer       = null

Each step of the draw, we plot thousands of steps of the function, calibrate the exposure, and render the buddha. If we've drawn more than limit times, the buddhabrot is detailed enough, and we can stop.

draw = ->
  clearInterval timer if time > limit
  time += 1
  plot()
  findMaxExposure()
  render()

Each plot takes points number of random locations in the canvas, and iterates them to see if they escape the Mandelbrot set. If so, we iterate the point again, recording the exposure.

plot = ->
  for n in [0...points]
    x = Math.random() * 3 - 2
    y = Math.random() * 3 - 1.5
    if iterate x, y, no
      iterate x, y, yes
  null

Perform iterations steps of the Mandelbrot function and return true if the point escapes the system. If shouldDraw is true, and the point is still on the page, increment the exposure.

iterate = (x0, y0, shouldDraw) ->
  x = y = 0
  for i in [0...iterations]
    xnew = x * x - y * y + x0
    ynew = 2 * x * y + y0
    if shouldDraw and i > 3
      drawnX = Math.round(N * (xnew + 2.0) / 3.0)
      drawnY = Math.round(N * (ynew + 1.5) / 3.0)
      if (0 <= drawnX < N) and (0 <= drawnY < N)
        exposures[drawnX * N + drawnY] += 1
    if (xnew * xnew + ynew * ynew) > 4
      return yes
    x = xnew
    y = ynew
  return no

Every so often, render the buddha by drawing the normalized exposures to the canvas, using the putImageData method.

render = ->
  data = image.data
  for i in [0...N]
    for j in [0...N]
      ramp = exposures[i * N + j] / (maxexposure / 2.5)
      ramp = 1 if ramp > 1
      idx  = (i * N + j) * 4
      data[idx] = data[idx + 1] = data[idx + 2] = ramp * 255
      data[idx + 3] = 255
  context.putImageData image, 0, 0

The array of exposures is too large to use Math.max in the browser, so to find the maximum exposure, search each value in the array.

findMaxExposure = ->
  for value in exposures when value > maxexposure
    maxexposure = value

Start the draw loop.

timer = setInterval draw, 0