The idea in plain words

Relativity says nothing can move through space faster than light. It puts no speed limit on space itself. Space can stretch and shrink, and it already does: because the universe expands, galaxies far enough away are receding from us faster than light right now, and no law is broken.

In 1994 Miguel Alcubierre used that loophole on a small scale. Shrink space in front of a ship and stretch it behind, and the ship is carried along inside a bubble of ordinary, flat space. Think of an airport moving walkway: you stand still and the floor takes you forward. The analogy has limits. The walkway moves through the building, while in the warp bubble nothing moves through space at all; the shape of space is what travels.

The ship feels nothing. It is in free fall, with no g-forces, and its clock ticks at the same rate as clocks far away. Seen from outside, the bubble can arrive sooner than a light beam sent along the same route through ordinary space.

The catch

Einstein's equations work like a recipe: matter and energy tell space how to curve. Alcubierre ran the recipe backwards. He chose the shape he wanted and asked what ingredients it needs. The answer is negative energy, a ring of it around the ship (switch the scene to Energy density). All ordinary matter has positive energy. Quantum physics allows tiny, brief patches of negative energy (the Casimir effect between two metal plates has been measured), but there are strict limits on how much and for how long. Applied to a warp bubble big enough for a person, those limits ask for more negative energy than the mass of the visible universe.

Is there a theorem?

Yes, in a precise sense. Ken Olum proved in 1998 that any spacetime that lets you beat light, in the sense of a shortcut that is faster than every nearby path, must contain negative energy somewhere. So this is not a flaw of Alcubierre's particular shape that a cleverer design could avoid. In 2021, Bobrick and Martire showed that warp bubbles made of positive energy are possible in principle, but only slower than light.

The equations

Units with c = G = 1. The ship sits at xs(t), moving at vs = dxs/dt; rs is the distance to it and ρ² = y² + z² the distance to its axis of motion.

  • Metric: ds² = −dt² + (dx − vs f(rs) dt)² + dy² + dz²
  • Shaping function: f(rs) = [tanh(σ(rs + R)) − tanh(σ(rs − R))] / [2 tanh(σR)]
  • York time: θ = vs (x − xs)/rs · df/drs
  • Energy density seen by observers at rest in the grid: T00 = −(1/8π) · vs² ρ² / (4 rs²) · (df/drs)²

Read the metric as a rule for measuring distances in spacetime. Far from the ship f = 0 and it is the flat spacetime of special relativity. Inside the bubble f = 1, so the term dx − vs dt says that "standing still" now means moving with the bubble.

What the picture does and does not say

  • Nothing moves faster than light locally. The superluminal speed is only "apparent", measured from far away.
  • Pfenning and Ford (1997) found that a bubble big enough for a person needs a wall about a hundred Planck lengths thick and a total negative energy far larger than the mass of the visible universe.
  • Natário (2002) showed that the expansion and contraction are not essential: a warp drive can be built with zero expansion. The ship is carried by the shift of space, not by "surfing" a wave.
  • Heights are normalised at vs = 1.5 c, so θ grows linearly with speed and the energy density with its square.

How it works

This part walks through the code of the scene, step by step. It covers:

  • Turning the shaping function into code, and its slope.
  • Computing York time and energy density from it.
  • Sampling a grid and scaling it into a surface.
  • Projecting 3D points onto a 2D canvas by hand.
  • Drawing thousands of lines fast enough for 60 frames per second.
  • Making the bubble travel, and letting you orbit it.

Everything is plain JavaScript and the Canvas 2D API: no 3D engine, no libraries. The complete script is about 300 lines.

1. The shaping function

The whole bubble is described by one function, f(r), where r is the distance from the ship. It should be 1 near the ship, 0 far away, and change smoothly in between. The hyperbolic tangent, tanh, is a smooth step from −1 to 1. Subtract two of them, shifted by R in opposite directions, and you get a smooth plateau of radius R. Dividing by 2 tanh(σR) makes the plateau exactly 1 at the centre.

function shape(r, sigma, R) {
  return (Math.tanh(sigma * (r + R)) - Math.tanh(sigma * (r - R)))
       / (2 * Math.tanh(sigma * R));
}

sigma controls how sharp the edge is. Move the Wall sharpness slider and watch the small plot: with low σ the plateau melts into a hill, with high σ it becomes a cliff.

2. Where things happen: the slope

