Burning ship fractal

burning ship fractal
x [-1.95, -1.45]
y [-0.09, 0.02]
.

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.

burning ship fractal
x [-1.8, -1.7]
y [-0.08, 0.01]
.

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.

burning ship fractal
x [-2.2, 2.2]
y [-2.2, 2.2]
.

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

burning ship fractal
x [-2.5, 0.5]
y [-1, 0.5]
.

Zooming in closer to the ships in the bottom left

burning ship fractal
x [-1.81, -1.39]
y [-0.32, 0.03]
.

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

burning ship fractal
x [-1.65, -1.58]
y [-0.074, 0.018]
.

Small ships near the edge

burning ship fractal
x [-1.59, -1.49]
y [-0.052, 0.014]
.

Zoomed in to the small ship near -1.57

burning ship fractal
x [-1.5805, -1.55]
y [-0.04, 0.006]
.

Small ship and disorder near the expanse.

burning ship fractal
x [-1.529, -1.499]
y [-0.06, 0.005]
.

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

burning ship fractal
x [-2.04, -1.66]
y [-0.142, 0.072]
.

Zoomed in to a small ship near -1.94

burning ship fractal
x [-1.948, -1.925]
y [-0.0095, 0.002]
.

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, 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

One 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.

burning ship fractal
x [-1.8, -1.7]
y [-0.08, 0.01]
.

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.

burning ship fractal
x [-1.8, -1.7]
y [-0.08, 0.01]
.

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

burning ship fractal
x [-1.7825, -1.765]
y [-0.0671, -0.0494]
.

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

burning ship fractal
x [-1.510987, -1.5090825]
y [-0.00296972, 0.00066197]
.

The left edge of the world near x = -2.0

burning ship fractal
x [-2.01, -1.9469]
y [-0.02, 0.02]
.

About

All fractals on the page are interactive and rendered when they're in view.

Fractal image controls

TODO