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 );
}