Inside the bubble f is flat, and far away it is flat too. Space is only deformed where f changes, so every physical quantity depends on its slope, df/dr. The derivative of tanh(u) is 1/cosh²(u), also written sech²(u), and the chain rule adds a factor σ.

function sech2(u) {
  const c = Math.cosh(u);
  return 1 / (c * c);
}

function dShape(r, sigma, R) {
  return sigma * (sech2(sigma * (r + R)) - sech2(sigma * (r - R)))
       / (2 * Math.tanh(sigma * R));
}

The dashed orange curve in the small plot is −df/dr: a narrow peak at the wall.

Why not compute the slope numerically?

We could, with (f(r + h) − f(r − h)) / 2h. But we know the exact derivative, it costs the same, and it has no error to tune. Use the formula when you have it.

3. York time and energy density

With f and its slope we can compute the two quantities in Alcubierre's paper. York time, θ, measures how fast a small volume of space grows (θ > 0) or shrinks (θ < 0). The factor dx / r is +1 straight ahead of the ship and −1 straight behind it, and since the slope is negative, space shrinks in front and grows behind.

The energy density is what Einstein's equations demand to hold that shape. It has a minus sign in front of squares, so it can never be positive. The factor ρ² makes it zero on the line of motion, which is why it forms a ring.

// dx = x − x_s(t), rho = sqrt(y² + z²)
function field(mode, dx, rho, p) {
  const r = Math.hypot(dx, rho);
  if (mode === "shape") return shape(r, p.sigma, p.R);
  if (r < 1e-6) return 0;
  const df = dShape(r, p.sigma, p.R);
  if (mode === "york") return p.vs * (dx / r) * df;              // θ
  return -(1 / (8 * Math.PI))
       * (p.vs * p.vs * rho * rho) / (4 * r * r) * df * df;      // T⁰⁰
}
Why is a 2D plane enough?

The bubble is symmetric around the direction of motion: rotate it about the x axis and nothing changes. So every quantity depends only on dx, the distance along the motion, and rho, the distance from that axis. A plane through the axis shows everything. It is the same picture as Figure 1 in Alcubierre's paper.

4. From numbers to a surface

Now sample field on a grid of 81 × 81 points in the x–ρ plane, and use each value as a height. Each grid point becomes a 3D point (x, ρ, height), which we turn into a screen position with project (next step).

const L = 6;      // half-width of the plotted region along x
const RHO = 4.5;  // half-width along ρ
const N = 80;     // grid resolution

for (let i = 0; i <= N; i++) {
  const x = -L + (2 * L * i) / N;
  for (let j = 0; j <= N; j++) {
    const rho = -RHO + (2 * RHO * j) / N;
    const v = field(state.mode, x, Math.abs(rho), p) / refMax;
    const k = i * (N + 1) + j;
    vals[k] = v;
    pts[k] = project(x, rho, v * heightScale);
  }
}

The raw values have awkward sizes, so we divide by refMax, the largest value the field reaches. The trick is that we measure it at a fixed reference speed rather than the current one. That way, doubling vs visibly doubles the York time and quadruples the energy, as the equations say.

const VS_REF = 1.5;

function referenceMax(mode, p) {
  if (mode === "shape") return 1;
  const ref = { ...p, vs: VS_REF };
  let m = 0;
  for (let i = 0; i <= 400; i++) {
    const r = (i / 400) * (p.R + 4 / p.sigma);
    for (const a of [0, Math.PI / 4, Math.PI / 2]) {
      m = Math.max(m, Math.abs(field(mode, r * Math.cos(a), r * Math.sin(a), ref)));
    }
  }
  return m || 1;
}

5. 3D on a 2D canvas

The canvas only knows flat (x, y) pixels, so we do the 3D projection ourselves, in three moves. Imagine the grid on a turntable. First, spin the turntable around the vertical axis by the yaw angle. Then tilt it toward you by the pitch angle. Both are ordinary 2D rotations, written with sine and cosine. Finally, shrink points that are farther away, which gives perspective.

function project(x, y, z) {
  const ca = Math.cos(state.yaw), sa = Math.sin(state.yaw);
  const cp = Math.cos(state.pitch), sp = Math.sin(state.pitch);
  // 1. Turn the table around the vertical axis (yaw).
  const px = x * ca - y * sa;
  const py = x * sa + y * ca;
  // 2. Tilt it towards the camera (pitch).
  const sy = z * cp + py * sp;
  const depth = -z * sp + py * cp;
  // 3. Far things look smaller (perspective).
  const scale = Math.min(W, H * 1.7) / (1.9 * L);
  const persp = 22 / (22 + depth);
  return [W / 2 + px * scale * persp, H * 0.58 - sy * scale * persp];
}

