E.06 · Código abiertoOpen sourceMandelbrot y JuliaMandelbrot and Julia

z ↦ z² + c

Un zoom profundo al valle de los caballitos de mar del conjunto de Mandelbrot, que en un golpe pasa a un conjunto de Julia.A deep zoom into the seahorse valley of the Mandelbrot set, which on one hit turns into a Julia set.

Escena del teaserScene from the teaser

// 01La cienciaThe science

Una regla mínima, z ↦ z² + c, repetida una y otra vez sobre los números complejos, separa los puntos cuya órbita se queda acotada de los que se escapan al infinito. La frontera entre ambos es un fractal: muestra detalle nuevo a cualquier escala.

En el conjunto de Mandelbrot cada píxel es un valor de c y la órbita parte de 0. En un conjunto de Julia se fija c y cada píxel es el punto de partida. Cada punto de Mandelbrot tiene su propia Julia.

A tiny rule, z ↦ z² + c, repeated over and over on complex numbers, separates the points whose orbit stays bounded from those that escape to infinity. The border between them is a fractal: it shows new detail at every scale.

In the Mandelbrot set each pixel is a value of c and the orbit starts at 0. In a Julia set c is fixed and each pixel is the starting point. Every point of Mandelbrot has its own Julia.

// 02Cómo se calculaHow it is computed

  1. Por cada píxel se itera z ↦ z² + c hasta que escapa o se llega al máximo de iteraciones, que crece con el zoom.
  2. Junto a z se itera su derivada, para estimar la distancia al borde del conjunto y hacerlo brillar con un trazo fino.
  3. El color usa el conteo suave de iteraciones, sin bandas, y un promedio de franjas (stripe average) para el detalle fino.
  4. El interior se ilumina con dos trampas de órbita, un círculo y una cruz. Los puntos de la cardioide y del bulbo principal se detectan antes, sin iterar.
  5. En el teaser el zoom salta un escalón en cada bombo y cambia de paleta en cada palmada; en el golpe de Julia, c = 0,7885·e^{iα} avanza un escalón por pulso y la cámara cae hacia su punto fijo, una espiral.
  1. For each pixel, z ↦ z² + c is iterated until it escapes or hits the iteration limit, which grows with the zoom.
  2. Alongside z, its derivative is iterated to estimate the distance to the border of the set and make it glow with a thin stroke.
  3. Color uses the smooth iteration count, with no banding, and a stripe average for fine detail.
  4. The interior is lit with two orbit traps, a circle and a cross. Points in the main cardioid and bulb are detected up front, without iterating.
  5. In the teaser the zoom jumps one step on every kick and changes palette on every clap; on the Julia hit, c = 0.7885·e^{iα} moves one step per beat and the camera falls towards its fixed point, a spiral.
TécnicaTechniqueConteo suave y estimación de distanciaSmooth count and distance estimation
MotorEngineCanvas 2D2D canvas
ArchivoFilesrc/escenas.ts
Cuadros (16:9)Frames (16:9)1570 – 1680

// 03El códigoThe code

Este es el código que corre en el video, tal como está en el repositorio. Todo es función del tiempo: cualquier cuadro se puede calcular solo y sale igual.

This is the code that runs in the video, exactly as it is in the repository. Everything is a function of time: any frame can be computed on its own and comes out the same.

src/escenas.ts · L1948–1962 Ver en GitHubView on GitHub

La iteración, con su derivada para estimar la distancia y las trampas de órbita.The iteration, with its derivative for distance estimation and the orbit traps.

      while (n < maxIt) {        const ndr = 2 * (zr * dr - zi * di) + (julia ? 0 : 1);        di = 2 * (zr * di + zi * dr);        dr = ndr;        const nr = zr * zr - zi * zi + cr;        zi = 2 * zr * zi + ci;        zr = nr;        r2 = zr * zr + zi * zi;        if (r2 > 1e6) break;        trap = Math.min(trap, Math.abs(r2 - 0.25));        trapP = Math.min(trapP, Math.abs(zr) * Math.abs(zi));        stPrev = st;        st += 0.5 + 0.5 * Math.sin(5 * Math.atan2(zi, zr));        n++;      }
src/escenas.ts · L1974–1984 Ver en GitHubView on GitHub

Conteo suave, promedio de franjas y brillo del borde.Smooth count, stripe average and edge glow.

        const zl = Math.sqrt(r2);        const lz = Math.log(zl);        const fr = 1 + Math.log2(Math.log(1e3) / lz); // fracción para interpolar el promedio de franjas        const nu = n + fr;        const stA = n > 1 ? st / n : 0;        const stB = n > 2 ? stPrev / (n - 1) : stA;        const stripe = lerp(stB, stA, cl(fr));        const dl = Math.hypot(dr, di) || 1e-20;        const dpx = (0.5 * zl * lz) / dl / esc;        paletaF(P, Math.log(nu + 1) * 0.3 + Math.sqrt(nu) * 0.11 + fase, col);        const borde = Math.exp(-dpx * 0.4) * 0.5;

// 04RecrearloRecreate it

Con Node 20 y Chrome alcanza para sacar los cuadros de esta escena. El video completo, con la canción y todas las simulaciones, se arma con los pasos del README.

Node 20 and Chrome are enough to render the frames of this scene. The full video, with the song and every simulation, is built with the steps in the README.

# 1 · clonar e instalar · clone and install
$ git clone https://github.com/4mser/bn-lectures-teaser.git
$ cd bn-lectures-teaser
$ npm ci
# 2 · cuadros sueltos de esta escena (16:9) · frames of this scene
$ node herramientas/cuadros.mjs out/fractal 1570,1680 ClaseYT
# 3 · o recorrerla en Remotion Studio · or scrub it in Remotion Studio
$ npm run dev