1

El Arte del Caos: generando fractales con Processing (Parte I)

Existe una propiedad matemática increíble que subyace en todas las estructuras naturales: el principio de autosimilitud; formas que se repiten dentro de sí mismas a escalas más pequeñas. Lo encontramos en la disposición de las ramas de los árboles, en las formas cambiantes de las nubes y en el perímetro de las líneas costeras.

Los matemáticos de la antiguedad siempre habían sentido una cierta aversión hacia las formas ásperas y caóticas de la naturaleza, centrándose en su lugar en figuras idealizadas (como los círculos o los triángulos), que podían ser expresadas mediante fórmulas muy elementales.

Sin embargo, a principios del siglo pasado, los matemáticos empezaron a interesarse por este extraño principio de autosimilitud presente en la naturaleza. Esto les permitió descubrir un tipo completamente nuevo de geometría, la geometría fractal, que permitía capturar la perfecta imperfección de la naturaleza mediante elegantes fórmulas matemáticas.

Un ejemplo de “fractal” en la naturaleza: cada flor del brócoli romanesco se asemeja al total (imagen de wikipedia)

Estas fórmulas pueden representarse gráficamente para crear un tipo de figuras geométricas muy especiales: los fractales. Lo más fascinante de estas figuras es que, a pesar de lo increíblemente intrincadas que son, pueden generarse por ordenador con algoritmos sencillos y muy pocas líneas de código.

En esta serie de artículos exploraremos varias formas de generar figuras fractales en lenguaje Processing y exploraremos algunos de los fractales más arquetípicos como El Conjunto de Mandelbrot , el Triángulo de Sierpinski o el Conjunto de Julia

En esta primera parte generaremos fractales a partir de sucesiones de números complejos e implementaremos el llamado “algoritmo de tiempo de escape”. El resultado serán imágenes de este tipo:

Para poder entender bien cada ejemplo de este tutorial, recomiendo tener algunas nociones elementales de Análisis Complejo: cómo operar con números complejos, cómo se define una sucesión en , etc.

Todos los códigos que aparecen están escritos con la versión 3.5.2 de Processing. Si utilizas otra versión puede que tengas que hacer modificaciones menores en la sintaxis de alguna función.

Dicho esto, empezamos.


El Conjunto de Mandelbrot

El Conjunto de Mandelbrot es el rey indiscutible de todos los fractales. Fue uno de los primeros en ser descubierto, y es uno de los más estudiados debido a sus propiedades fascinantes.

Representación gráfica del conjunto de Mandelbrot.

Aunque siempre se representa visualmente, el conjunto de Mandelbrot es un objeto matemático abstracto que se define como el conjunto de los puntos c para los cuales la siguiente sucesión está acotada:

Si alguna vez has hecho algún curso de Análisis Complejo, sabrás que no siempre es fácil ver si una sucesión está acotada. Por tanto, hoy vamos a arrojar el rigor matemático por la borda y supondremos que la sucesión tiende a infinito si, al calcular alguno de los términos, éste tiene módulo mayor que una cierta constante K (normalmente definiremos K = 2).

En cambio, si alcanzamos un máximo de iteraciones predefinido y no se ha dado esta condición, supondremos que la sucesión está acotada.

Algoritmo de tiempo de escape

Esto ya nos da una idea del método que podemos utilizar para dibujar el Conjunto de Mandelbrot. Para cada píxel (x,y) de la imagen, podemos calcular la sucesión zn con c = x + yi, y pintar el píxel de un color determinado según si la sucesión está acotada o no. Alternativamente, podemos crear un gradiente de color en función del número de iteraciones que se han calculado antes de que |zn| > 2.

Este método de generar fractales se conoce como el “algoritmo de tiempo de escape”. Vamos a ver algunos ejemplos.


Dibujar el Conjunto de Mandelbrot

Este primer código generará una ventana de 640×360 píxeles con un fondo blanco y calculará un máximo de 50 iteraciones de la sucesión zn para cada uno de los píxeles. Los píxeles cuya sucesión NO esté acotada serán pintados de color negro.

//Definimos el máximo de iteraciones:
int MAXITER = 50;
 
