DEV Community

Cover image for I built a fractal explorer where you write the math yourself and zoom 1e-290 deep
Artemii Karapetian
Artemii Karapetian

Posted on

I built a fractal explorer where you write the math yourself and zoom 1e-290 deep

Most fractal viewers give you a handful of hardcoded formulas and a zoom slider. I wanted something different. Type your own z(n) = f(z, w, c, n) and escape rule, and watch it render live, panned and zoomed like Desmos.

That became Make Your Fractal, it's open source, live in the browser, no signup, no build step on your end.

Editing a formula live, zooming and panning, then switching to Python mode

Two ways to define a fractal

  • Inline mode — three short expressions: f(z, w, c, n), rule(z, w, c, n), and a starting value z0(w, c).
  • Python mode — real def f(z, w, c, n): functions with if/elif/else, bounded for loops, and local variables, in a small constrained Python-like language I wrote a lexer/parser/compiler for from scratch.

Both compile down to the same typed AST, which gets transpiled to GLSL and run per-pixel in a WebGL2 fragment shader. That compiler is the part I'm most proud of, it's not string templating, it's a real small-language pipeline: tokenizer -> recursive-descent parser -> typed AST -> GLSL codegen, with a real/complex/bool type system so +, *, ** produce correct complex arithmetic instead of naive component-wise ops.

Cycling through presets and color palettes

The hard part: zooming past float32

Naive escape-time rendering falls apart around 1e-6 zoom, float32 just runs out of precision. The obvious fix, raising precision everywhere, works but is 2-4x slower per pixel and still eventually hits a wall.

So instead: compute one high-precision reference orbit per view, once, on the CPU, using arbitrary-precision decimal math. Every pixel then only tracks its own tiny delta from that orbit via automatic differentiation (dual numbers), compiled straight from the same AST the plain renderer uses in ordinary single-precision GLSL. No pixel ever adds a tiny number to a big one, so there's no precision left to lose. It's the same technique tools like Kalles Fraktaler use to reach 1e-300; this one self-caps around 1e-290, verified against float64 ground truth.

That alone isn't enough, even though deep reference orbits have periodic near-zero dips (satellite minibrots), and a pixel whose orbit diverges right at one of those dips compounds error from that point on: the classic perturbation "glitch", visible as speckling that can flicker between renders. So there's a rebasing pass that detects the moment a pixel's delta stops being small and restarts it from the orbit's start, without resetting its actual iteration count.

The eligibility for all this is automatic and scoped tightly: only single-expression iteration functions built from + - * /, integer powers, and a handful of complex ops qualify, everything else still renders, just capped at plain float32 depth. A little precision badge in the corner tells you which path you're on.

Try it

Presets ship for Mandelbrot, Julia, Burning Ship, Multibrot, Tricorn, and an angle-ruled variant with a custom escape condition but the point is to write your own. Would love feedback, bug reports, or your own formulas if you find something interesting.

Top comments (0)