The number 22 is the distance to the camera, in the same units as the grid. A smaller number makes the perspective stronger; a very large one makes it disappear.

6. Thousands of lines, fast

The wireframe has about 13,000 short segments, each coloured by its height. Calling stroke() 13,000 times per frame is slow. Instead, we sort segments into 19 buckets by value, collect each bucket into one Path2D, and stroke each bucket once: 19 draw calls per frame instead of 13,000.

const BUCKETS = 9;
const paths = Array.from({ length: BUCKETS * 2 + 1 }, () => new Path2D());
const bucketOf = (v) => Math.round(Math.max(-1, Math.min(1, v)) * BUCKETS) + BUCKETS;

const seg = (a, b) => {
  const path = paths[bucketOf((vals[a] + vals[b]) / 2)];
  path.moveTo(pts[a][0], pts[a][1]);
  path.lineTo(pts[b][0], pts[b][1]);
};
…
paths.forEach((path, b) => {
  ctx.strokeStyle = colorFor((b - BUCKETS) / BUCKETS);
  ctx.stroke(path);
});

Positive values (expansion) get orange, negative ones (contraction) blue, and the colour fades as the value approaches zero, so flat space almost disappears.

7. Making it travel

The first version moved the bubble across a fixed grid. It quickly left the screen. Physics suggests a better camera: inside the bubble the ship is at rest, so we ride along with it. The bubble stays centred and the grid of outside space slides backwards.

The grid lines across x are fixed to space, not to the ship. As the ship advances by center, each line shifts back by center modulo the line spacing. The double modulo keeps the result positive.

// const x = -L + (2 * L * i) / N;
const step = (2 * L) / N;
const shift = ((center % step) + step) % step;
for (let i = 0; i <= N; i++) {
  const x = Math.min(L, Math.max(-L, -L + i * step - shift));   // relative to the ship
  …
}
function tick(now) {
  const dt = Math.min(0.05, (now - last) / 1000);
  last = now;
  if (state.playing) {
    center += dt * state.vs * 1.6;
  }
  draw();
  requestAnimationFrame(tick);
}

dt is capped at 0.05 s, so when the tab comes back from the background the bubble does not jump. People who ask their system for reduced motion get the scene paused.

8. Orbiting

Dragging changes yaw and pitch. On touch screens only horizontal drags rotate the view, so vertical swipes still scroll the page.

canvas.addEventListener("pointermove", (e) => {
  if (!drag) return;
  state.yaw = drag.yaw + (e.clientX - drag.x) * 0.008;
  if (!drag.touch) {
    state.pitch = Math.max(0.05, Math.min(1.5, drag.pitch + (e.clientY - drag.y) * 0.006));
  }
});

Going further

  • Swap in Natário's zero-expansion metric and compare the pictures.
  • Move the surface to WebGL to raise the grid resolution.
  • Release test particles and integrate their paths through the bubble.

La idea en palabras simples

La relatividad dice que nada puede moverse a través del espacio más rápido que la luz. Pero no pone límite de velocidad al espacio mismo. El espacio puede estirarse y encogerse, y ya lo hace: como el universo se expande, las galaxias lo bastante lejanas se alejan de nosotros más rápido que la luz ahora mismo, sin romper ninguna ley.

En 1994 Miguel Alcubierre usó ese resquicio a pequeña escala. Si se encoge el espacio delante de una nave y se estira detrás, la nave viaja dentro de una burbuja de espacio normal y plano. Piensa en una cinta transportadora de aeropuerto: tú te quedas quieto y el suelo te lleva. La analogía tiene límites. La cinta se mueve a través del edificio; en la burbuja nada se mueve a través del espacio: lo que viaja es la forma del espacio.

La nave no siente nada. Está en caída libre, sin fuerzas g, y su reloj marcha al mismo ritmo que los relojes lejanos. Vista desde fuera, la burbuja puede llegar antes que un rayo de luz enviado por la misma ruta a través del espacio normal.

La trampa