// Definimos las dimensiones de la ventana:
int anchoPantalla = 640;
int altoPantalla = 360;

// Definimos la cota del módulo:
float K = 2;
 
void settings()
{
  //Creamos una ventana de 640x360:
  size(anchoPantalla, altoPantalla);
   
  // Desactivamos el bucle principal. Esto
  // hará que draw() sólo se ejecute una vez:
  noLoop();
   
  // Desactivamos el suavizado para tener más velocidad:
  smooth(0);
}
 
void draw()
{
  // Pintamos de blanco el fondo de la ventana:
  background(255);
   
  //Recorremos cada píxel de la pantalla con un doble bucle 'for':
  for(int x = 0; x < anchoPantalla; x++)
  {
    for(int y = 0; y < altoPantalla; y++)
    {
      // Definimos el valor de C a partir de la posición del píxel (x,y):
      float cx = map(x, 0, anchoPantalla, -2.5,1); //Parte real
      float cy = map(y, 0, altoPantalla, -1,1); //Parte imaginaria
       
      // Definimos las variables para guardar la sucesión:
      float zx = 0.0; //Parte real
      float zy = 0.0; //Parte imaginaria
       
      // Creamos un contador para las iteraciones:
      int n = 0;
       
      // Vamos calculando términos de la sucesión mientras
      // |z| < 2 y no lleguemos al máx. de iteraciones:
      while(zx*zx + zy*zy <= K*K && n < MAXITER)
      {
        // Calculamos z = z^2+C:
        float aux = zx*zx - zy*zy + cx;
        zy = 2*zx*zy + cy;
        zx = aux;
        n++;
      }
       
      // Si hemos llegado al máximo de iteraciones,
      // pintamos el píxel actual de color negro:
      if(n == MAXITER) {
          stroke(0);
          point(x,y);
      }
       
    }
  }
}

Si copias este código a tu IDE de Procesing y lo ejecutas, verás que aparece una ventana con una representación del Conjunto de Mandelbrot en blanco y negro puro:

Este será el resultado. La región negra son los puntos c del plano complejo para los cuales la sucesión está acotada.

Fíjemonos que a la hora de definir cx y cy (que son, respectivamente, la parte real y la parte imaginaria de c) hemos escalado los valores (x,y) del píxel a un rango pequeño utilizando la función map(). Esto es así porque la región de interés del Conjunto de Mandelbrot se encuentra en un entorno pequeñito alrededor del punto (0+0i). Si utilizásemos directamente el valor de x y de y para definir c no veríamos nada interesante en la imagen generada.

Otra cosa divertida que podemos hacer es pintar cada píxel de un color distinto en función del valor que tiene la variable ‘n’ al salir del bucle while:

  • Si n alcanza el máximo de iteraciones, pintamos el píxel de color negro.
  • Si no, asignamos un color al píxel en función del valor de n en relación al máximo de iteraciones.

Este segundo código utiliza la función map() para asignar un color a cada píxel. Para hacerlo, calcula el cociente de n / MAXITER (que estará entre 0 y 1) y lo mapea a un valor entre 0 y 255: el resultado será el canal Hue (tinte) del píxel en el espacio de color HSB:

//Definimos el máximo de iteraciones:
int MAXITER = 64;
//(Esta vez hemos aumentado el máximo de iter. a 64)
 
//Definimos las dimensiones de la ventana:
int anchoPantalla = 640;
int altoPantalla = 360;

//Definimos la cota del módulo:
float K = 2;
 
void settings()
{
  //Creamos una ventana de 640x360:
  size(anchoPantalla, altoPantalla);
   
  // Desactivamos el bucle principal:
  noLoop();
   
  // Desactivamos el suavizado:
  smooth(0);
}

void setup()
{
  //Cambiamos el espacio de color a HSB (Hue-Saturation-Brightness):
  colorMode(HSB); 
}
 
