Funzione ricorsiva per generare alberi

Un'applicazione tipica delle funzioni ricorsive è quella di disegnare alberi generando un ramo principale (tronco) da cui far generae altri due rami che generano ognuno altri due rami fino al livello di ricorsione scelto.

// height=240 lines=auto
class Ramo {
  constructor(x1, y1, x2, y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
  }
  display(quanto) {
    let x2 = lerp(this.x1, this.x2, quanto);
    let y2 = lerp(this.y1, this.y2, quanto);
    line(this.x1, this.y1, x2, y2);
  }
}

let rami = [];
let ultimo = 0;
let quanto = 0;

function setup() {
  createCanvas(windowWidth, 240);
  disegnaRamo(width / 2, height, PI * 1.5, 96);
}

function windowResized() {
  createCanvas(windowWidth, 240);
  rami = [];
  disegnaRamo(width / 2, height, PI * 1.5, 96);
}

function draw() {
  clear();
  stroke(96);
  strokeWeight(1.5);
//  strokeWeight(1.25);

  quanto += 0.1;
  if (quanto > 1) {
    ultimo++;
    if (ultimo > rami.length+12) {
      ultimo = 0;
    }
    quanto = 0;
  }

  for (i = 0; i < ultimo && i < rami.length; i++) {
    rami[i].display(1);
  }
  if (ultimo < rami.length) {
    rami[ultimo].display(quanto);
  }
}

function disegnaRamo(x, y, angolo, lunghezza) // FUNZIONE RICORSIVA disegnaRami() con lunghezza dei rami come parametro
{
  let x2 = x + cos(angolo) * lunghezza;
  let y2 = y + sin(angolo) * lunghezza;
  //  line(x, y, x2, y2); // disegna il ramo
  rami.push(new Ramo(x, y, x2, y2));

  if (lunghezza > 6) { // se la lunghezza del ramo è ancora superiore a 2 ...
    let nuovaLunghezza = lunghezza * 0.6; // riduci la lunghezza del ramo al 66%
    let deviazione = 0.75;
    disegnaRamo(x2, y2, angolo + deviazione, nuovaLunghezza); // avvia un'ulteriore diramazione ricorsiva
    disegnaRamo(x2, y2, angolo - deviazione, nuovaLunghezza); // avvia un'ulteriore diramazione ricorsiva
  }
}

L'ordine con cui vengono disegnati i rami conferma che la generazione non avviene in parallelo ma sequenzialmente perché ogni chiamata non si conclude finché non si concludono tutte le chiamate interne.

// height=320 lines=auto
function disegnaRamo(x, y, angolo, livello) {

    let lunghezza = pow( 0.6, livello ) * 130;
    let x2 = x + cos(angolo) * lunghezza;
    let y2 = y + sin(angolo) * lunghezza;
    line(x, y, x2, y2);

    livello++;
    if (livello < 7) {
        let deviazione = 0.75;
        disegnaRamo(x2, y2, angolo+deviazione, livello);
        disegnaRamo(x2, y2, angolo-deviazione, livello);
    }
}

function setup() {
    createCanvas(360, 320);
    frameRate(1);
}

function draw() {
    background(255);
    disegnaRamo( width/2, height, PI*1.5, 0 );
}
function disegnaRamo(x, y, angolo, livello) {

Funzione (ricorsiva) che disegna un segmento a partire dalle coordinate x, y con una direzione indicata da angolo e per una lunghezza ricavata attraverso livello.

let lunghezza = pow( 0.6, livello ) * 130;

La lunghezza del ramo parte da 130 pixel e si riduce al 60% a ogni livello di ricorsione.

let x2 = x + cos(angolo) * lunghezza;
let y2 = y + sin(angolo) * lunghezza;

Le coordinate dell'estremità finale del segmento vengono ricavate usando la conversione da coordinate polari (angolo, lunghezza) a coordinate cartesiane.

let deviazione = 0.75;
disegnaRamo(x2, y2, angolo + deviazione, livello);
disegnaRamo(x2, y2, angolo - deviazione, livello);

Chiama la funzione una volta con un angolo maggiore e una con un angolo minore a quello di base.

disegnaRamo( width/2, height, PI*1.5, 0 );

Avvia la ricorsione a partire dal centro del bordo inferiore del canvas procedendo verso l'alto (PI*1.5) e con il livello iniziale impostato a zero.

Rompere la simmetria facendo impostare deviazioni casuali dell'angolo dei rami, ad esempio con:

let deviazione = random(0.1, 1);

Rompere la regolarità delle lunghezze dei rami usando un valore di base casuale diverso da 130, ad esempio con

let lunghezza = pow( 0.6, livello ) * random( 50, 150 );

Prima dell'istruzione line(), impostare gli attributi grafici della linea in base al valore di livello o casualmente, ad esempio usando:

    strokeWeight( (7-livello) / 1.5 );
    stroke( 64, 180-livello*20, 0 );

Far disegnare dei cerchi alla fine dei rami più piccoli (quando il livello è uguale a 7) aggiungendo un else all'if della funzione ricorsiva, ad esempio:

    if (livello < 7) {
        ...
    } else {
        fill( 255, random(48,200), random(16,80), 180 );
        noStroke();
        circle( x2, y2, 6 );
    }