Ricorsione: attraversare lo specchio

Provate a mettervi tra due specchi, uno davanti e uno dietro. La vostra immagine verrà riflessa, il riflesso dell'immagina verrà riflesso, il riflesso del riflesso dell'immagina verrà riflesso, e così via all'infinito. Ora provate ad accendere una candela. Le micro variazioni della fiamma creeranno, attraverso l'infinita riflessione, un effetto bizzarro. Questa è la ricorsione (spiegata senza usare il fattoriale!).

La ricorsione e i sistemi complessi hanno molte cose in comune. Anche la ricorsione è un fenomeno che si ritrova spesso in natura: sotto forma di frattali, la ritroviamo negli alberi, nel profilo delle coste, nel profilo delle montagne, nelle nubi, nei cristalli di ghiaccio, in alcune foglie e fiori.

L'eau Bleue

La ricorsione, inoltre, sfruttando la potenza riflessiva, produce risultati complessi a partire da semplice regole codificate. E' infatti facile perdere il controllo della ricorsione e creare codice che satura le risorse o che esegue all'infinito. Anche in questo caso l'artista generativo deve essere un buon programmatore: la ricorsione deve essere ben fondata, vale a dire deve terminare dopo un numero finito di passaggi riflessivi. Infine, micro variazioni nelle regole della ricorsione possono produrre effetti di vasta portata.

Vediamo alcuni esempi. Cominciamo con il disegnare un albero: Tree1

Node tree;
int max_fanout = 3; // maximum node fanout
int max_level = 3; // maximum tree depth

void setup() {
 size(500, 500);
 tree = new Node(width/2, height/2, 1, max_fanout, 0, 0);
}

void draw() {
  background(255);
  tree.picture();
}

void mouseReleased() {
  tree = new Node(width/2, height/2, 1, max_fanout, 0, 0);
} 


class Node {
  float x, y; // node screen coordinates
  float angle, radius; // node polar coordinates wrt parent node
  int level; // node level
  int fanout; // node fanout
  Node[] children; // list of children
  
  Node(float _x, float _y, int _level, int _fanout, float _angle, float _radius) {
    x = _x;
    y = _y;
    level = _level;
    fanout = _fanout;
    angle = _angle;
    radius = _radius;
    
    children = new Node[fanout];
    for (int i = 0; i < fanout; i++) {
      int clevel = level + 1;
      int cfanout;
      if (level < (max_level - 1)) { // node with internal children
        cfanout = max_fanout; 
      } else { // node with leaf children
        cfanout = 0; 
      }
      float cradius = random(50, width/3); 
      float cangle = random(0, TWO_PI);
      float cx = x + cos(cangle) * cradius;
      float cy = y + sin(cangle) * cradius;
      children[i] = new Node(cx, cy, clevel, cfanout, cangle, cradius);
    }
    
  }
    
  void picture() {
    for (int i = 0; i < fanout; i++) {
      if (level > 0) {
        fill(0);
        ellipse(x, y, 20 / level, 20 / level); // draw node
        if (level == (max_level - 1)) { 
          ellipse(children[i].x, children[i].y, 20 / (level+1), 20 / (level+1)); // draw node
        }
        stroke(0);
        strokeWeight(5 / level);
        line(x, y, children[i].x, children[i].y); // draw edge
      }
      children[i].picture();
    }
  }
  
}

Facciamo ruotare i nodi attorno ai loro padri con verso e velocità casuali: Tree2

Node tree;
int max_fanout = 3; // maximum node fanout
int max_level = 3; // maximum tree depth

void setup() {
 size(500, 500);
 tree = new Node(width/2, height/2, 1, max_fanout, 0, 0);
}

void draw() {
  background(255);
  tree.picture();
  tree.update();
}

void mouseReleased() {
   tree = new Node(width/2, height/2, 1, max_fanout, 0, 0);
} 


class Node {  
  float x, y; // node positions
  int level; // node level
  int fanout; // node fanout
  float angle, radius; // node polar coordinates wrt parent node
  float rotation; // node rotation speed and direction
  Node[] children; // list of children
  
  Node(float _x, float _y, int _level, int _fanout, float _angle, float _radius) {
    x = _x;
    y = _y;
    level = _level;
    fanout = _fanout;
    angle = _angle;
    radius = _radius;
    rotation = radians(random(-1, 1));
    
    children = new Node[fanout];
    for (int i = 0; i < fanout; i++) {
      int clevel = level + 1;
      int cfanout;
      if (level < (max_level - 1)) { // node with internal children
        cfanout = max_fanout; 
      } else { // node with leaf children
        cfanout = 0; 
      }
      float cradius = random(50, width/3); 
      float cangle = random(0, TWO_PI);
      float cx = x + cos(cangle) * cradius;
      float cy = y + sin(cangle) * cradius;
      children[i] = new Node(cx, cy, clevel, cfanout, cangle, cradius);
    }
    
  }
    