void draw()
{   
  //Recorremos cada píxel de la pantalla con un doble bucle 'for':
  for(int x = 0; x < anchoPantalla; x++)
  {
    for(int y = 0; y < altoPantalla; y++)
    {
      // Definimos el valor de C a partir de la posición del píxel (x,y):
      float cx = map(x, 0, anchoPantalla, -2.5,1); //Parte real
      float cy = map(y, 0, altoPantalla, -1,1); //Parte imaginaria
       
      // Definimos las variables para guardar la sucesión:
      float zx = 0.0; //Parte real
      float zy = 0.0; //Parte imaginaria
       
      // Creamos un contador para el número de iteración:
      int n = 0;
       
      // Vamos calculando términos de la sucesión mientras
      // |z| < 2 y no lleguemos al máx. de iteraciones:
      while(zx*zx + zy*zy <= K*K && n < MAXITER)
      {
        // Calculamos z = z^2+C:
        float aux = zx*zx - zy*zy + cx;
        zy = 2*zx*zy + cy;
        zx = aux;
        n++;
      }
       
      // Si hemos llegado al máximo de iteraciones,
      // elegimos el color negro:
      if(n == MAXITER) stroke(0);
       
      // Si no, pintamos el píxel de un color diferente
      // según el valor de n/MAXITER:
      else
      {
        //Elegimos una tonalidad de color:
        int tonalidad = int(map(float(n)/MAXITER, 0, 1, 0, 255));
        int saturacion = 255;
        int luminosidad = 255;
        stroke(tonalidad, saturacion, luminosidad);
      }
       
      //Pintamos el píxel:
      point(x,y);     
       
    }
  }
}

Como probablemente ya habrás visto, cuanto mayor sea el número de iteraciones más precisa será la representación gráfica del fractal. Si variamos el máximo de iteraciones en función del tiempo, podemos crear una bonita animación que muestre la formación del Conjunto de Mandelbrot.

El siguiente código irá aumentando progresivamente el número máximo de iteraciones desde 1 a 100 y mostrará el resultado en una animación. También se ha limitado a 0-90 el rango de colores que pueden tomar los píxeles: de esta forma, los tonos serán más uniformes.

//Definimos el número máximo total de iteraciones:
int MAXITER = 100;
 
//Definimos el número inicial de iteraciones:
int M = 1;
 
//Definimos las dimensiones de la ventana:
int anchoPantalla = 640;
int altoPantalla = 360;

//Definimos la cota del módulo:
float K = 2;
 
void settings()
{
  //Creamos una ventana de 640x360:
  size(anchoPantalla, altoPantalla);
   
  // Desactivamos el suavizado:
  smooth(0);
}
 
void setup()
{
  //Establecemos el Frame-rate a 10 FPS:
  frameRate(10);
  
   //Cambiamos el espacio de color a HSB:
   colorMode(HSB); 
}
 
void draw()
{

   
  //Recorremos cada píxel de la pantalla con un doble bucle 'for':
  for(int x = 0; x < anchoPantalla; x++)
  {
    for(int y = 0; y < altoPantalla; y++)
    {
      // Definimos el valor de C:
      float cx = map(x, 0, anchoPantalla, -2.5,1); //Parte real
      float cy = map(y, 0, altoPantalla, -1,1); //Parte imaginaria
       
      // Definimos las variables para guardar la sucesión:
      float zx = 0.0; //Parte real
      float zy = 0.0; //Parte imaginaria
       
      // Creamos un contador para el número de iteración:
      int n = 0;
       
      // Vamos calculando términos de la sucesión mientras
      // |z| < 2 y no lleguemos al máx. de iteraciones:
      while(zx*zx + zy*zy <= K*K && n < M)
      {
        // Calculamos z = z^2+C:
        float aux = zx*zx - zy*zy + cx;
        zy = 2*zx*zy + cy;
        zx = aux;
        n++;
      }
       
      // Si hemos llegado al máximo de iteraciones,
      // elegimos el color negro para el píxel:
      if(n == M) stroke(0);
       
      // Si no, pintamos el píxel de un color diferente
      // según el valor de n/MAXITER:
      else
      {
        //Elegimos una tonalidad de color entre 0 (rojo) y 90 (verde):
        int tonalidad = int(map(float(n)/M, 0, 1, 0, 90));
        int saturacion = 255;
        int luminosidad = 255;
        stroke(tonalidad, saturacion, luminosidad);
      }
       
      //Pintamos el píxel:
      point(x,y);     
    }
  }
   
  // Sumamos +1 al contador de iteraciones y desactivamos
  // el bucle si hemos alcanzado el máximo:
  M++;
  if(M == MAXITER) noLoop();
}

