Cómo crear formas orgánicas complejas con matemáticas en p5.js

Introduction

Este tutorial desglosa una animación de arte generativo compleja en cinco pasos sencillos. Cada paso introduce nuevos conceptos matemáticos, culminando en una hermosa animación orgánica que se asemeja a criaturas submarinas.

Este trabajo está inspirado en los elegantes y simples sketches creados por el programador y artista conocido como @yuruyurau. Un ejemplo destacado de su trabajo es el siguiente:

Este ejemplo demuestra un uso magistral de funciones matemáticas para crear movimiento fluido y orgánico con tan solo 261 caracteres de código en p5.js. A través de este tutorial, mi objetivo es decodificar y descubir las ideas matemáticas detrás de tales creaciones, transformando una corta, pero a su vez densa, pieza de código en un viaje de aprendizaje paso a paso. Mi meta es hacer estas poderosas técnicas accesibles y comprensibles, para mostrar cómo la complejidad visual intrincada puede surgir de la simplicidad matemática.


Paso 1: Estructura básica de cuadrícula

Comenzamos con la estructura más simple posible: una cuadrícula estática de puntos. Esto establece nuestra base para transformar índices lineales en coordenadas 2D.


let t = 0;

function setup() {
  createCanvas(500, 500);
  background(0);
  stroke(255);
}

function draw() {
  background(0);
  
  // Crear una cuadrícula de puntos
  for (let i = 0; i < 20000; i++) {
    // Convertir índice lineal a coordenadas 2D
    const x = i % 200;
    const y = floor(i / 200);
    
    // Centrar la cuadrícula
    const k = x - 100;
    const e = y - 50;
    
    // Colocar puntos con cierto espaciado
    const px = width / 2 + k * 2;
    const py = height / 2 + e * 2;
    
    point(px, py);
  }
}

Conceptos clave

  • Conversión de índice lineal a coordenada 2D: $x=i\mod 200$, $y = \lfloor i / 200 \rfloor$
  • Centrado de la cuadrícula: $k = x - 100$, $e = y - 50$


Paso 2: Introduciendo coordenadas polares y una animación suave

En este paso, introducimos coordenadas polares usando la función mag() para el cálculo de la distancia y atan2() para el cálculo del ángulo, y también la variable de tiempo t para animar.


let t = 0;

function setup() {
  createCanvas(500, 500);
  pixelDensity(2);
  background(0);
  stroke(255, 120);
}

function draw() {
  background(0, 40);

  t += 0.01; // Actualizar tiempo para la animación
  
  for (let i = 0; i < 20000; i++) {
    const x = i % 200;
    const y = floor(i / 200);
    
    const k = x - 100;
    const e = y - 50;
    
    // Calcular distancia desde el centro (magnitud)
    const o = mag(k, e) / 50;
    
    // Convertir a ángulo
    const c = atan2(e, k);
    
    // Patrón circular simple
    const px = width / 2 + (k + o * 10 * cos(c + t)) * 2;
    const py = height / 2 + (e + o * 10 * sin(c + t)) * 2;
    
    point(px, py);
  }
}

Conceptos clave

  • Coordenadas polares: $(r, \theta)$ donde $r = \sqrt{k^2 + e^2}$, $\theta = \arctan(e/k)$
  • Uso de mag(k, e) para la distancia: $o = \frac{\sqrt{k^2 + e^2}}{50}$
  • Variable de tiempo: $t$ incrementada en cada fotograma: $t = t + 0.01$
  • Desplazamiento circular dependiente del tiempo: $o \times 10 \times \cos(c + t)$ y $o \times 10 \times \sin(c + t)$


Paso 3: Añadiendo animación basada en tiempo y distorsión suave

Ahora creamos nuestro primer efecto de distorsión animado usando funciones sinusoidales.


let t = 0;

function setup() {
  createCanvas(500, 500);
  pixelDensity(2);
  background(0);
  stroke(255, 80);
}

function draw() {
  background(0, 40);
  
  t += 0.02;
  
  for (let i = 0; i < 20000; i++) {
    const x = i % 200;
    const y = floor(i / 200);
    
    const k = x - 100;
    const e = y - 50;
    
    const o = mag(k, e) / 50;
    const c = atan2(e, k);
    
    // Añadir oscilación basada en el tiempo
    const q = o * 20 * sin(c * 2 + t);
    
    const px = width / 2 + (k + q * cos(c)) * 2;
    const py = height / 2 + (e + q * sin(c)) * 2;
    
    point(px, py);
  }
}

Conceptos clave

  • Variable de tiempo: $t$ incrementada en cada fotograma: $t = t + 0.02$
  • Desplazamiento dependiente del tiempo: $q = o \times 20 \times \sin(2c + t)$
  • Primera aparición de movimiento ondulatorio


Paso 4: Funciones de deformación complejas

Aumentamos la complejidad añadiendo múltiples funciones trigonométricas y cambiando la escala de coordenadas.


