0

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

[Volver a la Parte I]

Saludos, humano. Bienvenido a la segunda parte de esta serie de tutoriales dónde exploramos varios métodos y algoritmos para generar imágenes de fractales en lenguaje Processing.

El último día estuvimos viendo cómo hacerlo a partir de sucesiones de números complejos y aprendimos a implementar el conocido algoritmo de tiempo de escape. Hoy en cambio exploraremos un método muy diferente para generar fractales: el Juego del Caos.

Al término de este tutorial, habremos visto la forma de generar fractales tan conocidos como el Helecho de Barnsley o el Fractal de Vicsek.

Reglas simples, figuras complejas

El juego del caos es un método para crear fractales a partir de un polígono y un punto cualquiera de su interior. El fractal se construye moviendo este punto una cierta distancia hacia alguno de los vértices del polígono; la distancia y la forma de escoger el vértice hacia el que nos movemos determinará el tipo de figura que obtendremos.

En su versión más elemental, el Juego del Caos permite dibujar el Triángulo de Sierpiński. Este fractal habitualmente se generaría subdividiendo un triángulo equilátero en cuatro triángulos más pequeños y eliminando el triángulo del centro:

El Triáng. de Sierpińsky se genera subdividiendo un triángulo en otros más pequeños.

En cambio, para generar el Triángulo de Sierpińsky mediante el Juego del Caos hay que seguir estas reglas:

  1. Elegimos un punto P inicial situado en el interior del triángulo.
  2. Elegimos al azar uno de los tres vértices del triángulo. Lo llamamos V.
  3. Calculamos la distancia entre P y V. Llamamos d a esta distancia.
  4. Movemos el punto P una distancia d/2 hacia V.
  5. Volvemos al paso 2, y repetimos el proceso n veces.

Supongamos que cada vez que vamos moviendo el punto P dejamos una marca en su posición. Aunque la intuición nos dice que por muchas iteraciones que calculemos nos saldrá una figura desordenada (al fin y al cabo los movimientos son aleatorios, ¿no?) la realidad es que se acaba obteniendo un bonito Triángulo de Sierpińsky.

Programa en Processing

(Todos los ejemplos del tutorial están testeados con Processing 3 para Ubuntu 20.04)

En este primer ejemplo se implementa la versión básica del Juego del Caos para dibujar Triángulos de Sierpińsky. Si copias este programa en tu IDE y lo ejecutas, verás que aparece dibujado el fractal en negro sobre un fondo blanco.

//Definimos los tres vértices del triángulo:
float[][] t={{320,0},{528,360},{112,360}};
 
//Definimos dos variables para guardar
//las coordenadas del punto P:
float Px, Py;
 
//Establecemos el máximo de iteraciones:
int MAXITER = 90000;
 
void setup()
{
  //Creamos una ventana de 640x360:
  size(640,360);
   
  //Desactivamos el bucle principal:
  noLoop();
   
  //Pintamos de blanco el fondo de la ventana:
  background(255);

  //Cambiamos el color del lápiz a negro
  stroke(0);
   
  // Elegimos un punto inicial que esté situado
  // dentro del triángulo:
  Px = 320;
  Py = 1;
   
}
 
 
void draw()
{
  for(int i = 0; i < MAXITER; i++)
  {
    //Elegimos uno de los vértices del triángulo:
    int r=int(random(0,3));
     
    //Movemos el punto P la mitad de la distancia
    //hacia el vértice elegido:
    Px=Px + (t[r][0]-Px)/2;
    Py=Py + (t[r][1]-Py)/2;
     
    //Pintamos el punto sobre la ventana:
    point(Px,Py);
  }
   
}

Resultado obtenido tras 9000 iteraciones del Juego del Caos.

Este segundo código es una variación del anterior que en lugar de dibujar únicamente el resultado tras un cierto número prefijado de iteraciones, permite visualizar mediante una animación cómo se va generando el fractal a medida que se calculan nuevos puntos:

//Definimos los tres vértices del triángulo:
float[][] t={{320,0},{528,360},{112,360}};
 