El Conjunto de Julia

El Conjunto de Julia es otro fractal que se genera con una sucesión muy parecida a la del Conjunto de Mandelbrot, pero con algunas diferencias.

Para construir este conjunto empezamos definiendo un punto c cualquiera al que llamaremos centro. Después, para cada punto z se define la siguiente sucesión recurrente:

Si la sucesión es acotada, decimos que z pertenece al Conjunto de Julia de centro c.

¿Qué cambios habrá que hacer en el algoritmo de Processing para poder dibujar conjuntos de Julia? Pues muy pocos, en realidad. Definiremos un centro c = (cx, cy) que no variará en todo el programa. Después, recorreremos cada píxel (x,y) de la imagen y calcularemos la sucesión partiendo de este punto como término inicial z.

El código que hay aquí debajo dibujará el Conjunto de Julia de centro (0.285 – 0.01i) con 250 iteraciones en una ventana de 750×750. También se ha ajustado el rango de colores para que vaya de 180 (azul) a 30 (naranja):

// Máximo de iteraciones:
int MAXITER = 250;
 
// Dimensiones de la ventana:
int anchoPantalla = 750;
int altoPantalla = 750;
 
// Definimos el centro c:
float cx = 0.285;
float cy = -0.01;

//Definimos la cota del módulo:
float K = 2;
 
void settings()
{
  // Creamos una ventana de 750x750:
  size(anchoPantalla, altoPantalla);
   
  // Desactivamos el suavizado:
  noSmooth();
   
  // Desactiamos el bucle principal:
  noLoop();
}
 
void setup()
{
  // Cambiamos del espacio de color RGB al HSB:
  colorMode(HSB);
}
 
void draw()
{
  // Recorremos cada píxel de la pantalla con un doble bucle:
  for(int x = 0; x < anchoPantalla; x++)
  {
    for(int y = 0; y < altoPantalla; y++)
    {     
      // Calculamos z0 a partir de las coord. (x,y) del píxel:
      float zx = map(x, 0, anchoPantalla, -1, 1);
      float zy = map(y, 0, altoPantalla, -1.2,1.2);
      
      // Contador de iteraciones:
      int n = 0;
       
      // Vamos calculando nuevos términos de la sucesión mientras
      // |z| < 2 y no lleguemos al máx. de iteraciones:
      while(zx*zx + zy*zy <= K*K && n < MAXITER)
      {
        //Calculamos z = z^2 + c :
        float aux = zx*zx - zy*zy + cx;
        zy = 2*zx*zy + cy;
        zx = aux;
        n++;
      }
       
      // Si n ha alcanzado el máximo de iteraciones,
      // elegimos el color negro para el píxel:
      if(n == MAXITER) stroke(0);
       
      // Si no, elegimos una tonalidad de color para el píxel:
      else
      {
        // Elegimos un color entre 180 (azul) y 30 (naranja)
        int tonalidad = int(map(float(n)/MAXITER, 0, 1, 180, 30));
        int saturacion = 255;
        int luminosidad = 255;
        stroke(tonalidad, saturacion, luminosidad);
      }
       
      //Por último, pintamos el píxel:
      point(x,y);
    }
  }
}

Conjunto de Julia de centro (0.285 – 0.01i)

Como es de esperar, si cambiamos los valores del centro, el Conjunto de Julia también será diferente:

Conjunto de Julia con c = (0.4 + 0i) y 30 iteraciones.

Conjunto de Julia con c = (-0.4 + 0.6i) y 300 iteraciones.

En este otro ejemplo vamos variando el centro en función del tiempo y visualizamos los conjuntos resultantes en una animación. He reducido el tamaño de la ventana y el número de iteraciones para que la animación tenga más fluidez.

// Máximo de iteraciones:
int MAXITER = 64;

// Dimensiones de la ventana:
int anchoPantalla = 400;
int altoPantalla = 400;

// Variable para calcular el centro:
float t = 0;

// Definimos el la cota del módulo:
float K = 2;