let t = 0;

function setup() {
  createCanvas(500, 500);
  pixelDensity(2);
  background(0);
  stroke(255, 80);
}

function draw() {
  background(0, 40);
  
  t += 0.03;
  
  for (let i = 0; i < 30000; i++) {
    const x = i % 250;
    const y = floor(i / 250);
    
    // Centrado y escalado diferentes
    const k = x / 2 - 62.5;
    const e = y / 2 - 50;
    
    const o = mag(k, e) / 20;
    const c = o * e / 20 - t / 5;
    
    // Función de deformación más compleja
    const q = x + o * k * sin(2 * o - t);
    
    const px = width / 2 + q * sin(c);
    const py = height / 2 + (y / 2) * cos(2 * c - t) - q * cos(c);
    
    point(px, py);
  }
}

Conceptos clave

  • Escalado modificado: $k = x/2 - 62.5$, $e = y/2 - 50$
  • Cálculo de ángulo complejo: $c = \frac{o \times e}{20} - \frac{t}{5}$
  • Transformaciones en capas: $q = x + o \times k \times \sin(2o - t)$
  • Múltiples términos trigonométricos en la posición final


Paso 5: Animación orgánica final

El boceto completo con todas las transformaciones combinadas, creando el movimiento orgánico de criatura marina.


let t = 0;

function setup() {
  createCanvas(500, 500);
  pixelDensity(2);
  background(0);
  stroke(255, 80);
}

function draw() {
  background(0, 60);

  t += 0.05;

  for (let i = 0; i < 40000; i++) {
    // Cuadrícula de alta densidad con división de módulo diferente
    const x = i / 4 % 100;
    const y = floor(i / 150);

    // Recentrar con escalado
    const k = x / 4 - 12.5;
    const e = y / 9 - 9;

    // Cálculos de coordenadas polares
    const o = mag(k, e) / 9;
    const c = o * e / 30 - t / 8;

    // Función de desplazamiento compleja
    const q =
      x +
      cos(9 / (k + 1e-6)) +  // Evitar división por cero
      o * k *
      sin(4 * o - t);

    // Posición final con múltiples transformaciones
    const px = 0.9 * q * sin(c) + width / 2;
    const py =
      height / 2 +
      (y / 3.6) * cos(3 * c - t / 2) -
      (q / 2) * cos(c);

    point(px, py);
  }
}


Funciones matemáticas

La transformación completa usada en el último paso se puede expresar como: \begin{align*} x &= \frac{i}{4} \mod 100 \\ y &= \lfloor \frac{i}{150} \rfloor \\ k &= \frac{x}{4} - 12.5 \\ e &= \frac{y}{9} - 9 \\ o &= \frac{\sqrt{k^2 + e^2}}{9} \\ c &= \frac{o \times e}{30} - \frac{t}{8} \\ q &= x + \cos\left(\frac{9}{k + \epsilon}\right) + o \times k \times \sin(4o - t) \\ x' &= 0.9 \times q \times \sin(c) + \frac{\text{width}}{2} \\ y' &= \frac{\text{height}}{2} + \frac{y}{3.6} \times \cos(3c - t/2) - \frac{q}{2} \times \cos(c) \end{align*}

donde $\epsilon = 10^{-6}$ evita la división por cero.


Creando variaciones a través de la experimentación

Ahora que hemos deconstruido la animación hasta su núcleo matemático, el proceso creativo se convierte en un juego de exploración. Al ajustar las diversas constantes, factores de escala y frecuencias dentro de las fórmulas, puedes generar una variedad infinita de patrones y movimientos. Intenta modificar valores como el divisor en mag(k, e) / 9, los coeficientes delante de t, o los números dentro de funciones trigonométricas como sin(4 * o - t). Incluso puedes introducir nuevos términos o reemplazar los existentes con diferentes funciones como tan() o noise() de la biblioteca p5.js. Es en este proceso de prueba y error donde emergen patrones matemáticos inesperados y fantásticos, transformando el código de un algoritmo estático en una herramienta dinámica para el descubrimiento artístico y matemático.


Comentarios finales

Espero que los pasos descritos anteriormente brinden una mejor idea de cómo surge el movimiento orgánico complejo a partir de la superposición cuidadosa de transformaciones matemáticas simples. Al comenzar con conceptos básicos y añadir complejidad progresivamente, podemos crear animaciones sofisticadas que imitan fenómenos naturales. La idea clave es que los patrones intrincados pueden surgir de la interacción de funciones trigonométricas, coordenadas polares y variaciones basadas en el tiempo.



Gracias por llegar al final de este artículo. Si deseas puedes apoyarme en Patreon usando el siguiente enlace:

Con tu apoyo podré seguir escribiendo y compartiendo artículos y applets de matemáticas.

Comentarios

Entradas populares

Galileo Galilei y su ley de caída libre

¿Qué son las Partículas Lenia?

Breve historia del Cálculo