//Creamos dos variables para guardar
//las coordenadas x,y del punto P:
float Px, Py;
 
 
void setup()
{
  //Creamos una ventana de 640x320:
  size(640,360);
   
  //Pintamos de blanco el fondo de la ventana:
  background(255);

  //Cambiamos el color del lápiz a negro
  stroke(0);
  
  // Elegimos un punto inicial que esté situado
  // dentro del triángulo:
  Px = 320;
  Py = 1;
}
 
 
void draw()
{
  //Elegimos uno de los vértices del triángulo:
  int r = int(random(0,3));
   
  //Movemos el punto P la mitad de la distancia
  //hacia el vértice elegido:
  Px=Px + (t[r][0]-Px)/2;
  Py=Py + (t[r][1]-Py)/2;
   
  //Pintamos el punto sobre la ventana:
  point(Px,Py);
   
}

Si ejecutas el programa y lo dejas abierto durante unos minutos verás que poco a poco se va formando la figura del fractal:

 

Pero el Triángulo de Sierpińsky no es, ni mucho menos, el único fractal que puede generarse gracias al Juego del Caos. En los siguientes apartados vamos a jugar con las reglas y añadiremos pequeñas variaciones que nos permitirán obtener figuras mucho más exóticas…

El Juego del Caos restringido

¿Qué pasa si utilizamos un cuadrado y no un triángulo como figura base? Pues… nada. Nada remarcable, por lo menos. El interior del cuadrado se llenará de puntos sin ningún tipo de patrón y no aparecerá ningún fractal por muchas iteraciones del Juego del Caos que calculemos:

El punto P va saltando la mitad de la distancia hacia un vértice al azar: no aparece ningún fractal.

Pero… ¿y si variamos la forma de seleccionar los vértices? Por ejemplo: ¿qué ocurre si restringimos los vértices de tal manera que un mismo vértice no pueda elegirse dos veces seguidas? Pues en este caso obtendremos esta magnífica figura:

//Fijamos los cuatro vértices del cuadrado:
float[][] cuadrado={{0,0},{640,0},{640,640},{0,640}};
 
//Fijamos un máximo de iteraciones:
int MAXITER = 90000;
 
void setup()
{
  //Creamos una ventana de 640x640:
  size(640,640);
   
  //Desactivamos el bucle principal:
  noLoop();
 
  //Pintamos el fondo de blanco:
  background(255);

  //Cambiamos el color del lápiz a negro
  stroke(0);
   
}
 
void draw()
{
  //Elegimos un punto inicial:
  float Px=320;
  float Py=1;
   
  //Elegimos una de las esquinas:
  int r = int(random(0,4));
 
  for(int i = 0; i < MAXITER; i++)
  {
    //Movemos el punto P una distancia d/2
    //hacia el vértice:
    Px=Px+(cuadrado[r][0]-Px)/2;
    Py=Py+(cuadrado[r][1]-Py)/2;
     
    //Dibujamos el punto sobre la ventana:
    point(Px,Py);
     
    //Elegimos una nueva esquina.
    //Esta esquina debe ser diferente
    //a la que elegimos la última vez:
    r = (r+int(random(1,4)))%4;
     
  }
}

 

Otro fractal interesante se obtiene evitando que el nuevo vértice elegido esté a dos posiciones de distancia del vértice de la iteración anterior. Es decir: no podríamos pasar de la esquina superior izquierda a la esquina inferior derecha, o de la esquina superior derecha a la esquina inferior izquierda:

//Definimos los cuatro vértices del cuadrado:
float[][] cuadrado={{0,0},{640,0},{640,640},{0,640}};
// (Nota: en este ejemplo es muy importante que los vértices
//  estén ordenados en sentido horario)
 
//Fijamos un máximo de iteraciones:
int MAXITER = 90000;
 
void setup()
{
  //Creamos una ventana de 640x640:
  size(640,640);
   
  //Desactivamos el bucle principal:
  noLoop();
 
  //Pintamos el fondo de blanco:
  background(255);

  //Cambiamos el color del lápiz a negro
  stroke(0);
   
}
 