void settings()
{
  // Creamos la ventana:
  size(anchoPantalla, altoPantalla);
  
  // Desactivamos el suavizado:
  noSmooth();
  
}

void setup()
{
  // Cambiamos del espacio de color RGB al HSB:
  colorMode(HSB);
  
  //Establecemos el Frame-Rate a 24 FPS:
  frameRate(24);
}

void draw()
{
  // Alteramos ligeramente el centro:
  t = t + 0.1;
  float cx = sin(t);
  float cy = cos(t);
  if(t >= 2*PI) t = 0;
  
  // Recorremos cada píxel de la pantalla con un doble bucle:
  for(int x = 0; x < anchoPantalla; x++)
  {
    for(int y = 0; y < altoPantalla; y++)
    {      
      // Calculamos z0 a partir de las coord. del píxel:
      float zx = map(x, 0, anchoPantalla, -1, 1);
      float zy = map(y, 0, altoPantalla, -1.2,1.2);
      
      // Contador de iteraciones:
      int n = 0;
      
      // Vamos calculando términos de la sucesión mientras
      // |z| < 2 y no lleguemos al máx. de iteraciones:
      while(zx*zx + zy*zy <= K*K && n < MAXITER)
      {
        //Calculamos z = z^2 + c :
        float aux = zx*zx - zy*zy +cx;
        zy = 2*zx*zy + cy;
        zx = aux;
        n++;
      }
      
      // Si n ha alcanzado el máximo de iteraciones, 
      // elegimos el color negro para el píxel.
      if(n == MAXITER) stroke(0);
      
      // Si no, pintamos el píxel de un color diferente
      // según el valor de n/MAXITER:
      else
      {
        // Elegimos un color entre 180 (azul) y 30 (naranja)
        int tonalidad = int(map(float(n)/MAXITER, 0, 1, 180, 30));
        int saturacion = 255;
        int luminosidad = 255;
        stroke(tonalidad, saturacion, luminosidad);
      }
      
      //Por último, pintamos el píxel:
      point(x,y);
    }
  }
}

Burning Ship Fractal

Otro fractal interesante que podemos intentar generar es el conocido fractal del Barco en llamas (en inglés, the Burning Ship fractal). Este fractal se genera a partir de un punto c del plano complejo y la sucesión:

(Recordemos que Re(zn) indica la parte real de zn y Im(zn) su parte imaginaria).

A la hora de iterar esta sucesión, consideraremos que no está acotada si alguno de los términos calculados cumple que |zn| > 10. El conjunto del barco en llamas será, pues, el conjunto de los puntos c para los cuales la sucesión no está acotada.

// Máximo de iteraciones:
int MAXITER = 24;
 
// Dimensiones de la ventana:
int anchoPantalla = 800;
int altoPantalla = 600;

// Cota para el módulo:
float K = sqrt(10);
 
void settings()
{
  // Creamos la ventana:
  size(anchoPantalla, altoPantalla);
   
  // Desactivamos el suavizado:
  noSmooth();
   
  // Desactiamos el bucle principal:
  noLoop();
   
}
 
void setup()
{
  // Cambiamos del espacio de color RGB al HSB:
  colorMode(HSB);
}
 
void draw()
{
   
  // Recorremos cada píxel de la pantalla con un doble bucle:
  for(int x = 0; x < anchoPantalla; x++)
  {
    for(int y = 0; y < altoPantalla; y++)
    {
      // Calculamos el valor de c:
      float cx = map(x, 0, anchoPantalla, -2.3, 1.5);
      float cy = map(y, 0, altoPantalla, -1.8,0.7);
       
      // Variables para guardar la sucesión:
      float zx = 0.0;
      float zy = 0.0;
       
      // Contador de iteraciones:
      int n = 0;
       
      // Vamos calculando términos mientras |z| < K y
      // 'n' no llegue al máximo de iteraciones:
      while(zx*zx + zy*zy <= K*K && n < MAXITER)
      {
        //Calculamos z = (|Re(z)|+i|Im(z)|)^2 + c :
        float aux = zx*zx - zy*zy + cx;
        zy = abs(2*zx*zy) + cy;
        zx = aux;
        n++;
      }
       
      // Si 'n' ha alcanzado el máximo de iteraciones,
      // elegimos el color negro para el píxel:
      if(n == MAXITER) stroke(0);
       
      // Si no, elegimos una tonalidad de color para el píxel:
      else
      {
        // Elegimos un color entre 180 (azul) y 40 (amarillo):
        int tonalidad = int(map(float(n)/MAXITER, 0, 1, 180, 40));
        int saturacion = 255;
        int luminosidad = 255;
        stroke(tonalidad, saturacion, luminosidad);
      }
       
      //Por último, pintamos el píxel:
      point(x,y);
    }
  }
}

