2. Repte 2. Autòmats

2.3. Segona part: estructures de control

2.3.3. Translació, rotació i escala

Fins ara hem vist com posar formes gràfiques al canvas fent servir les coordenades on les volem situar. Sempre hem partit del fet que el punt (0, 0) (l’origen de les coordenades) està al vèrtex superior esquerre, però amb p5.js podem decidir a quin lloc del canvas situarem el punt (0, 0). El capítol 6 del llibre ens explica com podem moure l’origen de les coordenades. Movent l’origen de les coordenades mourem les formes que tenim dibuixades, atès que les haurem dibuixat a partir de l’origen de les coordenades. Per tant, això ens serà útil per a moure formes complexes creades a partir de diverses formes simples.

Vegem tres maneres de fer aquest canvi de l’origen de les coordenades.

Translació

L’opció més senzilla per a moure l’origen de les coordenades és la translació. Consisteix a desplaçar-lo un determinat nombre de píxels. Aquests dos exemples són prou clars:

La funció que fem servir per a determinar on tenim l’origen de les coordenades és translate(). Aquesta instrucció mou l’origen de les coordenades des de la posició actual a la posició indicada en els seus paràmetres. Així:

translate(40,20);

Mou l’origen de les coordenades a la posició (40,  20).

Atenció, perquè atès que l’origen de les coordenades es mou a partir de la seva posició actual, si posem dos translate() seguits el segon agafarà com a origen de les coordenades el que hem seleccionat primer.

I un detall més. L’origen de les coordenades es restableix (torna al seu lloc), cada cop que es torna a executar la funció draw() (que, com ja sabem, s’executa 50 cops per segon).

Aquest exemple pot ser prou aclaridor:

function setup() {
  createCanvas(200, 200);
}

function draw() {
  background(220);
    for (var i = 10; i < 400; i += 10){ // Dibuixem les línies
  	 line(i,0,i,height);   // Verticals
  	 line(0,i,width,i);    // Horitzontals
    }
    fill(255,0,0);
    rect(20,20,20,20);
    translate(20,20);
    fill(0,255,0);
    rect(20,20,20,20);
    fill(0,0,255);
    translate(20,20);
    rect(20,20,20,20);
}

Si l’executem, aquest és el resultat:

Si ens fixem en el codi, el dibuix del rectangle és sempre el mateix: rect(20,20,20,20); el que canviem és que cada cop traslladem l’origen de les coordenades. Però, a més a més, també el canvi de l’origen de les coordenades sempre és el mateix translate(20,20); perquè, com hem vist abans, el segon canvi parteix de l’origen de les coordenades que hem seleccionat primer.

Així, el primer rectangle es dibuixa a la posició (20, 20), el segon a la (40, 40) i el tercer a la (60, 60).

Ara que ja hem vist com funciona la funció translate(), ja ens podem posar amb els exemples del llibre.

El 6-1 és molt senzill: fem servir la posició del ratolí per a determinar el nou origen de les coordenades. D’aquesta manera, quan dibuixem el quadrat, es dibuixa allà on està el ratolí.

En el 6-2 es fan dues translacions, de manera que es dibuixen dos quadrats lleugerament separats. La veritat és que, un cop tenim clar el funcionament de la funció translate(), aquests dos exemples són molt fàcils d’entendre.

Rotació

Rotar és fer girar un objecte. En el cas de p5.js el que farem girar és l’origen de les coordenades. La funció que fem servir per a fer aquesta rotació és rotate().

Com que estem parlant de fer un gir, el paràmetre que li indicarem a la funció rotate() serà uns graus, normalment en radiants, tot i que podem fer servir graus. En aquest punt val la pena revisar, en la guia del repte 2, l’explicació de la funció arc().

Mireu aquest exemple:

function setup() {
  createCanvas(200, 200);
}
function draw() {
  background(220);
  translate(100,100)
  for (var i=0;i<16;i++){
    line(20, 20, 40, 40);
    rotate(QUARTER_PI/2.0);
  }
}

Dibuixa això:

El primer que fem és traslladar l’origen de les coordenades al centre del canvas.

A continuació, pintem una línia i rotem, pintem una línia i rotem. Així, 16 cops fins a completar el cercle.

Com hem vist en l’apartat anterior, els desplaçaments de l’origen de les coordenades són acumulatius.

Un cop entès com funciona la funció rotate() podem veure els exemples del llibre.

El 6-3 fa una rotació a partir de la posició x del ratolí. Dibuixem el quadrat i comencem a fer la rotació a partir de la posició (0, 0) actual.

