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 :
size(200, 200); noStroke(); fill(0); rect(0, 0, 100, 100);
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.
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.
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);
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.
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.
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.
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);
}
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.
size(200,200); scale(1.5); rect(0,0,100,100);
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.
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);
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().
size(200,200); smooth(); fill(0); translate(100,0); rotate(PI/5); rect(0,0,100,100);
size(200,200); smooth(); fill(0); rotate(PI/5); translate(100,0); rect(0,0,100,100);
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 :
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).
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);
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.
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.
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.