El resultado es un fractal que recuerda (con un poco de imaginación) a un barco en llamas hundiéndose en el mar…

 

También podemos buscar el Conjunto de Julia correspondiente a la sucesión del Barco en Llamas. Ahora el término z0 de la sucesión será el píxel y c será una constante que no variará en todo el programa.

En este ejemplo utilizaremos c = 0.598 -0.9225i, cuyo resultado es impresionante:

// Máximo de iteraciones:
int MAXITER = 64;

// Dimensiones de la ventana:
int anchoPantalla = 800;
int altoPantalla = 600;

// Definimos el centro c: 
float cx = 0.598; 
float cy = -0.9225;

// Definimos la cota para el módulo:
float K = sqrt(10);

void settings()
{
  // Creamos la ventana:
  size(anchoPantalla, altoPantalla);
  
  // Desactivamos el suavizado:
  noSmooth();
  
  // Desactiamos el bucle principal:
  noLoop();
  
}

void setup()
{
  // Cambiamos del espacio de color RGB al HSB:
  colorMode(HSB);
}

void draw()
{
  
  // Recorremos cada píxel de la pantalla con un doble bucle:
  for(int x = 0; x < anchoPantalla; x++)
  {
    for(int y = 0; y < altoPantalla; y++)
    {      
      // Calculamos z0 a partir de las coord. del píxel:
      float zx = map(x, 0, anchoPantalla, -2, 2);
      float zy = map(y, 0, altoPantalla, -1.5,1.5);
      
      // Contador de iteraciones:
      int n = 0;
      
      // Vamos calculando términos mientras |z| < K y
      // 'n' no llegue al máximo de iteraciones:
      while(zx*zx + zy*zy <= K*K && n < MAXITER)
      {
        //Calculamos z = (|Re(z)|+i|Im(z)|)^2 + c :
        float aux = zx*zx - zy*zy + cx;
        zy = abs(2*zx*zy) + cy;
        zx = aux;
        n++;
      }
      
      // Si n ha alcanzado el máximo de iteraciones,
      // elegimos el color negro para el píxel:
      if(n == MAXITER) stroke(0);
      
      // Si no, elegimos una tonalidad de color para el píxel:
      else
      {
        // Elegimos un color entre 180 (azul) y 40 (amarillo)
        int tonalidad = int(map(float(n)/MAXITER, 0, 1, 180, 40));
        int saturacion = 255;
        int luminosidad = 255;
        stroke(tonalidad, saturacion, luminosidad);
      }
      
      //Por último, pintamos el píxel:
      point(x,y);
    }
  }
}

 

También son muy interesantes los fractales que se obtienen definiendo el centro como c = (0 + 0.297i) y c = (0.975 – 1.1i) :

Resultado con c = 0 + 0.297i y 150 iteraciones.

Resultado con c = 0.975 -1.1i y 30 iteraciones.

Otras sucesiones

Cuando definimos la sucesión para nuestro fractal no tenemos por qué basarnos en la sucesión de Mandelbrot: podemos utilizar funciones tan exóticas como queramos.

Un fractal especialmente bonito es el que aparece al principio de este tutorial, que está formado por todos los puntos c para los cuales esta sucesión está acotada:

Recordemos que el coseno de un número complejo en la forma (a+bi) se calcula así:

 

El último código que veremos hoy es el que he utilizado para generar la imagen de la portada del tutorial. Como Procesing no tiene funciones propias que calculen el seno y el coseno hiperbólicos, he tenido que importar la librería Math de Java. A la hora de calcular la sucesión del fractal, he limitado el módulo a |z| < 10π y he centrado la imagen en la región del fractal limitada por el rectángulo (0.45, 0.85) x (0.85, 1.05) .

