If we plot the escape times for points in this complex plane series, we end up in a world of burning ships.
The largest ship is located on the real axis at -1.75.
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.
Zooming in closer to the ships in the bottom left
The ship near -1.6 between the largest ship and the dark expanse.
Small ships near the edge of darkness
Zoomed in to the small ship near -1.57
Small ship and disorder near the expanse, with curving spires.
Left of the largest ship are some very small ships. The light ends at (-2, 0)
Zoomed in to a small ship near -1.94
The equation using complex numbers is this:
It's similar to the Mandelbrot equation:
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:
Fractal images are generated by changing the value of c:
If we convert to using x and y, the burning ship fractal equation reduces to::
A common way of exploring fractals is by putting different starting (x0, y0) numbers into the equation and making note of whether the series:
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.
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.
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.
Zoomed-in to various parts of the fractal world.
The mast of the largest ship
Distant view when approaching the expanse along the real axis near x = -1.5
The left edge of the world near x = -2.0
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
TODO
Source code