Las ecuaciones de Einstein funcionan como una receta: la materia y la energía le dicen al espacio cómo curvarse. Alcubierre leyó la receta al revés. Eligió la forma que quería y preguntó qué ingredientes necesita. La respuesta es energía negativa, en un anillo alrededor de la nave (cambia la escena a Densidad de energía). Toda la materia ordinaria tiene energía positiva. La física cuántica permite zonas diminutas y breves de energía negativa (el efecto Casimir entre dos placas metálicas se ha medido), pero con límites estrictos de cantidad y duración. Aplicados a una burbuja donde quepa una persona, esos límites exigen más energía negativa que la masa de todo el universo visible.

¿Hay un teorema?

Sí, en un sentido preciso. Ken Olum demostró en 1998 que cualquier espaciotiempo que permita ganarle a la luz, en el sentido de un atajo más rápido que todos los caminos cercanos, tiene que contener energía negativa en alguna parte. Así que no es un defecto de la forma concreta de Alcubierre que un diseño más ingenioso pueda evitar. En 2021, Bobrick y Martire mostraron que las burbujas hechas de energía positiva son posibles en principio, pero solo más lentas que la luz.

Las ecuaciones

Unidades con c = G = 1. La nave está en xs(t) y se mueve a vs = dxs/dt; rs es la distancia a ella y ρ² = y² + z² la distancia a su eje de movimiento.

  • Métrica: ds² = −dt² + (dx − vs f(rs) dt)² + dy² + dz²
  • Función de forma: f(rs) = [tanh(σ(rs + R)) − tanh(σ(rs − R))] / [2 tanh(σR)]
  • Tiempo de York: θ = vs (x − xs)/rs · df/drs
  • Densidad de energía que miden los observadores en reposo en la malla: T00 = −(1/8π) · vs² ρ² / (4 rs²) · (df/drs)²

La métrica es una regla para medir distancias en el espaciotiempo. Lejos de la nave f = 0 y queda el espaciotiempo plano de la relatividad especial. Dentro de la burbuja f = 1, y el término dx − vs dt dice que "estar quieto" ahora significa moverse con la burbuja.

Qué dice la imagen y qué no

  • Nada se mueve más rápido que la luz localmente. La velocidad superlumínica es solo "aparente", medida desde lejos.
  • Pfenning y Ford (1997) calcularon que una burbuja para una persona necesita una pared de unas cien longitudes de Planck de grosor y una energía negativa total muy superior a la masa del universo visible.
  • Natário (2002) mostró que la expansión y la contracción no son esenciales: se puede construir un motor de curvatura con expansión cero. A la nave la lleva el desplazamiento del espacio, no una ola que "surfea".
  • Las alturas se normalizan a vs = 1,5 c: θ crece en proporción a la velocidad y la densidad de energía, con su cuadrado.

Cómo funciona

Esta parte recorre el código de la escena paso a paso. Trata:

  • Pasar la función de forma a código, junto con su pendiente.
  • Calcular con ella el tiempo de York y la densidad de energía.
  • Muestrear una malla y escalarla hasta formar una superficie.
  • Proyectar a mano puntos 3D sobre un canvas 2D.
  • Dibujar miles de líneas lo bastante rápido para ir a 60 fotogramas por segundo.
  • Hacer que la burbuja viaje y que puedas girar alrededor de ella.

Todo es JavaScript puro y la API Canvas 2D: sin motor 3D ni librerías. El script completo tiene unas 300 líneas. Los comentarios del código están en inglés, como en el repositorio.

1. La función de forma

Toda la burbuja se describe con una función, f(r), donde r es la distancia a la nave. Debe valer 1 cerca de la nave, 0 lejos y cambiar suavemente entre medio. La tangente hiperbólica, tanh, es un escalón suave de −1 a 1. Resta dos, desplazadas R en sentidos opuestos, y obtienes una meseta suave de radio R. Dividir entre 2 tanh(σR) hace que la meseta valga exactamente 1 en el centro.

function shape(r, sigma, R) {
  return (Math.tanh(sigma * (r + R)) - Math.tanh(sigma * (r - R)))
       / (2 * Math.tanh(sigma * R));
}

sigma controla lo abrupto del borde. Mueve el slider Nitidez de la pared y mira el gráfico pequeño: con σ bajo la meseta se funde en una colina; con σ alto, se vuelve un acantilado.

2. Dónde pasan las cosas: la pendiente

