Sites


Processing

Les transformations

Jusqu'à présent, nous avons dessiné des formes dans la fenêtre de notre application, en nous repérant toujours par rapport au coin supérieur gauche de la fenêtre.

Grâce aux transformations, il va être possible de déplacer cette origine, mais aussi de redéfinir l'orientation des axes et même de changer la graduation de ces axes (on parle de changement d'échelle).

Par défaut, lorsque l'on dessine une forme (dans notre exemple un rectangle), Processing définit le repère suivant :

TG_01

size(200, 200);
noStroke();
fill(0);
rect(0, 0, 100, 100);

Déplacer

Le changement de la position de l'origine se fait par la commande translate(). Nous pouvons nous déplacer sur l'axe x (« horizontalement ») et sur l'axe y (« verticalement ») et nous allons indiquer à translate() de « combien » nous voulons nous déplacer sur chacun des axes. Dans l'exemple suivant, nous déplaçons l'origine de notre repère de 50 pixels en x et de 50 pixels en y. Notons que translate() va seulement affecter les formes géométriques qui sont dessinées après cette instruction.

TG_02

size(200, 200);
noStroke();
fill(0);
translate(50, 50);
rect(0, 0, 100, 100);

Enchaîner les translate() permet d'accumuler les déplacements comme le montre l'exemple suivant.

3_carres_diag.tiff 

size(200,200);

// Noir
fill(0);
translate(20,20);
rect(0,0,40,40);

// Gris
fill(128);
translate(60,60);
rect(0,0,40,40);

// Blanc
fill(255);
translate(60,60);
rect(0,0,40,40);

Tourner

Nous avons pu déplacer l'origine du repère de dessin. Nous allons maintenant appliquer une rotation sur les axes de notre repère. Grâce à la commande rotate(), les axes x et y peuvent changer d'orientation. rotate() prend en paramètre un nombre qui va représenter l'angle de rotation, c'est-à-dire de « combien » nos axes vont tourner par rapport à notre fenêtre. Des valeurs positives indiquent une rotation dans le sens des aiguilles d'une montre.

radian_degrev2

Deux systèmes de mesure existent pour mesurer un angle : les radians et les degrés. Par défaut, Processing travaille en radians, mais pour nous il est d'habitude plus facile de raisonner en degrés. Par exemple, tourner de 180°, c'est faire un demi-tour.

Processing permet de passer de transformer une unité en une autre grâce aux fonctions radians() et degrees().

float d = degrees(PI/4); // transforme des radians en degrés
float r = radians(180.0); // transforme des degrés en radians

Illustrons la fonction rotate() par un exemple simple. Nous allons faire pivoter un carré autour de l'origine.

TG_03
size(200, 200);
noStroke();
fill(0);
rotate(PI/4);
rect(0, 0, 100, 100);

Comme pour translate(), rotate() se place avant les formes géométriques à dessiner. Il est possible de combiner ces changements d'orientations, qui vont s'accumuler.

quad_rotate.tiff
size(200,200);
smooth();
translate(width/2, height/2);
for (int i=0;i<360;i+=30){
  rotate(radians(30));
  quad(0, 0, 30, 15, 70, 60, 20, 60);

}

Mettre à l'échelle

La mise à l'échelle permet de redimensionner les objets par la commande scale(). Cette fonction permet d'agrandir ou de diminuer la taille des formes géométriques. Elle accepte un ou deux paramètres. Par exemple, scale(0.5) va diminuer de moitié la taille des formes géométriques tandis que scale(2.0) va la doubler, scale(1) n'a aucun effet.

L'écriture avec deux paramètres permet de découpler le redimensionnement en x et en y. Par exemple, scale(0.5, 2.0) va écraser la forme sur les x de moitié tandis que sur les y sa taille sera doublée.

TG_04

size(200,200);
scale(1.5);
rect(0,0,100,100);

TG_05

size(200,200);
scale(1.0,1.5);
rect(0,0,100,100);

Comme pour rotate() et translate(), l'enchaînement de scale() permet d'accumuler les mises à l'échelle. Illustrons cette propriété par le sketch suivant, qui reprend l'idée des poupées russes en emboitant des carrés par le jeu des scale() successifs.

3_carre.tiff

size(200,200);
noStroke();

// Noir
fill(0);
scale(1);
rect(0,0,200,200);

// Gris
fill(128);
scale(0.5);
rect(0,0,200,200);

// Blanc
fill(255);
scale(0.5);
rect(0,0,200,200);

L'ordre des transformations

Il est possible de combiner plusieurs types de transformations. Comme nous l'avons vu dans les exemples précédents, les transformations s'accumulent au fur et à mesure des appels successifs à translate(), rotate() ou scale() et chacune des transformations tient compte des transformations précédentes.

Lorsqu'on utilise plusieurs types de transformations, leur ordre d'écriture va être important. Lorsque vous êtes en voiture, « tourner à gauche » puis « continuer tout droit » est différent de « continuer tout droit » puis « tourner à gauche ». Vous n'arrivez pas forcément au même endroit en suivant successivement ces deux instructions. C'est la même chose pour les transformations dans Processing.

Illustrons ceci par un exemple en inversant un translate() et un rotate().

TG_tr_01

size(200,200);
smooth();
fill(0);

