E.01 · Código abiertoOpen sourceCampo de un dipoloField of a dipole
E = Σ k·qᵢ·r̂ᵢ / rᵢ²
Dos cargas de signo opuesto y las líneas de su campo eléctrico, recalculadas en cada cuadro mientras las cargas orbitan con la música.Two opposite charges and the lines of their electric field, recomputed every frame while the charges orbit to the music.
Escena del teaserScene from the teaser
// 01La cienciaThe science
Una carga eléctrica cambia el espacio que la rodea: en cada punto aparece un campo que indica hacia dónde y con qué fuerza se movería otra carga puesta ahí. Con varias cargas, los campos simplemente se suman.
Las líneas de campo son curvas que en cada punto siguen la dirección del campo. En un dipolo salen de la carga positiva y terminan en la negativa, y donde están más juntas el campo es más intenso. Es la misma idea que muestran las limaduras de hierro alrededor de un imán.
An electric charge changes the space around it: at every point there is a field that tells where, and how strongly, another charge placed there would move. With several charges, the fields simply add up.
Field lines are curves that follow the direction of the field at every point. In a dipole they leave the positive charge and end at the negative one, and where they crowd together the field is stronger. It is the same idea iron filings show around a magnet.
// 02Cómo se calculaHow it is computed
- El campo en cada punto es la suma de la contribución de cada carga,
k·q·r̂ / r², normalizado: solo importa su dirección. - De cada carga salen líneas en número proporcional a
|q|, repartidas alrededor del eje que une las dos cargas. - Cada línea se integra con Runge-Kutta de cuarto orden con paso adaptativo: entre 1,2 y 7 px, más fino cerca de las cargas, donde el campo cambia rápido.
- La línea termina al llegar a una carga de signo contrario o al salir del cuadro. Las que van de la negativa a la positiva ya las dibujó la positiva, así que no se repiten.
- Todo se recalcula en cada cuadro: con cada golpe fuerte de la canción las cargas cambian de distancia, y en el riser se cierran en espiral hasta fundirse en el agujero negro.
- The field at each point is the sum of every charge’s contribution,
k·q·r̂ / r², normalized: only its direction matters. - Each charge emits a number of lines proportional to
|q|, spread around the axis joining both charges. - Each line is integrated with fourth-order Runge-Kutta and an adaptive step: between 1.2 and 7 px, finer near the charges, where the field changes fast.
- A line stops when it reaches a charge of opposite sign or leaves the frame. Lines from the negative charge to the positive one were already drawn from the positive side, so they are not repeated.
- Everything is recomputed every frame: on each strong hit of the song the charges change distance, and during the riser they spiral in until they merge into the black hole.
// 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.
Trazado de todas las líneas del dipolo con RK4 de paso adaptativo.Tracing every line of the dipole with adaptive-step RK4.
// Líneas de campo completas: salen de cada carga en número ∝ |q|, repartidas simétricamente alrededor del eje que// une las cargas; se integran con RK4 de paso adaptativo (más fino cerca de las cargas) hasta otra carga (y ahí se// cierran en su centro) o fuera del cuadro. Las que salen de una negativa y llegan a una positiva ya las dibujó la// positiva: no se repiten.type LineaCampo = { pts: Pt[]; llega: boolean };const lineasCampo = (d: Disp, cargas: Carga[], porUnidad: number): LineaCampo[] => { const E = (x: number, y: number): Pt => { let ex = 0; let ey = 0; for (const c of cargas) { const dx = x - c.x; const dy = y - c.y; const r2 = dx * dx + dy * dy + 1; const k = c.q / (r2 * Math.sqrt(r2)); ex += k * dx; ey += k * dy; } const n = Math.hypot(ex, ey) || 1; return [ex / n, ey / n]; }; const caja = [-420, -420, d.W + 420, d.H + 420]; const out: LineaCampo[] = []; cargas.forEach((c, ci) => { const n = Math.max(4, Math.round(porUnidad * Math.abs(c.q))); const otra = cargas[(ci + 1) % cargas.length]; const eje = Math.atan2(otra.y - c.y, otra.x - c.x); const s = c.q > 0 ? 1 : -1; for (let i = 0; i < n; i++) { const an = eje + ((i + 0.5) / n) * TAU; let x = c.x + 6 * Math.cos(an); let y = c.y + 6 * Math.sin(an); const pts: Pt[] = [[c.x, c.y], [x, y]]; let llega = false; let fin: Carga | null = null; for (let k = 0; k < 2600; k++) { let dmin = Infinity; for (const o of cargas) dmin = Math.min(dmin, Math.hypot(x - o.x, y - o.y)); const h = s * Math.min(7, Math.max(1.2, 0.18 * dmin)); const k1 = E(x, y); const k2 = E(x + (h / 2) * k1[0], y + (h / 2) * k1[1]); const k3 = E(x + (h / 2) * k2[0], y + (h / 2) * k2[1]); const k4 = E(x + h * k3[0], y + h * k3[1]); x += (h / 6) * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]); y += (h / 6) * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]); pts.push([x, y]); if (x < caja[0] || x > caja[2] || y < caja[1] || y > caja[3]) break; fin = cargas.find((o) => o !== c && o.q * c.q < 0 && Math.hypot(x - o.x, y - o.y) < 3.5) ?? null; if (fin) { pts.push([fin.x, fin.y]); llega = true; break; } } if (c.q < 0 && llega) continue; out.push({ pts, llega }); } }); return out;};
// 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/campo 790,840 ClaseYT # 3 · o recorrerla en Remotion Studio · or scrub it in Remotion Studio $ npm run dev