Dentro de la burbuja f es plana, y lejos también. El espacio solo se deforma donde f cambia, así que toda magnitud física depende de su pendiente, df/dr. La derivada de tanh(u) es 1/cosh²(u), también escrita sech²(u), y la regla de la cadena añade un factor σ.

function sech2(u) {
  const c = Math.cosh(u);
  return 1 / (c * c);
}

function dShape(r, sigma, R) {
  return sigma * (sech2(sigma * (r + R)) - sech2(sigma * (r - R)))
       / (2 * Math.tanh(sigma * R));
}

La curva naranja discontinua del gráfico pequeño es −df/dr: un pico estrecho en la pared.

¿Por qué no calcular la pendiente numéricamente?

Se podría, con (f(r + h) − f(r − h)) / 2h. Pero conocemos la derivada exacta, cuesta lo mismo y no tiene error que ajustar. Si tienes la fórmula, úsala.

3. Tiempo de York y densidad de energía

Con f y su pendiente se calculan las dos magnitudes del artículo de Alcubierre. El tiempo de York, θ, mide lo rápido que un pequeño volumen de espacio crece (θ > 0) o se encoge (θ < 0). El factor dx / r vale +1 justo delante de la nave y −1 justo detrás, y como la pendiente es negativa, el espacio se encoge delante y crece detrás.

La densidad de energía es lo que exigen las ecuaciones de Einstein para mantener esa forma. Lleva un signo menos delante de cuadrados, así que nunca puede ser positiva. El factor ρ² la anula sobre la línea de movimiento, y por eso forma un anillo.

// dx = x − x_s(t), rho = sqrt(y² + z²)
function field(mode, dx, rho, p) {
  const r = Math.hypot(dx, rho);
  if (mode === "shape") return shape(r, p.sigma, p.R);
  if (r < 1e-6) return 0;
  const df = dShape(r, p.sigma, p.R);
  if (mode === "york") return p.vs * (dx / r) * df;              // θ
  return -(1 / (8 * Math.PI))
       * (p.vs * p.vs * rho * rho) / (4 * r * r) * df * df;      // T⁰⁰
}
¿Por qué basta un plano 2D?

La burbuja es simétrica alrededor de la dirección de movimiento: gírala sobre el eje x y nada cambia. Así que cada magnitud depende solo de dx, la distancia a lo largo del movimiento, y de rho, la distancia a ese eje. Un plano que pase por el eje lo muestra todo. Es la misma imagen que la figura 1 del artículo de Alcubierre.

4. De números a superficie

Ahora se muestrea field en una malla de 81 × 81 puntos en el plano x–ρ y cada valor se usa como altura. Cada punto de la malla se vuelve un punto 3D (x, ρ, altura), que project convierte en una posición en pantalla (siguiente paso).

const L = 6;      // half-width of the plotted region along x
const RHO = 4.5;  // half-width along ρ
const N = 80;     // grid resolution

for (let i = 0; i <= N; i++) {
  const x = -L + (2 * L * i) / N;
  for (let j = 0; j <= N; j++) {
    const rho = -RHO + (2 * RHO * j) / N;
    const v = field(state.mode, x, Math.abs(rho), p) / refMax;
    const k = i * (N + 1) + j;
    vals[k] = v;
    pts[k] = project(x, rho, v * heightScale);
  }
}

Los valores crudos tienen tamaños incómodos, así que se dividen entre refMax, el mayor valor que alcanza el campo. El truco es medirlo a una velocidad de referencia fija y no a la actual. Así, duplicar vs duplica visiblemente el tiempo de York y cuadruplica la energía, como dicen las ecuaciones.

const VS_REF = 1.5;

function referenceMax(mode, p) {
  if (mode === "shape") return 1;
  const ref = { ...p, vs: VS_REF };
  let m = 0;
  for (let i = 0; i <= 400; i++) {
    const r = (i / 400) * (p.R + 4 / p.sigma);
    for (const a of [0, Math.PI / 4, Math.PI / 2]) {
      m = Math.max(m, Math.abs(field(mode, r * Math.cos(a), r * Math.sin(a), ref)));
    }
  }
  return m || 1;
}

5. 3D sobre un canvas 2D