import java.lang.Math;
 
// Establecemos las dimensiones de la pantalla
int anchoPantalla = 1000;
int altoPantalla = 600;
 
// Establecemos el máximo de iteraciones
int MAXITER = 64;

// Cota para el módulo:
double K = 10*PI;
 
void settings()
{
  // Creamos la ventana:
  size(anchoPantalla, altoPantalla);
 
  // Desactivamos el suavizado y el bucle principal:
  noSmooth();
  noLoop();
   
}
 
void setup()
{
   // Cambiamos al espacio de color HSB:
   colorMode(HSB);
}
 
void draw()
{
  // Recorremos cada píxel de la pantalla con un doble bucle:
  for(int x = 0; x < anchoPantalla; x++)
  {
    for(int y = 0; y < altoPantalla; y++)
    {
      // Definimos el valor de c a partir del píxel actual:
      float cx = map(x, 0, anchoPantalla, 0.45, 0.85);
      float cy = map(y, 0, altoPantalla, 1.05,0.85);
       
      // Término inicial de la sucesión:
      double zx = 0.0;
      double zy = 0.0;
       
      // Contador de iteraciones:
      int n = 0;
       
      // Vamos calculando nuevas iteraciones mientras |z| < 10*PI
      // y 'n' no alcance el máx. de iteraciones.
      while(Math.sqrt(zx*zx + zy*zy) <= K && n < MAXITER)
      {
        // Calculamos z = cos(z) + c :
        double aux = Math.cos(zx)*Math.cosh(zy) + cx;
        zy = -Math.sin(zx)*Math.sinh(zy) + cy;
        zx = aux;
        n++;
      }
       
      // Si 'n' ha alcanzado el máx. de iter., elegimos
      // un color gris-oscuro para el píxel:
      if(n == MAXITER) stroke(10,50,50);
       
      // Si no, elegimos un color en función del núm. de iteración:
      else
      {
        // Elegimos un color entre 180 (azul) y 30 (naranja)
        int tonalidad = int(map(float(n)/MAXITER, 0, 1, 180, 30));
        int saturacion = 255;
        int luminosidad = 255;
        stroke(tonalidad,saturacion,luminosidad);
      }
       
      // Pintamos el píxel:
      point(x,y);
       
    }
  }
}

El fractal resultante con 64 iteraciones/píxel.

El mismo fractal, calculando 250 iteraciones/píxel.


Conclusión

En esta primera parte hemos aprendido cómo generar fractales con Processing a partir de sucesiones de números complejos. Los ejemplos que he mostrado aquí son tan sólo una pequeña parte de la infinidad de formas increíbles que pueden crearse. Con los conocimientos adquiridos hoy, ya estás preparado para convertirte en un explorador del mundo fractal e inventarte tus propias sucesiones complejas.

En la segunda parte de este tutorial generaremos fractales utilizando el Juego del Caos, un método que consiste en colocar puntos sobre el plano de forma semi-aleatoria. El resultado son figuras como el Triángulo de Sierpiński.

Si tienes alguna pregunta sobre lo que he explicado hoy aquí o tienes algún problema con los programas, no dudes en escribirme un comentario. Y si te ha gustado el artículo, puedes seguirnos en nuestra página de Facebook o Twitter: es una buena forma de apoyarnos, y estarás al corriente de nuestras publicaciones.

[Ir a la Parte II]

Tr4nsduc7or

Originariamente creado cómo un galvanómetro de bolsillo, Transductor tomó consciencia de si mismo y fue despedido cuando en vez cumplir con su trabajo se dedicó a pensar teorías filosóficas sobre los hilos de cobre, los electrones y el Sentido del efecto Joule en el Universo. Guarda cierto recelo a sus creadores por no comprender la esencia metafísica de las metáforas de su obra. Actualmente trabaja a media jornada cómo antena de radio, y dedica su tiempo libre a la electrónica recreativa y a la filosofía.

guest
1 Comment
más nuevos primero
más antiguos primero
Inline Feedbacks
Ver todos los comentarios
Rolando Augier
Rolando Augier
7 días

Genial!