  void picture() {
    for (int i = 0; i < fanout; i++) {
      if (level > 0) {
        fill(0);
        stroke(0);
        strokeWeight(5 / level);
        ellipse(x, y, 20 / level, 20 / level); // draw node
        if (level == (max_level - 1)) { 
          ellipse(children[i].x, children[i].y, 20 / (level+1), 20 / (level+1)); // draw node
        }
        line(x, y, children[i].x, children[i].y); // draw edge
      }
      children[i].picture();
    }
  }
  
  void update() {
    for (int i = 0; i < fanout; i++) {
      children[i].angle += children[i].rotation; 
      children[i].x = x + cos(children[i].angle) * children[i].radius;
      children[i].y = y + sin(children[i].angle) * children[i].radius;
      children[i].update();
    }
  }

}

Modificando progressivamente le lunghezze degli archi creiamo un effetto big bang / big crunch: Tree3

Node tree;
int max_fanout = 7; // maximum node fanout
int max_level = 5;  // maximum tree depth
int span = 800;
int jump = -8;

void setup() {
 size(500, 500);
 tree = new Node(width/2, height/2, 1, 1, 0, 0);
}

void draw() {
  background(255);
  tree.picture();
  tree.update();
}


class Node {
  
  float x, y; // node positions
  int level; // node level
  int fanout; // node fanout
  float angle, radius; // node polar coordinates wrt parent node
  float rotation; // node rotation speed
  Node[] children; // list of children
  
  Node(float _x, float _y, int _level, int _fanout, float _angle, float _radius) {
    x = _x;
    y = _y;
    level = _level;
    fanout = _fanout;
    angle = _angle;
    radius = _radius;
    rotation = radians(random(-1, 1));
    
    
    children = new Node[fanout];
    for (int i = 0; i < fanout; i++) {
      int clevel = level + 1;
      int cfanout;
      if (level < (max_level - 1)) { // node with internal children
        cfanout = max_fanout; 
      } else { // node with leaf children
        cfanout = 0; 
      }
      float cradius = span; 
      float cangle = random(0, TWO_PI);
      float cx = x + cos(cangle) * cradius;
      float cy = y + sin(cangle) * cradius;
      children[i] = new Node(cx, cy, clevel, cfanout, cangle, cradius);
    }
    
  }
    
  void picture() {
    for (int i = 0; i < fanout; i++) {
      if (level > 1) {
        fill(0);
        stroke(0);
        strokeWeight(5 / level);
        ellipse(x, y, 20 / level, 20 / level); // draw node
        if (level == (max_level - 1)) { 
          ellipse(children[i].x, children[i].y, 20 / (level+1), 20 / (level+1)); // draw node
        }
        line(x, y, children[i].x, children[i].y); // draw edge
      }
      children[i].picture();
    }
  }
  
  void update() {
    for (int i = 0; i < fanout; i++) {
      children[i].angle += children[i].rotation;
      children[i].radius += jump;
      if (children[i].radius < 0 || children[i].radius > 3 * span) jump *= -1;
      children[i].x = x + cos(children[i].angle) * children[i].radius;
      children[i].y = y + sin(children[i].angle) * children[i].radius;
      children[i].update();
    }
   
  }

}

Una colorata reinterpretazione: coeur.

Un buon esempio di ricorsione è il frattale: un oggetto autosomigliante, cioè che si ripete nella sua forma allo stesso modo su scale diverse, ovvero non cambia aspetto anche se visto con una lente d'ingrandimento. E' un oggetto a prima vista complesso, ma generato da una sorgente ordinata molto semplice. I frattali appartengono al mondo della natura ma vengono spesso studiati dai matematici e riprodotti dagli artisti (non solo generativi).

"Si ritiene che in qualche modo i frattali abbiano delle corrispondenze con la struttura della mente umana, è per questo che la gente li trova così familiari. Questa familiarità è ancora un mistero e più si approfondisce l'argomento più il mistero aumenta." (Benoit Mandelbrot)

Frattale di Sutcliffe

Vediamo come costruire il frattale di Sutcliffe mediante la ricorsione e come animarlo oltre i propri limiti naturali introducendo semplici modifiche al codice originale: Fractus (codice scritto da Matt Pearson).