# Burning ship fractal

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} = (|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.

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

The ships are located near the real axis (y = 0) among negative real numbers. x [-2.5, 0.5] y [-1, 0.5] .

Zooming in closer to the ships in the bottom left x [-1.81, -1.39] y [-0.32, 0.03] .

The ship near -1.6 between the largest ship and the dark expanse. x [-1.65, -1.58] y [-0.074, 0.018] .

Small ships near the edge of darkness x [-1.59, -1.49] y [-0.052, 0.014] .

Zoomed in to the small ship near -1.57 x [-1.5805, -1.55] y [-0.04, 0.006] .

Small ship and disorder near the expanse, with curving spires. x [-1.529, -1.499] y [-0.06, 0.005] .

Left of the largest ship are some very small ships. The light ends at (-2, 0) x [-2.04, -1.66] y [-0.142, 0.072] .

Zoomed in to a small ship near -1.94 x [-1.948, -1.925] y [-0.0095, 0.002] .

### Burning ship fractal equation

The equation using complex numbers is this:

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

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 + iy, \quad{i = \sqrt{-1}}$

Fractal images are generated by changing the value of c:

$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$
$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 +
j*(xRange - xRange) / canvasWidth;
const y0 = yRange +
i*(yRange - yRange) / 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.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 -
}

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

Here's the same image as above colored with renormalized iteration counts. 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 x [-1.7825, -1.765] y [-0.0671, -0.0494] .

Distant view when approaching the expanse along the real axis near x = -1.5 x [-1.510987, -1.5090825] y [-0.00296972, 0.00066197] .

The left edge of the world near x = -2.0 x [-2.01, -1.9469] y [-0.02, 0.02] .

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

Fractal image controls

• shift + scroll mouse wheel when hovering to zoom in/out
• click and drag to pan the view

TODO

• Enable undo'ing the last camera action with cmd-z
• Enable customizing colors
• Smoother rendering as camera views pan
• Optimize overall rendering performance
• Enable resetting each canvas to its original state
• Prevent page from scrolling while zooming
• 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
• Shift-drag to maintain the aspect ratio of the camera during zoom

Source code