void draw()
{
  //Elegimos un punto inicial:
  float Px=320;
  float Py=1;
   
  //Elegimos una de las esquinas:
  int r = int(random(0,4));
 
  for(int i = 0; i < MAXITER; i++)
  {
    //Movemos el punto P una distancia d/2
    //hacia el vértice:
    Px=Px+(cuadrado[r][0]-Px)/2;
    Py=Py+(cuadrado[r][1]-Py)/2;
     
    //Dibujamos el punto sobre la ventana:
    point(Px,Py);
     
    //Ahora hay que elegir un nuevo vértice, evitando
    //el que esté a dos posiciones del último vértice elegido.
    //Empezamos eligiendo un número entero entre 0 y 2 (inclusive):
    int opcion = int(random(0,3));
    
    // Si opcion == 0, avanzamos 1 vértice en
    // sentido horario:
    if(opcion == 0)
      r = (r+1)%4;
      
    // Si opcion == 1, avanzmos 3 vértices 
    // en sentido horario:
    else if(opcion == 1)
      r = (r+3)%4;
      
    //Y si opcion == 2, no cambiamos el valor de 'r'.
     
  }
}

Figura obtenida tras 9000 iteraciones.

La Alfombra de Sierpińsky

Otro fractal muy famoso que vamos a generar es la Alfombra de Sierpińsky. Normalmente, este fractal se generaría subdividiendo un cuadrado en 9 sub-cuadrados, eliminando el del centro, y repitiendo este proceso ad infinitum. Lo más fascinante de este objeto es que desafía toda intuición: puede demostrarse matemáticamente que tiene área cero, perímetro infinito y no contiene ningún punto en su interior.

La Alfombra de Sierpińsky se genera subdividiendo un cuadrado en 9 sub-cuadrados, y eliminando la región central.

El Juego del Caos nos puede servir para generar esta figura si se hacen dos modificaciones a las reglas. La primera es que además de tener como vértices las cuatro esquinas del cuadrado, también utilizaremos los puntos medios de cada costado. La segunda modificación será que el punto P avanzará una distancia de 2/3 en vez de 1/2, y podrá repetir los vértices.

El siguiente programa calculará 1.000.000 iteraciones del Juego del Caos siguiendo estas dos reglas.

//Fijamos los cuatro vértices del cuadrado y los puntos medios:
float[][] cuadrado={{0,0},{320,0},{640,0},{0,320},{640,640},{320,640},{0,640},{640,320}};
 
//Fijamos un máximo de iteraciones:
int MAXITER = 1000000;
 
void setup()
{
  //Creamos una ventana de 640x640:
  size(640,640);
   
  //Desactivamos el bucle principal:
  noLoop();
 
  //Pintamos el fondo de blanco:
  background(255);

  //Cambiamos el color del lápiz a negro
  stroke(0);
   
}
 
void draw()
{
  //Elegimos un punto inicial:
  float Px=320;
  float Py=1;
   
  //Elegimos uno de los vértices:
  int r = int(random(0,8));
 
  for(int i = 0; i < MAXITER; i++)
  {
    //Movemos el punto P una distancia 2*d/3
    //hacia el vértice:
    Px=Px+2*(cuadrado[r][0]-Px)/3;
    Py=Py+2*(cuadrado[r][1]-Py)/3;
     
    //Dibujamos el punto:
    point(Px,Py);
     
    //Elegimos un nuevo vértice:
    r = int(random(0,8));
     
  }
}

Resultado tras calcular un millón de iteraciones del Juego del Caos.

 

Otro fractal que puede generarse de forma parecida a la Alfombra de Sierpińsky es el Fractal de Vicsek. En este caso utilizamos como vértices las cuatro esquinas y el punto central del cuadrado, con una distancia de salto también de 2/3:

//Fijamos los cuatro vértices del cuadrado y el punto medio:
float[][] cuadrado={{0,0},{640,0},{640,640},{0,640},{320,320}};
 
//Fijamos un máximo de iteraciones:
int MAXITER = 100000;
 
void setup()
{
  //Creamos una ventana de 640x640:
  size(640,640);
   
  //Desactivamos el bucle principal:
  noLoop();
 
  //Pintamos el fondo de blanco:
  background(255);

  //Cambiamos el color del lápiz a negro
  stroke(0);
   
}
 