translate(100,0);
rotate(PI/5);
rect(0,0,100,100);
TG_tr_02
size(200,200);
smooth();
fill(0);

rotate(PI/5);
translate(100,0);
rect(0,0,100,100);

Isoler les transformations

Nous venons de voir que les transformations s'accumulaient au fur et à mesure de l'utilisation des commandes translate(), rotate() et scale(). Nous allons voir à présent comment sauvegarder les transformations à un moment donné et comment les restaurer ensuite, au cours d'une animation interactive faisant appel à la méthode draw().

Nous allons utiliser pour cela deux fonctions qui s'utilisent toujours par paire : pushMatrix() et popMatrix(). Nous verrons en fin de chapitre pourquoi ces deux fonctions portent un nom si bizarre.

Pour les deux exemples qui suivent, nous allons identifier les transformations suivantes :

  • A : origine en haut à gauche de la fenêtre.
  • B : origine au centre de l'écran.
  • C : origine au centre de l'écran, rotation de PI/4.

push_quad.tiff

size(200,200);
smooth();
rectMode(CENTER);

// Repère au centre de l'écran
translate(width/2,height/2);

// Sauvegarde de A
pushMatrix();

// Transformation B
rotate(PI/4);

// Dessin du carré noir
fill(0);
rect(0,0,120,120);

// Restauration A
// A ce point-là, notre repère revient au centre de l'écran
popMatrix();

// Dessin du carré blanc qui ne tient pas compte de la rotation
fill(255);
rect(0,0,50,50);

Il est possible d'imbriquer les sauvegardes de transformations, c'est-à-dire qu'à l'intérieur de n'importe quelle paire de pushMatrix()/popMatrix() nous pouvons rappeler ces fonctions pour sauver l'état de la transformation courante.

Reprenons l'exemple précédent en plaçant une paire pushMatrix()/popMatrix() qui encadre la première transformation translate(width/2, height/2).

push_pop.tiff

size(200,200);
smooth();
rectMode(CENTER);
noStroke();

// Sauvegarde de A
pushMatrix();

// Transformation B
translate(width/2,height/2);

// Sauvegarde de B
pushMatrix();

// Transformation C
rotate(PI/4);

// Dessin du carré noir
fill(0);
rect(0,0,120,120);

// Restauration de B
popMatrix();

// Dessin du carré blanc qui ne tient pas compte
// de la rotation rotate(PI/4)
fill(255);
rect(0,0,50,50);

// Restauration de A
popMatrix();

// Dessin du carré gris
fill(128);
rect(0,0,100,100);

Transformer en 3D

Toutes les transformations que nous venons d'aborder sont applicables en trois dimensions (3D). Processing permet de passer en 3D au moment de l'appel à size() :

size(300,300,P3D);

Dans ce mode, Processing définit un axe z qui pointe vers le fond de l'écran.

coordonnees3D_02

Les transformations de déplacement et de mise à l'échelle vont s'écrire en intégrant un troisième paramètre. Par exemple, pour se déplacer au centre de l'écran le long de x et y puis dessiner les formes comme si elles étaient éloignées de nous, nous pourrions écrire la ligne de code suivante :

translate(width/2, height/2, -100);

Pour les rotations, nous disposons de trois fonctions : rotateX, rotateY et rotateZ qui permettent respectivement de tourner autour des axes x, y et z.

Processing intègre des fonctions de dessin de formes simples en 3D, notamment les cubes et les sphères. Nous allons créer un cube dont la rotation autour des axes x et y va être paramétré par la position de la souris.

cube3D.tiff

float rx = 0;
float ry = 0;
float z = 100;

void setup() {
  size(200,200,P3D);
}

void draw() {
  background(128);
  rx = map(mouseX, 0,width,-PI,PI);
  ry = map(mouseY, 0,height,-PI,PI);

  translate(width/2,height/2,z);
  rotateX(rx);
  rotateY(ry);
  box(30);
}

Dans ce sketch, nous avons introduit une nouvelle fonction map(), qui permet de transformer une valeur d'une plage de valeurs à une autre plage de valeurs. Voici un exemple simple pour expliquer ce concept :

float v = 100;
float m = map(v,0,200, 0,1); // m vaut 0.5

Dans cet exemple, map()va transformer la valeur 100 qui est dans l'intervalle [0;200] et calculer la valeur équivalente dans l'intervalle [0;1]. La valeur 0.5 est retournée dans m.

Dans notre sketch, cette fonction permet de transformer la valeur de mouseX  dans l'intervalle situé entre 0 et width en une valeur équivalente dans l'intervalle entre -PI et PI.

Les concepts de pushMatrix() et popMatrix() sont aussi applicables en 3D pour sauvegarder et restaurer les transformations. C'est le meilleur moyen pour dessiner des univers en 3D, contenant plusieurs objets en mouvement les uns par rapport aux autres sans avoir recours à des concepts mathématiques complexes.

Toutes les transformations dans Processing sont stockées dans un tableau de 16 nombres qui est appelé matrice ou matrix en anglais. Ces nombres sont directement modifiés par des appels aux fonctions de transformations. Si vous êtes curieux, vous pouvez imprimer ce tableau par la fonction printMatrix().

Il y a une erreur de communication avec le serveur Booktype. Nous ne savons pas actuellement où est le problème.

Vous devriez rafraîchir la page.