Burning ship fractal

x [-1.95000000, -1.45000000]
y [-0.09000000, 0.02000000]
Renderer: WebGL

If we plot the escape times for points in this complex plane series, we end up in a world of burning ships.

z n + 1 = ( R e ( z n ) + i I m ( z n ) ) 2 + c , i = 1 z_{n+1} = (|Re(z_n)| + i|Im(z_n)|)^2 + c, \quad{i = \sqrt{-1}}

The largest ship is located on the real axis at -1.75.

x [-1.80000000, -1.70000000]
y [-0.08000000, 0.01000000]

World overview

If we consider an escape radius of 2, the world also has a radius of 2, since any points starting outside of the world diverge right away.

x [-2.20000000, 2.20000000]
y [-2.20000000, 2.20000000]

The ships are located near the real axis (y = 0) among negative real numbers.

x [-2.50000000, 0.50000000]
y [-1.00000000, 0.50000000]

Zooming in closer to the ships in the bottom left

x [-1.81000000, -1.39000000]
y [-0.32000000, 0.03000000]

The ship near -1.6 between the largest ship and the dark expanse.

x [-1.65000000, -1.58000000]
y [-0.07400000, 0.01800000]

Small ships near the edge of darkness

x [-1.59000000, -1.49000000]
y [-0.05200000, 0.01400000]

Zoomed in to the small ship near -1.57

x [-1.58050000, -1.55000000]
y [-0.04000000, 0.00600000]

Small ship and disorder near the expanse, with curving spires.

x [-1.52900000, -1.49900000]
y [-0.06000000, 0.00500000]

Left of the largest ship are some very small ships. The light ends at (-2, 0)

x [-2.04000000, -1.66000000]
y [-0.14200000, 0.07200000]

Zoomed in to a small ship near -1.94

x [-1.94800000, -1.92500000]
y [-0.00950000, 0.00200000]

Burning ship fractal equation

The equation using complex numbers is this:

z n + 1 = ( R e ( z n ) + i I m ( z n ) ) 2 + c z_{n+1} = (|Re(z_n)| + i|Im(z_n)|)^2 + c

It's similar to the Mandelbrot equation:

z n + 1 = z n 2 + c z_{n+1} = z_n^2 + c

Except instead of squaring each number, we add the absolute values of the real and imaginary components together during each iteration and square the sum instead.

z is a complex number composed of real and imaginary numbers:

z = x + i y , i = 1 z = x + iy, \quad{i = \sqrt{-1}}

Fractal images are generated by changing the value of c:

c = ( x 0 , y 0 ) c = (x_0, y_0)

If we convert to using x and y, the burning ship fractal equation reduces to::

x n + 1 = x n 2 y n 2 + x 0 x_{n+1} = x_n^2 - y_n^2 + x_0
y n + 1 = 2 x n y n + y 0 y_{n+1} = 2|x_ny_n| + y_0

A common way of exploring fractals is by putting different starting (x0, y0) numbers into the equation and making note of whether the series:

  • diverges to infinity or stays within a limit
  • if the series diverges, we make note of:
    • the number of iterations until reaching the escape radius
    • the distance from zero (0, 0) during the escape

Fractal coloring

Escape time: given a starting point (x0, y0), we can give each point a color by calculating the number of iterations it takes for the series to diverge, and the distance from zero when escaping.

const ESCAPE_THRESHOLD = 4;
const MAX_ITERATIONS = 255;

function iterateUntilEscape(x0, y0) {
  let x = 0, y = 0;
  let numIterations = 0;
  while ((x*x + y*y < ESCAPE_THRESHOLD) &&
         (numIterations < MAX_ITERATIONS)) {
    const x_next = x*x - y*y + x0;
    const y_next = 2*Math.abs(x*y) + y0;
    x = x_next;
    y = y_next;
    numIterations++;
  }
  return [numIterations, x*x + y*y];
}

We'll set the RGBA colors of every pixel in the image based on the number of iterations. Here's an example of a fiery yellow/red color palette by scaling just the green and alpha values of each pixel.

function getColor(numIterations) {
  return [
    255,                // red
    numIterations * 7,  // green
    0,                  // blue
    numIterations * 15  // alpha
  ];
}

Here's a general function for drawing fractals onto canvas elements. Changing the iterateUntilEscape function will change the drawn fractal.

function drawFractal(canvasEl, xRange, yRange, getColor) {
  const canvasWidth = canvasEl.width;
  const canvasHeight = canvasEl.height;
  const context = canvasEl.getContext('2d');
  const canvasImageData =
    context.createImageData(canvasWidth, canvasHeight);
  const image = canvasImageData.data;
  for (let i = 0; i < canvasHeight; i++) {
    for (let j = 0; j < canvasWidth; j++) {
      const x0 = xRange[0] +
        j*(xRange[1] - xRange[0]) / canvasWidth;
      const y0 = yRange[0] +
        i*(yRange[1] - yRange[0]) / canvasHeight;
      const [numIterations, escapeDistance] =
        iterateUntilEscape(x0, y0);
      const pixelIndex = 4*i*canvasWidth + 4*j;
      if (numIterations !== MAX_ITERATIONS) {
        const mu = getMu(numIterations, escapeDistance);
        const pixels = getColor({ numIterations, mu });
        for (let p = 0; p < 4; p++) {
          image[pixelIndex + p] = pixels[p];
        }
      }
    }
  }
  context.putImageData(canvasImageData, 0, 0);
}

Here's the largest ship colored based on number of iterations alone.

x [-1.80000000, -1.70000000]
y [-0.08000000, 0.01000000]

By considering escape distances on a logarithmic scale, we can use this extra information to smooth the color transitions.

const ESCAPE_RADIUS = 2;

function getMu(numIterations, escapeDistance) {
  return numIterations + 1 -
    Math.log(escapeDistance) / Math.log(ESCAPE_RADIUS);
}

function getColor(mu) {
  return [ 255, mu * 7, 0, mu * 15 ];
}

Here's the same image as above colored with renormalized iteration counts.

x [-1.80000000, -1.70000000]
y [-0.08000000, 0.01000000]

Nebulabrots are a way of rendering fractals where pixels are colored based on how frequently they're visited while calculating the iterations until escape across the viewport.

Sub-structures

Zoomed-in to various parts of the fractal world.

The mast of the largest ship

x [-1.78250000, -1.76500000]
y [-0.06710000, -0.04940000]

Distant view when approaching the expanse along the real axis near x = -1.5

x [-1.51098700, -1.50908250]
y [-0.00296972, 0.00066197]

The left edge of the world near x = -2.0

x [-2.01000000, -1.94690000]
y [-0.02000000, 0.02000000]

About

All fractals on the page are interactive and rendered when they're in view. The renderer uses WebGL by default for high performance, falling back to canvas if WebGL is not available.

Fractal image controls when hovering

  • shift + scroll mouse wheel — zoom in/out
  • pinch — zoom in/out
  • click and drag — pan the view
  • cmd-z — undo the last camera action

TODO

  • Enable customizing colors
  • Enable resetting each canvas to its original state
  • Allow editing x and y ranges: text inputs or click/drag slider
  • Maybe show the (x0, y0) center of each canvas
  • Draw boxes when selecting parts of the canvas to zoom in on

Source code