Controllare le trasformazioni: pushMatrix() e popMatrix()

Le funzioni di trasformazione viste nell’ultimo post ci hanno dato prova di come sia possibile andare a modificare dinamicamente il sistema di coordinate di Processing. In questo articolo, introdurremo due nuove funzioni chiamate pushMatrix()popMatrix() che, come vedremo insieme, ci permettono di avere maggiore controllo sulle trasformazioni.

Matrici

Ogni volta che usiamo le funzioni translate()rotate()scale() andiamo a modificare una matrice – in inglese matrix. Per spiegarlo nel modo più semplice possibile: una matrice non è altro che un insieme di numeri che descrivono, a livello matematico, come vengono disegnate le geometrie su uno schermo. Le funzioni di trasformazione vanno a modificare questi numeri.

pushMatrix() e popMatrix()

Le funzioni pushMatrix() popMatrix() vanno inserite nei nostri programmi sempre in coppia e la loro funzione è quella di salvare lo stato delle trasformazioni (push) e richiamarle (pop) al momento opportuno.

Quando ho cominciato a studiare Processing, non riuscivo a capire il senso di questa cosa. Alla fine, in un video tutorial trovato on-line, ho trovato la similitudine corretta per spiegarlo. Spero che quanto sto per scrivere aiuti anche voi, come è stato per me, a capire meglio il senso di pushMatrix e popMatrix.

Trattandosi di operazioni che facciamo sul sistema delle coordinate, dobbiamo immaginare di spostare il foglio su cui stiamo scrivendo e non la punta della penna con cui scriviamo.

Provo a spiegarmi meglio con un esempio:

void setup() {
  size(500, 500);
}

void draw() {
  background(255);
  translate(100, 0);
  rect(0, 20, 200, 75);
  rect(0, 105, 200, 75);
}

In setup() creiamo il nostro canvas di dimensione 500×500 pixel dopodiché in draw() per ogni ciclo della funzione, coloriamo lo sfondo di bianco, spostiamo la punta della nostra penna di 100 pixel verso destra (oppure possiamo pensare che il foglio si sia spostato 100 pixel nella direzione opposta) e poi disegniamo due rettangoli con dimensioni uguali e con valori di x identici e y differente.

Ci aspettiamo, ovviamente, che i due rettangoli siano allineati a sinistra uno con l’altro e infatti questo è risultato:pushMatrix e popMatrix in ProcessingSe modifichiamo il codice aggiungendo pushMatrix() e popMatrix() ecco che il risultato cambia:

void setup() {
  size(500, 500);
}

void draw() {
  background(255);
  pushMatrix();
  translate(100, 0);
  rect(0, 20, 200, 75);
  popMatrix();
  rect(0, 105, 200, 75);
}

Come funzionano pushMatrix e popMatrix?

Perché il secondo rettangolo non è più allineato con il primo? Quando chiamiamo la funzione pushMatrix() nella nostra matrice viene salvata la posizione corrente del nostro sistema di coordinate, cioè senza alcuna trasformazione perché il comando translate viene invocato successivamente. Dopodiché viene applicato uno spostamento verso destra, viene disegnato il primo rettangolo e, infine, utilizzando popMatrix() ripristiniamo il salvataggio precedente, annullando, cioè, la traslazione di 100 pixel.

In questo modo, il secondo rettangolo, con coordinate 0, 105, verrà disegnato vicino al bordo sinistro della nostra finestra.

Perché usare pushMatrix() e popMatrix()?

Qualcuno potrebbe chiedersi quale sia l’utilità di utilizzare pushMatrix() e popMatrix() all’interno di uno sketch in Processing. La risposta è abbastanza semplice ma, come scrivevo prima, personalmente non è stata immediata: se realizziamo animazioni complesse con una somma di trasformazioni continue può risultare difficile ricordarsi in ogni momento le coordinate di una determinata forma. Con queste due funzioni possiamo, invece, spostare le coordinate e fare in modo che le nostre figure abbiano sempre coordinate x = 0 e y = 0.

Trasformazioni: translate(), rotate(), scale()

Abbiamo già avuto modo all’inizio del percorso base di prendere esplorare il sistema di coordinate in Processing. Una cosa che non abbiamo affrontato in modo approfondito è che questo sistema non è statico. Esistono, infatti delle funzioni dette trasformazioni che ci permettono di modificare il nostro impianto di base. Tali funzioni sono translate(), rotate() e scale() che, come è chiaro dai loro nomi, ci permettono di traslare, ruotare e scalare le forme che rappresentiamo sullo schermo.

Perché usare le trasformazioni è utile? Semplicemente perché quando lavoriamo con geometria complesse è più semplice modificare una sola linea di codice per spostare una o più forme invece che ripensare nuovamente a tutte le coordinate. In poche parole, è un ottimo sistema per semplificarci la vita.

translate()

La funzione translate sposta il punto di origine della nostra forma; accetta due parametri: le coordinate x e y del nostro spostamento. Ovviamente quando chiamiamo questa funzione, lo spostamento viene applicato per tutte le forme che vengono disegnate dopo la funzione stessa.

void setup() {
  size(500, 500);
  background(255);
}

void draw() {
  rect(50, 50, 200, 50);
  translate(50, 100);
  rect(50, 50, 200, 50);
}

Translate() in Processing

Ecco che pur utilizzando le stesse coordinate per i due rettangoli, il secondo si trova spostato di 50 pixel verso destra e 100 più in basso.

Un particolare di questa funziona è che, se ripetuta, è incrementale:

void setup() {
  size(500, 500);
  background(255);
}

void draw() {
  rect(50, 50, 200, 50);
  translate(50, 100);
  rect(50, 50, 200, 50);
  translate(50, 100);
  rect(50, 50, 200, 50);
}

In questo secondo esempio il terzo rettangolo risulta spostato, rispetto al primo, di 100 pixel verso destra e 200 in basso.

Translate() in processing è una funzione additiva

Importante notare anche che pur essendo inserite all’interno di un ciclo draw(), a ogni loop l’intero sistema viene resettato.

rotate()

La funzione rotate() ruota il sistema di coordinate. Accetta un solo parametro, la quantità di rotazione che deve essere espresso in radianti. Le forme vengono sempre ruotate relativamente al punto di origine (0, 0) della finestra e non relativamente al punto di riferimento della forma. Se il parametro di rotazione è positivo, il movimento sarà in senso orario.

Esempio di rotazione di 45 gradi convertiti nel valore in radianti.

void setup() {
  size(500, 500);
  background(255);
}

void draw() {
  rect(50, 50, 200, 50);
  rotate(radians(45));
  rect(50, 50, 200, 50);
}

Trasformazioni: rotate()

scale()

La funzione scale() modifica il sistema di coordinate ingrandendo o rimpicciolendo le forme. Questa funzione accetta uno o due parametri: nel primo caso verrà applicato quel valore a tutte le dimensioni mentre, nel secondo, il primo scalerà l’asse x e il secondo l’y.

Per far funzionare la funzione scale() correttamente i valori devono essere espressi in percentuale usando i numeri decimali: scrivendo 2 otterremo un incremento del 200%, scrivendo 0.5 la forma sarà rimpicciolita del 50%.

void setup() {
  size(500, 500);
  background(255);
}

void draw() {
  fill(255);
  rect(50, 50, 200, 50);
  scale(0.5);
  fill(255, 0, 0);
  rect(50, 50, 200, 50);
}

Utilizzo di scale() in Processing