void draw()
{
  //Elegimos un punto inicial:
  float Px=320;
  float Py=1;
   
  //Elegimos uno de los vértices:
  int r = int(random(0,5));
 
  for(int i = 0; i < MAXITER; i++)
  {
    //Movemos el punto P una distancia 2*d/3
    //hacia el vértice:
    Px=Px+2*(cuadrado[r][0]-Px)/3;
    Py=Py+2*(cuadrado[r][1]-Py)/3;
     
    //Dibujamos el punto:
    point(Px,Py);
     
    //Elegimos un nuevo vértice:
    r = int(random(0,5));
     
  }
}

Fractal de Vicsek tras 100.000 iteraciones.

 

El helecho de Barnsley

El aspecto más crucial del Juego del Caos es ver cómo elegimos un nuevo punto aleatorio. En los fractales que hemos visto hasta ahora nos limitábamos únicamente a utilizar los vértices del polígono que los define, pero también tenemos la opción de calcular el nuevo punto a partir del punto anterior.

El último fractal que vamos a ver hoy es el Helecho de Barnsley. Tal y como indica su nombre, se trata de un fractal cuya forma recuerda a las hojas de un helecho. Para generarlo partimos de un punto inicial (x,y) cualquiera y calculamos un nuevo punto de acuerdo con estas reglas:

 

//Fijamos el máximo de iteraciones:
int MAXITER = 100000;

void setup() {
  
  //Creamos una ventana de 350x640:
  size(350, 640);
  
  //Pintamos el fondo de negro:
  background(0);
  
  //Pintaremos los puntos de verde:
  stroke(120, 200, 50);
  
  //Desactivamos el bucle principal:
  noLoop();
}

void draw() 
{
  
  //Variables para guardar los puntos del helecho:
  float Px = 0;
  float Py = 0;
  
  
  for (int i = 0; i < MAXITER; i++) 
  {
    
    //A la hora de pintar los puntos, hay que hacer "zoom":
    float posicionX = map(Px, -2.3, 2.7, 0, width);
    float posicionY = map(Py, 0, 10.2, height, 0);
    point(posicionX, posicionY);
    
    
    //Elegimos un nuevo punto, siguiendo las reglas:
    float Px_nuevo, Py_nuevo;
    float r = random(1);
    if (r < 0.01) 
    {
        Px_nuevo =  0.0;
        Py_nuevo =  0.16 * Py;
    } 
    else if (r < 0.86) 
    {
        Px_nuevo =  0.85 * Px + 0.04 * Py;
        Py_nuevo = -0.04 * Px + 0.85 * Py + 1.6;
    } 
    else if (r < 0.93) 
    {
        Px_nuevo =  0.20 * Px - 0.26 * Py;
        Py_nuevo =  0.23 * Px + 0.22 * Py + 1.6;
    } 
    else 
    {
        Px_nuevo = -0.15 * Px + 0.28 * Py;
        Py_nuevo =  0.26 * Px + 0.24 * Py + 0.44;
     }
     Px = Px_nuevo;
     Py = Py_nuevo;
  }
}

Conclusiones

En resumen, hoy has aprendido cómo funcionan las reglas del Juego del Caos y has visto varios ejemplos de su implementación. Estos son sólo unos pocos ejemplos del sinfín de fractales que pueden construirse con este método; te animo a continuar explorando y a descubrir variaciones de las reglas que puedan producir nuevas figuras.

Recuerdo que cuando descubrí por primera vez el Juego del Caos (hace ya muchos años) no pude evitar sentirme un poco hipnotizado: el hecho de que un conjunto de reglas tan elementales y fáciles de programar sean capaces de “traducirse” en fractales tan intrincados me pareció cosa de magia. Espero que tras este tutorial haya podido contagiarte un poco mi entusiasmo por este tipo de algoritmos.

Si tienes alguna pregunta o problema en referencia a lo que he explicado hoy, puedes escribirme un comentario e intentaré contestarte. Y como siempre, si te ha gustado el artículo no dudes en seguirnos en nuestra página de Facebook o Twitter: es una buena forma de darnos apoyo y podrás estar al corriente si publicamos algún artículo nuevo.

Final de línea.

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
0 Comments
Inline Feedbacks
Ver todos los comentarios