En aquest exemple, el llibre fa una explicació que no és ben bé real. Diu que mouseX només pot prendre valors de 0 a 120 (la mida del canvas) però això ja hem vist que no és així, ja que guarda el valor de la posició x del ratolí fins i tot quan el ratolí està fora del canvas.

L’exemple 6-4 és molt semblant. Però en aquest cas, el que s’ha fet és situar l’inici del quadrat fora del canvas de manera que l’origen de les coordenades queda al mig del rectangle. Així, dona la sensació que el rectangle gira sobre el seu eix. Una manera diferent de fer el mateix seria fent servir la funció rectMode() que ens permet definir com es dibuixa el rectangle i dibuixar-lo a partir del seu centre.

Modifiqueu aquest exemple per tal que l’origen de les coordenades quedi al mig del canvas. Feu servir rectMode() per a dibuixar el rectangle.

L’exemple 6-5 genera un efecte curiós. El quadre va rotant contínuament i alhora que movem el ratolí anem dibuixant quadrats. En aquest cas, la variable angle és la que determina l’angle en què es rota el quadrat. Recordeu que, cada cop que s’executa la funció draw(), l’origen de les coordenades torna al seu lloc. Per això necessitem guardar l’angle en una variable.

En aquest cas (i ho veurem en l’exemple següent) és important l’ordre en què cridem les funcions translate() i rotate() perquè, recordeu, dins la funció draw() els canvis de l’origen de les coordenades són acumulatius. Així, quan fem el rotate() en segon terme, el que fem és que el quadrat estigui contínuament rotant, però només es mou quan movem el ratolí.

L’exemple 6-6 canvia l’ordre, primer fa el rotate() i després el translate(). La diferència és notable. Ara el quadrat s’està movent contínuament fent un cercle. I el moviment del ratolí només determina com de gran és el cercle que estem dibuixant.

L’exemple 6-7 pot semblar complex però, a la pràctica, només fa servir funcions que ja hem vist. Defineix tres variables:

var angle = 0.0;
var angleDirection = 1;
var speed = 0.005;

La variable angle determina l’angle que cal fer servir en cada moment. És la mateixa variable (o almenys es fa servir pel mateix motiu) que la que ja hem fet servir en un exemple anterior.

La variable angleDirection es fa servir per a determinar si el braç va en una direcció o en la contrària. Canvia el seu valor a -1 quan arriba al límit determinat.

La variable speed determina la velocitat amb què es mou el braç.

Aquest exemple fa servir la funció strokeWeight() que el que fa és determinar l’amplada de la línia que es dibuixarà a continuació. I l’exemple no té més misteri.

Escala

L’escalat és el canvi de mida d’un objecte. Amb la funció scale() el que podem fer és canviar la mida de les coordenades del canvas, de manera que no hi haurà una correspondència entre els píxels de la pantalla i els píxels del canvas.

Recuperem aquest exemple que hem vist abans. Però hi afegim una nova variable i una nova línia:

var escala = 2;  // Variable afegida

function setup() {
  createCanvas(200, 200);
}

function draw() {
  background(220);
  scale(escala);   // Crida a la funció scale()
  for (var i = 10; i < 400; i += 10){ // Dibuixem les línies
    line(i,0,i,height);   // Verticals
    line(0,i,width,i);    // Horitzontals
  }
  fill(255,0,0);
  rect(20,20,20,20);
  translate(20,20);
  fill(0,255,0);
  rect(20,20,20,20);
  fill(0,0,255);
  translate(20,20);
  rect(20,20,20,20);
}

Varieu el valor de la variable escala i comproveu el que passa. A continuació, teniu tres captures de pantalla amb tres casos diferents:

L’exemple 6-8 també fa un escalat però fa servir el ratolí per a controlar-lo. I l’exemple 6-9 explica què cal fer per a evitar que les línies també es facin més gruixudes.

Amb el nostre exemple i fent aquest canvi (la segona línia és l’afegida):

scale(escala);   // Crida a la funció scale()
strokeWeight(1.0 / escala);
for (var i = 10; i < 400; i += 10){ // Dibuixem les línies

(amb escala = 5)

push() i pop()

Aquestes dues funcions ens permeten guardar la situació actual de l’origen de les coordenades. push() la guarda i pop() la recupera. Pot ser útil en programes molt llargs, amb la finalitat de tornar a una versió anterior de l’origen de les coordenades. L’exemple 6-10 fa una petita demostració.