El canvas solo conoce píxeles planos (x, y), así que la proyección 3D la hacemos nosotros, en tres movimientos. Imagina la malla sobre un plato giratorio. Primero, gira el plato alrededor del eje vertical con el ángulo yaw. Después, inclínalo hacia ti con el ángulo pitch. Las dos son rotaciones 2D corrientes, escritas con seno y coseno. Por último, encoge los puntos más lejanos, y eso da la perspectiva.

function project(x, y, z) {
  const ca = Math.cos(state.yaw), sa = Math.sin(state.yaw);
  const cp = Math.cos(state.pitch), sp = Math.sin(state.pitch);
  // 1. Turn the table around the vertical axis (yaw).
  const px = x * ca - y * sa;
  const py = x * sa + y * ca;
  // 2. Tilt it towards the camera (pitch).
  const sy = z * cp + py * sp;
  const depth = -z * sp + py * cp;
  // 3. Far things look smaller (perspective).
  const scale = Math.min(W, H * 1.7) / (1.9 * L);
  const persp = 22 / (22 + depth);
  return [W / 2 + px * scale * persp, H * 0.58 - sy * scale * persp];
}

El número 22 es la distancia a la cámara, en las mismas unidades que la malla. Un número menor exagera la perspectiva; uno muy grande la hace desaparecer.

6. Miles de líneas, rápido

La malla tiene unos 13 000 segmentos cortos, cada uno coloreado según su altura. Llamar a stroke() 13 000 veces por fotograma es lento. En su lugar, los segmentos se reparten en 19 cubetas según su valor, cada cubeta se junta en un solo Path2D y se dibuja una vez: 19 llamadas por fotograma en vez de 13 000.

const BUCKETS = 9;
const paths = Array.from({ length: BUCKETS * 2 + 1 }, () => new Path2D());
const bucketOf = (v) => Math.round(Math.max(-1, Math.min(1, v)) * BUCKETS) + BUCKETS;

const seg = (a, b) => {
  const path = paths[bucketOf((vals[a] + vals[b]) / 2)];
  path.moveTo(pts[a][0], pts[a][1]);
  path.lineTo(pts[b][0], pts[b][1]);
};
…
paths.forEach((path, b) => {
  ctx.strokeStyle = colorFor((b - BUCKETS) / BUCKETS);
  ctx.stroke(path);
});

Los valores positivos (expansión) van en naranja, los negativos (contracción) en azul, y el color se desvanece cerca de cero, así que el espacio plano casi desaparece.

7. Hacer que viaje

La primera versión movía la burbuja sobre una malla fija, y se salía de la pantalla enseguida. La física sugiere una cámara mejor: dentro de la burbuja la nave está en reposo, así que viajamos con ella. La burbuja queda centrada y la malla del espacio exterior se desliza hacia atrás.

Las líneas de la malla a lo largo de x están fijas al espacio, no a la nave. Cuando la nave avanza center, cada línea retrocede center módulo la separación entre líneas. El doble módulo mantiene el resultado positivo.

// const x = -L + (2 * L * i) / N;
const step = (2 * L) / N;
const shift = ((center % step) + step) % step;
for (let i = 0; i <= N; i++) {
  const x = Math.min(L, Math.max(-L, -L + i * step - shift));   // relative to the ship
  …
}
function tick(now) {
  const dt = Math.min(0.05, (now - last) / 1000);
  last = now;
  if (state.playing) {
    center += dt * state.vs * 1.6;
  }
  draw();
  requestAnimationFrame(tick);
}

dt se limita a 0,05 s, para que la burbuja no salte cuando la pestaña vuelve del segundo plano. A quien pide movimiento reducido en su sistema, la escena le arranca en pausa.

8. Girar la vista

Arrastrar cambia yaw y pitch. En pantallas táctiles solo el arrastre horizontal gira la vista, para que deslizar en vertical siga desplazando la página.

canvas.addEventListener("pointermove", (e) => {
  if (!drag) return;
  state.yaw = drag.yaw + (e.clientX - drag.x) * 0.008;
  if (!drag.touch) {
    state.pitch = Math.max(0.05, Math.min(1.5, drag.pitch + (e.clientY - drag.y) * 0.006));
  }
});

Para seguir

  • Cambiar a la métrica de expansión cero de Natário y comparar las imágenes.
  • Pasar la superficie a WebGL para subir la resolución de la malla.
  • Soltar partículas de prueba e integrar sus trayectorias a través de la burbuja.

SourcesFuentes

Tutorial format inspired by Catlike Coding.Formato de tutorial inspirado en Catlike Coding.